/* * Copyright 1999, Be Incorporated. All Rights Reserved. * This file may be used under the terms of the Be Sample Code License. * * Copyright 2001, pinc Software. All Rights Reserved. * * iso9960/multi-session, 1.0.0 */ #include #include #ifndef FS_SHELL # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include // TODO: temporary solution as long as there is no public I/O requests API # include #endif #include "iso9660.h" #include "iso9660_identify.h" //#define TRACE_ISO9660 #ifdef TRACE_ISO9660 # define TRACE(x) dprintf x #else # define TRACE(x) ; #endif struct identify_cookie { iso9660_info info; }; extern fs_volume_ops gISO9660VolumeOps; extern fs_vnode_ops gISO9660VnodeOps; //! fs_io() callback hook 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) { iso9660_inode* node = (iso9660_inode*)cookie; vecs->offset = offset + ((off_t)node->startLBN[FS_DATA_FORMAT] * (off_t)node->volume->logicalBlkSize[FS_DATA_FORMAT]); vecs->length = size; *_count = 1; return B_OK; } //! fs_io() callback hook static status_t iterative_io_finished_hook(void* cookie, io_request* request, status_t status, bool partialTransfer, size_t bytesTransferred) { // nothing to do here... return B_OK; } // #pragma mark - Scanning static float fs_identify_partition(int fd, partition_data* partition, void** _cookie) { iso9660_info* info = new iso9660_info; status_t status = iso9660_fs_identify(fd, info); if (status != B_OK) { delete info; return -1; } *_cookie = info; return 0.6f; } static status_t fs_scan_partition(int fd, partition_data* partition, void* _cookie) { iso9660_info* info = (iso9660_info*)_cookie; partition->status = B_PARTITION_VALID; partition->flags |= B_PARTITION_FILE_SYSTEM | B_PARTITION_READ_ONLY ; partition->block_size = ISO_PVD_SIZE; partition->content_size = ISO_PVD_SIZE * info->max_blocks; partition->content_name = strdup(info->PreferredName()); if (partition->content_name == NULL) return B_NO_MEMORY; return B_OK; } static void fs_free_identify_partition_cookie(partition_data* partition, void* _cookie) { delete (iso9660_info*)_cookie; } // #pragma mark - FS hooks static status_t fs_mount(fs_volume* _volume, const char* device, uint32 flags, const char* args, ino_t* _rootID) { bool allowJoliet = true; iso9660_volume* volume; // Check for a 'nojoliet' parm // all we check for is the existance of 'nojoliet' in the parms. if (args != NULL) { uint32 i; char* spot; char* buf = strdup(args); uint32 len = strlen(buf); // lower case the parms data for (i = 0; i < len + 1; i++) buf[i] = tolower(buf[i]); // look for nojoliet spot = strstr(buf, "nojoliet"); if (spot != NULL) allowJoliet = false; free(buf); } // Try and mount volume as an ISO volume. status_t result = ISOMount(device, O_RDONLY, &volume, allowJoliet); if (result == B_OK) { *_rootID = ISO_ROOTNODE_ID; _volume->private_volume = volume; _volume->ops = &gISO9660VolumeOps; volume->volume = _volume; volume->id = _volume->id; result = publish_vnode(_volume, *_rootID, &volume->rootDirRec, &gISO9660VnodeOps, volume->rootDirRec.attr.stat[FS_DATA_FORMAT].st_mode, 0); if (result != B_OK) { block_cache_delete(volume->fBlockCache, false); free(volume); result = B_ERROR; } } return result; } static status_t fs_unmount(fs_volume* _volume) { status_t result = B_NO_ERROR; iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; TRACE(("fs_unmount - ENTER\n")); // Unlike in BeOS, we need to put the reference to our root node ourselves put_vnode(_volume, ISO_ROOTNODE_ID); block_cache_delete(volume->fBlockCache, false); close(volume->fdOfSession); result = close(volume->fd); free(volume); TRACE(("fs_unmount - EXIT, result is %s\n", strerror(result))); return result; } static status_t fs_read_fs_stat(fs_volume* _volume, struct fs_info* info) { iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY; info->block_size = volume->logicalBlkSize[FS_DATA_FORMAT]; info->io_size = 65536; info->total_blocks = volume->volSpaceSize[FS_DATA_FORMAT]; info->free_blocks = 0; strlcpy(info->device_name, volume->devicePath, sizeof(info->device_name)); strlcpy(info->volume_name, volume->volIDString, sizeof(info->volume_name)); // strip trailing spaces int i; for (i = strlen(info->volume_name) - 1; i >=0 ; i--) { if (info->volume_name[i] != ' ') break; } if (i < 0) strcpy(info->volume_name, "UNKNOWN"); else info->volume_name[i + 1] = 0; strcpy(info->fsh_name, "iso9660"); return B_OK; } static status_t fs_get_vnode_name(fs_volume* _volume, fs_vnode* _node, char* buffer, size_t bufferSize) { iso9660_inode* node = (iso9660_inode*)_node->private_node; strlcpy(buffer, node->name, bufferSize); return B_OK; } static status_t fs_walk(fs_volume* _volume, fs_vnode* _base, const char* file, ino_t* _vnodeID) { iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; iso9660_inode* baseNode = (iso9660_inode*)_base->private_node; iso9660_inode* newNode = NULL; TRACE(("fs_walk - looking for %s in dir file of length %d\n", file, (int)baseNode->dataLen[FS_DATA_FORMAT])); if (strcmp(file, ".") == 0) { // base directory TRACE(("fs_walk - found \".\" file.\n")); *_vnodeID = baseNode->id; return get_vnode(_volume, *_vnodeID, NULL); } else if (strcmp(file, "..") == 0) { // parent directory TRACE(("fs_walk - found \"..\" file.\n")); *_vnodeID = baseNode->parID; return get_vnode(_volume, *_vnodeID, NULL); } // look up file in the directory uint32 dataLength = baseNode->dataLen[FS_DATA_FORMAT]; status_t result = ENOENT; size_t totalRead = 0; off_t block = baseNode->startLBN[FS_DATA_FORMAT]; bool done = false; while (totalRead < dataLength && !done) { off_t cachedBlock = block; char* blockData = (char*)block_cache_get(volume->fBlockCache, block); if (blockData == NULL) break; size_t bytesRead = 0; off_t blockBytesRead = 0; iso9660_inode node; int initResult; TRACE(("fs_walk - read buffer from disk at LBN %lld into buffer " "%p.\n", block, blockData)); // Move to the next block if necessary // Don't go over end of buffer, if dir record sits on boundary. while (blockBytesRead < volume->logicalBlkSize[FS_DATA_FORMAT] && totalRead + blockBytesRead < dataLength && blockData[0] != 0 && !done) { initResult = InitNode(volume, &node, blockData, &bytesRead); TRACE(("fs_walk - InitNode returned %s, filename %s, %u bytes " "read\n", strerror(initResult), node.name, (unsigned)bytesRead)); if (initResult == B_OK) { if ((node.flags & ISO_IS_ASSOCIATED_FILE) == 0 && !strcmp(node.name, file)) { TRACE(("fs_walk - success, found vnode at block %lld, pos " "%lld\n", block, blockBytesRead)); *_vnodeID = (block << 30) + (blockBytesRead & 0xffffffff); TRACE(("fs_walk - New vnode id is %lld\n", *_vnodeID)); result = get_vnode(_volume, *_vnodeID, (void**)&newNode); if (result == B_OK) { newNode->parID = baseNode->id; done = true; } } else { free(node.name); free(node.attr.slName); } } else { result = initResult; if (bytesRead == 0) done = true; } blockData += bytesRead; blockBytesRead += bytesRead; TRACE(("fs_walk - Adding %u bytes to blockBytes read (total " "%lld/%u).\n", (unsigned)bytesRead, blockBytesRead, (unsigned)baseNode->dataLen[FS_DATA_FORMAT])); } totalRead += volume->logicalBlkSize[FS_DATA_FORMAT]; block++; TRACE(("fs_walk - moving to next block %lld, total read %u\n", block, (unsigned)totalRead)); block_cache_put(volume->fBlockCache, cachedBlock); } TRACE(("fs_walk - EXIT, result is %s, vnid is %Lu\n", strerror(result), *_vnodeID)); return result; } static status_t fs_read_vnode(fs_volume* _volume, ino_t vnodeID, fs_vnode* _node, int* _type, uint32* _flags, bool reenter) { iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; iso9660_inode* newNode = (iso9660_inode*)calloc(sizeof(iso9660_inode), 1); if (newNode == NULL) return B_NO_MEMORY; uint32 pos = vnodeID & 0x3fffffff; uint32 block = vnodeID >> 30; TRACE(("fs_read_vnode - block = %u, pos = %u, raw = %Lu node %p\n", (unsigned)block, (unsigned) pos, vnodeID, newNode)); if (pos > volume->logicalBlkSize[FS_DATA_FORMAT]) { free(newNode); return B_BAD_VALUE; } char* data = (char*)block_cache_get(volume->fBlockCache, block); if (data == NULL) { free(newNode); return B_IO_ERROR; } status_t result = InitNode(volume, newNode, data + pos, NULL); block_cache_put(volume->fBlockCache, block); if (result < B_OK) { free(newNode); return result; } newNode->volume = volume; newNode->id = vnodeID; _node->private_node = newNode; _node->ops = &gISO9660VnodeOps; *_type = newNode->attr.stat[FS_DATA_FORMAT].st_mode & ~(S_IWUSR | S_IWGRP | S_IWOTH); *_flags = 0; if ((newNode->flags & ISO_IS_DIR) == 0) { newNode->cache = file_cache_create(volume->id, vnodeID, newNode->dataLen[FS_DATA_FORMAT]); } return B_OK; } static status_t fs_release_vnode(fs_volume* /*_volume*/, fs_vnode* _node, bool /*reenter*/) { iso9660_inode* node = (iso9660_inode*)_node->private_node; TRACE(("fs_release_vnode - ENTER (%p)\n", node)); if (node->id != ISO_ROOTNODE_ID) { free(node->name); free(node->attr.slName); if (node->cache != NULL) file_cache_delete(node->cache); free(node); } TRACE(("fs_release_vnode - EXIT\n")); return B_OK; } static status_t fs_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, const iovec* vecs, size_t count, size_t* _numBytes) { iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; iso9660_inode* node = (iso9660_inode*)_node->private_node; uint32 fileSize = node->dataLen[FS_DATA_FORMAT]; size_t bytesLeft = *_numBytes; if (pos >= fileSize) { *_numBytes = 0; return B_OK; } if (pos + bytesLeft > fileSize) { bytesLeft = fileSize - pos; *_numBytes = bytesLeft; } file_io_vec fileVec; fileVec.offset = pos + ((off_t)node->startLBN[FS_DATA_FORMAT] * (off_t)volume->logicalBlkSize[FS_DATA_FORMAT]); fileVec.length = bytesLeft; uint32 vecIndex = 0; size_t vecOffset = 0; return read_file_io_vec_pages(volume->fd, &fileVec, 1, vecs, count, &vecIndex, &vecOffset, &bytesLeft); } static status_t fs_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request) { iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; iso9660_inode* node = (iso9660_inode*)_node->private_node; #ifndef FS_SHELL if (io_request_is_write(request)) { notify_io_request(request, B_READ_ONLY_DEVICE); return B_READ_ONLY_DEVICE; } #endif if ((node->flags & ISO_IS_DIR) != 0) { #ifndef FS_SHELL notify_io_request(request, B_IS_A_DIRECTORY); #endif return B_IS_A_DIRECTORY; } return do_iterative_fd_io(volume->fd, request, iterative_io_get_vecs_hook, iterative_io_finished_hook, node); } static status_t fs_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* st) { iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; iso9660_inode* node = (iso9660_inode*)_node->private_node; status_t result = B_NO_ERROR; time_t time; TRACE(("fs_read_stat - ENTER\n")); st->st_dev = volume->id; st->st_ino = node->id; st->st_nlink = node->attr.stat[FS_DATA_FORMAT].st_nlink; st->st_uid = node->attr.stat[FS_DATA_FORMAT].st_uid; st->st_gid = node->attr.stat[FS_DATA_FORMAT].st_gid; st->st_blksize = 65536; st->st_mode = node->attr.stat[FS_DATA_FORMAT].st_mode; // Same for file/dir in ISO9660 st->st_size = node->dataLen[FS_DATA_FORMAT]; st->st_blocks = (st->st_size + 511) / 512; if (ConvertRecDate(&(node->recordDate), &time) == B_NO_ERROR) { st->st_ctim.tv_sec = st->st_mtim.tv_sec = st->st_atim.tv_sec = time; st->st_ctim.tv_nsec = st->st_mtim.tv_nsec = st->st_atim.tv_nsec = 0; } TRACE(("fs_read_stat - EXIT, result is %s\n", strerror(result))); return result; } static status_t fs_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode, void** /*cookie*/) { // Do not allow any of the write-like open modes to get by if ((openMode & O_RWMASK) == O_WRONLY || (openMode & O_RWMASK) == O_RDWR || (openMode & O_TRUNC) != 0 || (openMode & O_CREAT) != 0) return EROFS; return B_OK; } static status_t fs_read(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos, void* buffer, size_t* _length) { iso9660_inode* node = (iso9660_inode*)_node->private_node; if ((node->flags & ISO_IS_DIR) != 0) return EISDIR; return file_cache_read(node->cache, NULL, pos, buffer, _length); } static status_t fs_close(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) { return B_OK; } static status_t fs_free_cookie(fs_volume* /*_volume*/, fs_vnode* /*_node*/, void* /*cookie*/) { return B_OK; } static status_t fs_access(fs_volume* /*_volume*/, fs_vnode* /*_node*/, int /*mode*/) { return B_OK; } static status_t fs_read_link(fs_volume* _volume, fs_vnode* _node, char* buffer, size_t* _bufferSize) { iso9660_inode* node = (iso9660_inode*)_node->private_node; if (!S_ISLNK(node->attr.stat[FS_DATA_FORMAT].st_mode)) return B_BAD_VALUE; size_t length = strlen(node->attr.slName); size_t bytesToCopy = std::min(length, *_bufferSize); *_bufferSize = length; memcpy(buffer, node->attr.slName, bytesToCopy); return B_OK; } static status_t fs_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie) { iso9660_inode* node = (iso9660_inode*)_node->private_node; TRACE(("fs_open_dir - node is %p\n", node)); if ((node->flags & ISO_IS_DIR) == 0) return B_NOT_A_DIRECTORY; dircookie* dirCookie = (dircookie*)malloc(sizeof(dircookie)); if (dirCookie == NULL) return B_NO_MEMORY; dirCookie->startBlock = node->startLBN[FS_DATA_FORMAT]; dirCookie->block = node->startLBN[FS_DATA_FORMAT]; dirCookie->totalSize = node->dataLen[FS_DATA_FORMAT]; dirCookie->pos = 0; dirCookie->id = node->id; *_cookie = (void*)dirCookie; return B_OK; } static status_t fs_read_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie, struct dirent* buffer, size_t bufferSize, uint32* num) { iso9660_volume* volume = (iso9660_volume*)_volume->private_volume; dircookie* dirCookie = (dircookie*)_cookie; TRACE(("fs_read_dir - ENTER\n")); status_t result = ISOReadDirEnt(volume, dirCookie, buffer, bufferSize); // If we succeeded, return 1, the number of dirents we read. if (result == B_OK) *num = 1; else *num = 0; // When you get to the end, don't return an error, just return // a zero in *num. if (result == B_ENTRY_NOT_FOUND) result = B_OK; TRACE(("fs_read_dir - EXIT, result is %s\n", strerror(result))); return result; } static status_t fs_rewind_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie) { dircookie* cookie = (dircookie*)_cookie; cookie->block = cookie->startBlock; cookie->pos = 0; return B_OK; } static status_t fs_close_dir(fs_volume* _volume, fs_vnode* _node, void* cookie) { return B_OK; } static status_t fs_free_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie) { free(cookie); return B_OK; } // #pragma mark - static status_t iso_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: case B_MODULE_UNINIT: return B_OK; default: return B_ERROR; } } fs_volume_ops gISO9660VolumeOps = { &fs_unmount, &fs_read_fs_stat, NULL, NULL, &fs_read_vnode, /* index and index directory ops */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* query ops */ NULL, NULL, NULL, NULL, NULL, /* FS layer ops */ NULL, NULL, }; fs_vnode_ops gISO9660VnodeOps = { &fs_walk, &fs_get_vnode_name, &fs_release_vnode, NULL, /* vm-related ops */ NULL, &fs_read_pages, NULL, &fs_io, NULL, // cancel_io() /* cache file access */ NULL, /* common */ NULL, NULL, NULL, NULL, NULL, &fs_read_link, NULL, NULL, NULL, NULL, &fs_access, &fs_read_stat, NULL, NULL, /* file */ NULL, &fs_open, &fs_close, &fs_free_cookie, &fs_read, NULL, /* dir */ NULL, NULL, &fs_open_dir, &fs_close_dir, &fs_free_dir_cookie, &fs_read_dir, &fs_rewind_dir, /* attribute directory ops */ NULL, NULL, NULL, NULL, NULL, /* attribute ops */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* node and FS layer support */ NULL, NULL, }; static file_system_module_info sISO660FileSystem = { { "file_systems/iso9660" B_CURRENT_FS_API_VERSION, 0, iso_std_ops, }, "iso9660", // short_name "ISO9660 File System", // pretty_name 0, // DDM flags // scanning fs_identify_partition, fs_scan_partition, fs_free_identify_partition_cookie, NULL, // free_partition_content_cookie() &fs_mount, /* capability querying */ NULL, NULL, NULL, NULL, NULL, NULL, /* shadow partition modifications */ NULL, /* writing */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, }; module_info* modules[] = { (module_info*)&sISO660FileSystem, NULL, };