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 <algorithm> 12 #include <ctype.h> 13 14 #ifndef FS_SHELL 15 # include <dirent.h> 16 # include <errno.h> 17 # include <fcntl.h> 18 # include <stdio.h> 19 # include <stdlib.h> 20 # include <string.h> 21 # include <sys/stat.h> 22 # include <time.h> 23 # include <unistd.h> 24 25 # include <KernelExport.h> 26 # include <NodeMonitor.h> 27 # include <fs_interface.h> 28 # include <fs_cache.h> 29 30 # include <fs_attr.h> 31 # include <fs_info.h> 32 # include <fs_index.h> 33 # include <fs_query.h> 34 # include <fs_volume.h> 35 36 # include <util/kernel_cpp.h> 37 38 // TODO: temporary solution as long as there is no public I/O requests API 39 # include <io_requests.h> 40 #endif 41 42 #include "iso9660.h" 43 #include "iso9660_identify.h" 44 45 46 //#define TRACE_ISO9660 47 #ifdef TRACE_ISO9660 48 # define TRACE(x) dprintf x 49 #else 50 # define TRACE(x) ; 51 #endif 52 53 54 struct identify_cookie { 55 iso9660_info info; 56 }; 57 58 extern fs_volume_ops gISO9660VolumeOps; 59 extern fs_vnode_ops gISO9660VnodeOps; 60 61 62 //! fs_io() callback hook 63 static status_t 64 iterative_io_get_vecs_hook(void* cookie, io_request* request, off_t offset, 65 size_t size, struct file_io_vec* vecs, size_t* _count) 66 { 67 iso9660_inode* node = (iso9660_inode*)cookie; 68 69 vecs->offset = offset + ((off_t)node->startLBN[FS_DATA_FORMAT] 70 * (off_t)node->volume->logicalBlkSize[FS_DATA_FORMAT]); 71 vecs->length = size; 72 73 *_count = 1; 74 75 return B_OK; 76 } 77 78 79 //! fs_io() callback hook 80 static status_t 81 iterative_io_finished_hook(void* cookie, io_request* request, status_t status, 82 bool partialTransfer, size_t bytesTransferred) 83 { 84 // nothing to do here... 85 return B_OK; 86 } 87 88 89 // #pragma mark - Scanning 90 91 92 static float 93 fs_identify_partition(int fd, partition_data* partition, void** _cookie) 94 { 95 iso9660_info* info = new iso9660_info; 96 97 status_t status = iso9660_fs_identify(fd, info); 98 if (status != B_OK) { 99 delete info; 100 return -1; 101 } 102 103 *_cookie = info; 104 return 0.6f; 105 } 106 107 108 static status_t 109 fs_scan_partition(int fd, partition_data* partition, void* _cookie) 110 { 111 iso9660_info* info = (iso9660_info*)_cookie; 112 113 partition->status = B_PARTITION_VALID; 114 partition->flags |= B_PARTITION_FILE_SYSTEM | B_PARTITION_READ_ONLY ; 115 partition->block_size = ISO_PVD_SIZE; 116 partition->content_size = ISO_PVD_SIZE * info->max_blocks; 117 partition->content_name = strdup(info->PreferredName()); 118 119 if (partition->content_name == NULL) 120 return B_NO_MEMORY; 121 122 return B_OK; 123 } 124 125 126 static void 127 fs_free_identify_partition_cookie(partition_data* partition, void* _cookie) 128 { 129 delete (iso9660_info*)_cookie; 130 } 131 132 133 // #pragma mark - FS hooks 134 135 136 static status_t 137 fs_mount(fs_volume* _volume, const char* device, uint32 flags, 138 const char* args, ino_t* _rootID) 139 { 140 bool allowJoliet = true; 141 iso9660_volume* volume; 142 143 // Check for a 'nojoliet' parm 144 // all we check for is the existance of 'nojoliet' in the parms. 145 if (args != NULL) { 146 uint32 i; 147 char* spot; 148 char* buf = strdup(args); 149 150 uint32 len = strlen(buf); 151 // lower case the parms data 152 for (i = 0; i < len + 1; i++) 153 buf[i] = tolower(buf[i]); 154 155 // look for nojoliet 156 spot = strstr(buf, "nojoliet"); 157 if (spot != NULL) 158 allowJoliet = false; 159 160 free(buf); 161 } 162 163 // Try and mount volume as an ISO volume. 164 status_t result = ISOMount(device, O_RDONLY, &volume, allowJoliet); 165 if (result == B_OK) { 166 *_rootID = ISO_ROOTNODE_ID; 167 168 _volume->private_volume = volume; 169 _volume->ops = &gISO9660VolumeOps; 170 volume->volume = _volume; 171 volume->id = _volume->id; 172 173 result = publish_vnode(_volume, *_rootID, &volume->rootDirRec, 174 &gISO9660VnodeOps, 175 volume->rootDirRec.attr.stat[FS_DATA_FORMAT].st_mode, 0); 176 if (result != B_OK) { 177 block_cache_delete(volume->fBlockCache, false); 178 free(volume); 179 result = B_ERROR; 180 } 181 } 182 return result; 183 } 184 185 186 static status_t 187 fs_unmount(fs_volume* _volume) 188 { 189 status_t result = B_NO_ERROR; 190 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 191 192 TRACE(("fs_unmount - ENTER\n")); 193 194 // Unlike in BeOS, we need to put the reference to our root node ourselves 195 put_vnode(_volume, ISO_ROOTNODE_ID); 196 197 block_cache_delete(volume->fBlockCache, false); 198 close(volume->fdOfSession); 199 result = close(volume->fd); 200 201 free(volume); 202 203 TRACE(("fs_unmount - EXIT, result is %s\n", strerror(result))); 204 return result; 205 } 206 207 208 static status_t 209 fs_read_fs_stat(fs_volume* _volume, struct fs_info* info) 210 { 211 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 212 213 info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY; 214 info->block_size = volume->logicalBlkSize[FS_DATA_FORMAT]; 215 info->io_size = 65536; 216 info->total_blocks = volume->volSpaceSize[FS_DATA_FORMAT]; 217 info->free_blocks = 0; 218 219 strlcpy(info->device_name, volume->devicePath, sizeof(info->device_name)); 220 strlcpy(info->volume_name, volume->volIDString, sizeof(info->volume_name)); 221 222 // strip trailing spaces 223 int i; 224 for (i = strlen(info->volume_name) - 1; i >=0 ; i--) { 225 if (info->volume_name[i] != ' ') 226 break; 227 } 228 229 if (i < 0) 230 strcpy(info->volume_name, "UNKNOWN"); 231 else 232 info->volume_name[i + 1] = 0; 233 234 strcpy(info->fsh_name, "iso9660"); 235 return B_OK; 236 } 237 238 239 static status_t 240 fs_get_vnode_name(fs_volume* _volume, fs_vnode* _node, char* buffer, 241 size_t bufferSize) 242 { 243 iso9660_inode* node = (iso9660_inode*)_node->private_node; 244 245 strlcpy(buffer, node->name, bufferSize); 246 return B_OK; 247 } 248 249 250 static status_t 251 fs_walk(fs_volume* _volume, fs_vnode* _base, const char* file, ino_t* _vnodeID) 252 { 253 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 254 iso9660_inode* baseNode = (iso9660_inode*)_base->private_node; 255 iso9660_inode* newNode = NULL; 256 257 TRACE(("fs_walk - looking for %s in dir file of length %d\n", file, 258 (int)baseNode->dataLen[FS_DATA_FORMAT])); 259 260 if (strcmp(file, ".") == 0) { 261 // base directory 262 TRACE(("fs_walk - found \".\" file.\n")); 263 *_vnodeID = baseNode->id; 264 return get_vnode(_volume, *_vnodeID, NULL); 265 } else if (strcmp(file, "..") == 0) { 266 // parent directory 267 TRACE(("fs_walk - found \"..\" file.\n")); 268 *_vnodeID = baseNode->parID; 269 return get_vnode(_volume, *_vnodeID, NULL); 270 } 271 272 // look up file in the directory 273 uint32 dataLength = baseNode->dataLen[FS_DATA_FORMAT]; 274 status_t result = ENOENT; 275 size_t totalRead = 0; 276 off_t block = baseNode->startLBN[FS_DATA_FORMAT]; 277 bool done = false; 278 279 while (totalRead < dataLength && !done) { 280 off_t cachedBlock = block; 281 char* blockData = (char*)block_cache_get(volume->fBlockCache, block); 282 if (blockData == NULL) 283 break; 284 285 size_t bytesRead = 0; 286 off_t blockBytesRead = 0; 287 iso9660_inode node; 288 int initResult; 289 290 TRACE(("fs_walk - read buffer from disk at LBN %lld into buffer " 291 "%p.\n", block, blockData)); 292 293 // Move to the next block if necessary 294 // Don't go over end of buffer, if dir record sits on boundary. 295 296 while (blockBytesRead < volume->logicalBlkSize[FS_DATA_FORMAT] 297 && totalRead + blockBytesRead < dataLength 298 && blockData[0] != 0 299 && !done) { 300 initResult = InitNode(volume, &node, blockData, &bytesRead); 301 TRACE(("fs_walk - InitNode returned %s, filename %s, %u bytes " 302 "read\n", strerror(initResult), node.name, (unsigned)bytesRead)); 303 304 if (initResult == B_OK) { 305 if ((node.flags & ISO_IS_ASSOCIATED_FILE) == 0 306 && !strcmp(node.name, file)) { 307 TRACE(("fs_walk - success, found vnode at block %lld, pos " 308 "%lld\n", block, blockBytesRead)); 309 310 *_vnodeID = (block << 30) + (blockBytesRead & 0xffffffff); 311 TRACE(("fs_walk - New vnode id is %lld\n", *_vnodeID)); 312 313 result = get_vnode(_volume, *_vnodeID, (void**)&newNode); 314 if (result == B_OK) { 315 newNode->parID = baseNode->id; 316 done = true; 317 } 318 } else { 319 free(node.name); 320 free(node.attr.slName); 321 } 322 } else { 323 result = initResult; 324 if (bytesRead == 0) 325 done = true; 326 } 327 blockData += bytesRead; 328 blockBytesRead += bytesRead; 329 330 TRACE(("fs_walk - Adding %u bytes to blockBytes read (total " 331 "%lld/%u).\n", (unsigned)bytesRead, blockBytesRead, 332 (unsigned)baseNode->dataLen[FS_DATA_FORMAT])); 333 } 334 totalRead += volume->logicalBlkSize[FS_DATA_FORMAT]; 335 block++; 336 337 TRACE(("fs_walk - moving to next block %lld, total read %u\n", 338 block, (unsigned)totalRead)); 339 block_cache_put(volume->fBlockCache, cachedBlock); 340 } 341 342 TRACE(("fs_walk - EXIT, result is %s, vnid is %Lu\n", 343 strerror(result), *_vnodeID)); 344 return result; 345 } 346 347 348 static status_t 349 fs_read_vnode(fs_volume* _volume, ino_t vnodeID, fs_vnode* _node, 350 int* _type, uint32* _flags, bool reenter) 351 { 352 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 353 354 iso9660_inode* newNode = (iso9660_inode*)calloc(sizeof(iso9660_inode), 1); 355 if (newNode == NULL) 356 return B_NO_MEMORY; 357 358 uint32 pos = vnodeID & 0x3fffffff; 359 uint32 block = vnodeID >> 30; 360 361 TRACE(("fs_read_vnode - block = %u, pos = %u, raw = %Lu node %p\n", 362 (unsigned)block, (unsigned) pos, vnodeID, newNode)); 363 364 if (pos > volume->logicalBlkSize[FS_DATA_FORMAT]) { 365 free(newNode); 366 return B_BAD_VALUE; 367 } 368 369 char* data = (char*)block_cache_get(volume->fBlockCache, block); 370 if (data == NULL) { 371 free(newNode); 372 return B_IO_ERROR; 373 } 374 375 status_t result = InitNode(volume, newNode, data + pos, NULL); 376 block_cache_put(volume->fBlockCache, block); 377 378 if (result < B_OK) { 379 free(newNode); 380 return result; 381 } 382 383 newNode->volume = volume; 384 newNode->id = vnodeID; 385 386 _node->private_node = newNode; 387 _node->ops = &gISO9660VnodeOps; 388 *_type = newNode->attr.stat[FS_DATA_FORMAT].st_mode 389 & ~(S_IWUSR | S_IWGRP | S_IWOTH); 390 *_flags = 0; 391 392 if ((newNode->flags & ISO_IS_DIR) == 0) { 393 newNode->cache = file_cache_create(volume->id, vnodeID, 394 newNode->dataLen[FS_DATA_FORMAT]); 395 } 396 397 return B_OK; 398 } 399 400 401 static status_t 402 fs_release_vnode(fs_volume* /*_volume*/, fs_vnode* _node, bool /*reenter*/) 403 { 404 iso9660_inode* node = (iso9660_inode*)_node->private_node; 405 406 TRACE(("fs_release_vnode - ENTER (%p)\n", node)); 407 408 if (node->id != ISO_ROOTNODE_ID) { 409 free(node->name); 410 free(node->attr.slName); 411 412 if (node->cache != NULL) 413 file_cache_delete(node->cache); 414 415 free(node); 416 } 417 418 TRACE(("fs_release_vnode - EXIT\n")); 419 return B_OK; 420 } 421 422 423 static status_t 424 fs_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, 425 const iovec* vecs, size_t count, size_t* _numBytes) 426 { 427 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 428 iso9660_inode* node = (iso9660_inode*)_node->private_node; 429 430 uint32 fileSize = node->dataLen[FS_DATA_FORMAT]; 431 size_t bytesLeft = *_numBytes; 432 433 if (pos >= fileSize) { 434 *_numBytes = 0; 435 return B_OK; 436 } 437 if (pos + bytesLeft > fileSize) { 438 bytesLeft = fileSize - pos; 439 *_numBytes = bytesLeft; 440 } 441 442 file_io_vec fileVec; 443 fileVec.offset = pos + ((off_t)node->startLBN[FS_DATA_FORMAT] 444 * (off_t)volume->logicalBlkSize[FS_DATA_FORMAT]); 445 fileVec.length = bytesLeft; 446 447 uint32 vecIndex = 0; 448 size_t vecOffset = 0; 449 return read_file_io_vec_pages(volume->fd, &fileVec, 1, vecs, count, 450 &vecIndex, &vecOffset, &bytesLeft); 451 } 452 453 454 static status_t 455 fs_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request) 456 { 457 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 458 iso9660_inode* node = (iso9660_inode*)_node->private_node; 459 460 #ifndef FS_SHELL 461 if (io_request_is_write(request)) { 462 notify_io_request(request, B_READ_ONLY_DEVICE); 463 return B_READ_ONLY_DEVICE; 464 } 465 #endif 466 467 if ((node->flags & ISO_IS_DIR) != 0) { 468 #ifndef FS_SHELL 469 notify_io_request(request, B_IS_A_DIRECTORY); 470 #endif 471 return B_IS_A_DIRECTORY; 472 } 473 474 return do_iterative_fd_io(volume->fd, request, iterative_io_get_vecs_hook, 475 iterative_io_finished_hook, node); 476 } 477 478 479 static status_t 480 fs_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* st) 481 { 482 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 483 iso9660_inode* node = (iso9660_inode*)_node->private_node; 484 status_t result = B_NO_ERROR; 485 time_t time; 486 487 TRACE(("fs_read_stat - ENTER\n")); 488 489 st->st_dev = volume->id; 490 st->st_ino = node->id; 491 st->st_nlink = node->attr.stat[FS_DATA_FORMAT].st_nlink; 492 st->st_uid = node->attr.stat[FS_DATA_FORMAT].st_uid; 493 st->st_gid = node->attr.stat[FS_DATA_FORMAT].st_gid; 494 st->st_blksize = 65536; 495 st->st_mode = node->attr.stat[FS_DATA_FORMAT].st_mode; 496 497 // Same for file/dir in ISO9660 498 st->st_size = node->dataLen[FS_DATA_FORMAT]; 499 st->st_blocks = (st->st_size + 511) / 512; 500 if (ConvertRecDate(&(node->recordDate), &time) == B_NO_ERROR) { 501 st->st_ctim.tv_sec = st->st_mtim.tv_sec = st->st_atim.tv_sec = time; 502 st->st_ctim.tv_nsec = st->st_mtim.tv_nsec = st->st_atim.tv_nsec = 0; 503 } 504 505 TRACE(("fs_read_stat - EXIT, result is %s\n", strerror(result))); 506 507 return result; 508 } 509 510 511 static status_t 512 fs_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode, void** /*cookie*/) 513 { 514 // Do not allow any of the write-like open modes to get by 515 if ((openMode & O_RWMASK) == O_WRONLY || (openMode & O_RWMASK) == O_RDWR 516 || (openMode & O_TRUNC) != 0 || (openMode & O_CREAT) != 0) 517 return EROFS; 518 519 return B_OK; 520 } 521 522 523 static status_t 524 fs_read(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos, 525 void* buffer, size_t* _length) 526 { 527 iso9660_inode* node = (iso9660_inode*)_node->private_node; 528 529 if ((node->flags & ISO_IS_DIR) != 0) 530 return EISDIR; 531 532 return file_cache_read(node->cache, NULL, pos, buffer, _length); 533 } 534 535 536 static status_t 537 fs_close(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) 538 { 539 return B_OK; 540 } 541 542 543 static status_t 544 fs_free_cookie(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) 545 { 546 return B_OK; 547 } 548 549 550 static status_t 551 fs_access(fs_volume* /*_volume*/, fs_vnode* /*_node*/, int /*mode*/) 552 { 553 return B_OK; 554 } 555 556 557 static status_t 558 fs_read_link(fs_volume* _volume, fs_vnode* _node, char* buffer, 559 size_t* _bufferSize) 560 { 561 iso9660_inode* node = (iso9660_inode*)_node->private_node; 562 563 if (!S_ISLNK(node->attr.stat[FS_DATA_FORMAT].st_mode)) 564 return B_BAD_VALUE; 565 566 size_t length = strlen(node->attr.slName); 567 568 size_t bytesToCopy = std::min(length, *_bufferSize); 569 570 *_bufferSize = length; 571 572 memcpy(buffer, node->attr.slName, bytesToCopy); 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