// read the target in a symlink (including the null character at the end) // return the number of bytes read on success (up to MIN(buflen, linkpath size)) // return -EINVAL if the entry isn't a symlink // return -EIO if the entry is corrupt (indicates a bug in fskit) // return -errno if path resolution fails ssize_t fskit_readlink( struct fskit_core* core, char const* path, uint64_t user, uint64_t group, char* buf, size_t buflen ) { int err = 0; ssize_t num_read = 0; struct fskit_entry* fent = NULL; // find this fent = fskit_entry_resolve_path( core, path, user, group, false, &err ); if( err != 0 || fent == NULL ) { return err; } // symlink? if( fent->type != FSKIT_ENTRY_TYPE_LNK ) { fskit_entry_unlock( fent ); return -EINVAL; } // sanity check if( fent->symlink_target == NULL ) { fskit_error("BUG: fskit entry %" PRIX64 " (at %p) is a symlink, but has no target path set\n", fent->file_id, fent ); fskit_entry_unlock( fent ); return -EIO; } // read it (including null character) num_read = (ssize_t)MIN( buflen, (unsigned)fent->size + 1 ); memcpy( buf, fent->symlink_target, num_read ); fskit_entry_unlock( fent ); return num_read; }
int fskit_access( struct fskit_core* core, char const* path, uint64_t user, uint64_t group, mode_t mode ) { int err = 0; struct fskit_entry* fent = fskit_entry_resolve_path( core, path, user, group, false, &err ); if( !fent || err ) { if( !err ) { err = -ENOMEM; } return err; } // F_OK implicitly satisfied // give the application a chance to process the stat buffer struct stat sb; err = fskit_entry_fstat( fent, &sb ); if( err == 0 ) { // check against stat buffer if( (mode & R_OK) && !FSKIT_ENTRY_IS_READABLE( sb.st_mode, sb.st_uid, sb.st_gid, user, group ) ) { err = -EACCES; } else if( (mode & W_OK) && !FSKIT_ENTRY_IS_WRITEABLE( sb.st_mode, sb.st_uid, sb.st_gid, user, group ) ) { err = -EACCES; } else if( (mode & X_OK) && !FSKIT_ENTRY_IS_EXECUTABLE( sb.st_mode, sb.st_uid, sb.st_gid, user, group ) ) { err = -EACCES; } } fskit_entry_unlock( fent ); return err; }
// update a file's manifest, in response to a remote call // return 0 on success // return -ENOENT if not found // return -ESTALE if not local // return -errno on error // NOTE: the permissions will already have been checked by the server static int UG_impl_manifest_patch( struct SG_gateway* gateway, struct SG_request_data* reqdat, struct SG_manifest* write_delta, void* cls ) { int rc = 0; int ref_rc = 0; struct fskit_entry* fent = NULL; struct UG_state* ug = (struct UG_state*)SG_gateway_cls( gateway ); struct fskit_core* fs = UG_state_fs( ug ); struct UG_inode* inode = NULL; struct ms_client* ms = SG_gateway_ms( gateway ); uint64_t volume_id = ms_client_get_volume_id( ms ); rc = UG_consistency_path_ensure_fresh( gateway, reqdat->fs_path ); if( rc != 0 ) { SG_error("UG_consistency_path_ensure_fresh('%s') rc = %d\n", reqdat->fs_path, rc ); return rc; } rc = UG_consistency_manifest_ensure_fresh( gateway, reqdat->fs_path ); if( rc != 0 ) { SG_error("UG_consistency_manifest_ensure_fresh('%s') rc = %d\n", reqdat->fs_path, rc ); return rc; } // look up fent = fskit_entry_resolve_path( fs, reqdat->fs_path, reqdat->user_id, volume_id, true, &rc ); if( fent == NULL ) { return rc; } inode = (struct UG_inode*)fskit_entry_get_user_data( fent ); // must be coordinated by us if( UG_inode_coordinator_id( inode ) != SG_gateway_id( gateway ) ) { fskit_entry_unlock( fent ); return -ESTALE; } // update the manifest fskit_entry_ref_entry( fent ); rc = UG_write_patch_manifest( gateway, reqdat, inode, write_delta ); fskit_entry_unlock( fent ); ref_rc = fskit_entry_unref( fs, reqdat->fs_path, fent ); if( ref_rc != 0 ) { SG_warn("fskit_entry_unref('%s') rc = %d\n", reqdat->fs_path, rc ); } return rc; }
// truncate a file to a given size // return 0 on success // return negative on failure int fskit_trunc( struct fskit_core* core, char const* path, uint64_t user, uint64_t group, off_t new_size ) { int err = 0; int rc = 0; // get the fent struct fskit_entry* fent = fskit_entry_resolve_path( core, path, user, group, true, &err ); if( fent == NULL || err != 0 ) { return err; } // reference the fent, so it won't go anywhere fent->open_count++; fskit_entry_unlock( fent ); rc = fskit_run_user_trunc( core, path, fent, new_size, NULL ); // unreference fskit_entry_wlock( fent ); fent->open_count--; // need to free? note that this may unlock and re-lock fent, but only if it cannot be resolved by any path // NOTE: this may unlock and destroy the fent rc = fskit_entry_try_destroy_and_free( core, path, fent ); if( rc > 0 ) { // fent was unlocked and destroyed rc = 0; } else if( rc < 0 ) { // some error occurred fskit_error("fskit_entry_try_destroy(%p) rc = %d\n", fent, rc ); fskit_entry_unlock( fent ); return rc; } else { // done with this entry fskit_entry_unlock( fent ); } return rc; }
int fskit_listxattr( struct fskit_core* core, char const* path, uint64_t user, uint64_t group, char* list, size_t size ) { int err = 0; int rc = 0; // get the fent struct fskit_entry* fent = fskit_entry_resolve_path( core, path, user, group, false, &err ); if( fent == NULL || err != 0 ) { return err; } // get the xattr rc = fskit_flistxattr( core, fent, list, size ); fskit_entry_unlock( fent ); return rc; }
// change the owner of a path. // the file must be owned by the given user. // NOTE: no ingroup-checking occurs--if the caller is the owner, the new_group can be arbitrary. // the caller should verify that the new_group is allowed by its security model. // Return 0 on success, negative on error: // -ENOMEM if oom // -EPERM if the caller doesn't own the file // -EACCES if the caller can't search a component of the path // -ENOTDIR if the path has a parent non-directory // -ENOENT if the entry doesn't exist int fskit_chown( struct fskit_core* core, char const* path, uint64_t user, uint64_t group, uint64_t new_user, uint64_t new_group ) { int err = 0; struct fskit_entry* fent = fskit_entry_resolve_path( core, path, user, group, true, &err ); if( !fent || err ) { if( !err ) { err = -ENOMEM; } return err; } // can't chown unless we own the file if( fent->owner != user ) { fskit_entry_unlock( fent ); return -EPERM; } fskit_entry_set_owner_and_group( fent, new_user, new_group ); fskit_entry_unlock( fent ); return err; }
// load the filesystem with metadata from under the mountpoint // return 0 on success // return -ENOMEM on OOM static int vdevfs_dev_import( struct fskit_fuse_state* fs, void* arg ) { struct vdevfs* vdev = (struct vdevfs*)arg; int rc = 0; queue<int> dirfds; // directory descriptors queue<char*> dirpaths; // relative paths to the mountpoint struct fskit_entry* dir_ent = NULL; struct vdevfs_scandirat_context scan_context; char* root = vdev_strdup_or_null("/"); if( root == NULL ) { return -ENOMEM; } int root_fd = dup( vdev->mountpoint_dirfd ); if( root_fd < 0 ) { vdev_error("dup(%d) rc = %d\n", vdev->mountpoint_dirfd, rc ); rc = -errno; free( root ); return rc; } // start at the mountpoint try { dirfds.push( root_fd ); dirpaths.push( root ); } catch( bad_alloc& ba ) { free( root ); return -ENOMEM; } while( dirfds.size() > 0 ) { // next directory int dirfd = dirfds.front(); char* dirpath = dirpaths.front(); dirfds.pop(); dirpaths.pop(); // look up this entry dir_ent = fskit_entry_resolve_path( fskit_fuse_get_core( vdev->fs ), dirpath, 0, 0, true, &rc ); if( rc != 0 ) { // shouldn't happen--we're going breadth-first vdev_error("fskit_entry_resolve_path('%s') rc = %d\n", dirpath, rc ); close( dirfd ); free( dirpath ); dirpath = NULL; break; } // make a scan context vdevfs_scandirat_context_init( &scan_context, fskit_fuse_get_core( vdev->fs ), dir_ent, dirpath, &dirfds, &dirpaths ); // scan this directory rc = vdev_load_all_at( dirfd, vdevfs_scandirat_context_callback, &scan_context ); fskit_entry_unlock( dir_ent ); if( rc != 0 ) { // failed vdev_error("vdev_load_all_at(%d, '%s') rc = %d\n", dirfd, dirpath, rc ); } close( dirfd ); free( dirpath ); dirpath = NULL; if( rc != 0 ) { break; } } // free any remaining directory state size_t num_dirfds = dirfds.size(); for( unsigned int i = 0; i < num_dirfds; i++ ) { int next_dirfd = dirfds.front(); dirfds.pop(); close( next_dirfd ); } size_t num_dirpaths = dirpaths.size(); for( unsigned int i = 0; i < num_dirpaths; i++ ) { char* path = dirpaths.front(); dirpaths.pop(); free( path ); } return rc; }
// create/open a file, with the given flags and (if creating) mode // on success, return a file handle to the created/opened file. // on failure, return NULL and set *err to the appropriate errno struct fskit_file_handle* fskit_open_ex( struct fskit_core* core, char const* _path, uint64_t user, uint64_t group, int flags, mode_t mode, void* cls, int* err ) { if( fskit_check_flags( flags ) != 0 ) { *err = -EINVAL; return NULL; } int rc = 0; char* path = strdup(_path); if( path == NULL ) { *err = -ENOMEM; return NULL; } fskit_sanitize_path( path ); size_t basename_len = fskit_basename_len( path ); if( basename_len > FSKIT_FILESYSTEM_NAMEMAX ) { free( path ); *err = -ENAMETOOLONG; return NULL; } void* handle_data = NULL; // resolve the parent of this child (and write-lock it) char* path_dirname = fskit_dirname( path, NULL ); char path_basename[FSKIT_FILESYSTEM_NAMEMAX + 1]; memset( path_basename, 0, FSKIT_FILESYSTEM_NAMEMAX + 1 ); fskit_basename( path, path_basename ); struct fskit_file_handle* ret = NULL; // write-lock parent--we need to ensure that the child does not disappear on us between attaching it and routing the user-given callback struct fskit_entry* parent = fskit_entry_resolve_path( core, path_dirname, user, group, true, err ); if( parent == NULL ) { fskit_safe_free( path_dirname ); fskit_safe_free( path ); // err is set appropriately return NULL; } fskit_safe_free( path_dirname ); rc = fskit_do_parent_check( parent, flags, user, group ); if( rc != 0 ) { // can't perform this operation fskit_entry_unlock( parent ); fskit_safe_free( path ); *err = rc; return NULL; } // resolve the child (which may be in the process of being deleted) struct fskit_entry* child = fskit_entry_set_find_name( parent->children, path_basename ); bool created = false; if( flags & O_CREAT ) { if( child != NULL ) { fskit_entry_wlock( child ); // it might have been marked for garbage-collection rc = fskit_entry_try_garbage_collect( core, path, parent, child ); if( rc >= 0 ) { if( rc == 0 ) { // not destroyed, but no longer attached fskit_entry_unlock( child ); } // safe to create child = NULL; } else { // can't garbage-collect--child still exists fskit_entry_unlock( parent ); fskit_entry_unlock( child ); fskit_safe_free( path ); if( rc == -EEXIST ) { *err = -EEXIST; return NULL; } else { // shouldn't happen fskit_error("BUG: fskit_entry_try_garbage_collect(%s) rc = %d\n", path, rc ); *err = -EIO; } return NULL; } } if( child == NULL ) { // can create! // NOTE: do *not* lock child--it has to be unlocked for running user-given routes rc = fskit_do_create( core, parent, path, mode, user, group, cls, &child, &handle_data ); if( rc != 0 ) { fskit_entry_unlock( parent ); fskit_safe_free( path ); *err = rc; return NULL; } // created! created = true; } } else if( child == NULL ) { // not found fskit_entry_unlock( parent ); fskit_safe_free( path ); *err = -ENOENT; return NULL; } // now child exists. // don't lock it, though, since we have to pass it to truncate or open // do we have to truncate? if( (flags & O_TRUNC) && (flags & (O_RDWR | O_WRONLY)) ) { // run user truncate // NOTE: do *not* lock it--it has to be unlocked for running user-given routes rc = fskit_run_user_trunc( core, path, child, 0, NULL ); if( rc != 0 ) { // truncate failed fskit_entry_unlock( parent ); fskit_safe_free( path ); *err = rc; return NULL; } } if( !created ) { // do the open // NOTE: do *not* lock it--it has to be unlocked for running user-given routes rc = fskit_do_open( core, path, parent, child, flags, user, group, &handle_data ); if( rc != 0 ) { // open failed fskit_entry_unlock( parent ); fskit_safe_free( path ); *err = rc; return NULL; } } // done with parent fskit_entry_unlock( parent ); // still here--we can open the file now! fskit_entry_set_atime( child, NULL ); ret = fskit_file_handle_create( core, child, path, flags, handle_data ); fskit_safe_free( path ); if( ret == NULL ) { // only possible if we're out of memory! *err = -ENOMEM; } return ret; }
// open a directory. // On success, return an fskit_dir_handle // Return any error code via the *err argument (which will be non-zero if there was an error). // on error, return NULL, and set *err appropriately: // * -ENAMETOOLONG if _path is too long // * -EACCES if some part of _path is in accessible to the given user and group // * -ENOTDIR if the entry referred to by _path isn't a directory // * -ENOENT if the entry doesn't exist // * -ENOMEM on OOM struct fskit_dir_handle* fskit_opendir( struct fskit_core* core, char const* _path, uint64_t user, uint64_t group, int* err ) { void* app_handle_data = NULL; int rc = 0; struct fskit_entry* dir = NULL; struct fskit_dir_handle* dirh = NULL; char path[PATH_MAX]; if( strlen(_path) >= PATH_MAX ) { // too long *err = -ENAMETOOLONG; return NULL; } // ensure path ends in / memset( path, 0, PATH_MAX ); strncpy( path, _path, PATH_MAX - 1 ); fskit_sanitize_path( path ); dir = fskit_entry_resolve_path( core, path, user, group, true, err ); if( dir == NULL ) { // resolution error; err is set appropriately return NULL; } // make sure it's a directory if( dir->type != FSKIT_ENTRY_TYPE_DIR ) { *err = -ENOTDIR; fskit_entry_unlock( dir ); return NULL; } // reference it--it cannot be unlinked fskit_entry_ref_entry( dir ); fskit_entry_unlock( dir ); // generate handle data rc = fskit_run_user_open( core, path, dir, 0, &app_handle_data ); fskit_entry_wlock( dir ); if( rc != 0 ) { // user-run open code failed fskit_error("fskit_run_user_open(%s) rc = %d\n", path, rc ); fskit_entry_unlock( dir ); *err = rc; fskit_entry_unref( core, path, dir ); return NULL; } // make a handle to it dirh = fskit_dir_handle_create( dir, path, app_handle_data ); if( dirh == NULL ) { // OOM fskit_entry_unlock( dir ); fskit_entry_unref( core, path, dir ); *err = -ENOMEM; } else { // release the directory fskit_entry_unlock( dir ); } return dirh; }