1 /* 2 * Copyright 1999, Be Incorporated. All Rights Reserved. 3 * This file may be used under the terms of the Be Sample Code License. 4 * 5 * Copyright 2001, pinc Software. All Rights Reserved. 6 * 7 * iso9960/multi-session, 1.0.0 8 */ 9 10 11 #include <ctype.h> 12 13 #ifndef FS_SHELL 14 # include <dirent.h> 15 # include <errno.h> 16 # include <fcntl.h> 17 # include <stdio.h> 18 # include <stdlib.h> 19 # include <string.h> 20 # include <sys/stat.h> 21 # include <time.h> 22 # include <unistd.h> 23 24 # include <KernelExport.h> 25 # include <NodeMonitor.h> 26 # include <fs_interface.h> 27 # include <fs_cache.h> 28 29 # include <fs_attr.h> 30 # include <fs_info.h> 31 # include <fs_index.h> 32 # include <fs_query.h> 33 # include <fs_volume.h> 34 35 # include <util/kernel_cpp.h> 36 #endif 37 38 #include "iso9660.h" 39 #include "iso9660_identify.h" 40 41 // TODO: temporary solution as long as there is no public I/O requests API 42 #include <io_requests.h> 43 44 45 //#define TRACE_ISO9660 46 #ifdef TRACE_ISO9660 47 # define TRACE(x) dprintf x 48 #else 49 # define TRACE(x) ; 50 #endif 51 52 53 struct identify_cookie { 54 iso9660_info info; 55 }; 56 57 extern fs_volume_ops gISO9660VolumeOps; 58 extern fs_vnode_ops gISO9660VnodeOps; 59 60 61 //! fs_io() callback hook 62 static status_t 63 iterative_io_get_vecs_hook(void* cookie, io_request* request, off_t offset, 64 size_t size, struct file_io_vec* vecs, size_t* _count) 65 { 66 iso9660_inode* node = (iso9660_inode*)cookie; 67 68 vecs->offset = offset + ((off_t)node->startLBN[FS_DATA_FORMAT] 69 * (off_t)node->volume->logicalBlkSize[FS_DATA_FORMAT]); 70 vecs->length = size; 71 72 *_count = 1; 73 74 return B_OK; 75 } 76 77 78 //! fs_io() callback hook 79 static status_t 80 iterative_io_finished_hook(void* cookie, io_request* request, status_t status, 81 bool partialTransfer, size_t bytesTransferred) 82 { 83 // nothing to do here... 84 return B_OK; 85 } 86 87 88 // #pragma mark - Scanning 89 90 91 static float 92 fs_identify_partition(int fd, partition_data* partition, void** _cookie) 93 { 94 iso9660_info* info = new iso9660_info; 95 96 status_t status = iso9660_fs_identify(fd, info); 97 if (status != B_OK) { 98 delete info; 99 return -1; 100 } 101 102 *_cookie = info; 103 return 0.6f; 104 } 105 106 107 static status_t 108 fs_scan_partition(int fd, partition_data* partition, void* _cookie) 109 { 110 iso9660_info* info = (iso9660_info*)_cookie; 111 112 partition->status = B_PARTITION_VALID; 113 partition->flags |= B_PARTITION_FILE_SYSTEM | B_PARTITION_READ_ONLY ; 114 partition->block_size = ISO_PVD_SIZE; 115 partition->content_size = ISO_PVD_SIZE * info->max_blocks; 116 partition->content_name = strdup(info->PreferredName()); 117 118 if (partition->content_name == NULL) 119 return B_NO_MEMORY; 120 121 return B_OK; 122 } 123 124 125 static void 126 fs_free_identify_partition_cookie(partition_data* partition, void* _cookie) 127 { 128 delete (iso9660_info*)_cookie; 129 } 130 131 132 // #pragma mark - FS hooks 133 134 135 static status_t 136 fs_mount(fs_volume* _volume, const char* device, uint32 flags, 137 const char* args, ino_t* _rootID) 138 { 139 bool allowJoliet = true; 140 iso9660_volume* volume; 141 142 // Check for a 'nojoliet' parm 143 // all we check for is the existance of 'nojoliet' in the parms. 144 if (args != NULL) { 145 uint32 i; 146 char* spot; 147 char* buf = strdup(args); 148 149 uint32 len = strlen(buf); 150 // lower case the parms data 151 for (i = 0; i < len + 1; i++) 152 buf[i] = tolower(buf[i]); 153 154 // look for nojoliet 155 spot = strstr(buf, "nojoliet"); 156 if (spot != NULL) 157 allowJoliet = false; 158 159 free(buf); 160 } 161 162 // Try and mount volume as an ISO volume. 163 status_t result = ISOMount(device, O_RDONLY, &volume, allowJoliet); 164 if (result == B_OK) { 165 *_rootID = ISO_ROOTNODE_ID; 166 167 _volume->private_volume = volume; 168 _volume->ops = &gISO9660VolumeOps; 169 volume->volume = _volume; 170 volume->id = _volume->id; 171 172 result = publish_vnode(_volume, *_rootID, &volume->rootDirRec, 173 &gISO9660VnodeOps, 174 volume->rootDirRec.attr.stat[FS_DATA_FORMAT].st_mode, 0); 175 if (result != B_OK) { 176 block_cache_delete(volume->fBlockCache, false); 177 free(volume); 178 result = B_ERROR; 179 } 180 } 181 return result; 182 } 183 184 185 static status_t 186 fs_unmount(fs_volume* _volume) 187 { 188 status_t result = B_NO_ERROR; 189 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 190 191 TRACE(("fs_unmount - ENTER\n")); 192 193 // Unlike in BeOS, we need to put the reference to our root node ourselves 194 put_vnode(_volume, ISO_ROOTNODE_ID); 195 196 block_cache_delete(volume->fBlockCache, false); 197 close(volume->fdOfSession); 198 result = close(volume->fd); 199 200 free(volume); 201 202 TRACE(("fs_unmount - EXIT, result is %s\n", strerror(result))); 203 return result; 204 } 205 206 207 static status_t 208 fs_read_fs_stat(fs_volume* _volume, struct fs_info* info) 209 { 210 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 211 212 info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY; 213 info->block_size = volume->logicalBlkSize[FS_DATA_FORMAT]; 214 info->io_size = 65536; 215 info->total_blocks = volume->volSpaceSize[FS_DATA_FORMAT]; 216 info->free_blocks = 0; 217 218 strlcpy(info->device_name, volume->devicePath, sizeof(info->device_name)); 219 strlcpy(info->volume_name, volume->volIDString, sizeof(info->volume_name)); 220 221 // strip trailing spaces 222 int i; 223 for (i = strlen(info->volume_name) - 1; i >=0 ; i--) { 224 if (info->volume_name[i] != ' ') 225 break; 226 } 227 228 if (i < 0) 229 strcpy(info->volume_name, "UNKNOWN"); 230 else 231 info->volume_name[i + 1] = 0; 232 233 strcpy(info->fsh_name, "iso9660"); 234 return B_OK; 235 } 236 237 238 static status_t 239 fs_get_vnode_name(fs_volume* _volume, fs_vnode* _node, char* buffer, 240 size_t bufferSize) 241 { 242 iso9660_inode* node = (iso9660_inode*)_node->private_node; 243 244 strlcpy(buffer, node->name, bufferSize); 245 return B_OK; 246 } 247 248 249 static status_t 250 fs_walk(fs_volume* _volume, fs_vnode* _base, const char* file, ino_t* _vnodeID) 251 { 252 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 253 iso9660_inode* baseNode = (iso9660_inode*)_base->private_node; 254 iso9660_inode* newNode = NULL; 255 256 TRACE(("fs_walk - looking for %s in dir file of length %d\n", file, 257 (int)baseNode->dataLen[FS_DATA_FORMAT])); 258 259 if (strcmp(file, ".") == 0) { 260 // base directory 261 TRACE(("fs_walk - found \".\" file.\n")); 262 *_vnodeID = baseNode->id; 263 return get_vnode(_volume, *_vnodeID, NULL); 264 } else if (strcmp(file, "..") == 0) { 265 // parent directory 266 TRACE(("fs_walk - found \"..\" file.\n")); 267 *_vnodeID = baseNode->parID; 268 return get_vnode(_volume, *_vnodeID, NULL); 269 } 270 271 // look up file in the directory 272 uint32 dataLength = baseNode->dataLen[FS_DATA_FORMAT]; 273 status_t result = ENOENT; 274 size_t totalRead = 0; 275 off_t block = baseNode->startLBN[FS_DATA_FORMAT]; 276 bool done = false; 277 278 while (totalRead < dataLength && !done) { 279 off_t cachedBlock = block; 280 char* blockData = (char*)block_cache_get(volume->fBlockCache, block); 281 if (blockData == NULL) 282 break; 283 284 size_t bytesRead = 0; 285 off_t blockBytesRead = 0; 286 iso9660_inode node; 287 int initResult; 288 289 TRACE(("fs_walk - read buffer from disk at LBN %Ld into buffer " 290 "%p.\n", block, blockData)); 291 292 // Move to the next block if necessary 293 // Don't go over end of buffer, if dir record sits on boundary. 294 295 while (blockBytesRead < volume->logicalBlkSize[FS_DATA_FORMAT] 296 && totalRead + blockBytesRead < dataLength 297 && blockData[0] != 0 298 && !done) { 299 initResult = InitNode(volume, &node, blockData, &bytesRead); 300 TRACE(("fs_walk - InitNode returned %s, filename %s, %u bytes " 301 "read\n", strerror(initResult), node.name, (unsigned)bytesRead)); 302 303 if (initResult == B_OK) { 304 if ((node.flags & ISO_IS_ASSOCIATED_FILE) == 0 305 && !strcmp(node.name, file)) { 306 TRACE(("fs_walk - success, found vnode at block %Ld, pos " 307 "%Ld\n", block, blockBytesRead)); 308 309 *_vnodeID = (block << 30) + (blockBytesRead & 0xffffffff); 310 TRACE(("fs_walk - New vnode id is %Ld\n", *_vnodeID)); 311 312 result = get_vnode(_volume, *_vnodeID, (void**)&newNode); 313 if (result == B_OK) { 314 newNode->parID = baseNode->id; 315 done = true; 316 } 317 } else { 318 free(node.name); 319 free(node.attr.slName); 320 } 321 } else { 322 result = initResult; 323 if (bytesRead == 0) 324 done = true; 325 } 326 blockData += bytesRead; 327 blockBytesRead += bytesRead; 328 329 TRACE(("fs_walk - Adding %u bytes to blockBytes read (total " 330 "%Ld/%u).\n", (unsigned)bytesRead, blockBytesRead, 331 (unsigned)baseNode->dataLen[FS_DATA_FORMAT])); 332 } 333 totalRead += volume->logicalBlkSize[FS_DATA_FORMAT]; 334 block++; 335 336 TRACE(("fs_walk - moving to next block %Ld, total read %u\n", 337 block, (unsigned)totalRead)); 338 block_cache_put(volume->fBlockCache, cachedBlock); 339 } 340 341 TRACE(("fs_walk - EXIT, result is %s, vnid is %Lu\n", 342 strerror(result), *_vnodeID)); 343 return result; 344 } 345 346 347 static status_t 348 fs_read_vnode(fs_volume* _volume, ino_t vnodeID, fs_vnode* _node, 349 int* _type, uint32* _flags, bool reenter) 350 { 351 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 352 353 iso9660_inode* newNode = (iso9660_inode*)calloc(sizeof(iso9660_inode), 1); 354 if (newNode == NULL) 355 return B_NO_MEMORY; 356 357 uint32 pos = vnodeID & 0x3fffffff; 358 uint32 block = vnodeID >> 30; 359 360 TRACE(("fs_read_vnode - block = %u, pos = %u, raw = %Lu node %p\n", 361 (unsigned)block, (unsigned) pos, vnodeID, newNode)); 362 363 if (pos > volume->logicalBlkSize[FS_DATA_FORMAT]) { 364 free(newNode); 365 return B_BAD_VALUE; 366 } 367 368 char* data = (char*)block_cache_get(volume->fBlockCache, block); 369 if (data == NULL) { 370 free(newNode); 371 return B_IO_ERROR; 372 } 373 374 status_t result = InitNode(volume, newNode, data + pos, NULL); 375 block_cache_put(volume->fBlockCache, block); 376 377 if (result < B_OK) { 378 free(newNode); 379 return result; 380 } 381 382 newNode->volume = volume; 383 newNode->id = vnodeID; 384 385 _node->private_node = newNode; 386 _node->ops = &gISO9660VnodeOps; 387 *_type = newNode->attr.stat[FS_DATA_FORMAT].st_mode 388 & ~(S_IWUSR | S_IWGRP | S_IWOTH); 389 *_flags = 0; 390 391 if ((newNode->flags & ISO_IS_DIR) == 0) { 392 newNode->cache = file_cache_create(volume->id, vnodeID, 393 newNode->dataLen[FS_DATA_FORMAT]); 394 } 395 396 return B_OK; 397 } 398 399 400 static status_t 401 fs_release_vnode(fs_volume* /*_volume*/, fs_vnode* _node, bool /*reenter*/) 402 { 403 iso9660_inode* node = (iso9660_inode*)_node->private_node; 404 405 TRACE(("fs_release_vnode - ENTER (%p)\n", node)); 406 407 if (node->id != ISO_ROOTNODE_ID) { 408 free(node->name); 409 free(node->attr.slName); 410 411 if (node->cache != NULL) 412 file_cache_delete(node->cache); 413 414 free(node); 415 } 416 417 TRACE(("fs_release_vnode - EXIT\n")); 418 return B_OK; 419 } 420 421 422 static status_t 423 fs_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, 424 const iovec* vecs, size_t count, size_t* _numBytes) 425 { 426 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 427 iso9660_inode* node = (iso9660_inode*)_node->private_node; 428 429 uint32 fileSize = node->dataLen[FS_DATA_FORMAT]; 430 size_t bytesLeft = *_numBytes; 431 432 if (pos >= fileSize) { 433 *_numBytes = 0; 434 return B_OK; 435 } 436 if (pos + bytesLeft > fileSize) { 437 bytesLeft = fileSize - pos; 438 *_numBytes = bytesLeft; 439 } 440 441 file_io_vec fileVec; 442 fileVec.offset = pos + ((off_t)node->startLBN[FS_DATA_FORMAT] 443 * (off_t)volume->logicalBlkSize[FS_DATA_FORMAT]); 444 fileVec.length = bytesLeft; 445 446 uint32 vecIndex = 0; 447 size_t vecOffset = 0; 448 return read_file_io_vec_pages(volume->fd, &fileVec, 1, vecs, count, 449 &vecIndex, &vecOffset, &bytesLeft); 450 } 451 452 453 static status_t 454 fs_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request) 455 { 456 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 457 iso9660_inode* node = (iso9660_inode*)_node->private_node; 458 459 if (io_request_is_write(request)) { 460 notify_io_request(request, B_READ_ONLY_DEVICE); 461 return B_READ_ONLY_DEVICE; 462 } 463 464 if ((node->flags & ISO_IS_DIR) != 0) { 465 notify_io_request(request, B_IS_A_DIRECTORY); 466 return B_IS_A_DIRECTORY; 467 } 468 469 return do_iterative_fd_io(volume->fd, request, iterative_io_get_vecs_hook, 470 iterative_io_finished_hook, node); 471 } 472 473 474 static status_t 475 fs_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* st) 476 { 477 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 478 iso9660_inode* node = (iso9660_inode*)_node->private_node; 479 status_t result = B_NO_ERROR; 480 time_t time; 481 482 TRACE(("fs_read_stat - ENTER\n")); 483 484 st->st_dev = volume->id; 485 st->st_ino = node->id; 486 st->st_nlink = node->attr.stat[FS_DATA_FORMAT].st_nlink; 487 st->st_uid = node->attr.stat[FS_DATA_FORMAT].st_uid; 488 st->st_gid = node->attr.stat[FS_DATA_FORMAT].st_gid; 489 st->st_blksize = 65536; 490 st->st_mode = node->attr.stat[FS_DATA_FORMAT].st_mode; 491 492 // Same for file/dir in ISO9660 493 st->st_size = node->dataLen[FS_DATA_FORMAT]; 494 st->st_blocks = (st->st_size + 511) / 512; 495 if (ConvertRecDate(&(node->recordDate), &time) == B_NO_ERROR) 496 st->st_ctime = st->st_mtime = st->st_atime = time; 497 498 TRACE(("fs_read_stat - EXIT, result is %s\n", strerror(result))); 499 500 return result; 501 } 502 503 504 static status_t 505 fs_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode, void** /*cookie*/) 506 { 507 // Do not allow any of the write-like open modes to get by 508 if ((openMode & O_RWMASK) == O_WRONLY || (openMode & O_RWMASK) == O_RDWR 509 || (openMode & O_TRUNC) != 0 || (openMode & O_CREAT) != 0) 510 return EROFS; 511 512 return B_OK; 513 } 514 515 516 static status_t 517 fs_read(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos, 518 void* buffer, size_t* _length) 519 { 520 iso9660_inode* node = (iso9660_inode*)_node->private_node; 521 522 if ((node->flags & ISO_IS_DIR) != 0) 523 return EISDIR; 524 525 uint32 fileSize = node->dataLen[FS_DATA_FORMAT]; 526 527 // set/check boundaries for pos/length 528 if (pos < 0) 529 return B_BAD_VALUE; 530 if (pos >= fileSize) { 531 *_length = 0; 532 return B_OK; 533 } 534 535 return file_cache_read(node->cache, NULL, pos, buffer, _length); 536 } 537 538 539 static status_t 540 fs_close(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) 541 { 542 return B_OK; 543 } 544 545 546 static status_t 547 fs_free_cookie(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) 548 { 549 return B_OK; 550 } 551 552 553 static status_t 554 fs_access(fs_volume* /*_volume*/, fs_vnode* /*_node*/, int /*mode*/) 555 { 556 return B_OK; 557 } 558 559 560 static status_t 561 fs_read_link(fs_volume* _volume, fs_vnode* _node, char* buffer, 562 size_t* _bufferSize) 563 { 564 iso9660_inode* node = (iso9660_inode*)_node->private_node; 565 566 if (!S_ISLNK(node->attr.stat[FS_DATA_FORMAT].st_mode)) 567 return B_BAD_VALUE; 568 569 size_t length = strlen(node->attr.slName); 570 if (length > *_bufferSize) 571 memcpy(buffer, node->attr.slName, *_bufferSize); 572 else { 573 memcpy(buffer, node->attr.slName, length); 574 *_bufferSize = length; 575 } 576 577 return B_OK; 578 } 579 580 581 static status_t 582 fs_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie) 583 { 584 iso9660_inode* node = (iso9660_inode*)_node->private_node; 585 586 TRACE(("fs_open_dir - node is %p\n", node)); 587 588 if ((node->flags & ISO_IS_DIR) == 0) 589 return B_NOT_A_DIRECTORY; 590 591 dircookie* dirCookie = (dircookie*)malloc(sizeof(dircookie)); 592 if (dirCookie == NULL) 593 return B_NO_MEMORY; 594 595 dirCookie->startBlock = node->startLBN[FS_DATA_FORMAT]; 596 dirCookie->block = node->startLBN[FS_DATA_FORMAT]; 597 dirCookie->totalSize = node->dataLen[FS_DATA_FORMAT]; 598 dirCookie->pos = 0; 599 dirCookie->id = node->id; 600 *_cookie = (void*)dirCookie; 601 602 return B_OK; 603 } 604 605 606 static status_t 607 fs_read_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie, 608 struct dirent* buffer, size_t bufferSize, uint32* num) 609 { 610 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 611 dircookie* dirCookie = (dircookie*)_cookie; 612 613 TRACE(("fs_read_dir - ENTER\n")); 614 615 status_t result = ISOReadDirEnt(volume, dirCookie, buffer, bufferSize); 616 617 // If we succeeded, return 1, the number of dirents we read. 618 if (result == B_OK) 619 *num = 1; 620 else 621 *num = 0; 622 623 // When you get to the end, don't return an error, just return 624 // a zero in *num. 625 626 if (result == B_ENTRY_NOT_FOUND) 627 result = B_OK; 628 629 TRACE(("fs_read_dir - EXIT, result is %s\n", strerror(result))); 630 return result; 631 } 632 633 634 static status_t 635 fs_rewind_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie) 636 { 637 dircookie* cookie = (dircookie*)_cookie; 638 639 cookie->block = cookie->startBlock; 640 cookie->pos = 0; 641 return B_OK; 642 } 643 644 645 static status_t 646 fs_close_dir(fs_volume* _volume, fs_vnode* _node, void* cookie) 647 { 648 return B_OK; 649 } 650 651 652 static status_t 653 fs_free_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie) 654 { 655 free(cookie); 656 return B_OK; 657 } 658 659 660 // #pragma mark - 661 662 663 static status_t 664 iso_std_ops(int32 op, ...) 665 { 666 switch (op) { 667 case B_MODULE_INIT: 668 case B_MODULE_UNINIT: 669 return B_OK; 670 default: 671 return B_ERROR; 672 } 673 } 674 675 676 fs_volume_ops gISO9660VolumeOps = { 677 &fs_unmount, 678 &fs_read_fs_stat, 679 NULL, 680 NULL, 681 &fs_read_vnode, 682 683 /* index and index directory ops */ 684 NULL, 685 NULL, 686 NULL, 687 NULL, 688 NULL, 689 NULL, 690 NULL, 691 NULL, 692 693 /* query ops */ 694 NULL, 695 NULL, 696 NULL, 697 NULL, 698 NULL, 699 700 /* FS layer ops */ 701 NULL, 702 NULL, 703 }; 704 705 fs_vnode_ops gISO9660VnodeOps = { 706 &fs_walk, 707 &fs_get_vnode_name, 708 &fs_release_vnode, 709 NULL, 710 711 /* vm-related ops */ 712 NULL, 713 &fs_read_pages, 714 NULL, 715 716 &fs_io, 717 NULL, // cancel_io() 718 719 /* cache file access */ 720 NULL, 721 722 /* common */ 723 NULL, 724 NULL, 725 NULL, 726 NULL, 727 NULL, 728 &fs_read_link, 729 NULL, 730 NULL, 731 NULL, 732 NULL, 733 &fs_access, 734 &fs_read_stat, 735 NULL, 736 NULL, 737 738 /* file */ 739 NULL, 740 &fs_open, 741 &fs_close, 742 &fs_free_cookie, 743 &fs_read, 744 NULL, 745 746 /* dir */ 747 NULL, 748 NULL, 749 &fs_open_dir, 750 &fs_close_dir, 751 &fs_free_dir_cookie, 752 &fs_read_dir, 753 &fs_rewind_dir, 754 755 /* attribute directory ops */ 756 NULL, 757 NULL, 758 NULL, 759 NULL, 760 NULL, 761 762 /* attribute ops */ 763 NULL, 764 NULL, 765 NULL, 766 NULL, 767 NULL, 768 NULL, 769 NULL, 770 NULL, 771 NULL, 772 NULL, 773 774 /* node and FS layer support */ 775 NULL, 776 NULL, 777 }; 778 779 static file_system_module_info sISO660FileSystem = { 780 { 781 "file_systems/iso9660" B_CURRENT_FS_API_VERSION, 782 0, 783 iso_std_ops, 784 }, 785 786 "iso9660", // short_name 787 "ISO9660 File System", // pretty_name 788 0, // DDM flags 789 790 // scanning 791 fs_identify_partition, 792 fs_scan_partition, 793 fs_free_identify_partition_cookie, 794 NULL, // free_partition_content_cookie() 795 796 &fs_mount, 797 798 /* capability querying */ 799 NULL, 800 NULL, 801 NULL, 802 NULL, 803 NULL, 804 NULL, 805 806 /* shadow partition modifications */ 807 NULL, 808 809 /* writing */ 810 NULL, 811 NULL, 812 NULL, 813 NULL, 814 NULL, 815 NULL, 816 NULL, 817 }; 818 819 module_info* modules[] = { 820 (module_info*)&sISO660FileSystem, 821 NULL, 822 }; 823 824