Changeset 2988aec7 in mainline
- Timestamp:
- 2012-08-14T03:17:17Z (13 years ago)
- Branches:
- lfn, master, serial, ticket/834-toolchain-update, topic/msim-upgrade, topic/simplify-dev-export
- Children:
- 71b0d4d4
- Parents:
- 1b6b76d
- Location:
- uspace
- Files:
-
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/app/bithenge/Makefile.linux
r1b6b76d r2988aec7 50 50 51 51 $(BINARY): $(OBJECTS) 52 $(CC) -o $@ $^52 $(CC) $(CFLAGS) -o $@ $^ 53 53 54 54 clean: -
uspace/app/bithenge/blob.c
r1b6b76d r2988aec7 459 459 } 460 460 461 typedef struct { 462 bithenge_blob_t base; 463 bithenge_blob_t *a, *b; 464 aoff64_t a_size; 465 } concat_blob_t; 466 467 static inline concat_blob_t *blob_as_concat(bithenge_blob_t *base) 468 { 469 return (concat_blob_t *)base; 470 } 471 472 static inline bithenge_blob_t *concat_as_blob(concat_blob_t *blob) 473 { 474 return &blob->base; 475 } 476 477 static int concat_blob_size(bithenge_blob_t *base, aoff64_t *size) 478 { 479 concat_blob_t *self = blob_as_concat(base); 480 int rc = bithenge_blob_size(self->b, size); 481 *size += self->a_size; 482 return rc; 483 } 484 485 static int concat_blob_read(bithenge_blob_t *base, aoff64_t offset, 486 char *buffer, aoff64_t *size) 487 { 488 int rc = EOK; 489 concat_blob_t *self = blob_as_concat(base); 490 491 aoff64_t a_size = 0, b_size = 0; 492 if (offset < self->a_size) { 493 a_size = *size; 494 rc = bithenge_blob_read(self->a, offset, buffer, &a_size); 495 if (rc != EOK) 496 return rc; 497 } 498 if (offset + *size > self->a_size) { 499 b_size = *size - a_size; 500 rc = bithenge_blob_read(self->b, 501 offset + a_size - self->a_size, buffer + a_size, &b_size); 502 if (rc != EOK) 503 return rc; 504 } 505 assert(a_size + b_size <= *size); 506 *size = a_size + b_size; 507 return rc; 508 } 509 510 static int concat_blob_read_bits(bithenge_blob_t *base, aoff64_t offset, 511 char *buffer, aoff64_t *size, bool little_endian) 512 { 513 int rc = EOK; 514 concat_blob_t *self = blob_as_concat(base); 515 516 aoff64_t a_size = 0, b_size = 0; 517 if (offset < self->a_size) { 518 a_size = *size; 519 rc = bithenge_blob_read_bits(self->a, offset, buffer, &a_size, 520 little_endian); 521 if (rc != EOK) 522 return rc; 523 } 524 if (offset + *size > self->a_size) { 525 b_size = offset + *size - self->a_size; 526 rc = bithenge_blob_read_bits(self->b, 527 offset + a_size - self->a_size, buffer + a_size, &b_size, 528 little_endian); 529 if (rc != EOK) 530 return rc; 531 } 532 *size = a_size + b_size; 533 return rc; 534 } 535 536 static void concat_blob_destroy(bithenge_blob_t *base) 537 { 538 concat_blob_t *self = blob_as_concat(base); 539 bithenge_blob_dec_ref(self->a); 540 bithenge_blob_dec_ref(self->b); 541 free(self); 542 } 543 544 static const bithenge_random_access_blob_ops_t concat_blob_ops = { 545 .size = concat_blob_size, 546 .read = concat_blob_read, 547 .read_bits = concat_blob_read_bits, 548 .destroy = concat_blob_destroy, 549 }; 550 551 /** Create a concatenated blob. Takes references to @a a and @a b. 552 * @param[out] out Holds the new blob. 553 * @param a The first blob. 554 * @param b The second blob. 555 * @return EOK on success or an error code from errno.h. */ 556 int bithenge_concat_blob(bithenge_node_t **out, bithenge_blob_t *a, 557 bithenge_blob_t *b) 558 { 559 assert(out); 560 assert(a); 561 assert(b); 562 int rc; 563 concat_blob_t *self = malloc(sizeof(*self)); 564 if (!self) { 565 rc = ENOMEM; 566 goto error; 567 } 568 569 rc = bithenge_blob_size(a, &self->a_size); 570 if (rc != EOK) 571 goto error; 572 573 rc = bithenge_init_random_access_blob(concat_as_blob(self), 574 &concat_blob_ops); 575 if (rc != EOK) 576 goto error; 577 self->a = a; 578 self->b = b; 579 *out = bithenge_blob_as_node(concat_as_blob(self)); 580 return EOK; 581 582 error: 583 bithenge_blob_dec_ref(a); 584 bithenge_blob_dec_ref(b); 585 free(self); 586 return rc; 587 } 588 461 589 /** Check whether the contents of two blobs are equal. 462 590 * @memberof bithenge_blob_t -
uspace/app/bithenge/blob.h
r1b6b76d r2988aec7 243 243 int bithenge_new_subblob(bithenge_node_t **, bithenge_blob_t *, aoff64_t, 244 244 aoff64_t); 245 int bithenge_concat_blob(bithenge_node_t **, bithenge_blob_t *, 246 bithenge_blob_t *); 245 247 bool bithenge_blob_equal(bithenge_blob_t *, bithenge_blob_t *); 246 248 -
uspace/app/bithenge/expression.c
r1b6b76d r2988aec7 101 101 /* Check types and get values. */ 102 102 bithenge_int_t a_int = 0, b_int = 0; 103 bool a_bool = false, b_bool = false; 103 104 switch (self->op) { 104 105 case BITHENGE_EXPRESSION_ADD: /* fallthrough */ … … 119 120 b_int = bithenge_integer_node_value(b); 120 121 break; 122 case BITHENGE_EXPRESSION_AND: /* fallthrough */ 123 case BITHENGE_EXPRESSION_OR: 124 rc = EINVAL; 125 if (bithenge_node_type(a) != BITHENGE_NODE_BOOLEAN) 126 goto error; 127 if (bithenge_node_type(b) != BITHENGE_NODE_BOOLEAN) 128 goto error; 129 a_bool = bithenge_boolean_node_value(a); 130 b_bool = bithenge_boolean_node_value(b); 131 break; 132 case BITHENGE_EXPRESSION_CONCAT: 133 if (bithenge_node_type(a) != BITHENGE_NODE_BLOB) 134 goto error; 135 if (bithenge_node_type(b) != BITHENGE_NODE_BLOB) 136 goto error; 137 break; 121 138 default: 122 139 break; … … 135 152 case BITHENGE_EXPRESSION_INTEGER_DIVIDE: 136 153 /* Integer division can behave in three major ways when the 137 *operands are signed: truncated, floored, or Euclidean. When154 operands are signed: truncated, floored, or Euclidean. When 138 155 * b > 0, we give the same result as floored and Euclidean; 139 156 * otherwise, we currently raise an error. See … … 173 190 case BITHENGE_EXPRESSION_NOT_EQUALS: 174 191 rc = bithenge_new_boolean_node(out, 175 ~bithenge_node_equal(a, b)); 192 !bithenge_node_equal(a, b)); 193 break; 194 case BITHENGE_EXPRESSION_AND: 195 rc = bithenge_new_boolean_node(out, a_bool && b_bool); 196 break; 197 case BITHENGE_EXPRESSION_OR: 198 rc = bithenge_new_boolean_node(out, a_bool || b_bool); 199 break; 200 case BITHENGE_EXPRESSION_MEMBER: 201 rc = bithenge_node_get(a, b, out); 202 b = NULL; 203 break; 204 case BITHENGE_EXPRESSION_CONCAT: 205 rc = bithenge_concat_blob(out, bithenge_node_as_blob(a), 206 bithenge_node_as_blob(b)); 207 a = NULL; 208 b = NULL; 176 209 break; 177 210 case BITHENGE_EXPRESSION_INVALID_BINARY_OP: … … 430 463 free(self); 431 464 bithenge_node_dec_ref(node); 432 return rc;433 }434 435 436 437 /***************** member_expression *****************/438 439 typedef struct {440 bithenge_expression_t base;441 bithenge_expression_t *expr;442 bithenge_node_t *key;443 } member_expression_t;444 445 static member_expression_t *expression_as_member(bithenge_expression_t *base)446 {447 return (member_expression_t *)base;448 }449 450 static bithenge_expression_t *member_as_expression(member_expression_t *expr)451 {452 return &expr->base;453 }454 455 static int member_expression_evaluate(bithenge_expression_t *base,456 bithenge_scope_t *scope, bithenge_node_t **out)457 {458 member_expression_t *self = expression_as_member(base);459 bithenge_node_t *node;460 int rc = bithenge_expression_evaluate(self->expr, scope, &node);461 if (rc != EOK)462 return rc;463 bithenge_node_inc_ref(self->key);464 rc = bithenge_node_get(node, self->key, out);465 bithenge_node_dec_ref(node);466 if (rc == ENOENT)467 return bithenge_scope_error(scope, "No member %t", self->key);468 return rc;469 }470 471 static void member_expression_destroy(bithenge_expression_t *base)472 {473 member_expression_t *self = expression_as_member(base);474 bithenge_expression_dec_ref(self->expr);475 bithenge_node_dec_ref(self->key);476 free(self);477 }478 479 static const bithenge_expression_ops_t member_expression_ops = {480 .evaluate = member_expression_evaluate,481 .destroy = member_expression_destroy,482 };483 484 /** Create an expression that gets a member from a node. Takes references to485 * @a expr and @a key.486 * @param[out] out Holds the new expression.487 * @param expr Calculates the node to get the member of.488 * @param key The member to get.489 * @return EOK on success or an error code from errno.h. */490 int bithenge_member_expression(bithenge_expression_t **out,491 bithenge_expression_t *expr, bithenge_node_t *key)492 {493 int rc;494 member_expression_t *self = malloc(sizeof(*self));495 if (!self) {496 rc = ENOMEM;497 goto error;498 }499 500 rc = bithenge_init_expression(member_as_expression(self),501 &member_expression_ops);502 if (rc != EOK)503 goto error;504 505 self->expr = expr;506 self->key = key;507 *out = member_as_expression(self);508 return EOK;509 510 error:511 bithenge_expression_dec_ref(expr);512 bithenge_node_dec_ref(key);513 free(self);514 465 return rc; 515 466 } -
uspace/app/bithenge/expression.h
r1b6b76d r2988aec7 93 93 typedef enum { 94 94 BITHENGE_EXPRESSION_INVALID_BINARY_OP, 95 95 96 BITHENGE_EXPRESSION_ADD, 96 97 BITHENGE_EXPRESSION_SUBTRACT, … … 98 99 BITHENGE_EXPRESSION_INTEGER_DIVIDE, 99 100 BITHENGE_EXPRESSION_MODULO, 101 100 102 BITHENGE_EXPRESSION_LESS_THAN, 101 103 BITHENGE_EXPRESSION_GREATER_THAN, … … 104 106 BITHENGE_EXPRESSION_EQUALS, 105 107 BITHENGE_EXPRESSION_NOT_EQUALS, 108 109 BITHENGE_EXPRESSION_AND, 110 BITHENGE_EXPRESSION_OR, 111 112 BITHENGE_EXPRESSION_MEMBER, 113 BITHENGE_EXPRESSION_CONCAT, 106 114 } bithenge_binary_op_t; 107 115 … … 114 122 int bithenge_param_expression(bithenge_expression_t **, int); 115 123 int bithenge_const_expression(bithenge_expression_t **, bithenge_node_t *); 116 int bithenge_member_expression(bithenge_expression_t **,117 bithenge_expression_t *, bithenge_node_t *);118 124 int bithenge_scope_member_expression(bithenge_expression_t **, 119 125 bithenge_node_t *); -
uspace/app/bithenge/print.c
r1b6b76d r2988aec7 181 181 bithenge_blob_t *blob = bithenge_node_as_blob(node); 182 182 aoff64_t pos = 0; 183 charbuffer[1024];183 uint8_t buffer[1024]; 184 184 aoff64_t size = sizeof(buffer); 185 185 int rc; … … 187 187 state->type == BITHENGE_PRINT_PYTHON ? "b\"" : "\""); 188 188 do { 189 rc = bithenge_blob_read(blob, pos, buffer, &size);189 rc = bithenge_blob_read(blob, pos, (char *)buffer, &size); 190 190 if (rc != EOK) 191 191 return rc; 192 192 for (aoff64_t i = 0; i < size; i++) 193 193 state_printf(state, "\\x%02x", 194 (unsigned int) (uint8_t)buffer[i]);194 (unsigned int)buffer[i]); 195 195 pos += size; 196 196 } while (size == sizeof(buffer)); -
uspace/app/bithenge/script.c
r1b6b76d r2988aec7 53 53 * other token uses one of these values: */ 54 54 typedef enum { 55 TOKEN_EQUALS = -128, 56 TOKEN_ERROR, 55 TOKEN_ERROR = -128, 56 57 TOKEN_AND, 58 TOKEN_CONCAT, 59 TOKEN_EQUALS, 57 60 TOKEN_EOF, 58 61 TOKEN_GREATER_THAN_OR_EQUAL, … … 63 66 TOKEN_LESS_THAN_OR_EQUAL, 64 67 TOKEN_NOT_EQUAL, 68 TOKEN_OR, 65 69 66 70 /* Keywords */ … … 308 312 state->buffer_pos++; 309 313 } 314 } else if (ch == '&') { 315 state->token = ch; 316 state->buffer_pos++; 317 if (state->buffer[state->buffer_pos] == '&') { 318 state->token = TOKEN_AND; 319 state->buffer_pos++; 320 } 321 } else if (ch == '|') { 322 state->token = ch; 323 state->buffer_pos++; 324 if (state->buffer[state->buffer_pos] == '|') { 325 state->token = TOKEN_OR; 326 state->buffer_pos++; 327 } 328 } else if (ch == '+') { 329 state->token = ch; 330 state->buffer_pos++; 331 if (state->buffer[state->buffer_pos] == '+') { 332 state->token = TOKEN_CONCAT; 333 state->buffer_pos++; 334 } 310 335 } else { 311 336 state->token = ch; … … 415 440 typedef enum { 416 441 PRECEDENCE_NONE, 442 PRECEDENCE_AND, 417 443 PRECEDENCE_EQUALS, 418 444 PRECEDENCE_COMPARE, … … 446 472 case TOKEN_NOT_EQUAL: 447 473 return BITHENGE_EXPRESSION_NOT_EQUALS; 474 case TOKEN_AND: 475 return BITHENGE_EXPRESSION_AND; 476 case TOKEN_OR: 477 return BITHENGE_EXPRESSION_OR; 478 case TOKEN_CONCAT: 479 return BITHENGE_EXPRESSION_CONCAT; 448 480 default: 449 481 return BITHENGE_EXPRESSION_INVALID_BINARY_OP; … … 455 487 switch (op) { 456 488 case BITHENGE_EXPRESSION_ADD: /* fallthrough */ 457 case BITHENGE_EXPRESSION_SUBTRACT: 489 case BITHENGE_EXPRESSION_SUBTRACT: /* fallthrough */ 490 case BITHENGE_EXPRESSION_CONCAT: 458 491 return PRECEDENCE_ADD; 459 492 case BITHENGE_EXPRESSION_MULTIPLY: /* fallthrough */ … … 469 502 case BITHENGE_EXPRESSION_NOT_EQUALS: 470 503 return PRECEDENCE_EQUALS; 504 case BITHENGE_EXPRESSION_AND: /* fallthrough */ 505 case BITHENGE_EXPRESSION_OR: 506 return PRECEDENCE_AND; 471 507 default: 472 508 assert(false); … … 609 645 } 610 646 611 rc = bithenge_member_expression(&expr, expr, key); 647 bithenge_expression_t *key_expr; 648 rc = bithenge_const_expression(&key_expr, key); 649 if (rc != EOK) { 650 error_errno(state, rc); 651 bithenge_expression_dec_ref(expr); 652 return NULL; 653 } 654 655 rc = bithenge_binary_expression(&expr, 656 BITHENGE_EXPRESSION_MEMBER, expr, key_expr); 612 657 if (rc != EOK) { 613 658 error_errno(state, rc); … … 618 663 bithenge_expression_t *start = parse_expression(state); 619 664 bool absolute_limit = false; 620 if (state->token == ',' ) {621 absolute_limit = false;665 if (state->token == ',' || state->token == ':') { 666 absolute_limit = state->token == ':'; 622 667 next_token(state); 623 } else if (state->token == ':') { 624 absolute_limit = true; 668 bithenge_expression_t *limit = NULL; 669 if (!(state->token == ']' && absolute_limit)) 670 limit = parse_expression(state); 671 expect(state, ']'); 672 673 if (state->error != EOK) { 674 bithenge_expression_dec_ref(expr); 675 bithenge_expression_dec_ref(start); 676 bithenge_expression_dec_ref(limit); 677 return NULL; 678 } 679 rc = bithenge_subblob_expression(&expr, expr, start, 680 limit, absolute_limit); 681 if (rc != EOK) { 682 error_errno(state, rc); 683 return NULL; 684 } 685 } else if (state->token == ']') { 625 686 next_token(state); 687 688 if (state->error != EOK) { 689 bithenge_expression_dec_ref(expr); 690 bithenge_expression_dec_ref(start); 691 return NULL; 692 } 693 rc = bithenge_binary_expression(&expr, 694 BITHENGE_EXPRESSION_MEMBER, expr, start); 695 if (rc != EOK) { 696 error_errno(state, rc); 697 return NULL; 698 } 626 699 } else { 627 syntax_error(state, "expected ',' or ':'"); 628 } 629 bithenge_expression_t *limit = NULL; 630 if (!(state->token == ']' && absolute_limit)) 631 limit = parse_expression(state); 632 expect(state, ']'); 633 634 if (state->error != EOK) { 700 syntax_error(state, "expected ',', ':', or ']'"); 635 701 bithenge_expression_dec_ref(expr); 636 702 bithenge_expression_dec_ref(start); 637 bithenge_expression_dec_ref(limit);638 return NULL;639 }640 rc = bithenge_subblob_expression(&expr, expr, start,641 limit, absolute_limit);642 if (rc != EOK) {643 error_errno(state, rc);644 703 return NULL; 645 704 } … … 1173 1232 } 1174 1233 1234 bithenge_transform_t *barrier = NULL; 1235 if (state->error == EOK) { 1236 int rc = bithenge_new_barrier_transform(&barrier, 1237 state->num_params); 1238 if (rc != EOK) { 1239 barrier = NULL; 1240 error_errno(state, rc); 1241 } 1242 } 1243 1244 add_named_transform(state, barrier, name); 1245 1175 1246 expect(state, '='); 1176 1247 bithenge_transform_t *xform = parse_transform(state); … … 1178 1249 1179 1250 if (state->error == EOK) { 1180 int rc = bithenge_ new_barrier_transform(&xform, xform,1181 state->num_params);1182 if (rc != EOK) {1183 xform = NULL;1184 error_errno(state, rc); 1185 1186 } 1187 1188 add_named_transform(state, xform, name);1251 int rc = bithenge_barrier_transform_set_subtransform(barrier, 1252 xform); 1253 xform = NULL; 1254 if (rc != EOK) 1255 error_errno(state, rc); 1256 } 1257 1258 if (state->error != EOK) 1259 bithenge_transform_dec_ref(xform); 1189 1260 1190 1261 for (int i = 0; i < state->num_params; i++) -
uspace/app/bithenge/sequence.c
r1b6b76d r2988aec7 619 619 /***************** bithenge_repeat_transform *****************/ 620 620 621 /* TODO: ignore errors */ 622 621 623 typedef struct { 622 624 bithenge_transform_t base; -
uspace/app/bithenge/transform.c
r1b6b76d r2988aec7 477 477 }; 478 478 479 /** Set the subtransform of a barrier transform. This must be done before the 480 * barrier transform is used. Takes a reference to @a transform. 481 * @param base The barrier transform. 482 * @param transform The subtransform to use for all operations. 483 * @return EOK on success or an error code from errno.h. */ 484 int bithenge_barrier_transform_set_subtransform(bithenge_transform_t *base, 485 bithenge_transform_t *transform) 486 { 487 assert(transform); 488 assert(bithenge_transform_num_params(transform) == 0); 489 490 barrier_transform_t *self = transform_as_barrier(base); 491 assert(!self->transform); 492 self->transform = transform; 493 return EOK; 494 } 495 479 496 /** Create a wrapper transform that creates a new scope. This ensures nothing 480 497 * from the outer scope is passed in, other than parameters. The wrapper may 481 * have a different value for num_params. T akes a reference to @a transform,482 * which it will use for all operations.498 * have a different value for num_params. The subtransform must be set with @a 499 * bithenge_barrier_transform_set_subtransform before the result is used. 483 500 * @param[out] out Holds the created transform. 484 * @param transform The transform to wrap.485 501 * @param num_params The number of parameters to require, which may be 0. 486 502 * @return EOK on success or an error code from errno.h. */ 487 int bithenge_new_barrier_transform(bithenge_transform_t **out, 488 bithenge_transform_t *transform, int num_params) 489 { 490 assert(transform); 491 assert(bithenge_transform_num_params(transform) == 0); 492 503 int bithenge_new_barrier_transform(bithenge_transform_t **out, int num_params) 504 { 493 505 int rc; 494 506 barrier_transform_t *self = malloc(sizeof(*self)); … … 501 513 if (rc != EOK) 502 514 goto error; 503 self->transform = transform;515 self->transform = NULL; 504 516 *out = barrier_as_transform(self); 505 517 return EOK; 506 518 error: 507 bithenge_transform_dec_ref(transform);508 519 free(self); 509 520 return rc; -
uspace/app/bithenge/transform.h
r1b6b76d r2988aec7 147 147 int bithenge_transform_prefix_apply(bithenge_transform_t *, bithenge_scope_t *, 148 148 bithenge_blob_t *, bithenge_node_t **, aoff64_t *); 149 int bithenge_new_barrier_transform(bithenge_transform_t **, 150 bithenge_transform_t *, int); 149 int bithenge_new_barrier_transform(bithenge_transform_t **, int); 150 int bithenge_barrier_transform_set_subtransform(bithenge_transform_t *, 151 bithenge_transform_t *); 151 152 152 153 int bithenge_scope_new(bithenge_scope_t **, bithenge_scope_t *); -
uspace/dist/src/bithenge/fat.bh
r1b6b76d r2988aec7 44 44 } <- bits_le <- known_length(1); 45 45 46 transform fat_dir_entry(disk) = struct { 46 transform file_data(data, bits, fat, cluster_size, start) = (in.data) <- struct { 47 .cluster <- (data[(start-2)*cluster_size, cluster_size]); 48 .last_cluster_number <- switch (bits) { 49 12: (488); # 0x00000ff8 50 16: (65528); # 0x0000fff8 51 32: (268435448); # 0x0ffffff8 52 }; 53 .next <- (fat[start]); 54 if (.next == 0 || .next >= .last_cluster_number) { 55 .data <- (.cluster); 56 } else { 57 .rest <- file_data(data, bits, fat, cluster_size, .next); 58 .data <- (.cluster ++ .rest); 59 } 60 }; 61 62 transform fat_dir_entry(data, bits, fat, cluster_size, self_start, parent) = struct { 47 63 .filename <- known_length(8); 48 64 .extension <- known_length(3); … … 58 74 .start <- u16; 59 75 .size <- u32; 76 .size_shown <- if (.size > 32) { (32) } else { (.size) }; 77 78 if (.start != 0 && .start != self_start && .start != parent) { 79 .data 80 <- if (.attrs.subdirectory) { 81 repeat { fat_dir_entry(data, bits, fat, cluster_size, .start, self_start) } 82 } else { 83 (in[0,.size_shown]) 84 } 85 <- if (.size != 0) { (in[0,.size]) } else { (in) } 86 <- file_data(data, bits, fat, cluster_size, .start); 87 } 60 88 }; 61 89 … … 112 140 }; 113 141 142 .cluster_size <- (.super.sectors_per_cluster * .super.bytes_per_sector); 114 143 .first_root_sector <- (.super.num_reserved_sectors + .super.num_fats * .super.sectors_per_fat); 115 144 .first_data_sector <- (.first_root_sector + … … 128 157 129 158 .root <- partial(.first_root_sector * .super.bytes_per_sector) { 130 repeat(.super.num_root_entries) { fat_dir_entry(disk) } } <- (disk); 159 repeat(.super.num_root_entries) { 160 fat_dir_entry(disk[.first_data_sector * .super.bytes_per_sector:], 161 .bits, .fats[0], .cluster_size, 0, 0) 162 } 163 } <- (disk); 131 164 }; 132 165
Note:
See TracChangeset
for help on using the changeset viewer.