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 uint32 fileSize = node->dataLen[FS_DATA_FORMAT]; 532 533 // set/check boundaries for pos/length 534 if (pos < 0) 535 return B_BAD_VALUE; 536 if (pos >= fileSize) { 537 *_length = 0; 538 return B_OK; 539 } 540 541 return file_cache_read(node->cache, NULL, pos, buffer, _length); 542 } 543 544 545 static status_t 546 fs_close(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) 547 { 548 return B_OK; 549 } 550 551 552 static status_t 553 fs_free_cookie(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) 554 { 555 return B_OK; 556 } 557 558 559 static status_t 560 fs_access(fs_volume* /*_volume*/, fs_vnode* /*_node*/, int /*mode*/) 561 { 562 return B_OK; 563 } 564 565 566 static status_t 567 fs_read_link(fs_volume* _volume, fs_vnode* _node, char* buffer, 568 size_t* _bufferSize) 569 { 570 iso9660_inode* node = (iso9660_inode*)_node->private_node; 571 572 if (!S_ISLNK(node->attr.stat[FS_DATA_FORMAT].st_mode)) 573 return B_BAD_VALUE; 574 575 size_t length = strlen(node->attr.slName); 576 if (length > *_bufferSize) 577 memcpy(buffer, node->attr.slName, *_bufferSize); 578 else { 579 memcpy(buffer, node->attr.slName, length); 580 *_bufferSize = length; 581 } 582 583 return B_OK; 584 } 585 586 587 static status_t 588 fs_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie) 589 { 590 iso9660_inode* node = (iso9660_inode*)_node->private_node; 591 592 TRACE(("fs_open_dir - node is %p\n", node)); 593 594 if ((node->flags & ISO_IS_DIR) == 0) 595 return B_NOT_A_DIRECTORY; 596 597 dircookie* dirCookie = (dircookie*)malloc(sizeof(dircookie)); 598 if (dirCookie == NULL) 599 return B_NO_MEMORY; 600 601 dirCookie->startBlock = node->startLBN[FS_DATA_FORMAT]; 602 dirCookie->block = node->startLBN[FS_DATA_FORMAT]; 603 dirCookie->totalSize = node->dataLen[FS_DATA_FORMAT]; 604 dirCookie->pos = 0; 605 dirCookie->id = node->id; 606 *_cookie = (void*)dirCookie; 607 608 return B_OK; 609 } 610 611 612 static status_t 613 fs_read_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie, 614 struct dirent* buffer, size_t bufferSize, uint32* num) 615 { 616 iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; 617 dircookie* dirCookie = (dircookie*)_cookie; 618 619 TRACE(("fs_read_dir - ENTER\n")); 620 621 status_t result = ISOReadDirEnt(volume, dirCookie, buffer, bufferSize); 622 623 // If we succeeded, return 1, the number of dirents we read. 624 if (result == B_OK) 625 *num = 1; 626 else 627 *num = 0; 628 629 // When you get to the end, don't return an error, just return 630 // a zero in *num. 631 632 if (result == B_ENTRY_NOT_FOUND) 633 result = B_OK; 634 635 TRACE(("fs_read_dir - EXIT, result is %s\n", strerror(result))); 636 return result; 637 } 638 639 640 static status_t 641 fs_rewind_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie) 642 { 643 dircookie* cookie = (dircookie*)_cookie; 644 645 cookie->block = cookie->startBlock; 646 cookie->pos = 0; 647 return B_OK; 648 } 649 650 651 static status_t 652 fs_close_dir(fs_volume* _volume, fs_vnode* _node, void* cookie) 653 { 654 return B_OK; 655 } 656 657 658 static status_t 659 fs_free_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie) 660 { 661 free(cookie); 662 return B_OK; 663 } 664 665 666 // #pragma mark - 667 668 669 static status_t 670 iso_std_ops(int32 op, ...) 671 { 672 switch (op) { 673 case B_MODULE_INIT: 674 case B_MODULE_UNINIT: 675 return B_OK; 676 default: 677 return B_ERROR; 678 } 679 } 680 681 682 fs_volume_ops gISO9660VolumeOps = { 683 &fs_unmount, 684 &fs_read_fs_stat, 685 NULL, 686 NULL, 687 &fs_read_vnode, 688 689 /* index and index directory ops */ 690 NULL, 691 NULL, 692 NULL, 693 NULL, 694 NULL, 695 NULL, 696 NULL, 697 NULL, 698 699 /* query ops */ 700 NULL, 701 NULL, 702 NULL, 703 NULL, 704 NULL, 705 706 /* FS layer ops */ 707 NULL, 708 NULL, 709 }; 710 711 fs_vnode_ops gISO9660VnodeOps = { 712 &fs_walk, 713 &fs_get_vnode_name, 714 &fs_release_vnode, 715 NULL, 716 717 /* vm-related ops */ 718 NULL, 719 &fs_read_pages, 720 NULL, 721 722 &fs_io, 723 NULL, // cancel_io() 724 725 /* cache file access */ 726 NULL, 727 728 /* common */ 729 NULL, 730 NULL, 731 NULL, 732 NULL, 733 NULL, 734 &fs_read_link, 735 NULL, 736 NULL, 737 NULL, 738 NULL, 739 &fs_access, 740 &fs_read_stat, 741 NULL, 742 NULL, 743 744 /* file */ 745 NULL, 746 &fs_open, 747 &fs_close, 748 &fs_free_cookie, 749 &fs_read, 750 NULL, 751 752 /* dir */ 753 NULL, 754 NULL, 755 &fs_open_dir, 756 &fs_close_dir, 757 &fs_free_dir_cookie, 758 &fs_read_dir, 759 &fs_rewind_dir, 760 761 /* attribute directory ops */ 762 NULL, 763 NULL, 764 NULL, 765 NULL, 766 NULL, 767 768 /* attribute ops */ 769 NULL, 770 NULL, 771 NULL, 772 NULL, 773 NULL, 774 NULL, 775 NULL, 776 NULL, 777 NULL, 778 NULL, 779 780 /* node and FS layer support */ 781 NULL, 782 NULL, 783 }; 784 785 static file_system_module_info sISO660FileSystem = { 786 { 787 "file_systems/iso9660" B_CURRENT_FS_API_VERSION, 788 0, 789 iso_std_ops, 790 }, 791 792 "iso9660", // short_name 793 "ISO9660 File System", // pretty_name 794 0, // DDM flags 795 796 // scanning 797 fs_identify_partition, 798 fs_scan_partition, 799 fs_free_identify_partition_cookie, 800 NULL, // free_partition_content_cookie() 801 802 &fs_mount, 803 804 /* capability querying */ 805 NULL, 806 NULL, 807 NULL, 808 NULL, 809 NULL, 810 NULL, 811 812 /* shadow partition modifications */ 813 NULL, 814 815 /* writing */ 816 NULL, 817 NULL, 818 NULL, 819 NULL, 820 NULL, 821 NULL, 822 NULL, 823 }; 824 825 module_info* modules[] = { 826 (module_info*)&sISO660FileSystem, 827 NULL, 828 }; 829 830