/* kernel_interface - file system interface to BeOS' vnode layer ** ** Initial version by Axel Dörfler, axeld@pinc-software.de ** This file may be used under the terms of the OpenBeOS License. */ #include "Debug.h" #include "Volume.h" #include "Inode.h" #include "Index.h" #include "BPlusTree.h" #include "Query.h" #include "bfs_control.h" #include #include #include // BeOS vnode layer stuff #include #ifndef _IMPEXP_KERNEL # define _IMPEXP_KERNEL #endif extern "C" { #include #include #include } #include #include #ifdef USER # define dprintf printf #endif // file system API static int bfs_mount(nspace_id nsid, const char *device, ulong flags, void *parms, size_t len, void **data, vnode_id *vnid); static int bfs_unmount(void *_ns); static int bfs_read_fs_stat(void *_ns, struct fs_info *); static int bfs_write_fs_stat(void *ns, struct fs_info *, long mode); static int bfs_initialize(const char *devname, void *parms, size_t len); static int bfs_sync(void *ns); static int bfs_read_vnode(void *_ns, vnode_id vnid, char r, void **node); static int bfs_release_vnode(void *_ns, void *_node, char r); static int bfs_remove_vnode(void *ns, void *node, char r); static int bfs_walk(void *_ns, void *_base, const char *file, char **newpath, vnode_id *vnid); static int bfs_ioctl(void *ns, void *node, void *cookie, int cmd, void *buf, size_t len); static int bfs_setflags(void *ns, void *node, void *cookie, int flags); static int bfs_select(void *ns, void *node, void *cookie, uint8 event, uint32 ref, selectsync *sync); static int bfs_deselect(void *ns, void *node, void *cookie, uint8 event, selectsync *sync); static int bfs_fsync(void *ns, void *node); static int bfs_create(void *ns, void *dir, const char *name, int perms, int omode, vnode_id *vnid, void **cookie); static int bfs_symlink(void *ns, void *dir, const char *name, const char *path); static int bfs_link(void *ns, void *dir, const char *name, void *node); static int bfs_unlink(void *ns, void *dir, const char *name); static int bfs_rename(void *ns, void *oldDir, const char *oldName, void *newDir, const char *newName); static int bfs_read_stat(void *_ns, void *_node, struct stat *st); static int bfs_write_stat(void *ns, void *node, struct stat *st, long mask); static int bfs_open(void *_ns, void *_node, int omode, void **cookie); static int bfs_read(void *_ns, void *_node, void *cookie, off_t pos, void *buf, size_t *len); static int bfs_write(void *ns, void *node, void *cookie, off_t pos, const void *buf, size_t *len); static int bfs_free_cookie(void *ns, void *node, void *cookie); static int bfs_close(void *ns, void *node, void *cookie); static int bfs_access(void *_ns, void *_node, int mode); static int bfs_read_link(void *_ns, void *_node, char *buffer, size_t *bufferSize); // directory functions static int bfs_mkdir(void *ns, void *dir, const char *name, int perms); static int bfs_rmdir(void *ns, void *dir, const char *name); static int bfs_open_dir(void *_ns, void *_node, void **cookie); static int bfs_read_dir(void *_ns, void *_node, void *cookie, long *num, struct dirent *dirent, size_t bufferSize); static int bfs_rewind_dir(void *_ns, void *_node, void *cookie); static int bfs_close_dir(void *_ns, void *_node, void *cookie); static int bfs_free_dir_cookie(void *_ns, void *_node, void *cookie); // attribute support static int bfs_open_attrdir(void *ns, void *node, void **cookie); static int bfs_close_attrdir(void *ns, void *node, void *cookie); static int bfs_free_attrdir_cookie(void *ns, void *node, void *cookie); static int bfs_rewind_attrdir(void *ns, void *node, void *cookie); static int bfs_read_attrdir(void *ns, void *node, void *cookie, long *num, struct dirent *buf, size_t bufferSize); static int bfs_remove_attr(void *ns, void *node, const char *name); static int bfs_rename_attr(void *ns, void *node, const char *oldname, const char *newname); static int bfs_stat_attr(void *ns, void *node, const char *name, struct attr_info *buf); static int bfs_write_attr(void *ns, void *node, const char *name, int type, const void *buf, size_t *len, off_t pos); static int bfs_read_attr(void *ns, void *node, const char *name, int type, void *buf, size_t *len, off_t pos); // index support static int bfs_open_indexdir(void *ns, void **cookie); static int bfs_close_indexdir(void *ns, void *cookie); static int bfs_free_indexdir_cookie(void *ns, void *node, void *cookie); static int bfs_rewind_indexdir(void *ns, void *cookie); static int bfs_read_indexdir(void *ns, void *cookie, long *num, struct dirent *dirent, size_t bufferSize); static int bfs_create_index(void *ns, const char *name, int type, int flags); static int bfs_remove_index(void *ns, const char *name); static int bfs_rename_index(void *ns, const char *oldname, const char *newname); static int bfs_stat_index(void *ns, const char *name, struct index_info *indexInfo); // query support static int bfs_open_query(void *ns, const char *query, ulong flags, port_id port, long token, void **cookie); static int bfs_close_query(void *ns, void *cookie); static int bfs_free_query_cookie(void *ns, void *node, void *cookie); static int bfs_read_query(void *ns, void *cookie, long *num, struct dirent *buf, size_t bufsize); // Dano compatibility (required for booting) static int bfs_wake_vnode(void *ns, void *node); static int bfs_suspend_vnode(void *ns, void *node); /* vnode_ops struct. Fill this in to tell the kernel how to call functions in your driver. */ vnode_ops fs_entry = { &bfs_read_vnode, // read_vnode &bfs_release_vnode, // write_vnode &bfs_remove_vnode, // remove_vnode NULL, // secure_vnode (not needed) &bfs_walk, // walk &bfs_access, // access &bfs_create, // create &bfs_mkdir, // mkdir &bfs_symlink, // symlink &bfs_link, // link &bfs_rename, // rename &bfs_unlink, // unlink &bfs_rmdir, // rmdir &bfs_read_link, // readlink &bfs_open_dir, // opendir &bfs_close_dir, // closedir &bfs_free_dir_cookie, // free_dircookie &bfs_rewind_dir, // rewinddir &bfs_read_dir, // readdir &bfs_open, // open file &bfs_close, // close file &bfs_free_cookie, // free cookie &bfs_read, // read file &bfs_write, // write file NULL, // readv NULL, // writev &bfs_ioctl, // ioctl &bfs_setflags, // setflags file &bfs_read_stat, // read stat &bfs_write_stat, // write stat &bfs_fsync, // fsync &bfs_initialize, // initialize &bfs_mount, // mount &bfs_unmount, // unmount &bfs_sync, // sync &bfs_read_fs_stat, // read fs stat &bfs_write_fs_stat, // write fs stat &bfs_select, // select &bfs_deselect, // deselect &bfs_open_indexdir, // open index dir &bfs_close_indexdir, // close index dir &bfs_free_indexdir_cookie, // free index dir cookie &bfs_rewind_indexdir, // rewind index dir &bfs_read_indexdir, // read index dir &bfs_create_index, // create index &bfs_remove_index, // remove index &bfs_rename_index, // rename index &bfs_stat_index, // stat index &bfs_open_attrdir, // open attr dir &bfs_close_attrdir, // close attr dir &bfs_free_attrdir_cookie, // free attr dir cookie &bfs_rewind_attrdir, // rewind attr dir &bfs_read_attrdir, // read attr dir &bfs_write_attr, // write attr &bfs_read_attr, // read attr &bfs_remove_attr, // remove attr &bfs_rename_attr, // rename attr &bfs_stat_attr, // stat attr &bfs_open_query, // open query &bfs_close_query, // close query &bfs_free_query_cookie, // free query cookie &bfs_read_query, // read query &bfs_wake_vnode, // these two are added for Dano compatibility; &bfs_suspend_vnode // they do nothing. }; #define BFS_IO_SIZE 65536 // ToDo: has to change to "bfs" later #ifdef COMPILE_FOR_R5 # define BFS_NAME "bfs" #else # define BFS_NAME "obfs" #endif int32 api_version = B_CUR_FS_API_VERSION; static int bfs_mount(nspace_id nsid, const char *device, ulong flags, void *parms, size_t len, void **data, vnode_id *rootID) { FUNCTION(); Volume *volume = new Volume(nsid); if (volume == NULL) return B_NO_MEMORY; status_t status; if ((status = volume->Mount(device, flags)) == B_OK) { *data = volume; *rootID = volume->ToVnode(volume->Root()); INFORM(("mounted \"%s\" (root node at %Ld, device = %s)\n", volume->Name(), *rootID, device)); #ifdef DEBUG add_debugger_commands(); #endif } else delete volume; RETURN_ERROR(status); } static int bfs_unmount(void *ns) { FUNCTION(); Volume* volume = (Volume *)ns; status_t status = volume->Unmount(); delete volume; #ifdef DEBUG remove_debugger_commands(); #endif RETURN_ERROR(status); } /** Fill in bfs_info struct for device. */ static int bfs_read_fs_stat(void *_ns, struct fs_info *info) { FUNCTION(); if (_ns == NULL || info == NULL) return B_BAD_VALUE; Volume *volume = (Volume *)_ns; RecursiveLocker locker(volume->Lock()); // File system flags. info->flags = B_FS_IS_PERSISTENT | B_FS_HAS_ATTR | B_FS_HAS_MIME | B_FS_HAS_QUERY | (volume->IsReadOnly() ? B_FS_IS_READONLY : 0); info->io_size = BFS_IO_SIZE; // whatever is appropriate here? Just use the same value as BFS (and iso9660) for now info->block_size = volume->BlockSize(); info->total_blocks = volume->NumBlocks(); info->free_blocks = volume->FreeBlocks(); // Volume name strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name)); // File system name strlcpy(info->fsh_name, BFS_NAME, sizeof(info->fsh_name)); return B_OK; } static int bfs_write_fs_stat(void *_ns, struct fs_info *info, long mask) { FUNCTION_START(("mask = %ld\n", mask)); Volume *volume = (Volume *)_ns; disk_super_block &superBlock = volume->SuperBlock(); RecursiveLocker locker(volume->Lock()); status_t status = B_BAD_VALUE; if (mask & WFSSTAT_NAME) { strncpy(superBlock.name, info->volume_name, sizeof(superBlock.name) - 1); superBlock.name[sizeof(superBlock.name) - 1] = '\0'; status = volume->WriteSuperBlock(); } return status; } static int bfs_initialize(const char *deviceName, void *parms, size_t len) { FUNCTION_START(("deviceName = %s, parameter len = %ld\n", deviceName, len)); // This function is not available from the outside in BeOS // It will be similarly implemented in OpenBeOS, though - the // backend (to create the file system) is already done; just // call Volume::Initialize(). return B_ERROR; } static int bfs_sync(void *_ns) { FUNCTION(); if (_ns == NULL) return B_BAD_VALUE; Volume *volume = (Volume *)_ns; return volume->Sync(); } // #pragma mark - /** Reads in the node from disk and creates an inode object from it. * Has to be cautious if the node in question is currently under * construction, in which case it waits for that action to be completed, * and uses the inode object from the construction instead of creating * a new one. * ToDo: Must not be called without the volume lock being held. Actually, * this might even happen with the BDirectory(node_ref *) constructor * (at least I think so, I haven't tested it yet), so we should better * test this. Fortunately, we can easily solve the issue with our kernel. */ static int bfs_read_vnode(void *_ns, vnode_id id, char reenter, void **_node) { FUNCTION_START(("vnode_id = %Ld\n", id)); Volume *volume = (Volume *)_ns; if (id < 0 || id > volume->NumBlocks()) { FATAL(("inode at %Ld requested!\n", id)); return B_ERROR; } CachedBlock cached(volume, id); bfs_inode *node = (bfs_inode *)cached.Block(); Inode *inode = NULL; int32 tries = 0; restartIfBusy: status_t status = node->InitCheck(volume); if (status == B_BUSY) { inode = (Inode *)node->etc; // We have to use the "etc" field to get the inode object // (the inode is currently being constructed) // We would need to call something like get_vnode() again here // to get rid of the "etc" field - which would be nice, especially // for other file systems which don't have this messy field. // let us wait a bit and try again later if (tries++ < 200) { // wait for one second at maximum snooze(5000); goto restartIfBusy; } FATAL(("inode is not becoming unbusy (id = %Ld)\n", id)); return status; } else if (status < B_OK) { FATAL(("inode at %Ld is corrupt!\n", id)); return status; } // If the inode is currently being constructed, we already have an inode // pointer (taken from the node's etc field). // If not, we create a new one here if (inode == NULL) { inode = new Inode(&cached); if (inode == NULL) return B_NO_MEMORY; status = inode->InitCheck(false); if (status < B_OK) delete inode; } else status = inode->InitCheck(false); if (status == B_OK) *_node = inode; return status; } static int bfs_release_vnode(void *ns, void *_node, char reenter) { //FUNCTION_START(("node = %p\n", _node)); Inode *inode = (Inode *)_node; delete inode; return B_NO_ERROR; } static int bfs_remove_vnode(void *_ns, void *_node, char reenter) { FUNCTION(); if (_ns == NULL || _node == NULL) return B_BAD_VALUE; Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; // The "chkbfs" functionality uses this flag to prevent the space used // up by the inode from being freed - this flag is set only in situations // where this is a good idea... (the block bitmap will get fixed anyway // in this case). if (inode->Flags() & INODE_DONT_FREE_SPACE) { delete inode; return B_OK; } // If the inode isn't in use anymore, we were called before // bfs_unlink() returns - in this case, we can just use the // transaction which has already deleted the inode. Transaction localTransaction, *transaction = NULL; Journal *journal = volume->GetJournal(volume->ToBlock(inode->Parent())); if (journal != NULL) transaction = journal->CurrentTransaction(); if (transaction == NULL) { transaction = &localTransaction; localTransaction.Start(volume, inode->BlockNumber()); } status_t status = inode->Free(transaction); if (status == B_OK) { if (transaction == &localTransaction) localTransaction.Done(); delete inode; } return status; } static int bfs_wake_vnode(void *_ns, void *_node) { return B_OK; } static int bfs_suspend_vnode(void *_ns, void *_node) { return B_OK; } // #pragma mark - /** the walk function just "walks" through a directory looking for the * specified file. It calls get_vnode() on its vnode-id to init it * for the kernel. */ static int bfs_walk(void *_ns, void *_directory, const char *file, char **_resolvedPath, vnode_id *_vnodeID) { //FUNCTION_START(("file = %s\n", file)); if (_ns == NULL || _directory == NULL || file == NULL || _vnodeID == NULL) return B_BAD_VALUE; Volume *volume = (Volume *)_ns; Inode *directory = (Inode *)_directory; // check access permissions status_t status = directory->CheckPermissions(R_OK); if (status < B_OK) RETURN_ERROR(status); BPlusTree *tree; if (directory->GetTree(&tree) != B_OK) RETURN_ERROR(B_BAD_VALUE); if ((status = tree->Find((uint8 *)file, (uint16)strlen(file), _vnodeID)) < B_OK) { PRINT(("bfs_walk() could not find %Ld:\"%s\": %s\n", directory->BlockNumber(), file, strerror(status))); return status; } RecursiveLocker locker(volume->Lock()); // we have to hold the volume lock in order to not // interfere with new_vnode() here Inode *inode; if ((status = get_vnode(volume->ID(), *_vnodeID, (void **)&inode)) != B_OK) { REPORT_ERROR(status); return B_ENTRY_NOT_FOUND; } // Is inode a symlink? Then resolve it, if we should if (inode->IsSymLink() && _resolvedPath != NULL) { status_t status = B_OK; char *newPath = NULL; // Symbolic links can store their target in the data stream (for links // that take more than 144 bytes of storage [the size of the data_stream // structure]), or directly instead of the data_stream class // So we have to deal with both cases here. // Note: we would naturally call bfs_read_link() here, but the API of the // vnode layer would require us to always reserve a large chunk of memory // for the path, so we're not going to do that if (inode->Flags() & INODE_LONG_SYMLINK) { size_t readBytes = inode->Size(); char *data = (char *)malloc(readBytes); if (data != NULL) { status = inode->ReadAt(0, (uint8 *)data, &readBytes); if (status == B_OK && readBytes == inode->Size()) status = new_path(data, &newPath); free(data); } else status = B_NO_MEMORY; } else status = new_path((char *)&inode->Node()->short_symlink, &newPath); put_vnode(volume->ID(), inode->ID()); if (status == B_OK) *_resolvedPath = newPath; RETURN_ERROR(status); } return B_OK; } static int bfs_ioctl(void *_ns, void *_node, void *_cookie, int cmd, void *buffer, size_t bufferLength) { FUNCTION_START(("node = %p, cmd = %d, buf = %p, len = %ld\n", _node, cmd, buffer, bufferLength)); if (_ns == NULL) return B_BAD_VALUE; Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; switch (cmd) { case IOCTL_FILE_UNCACHED_IO: { if (inode == NULL) return B_BAD_VALUE; // if the inode is already set up for uncached access, bail out if (inode->Flags() & INODE_NO_CACHE) { FATAL(("File %Ld is already uncached\n", inode->ID())); return B_ERROR; } PRINT(("uncached access to inode %Ld\n", inode->ID())); // ToDo: sync the cache for this file! // Unfortunately, we can't remove any blocks from the cache in BeOS, // that means we can't guarantee consistency for the file contents // spanning over both access modes. // request buffers for being able to access the file without // using the cache or allocating memory status_t status = volume->Pool().RequestBuffers(volume->BlockSize()); if (status == B_OK) inode->Node()->flags |= HOST_ENDIAN_TO_BFS_INT32(INODE_NO_CACHE); return status; } case IOCTL_CREATE_TIME: { if (inode == NULL || buffer == NULL) return B_BAD_VALUE; off_t *creationTime = (off_t *)buffer; *creationTime = inode->Node()->CreateTime(); return B_OK; } case IOCTL_MODIFIED_TIME: { if (inode == NULL || buffer == NULL) return B_BAD_VALUE; off_t *modifiedTime = (off_t *)buffer; *modifiedTime = inode->LastModified(); return B_OK; } case BFS_IOCTL_VERSION: { uint32 *version = (uint32 *)buffer; *version = 0x10000; return B_OK; } case BFS_IOCTL_START_CHECKING: { // start checking BlockAllocator &allocator = volume->Allocator(); check_control *control = (check_control *)buffer; status_t status = allocator.StartChecking(control); if (status == B_OK && inode != NULL) inode->Node()->flags |= HOST_ENDIAN_TO_BFS_INT32(INODE_CHKBFS_RUNNING); return status; } case BFS_IOCTL_STOP_CHECKING: { // stop checking BlockAllocator &allocator = volume->Allocator(); check_control *control = (check_control *)buffer; status_t status = allocator.StopChecking(control); if (status == B_OK && inode != NULL) inode->Node()->flags &= HOST_ENDIAN_TO_BFS_INT32(~INODE_CHKBFS_RUNNING); return status; } case BFS_IOCTL_CHECK_NEXT_NODE: { // check next BlockAllocator &allocator = volume->Allocator(); check_control *control = (check_control *)buffer; return allocator.CheckNextNode(control); } #ifdef DEBUG case 56742: { // allocate all free blocks and zero them out (a test for the BlockAllocator)! BlockAllocator &allocator = volume->Allocator(); Transaction transaction(volume, 0); CachedBlock cached(volume); block_run run; while (allocator.AllocateBlocks(&transaction, 8, 0, 64, 1, run) == B_OK) { PRINT(("write block_run(%ld, %d, %d)\n", run.allocation_group, run.start, run.length)); for (int32 i = 0;i < run.length;i++) { uint8 *block = cached.SetTo(run); if (block != NULL) { memset(block, 0, volume->BlockSize()); cached.WriteBack(&transaction); } } } return B_OK; } case 56743: dump_super_block(&volume->SuperBlock()); return B_OK; case 56744: if (inode != NULL) dump_inode(inode->Node()); return B_OK; case 56745: if (inode != NULL) dump_block((const char *)inode->Node(), volume->BlockSize()); return B_OK; #endif } return B_BAD_VALUE; } /** Sets the open-mode flags for the open file cookie - only * supports O_APPEND currently, but that should be sufficient * for a file system. */ static int bfs_setflags(void *_ns, void *_node, void *_cookie, int flags) { FUNCTION_START(("node = %p, flags = %d", _node, flags)); file_cookie *cookie = (file_cookie *)_cookie; cookie->open_mode = (cookie->open_mode & ~O_APPEND) | (flags & O_APPEND); return B_OK; } int bfs_select(void *ns, void *node, void *cookie, uint8 event, uint32 ref, selectsync *sync) { FUNCTION_START(("event = %d, ref = %lu, sync = %p\n", event, ref, sync)); notify_select_event(sync, ref); return B_OK; } static int bfs_deselect(void *ns, void *node, void *cookie, uint8 event, selectsync *sync) { FUNCTION(); return B_OK; } static int bfs_fsync(void *_ns, void *_node) { FUNCTION(); if (_node == NULL) return B_BAD_VALUE; Inode *inode = (Inode *)_node; return inode->Sync(); } /** Fills in the stat struct for a node */ static int bfs_read_stat(void *_ns, void *_node, struct stat *st) { FUNCTION(); Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; bfs_inode *node = inode->Node(); st->st_dev = volume->ID(); st->st_ino = inode->ID(); st->st_nlink = 1; st->st_blksize = BFS_IO_SIZE; st->st_uid = node->UserID(); st->st_gid = node->GroupID(); st->st_mode = node->Mode(); st->st_size = node->data.Size(); st->st_atime = time(NULL); st->st_mtime = st->st_ctime = (time_t)(node->LastModifiedTime() >> INODE_TIME_SHIFT); st->st_crtime = (time_t)(node->CreateTime() >> INODE_TIME_SHIFT); return B_NO_ERROR; } static int bfs_write_stat(void *_ns, void *_node, struct stat *stat, long mask) { FUNCTION(); if (_ns == NULL || _node == NULL || stat == NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; // that may be incorrect here - I don't think we need write access to // change most of the stat... // we should definitely check a bit more if the new stats are correct and valid... status_t status = inode->CheckPermissions(W_OK); if (status < B_OK) RETURN_ERROR(status); #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif WriteLocked locked(inode->Lock()); if (locked.IsLocked() < B_OK) RETURN_ERROR(B_ERROR); Transaction transaction(volume, inode->BlockNumber()); bfs_inode *node = inode->Node(); if (mask & WSTAT_SIZE) { // Since WSTAT_SIZE is the only thing that can fail directly, we // do it first, so that the inode state will still be consistent // with the on-disk version if (inode->IsDirectory()) return B_IS_A_DIRECTORY; if (inode->Size() != stat->st_size) { status = inode->SetFileSize(&transaction, stat->st_size); if (status < B_OK) return status; // fill the new blocks (if any) with zeros inode->FillGapWithZeros(inode->OldSize(), inode->Size()); Index index(volume); index.UpdateSize(&transaction, inode); if ((mask & WSTAT_MTIME) == 0) index.UpdateLastModified(&transaction, inode); } } if (mask & WSTAT_MODE) { PRINT(("original mode = %ld, stat->st_mode = %d\n", node->mode, stat->st_mode)); node->mode = node->mode & ~S_IUMSK | stat->st_mode & S_IUMSK; } if (mask & WSTAT_UID) node->uid = stat->st_uid; if (mask & WSTAT_GID) node->gid = stat->st_gid; if (mask & WSTAT_MTIME) { // Index::UpdateLastModified() will set the new time in the inode Index index(volume); index.UpdateLastModified(&transaction, inode, (bigtime_t)stat->st_mtime << INODE_TIME_SHIFT); } if (mask & WSTAT_CRTIME) { node->create_time = (bigtime_t)stat->st_crtime << INODE_TIME_SHIFT; } if ((status = inode->WriteBack(&transaction)) == B_OK) transaction.Done(); notify_listener(B_STAT_CHANGED, volume->ID(), 0, 0, inode->ID(), NULL); return status; } int bfs_create(void *_ns, void *_directory, const char *name, int omode, int mode, vnode_id *vnodeID, void **_cookie) { FUNCTION_START(("name = \"%s\", perms = %d, omode = %d\n", name, mode, omode)); if (_ns == NULL || _directory == NULL || _cookie == NULL || name == NULL || *name == '\0') RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; Inode *directory = (Inode *)_directory; if (!directory->IsDirectory()) RETURN_ERROR(B_BAD_TYPE); status_t status = directory->CheckPermissions(W_OK); if (status < B_OK) RETURN_ERROR(status); // We are creating the cookie at this point, so that we don't have // to remove the inode if we don't have enough free memory later... file_cookie *cookie = (file_cookie *)malloc(sizeof(file_cookie)); if (cookie == NULL) RETURN_ERROR(B_NO_MEMORY); // initialize the cookie cookie->open_mode = omode; cookie->last_size = 0; cookie->last_notification = system_time(); #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, directory->BlockNumber()); status = Inode::Create(&transaction, directory, name, S_FILE | (mode & S_IUMSK), omode, 0, vnodeID); if (status >= B_OK) { transaction.Done(); // register the cookie *_cookie = cookie; notify_listener(B_ENTRY_CREATED, volume->ID(), directory->ID(), 0, *vnodeID, name); } else free(cookie); return status; } int bfs_symlink(void *_ns, void *_directory, const char *name, const char *path) { FUNCTION(); if (_ns == NULL || _directory == NULL || path == NULL || name == NULL || *name == '\0') RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; Inode *directory = (Inode *)_directory; if (!directory->IsDirectory()) RETURN_ERROR(B_BAD_TYPE); status_t status = directory->CheckPermissions(W_OK); if (status < B_OK) RETURN_ERROR(status); #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, directory->BlockNumber()); Inode *link; off_t id; status = Inode::Create(&transaction, directory, name, S_SYMLINK | 0777, 0, 0, &id, &link); if (status < B_OK) RETURN_ERROR(status); size_t length = strlen(path); if (length < SHORT_SYMLINK_NAME_LENGTH) { strcpy(link->Node()->short_symlink, path); status = link->WriteBack(&transaction); } else { link->Node()->flags |= HOST_ENDIAN_TO_BFS_INT32(INODE_LONG_SYMLINK | INODE_LOGGED); // The following call will have to write the inode back, so // we don't have to do that here... status = link->WriteAt(&transaction, 0, (const uint8 *)path, &length); } // ToDo: would be nice if Inode::Create() would let the INODE_NOT_READY // flag set until here, so that it can be accessed directly // Inode::Create() left the inode locked in memory put_vnode(volume->ID(), id); if (status == B_OK) { transaction.Done(); notify_listener(B_ENTRY_CREATED, volume->ID(), directory->ID(), 0, id, name); } return status; } int bfs_link(void *ns, void *dir, const char *name, void *node) { FUNCTION_START(("name = \"%s\"\n", name)); // This one won't be implemented in a binary compatible BFS return B_ERROR; } int bfs_unlink(void *_ns, void *_directory, const char *name) { FUNCTION_START(("name = \"%s\"\n", name)); if (_ns == NULL || _directory == NULL || name == NULL || *name == '\0') return B_BAD_VALUE; if (!strcmp(name, "..") || !strcmp(name, ".")) return B_NOT_ALLOWED; Volume *volume = (Volume *)_ns; Inode *directory = (Inode *)_directory; status_t status = directory->CheckPermissions(W_OK); if (status < B_OK) return status; #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, directory->BlockNumber()); off_t id; if ((status = directory->Remove(&transaction, name, &id)) == B_OK) { transaction.Done(); notify_listener(B_ENTRY_REMOVED, volume->ID(), directory->ID(), 0, id, NULL); } return status; } int bfs_rename(void *_ns, void *_oldDir, const char *oldName, void *_newDir, const char *newName) { FUNCTION_START(("oldDir = %p, oldName = \"%s\", newDir = %p, newName = \"%s\"\n", _oldDir, oldName, _newDir, newName)); // there might be some more tests needed?! if (_ns == NULL || _oldDir == NULL || _newDir == NULL || oldName == NULL || *oldName == '\0' || newName == NULL || *newName == '\0' || !strcmp(oldName, ".") || !strcmp(oldName, "..") || !strcmp(newName, ".") || !strcmp(newName, "..") || strchr(newName, '/') != NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; Inode *oldDirectory = (Inode *)_oldDir; Inode *newDirectory = (Inode *)_newDir; // are we already done? if (oldDirectory == newDirectory && !strcmp(oldName, newName)) return B_OK; RecursiveLocker locker(volume->Lock()); // get the directory's tree, and a pointer to the inode which should be changed BPlusTree *tree; status_t status = oldDirectory->GetTree(&tree); if (status < B_OK) RETURN_ERROR(status); off_t id; status = tree->Find((const uint8 *)oldName, strlen(oldName), &id); if (status < B_OK) RETURN_ERROR(status); Vnode vnode(volume, id); Inode *inode; if (vnode.Get(&inode) < B_OK) return B_IO_ERROR; // Don't move a directory into one of its children - we soar up // from the newDirectory to either the root node or the old // directory, whichever comes first. // If we meet our inode on that way, we have to bail out. if (oldDirectory != newDirectory) { vnode_id parent = volume->ToVnode(newDirectory->Parent()); vnode_id root = volume->RootNode()->ID(); while (true) { if (parent == id) return B_BAD_VALUE; else if (parent == root || parent == oldDirectory->ID()) break; Vnode vnode(volume, parent); Inode *parentNode; if (vnode.Get(&parentNode) < B_OK) return B_ERROR; parent = volume->ToVnode(parentNode->Parent()); } } // Everything okay? Then lets get to work... Transaction transaction(volume, oldDirectory->BlockNumber()); // First, try to make sure there is nothing that will stop us in // the target directory - since this is the only non-critical // failure, we will test this case first BPlusTree *newTree = tree; if (newDirectory != oldDirectory) { status = newDirectory->GetTree(&newTree); if (status < B_OK) RETURN_ERROR(status); } status = newTree->Insert(&transaction, (const uint8 *)newName, strlen(newName), id); if (status == B_NAME_IN_USE) { // If there is already a file with that name, we have to remove // it, as long it's not a directory with files in it off_t clobber; if (newTree->Find((const uint8 *)newName, strlen(newName), &clobber) < B_OK) return B_NAME_IN_USE; if (clobber == id) return B_BAD_VALUE; Vnode vnode(volume, clobber); Inode *other; if (vnode.Get(&other) < B_OK) return B_NAME_IN_USE; status = newDirectory->Remove(&transaction, newName, NULL, other->IsDirectory()); if (status < B_OK) return status; notify_listener(B_ENTRY_REMOVED, volume->ID(), newDirectory->ID(), 0, clobber, NULL); status = newTree->Insert(&transaction, (const uint8 *)newName, strlen(newName), id); } if (status < B_OK) return status; // If anything fails now, we have to remove the inode from the // new directory in any case to restore the previous state status_t bailStatus = B_OK; // update the name only when they differ bool nameUpdated = false; if (strcmp(oldName, newName)) { status = inode->SetName(&transaction, newName); if (status == B_OK) { Index index(volume); index.UpdateName(&transaction, oldName, newName, inode); nameUpdated = true; } } if (status == B_OK) { status = tree->Remove(&transaction, (const uint8 *)oldName, strlen(oldName), id); if (status == B_OK) { inode->Node()->parent = newDirectory->BlockRun(); // if it's a directory, update the parent directory pointer // in its tree if necessary BPlusTree *movedTree = NULL; if (oldDirectory != newDirectory && inode->IsDirectory() && (status = inode->GetTree(&movedTree)) == B_OK) status = movedTree->Replace(&transaction, (const uint8 *)"..", 2, newDirectory->ID()); if (status == B_OK) { status = inode->WriteBack(&transaction); if (status == B_OK) { transaction.Done(); notify_listener(B_ENTRY_MOVED, volume->ID(), oldDirectory->ID(), newDirectory->ID(), id, newName); return B_OK; } } // Those better don't fail, or we switch to a read-only // device for safety reasons (Volume::Panic() does this // for us) // Anyway, if we overwrote a file in the target directory // this is lost now (only in-memory, not on-disk)... bailStatus = tree->Insert(&transaction, (const uint8 *)oldName, strlen(oldName), id); if (movedTree != NULL) movedTree->Replace(&transaction, (const uint8 *)"..", 2, oldDirectory->ID()); } } if (bailStatus == B_OK && nameUpdated) bailStatus = inode->SetName(&transaction, oldName); if (bailStatus == B_OK) bailStatus = newTree->Remove(&transaction, (const uint8 *)newName, strlen(newName), id); if (bailStatus < B_OK) volume->Panic(); return status; } /** Opens the file with the specified mode. */ static int bfs_open(void *_ns, void *_node, int omode, void **_cookie) { FUNCTION(); if (_ns == NULL || _node == NULL || _cookie == NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; // we can't open a file which uses uncached access twice if (inode->Flags() & INODE_NO_CACHE) return B_BUSY; // opening a directory read-only is allowed, although you can't read // any data from it. if (inode->IsDirectory() && omode & O_RWMASK) { omode = omode & ~O_RWMASK; // ToDo: for compatibility reasons, we don't return an error here... // e.g. "copyattr" tries to do that //return B_IS_A_DIRECTORY; } status_t status = inode->CheckPermissions(oModeToAccess(omode)); if (status < B_OK) RETURN_ERROR(status); // we could actually use the cookie to keep track of: // - the last block_run // - the location in the data_stream (indirect, double indirect, // position in block_run array) // // This could greatly speed up continuous reads of big files, especially // in the indirect block section. file_cookie *cookie = (file_cookie *)malloc(sizeof(file_cookie)); if (cookie == NULL) RETURN_ERROR(B_NO_MEMORY); // initialize the cookie cookie->open_mode = omode; // needed by e.g. bfs_write() for O_APPEND cookie->last_size = inode->Size(); cookie->last_notification = system_time(); // Should we truncate the file? if (omode & O_TRUNC) { WriteLocked locked(inode->Lock()); Transaction transaction(volume, inode->BlockNumber()); status_t status = inode->SetFileSize(&transaction, 0); if (status < B_OK) { // bfs_free_cookie() is only called if this function is successful free(cookie); return status; } transaction.Done(); } *_cookie = cookie; return B_OK; } /** Read a file specified by node, using information in cookie * and at offset specified by pos. read len bytes into buffer buf. */ static int bfs_read(void *_ns, void *_node, void *_cookie, off_t pos, void *buffer, size_t *_length) { //FUNCTION(); Inode *inode = (Inode *)_node; if (!inode->HasUserAccessableStream()) { *_length = 0; RETURN_ERROR(B_BAD_VALUE); } ReadLocked locked(inode->Lock()); return inode->ReadAt(pos, (uint8 *)buffer, _length); } int bfs_write(void *_ns, void *_node, void *_cookie, off_t pos, const void *buffer, size_t *_length) { //FUNCTION(); // uncomment to be more robust against a buggy vnode layer ;-) //if (_ns == NULL || _node == NULL || _cookie == NULL) // return B_BAD_VALUE; Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; if (!inode->HasUserAccessableStream()) { *_length = 0; RETURN_ERROR(B_BAD_VALUE); } file_cookie *cookie = (file_cookie *)_cookie; if (cookie->open_mode & O_APPEND) pos = inode->Size(); WriteLocked locked(inode->Lock()); if (locked.IsLocked() < B_OK) RETURN_ERROR(B_ERROR); Transaction transaction; // We are not starting the transaction here, since // it might not be needed at all (the contents of // regular files aren't logged) status_t status = inode->WriteAt(&transaction, pos, (const uint8 *)buffer, _length); if (status == B_OK) transaction.Done(); if (status == B_OK && (inode->Flags() & INODE_NO_CACHE) == 0) { // uncached files don't cause notifications during access, and // never want to write back any cached blocks // periodically notify if the file size has changed // ToDo: should we better test for a change in the last_modified time only? if (cookie->last_size != inode->Size() && system_time() > cookie->last_notification + INODE_NOTIFICATION_INTERVAL) { notify_listener(B_STAT_CHANGED, volume->ID(), 0, 0, inode->ID(), NULL); cookie->last_size = inode->Size(); cookie->last_notification = system_time(); } // This will flush the dirty blocks to disk from time to time. // It's done here and not in Inode::WriteAt() so that it won't // add to the duration of a transaction - it might even be a // good idea to offload those calls to another thread volume->WriteCachedBlocksIfNecessary(); } return status; } /** Do whatever is necessary to close a file, EXCEPT for freeing * the cookie! */ static int bfs_close(void *_ns, void *_node, void *_cookie) { FUNCTION(); if (_ns == NULL || _node == NULL || _cookie == NULL) return B_BAD_VALUE; file_cookie *cookie = (file_cookie *)_cookie; Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; if (cookie->open_mode & O_RWMASK) { #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif ReadLocked locked(inode->Lock()); // trim the preallocated blocks and update the size, // and last_modified indices if needed Transaction transaction(volume, inode->BlockNumber()); status_t status = B_OK; bool changed = false; Index index(volume); if (inode->OldSize() != inode->Size()) { status = inode->Trim(&transaction); if (status < B_OK) FATAL(("Could not trim preallocated blocks!")); index.UpdateSize(&transaction, inode); changed = true; } if (inode->OldLastModified() != inode->LastModified()) { index.UpdateLastModified(&transaction, inode, inode->LastModified()); changed = true; // updating the index doesn't write back the inode inode->WriteBack(&transaction); } if (status == B_OK) transaction.Done(); if (changed) notify_listener(B_STAT_CHANGED, volume->ID(), 0, 0, inode->ID(), NULL); } return B_OK; } static int bfs_free_cookie(void *_ns, void *_node, void *_cookie) { FUNCTION(); if (_ns == NULL || _node == NULL || _cookie == NULL) return B_BAD_VALUE; file_cookie *cookie = (file_cookie *)_cookie; Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; if (cookie != NULL) free(cookie); if (inode->Flags() & INODE_NO_CACHE) { volume->Pool().ReleaseBuffers(); inode->Node()->flags &= HOST_ENDIAN_TO_BFS_INT32(~INODE_NO_CACHE); // We don't need to save the inode, because INODE_NO_CACHE is a // non-permanent flag which will be removed when the inode is loaded // into memory. } if (inode->Flags() & INODE_CHKBFS_RUNNING) { // "chkbfs" exited abnormally, so we have to stop it here... FATAL(("check process was aborted!\n")); volume->Allocator().StopChecking(NULL); } return B_OK; } /** Checks access permissions, return B_NOT_ALLOWED if the action * is not allowed. */ static int bfs_access(void *_ns, void *_node, int accessMode) { FUNCTION(); if (_ns == NULL || _node == NULL) return B_BAD_VALUE; Inode *inode = (Inode *)_node; status_t status = inode->CheckPermissions(accessMode); if (status < B_OK) RETURN_ERROR(status); return B_OK; } static int bfs_read_link(void *_ns, void *_node, char *buffer, size_t *bufferSize) { FUNCTION(); Inode *inode = (Inode *)_node; if (!inode->IsSymLink()) RETURN_ERROR(B_BAD_VALUE); if (inode->Flags() & INODE_LONG_SYMLINK) { status_t status = inode->ReadAt(0, (uint8 *)buffer, bufferSize); if (status < B_OK) RETURN_ERROR(status); *bufferSize = inode->Size(); return B_OK; } size_t numBytes = strlen((char *)&inode->Node()->short_symlink); uint32 bytes = numBytes; if (bytes > *bufferSize) bytes = *bufferSize; memcpy(buffer, inode->Node()->short_symlink, bytes); *bufferSize = numBytes; return B_OK; } // #pragma mark - // Directory functions static int bfs_mkdir(void *_ns, void *_directory, const char *name, int mode) { FUNCTION_START(("name = \"%s\", perms = %d\n", name, mode)); if (_ns == NULL || _directory == NULL || name == NULL || *name == '\0') RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; Inode *directory = (Inode *)_directory; if (!directory->IsDirectory()) RETURN_ERROR(B_BAD_TYPE); status_t status = directory->CheckPermissions(W_OK); if (status < B_OK) RETURN_ERROR(status); #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, directory->BlockNumber()); // Inode::Create() locks the inode if we pass the "id" parameter, but we // need it anyway off_t id; status = Inode::Create(&transaction, directory, name, S_DIRECTORY | (mode & S_IUMSK), 0, 0, &id); if (status == B_OK) { put_vnode(volume->ID(), id); transaction.Done(); notify_listener(B_ENTRY_CREATED, volume->ID(), directory->ID(), 0, id, name); } return status; } static int bfs_rmdir(void *_ns, void *_directory, const char *name) { FUNCTION_START(("name = \"%s\"\n", name)); if (_ns == NULL || _directory == NULL || name == NULL || *name == '\0') return B_BAD_VALUE; Volume *volume = (Volume *)_ns; Inode *directory = (Inode *)_directory; #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, directory->BlockNumber()); off_t id; status_t status = directory->Remove(&transaction, name, &id, true); if (status == B_OK) { transaction.Done(); notify_listener(B_ENTRY_REMOVED, volume->ID(), directory->ID(), 0, id, NULL); } return status; } /** creates fs-specific "cookie" struct that keeps track of where * you are at in reading through directory entries in bfs_readdir. */ static int bfs_open_dir(void *_ns, void *_node, void **_cookie) { FUNCTION(); if (_ns == NULL || _node == NULL || _cookie == NULL) RETURN_ERROR(B_BAD_VALUE); Inode *inode = (Inode *)_node; // we don't ask here for directories only, because the bfs_open_index_dir() // function utilizes us (so we must be able to open indices as well) if (!inode->IsContainer()) RETURN_ERROR(B_BAD_VALUE); BPlusTree *tree; if (inode->GetTree(&tree) != B_OK) RETURN_ERROR(B_BAD_VALUE); TreeIterator *iterator = new TreeIterator(tree); if (iterator == NULL) RETURN_ERROR(B_NO_MEMORY); *_cookie = iterator; return B_OK; } static int bfs_read_dir(void *_ns, void *_node, void *_cookie, long *num, struct dirent *dirent, size_t bufferSize) { FUNCTION(); TreeIterator *iterator = (TreeIterator *)_cookie; if (iterator == NULL) RETURN_ERROR(B_BAD_VALUE); uint16 length; vnode_id id; status_t status = iterator->GetNextEntry(dirent->d_name, &length, bufferSize, &id); if (status == B_ENTRY_NOT_FOUND) { *num = 0; return B_OK; } else if (status != B_OK) RETURN_ERROR(status); Volume *volume = (Volume *)_ns; dirent->d_dev = volume->ID(); dirent->d_ino = id; #ifdef KEEP_WRONG_DIRENT_RECLEN dirent->d_reclen = length; #else dirent->d_reclen = sizeof(struct dirent) + length; #endif *num = 1; return B_OK; } /** Sets the TreeIterator back to the beginning of the directory */ static int bfs_rewind_dir(void * /*ns*/, void * /*node*/, void *_cookie) { FUNCTION(); TreeIterator *iterator = (TreeIterator *)_cookie; if (iterator == NULL) RETURN_ERROR(B_BAD_VALUE); return iterator->Rewind(); } static int bfs_close_dir(void * /*ns*/, void * /*node*/, void * /*_cookie*/) { FUNCTION(); // Do whatever you need to to close a directory, but DON'T free the cookie! return B_OK; } static int bfs_free_dir_cookie(void *ns, void *node, void *_cookie) { TreeIterator *iterator = (TreeIterator *)_cookie; if (iterator == NULL) RETURN_ERROR(B_BAD_VALUE); delete iterator; return B_OK; } // #pragma mark - // Attribute functions static int bfs_open_attrdir(void *_ns, void *_node, void **cookie) { FUNCTION(); Inode *inode = (Inode *)_node; if (inode == NULL || inode->Node() == NULL) RETURN_ERROR(B_ERROR); AttributeIterator *iterator = new AttributeIterator(inode); if (iterator == NULL) RETURN_ERROR(B_NO_MEMORY); *cookie = iterator; return B_OK; } static int bfs_close_attrdir(void *ns, void *node, void *cookie) { FUNCTION(); return B_OK; } static int bfs_free_attrdir_cookie(void *ns, void *node, void *_cookie) { FUNCTION(); AttributeIterator *iterator = (AttributeIterator *)_cookie; if (iterator == NULL) RETURN_ERROR(B_BAD_VALUE); delete iterator; return B_OK; } static int bfs_rewind_attrdir(void *_ns, void *_node, void *_cookie) { FUNCTION(); AttributeIterator *iterator = (AttributeIterator *)_cookie; if (iterator == NULL) RETURN_ERROR(B_BAD_VALUE); RETURN_ERROR(iterator->Rewind()); } static int bfs_read_attrdir(void *_ns, void *node, void *_cookie, long *num, struct dirent *dirent, size_t bufsize) { FUNCTION(); AttributeIterator *iterator = (AttributeIterator *)_cookie; if (iterator == NULL) RETURN_ERROR(B_BAD_VALUE); uint32 type; size_t length; status_t status = iterator->GetNext(dirent->d_name, &length, &type, &dirent->d_ino); if (status == B_ENTRY_NOT_FOUND) { *num = 0; return B_OK; } else if (status != B_OK) RETURN_ERROR(status); Volume *volume = (Volume *)_ns; dirent->d_dev = volume->ID(); #ifdef KEEP_WRONG_DIRENT_RECLEN dirent->d_reclen = length; #else dirent->d_reclen = sizeof(struct dirent) + length; #endif *num = 1; return B_OK; } static int bfs_remove_attr(void *_ns, void *_node, const char *name) { FUNCTION_START(("name = \"%s\"\n", name)); if (_ns == NULL || _node == NULL || name == NULL) return B_BAD_VALUE; Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; status_t status = inode->CheckPermissions(W_OK); if (status < B_OK) return status; #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, inode->BlockNumber()); status = inode->RemoveAttribute(&transaction, name); if (status == B_OK) { transaction.Done(); notify_listener(B_ATTR_CHANGED, volume->ID(), 0, 0, inode->ID(), name); } RETURN_ERROR(status); } static int bfs_rename_attr(void *ns, void *node, const char *oldname, const char *newname) { FUNCTION_START(("name = \"%s\", to = \"%s\"\n", oldname, newname)); // ToDo: implement bfs_rename_attr()! // I'll skip the implementation here, and will do it for OpenBeOS - at least // there will be an API to move one attribute to another file, making that // function much more complicated - oh joy ;-) RETURN_ERROR(B_ENTRY_NOT_FOUND); } static int bfs_stat_attr(void *ns, void *_node, const char *name, struct attr_info *attrInfo) { FUNCTION_START(("name = \"%s\"\n", name)); Inode *inode = (Inode *)_node; if (inode == NULL || inode->Node() == NULL) RETURN_ERROR(B_ERROR); // first, try to find it in the small data region small_data *smallData = NULL; if (inode->SmallDataLock().Lock() == B_OK) { if ((smallData = inode->FindSmallData((const char *)name)) != NULL) { attrInfo->type = smallData->Type(); attrInfo->size = smallData->DataSize(); } inode->SmallDataLock().Unlock(); } if (smallData != NULL) return B_OK; // then, search in the attribute directory Inode *attribute; status_t status = inode->GetAttribute(name, &attribute); if (status == B_OK) { attrInfo->type = attribute->Type(); attrInfo->size = attribute->Size(); inode->ReleaseAttribute(attribute); return B_OK; } RETURN_ERROR(status); } static int bfs_write_attr(void *_ns, void *_node, const char *name, int type, const void *buffer, size_t *_length, off_t pos) { FUNCTION_START(("name = \"%s\"\n", name)); if (_ns == NULL || _node == NULL || name == NULL || *name == '\0') RETURN_ERROR(B_BAD_VALUE); // Writing the name attribute using this function is not allowed, // also using the reserved indices name, last_modified, and size // shouldn't be allowed. // ToDo: we might think about allowing to update those values, but // really change their corresponding values in the bfs_inode structure if (name[0] == FILE_NAME_NAME && name[1] == '\0' || !strcmp(name, "name") || !strcmp(name, "last_modified") || !strcmp(name, "size")) RETURN_ERROR(B_NOT_ALLOWED); Volume *volume = (Volume *)_ns; Inode *inode = (Inode *)_node; status_t status = inode->CheckPermissions(W_OK); if (status < B_OK) return status; #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, inode->BlockNumber()); status = inode->WriteAttribute(&transaction, name, type, pos, (const uint8 *)buffer, _length); if (status == B_OK) { transaction.Done(); notify_listener(B_ATTR_CHANGED, volume->ID(), 0, 0, inode->ID(), name); } return status; } static int bfs_read_attr(void *_ns, void *_node, const char *name, int type, void *buffer, size_t *_length, off_t pos) { FUNCTION(); Inode *inode = (Inode *)_node; if (inode == NULL || name == NULL || *name == '\0' || buffer == NULL) RETURN_ERROR(B_BAD_VALUE); status_t status = inode->CheckPermissions(R_OK); if (status < B_OK) return status; return inode->ReadAttribute(name, type, pos, (uint8 *)buffer, _length); } // #pragma mark - // Index functions static int bfs_open_indexdir(void *_ns, void **_cookie) { FUNCTION(); if (_ns == NULL || _cookie == NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; if (volume->IndicesNode() == NULL) RETURN_ERROR(B_ENTRY_NOT_FOUND); // Since the indices root node is just a directory, and we are storing // a pointer to it in our Volume object, we can just use the directory // traversal functions. // In fact we're storing it in the Volume object for that reason. RETURN_ERROR(bfs_open_dir(_ns, volume->IndicesNode(), _cookie)); } static int bfs_close_indexdir(void *_ns, void *_cookie) { FUNCTION(); if (_ns == NULL || _cookie == NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; RETURN_ERROR(bfs_close_dir(_ns, volume->IndicesNode(), _cookie)); } static int bfs_free_indexdir_cookie(void *_ns, void *_node, void *_cookie) { FUNCTION(); if (_ns == NULL || _cookie == NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; RETURN_ERROR(bfs_free_dir_cookie(_ns, volume->IndicesNode(), _cookie)); } static int bfs_rewind_indexdir(void *_ns, void *_cookie) { FUNCTION(); if (_ns == NULL || _cookie == NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; RETURN_ERROR(bfs_rewind_dir(_ns, volume->IndicesNode(), _cookie)); } static int bfs_read_indexdir(void *_ns, void *_cookie, long *num, struct dirent *dirent, size_t bufferSize) { FUNCTION(); if (_ns == NULL || _cookie == NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; RETURN_ERROR(bfs_read_dir(_ns, volume->IndicesNode(), _cookie, num, dirent, bufferSize)); } static int bfs_create_index(void *_ns, const char *name, int type, int flags) { FUNCTION_START(("name = \"%s\", type = %d, flags = %d\n", name, type, flags)); if (_ns == NULL || name == NULL || *name == '\0') return B_BAD_VALUE; Volume *volume = (Volume *)_ns; if (volume->IsReadOnly()) return B_READ_ONLY_DEVICE; // only root users are allowed to create indices if (geteuid() != 0) return B_NOT_ALLOWED; #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, volume->Indices()); Index index(volume); status_t status = index.Create(&transaction, name, type); if (status == B_OK) transaction.Done(); RETURN_ERROR(status); } static int bfs_remove_index(void *_ns, const char *name) { FUNCTION(); if (_ns == NULL || name == NULL || *name == '\0') return B_BAD_VALUE; Volume *volume = (Volume *)_ns; if (volume->IsReadOnly()) return B_READ_ONLY_DEVICE; // only root users are allowed to remove indices if (geteuid() != 0) return B_NOT_ALLOWED; Inode *indices; if ((indices = volume->IndicesNode()) == NULL) return B_ENTRY_NOT_FOUND; #ifdef UNSAFE_GET_VNODE RecursiveLocker locker(volume->Lock()); #endif Transaction transaction(volume, volume->Indices()); status_t status = indices->Remove(&transaction, name); if (status == B_OK) transaction.Done(); RETURN_ERROR(status); } static int bfs_rename_index(void *ns, const char *oldname, const char *newname) { FUNCTION_START(("from = %s, to = %s\n", oldname, newname)); // Well, renaming an index doesn't make that much sense, as you // would also need to remove every file in it (or the index // would contain wrong data) // But in that case, you can better remove the old one, and // create a new one... // There is also no way to call this function from a userland // application. RETURN_ERROR(B_ENTRY_NOT_FOUND); } static int bfs_stat_index(void *_ns, const char *name, struct index_info *indexInfo) { FUNCTION_START(("name = %s\n", name)); if (_ns == NULL || name == NULL || indexInfo == NULL) RETURN_ERROR(B_BAD_VALUE); Volume *volume = (Volume *)_ns; Index index(volume); status_t status = index.SetTo(name); if (status < B_OK) RETURN_ERROR(status); bfs_inode *node = index.Node()->Node(); indexInfo->type = index.Type(); indexInfo->size = node->data.Size(); indexInfo->modification_time = (time_t)(node->LastModifiedTime() >> INODE_TIME_SHIFT); indexInfo->creation_time = (time_t)(node->CreateTime() >> INODE_TIME_SHIFT); indexInfo->uid = node->UserID(); indexInfo->gid = node->GroupID(); return B_OK; } // #pragma mark - // Query functions static int bfs_open_query(void *_ns, const char *queryString, ulong flags, port_id port, long token, void **cookie) { FUNCTION(); if (_ns == NULL || queryString == NULL || cookie == NULL) RETURN_ERROR(B_BAD_VALUE); PRINT(("query = \"%s\", flags = %lu, port_id = %ld, token = %ld\n", queryString, flags, port, token)); Volume *volume = (Volume *)_ns; Expression *expression = new Expression((char *)queryString); if (expression == NULL) RETURN_ERROR(B_NO_MEMORY); if (expression->InitCheck() < B_OK) { FATAL(("Could not parse query, stopped at: \"%s\"\n", expression->Position())); delete expression; RETURN_ERROR(B_BAD_VALUE); } Query *query = new Query(volume, expression, flags); if (query == NULL) { delete expression; RETURN_ERROR(B_NO_MEMORY); } if (flags & B_LIVE_QUERY) query->SetLiveMode(port, token); *cookie = (void *)query; return B_OK; } static int bfs_close_query(void *ns, void *cookie) { FUNCTION(); return B_OK; } static int bfs_free_query_cookie(void *ns, void *node, void *cookie) { FUNCTION(); if (cookie == NULL) RETURN_ERROR(B_BAD_VALUE); Query *query = (Query *)cookie; Expression *expression = query->GetExpression(); delete query; delete expression; return B_OK; } static int bfs_read_query(void */*ns*/, void *cookie, long *num, struct dirent *dirent, size_t bufferSize) { FUNCTION(); Query *query = (Query *)cookie; if (query == NULL) RETURN_ERROR(B_BAD_VALUE); status_t status = query->GetNextEntry(dirent, bufferSize); if (status == B_OK) *num = 1; else if (status == B_ENTRY_NOT_FOUND) *num = 0; else return status; return B_OK; }