/* * Copyright 2019, Les De Ridder, les@lesderid.net * Copyright 2017, Chế Vũ Gia Hy, cvghy116@gmail.com. * Copyright 2011, Jérôme Duval, korli@users.berlios.de. * Copyright 2008, Axel Dörfler, axeld@pinc-software.de. * * This file may be used under the terms of the MIT License. */ #include "Attribute.h" #include "AttributeIterator.h" #include "btrfs.h" #include "btrfs_disk_system.h" #include "DirectoryIterator.h" #include "Inode.h" #include "system_dependencies.h" #include "Utility.h" #ifdef FS_SHELL #define ERROR(x...) TRACE(x) #define INFORM(x...) TRACE(x) #define init_debugging() #define exit_debugging() #else #include #endif //#define TRACE_BTRFS #ifdef TRACE_BTRFS # define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x) #else # define TRACE(x...) ; #endif #define BTRFS_IO_SIZE 65536 struct identify_cookie { btrfs_super_block super_block; }; //! btrfs_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) { Inode* inode = (Inode*)cookie; return file_map_translate(inode->Map(), offset, size, vecs, _count, inode->GetVolume()->BlockSize()); } //! btrfs_io() callback hook static status_t iterative_io_finished_hook(void* cookie, io_request* request, status_t status, bool partialTransfer, size_t bytesTransferred) { Inode* inode = (Inode*)cookie; rw_lock_read_unlock(inode->Lock()); return B_OK; } // #pragma mark - Scanning static float btrfs_identify_partition(int fd, partition_data* partition, void** _cookie) { btrfs_super_block superBlock; status_t status = Volume::Identify(fd, &superBlock); if (status != B_OK) return -1; identify_cookie* cookie = new identify_cookie; memcpy(&cookie->super_block, &superBlock, sizeof(btrfs_super_block)); *_cookie = cookie; return 0.8f; } static status_t btrfs_scan_partition(int fd, partition_data* partition, void* _cookie) { identify_cookie* cookie = (identify_cookie*)_cookie; partition->status = B_PARTITION_VALID; partition->flags |= B_PARTITION_FILE_SYSTEM; partition->content_size = cookie->super_block.TotalSize(); partition->block_size = cookie->super_block.BlockSize(); partition->content_name = strdup(cookie->super_block.label); if (partition->content_name == NULL) return B_NO_MEMORY; return B_OK; } static void btrfs_free_identify_partition_cookie(partition_data* partition, void* _cookie) { delete (identify_cookie*)_cookie; } // #pragma mark - static status_t btrfs_mount(fs_volume* _volume, const char* device, uint32 flags, const char* args, ino_t* _rootID) { Volume* volume = new(std::nothrow) Volume(_volume); if (volume == NULL) return B_NO_MEMORY; // TODO: this is a bit hacky: we can't use publish_vnode() to publish // the root node, or else its file cache cannot be created (we could // create it later, though). Therefore we're using get_vnode() in Mount(), // but that requires us to export our volume data before calling it. _volume->private_volume = volume; _volume->ops = &gBtrfsVolumeOps; status_t status = volume->Mount(device, flags); if (status != B_OK) { ERROR("Failed mounting the volume. Error: %s\n", strerror(status)); delete volume; return status; } *_rootID = volume->RootNode()->ID(); return B_OK; } static status_t btrfs_unmount(fs_volume* _volume) { Volume* volume = (Volume*)_volume->private_volume; status_t status = volume->Unmount(); delete volume; return status; } static status_t btrfs_read_fs_info(fs_volume* _volume, struct fs_info* info) { Volume* volume = (Volume*)_volume->private_volume; // File system flags info->flags = B_FS_IS_PERSISTENT | B_FS_HAS_ATTR | (volume->IsReadOnly() ? B_FS_IS_READONLY : 0); info->io_size = BTRFS_IO_SIZE; info->block_size = volume->BlockSize(); info->total_blocks = volume->SuperBlock().TotalSize() / volume->BlockSize(); info->free_blocks = 0; //volume->NumFreeBlocks(); // Volume name strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name)); // File system name strlcpy(info->fsh_name, "btrfs", sizeof(info->fsh_name)); return B_OK; } // #pragma mark - static status_t btrfs_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type, uint32* _flags, bool reenter) { Volume* volume = (Volume*)_volume->private_volume; Inode* inode = new(std::nothrow) Inode(volume, id); if (inode == NULL) return B_NO_MEMORY; status_t status = inode->InitCheck(); if (status != B_OK) { delete inode; ERROR("get_vnode: InitCheck() failed. Error: %s\n", strerror(status)); return status; } _node->private_node = inode; _node->ops = &gBtrfsVnodeOps; *_type = inode->Mode(); *_flags = 0; return B_OK; } static status_t btrfs_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) { delete (Inode*)_node->private_node; return B_OK; } static bool btrfs_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie) { return true; } static status_t btrfs_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, const iovec* vecs, size_t count, size_t* _numBytes) { Volume* volume = (Volume*)_volume->private_volume; Inode* inode = (Inode*)_node->private_node; if (inode->FileCache() == NULL) return B_BAD_VALUE; rw_lock_read_lock(inode->Lock()); uint32 vecIndex = 0; size_t vecOffset = 0; size_t bytesLeft = *_numBytes; status_t status; while (true) { file_io_vec fileVecs[8]; size_t fileVecCount = 8; status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs, &fileVecCount, 0); if (status != B_OK && status != B_BUFFER_OVERFLOW) break; bool bufferOverflow = status == B_BUFFER_OVERFLOW; size_t bytes = bytesLeft; status = read_file_io_vec_pages(volume->Device(), fileVecs, fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes); if (status != B_OK || !bufferOverflow) break; pos += bytes; bytesLeft -= bytes; } rw_lock_read_unlock(inode->Lock()); return status; } static status_t btrfs_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request) { Volume* volume = (Volume*)_volume->private_volume; Inode* inode = (Inode*)_node->private_node; #ifndef FS_SHELL if (io_request_is_write(request) && volume->IsReadOnly()) { notify_io_request(request, B_READ_ONLY_DEVICE); return B_READ_ONLY_DEVICE; } #endif if (inode->FileCache() == NULL) { #ifndef FS_SHELL notify_io_request(request, B_BAD_VALUE); #endif return B_BAD_VALUE; } // We lock the node here and will unlock it in the "finished" hook. rw_lock_read_lock(inode->Lock()); return do_iterative_fd_io(volume->Device(), request, iterative_io_get_vecs_hook, iterative_io_finished_hook, inode); } static status_t btrfs_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset, size_t size, struct file_io_vec* vecs, size_t* _count) { TRACE("btrfs_get_file_map()\n"); Inode* inode = (Inode*)_node->private_node; size_t index = 0, max = *_count; while (true) { off_t blockOffset; off_t blockLength; status_t status = inode->FindBlock(offset, blockOffset, &blockLength); if (status != B_OK) return status; if (index > 0 && (vecs[index - 1].offset == blockOffset - vecs[index - 1].length)) { vecs[index - 1].length += blockLength; } else { if (index >= max) { // we're out of file_io_vecs; let's bail out *_count = index; return B_BUFFER_OVERFLOW; } vecs[index].offset = blockOffset; vecs[index].length = blockLength; index++; } offset += blockLength; size -= blockLength; if ((off_t)size <= vecs[index - 1].length || offset >= inode->Size()) { // We're done! *_count = index; TRACE("btrfs_get_file_map for inode %" B_PRIdINO "\n", inode->ID()); return B_OK; } } // can never get here return B_ERROR; } // #pragma mark - static status_t btrfs_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name, ino_t* _vnodeID) { TRACE("btrfs_lookup: name address: %p (%s)\n", name, name); Volume* volume = (Volume*)_volume->private_volume; Inode* directory = (Inode*)_directory->private_node; // check access permissions status_t status = directory->CheckPermissions(X_OK); if (status < B_OK) return status; status = DirectoryIterator(directory).Lookup(name, strlen(name), _vnodeID); if (status != B_OK) return status; return get_vnode(volume->FSVolume(), *_vnodeID, NULL); } static status_t btrfs_ioctl(fs_volume* _volume, fs_vnode* _node, void* _cookie, uint32 cmd, void* buffer, size_t bufferLength) { TRACE("ioctl: %" B_PRIu32 "\n", cmd); /*Volume* volume = (Volume*)_volume->private_volume;*/ return B_DEV_INVALID_IOCTL; } static status_t btrfs_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat) { Inode* inode = (Inode*)_node->private_node; stat->st_dev = inode->GetVolume()->ID(); stat->st_ino = inode->ID(); stat->st_nlink = 1; stat->st_blksize = BTRFS_IO_SIZE; stat->st_uid = inode->UserID(); stat->st_gid = inode->GroupID(); stat->st_mode = inode->Mode(); stat->st_type = 0; inode->GetAccessTime(stat->st_atim); inode->GetModificationTime(stat->st_mtim); inode->GetChangeTime(stat->st_ctim); inode->GetCreationTime(stat->st_crtim); stat->st_size = inode->Size(); stat->st_blocks = (inode->Size() + 511) / 512; return B_OK; } static status_t btrfs_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode, void** _cookie) { Inode* inode = (Inode*)_node->private_node; // opening a directory read-only is allowed, although you can't read // any data from it. if (inode->IsDirectory() && (openMode & O_RWMASK) != 0) return B_IS_A_DIRECTORY; status_t status = inode->CheckPermissions(open_mode_to_access(openMode) | (openMode & O_TRUNC ? W_OK : 0)); if (status != B_OK) return status; // Prepare the cookie file_cookie* cookie = new(std::nothrow) file_cookie; if (cookie == NULL) return B_NO_MEMORY; ObjectDeleter cookieDeleter(cookie); cookie->open_mode = openMode & BTRFS_OPEN_MODE_USER_MASK; cookie->last_size = inode->Size(); cookie->last_notification = system_time(); if ((openMode & O_NOCACHE) != 0 && inode->FileCache() != NULL) { // Disable the file cache, if requested? status = file_cache_disable(inode->FileCache()); if (status != B_OK) return status; } cookieDeleter.Detach(); *_cookie = cookie; return B_OK; } static status_t btrfs_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, void* buffer, size_t* _length) { Inode* inode = (Inode*)_node->private_node; if (!inode->IsFile()) { *_length = 0; return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE; } return inode->ReadAt(pos, (uint8*)buffer, _length); } static status_t btrfs_close(fs_volume* _volume, fs_vnode* _node, void* _cookie) { return B_OK; } static status_t btrfs_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) { file_cookie* cookie = (file_cookie*)_cookie; Volume* volume = (Volume*)_volume->private_volume; Inode* inode = (Inode*)_node->private_node; if (inode->Size() != cookie->last_size) notify_stat_changed(volume->ID(), -1, inode->ID(), B_STAT_SIZE); delete cookie; return B_OK; } static status_t btrfs_access(fs_volume* _volume, fs_vnode* _node, int accessMode) { Inode* inode = (Inode*)_node->private_node; return inode->CheckPermissions(accessMode); } static status_t btrfs_read_link(fs_volume* _volume, fs_vnode* _node, char* buffer, size_t* _bufferSize) { Inode* inode = (Inode*)_node->private_node; if (!inode->IsSymLink()) return B_BAD_VALUE; status_t result = inode->ReadAt(0, reinterpret_cast(buffer), _bufferSize); if (result != B_OK) return result; *_bufferSize = inode->Size(); return B_OK; } status_t btrfs_unlink(fs_volume* _volume, fs_vnode* _directory, const char* name) { if (!strcmp(name, "..") || !strcmp(name, ".")) return B_NOT_ALLOWED; Volume* volume = (Volume*)_volume->private_volume; Inode* directory = (Inode*)_directory->private_node; status_t status = directory->CheckPermissions(W_OK); if (status < B_OK) return status; Transaction transaction(volume); BTree::Path path(volume->FSTree()); ino_t id; status = DirectoryIterator(directory).Lookup(name, strlen(name), &id); if (status != B_OK) return status; Inode inode(volume, id); status = inode.InitCheck(); if (status != B_OK) return status; status = inode.Remove(transaction, &path); if (status != B_OK) return status; status = inode.Dereference(transaction, &path, directory->ID(), name); if (status != B_OK) return status; entry_cache_remove(volume->ID(), directory->ID(), name); status = transaction.Done(); if (status == B_OK) notify_entry_removed(volume->ID(), directory->ID(), name, id); else entry_cache_add(volume->ID(), directory->ID(), name, id); return status; } // #pragma mark - Directory functions static status_t btrfs_create_dir(fs_volume* _volume, fs_vnode* _directory, const char* name, int mode) { Volume* volume = (Volume*)_volume->private_volume; Inode* directory = (Inode*)_directory->private_node; BTree::Path path(volume->FSTree()); if (volume->IsReadOnly()) return B_READ_ONLY_DEVICE; if (!directory->IsDirectory()) return B_NOT_A_DIRECTORY; status_t status = directory->CheckPermissions(W_OK); if (status < B_OK) return status; Transaction transaction(volume); ino_t id = volume->GetNextInodeID(); mode = S_DIRECTORY | (mode & S_IUMSK); Inode* inode = Inode::Create(transaction, id, directory, mode); if (inode == NULL) return B_NO_MEMORY; status = inode->Insert(transaction, &path); if (status != B_OK) return status; status = inode->MakeReference(transaction, &path, directory, name, mode); if (status != B_OK) return status; put_vnode(volume->FSVolume(), inode->ID()); entry_cache_add(volume->ID(), directory->ID(), name, inode->ID()); status = transaction.Done(); if (status == B_OK) notify_entry_created(volume->ID(), directory->ID(), name, inode->ID()); else entry_cache_remove(volume->ID(), directory->ID(), name); return status; } static status_t btrfs_remove_dir(fs_volume* _volume, fs_vnode* _directory, const char* name) { Volume* volume = (Volume*)_volume->private_volume; Inode* directory = (Inode*)_directory->private_node; Transaction transaction(volume); BTree::Path path(volume->FSTree()); ino_t id; status_t status = DirectoryIterator(directory).Lookup(name, strlen(name), &id); if (status != B_OK) return status; Inode inode(volume, id); status = inode.InitCheck(); if (status != B_OK) return status; status = inode.Remove(transaction, &path); if (status != B_OK) return status; status = inode.Dereference(transaction, &path, directory->ID(), name); if (status != B_OK) return status; entry_cache_remove(volume->ID(), directory->ID(), name); entry_cache_remove(volume->ID(), id, ".."); status = transaction.Done(); if (status == B_OK) notify_entry_removed(volume->ID(), directory->ID(), name, id); else { entry_cache_add(volume->ID(), directory->ID(), name, id); entry_cache_add(volume->ID(), id, "..", id); } return status; } static status_t btrfs_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie) { Inode* inode = (Inode*)_node->private_node; status_t status = inode->CheckPermissions(R_OK); if (status < B_OK) return status; if (!inode->IsDirectory()) return B_NOT_A_DIRECTORY; DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode); if (iterator == NULL || iterator->InitCheck() != B_OK) { delete iterator; return B_NO_MEMORY; } *_cookie = iterator; return B_OK; } static status_t btrfs_read_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie, struct dirent* dirent, size_t bufferSize, uint32* _num) { DirectoryIterator* iterator = (DirectoryIterator*)_cookie; Volume* volume = (Volume*)_volume->private_volume; uint32 maxCount = *_num; uint32 count = 0; while (count < maxCount && bufferSize > sizeof(struct dirent)) { ino_t id; size_t length = bufferSize - sizeof(struct dirent) + 1; status_t status = iterator->GetNext(dirent->d_name, &length, &id); if (status == B_ENTRY_NOT_FOUND) break; if (status == B_BUFFER_OVERFLOW) { // the remaining name buffer length was too small if (count == 0) return B_BUFFER_OVERFLOW; break; } if (status != B_OK) return status; dirent->d_dev = volume->ID(); dirent->d_ino = id; dirent->d_reclen = sizeof(struct dirent) + length; bufferSize -= dirent->d_reclen; dirent = (struct dirent*)((uint8*)dirent + dirent->d_reclen); count++; } *_num = count; return B_OK; } static status_t btrfs_rewind_dir(fs_volume* /*_volume*/, fs_vnode* /*node*/, void* _cookie) { DirectoryIterator* iterator = (DirectoryIterator*)_cookie; return iterator->Rewind(); } static status_t btrfs_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/) { return B_OK; } static status_t btrfs_free_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) { delete (DirectoryIterator*)_cookie; return B_OK; } static status_t btrfs_open_attr_dir(fs_volume* _volume, fs_vnode* _node, void** _cookie) { Inode* inode = (Inode*)_node->private_node; TRACE("%s()\n", __FUNCTION__); // on directories too ? if (!inode->IsFile()) return EINVAL; AttributeIterator* iterator = new(std::nothrow) AttributeIterator(inode); if (iterator == NULL || iterator->InitCheck() != B_OK) { delete iterator; return B_NO_MEMORY; } *_cookie = iterator; return B_OK; } static status_t btrfs_close_attr_dir(fs_volume* _volume, fs_vnode* _node, void* cookie) { TRACE("%s()\n", __FUNCTION__); return B_OK; } static status_t btrfs_free_attr_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) { TRACE("%s()\n", __FUNCTION__); delete (AttributeIterator*)_cookie; return B_OK; } static status_t btrfs_read_attr_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie, struct dirent* dirent, size_t bufferSize, uint32* _num) { TRACE("%s()\n", __FUNCTION__); AttributeIterator* iterator = (AttributeIterator*)_cookie; size_t length = bufferSize; status_t status = iterator->GetNext(dirent->d_name, &length); if (status == B_ENTRY_NOT_FOUND) { *_num = 0; return B_OK; } if (status != B_OK) return status; Volume* volume = (Volume*)_volume->private_volume; dirent->d_dev = volume->ID(); dirent->d_reclen = sizeof(struct dirent) + length; *_num = 1; return B_OK; } static status_t btrfs_rewind_attr_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie) { AttributeIterator* iterator = (AttributeIterator*)_cookie; return iterator->Rewind(); } /* attribute operations */ static status_t btrfs_create_attr(fs_volume* _volume, fs_vnode* _node, const char* name, uint32 type, int openMode, void** _cookie) { return EROFS; } static status_t btrfs_open_attr(fs_volume* _volume, fs_vnode* _node, const char* name, int openMode, void** _cookie) { TRACE("%s()\n", __FUNCTION__); Inode* inode = (Inode*)_node->private_node; Attribute attribute(inode); return attribute.Open(name, openMode, (attr_cookie**)_cookie); } static status_t btrfs_close_attr(fs_volume* _volume, fs_vnode* _node, void* cookie) { return B_OK; } static status_t btrfs_free_attr_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie) { delete (attr_cookie*)cookie; return B_OK; } static status_t btrfs_read_attr(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, void* buffer, size_t* _length) { TRACE("%s()\n", __FUNCTION__); attr_cookie* cookie = (attr_cookie*)_cookie; Inode* inode = (Inode*)_node->private_node; Attribute attribute(inode, cookie); return attribute.Read(cookie, pos, (uint8*)buffer, _length); } static status_t btrfs_write_attr(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos, const void* buffer, size_t* length) { return EROFS; } static status_t btrfs_read_attr_stat(fs_volume* _volume, fs_vnode* _node, void* _cookie, struct stat* stat) { attr_cookie* cookie = (attr_cookie*)_cookie; Inode* inode = (Inode*)_node->private_node; Attribute attribute(inode, cookie); return attribute.Stat(*stat); } static status_t btrfs_write_attr_stat(fs_volume* _volume, fs_vnode* _node, void* cookie, const struct stat* stat, int statMask) { return EROFS; } static status_t btrfs_rename_attr(fs_volume* _volume, fs_vnode* fromVnode, const char* fromName, fs_vnode* toVnode, const char* toName) { return EROFS; } static status_t btrfs_remove_attr(fs_volume* _volume, fs_vnode* vnode, const char* name) { return EROFS; } static uint32 btrfs_get_supported_operations(partition_data* partition, uint32 mask) { // TODO: We should at least check the partition size. return B_DISK_SYSTEM_SUPPORTS_INITIALIZING | B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME // | B_DISK_SYSTEM_SUPPORTS_WRITING ; } static status_t btrfs_initialize(int fd, partition_id partitionID, const char* name, const char* parameterString, off_t partitionSize, disk_job_id job) { // check name status_t status = check_volume_name(name); if (status != B_OK) return status; // parse parameters initialize_parameters parameters; status = parse_initialize_parameters(parameterString, parameters); if (status != B_OK) return status; update_disk_device_job_progress(job, 0); // initialize the volume Volume volume(NULL); status = volume.Initialize(fd, name, parameters.blockSize, parameters.sectorSize); if (status < B_OK) { INFORM("Initializing volume failed: %s\n", strerror(status)); return status; } // rescan partition status = scan_partition(partitionID); if (status != B_OK) return status; update_disk_device_job_progress(job, 1); // print some info, if desired if (parameters.verbose) { btrfs_super_block super = volume.SuperBlock(); INFORM("Disk was initialized successfully.\n"); INFORM("\tlabel: \"%s\"\n", super.label); INFORM("\tblock size: %u bytes\n", (unsigned)super.BlockSize()); INFORM("\tsector size: %u bytes\n", (unsigned)super.SectorSize()); } return B_OK; } static status_t btrfs_uninitialize(int fd, partition_id partitionID, off_t partitionSize, uint32 blockSize, disk_job_id job) { if (blockSize == 0) return B_BAD_VALUE; update_disk_device_job_progress(job, 0.0); // just overwrite the superblock btrfs_super_block superBlock; memset(&superBlock, 0, sizeof(superBlock)); if (write_pos(fd, BTRFS_SUPER_BLOCK_OFFSET, &superBlock, sizeof(superBlock)) < 0) return errno; update_disk_device_job_progress(job, 1.0); return B_OK; } // #pragma mark - static status_t btrfs_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: init_debugging(); return B_OK; case B_MODULE_UNINIT: exit_debugging(); return B_OK; default: return B_ERROR; } } fs_volume_ops gBtrfsVolumeOps = { &btrfs_unmount, &btrfs_read_fs_info, NULL, // write_fs_info() NULL, // fs_sync, &btrfs_get_vnode, }; fs_vnode_ops gBtrfsVnodeOps = { /* vnode operations */ &btrfs_lookup, NULL, // btrfs_get_vnode_name - optional, and we can't do better than the // fallback implementation, so leave as NULL. &btrfs_put_vnode, NULL, // btrfs_remove_vnode, /* VM file access */ &btrfs_can_page, &btrfs_read_pages, NULL, // btrfs_write_pages, NULL, // io() NULL, // cancel_io() &btrfs_get_file_map, &btrfs_ioctl, NULL, NULL, // fs_select NULL, // fs_deselect NULL, // fs_fsync, &btrfs_read_link, NULL, // fs_create_symlink, NULL, // fs_link, &btrfs_unlink, NULL, // fs_rename, &btrfs_access, &btrfs_read_stat, NULL, // fs_write_stat, NULL, // fs_preallocate /* file operations */ NULL, // fs_create, &btrfs_open, &btrfs_close, &btrfs_free_cookie, &btrfs_read, NULL, // fs_write, /* directory operations */ &btrfs_create_dir, &btrfs_remove_dir, &btrfs_open_dir, &btrfs_close_dir, &btrfs_free_dir_cookie, &btrfs_read_dir, &btrfs_rewind_dir, /* attribute directory operations */ &btrfs_open_attr_dir, &btrfs_close_attr_dir, &btrfs_free_attr_dir_cookie, &btrfs_read_attr_dir, &btrfs_rewind_attr_dir, /* attribute operations */ &btrfs_create_attr, &btrfs_open_attr, &btrfs_close_attr, &btrfs_free_attr_cookie, &btrfs_read_attr, &btrfs_write_attr, &btrfs_read_attr_stat, &btrfs_write_attr_stat, &btrfs_rename_attr, &btrfs_remove_attr, }; static file_system_module_info sBtrfsFileSystem = { { "file_systems/btrfs" B_CURRENT_FS_API_VERSION, 0, btrfs_std_ops, }, "btrfs", // short_name "Btrfs File System", // pretty_name // DDM flags 0 | B_DISK_SYSTEM_SUPPORTS_INITIALIZING | B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME // | B_DISK_SYSTEM_SUPPORTS_WRITING , // scanning btrfs_identify_partition, btrfs_scan_partition, btrfs_free_identify_partition_cookie, NULL, // free_partition_content_cookie() &btrfs_mount, /* capability querying operations */ &btrfs_get_supported_operations, NULL, // validate_resize NULL, // validate_move NULL, // validate_set_content_name NULL, // validate_set_content_parameters NULL, // validate_initialize, /* shadow partition modification */ NULL, // shadow_changed /* writing */ NULL, // defragment NULL, // repair NULL, // resize NULL, // move NULL, // set_content_name NULL, // set_content_parameters btrfs_initialize, btrfs_uninitialize }; module_info* modules[] = { (module_info*)&sBtrfsFileSystem, NULL, };