1 /* 2 * Copyright 2003, Tyler Dauwalder, tyler@dauwalder.net. 3 * Copyright 2008, Salvatore Benedetto, salvatore.benedetto@gmail.com. 4 * Copyright 2010, Michael Lotz, mmlr@mlotz.ch. 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 /*! \file kernel_interface.cpp */ 10 11 12 #include <Drivers.h> 13 #include <ctype.h> 14 #include <errno.h> 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <string.h> 18 19 #include <KernelExport.h> 20 #include <util/kernel_cpp.h> 21 22 #include <io_requests.h> 23 24 #include "Icb.h" 25 #include "Recognition.h" 26 #include "Utils.h" 27 #include "Volume.h" 28 29 30 #undef TRACE 31 #undef TRACE_ERROR 32 //#define UDF_KERNEL_INTERFACE_DEBUG 33 #ifdef UDF_KERNEL_INTERFACE_DEBUG 34 # define TRACE(x) dprintf x 35 # define TRACE_ERROR(x) dprintf x 36 #else 37 # define TRACE(x) /* nothing */ 38 # define TRACE_ERROR(x) dprintf x 39 #endif 40 41 extern fs_volume_ops gUDFVolumeOps; 42 extern fs_vnode_ops gUDFVnodeOps; 43 44 45 struct identify_cookie { 46 struct logical_volume_descriptor logical_volume_descriptor; 47 }; 48 49 50 // #pragma mark - io callbacks 51 52 53 static status_t 54 iterative_io_get_vecs_hook(void *cookie, io_request *request, off_t offset, 55 size_t size, struct file_io_vec *vecs, size_t *count) 56 { 57 Icb *icb = (Icb *)cookie; 58 return file_map_translate(icb->FileMap(), offset, size, vecs, count, 59 icb->GetVolume()->BlockSize()); 60 } 61 62 63 static status_t 64 iterative_io_finished_hook(void *cookie, io_request *request, status_t status, 65 bool partialTransfer, size_t bytesTransferred) 66 { 67 // nothing to do 68 return B_OK; 69 } 70 71 72 // #pragma mark - fs_volume_ops fuctions 73 74 75 static float 76 udf_identify_partition(int fd, partition_data *partition, void **_cookie) 77 { 78 TRACE(("udf_identify_partition: fd = %d, id = %ld, offset = %Ld, size = %Ld " 79 "content_size = %Ld, block_size = %lu\n", fd, partition->id, 80 partition->offset, partition->size, partition->content_size, 81 partition->block_size)); 82 83 logical_volume_descriptor logicalVolumeDescriptor; 84 partition_descriptor partitionDescriptors[kMaxPartitionDescriptors]; 85 uint8 descriptorCount = kMaxPartitionDescriptors; 86 uint32 blockShift; 87 status_t error = udf_recognize(fd, partition->offset, partition->size, 88 partition->block_size, blockShift, logicalVolumeDescriptor, 89 partitionDescriptors, descriptorCount); 90 if (error != B_OK) 91 return -1; 92 93 identify_cookie *cookie = new(std::nothrow) identify_cookie; 94 if (cookie == NULL) 95 return -1; 96 97 cookie->logical_volume_descriptor = logicalVolumeDescriptor; 98 *_cookie = cookie; 99 return 0.8f; 100 } 101 102 103 static status_t 104 udf_scan_partition(int fd, partition_data *partition, void *_cookie) 105 { 106 TRACE(("udf_scan_partition: fd = %d\n", fd)); 107 identify_cookie *cookie = (identify_cookie *)_cookie; 108 logical_volume_descriptor &volumeDescriptor 109 = cookie->logical_volume_descriptor; 110 111 partition->status = B_PARTITION_VALID; 112 partition->flags |= B_PARTITION_FILE_SYSTEM; 113 partition->content_size = partition->size; 114 // TODO: not actually correct 115 partition->block_size = volumeDescriptor.logical_block_size(); 116 117 UdfString name(volumeDescriptor.logical_volume_identifier()); 118 partition->content_name = strdup(name.Utf8()); 119 if (partition->content_name == NULL) 120 return B_NO_MEMORY; 121 122 return B_OK; 123 } 124 125 126 static void 127 udf_free_identify_partition_cookie(partition_data *partition, void *cookie) 128 { 129 delete (identify_cookie *)cookie; 130 } 131 132 133 static status_t 134 udf_unmount(fs_volume *_volume) 135 { 136 TRACE(("udb_unmount: _volume = %p\n", _volume)); 137 Volume *volume = (Volume *)_volume->private_volume; 138 delete volume; 139 return B_OK; 140 } 141 142 143 static status_t 144 udf_read_fs_stat(fs_volume *_volume, struct fs_info *info) 145 { 146 TRACE(("udf_read_fs_stat: _volume = %p, info = %p\n", _volume, info)); 147 148 Volume *volume = (Volume *)_volume->private_volume; 149 150 // File system flags. 151 info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY; 152 153 info->io_size = 65536; 154 // whatever is appropriate here? Just use the same value as BFS (and iso9660) for now 155 156 info->block_size = volume->BlockSize(); 157 info->total_blocks = volume->Length(); 158 info->free_blocks = 0; 159 160 // Volume name 161 sprintf(info->volume_name, "%s", volume->Name()); 162 163 // File system name 164 strcpy(info->fsh_name, "udf"); 165 166 return B_OK; 167 } 168 169 170 static status_t 171 udf_get_vnode(fs_volume *_volume, ino_t id, fs_vnode *node, int *_type, 172 uint32 *_flags, bool reenter) 173 { 174 TRACE(("udf_get_vnode: _volume = %p, _node = %p, reenter = %s\n", 175 _volume, _node, (reenter ? "true" : "false"))); 176 177 Volume *volume = (Volume *)_volume->private_volume; 178 179 // Convert the given vnode id to an address, and create 180 // and return a corresponding Icb object for it. 181 TRACE(("udf_get_vnode: id = %Ld, blockSize = %lu\n", id, 182 volume->BlockSize())); 183 184 Icb *icb = new(std::nothrow) Icb(volume, 185 to_long_address(id, volume->BlockSize())); 186 if (icb == NULL) 187 return B_NO_MEMORY; 188 189 if (icb->InitCheck() == B_OK) { 190 node->private_node = icb; 191 node->ops = &gUDFVnodeOps; 192 *_type = icb->Mode(); 193 *_flags = 0; 194 return B_OK; 195 } 196 197 TRACE_ERROR(("udf_get_vnode: InitCheck failed\n")); 198 delete icb; 199 return B_ERROR; 200 } 201 202 203 // #pragma mark - fs_vnode_ops functions 204 205 206 static status_t 207 udf_lookup(fs_volume *_volume, fs_vnode *_directory, const char *file, 208 ino_t *vnodeID) 209 { 210 TRACE(("udf_lookup: _directory = %p, filename = %s\n", _directory, file)); 211 212 Volume *volume = (Volume *)_volume->private_volume; 213 Icb *dir = (Icb *)_directory->private_node; 214 Icb *node = NULL; 215 216 status_t status = B_OK; 217 218 if (strcmp(file, ".") == 0) { 219 TRACE(("udf_lookup: file = ./\n")); 220 *vnodeID = dir->Id(); 221 status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&node); 222 if (status != B_OK) 223 return B_ENTRY_NOT_FOUND; 224 } else { 225 status = dir->Find(file, vnodeID); 226 if (status != B_OK) 227 return status; 228 229 Icb *icb; 230 status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&icb); 231 if (status != B_OK) 232 return B_ENTRY_NOT_FOUND; 233 } 234 TRACE(("udf_lookup: vnodeId = %Ld found!\n", *vnodeID)); 235 236 return B_OK; 237 } 238 239 240 static status_t 241 udf_put_vnode(fs_volume *volume, fs_vnode *node, bool reenter) 242 { 243 TRACE(("udf_put_vnode: volume = %p, node = %p\n", volume, node)); 244 // No debug-to-file in release_vnode; can cause a deadlock in 245 // rare circumstances. 246 #if !DEBUG_TO_FILE 247 DEBUG_INIT_ETC(NULL, ("node: %p", node)); 248 #endif 249 Icb *icb = (Icb *)node->private_node; 250 delete icb; 251 #if !DEBUG_TO_FILE 252 RETURN(B_OK); 253 #else 254 return B_OK; 255 #endif 256 } 257 258 259 static status_t 260 udf_remove_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) 261 { 262 TRACE(("udf_remove_vnode: _volume = %p, _node = %p\n", _volume, _node)); 263 return B_ERROR; 264 } 265 266 267 static status_t 268 udf_read_stat(fs_volume *_volume, fs_vnode *node, struct stat *stat) 269 { 270 TRACE(("udf_read_stat: _volume = %p, node = %p\n", _volume, node)); 271 272 if (!_volume || !node || !stat) 273 return B_BAD_VALUE; 274 275 Volume *volume = (Volume *)_volume->private_volume; 276 Icb *icb = (Icb *)node->private_node; 277 278 stat->st_dev = volume->ID(); 279 stat->st_ino = icb->Id(); 280 stat->st_nlink = icb->FileLinkCount(); 281 stat->st_blksize = volume->BlockSize(); 282 283 TRACE(("udf_read_stat: st_dev = %ld, st_ino = %Ld, st_blksize = %d\n", 284 stat->st_dev, stat->st_ino, stat->st_blksize)); 285 286 stat->st_uid = icb->Uid(); 287 stat->st_gid = icb->Gid(); 288 289 stat->st_mode = icb->Mode(); 290 stat->st_size = icb->Length(); 291 stat->st_blocks = (stat->st_size + 511) / 512; 292 293 // File times. For now, treat the modification time as creation 294 // time as well, since true creation time is an optional extended 295 // attribute, and supporting EAs is going to be a PITA. ;-) 296 stat->st_atime = icb->AccessTime(); 297 stat->st_mtime = stat->st_ctime = stat->st_crtime = icb->ModificationTime(); 298 299 TRACE(("udf_read_stat: mode = 0x%x, st_ino: %Ld\n", stat->st_mode, 300 stat->st_ino)); 301 302 return B_OK; 303 } 304 305 306 static status_t 307 udf_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie) 308 { 309 TRACE(("udf_open: _volume = %p, _node = %p\n", _volume, _node)); 310 return B_OK; 311 } 312 313 314 static status_t 315 udf_close(fs_volume* _volume, fs_vnode* _node, void* _cookie) 316 { 317 TRACE(("udf_close: _volume = %p, _node = %p\n", _volume, _node)); 318 return B_OK; 319 } 320 321 322 static status_t 323 udf_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) 324 { 325 TRACE(("udf_free_cookie: _volume = %p, _node = %p\n", _volume, _node)); 326 return B_OK; 327 } 328 329 330 static status_t 331 udf_access(fs_volume* _volume, fs_vnode* _node, int accessMode) 332 { 333 TRACE(("udf_access: _volume = %p, _node = %p\n", _volume, _node)); 334 return B_OK; 335 } 336 337 338 static status_t 339 udf_read(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos, 340 void *buffer, size_t *length) 341 { 342 TRACE(("udf_read: ID = %ld, pos = %lld, length = %lu\n", 343 ((Volume *)volume->private_volume)->ID(), pos, *length)); 344 345 Icb *icb = (Icb *)vnode->private_node; 346 347 // if (!inode->HasUserAccessableStream()) { 348 // *_length = 0; 349 // RETURN_ERROR(B_BAD_VALUE); 350 // } 351 352 RETURN(icb->Read(pos, buffer, length)); 353 } 354 355 356 static status_t 357 udf_io(fs_volume *volume, fs_vnode *vnode, void *cookie, io_request *request) 358 { 359 if (io_request_is_write(request)) { 360 notify_io_request(request, B_READ_ONLY_DEVICE); 361 return B_READ_ONLY_DEVICE; 362 } 363 364 Icb *icb = (Icb *)vnode->private_node; 365 if (icb->FileCache() == NULL) { 366 notify_io_request(request, B_BAD_VALUE); 367 return B_BAD_VALUE; 368 } 369 370 return do_iterative_fd_io(((Volume *)volume->private_volume)->Device(), 371 request, iterative_io_get_vecs_hook, iterative_io_finished_hook, icb); 372 } 373 374 375 static status_t 376 udf_get_file_map(fs_volume *_volume, fs_vnode *vnode, off_t offset, size_t size, 377 struct file_io_vec *vecs, size_t *count) 378 { 379 Icb *icb = (Icb *)vnode->private_node; 380 return icb->GetFileMap(offset, size, vecs, count); 381 } 382 383 384 static status_t 385 udf_open_dir(fs_volume *volume, fs_vnode *vnode, void **cookie) 386 { 387 TRACE(("udf_open_dir: volume = %p, vnode = %p\n", volume, vnode)); 388 389 if (!volume || !vnode || !cookie) 390 RETURN(B_BAD_VALUE); 391 392 Icb *dir = (Icb *)vnode->private_node; 393 394 if (!dir->IsDirectory()) { 395 TRACE_ERROR(("udf_open_dir: given Icb is not a directory (type: %d)\n", 396 dir->Type())); 397 return B_NOT_A_DIRECTORY; 398 } 399 400 DirectoryIterator *iterator = NULL; 401 status_t status = dir->GetDirectoryIterator(&iterator); 402 if (status != B_OK) { 403 TRACE_ERROR(("udf_open_dir: error getting directory iterator: 0x%lx, " 404 "`%s'\n", status, strerror(status))); 405 return status; 406 } 407 *cookie = (void *)iterator; 408 TRACE(("udf_open_dir: *cookie = %p\n", *cookie)); 409 410 return B_OK; 411 } 412 413 414 static status_t 415 udf_close_dir(fs_volume *_volume, fs_vnode *node, void *_cookie) 416 { 417 TRACE(("udf_close_dir: _volume = %p, node = %p\n", _volume, node)); 418 return B_OK; 419 } 420 421 422 static status_t 423 udf_free_dir_cookie(fs_volume *_volume, fs_vnode *node, void *_cookie) 424 { 425 TRACE(("udf_free_dir_cookie: _volume = %p, node = %p\n", _volume, node)); 426 return B_OK; 427 } 428 429 430 static status_t 431 udf_read_dir(fs_volume *_volume, fs_vnode *vnode, void *cookie, 432 struct dirent *dirent, size_t bufferSize, uint32 *_num) 433 { 434 TRACE(("udf_read_dir: _volume = %p, vnode = %p, bufferSize = %ld\n", 435 _volume, vnode, bufferSize)); 436 437 if (!_volume || !vnode || !cookie || !_num 438 || bufferSize < sizeof(struct dirent)) { 439 return B_BAD_VALUE; 440 } 441 442 Volume *volume = (Volume *)_volume->private_volume; 443 Icb *dir = (Icb *)vnode->private_node; 444 DirectoryIterator *iterator = (DirectoryIterator *)cookie; 445 446 if (dir != iterator->Parent()) { 447 TRACE_ERROR(("udf_read_dir: Icb does not match parent Icb of given " 448 "DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent())); 449 return B_BAD_VALUE; 450 } 451 452 uint32 nameLength = bufferSize - sizeof(struct dirent) + 1; 453 ino_t id; 454 status_t status = iterator->GetNextEntry(dirent->d_name, &nameLength, &id); 455 if (!status) { 456 TRACE(("udf_read_dir: dirent->d_name = %s, length = %ld\n", dirent->d_name, nameLength)); 457 *_num = 1; 458 dirent->d_dev = volume->ID(); 459 dirent->d_ino = id; 460 dirent->d_reclen = sizeof(struct dirent) + nameLength - 1; 461 } else { 462 *_num = 0; 463 // Clear the status for end of directory 464 if (status == B_ENTRY_NOT_FOUND) 465 status = B_OK; 466 } 467 468 RETURN(status); 469 } 470 471 472 status_t 473 udf_rewind_dir(fs_volume *volume, fs_vnode *vnode, void *cookie) 474 { 475 TRACE(("udf_rewind_dir: volume = %p, vnode = %p, cookie = %p\n", 476 volume, vnode, cookie)); 477 478 if (!volume || !vnode || !cookie) 479 RETURN(B_BAD_VALUE); 480 481 Icb *dir = (Icb *)vnode->private_node; 482 DirectoryIterator *iterator = (DirectoryIterator *)cookie; 483 484 if (dir != iterator->Parent()) { 485 PRINT(("udf_rewind_dir: icb does not match parent Icb of given " 486 "DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent())); 487 return B_BAD_VALUE; 488 } 489 490 iterator->Rewind(); 491 492 return B_OK; 493 } 494 495 496 // #pragma mark - 497 498 499 /*! \brief mount 500 501 \todo I'm using the B_GET_GEOMETRY ioctl() to find out where the end of the 502 partition is. This won't work for handling multi-session semantics correctly. 503 To support them correctly in R5 I need either: 504 - A way to get the proper info (best) 505 - To ignore trying to find anchor volume descriptor pointers at 506 locations N-256 and N. (acceptable, perhaps, but not really correct) 507 Either way we should address this problem properly for OBOS::R1. 508 \todo Looks like B_GET_GEOMETRY doesn't work on non-device files (i.e. 509 disk images), so I need to use stat or something else for those 510 instances. 511 */ 512 static status_t 513 udf_mount(fs_volume *_volume, const char *_device, uint32 flags, 514 const char *args, ino_t *_rootVnodeID) 515 { 516 TRACE(("udf_mount: device = %s\n", _device)); 517 status_t status = B_OK; 518 Volume *volume = NULL; 519 off_t deviceOffset = 0; 520 off_t numBlock = 0; 521 partition_info info; 522 device_geometry geometry; 523 524 // Here we need to figure out the length of the device, and if we're 525 // attempting to open a multisession volume, we need to figure out the 526 // offset into the raw disk at which the volume begins, then open 527 // the raw volume itself instead of the fake partition device the 528 // kernel gives us, since multisession UDF volumes are allowed to access 529 // the data in their own partition, as well as the data in any partitions 530 // that precede them physically on the disc. 531 int device = open(_device, O_RDONLY); 532 status = device < B_OK ? device : B_OK; 533 if (!status) { 534 // First try to treat the device like a special partition device. If that's 535 // what we have, then we can use the partition_info data to figure out the 536 // name of the raw device (which we'll open instead), the offset into the 537 // raw device at which the volume of interest will begin, and the total 538 // length from the beginning of the raw device that we're allowed to access. 539 // 540 // If that fails, then we try to treat the device as an actual raw device, 541 // and see if we can get the device size with B_GET_GEOMETRY syscall, since 542 // stat()ing a raw device appears to not work. 543 // 544 // Finally, if that also fails, we're probably stuck with trying to mount 545 // a regular file, so we just stat() it to get the device size. 546 // 547 // If that fails, you're just SOL. 548 549 if (ioctl(device, B_GET_PARTITION_INFO, &info) == 0) { 550 TRACE(("partition_info:\n")); 551 TRACE(("\toffset: %Ld\n", info.offset)); 552 TRACE(("\tsize: %Ld\n", info.size)); 553 TRACE(("\tlogical_block_size: %ld\n", info.logical_block_size)); 554 TRACE(("\tsession: %ld\n", info.session)); 555 TRACE(("\tpartition: %ld\n", info.partition)); 556 TRACE(("\tdevice: `%s'\n", info.device)); 557 _device = info.device; 558 deviceOffset = info.offset / info.logical_block_size; 559 numBlock = deviceOffset + info.size / info.logical_block_size; 560 } else if (ioctl(device, B_GET_GEOMETRY, &geometry) == 0) { 561 TRACE(("geometry_info:\n")); 562 TRACE(("\tsectors_per_track: %ld\n", geometry.sectors_per_track)); 563 TRACE(("\tcylinder_count: %ld\n", geometry.cylinder_count)); 564 TRACE(("\thead_count: %ld\n", geometry.head_count)); 565 deviceOffset = 0; 566 numBlock = (off_t)geometry.sectors_per_track 567 * geometry.cylinder_count * geometry.head_count; 568 } else { 569 struct stat stat; 570 status = fstat(device, &stat) < 0 ? B_ERROR : B_OK; 571 if (!status) { 572 TRACE(("stat_info:\n")); 573 TRACE(("\tst_size: %Ld\n", stat.st_size)); 574 deviceOffset = 0; 575 numBlock = stat.st_size / 2048; 576 } 577 } 578 // Close the device 579 close(device); 580 } 581 582 // Create and mount the volume 583 volume = new(std::nothrow) Volume(_volume); 584 status = volume->Mount(_device, deviceOffset, numBlock, 2048, flags); 585 if (status != B_OK) { 586 delete volume; 587 return status; 588 } 589 590 _volume->private_volume = volume; 591 _volume->ops = &gUDFVolumeOps; 592 *_rootVnodeID = volume->RootIcb()->Id(); 593 594 TRACE(("udf_mount: succefully mounted the partition\n")); 595 return B_OK; 596 } 597 598 599 // #pragma mark - 600 601 602 static status_t 603 udf_std_ops(int32 op, ...) 604 { 605 switch (op) { 606 case B_MODULE_INIT: 607 case B_MODULE_UNINIT: 608 return B_OK; 609 default: 610 return B_ERROR; 611 } 612 } 613 614 fs_volume_ops gUDFVolumeOps = { 615 &udf_unmount, 616 &udf_read_fs_stat, 617 NULL, // write_fs_stat 618 NULL, // sync 619 &udf_get_vnode, 620 621 /* index directory & index operations */ 622 NULL, // open_index_dir 623 NULL, // close_index_dir 624 NULL, // free_index_dir_cookie 625 NULL, // read_index_dir 626 NULL, // rewind_index_dir 627 NULL, // create_index 628 NULL, // remove_index 629 NULL, // read_index_stat 630 631 /* query operations */ 632 NULL, // open_query 633 NULL, // close_query 634 NULL, // free_query_cookie 635 NULL, // read_query 636 NULL, // rewind_query 637 638 /* support for FS layers */ 639 NULL, // create_sub_vnode 640 NULL, // delete_sub_vnode 641 }; 642 643 fs_vnode_ops gUDFVnodeOps = { 644 /* vnode operatoins */ 645 &udf_lookup, 646 NULL, // get_vnode_name 647 &udf_put_vnode, 648 &udf_remove_vnode, 649 650 /* VM file access */ 651 NULL, // can_page 652 NULL, // read_pages 653 NULL, // write_pages 654 655 /* asynchronous I/O */ 656 &udf_io, 657 NULL, // cancel_io() 658 659 /* cache file access */ 660 &udf_get_file_map, 661 662 /* common operations */ 663 NULL, // ioctl 664 NULL, // set_flags 665 NULL, // select 666 NULL, // deselect 667 NULL, // fsync 668 NULL, // read_symlink 669 NULL, // create_symlnk 670 NULL, // link 671 NULL, // unlink 672 NULL, // rename 673 &udf_access, 674 &udf_read_stat, 675 NULL, // write_stat 676 NULL, // preallocate 677 678 /* file operations */ 679 NULL, // create 680 &udf_open, 681 &udf_close, 682 &udf_free_cookie, 683 &udf_read, 684 NULL, // write 685 686 /* directory operations */ 687 NULL, // create_dir 688 NULL, // remove_dir 689 &udf_open_dir, 690 &udf_close_dir, 691 &udf_free_dir_cookie, 692 &udf_read_dir, 693 &udf_rewind_dir, 694 695 /* attribue directory operations */ 696 NULL, // open_attr_dir 697 NULL, // close_attr_dir 698 NULL, // free_attr_dir_cookie 699 NULL, // read_attr_dir 700 NULL, // rewind_attr_dir 701 702 /* attribute operations */ 703 NULL, // create_attr 704 NULL, // open_attr 705 NULL, // close_attr 706 NULL, // free_attr_cookie 707 NULL, // read_attr 708 NULL, // write_attr 709 NULL, // read_attr_stat 710 NULL, // write_attr_stat 711 NULL, // rename_attr 712 NULL, // remove_attr 713 714 /* support for node and FS layers */ 715 NULL, // create_special_node 716 NULL // get_super_vnode 717 718 }; 719 720 static file_system_module_info sUDFFileSystem = { 721 { 722 "file_systems/udf" B_CURRENT_FS_API_VERSION, 723 0, 724 udf_std_ops, 725 }, 726 727 "udf", // short_name 728 "UDF File System", // pretty_name 729 0, // DDM flags 730 731 &udf_identify_partition, 732 &udf_scan_partition, 733 &udf_free_identify_partition_cookie, 734 NULL, // free_partition_content_cookie() 735 736 &udf_mount, 737 738 NULL, 739 }; 740 741 module_info *modules[] = { 742 (module_info *)&sUDFFileSystem, 743 NULL, 744 }; 745