/* * Copyright 2012, Jérôme Duval, korli@users.berlios.de. * Copyright 2010, Michael Lotz, mmlr@mlotz.ch. * Copyright 2008, Salvatore Benedetto, salvatore.benedetto@gmail.com. * Copyright 2003, Tyler Dauwalder, tyler@dauwalder.net. * Distributed under the terms of the MIT License. */ /*! \file kernel_interface.cpp */ #include #include #include #include #include #include #include #include #include #include "Icb.h" #include "Recognition.h" #include "Utils.h" #include "Volume.h" #undef TRACE #undef TRACE_ERROR //#define UDF_KERNEL_INTERFACE_DEBUG #ifdef UDF_KERNEL_INTERFACE_DEBUG # define TRACE(x) dprintf x # define TRACE_ERROR(x) dprintf x #else # define TRACE(x) /* nothing */ # define TRACE_ERROR(x) dprintf x #endif extern fs_volume_ops gUDFVolumeOps; extern fs_vnode_ops gUDFVnodeOps; struct identify_cookie { struct logical_volume_descriptor logical_volume_descriptor; }; // #pragma mark - io callbacks static status_t iterative_io_get_vecs_hook(void *cookie, io_request *request, off_t offset, size_t size, struct file_io_vec *vecs, size_t *count) { Icb *icb = (Icb *)cookie; return file_map_translate(icb->FileMap(), offset, size, vecs, count, icb->GetVolume()->BlockSize()); } static status_t iterative_io_finished_hook(void *cookie, io_request *request, status_t status, bool partialTransfer, size_t bytesTransferred) { // nothing to do return B_OK; } // #pragma mark - fs_volume_ops fuctions static float udf_identify_partition(int fd, partition_data *partition, void **_cookie) { TRACE(("udf_identify_partition: fd = %d, id = %ld, offset = %lld, size = %lld " "content_size = %lld, block_size = %lu\n", fd, partition->id, partition->offset, partition->size, partition->content_size, partition->block_size)); primary_volume_descriptor primaryVolumeDescriptor; logical_volume_descriptor logicalVolumeDescriptor; partition_descriptor partitionDescriptors[kMaxPartitionDescriptors]; uint8 descriptorCount = kMaxPartitionDescriptors; uint32 blockShift; status_t error = udf_recognize(fd, partition->offset, partition->size, partition->block_size, blockShift, primaryVolumeDescriptor, logicalVolumeDescriptor, partitionDescriptors, descriptorCount); if (error != B_OK) return -1; identify_cookie *cookie = new(std::nothrow) identify_cookie; if (cookie == NULL) return -1; cookie->logical_volume_descriptor = logicalVolumeDescriptor; *_cookie = cookie; return 0.8f; } static status_t udf_scan_partition(int fd, partition_data *partition, void *_cookie) { TRACE(("udf_scan_partition: fd = %d\n", fd)); identify_cookie *cookie = (identify_cookie *)_cookie; logical_volume_descriptor &volumeDescriptor = cookie->logical_volume_descriptor; partition->status = B_PARTITION_VALID; partition->flags |= B_PARTITION_FILE_SYSTEM; partition->content_size = partition->size; // TODO: not actually correct partition->block_size = volumeDescriptor.logical_block_size(); UdfString name(volumeDescriptor.logical_volume_identifier()); partition->content_name = strdup(name.Utf8()); if (partition->content_name == NULL) return B_NO_MEMORY; return B_OK; } static void udf_free_identify_partition_cookie(partition_data *partition, void *cookie) { delete (identify_cookie *)cookie; } static status_t udf_unmount(fs_volume *_volume) { TRACE(("udb_unmount: _volume = %p\n", _volume)); Volume *volume = (Volume *)_volume->private_volume; delete volume; return B_OK; } static status_t udf_read_fs_stat(fs_volume *_volume, struct fs_info *info) { TRACE(("udf_read_fs_stat: _volume = %p, info = %p\n", _volume, info)); Volume *volume = (Volume *)_volume->private_volume; // File system flags. info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY; info->io_size = 65536; // whatever is appropriate here? Just use the same value as BFS (and iso9660) for now info->block_size = volume->BlockSize(); info->total_blocks = volume->Length(); info->free_blocks = 0; // Volume name sprintf(info->volume_name, "%s", volume->Name()); // File system name strcpy(info->fsh_name, "udf"); return B_OK; } static status_t udf_get_vnode(fs_volume *_volume, ino_t id, fs_vnode *node, int *_type, uint32 *_flags, bool reenter) { TRACE(("udf_get_vnode: _volume = %p, _node = %p, reenter = %s\n", _volume, _node, (reenter ? "true" : "false"))); Volume *volume = (Volume *)_volume->private_volume; // Convert the given vnode id to an address, and create // and return a corresponding Icb object for it. TRACE(("udf_get_vnode: id = %lld, blockSize = %lu\n", id, volume->BlockSize())); Icb *icb = new(std::nothrow) Icb(volume, to_long_address(id, volume->BlockSize())); if (icb == NULL) return B_NO_MEMORY; if (icb->InitCheck() == B_OK) { node->private_node = icb; node->ops = &gUDFVnodeOps; *_type = icb->Mode(); *_flags = 0; return B_OK; } TRACE_ERROR(("udf_get_vnode: InitCheck failed\n")); delete icb; return B_ERROR; } // #pragma mark - fs_vnode_ops functions static status_t udf_lookup(fs_volume *_volume, fs_vnode *_directory, const char *file, ino_t *vnodeID) { TRACE(("udf_lookup: _directory = %p, filename = %s\n", _directory, file)); Volume *volume = (Volume *)_volume->private_volume; Icb *dir = (Icb *)_directory->private_node; Icb *node = NULL; status_t status = B_OK; if (strcmp(file, ".") == 0) { TRACE(("udf_lookup: file = ./\n")); *vnodeID = dir->Id(); status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&node); if (status != B_OK) return B_ENTRY_NOT_FOUND; } else { status = dir->Find(file, vnodeID); if (status != B_OK) return status; Icb *icb; status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&icb); if (status != B_OK) return B_ENTRY_NOT_FOUND; } TRACE(("udf_lookup: vnodeId = %lld found!\n", *vnodeID)); return B_OK; } static status_t udf_put_vnode(fs_volume *volume, fs_vnode *node, bool reenter) { TRACE(("udf_put_vnode: volume = %p, node = %p\n", volume, node)); // No debug-to-file in release_vnode; can cause a deadlock in // rare circumstances. #if !DEBUG_TO_FILE DEBUG_INIT_ETC(NULL, ("node: %p", node)); #endif Icb *icb = (Icb *)node->private_node; delete icb; #if !DEBUG_TO_FILE RETURN(B_OK); #else return B_OK; #endif } static status_t udf_remove_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) { TRACE(("udf_remove_vnode: _volume = %p, _node = %p\n", _volume, _node)); return B_ERROR; } static status_t udf_read_stat(fs_volume *_volume, fs_vnode *node, struct stat *stat) { TRACE(("udf_read_stat: _volume = %p, node = %p\n", _volume, node)); if (!_volume || !node || !stat) return B_BAD_VALUE; Volume *volume = (Volume *)_volume->private_volume; Icb *icb = (Icb *)node->private_node; stat->st_dev = volume->ID(); stat->st_ino = icb->Id(); stat->st_nlink = icb->FileLinkCount(); stat->st_blksize = volume->BlockSize(); TRACE(("udf_read_stat: st_dev = %ld, st_ino = %lld, st_blksize = %d\n", stat->st_dev, stat->st_ino, stat->st_blksize)); stat->st_uid = icb->Uid(); stat->st_gid = icb->Gid(); stat->st_mode = icb->Mode(); stat->st_size = icb->Length(); stat->st_blocks = (stat->st_size + 511) / 512; // File times. For now, treat the modification time as creation // time as well, since true creation time is an optional extended // attribute, and supporting EAs is going to be a PITA. ;-) icb->GetAccessTime(stat->st_atim); icb->GetModificationTime(stat->st_mtim); icb->GetModificationTime(stat->st_ctim); icb->GetModificationTime(stat->st_crtim); //icb->GetChangeTime(stat->st_ctim); //icb->GetCreationTime(stat->st_crtim); TRACE(("udf_read_stat: mode = 0x%x, st_ino: %lld\n", stat->st_mode, stat->st_ino)); return B_OK; } static status_t udf_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie) { TRACE(("udf_open: _volume = %p, _node = %p\n", _volume, _node)); return B_OK; } static status_t udf_close(fs_volume* _volume, fs_vnode* _node, void* _cookie) { TRACE(("udf_close: _volume = %p, _node = %p\n", _volume, _node)); return B_OK; } static status_t udf_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) { TRACE(("udf_free_cookie: _volume = %p, _node = %p\n", _volume, _node)); return B_OK; } static status_t udf_access(fs_volume* _volume, fs_vnode* _node, int accessMode) { TRACE(("udf_access: _volume = %p, _node = %p\n", _volume, _node)); return B_OK; } static status_t udf_read(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos, void *buffer, size_t *length) { TRACE(("udf_read: ID = %" B_PRIdDEV ", pos = %" B_PRIdOFF ", " "length = %lu\n", ((Volume *)volume->private_volume)->ID(), pos, *length)); Icb *icb = (Icb *)vnode->private_node; DEBUG_INIT_ETC("udf_read", ("ID = %" B_PRIdDEV ", pos = %" B_PRIdOFF ", " "length = %lu", ((Volume *)volume->private_volume)->ID(), pos, *length)); // if (!inode->HasUserAccessableStream()) { // *_length = 0; // RETURN_ERROR(B_BAD_VALUE); // } RETURN(icb->Read(pos, buffer, length)); } static status_t udf_io(fs_volume *volume, fs_vnode *vnode, void *cookie, io_request *request) { if (io_request_is_write(request)) { notify_io_request(request, B_READ_ONLY_DEVICE); return B_READ_ONLY_DEVICE; } Icb *icb = (Icb *)vnode->private_node; if (icb->FileCache() == NULL) { notify_io_request(request, B_BAD_VALUE); return B_BAD_VALUE; } return do_iterative_fd_io(((Volume *)volume->private_volume)->Device(), request, iterative_io_get_vecs_hook, iterative_io_finished_hook, icb); } static status_t udf_get_file_map(fs_volume *_volume, fs_vnode *vnode, off_t offset, size_t size, struct file_io_vec *vecs, size_t *count) { Icb *icb = (Icb *)vnode->private_node; return icb->GetFileMap(offset, size, vecs, count); } static status_t udf_open_dir(fs_volume *volume, fs_vnode *vnode, void **cookie) { TRACE(("udf_open_dir: volume = %p, vnode = %p\n", volume, vnode)); DEBUG_INIT_ETC("udf_open_dir", ("ID = %" B_PRIdDEV, ((Volume *)volume->private_volume)->ID())); if (!volume || !vnode || !cookie) RETURN(B_BAD_VALUE); Icb *dir = (Icb *)vnode->private_node; if (!dir->IsDirectory()) { TRACE_ERROR(("udf_open_dir: given Icb is not a directory (type: %d)\n", dir->Type())); return B_NOT_A_DIRECTORY; } DirectoryIterator *iterator = NULL; status_t status = dir->GetDirectoryIterator(&iterator); if (status != B_OK) { TRACE_ERROR(("udf_open_dir: error getting directory iterator: 0x%" B_PRIx32 ", `%s'\n", status, strerror(status))); return status; } *cookie = (void *)iterator; TRACE(("udf_open_dir: *cookie = %p\n", *cookie)); return B_OK; } static status_t udf_close_dir(fs_volume *_volume, fs_vnode *node, void *_cookie) { TRACE(("udf_close_dir: _volume = %p, node = %p\n", _volume, node)); return B_OK; } static status_t udf_free_dir_cookie(fs_volume *_volume, fs_vnode *node, void *_cookie) { TRACE(("udf_free_dir_cookie: _volume = %p, node = %p\n", _volume, node)); return B_OK; } static status_t udf_read_dir(fs_volume *_volume, fs_vnode *vnode, void *cookie, struct dirent *dirent, size_t bufferSize, uint32 *_num) { TRACE(("udf_read_dir: _volume = %p, vnode = %p, bufferSize = %ld\n", _volume, vnode, bufferSize)); if (!_volume || !vnode || !cookie || !_num || bufferSize < sizeof(struct dirent)) { return B_BAD_VALUE; } Volume *volume = (Volume *)_volume->private_volume; Icb *dir = (Icb *)vnode->private_node; DirectoryIterator *iterator = (DirectoryIterator *)cookie; DEBUG_INIT_ETC("udf_read_dir", ("ID = %" B_PRIdDEV , volume->ID())); if (dir != iterator->Parent()) { TRACE_ERROR(("udf_read_dir: Icb does not match parent Icb of given " "DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent())); return B_BAD_VALUE; } uint32 nameLength = bufferSize - offsetof(struct dirent, d_name); ino_t id; status_t status = iterator->GetNextEntry(dirent->d_name, &nameLength, &id); if (!status) { TRACE(("udf_read_dir: dirent->d_name = %s, length = %ld\n", dirent->d_name, nameLength)); *_num = 1; dirent->d_dev = volume->ID(); dirent->d_ino = id; dirent->d_reclen = offsetof(struct dirent, d_name) + nameLength + 1; } else { *_num = 0; // Clear the status for end of directory if (status == B_ENTRY_NOT_FOUND) status = B_OK; } RETURN(status); } status_t udf_rewind_dir(fs_volume *volume, fs_vnode *vnode, void *cookie) { TRACE(("udf_rewind_dir: volume = %p, vnode = %p, cookie = %p\n", volume, vnode, cookie)); DEBUG_INIT_ETC("udf_rewind_dir", ("ID = %" B_PRIdDEV, ((Volume *)volume->private_volume)->ID())); if (!volume || !vnode || !cookie) RETURN(B_BAD_VALUE); Icb *dir = (Icb *)vnode->private_node; DirectoryIterator *iterator = (DirectoryIterator *)cookie; if (dir != iterator->Parent()) { PRINT(("udf_rewind_dir: icb does not match parent Icb of given " "DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent())); return B_BAD_VALUE; } iterator->Rewind(); return B_OK; } // #pragma mark - /*! \brief mount \todo I'm using the B_GET_GEOMETRY ioctl() to find out where the end of the partition is. This won't work for handling multi-session semantics correctly. To support them correctly in R5 I need either: - A way to get the proper info (best) - To ignore trying to find anchor volume descriptor pointers at locations N-256 and N. (acceptable, perhaps, but not really correct) Either way we should address this problem properly for Haiku::R1. \todo Looks like B_GET_GEOMETRY doesn't work on non-device files (i.e. disk images), so I need to use stat or something else for those instances. */ static status_t udf_mount(fs_volume *_volume, const char *_device, uint32 flags, const char *args, ino_t *_rootVnodeID) { TRACE(("udf_mount: device = %s\n", _device)); status_t status = B_OK; Volume *volume = NULL; off_t deviceOffset = 0; off_t numBlock = 0; partition_info info; device_geometry geometry; // Here we need to figure out the length of the device, and if we're // attempting to open a multisession volume, we need to figure out the // offset into the raw disk at which the volume begins, then open // the raw volume itself instead of the fake partition device the // kernel gives us, since multisession UDF volumes are allowed to access // the data in their own partition, as well as the data in any partitions // that precede them physically on the disc. int device = open(_device, O_RDONLY); status = device < B_OK ? device : B_OK; if (!status) { // First try to treat the device like a special partition device. If that's // what we have, then we can use the partition_info data to figure out the // name of the raw device (which we'll open instead), the offset into the // raw device at which the volume of interest will begin, and the total // length from the beginning of the raw device that we're allowed to access. // // If that fails, then we try to treat the device as an actual raw device, // and see if we can get the device size with B_GET_GEOMETRY syscall, since // stat()ing a raw device appears to not work. // // Finally, if that also fails, we're probably stuck with trying to mount // a regular file, so we just stat() it to get the device size. // // If that fails, you're just SOL. if (ioctl(device, B_GET_PARTITION_INFO, &info, sizeof(partition_info)) == 0) { TRACE(("partition_info:\n")); TRACE(("\toffset: %lld\n", info.offset)); TRACE(("\tsize: %lld\n", info.size)); TRACE(("\tlogical_block_size: %ld\n", info.logical_block_size)); TRACE(("\tsession: %ld\n", info.session)); TRACE(("\tpartition: %ld\n", info.partition)); TRACE(("\tdevice: `%s'\n", info.device)); _device = info.device; deviceOffset = info.offset / info.logical_block_size; numBlock = deviceOffset + info.size / info.logical_block_size; } else if (ioctl(device, B_GET_GEOMETRY, &geometry, sizeof(device_geometry)) == 0) { TRACE(("geometry_info:\n")); TRACE(("\tsectors_per_track: %ld\n", geometry.sectors_per_track)); TRACE(("\tcylinder_count: %ld\n", geometry.cylinder_count)); TRACE(("\thead_count: %ld\n", geometry.head_count)); deviceOffset = 0; numBlock = (off_t)geometry.sectors_per_track * geometry.cylinder_count * geometry.head_count; } else { struct stat stat; status = fstat(device, &stat) < 0 ? B_ERROR : B_OK; if (!status) { TRACE(("stat_info:\n")); TRACE(("\tst_size: %lld\n", stat.st_size)); deviceOffset = 0; numBlock = stat.st_size / 2048; } } // Close the device close(device); } // Create and mount the volume volume = new(std::nothrow) Volume(_volume); status = volume->Mount(_device, deviceOffset, numBlock, 2048, flags); if (status != B_OK) { delete volume; return status; } _volume->private_volume = volume; _volume->ops = &gUDFVolumeOps; *_rootVnodeID = volume->RootIcb()->Id(); TRACE(("udf_mount: succefully mounted the partition\n")); return B_OK; } // #pragma mark - static status_t udf_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: init_entities(); return B_OK; case B_MODULE_UNINIT: return B_OK; default: return B_ERROR; } } fs_volume_ops gUDFVolumeOps = { &udf_unmount, &udf_read_fs_stat, NULL, // write_fs_stat NULL, // sync &udf_get_vnode, /* index directory & index operations */ NULL, // open_index_dir NULL, // close_index_dir NULL, // free_index_dir_cookie NULL, // read_index_dir NULL, // rewind_index_dir NULL, // create_index NULL, // remove_index NULL, // read_index_stat /* query operations */ NULL, // open_query NULL, // close_query NULL, // free_query_cookie NULL, // read_query NULL, // rewind_query /* support for FS layers */ NULL, // create_sub_vnode NULL, // delete_sub_vnode }; fs_vnode_ops gUDFVnodeOps = { /* vnode operatoins */ &udf_lookup, NULL, // get_vnode_name &udf_put_vnode, &udf_remove_vnode, /* VM file access */ NULL, // can_page NULL, // read_pages NULL, // write_pages /* asynchronous I/O */ &udf_io, NULL, // cancel_io() /* cache file access */ &udf_get_file_map, /* common operations */ NULL, // ioctl NULL, // set_flags NULL, // select NULL, // deselect NULL, // fsync NULL, // read_symlink NULL, // create_symlnk NULL, // link NULL, // unlink NULL, // rename &udf_access, &udf_read_stat, NULL, // write_stat NULL, // preallocate /* file operations */ NULL, // create &udf_open, &udf_close, &udf_free_cookie, &udf_read, NULL, // write /* directory operations */ NULL, // create_dir NULL, // remove_dir &udf_open_dir, &udf_close_dir, &udf_free_dir_cookie, &udf_read_dir, &udf_rewind_dir, /* attribue directory operations */ NULL, // open_attr_dir NULL, // close_attr_dir NULL, // free_attr_dir_cookie NULL, // read_attr_dir NULL, // rewind_attr_dir /* attribute operations */ NULL, // create_attr NULL, // open_attr NULL, // close_attr NULL, // free_attr_cookie NULL, // read_attr NULL, // write_attr NULL, // read_attr_stat NULL, // write_attr_stat NULL, // rename_attr NULL, // remove_attr /* support for node and FS layers */ NULL, // create_special_node NULL // get_super_vnode }; static file_system_module_info sUDFFileSystem = { { "file_systems/udf" B_CURRENT_FS_API_VERSION, 0, udf_std_ops, }, "udf", // short_name "UDF File System", // pretty_name 0, // DDM flags &udf_identify_partition, &udf_scan_partition, &udf_free_identify_partition_cookie, NULL, // free_partition_content_cookie() &udf_mount, NULL, }; module_info *modules[] = { (module_info *)&sUDFFileSystem, NULL, };