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