Changes in uspace/srv/fs/devfs/devfs_ops.c [0da4e41:1313ee9] in mainline
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/srv/fs/devfs/devfs_ops.c
r0da4e41 r1313ee9 42 42 #include <string.h> 43 43 #include <libfs.h> 44 #include <fibril_sync .h>44 #include <fibril_synch.h> 45 45 #include <adt/hash_table.h> 46 #include <ipc/devmap.h> 46 47 #include <sys/stat.h> 48 #include <libfs.h> 49 #include <assert.h> 47 50 #include "devfs.h" 48 51 #include "devfs_ops.h" 49 52 50 #define PLB_GET_CHAR(pos) (devfs_reg.plb_ro[pos % PLB_SIZE]) 53 typedef struct { 54 devmap_handle_type_t type; 55 dev_handle_t handle; 56 } devfs_node_t; 51 57 52 58 /** Opened devices structure */ … … 91 97 }; 92 98 99 static int devfs_node_get_internal(fs_node_t **rfn, devmap_handle_type_t type, 100 dev_handle_t handle) 101 { 102 devfs_node_t *node = (devfs_node_t *) malloc(sizeof(devfs_node_t)); 103 if (node == NULL) { 104 *rfn = NULL; 105 return ENOMEM; 106 } 107 108 *rfn = (fs_node_t *) malloc(sizeof(fs_node_t)); 109 if (*rfn == NULL) { 110 free(node); 111 *rfn = NULL; 112 return ENOMEM; 113 } 114 115 fs_node_initialize(*rfn); 116 node->type = type; 117 node->handle = handle; 118 119 (*rfn)->data = node; 120 return EOK; 121 } 122 123 static int devfs_root_get(fs_node_t **rfn, dev_handle_t dev_handle) 124 { 125 return devfs_node_get_internal(rfn, DEV_HANDLE_NONE, 0); 126 } 127 128 static int devfs_match(fs_node_t **rfn, fs_node_t *pfn, const char *component) 129 { 130 devfs_node_t *node = (devfs_node_t *) pfn->data; 131 132 if (node->handle == 0) { 133 /* Root directory */ 134 135 dev_desc_t *devs; 136 size_t count = devmap_get_namespaces(&devs); 137 138 if (count > 0) { 139 size_t pos; 140 for (pos = 0; pos < count; pos++) { 141 /* Ignore root namespace */ 142 if (str_cmp(devs[pos].name, "") == 0) 143 continue; 144 145 if (str_cmp(devs[pos].name, component) == 0) { 146 free(devs); 147 return devfs_node_get_internal(rfn, DEV_HANDLE_NAMESPACE, devs[pos].handle); 148 } 149 } 150 151 free(devs); 152 } 153 154 /* Search root namespace */ 155 dev_handle_t namespace; 156 if (devmap_namespace_get_handle("", &namespace, 0) == EOK) { 157 count = devmap_get_devices(namespace, &devs); 158 159 if (count > 0) { 160 size_t pos; 161 for (pos = 0; pos < count; pos++) { 162 if (str_cmp(devs[pos].name, component) == 0) { 163 free(devs); 164 return devfs_node_get_internal(rfn, DEV_HANDLE_DEVICE, devs[pos].handle); 165 } 166 } 167 168 free(devs); 169 } 170 } 171 172 *rfn = NULL; 173 return EOK; 174 } 175 176 if (node->type == DEV_HANDLE_NAMESPACE) { 177 /* Namespace directory */ 178 179 dev_desc_t *devs; 180 size_t count = devmap_get_devices(node->handle, &devs); 181 if (count > 0) { 182 size_t pos; 183 for (pos = 0; pos < count; pos++) { 184 if (str_cmp(devs[pos].name, component) == 0) { 185 free(devs); 186 return devfs_node_get_internal(rfn, DEV_HANDLE_DEVICE, devs[pos].handle); 187 } 188 } 189 190 free(devs); 191 } 192 193 *rfn = NULL; 194 return EOK; 195 } 196 197 *rfn = NULL; 198 return EOK; 199 } 200 201 static int devfs_node_get(fs_node_t **rfn, dev_handle_t dev_handle, fs_index_t index) 202 { 203 return devfs_node_get_internal(rfn, devmap_handle_probe(index), index); 204 } 205 206 static int devfs_node_open(fs_node_t *fn) 207 { 208 devfs_node_t *node = (devfs_node_t *) fn->data; 209 210 if (node->handle == 0) { 211 /* Root directory */ 212 return EOK; 213 } 214 215 devmap_handle_type_t type = devmap_handle_probe(node->handle); 216 217 if (type == DEV_HANDLE_NAMESPACE) { 218 /* Namespace directory */ 219 return EOK; 220 } 221 222 if (type == DEV_HANDLE_DEVICE) { 223 /* Device node */ 224 225 unsigned long key[] = { 226 [DEVICES_KEY_HANDLE] = (unsigned long) node->handle 227 }; 228 229 fibril_mutex_lock(&devices_mutex); 230 link_t *lnk = hash_table_find(&devices, key); 231 if (lnk == NULL) { 232 device_t *dev = (device_t *) malloc(sizeof(device_t)); 233 if (dev == NULL) { 234 fibril_mutex_unlock(&devices_mutex); 235 return ENOMEM; 236 } 237 238 int phone = devmap_device_connect(node->handle, 0); 239 if (phone < 0) { 240 fibril_mutex_unlock(&devices_mutex); 241 free(dev); 242 return ENOENT; 243 } 244 245 dev->handle = node->handle; 246 dev->phone = phone; 247 dev->refcount = 1; 248 249 hash_table_insert(&devices, key, &dev->link); 250 } else { 251 device_t *dev = hash_table_get_instance(lnk, device_t, link); 252 dev->refcount++; 253 } 254 255 fibril_mutex_unlock(&devices_mutex); 256 257 return EOK; 258 } 259 260 return ENOENT; 261 } 262 263 static int devfs_node_put(fs_node_t *fn) 264 { 265 free(fn->data); 266 free(fn); 267 return EOK; 268 } 269 270 static int devfs_create_node(fs_node_t **rfn, dev_handle_t dev_handle, int lflag) 271 { 272 assert((lflag & L_FILE) ^ (lflag & L_DIRECTORY)); 273 274 *rfn = NULL; 275 return ENOTSUP; 276 } 277 278 static int devfs_destroy_node(fs_node_t *fn) 279 { 280 return ENOTSUP; 281 } 282 283 static int devfs_link_node(fs_node_t *pfn, fs_node_t *cfn, const char *nm) 284 { 285 return ENOTSUP; 286 } 287 288 static int devfs_unlink_node(fs_node_t *pfn, fs_node_t *cfn, const char *nm) 289 { 290 return ENOTSUP; 291 } 292 293 static int devfs_has_children(bool *has_children, fs_node_t *fn) 294 { 295 devfs_node_t *node = (devfs_node_t *) fn->data; 296 297 if (node->handle == 0) { 298 size_t count = devmap_count_namespaces(); 299 if (count > 0) { 300 *has_children = true; 301 return EOK; 302 } 303 304 /* Root namespace */ 305 dev_handle_t namespace; 306 if (devmap_namespace_get_handle("", &namespace, 0) == EOK) { 307 count = devmap_count_devices(namespace); 308 if (count > 0) { 309 *has_children = true; 310 return EOK; 311 } 312 } 313 314 *has_children = false; 315 return EOK; 316 } 317 318 if (node->type == DEV_HANDLE_NAMESPACE) { 319 size_t count = devmap_count_devices(node->handle); 320 if (count > 0) { 321 *has_children = true; 322 return EOK; 323 } 324 325 *has_children = false; 326 return EOK; 327 } 328 329 *has_children = false; 330 return EOK; 331 } 332 333 static fs_index_t devfs_index_get(fs_node_t *fn) 334 { 335 devfs_node_t *node = (devfs_node_t *) fn->data; 336 return node->handle; 337 } 338 339 static size_t devfs_size_get(fs_node_t *fn) 340 { 341 return 0; 342 } 343 344 static unsigned int devfs_lnkcnt_get(fs_node_t *fn) 345 { 346 devfs_node_t *node = (devfs_node_t *) fn->data; 347 348 if (node->handle == 0) 349 return 0; 350 351 return 1; 352 } 353 354 static char devfs_plb_get_char(unsigned pos) 355 { 356 return devfs_reg.plb_ro[pos % PLB_SIZE]; 357 } 358 359 static bool devfs_is_directory(fs_node_t *fn) 360 { 361 devfs_node_t *node = (devfs_node_t *) fn->data; 362 363 return ((node->type == DEV_HANDLE_NONE) || (node->type == DEV_HANDLE_NAMESPACE)); 364 } 365 366 static bool devfs_is_file(fs_node_t *fn) 367 { 368 devfs_node_t *node = (devfs_node_t *) fn->data; 369 370 return (node->type == DEV_HANDLE_DEVICE); 371 } 372 373 static dev_handle_t devfs_device_get(fs_node_t *fn) 374 { 375 devfs_node_t *node = (devfs_node_t *) fn->data; 376 377 if (node->type == DEV_HANDLE_DEVICE) 378 return node->handle; 379 380 return 0; 381 } 382 383 /** libfs operations */ 384 libfs_ops_t devfs_libfs_ops = { 385 .root_get = devfs_root_get, 386 .match = devfs_match, 387 .node_get = devfs_node_get, 388 .node_open = devfs_node_open, 389 .node_put = devfs_node_put, 390 .create = devfs_create_node, 391 .destroy = devfs_destroy_node, 392 .link = devfs_link_node, 393 .unlink = devfs_unlink_node, 394 .has_children = devfs_has_children, 395 .index_get = devfs_index_get, 396 .size_get = devfs_size_get, 397 .lnkcnt_get = devfs_lnkcnt_get, 398 .plb_get_char = devfs_plb_get_char, 399 .is_directory = devfs_is_directory, 400 .is_file = devfs_is_file, 401 .device_get = devfs_device_get 402 }; 403 93 404 bool devfs_init(void) 94 405 { … … 105 416 void devfs_mounted(ipc_callid_t rid, ipc_call_t *request) 106 417 { 418 char *opts; 419 107 420 /* Accept the mount options */ 108 ipc_callid_t callid; 109 size_t size; 110 if (!async_data_write_receive(&callid, &size)) { 111 ipc_answer_0(callid, EINVAL); 112 ipc_answer_0(rid, EINVAL); 113 return; 114 } 115 116 char *opts = malloc(size + 1); 117 if (!opts) { 118 ipc_answer_0(callid, ENOMEM); 119 ipc_answer_0(rid, ENOMEM); 120 return; 121 } 122 123 ipcarg_t retval = async_data_write_finalize(callid, opts, size); 421 ipcarg_t retval = async_data_string_receive(&opts, 0); 124 422 if (retval != EOK) { 125 423 ipc_answer_0(rid, retval); 126 free(opts);127 424 return; 128 425 } 129 426 130 427 free(opts); 131 132 428 ipc_answer_3(rid, EOK, 0, 0, 0); 133 429 } … … 135 431 void devfs_mount(ipc_callid_t rid, ipc_call_t *request) 136 432 { 137 ipc_answer_0(rid, ENOTSUP);433 libfs_mount(&devfs_libfs_ops, devfs_reg.fs_handle, rid, request); 138 434 } 139 435 140 436 void devfs_lookup(ipc_callid_t rid, ipc_call_t *request) 141 437 { 142 ipcarg_t first = IPC_GET_ARG1(*request); 143 ipcarg_t last = IPC_GET_ARG2(*request); 144 dev_handle_t dev_handle = IPC_GET_ARG3(*request); 145 ipcarg_t lflag = IPC_GET_ARG4(*request); 146 fs_index_t index = IPC_GET_ARG5(*request); 147 148 /* Hierarchy is flat, no altroot is supported */ 149 if (index != 0) { 150 ipc_answer_0(rid, ENOENT); 151 return; 152 } 153 154 if ((lflag & L_LINK) || (lflag & L_UNLINK)) { 155 ipc_answer_0(rid, ENOTSUP); 156 return; 157 } 158 159 /* Eat slash */ 160 if (PLB_GET_CHAR(first) == '/') { 161 first++; 162 first %= PLB_SIZE; 163 } 164 165 if (first >= last) { 166 /* Root entry */ 167 if (!(lflag & L_FILE)) 168 ipc_answer_5(rid, EOK, devfs_reg.fs_handle, dev_handle, 0, 0, 0); 169 else 170 ipc_answer_0(rid, ENOENT); 171 } else { 172 if (!(lflag & L_DIRECTORY)) { 173 size_t len; 174 if (last >= first) 175 len = last - first + 1; 176 else 177 len = first + PLB_SIZE - last + 1; 438 libfs_lookup(&devfs_libfs_ops, devfs_reg.fs_handle, rid, request); 439 } 440 441 void devfs_open_node(ipc_callid_t rid, ipc_call_t *request) 442 { 443 libfs_open_node(&devfs_libfs_ops, devfs_reg.fs_handle, rid, request); 444 } 445 446 void devfs_stat(ipc_callid_t rid, ipc_call_t *request) 447 { 448 libfs_stat(&devfs_libfs_ops, devfs_reg.fs_handle, rid, request); 449 } 450 451 void devfs_read(ipc_callid_t rid, ipc_call_t *request) 452 { 453 fs_index_t index = (fs_index_t) IPC_GET_ARG2(*request); 454 off_t pos = (off_t) IPC_GET_ARG3(*request); 455 456 if (index == 0) { 457 ipc_callid_t callid; 458 size_t size; 459 if (!async_data_read_receive(&callid, &size)) { 460 ipc_answer_0(callid, EINVAL); 461 ipc_answer_0(rid, EINVAL); 462 return; 463 } 464 465 dev_desc_t *desc; 466 size_t count = devmap_get_namespaces(&desc); 467 468 /* Get rid of root namespace */ 469 size_t i; 470 for (i = 0; i < count; i++) { 471 if (str_cmp(desc[i].name, "") == 0) { 472 if (pos >= i) 473 pos++; 474 475 break; 476 } 477 } 478 479 if (pos < count) { 480 async_data_read_finalize(callid, desc[pos].name, str_size(desc[pos].name) + 1); 481 free(desc); 482 ipc_answer_1(rid, EOK, 1); 483 return; 484 } 485 486 free(desc); 487 pos -= count; 488 489 /* Search root namespace */ 490 dev_handle_t namespace; 491 if (devmap_namespace_get_handle("", &namespace, 0) == EOK) { 492 count = devmap_get_devices(namespace, &desc); 178 493 179 char *name = (char *) malloc(len + 1); 180 if (name == NULL) { 181 ipc_answer_0(rid, ENOMEM); 494 if (pos < count) { 495 async_data_read_finalize(callid, desc[pos].name, str_size(desc[pos].name) + 1); 496 free(desc); 497 ipc_answer_1(rid, EOK, 1); 182 498 return; 183 499 } 184 500 185 size_t i; 186 for (i = 0; i < len; i++) 187 name[i] = PLB_GET_CHAR(first + i); 188 189 name[len] = 0; 190 191 dev_handle_t handle; 192 if (devmap_device_get_handle(name, &handle, 0) != EOK) { 193 free(name); 194 ipc_answer_0(rid, ENOENT); 195 return; 196 } 197 198 if (lflag & L_OPEN) { 199 unsigned long key[] = { 200 [DEVICES_KEY_HANDLE] = (unsigned long) handle 201 }; 202 203 fibril_mutex_lock(&devices_mutex); 204 link_t *lnk = hash_table_find(&devices, key); 205 if (lnk == NULL) { 206 int phone = devmap_device_connect(handle, 0); 207 if (phone < 0) { 208 fibril_mutex_unlock(&devices_mutex); 209 free(name); 210 ipc_answer_0(rid, ENOENT); 211 return; 212 } 213 214 device_t *dev = (device_t *) malloc(sizeof(device_t)); 215 if (dev == NULL) { 216 fibril_mutex_unlock(&devices_mutex); 217 free(name); 218 ipc_answer_0(rid, ENOMEM); 219 return; 220 } 221 222 dev->handle = handle; 223 dev->phone = phone; 224 dev->refcount = 1; 225 226 hash_table_insert(&devices, key, &dev->link); 227 } else { 228 device_t *dev = hash_table_get_instance(lnk, device_t, link); 229 dev->refcount++; 230 } 231 fibril_mutex_unlock(&devices_mutex); 232 } 233 234 free(name); 235 236 ipc_answer_5(rid, EOK, devfs_reg.fs_handle, dev_handle, handle, 0, 1); 237 } else 238 ipc_answer_0(rid, ENOENT); 239 } 240 } 241 242 void devfs_open_node(ipc_callid_t rid, ipc_call_t *request) 243 { 244 dev_handle_t handle = IPC_GET_ARG2(*request); 245 246 unsigned long key[] = { 247 [DEVICES_KEY_HANDLE] = (unsigned long) handle 248 }; 249 250 fibril_mutex_lock(&devices_mutex); 251 link_t *lnk = hash_table_find(&devices, key); 252 if (lnk == NULL) { 253 int phone = devmap_device_connect(handle, 0); 254 if (phone < 0) { 255 fibril_mutex_unlock(&devices_mutex); 256 ipc_answer_0(rid, ENOENT); 257 return; 258 } 259 260 device_t *dev = (device_t *) malloc(sizeof(device_t)); 261 if (dev == NULL) { 262 fibril_mutex_unlock(&devices_mutex); 263 ipc_answer_0(rid, ENOMEM); 264 return; 265 } 266 267 dev->handle = handle; 268 dev->phone = phone; 269 dev->refcount = 1; 270 271 hash_table_insert(&devices, key, &dev->link); 272 } else { 273 device_t *dev = hash_table_get_instance(lnk, device_t, link); 274 dev->refcount++; 275 } 276 fibril_mutex_unlock(&devices_mutex); 277 278 ipc_answer_3(rid, EOK, 0, 1, L_FILE); 279 } 280 281 void devfs_stat(ipc_callid_t rid, ipc_call_t *request) 282 { 283 dev_handle_t dev_handle = (dev_handle_t) IPC_GET_ARG1(*request); 284 fs_index_t index = (fs_index_t) IPC_GET_ARG2(*request); 285 286 ipc_callid_t callid; 287 size_t size; 288 if (!async_data_read_receive(&callid, &size) || 289 size != sizeof(struct stat)) { 290 ipc_answer_0(callid, EINVAL); 291 ipc_answer_0(rid, EINVAL); 292 return; 293 } 294 295 struct stat stat; 296 memset(&stat, 0, sizeof(struct stat)); 297 298 stat.fs_handle = devfs_reg.fs_handle; 299 stat.dev_handle = dev_handle; 300 stat.index = index; 301 stat.lnkcnt = 1; 302 stat.is_file = (index != 0); 303 stat.size = 0; 304 305 if (index != 0) { 306 unsigned long key[] = { 307 [DEVICES_KEY_HANDLE] = (unsigned long) index 308 }; 309 310 fibril_mutex_lock(&devices_mutex); 311 link_t *lnk = hash_table_find(&devices, key); 312 if (lnk != NULL) 313 stat.devfs_stat.device = (dev_handle_t)index; 314 fibril_mutex_unlock(&devices_mutex); 315 } 316 317 async_data_read_finalize(callid, &stat, sizeof(struct stat)); 318 ipc_answer_0(rid, EOK); 319 } 320 321 void devfs_read(ipc_callid_t rid, ipc_call_t *request) 322 { 323 fs_index_t index = (fs_index_t) IPC_GET_ARG2(*request); 324 off_t pos = (off_t) IPC_GET_ARG3(*request); 325 326 if (index != 0) { 501 free(desc); 502 } 503 504 ipc_answer_0(callid, ENOENT); 505 ipc_answer_1(rid, ENOENT, 0); 506 return; 507 } 508 509 devmap_handle_type_t type = devmap_handle_probe(index); 510 511 if (type == DEV_HANDLE_NAMESPACE) { 512 /* Namespace directory */ 513 ipc_callid_t callid; 514 size_t size; 515 if (!async_data_read_receive(&callid, &size)) { 516 ipc_answer_0(callid, EINVAL); 517 ipc_answer_0(rid, EINVAL); 518 return; 519 } 520 521 dev_desc_t *desc; 522 size_t count = devmap_get_devices(index, &desc); 523 524 if (pos < count) { 525 async_data_read_finalize(callid, desc[pos].name, str_size(desc[pos].name) + 1); 526 free(desc); 527 ipc_answer_1(rid, EOK, 1); 528 return; 529 } 530 531 free(desc); 532 ipc_answer_0(callid, ENOENT); 533 ipc_answer_1(rid, ENOENT, 0); 534 return; 535 } 536 537 if (type == DEV_HANDLE_DEVICE) { 538 /* Device node */ 539 327 540 unsigned long key[] = { 328 541 [DEVICES_KEY_HANDLE] = (unsigned long) index … … 364 577 /* Driver reply is the final result of the whole operation */ 365 578 ipc_answer_1(rid, rc, bytes); 366 } else { 367 ipc_callid_t callid; 368 size_t size; 369 if (!async_data_read_receive(&callid, &size)) { 370 ipc_answer_0(callid, EINVAL); 371 ipc_answer_0(rid, EINVAL); 372 return; 373 } 374 375 size_t count = devmap_device_get_count(); 376 dev_desc_t *desc = malloc(count * sizeof(dev_desc_t)); 377 if (desc == NULL) { 378 ipc_answer_0(callid, ENOMEM); 379 ipc_answer_1(rid, ENOMEM, 0); 380 return; 381 } 382 383 size_t max = devmap_device_get_devices(count, desc); 384 385 if (pos < max) { 386 async_data_read_finalize(callid, desc[pos].name, str_size(desc[pos].name) + 1); 387 } else { 388 ipc_answer_0(callid, ENOENT); 389 ipc_answer_1(rid, ENOENT, 0); 390 return; 391 } 392 393 free(desc); 394 395 ipc_answer_1(rid, EOK, 1); 396 } 579 return; 580 } 581 582 ipc_answer_0(rid, ENOENT); 397 583 } 398 584 … … 402 588 off_t pos = (off_t) IPC_GET_ARG3(*request); 403 589 404 if (index != 0) { 590 if (index == 0) { 591 ipc_answer_0(rid, ENOTSUP); 592 return; 593 } 594 595 devmap_handle_type_t type = devmap_handle_probe(index); 596 597 if (type == DEV_HANDLE_NAMESPACE) { 598 /* Namespace directory */ 599 ipc_answer_0(rid, ENOTSUP); 600 return; 601 } 602 603 if (type == DEV_HANDLE_DEVICE) { 604 /* Device node */ 405 605 unsigned long key[] = { 406 606 [DEVICES_KEY_HANDLE] = (unsigned long) index … … 443 643 /* Driver reply is the final result of the whole operation */ 444 644 ipc_answer_1(rid, rc, bytes); 445 } else {446 /* Read-only filesystem */447 ipc_answer_0(rid, ENOTSUP);448 }645 return; 646 } 647 648 ipc_answer_0(rid, ENOENT); 449 649 } 450 650 … … 458 658 fs_index_t index = (fs_index_t) IPC_GET_ARG2(*request); 459 659 460 if (index != 0) { 660 if (index == 0) { 661 ipc_answer_0(rid, EOK); 662 return; 663 } 664 665 devmap_handle_type_t type = devmap_handle_probe(index); 666 667 if (type == DEV_HANDLE_NAMESPACE) { 668 /* Namespace directory */ 669 ipc_answer_0(rid, EOK); 670 return; 671 } 672 673 if (type == DEV_HANDLE_DEVICE) { 461 674 unsigned long key[] = { 462 675 [DEVICES_KEY_HANDLE] = (unsigned long) index … … 482 695 483 696 ipc_answer_0(rid, EOK); 484 } else 485 ipc_answer_0(rid, ENOTSUP); 697 return; 698 } 699 700 ipc_answer_0(rid, ENOENT); 486 701 } 487 702 … … 490 705 fs_index_t index = (fs_index_t) IPC_GET_ARG2(*request); 491 706 492 if (index != 0) { 707 if (index == 0) { 708 ipc_answer_0(rid, EOK); 709 return; 710 } 711 712 devmap_handle_type_t type = devmap_handle_probe(index); 713 714 if (type == DEV_HANDLE_NAMESPACE) { 715 /* Namespace directory */ 716 ipc_answer_0(rid, EOK); 717 return; 718 } 719 720 if (type == DEV_HANDLE_DEVICE) { 493 721 unsigned long key[] = { 494 722 [DEVICES_KEY_HANDLE] = (unsigned long) index … … 518 746 /* Driver reply is the final result of the whole operation */ 519 747 ipc_answer_0(rid, rc); 520 } else 521 ipc_answer_0(rid, ENOTSUP); 748 return; 749 } 750 751 ipc_answer_0(rid, ENOENT); 522 752 } 523 753
Note:
See TracChangeset
for help on using the changeset viewer.