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 37 // TODO: temporary solution as long as there is no public I/O requests API 38 # include <io_requests.h> 39 #endif 40 41 #include "iso9660.h" 42 #include "iso9660_identify.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 #ifndef FS_SHELL 460 if (io_request_is_write(request)) { 461 notify_io_request(request, B_READ_ONLY_DEVICE); 462 return B_READ_ONLY_DEVICE; 463 } 464 #endif 465 466 if ((node->flags & ISO_IS_DIR) != 0) { 467 #ifndef FS_SHELL 468 notify_io_request(request, B_IS_A_DIRECTORY); 469 #endif 470 return B_IS_A_DIRECTORY; 471 } 472 473 return do_iterative_fd_io(volume->fd, request, iterative_io_get_vecs_hook, 474 iterative_io_finished_hook, node); 475 } 476 477 478 static status_t 479 fs_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* st) 480 { 481 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 482 iso9660_inode* node = (iso9660_inode*)_node->private_node; 483 status_t result = B_NO_ERROR; 484 time_t time; 485 486 TRACE(("fs_read_stat - ENTER\n")); 487 488 st->st_dev = volume->id; 489 st->st_ino = node->id; 490 st->st_nlink = node->attr.stat[FS_DATA_FORMAT].st_nlink; 491 st->st_uid = node->attr.stat[FS_DATA_FORMAT].st_uid; 492 st->st_gid = node->attr.stat[FS_DATA_FORMAT].st_gid; 493 st->st_blksize = 65536; 494 st->st_mode = node->attr.stat[FS_DATA_FORMAT].st_mode; 495 496 // Same for file/dir in ISO9660 497 st->st_size = node->dataLen[FS_DATA_FORMAT]; 498 st->st_blocks = (st->st_size + 511) / 512; 499 if (ConvertRecDate(&(node->recordDate), &time) == B_NO_ERROR) { 500 st->st_ctim.tv_sec = st->st_mtim.tv_sec = st->st_atim.tv_sec = time; 501 st->st_ctim.tv_nsec = st->st_mtim.tv_nsec = st->st_atim.tv_nsec = 0; 502 } 503 504 TRACE(("fs_read_stat - EXIT, result is %s\n", strerror(result))); 505 506 return result; 507 } 508 509 510 static status_t 511 fs_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode, void** /*cookie*/) 512 { 513 // Do not allow any of the write-like open modes to get by 514 if ((openMode & O_RWMASK) == O_WRONLY || (openMode & O_RWMASK) == O_RDWR 515 || (openMode & O_TRUNC) != 0 || (openMode & O_CREAT) != 0) 516 return EROFS; 517 518 return B_OK; 519 } 520 521 522 static status_t 523 fs_read(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos, 524 void* buffer, size_t* _length) 525 { 526 iso9660_inode* node = (iso9660_inode*)_node->private_node; 527 528 if ((node->flags & ISO_IS_DIR) != 0) 529 return EISDIR; 530 531 return file_cache_read(node->cache, NULL, pos, buffer, _length); 532 } 533 534 535 static status_t 536 fs_close(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) 537 { 538 return B_OK; 539 } 540 541 542 static status_t 543 fs_free_cookie(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) 544 { 545 return B_OK; 546 } 547 548 549 static status_t 550 fs_access(fs_volume* /*_volume*/, fs_vnode* /*_node*/, int /*mode*/) 551 { 552 return B_OK; 553 } 554 555 556 static status_t 557 fs_read_link(fs_volume* _volume, fs_vnode* _node, char* buffer, 558 size_t* _bufferSize) 559 { 560 iso9660_inode* node = (iso9660_inode*)_node->private_node; 561 562 if (!S_ISLNK(node->attr.stat[FS_DATA_FORMAT].st_mode)) 563 return B_BAD_VALUE; 564 565 size_t length = strlen(node->attr.slName); 566 if (length > *_bufferSize) 567 memcpy(buffer, node->attr.slName, *_bufferSize); 568 else { 569 memcpy(buffer, node->attr.slName, length); 570 *_bufferSize = length; 571 } 572 573 return B_OK; 574 } 575 576 577 static status_t 578 fs_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie) 579 { 580 iso9660_inode* node = (iso9660_inode*)_node->private_node; 581 582 TRACE(("fs_open_dir - node is %p\n", node)); 583 584 if ((node->flags & ISO_IS_DIR) == 0) 585 return B_NOT_A_DIRECTORY; 586 587 dircookie* dirCookie = (dircookie*)malloc(sizeof(dircookie)); 588 if (dirCookie == NULL) 589 return B_NO_MEMORY; 590 591 dirCookie->startBlock = node->startLBN[FS_DATA_FORMAT]; 592 dirCookie->block = node->startLBN[FS_DATA_FORMAT]; 593 dirCookie->totalSize = node->dataLen[FS_DATA_FORMAT]; 594 dirCookie->pos = 0; 595 dirCookie->id = node->id; 596 *_cookie = (void*)dirCookie; 597 598 return B_OK; 599 } 600 601 602 static status_t 603 fs_read_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie, 604 struct dirent* buffer, size_t bufferSize, uint32* num) 605 { 606 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 607 dircookie* dirCookie = (dircookie*)_cookie; 608 609 TRACE(("fs_read_dir - ENTER\n")); 610 611 status_t result = ISOReadDirEnt(volume, dirCookie, buffer, bufferSize); 612 613 // If we succeeded, return 1, the number of dirents we read. 614 if (result == B_OK) 615 *num = 1; 616 else 617 *num = 0; 618 619 // When you get to the end, don't return an error, just return 620 // a zero in *num. 621 622 if (result == B_ENTRY_NOT_FOUND) 623 result = B_OK; 624 625 TRACE(("fs_read_dir - EXIT, result is %s\n", strerror(result))); 626 return result; 627 } 628 629 630 static status_t 631 fs_rewind_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie) 632 { 633 dircookie* cookie = (dircookie*)_cookie; 634 635 cookie->block = cookie->startBlock; 636 cookie->pos = 0; 637 return B_OK; 638 } 639 640 641 static status_t 642 fs_close_dir(fs_volume* _volume, fs_vnode* _node, void* cookie) 643 { 644 return B_OK; 645 } 646 647 648 static status_t 649 fs_free_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie) 650 { 651 free(cookie); 652 return B_OK; 653 } 654 655 656 // #pragma mark - 657 658 659 static status_t 660 iso_std_ops(int32 op, ...) 661 { 662 switch (op) { 663 case B_MODULE_INIT: 664 case B_MODULE_UNINIT: 665 return B_OK; 666 default: 667 return B_ERROR; 668 } 669 } 670 671 672 fs_volume_ops gISO9660VolumeOps = { 673 &fs_unmount, 674 &fs_read_fs_stat, 675 NULL, 676 NULL, 677 &fs_read_vnode, 678 679 /* index and index directory ops */ 680 NULL, 681 NULL, 682 NULL, 683 NULL, 684 NULL, 685 NULL, 686 NULL, 687 NULL, 688 689 /* query ops */ 690 NULL, 691 NULL, 692 NULL, 693 NULL, 694 NULL, 695 696 /* FS layer ops */ 697 NULL, 698 NULL, 699 }; 700 701 fs_vnode_ops gISO9660VnodeOps = { 702 &fs_walk, 703 &fs_get_vnode_name, 704 &fs_release_vnode, 705 NULL, 706 707 /* vm-related ops */ 708 NULL, 709 &fs_read_pages, 710 NULL, 711 712 &fs_io, 713 NULL, // cancel_io() 714 715 /* cache file access */ 716 NULL, 717 718 /* common */ 719 NULL, 720 NULL, 721 NULL, 722 NULL, 723 NULL, 724 &fs_read_link, 725 NULL, 726 NULL, 727 NULL, 728 NULL, 729 &fs_access, 730 &fs_read_stat, 731 NULL, 732 NULL, 733 734 /* file */ 735 NULL, 736 &fs_open, 737 &fs_close, 738 &fs_free_cookie, 739 &fs_read, 740 NULL, 741 742 /* dir */ 743 NULL, 744 NULL, 745 &fs_open_dir, 746 &fs_close_dir, 747 &fs_free_dir_cookie, 748 &fs_read_dir, 749 &fs_rewind_dir, 750 751 /* attribute directory ops */ 752 NULL, 753 NULL, 754 NULL, 755 NULL, 756 NULL, 757 758 /* attribute ops */ 759 NULL, 760 NULL, 761 NULL, 762 NULL, 763 NULL, 764 NULL, 765 NULL, 766 NULL, 767 NULL, 768 NULL, 769 770 /* node and FS layer support */ 771 NULL, 772 NULL, 773 }; 774 775 static file_system_module_info sISO660FileSystem = { 776 { 777 "file_systems/iso9660" B_CURRENT_FS_API_VERSION, 778 0, 779 iso_std_ops, 780 }, 781 782 "iso9660", // short_name 783 "ISO9660 File System", // pretty_name 784 0, // DDM flags 785 786 // scanning 787 fs_identify_partition, 788 fs_scan_partition, 789 fs_free_identify_partition_cookie, 790 NULL, // free_partition_content_cookie() 791 792 &fs_mount, 793 794 /* capability querying */ 795 NULL, 796 NULL, 797 NULL, 798 NULL, 799 NULL, 800 NULL, 801 802 /* shadow partition modifications */ 803 NULL, 804 805 /* writing */ 806 NULL, 807 NULL, 808 NULL, 809 NULL, 810 NULL, 811 NULL, 812 NULL, 813 }; 814 815 module_info* modules[] = { 816 (module_info*)&sISO660FileSystem, 817 NULL, 818 }; 819 820