// 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; }
// do a file open // child must *not* be locked. // parent must be write-locked, but it will be unlocked and re-locked. The child will be referenced during that time, so the parent is guaranteed not to disappear // on success, fill in the handle data int fskit_do_open( struct fskit_core* core, char const* path, struct fskit_entry* parent, struct fskit_entry* child, int flags, uint64_t user, uint64_t group, void** handle_data ) { int rc = 0; // block other processes so we can do access checks fskit_entry_wlock( child ); // sanity check if( child->link_count == 0 || child->deletion_in_progress || child->type == FSKIT_ENTRY_TYPE_DEAD ) { rc = -ENOENT; } // sanity check else if( child->type == FSKIT_ENTRY_TYPE_DIR ) { rc = -EISDIR; } // access control for normal open // check read/write status of flags, and bail on error else if( (!(flags & O_RDWR) && !(flags & O_WRONLY)) && !FSKIT_ENTRY_IS_READABLE(child->mode, child->owner, child->group, user, group) ) { rc = -EACCES; // not readable } else if( (flags & O_WRONLY) && !FSKIT_ENTRY_IS_WRITEABLE(child->mode, child->owner, child->group, user, group) ) { rc = -EACCES; // not writable } else if( (flags & O_RDWR) && (!FSKIT_ENTRY_IS_READABLE(child->mode, child->owner, child->group, user, group) || !FSKIT_ENTRY_IS_WRITEABLE(child->mode, child->owner, child->group, user, group)) ) { rc = -EACCES; // not readable or not writable } if( rc == 0 ) { // reference the child fskit_entry_ref_entry( child ); } fskit_entry_unlock( child ); if( rc != 0 ) { // can't open return rc; } // safe to allow access to the child while the user route is running, since the child (and thus the parent) can't get unlinked fskit_entry_unlock( parent ); // open will succeed according to fskit. invoke the user callback to generate handle data rc = fskit_run_user_open( core, path, child, flags, handle_data ); // reaquire... fskit_entry_wlock( parent ); if( rc != 0 ) { fskit_error("fskit_run_user_open(%s) rc = %d\n", path, rc ); fskit_entry_unref( core, path, child ); } return rc; }
// 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; }
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; }
// write up to buflen bytes into buf, starting at the given offset in the file. // return the number of bytes written on success. // return negative on failure. ssize_t fskit_write( struct fskit_core* core, struct fskit_file_handle* fh, char const* buf, size_t buflen, off_t offset ) { fskit_file_handle_rlock( fh ); // sanity check if( (fh->flags & (O_RDWR | O_WRONLY)) == 0 ) { fskit_file_handle_unlock( fh ); return -EBADF; } ssize_t num_written = fskit_run_user_write( core, fh->path, fh->fent, buf, buflen, offset, fh->app_data ); if( num_written >= 0 ) { // update metadata fskit_entry_wlock( fh->fent ); fskit_entry_set_mtime( fh->fent, NULL ); fskit_entry_set_atime( fh->fent, NULL ); fh->fent->size = ((unsigned)(offset + buflen) > fh->fent->size ? offset + buflen : fh->fent->size); fskit_entry_unlock( fh->fent ); } fskit_file_handle_unlock( fh ); return num_written; }
// 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; }
// stat an inode directly // fill in the stat buffer, and call the user route // NOTE: fent must NOT be locked, but it must be ref'ed int fskit_fstat( struct fskit_core* core, char const* fs_path, struct fskit_entry* fent, struct stat* sb ) { // fill in defaults fskit_entry_rlock( fent ); fskit_entry_fstat( fent, sb ); fskit_entry_unlock( fent ); // route to user callback int rc = fskit_do_user_stat( core, fs_path, fent, sb ); 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; }
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; }
// stat a file's block--build a manifest request, and set its mode // return 0 on success // return -ESTALE if the inode is not local // return -ENOENT if we don't have it // return -ENOMEM on OOM // return -errno on error static int UG_impl_stat_block( struct SG_gateway* gateway, struct SG_request_data* reqdat, struct SG_request_data* entity_info, mode_t* mode, void* cls ) { int rc = 0; struct UG_state* ug = (struct UG_state*)SG_gateway_cls( gateway ); int64_t block_version = 0; UG_handle_t* fi = NULL; struct fskit_entry* fent = NULL; struct UG_inode* inode = NULL; uint64_t file_id = 0; int64_t file_version = 0; int close_rc = 0; fi = UG_open( ug, reqdat->fs_path, O_RDONLY, &rc ); if( fi == NULL ) { SG_error("UG_open('%s') rc = %d\n", reqdat->fs_path, rc ); return rc; } fskit_file_handle_rlock( fi->fh ); fent = fskit_file_handle_get_entry( fi->fh ); if( fent == NULL ) { SG_error("BUG: no entry for handle %p\n", fi->fh ); exit(1); } fskit_entry_rlock( fent ); inode = (struct UG_inode*)fskit_entry_get_user_data( fent ); if( inode == NULL ) { SG_error("BUG: no inode for entry %p\n", fent ); exit(1); } if( UG_inode_coordinator_id( inode ) != SG_gateway_id( gateway ) ) { // not ours SG_error("Not the coordinator of '%s' (it is now %" PRIu64 ")\n", reqdat->fs_path, UG_inode_coordinator_id( inode ) ); fskit_entry_unlock( fent ); fskit_file_handle_unlock( fi->fh ); rc = UG_close( ug, fi ); if( rc != 0 ) { SG_error("UG_close('%s') rc = %d\n", reqdat->fs_path, rc ); } return rc; } file_id = UG_inode_file_id( inode ); file_version = UG_inode_file_version( inode ); if( mode != NULL ) { *mode = fskit_entry_get_mode( fent ); } if( entity_info != NULL ) { rc = UG_getblockinfo( ug, reqdat->block_id, &block_version, NULL, fi ); } fskit_entry_unlock( fent ); fskit_file_handle_unlock( fi->fh ); inode = NULL; if( rc != 0 ) { SG_error("UG_getblockinfo(%s[%" PRIu64 "]) rc = %d\n", reqdat->fs_path, reqdat->block_id, rc); goto UG_impl_stat_block_out; } rc = SG_request_data_init_block( gateway, reqdat->fs_path, file_id, file_version, reqdat->block_id, block_version, entity_info ); if( rc != 0 ) { SG_error("SG_request_data_init_block rc = %d\n", rc ); goto UG_impl_stat_block_out; } UG_impl_stat_block_out: close_rc = UG_close( ug, fi ); if( close_rc != 0 ) { SG_error("UG_close('%s') rc = %d\n", reqdat->fs_path, close_rc ); } return rc; }
// close a directory handle, freeing it. // a directory may be unlinked on close, if this was the last handle to it, and its link count was zero. // if this happens, then app_dir_data will contain the directory's app data, and the directory will be freed. // return 0 on success // return the following errors: // * EBADF if the directory handle is invalid // * EDEADLK if there is a bug in the lock handling int fskit_closedir( struct fskit_core* core, struct fskit_dir_handle* dirh ) { int rc = 0; rc = fskit_dir_handle_wlock( dirh ); if( rc != 0 ) { // indicates deadlock; shouldn't happen fskit_error("BUG: fskit_dir_handle_wlock(%p) rc = %d\n", dirh, rc ); return rc; } if( dirh->dent == NULL ) { fskit_dir_handle_unlock( dirh ); return -EBADF; } // run user-given close route. Note that this may unlock dirh->dent and re-lock it, but only if it is fully unlinked. rc = fskit_run_user_close( core, dirh->path, dirh->dent, dirh->app_data ); if( rc != 0 ) { fskit_error("fskit_run_user_close(%s) rc = %d\n", dirh->path, rc ); fskit_dir_handle_unlock( dirh ); return rc; } rc = fskit_entry_wlock( dirh->dent ); if( rc != 0 ) { // shouldn't happen; indicates deadlock fskit_error("BUG: fskit_entry_wlock(%p) rc = %d\n", dirh->dent, rc ); fskit_dir_handle_unlock( dirh ); return rc; } // no longer open dirh->dent->open_count--; // see if we can destroy this.... // NOTE: this may unlock and free dirh->dent rc = fskit_entry_try_destroy_and_free( core, dirh->path, dirh->dent ); if( rc > 0 ) { // dent was unlocked and destroyed dirh->dent = NULL; rc = 0; } if( rc < 0 ) { // some error occurred fskit_error("fskit_entry_try_destroy(%p) rc = %d\n", dirh->dent, rc ); fskit_entry_unlock( dirh->dent ); return rc; } else { // not destroyed. // done with this directory fskit_entry_unlock( dirh->dent ); } // get rid of this handle fskit_dir_handle_unlock( dirh ); fskit_dir_handle_destroy( dirh ); return 0; }
// 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; }
// readdir: equivocate about which devices exist, depending on who's asking // omit entries if the ACLs forbid them int vdevfs_readdir( struct fskit_core* core, struct fskit_match_group* grp, struct fskit_entry* fent, struct fskit_dir_entry** dirents, size_t num_dirents ) { int rc = 0; struct fskit_entry* child = NULL; // entries to omit in the listing vector<int> omitted_idx; pid_t pid = 0; uid_t uid = 0; gid_t gid = 0; struct vdevfs* vdev = (struct vdevfs*)fskit_core_get_user_data( core ); struct fskit_fuse_state* fs_state = fskit_fuse_get_state(); struct stat sb; struct pstat ps; char* child_path = NULL; pid = fskit_fuse_get_pid(); uid = fskit_fuse_get_uid( fs_state ); gid = fskit_fuse_get_gid( fs_state ); vdev_debug("vdevfs_readdir(%s, %zu) from user %d group %d task %d\n", grp->path, num_dirents, uid, gid, pid ); // see who's asking rc = pstat( pid, &ps, 0 ); if( rc != 0 ) { vdev_error("pstat(%d) rc = %d\n", pid, rc ); return -EIO; } for( unsigned int i = 0; i < num_dirents; i++ ) { // skip . and .. if( strcmp(dirents[i]->name, ".") == 0 || strcmp(dirents[i]->name, "..") == 0 ) { continue; } // find the associated fskit_entry child = fskit_dir_find_by_name( fent, dirents[i]->name ); if( child == NULL ) { // strange, shouldn't happen... continue; } fskit_entry_rlock( child ); // construct a stat buffer from what we actually need memset( &sb, 0, sizeof(struct stat) ); sb.st_uid = child->owner; sb.st_gid = child->group; sb.st_mode = fskit_fullmode( child->type, child->mode ); child_path = fskit_fullpath( grp->path, child->name, NULL ); if( child_path == NULL ) { // can't continue; OOM fskit_entry_unlock( child ); rc = -ENOMEM; break; } // filter it rc = vdev_acl_apply_all( vdev->config, vdev->acls, vdev->num_acls, child_path, &ps, uid, gid, &sb ); if( rc < 0 ) { vdev_error("vdev_acl_apply_all('%s', uid=%d, gid=%d, pid=%d) rc = %d\n", child_path, uid, gid, pid, rc ); rc = -EIO; } else if( rc == 0 || (sb.st_mode & 0777) == 0 ) { // omit this one vdev_debug("Filter '%s'\n", child->name ); omitted_idx.push_back( i ); rc = 0; } else { // success; matched rc = 0; } fskit_entry_unlock( child ); free( child_path ); // error? if( rc != 0 ) { break; } } // skip ACL'ed entries for( unsigned int i = 0; i < omitted_idx.size(); i++ ) { fskit_readdir_omit( dirents, omitted_idx[i] ); } 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; }
// low-level mkdir: create a child and insert it into the parent. // parent must be a directory // parent must be write-locked // return -EEXIST if an entry with the given name (path_basename) already exists in parent. // return -EIO if we couldn't allocate an inode // return -ENOMEM if we couldn't allocate memory static int fskit_mkdir_lowlevel( struct fskit_core* core, char const* path, struct fskit_entry* parent, char const* path_basename, mode_t mode, uint64_t user, uint64_t group, void* cls ) { // resolve the child within the parent struct fskit_entry* child = fskit_entry_set_find_name( parent->children, path_basename ); int err = 0; void* app_dir_data = NULL; if( child != NULL ) { fskit_entry_wlock( child ); err = fskit_entry_try_garbage_collect( core, path, parent, child ); if( err >= 0 ) { if( err == 0 ) { // detached but not destroyed fskit_entry_unlock( child ); } // name is free child = NULL; } else { fskit_entry_unlock( child ); if( err == -EEXIST ) { // still exists; can't be garbage-collected return -EEXIST; } else { // shouldn't happen fskit_error("BUG: fskit_entry_try_garbage_collect(%s) rc = %d\n", path, err ); return -EIO; } } } if( child == NULL ) { // create an fskit_entry and attach it child = CALLOC_LIST( struct fskit_entry, 1 ); if( child == NULL ) { return -ENOMEM; } // get an inode for this child uint64_t child_inode = fskit_core_inode_alloc( core, parent, child ); if( child_inode == 0 ) { // error in allocation fskit_error("fskit_core_inode_alloc(%s) failed\n", path ); fskit_safe_free( child ); return -EIO; } // set up the directory err = fskit_entry_init_dir( child, parent, child_inode, user, group, mode ); if( err != 0 ) { fskit_error("fskit_entry_init_dir(%s) rc = %d\n", path, err ); fskit_safe_free( child ); return err; } // reference this directory, so it won't disappear during the user's route child->open_count++; // almost done. run the route callback for this path if needed err = fskit_run_user_mkdir( core, path, parent, child, mode, cls, &app_dir_data ); child->open_count--; if( err != 0 ) { // user route failed fskit_error("fskit_run_user_mkdir(%s) rc = %d\n", path, err ); fskit_entry_destroy( core, child, false ); fskit_safe_free( child ); } else { fskit_entry_set_user_data( child, app_dir_data ); // attach to parent fskit_entry_attach_lowlevel( parent, child, path_basename ); } }