// read: read as usual, but from the underlying filesystem // NOTE: since this is backed by FUSE, this handler will only be called for regular files int vdevfs_read( struct fskit_core* core, struct fskit_match_group* grp, struct fskit_entry* fent, char* buf, size_t len, off_t offset, void* handle_cls ) { // careful... int fd = 0; memcpy( &fd, &handle_cls, 4 ); int rc = 0; struct vdevfs* vdev = (struct vdevfs*)fskit_core_get_user_data( core ); rc = lseek( fd, offset, SEEK_SET ); if( rc < 0 ) { rc = -errno; vdev_error("lseek(%d '%s') rc = %d\n", fd, grp->path, rc ); return rc; } rc = read( fd, buf, len ); if( rc < 0 ) { rc = -errno; vdev_error("read(%d '%s') rc = %d\n", fd, grp->path, rc ); return rc; } return rc; }
/* * Check that a file is valid. All we can do in this case is check that it's * not in use by another pool. */ int check_file(const char *file, boolean_t force, boolean_t isspare) { char *name; int fd; int ret = 0; pool_state_t state; boolean_t inuse; if ((fd = open(file, O_RDONLY)) < 0) return (0); if (zpool_in_use(g_zfs, fd, &state, &name, &inuse) == 0 && inuse) { const char *desc; switch (state) { case POOL_STATE_ACTIVE: desc = gettext("active"); break; case POOL_STATE_EXPORTED: desc = gettext("exported"); break; case POOL_STATE_POTENTIALLY_ACTIVE: desc = gettext("potentially active"); break; default: desc = gettext("unknown"); break; } /* * Allow hot spares to be shared between pools. */ if (state == POOL_STATE_SPARE && isspare) return (0); if (state == POOL_STATE_ACTIVE || state == POOL_STATE_SPARE || !force) { switch (state) { case POOL_STATE_SPARE: vdev_error(gettext("%s is reserved as a hot " "spare for pool %s\n"), file, name); break; default: vdev_error(gettext("%s is part of %s pool " "'%s'\n"), file, desc, name); break; } ret = -1; } free(name); } (void) close(fd); return (ret); }
// read the device major and minor number, using the devpath // return 0 on success, and set *major and *minor // return -ENOMEM on OOM // return -errno on failure to open or read static int vdev_linux_sysfs_read_dev_nums( struct vdev_linux_context* ctx, char const* devpath, unsigned int* major, unsigned int* minor ) { int rc = 0; int fd = 0; ssize_t nr = 0; char devbuf[101]; memset( devbuf, 0, 101 ); char* full_devpath = vdev_linux_sysfs_fullpath( ctx->sysfs_mountpoint, devpath, "dev" ); if( full_devpath == NULL ) { return -ENOMEM; } // open device path fd = open( full_devpath, O_RDONLY ); if( fd < 0 ) { rc = -errno; if( rc != -ENOENT ) { vdev_error("open('%s') rc = %d\n", full_devpath, rc ); } free( full_devpath ); return rc; } nr = vdev_read_uninterrupted( fd, devbuf, 100 ); if( nr < 0 ) { rc = nr; vdev_error("read('%s') rc = %d\n", full_devpath, rc ); free( full_devpath ); close( fd ); return rc; } close( fd ); free( full_devpath ); rc = vdev_linux_sysfs_parse_device_nums( devbuf, major, minor ); if( rc != 0 ) { vdev_error("Failed to parse '%s'\n", devbuf ); rc = -EIO; } return rc; }
// for creating, opening, or stating files, verify that the caller is permitted according to our ACLs // return 0 on success // return -EPERM if denied // return other -errno on error static int vdevfs_access_check( struct vdevfs* vdev, struct fskit_fuse_state* fs_state, char const* method_name, char const* path ) { int rc = 0; pid_t pid = 0; uid_t uid = 0; gid_t gid = 0; struct stat sb; struct pstat ps; memset( &sb, 0, sizeof(struct stat) ); sb.st_mode = 0777; memset( &ps, 0, sizeof(struct pstat) ); // stat the calling process pid = fskit_fuse_get_pid(); uid = fskit_fuse_get_uid( fs_state ); gid = fskit_fuse_get_gid( fs_state ); vdev_debug("%s('%s') from user %d group %d task %d\n", method_name, path, 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; } // apply the ACLs on the stat buffer rc = vdev_acl_apply_all( vdev->config, vdev->acls, vdev->num_acls, path, &ps, uid, gid, &sb ); if( rc < 0 ) { vdev_error("vdev_acl_apply_all(%s, uid=%d, gid=%d, pid=%d) rc = %d\n", path, uid, gid, pid, rc ); return -EIO; } // omit entirely? if( rc == 0 || (sb.st_mode & 0777) == 0 ) { // filter vdev_debug("DENY '%s'\n", path ); return -EPERM; } else { // accept! return 0; } }
// parse a regex and append it to a list of regexes and strings // return 0 on success // return -EINVAL if the regex is invalid // return -ENOMEM if we're out of memory int vdev_match_regex_append( char*** strings, regex_t** regexes, size_t* len, char const* next ) { // verify that this is a valid regex regex_t reg; int rc = 0; memset( ®, 0, sizeof(regex_t) ); rc = regcomp( ®, next, REG_EXTENDED | REG_NEWLINE | REG_NOSUB ); if( rc != 0 ) { vdev_error("regcomp(%s) rc = %d\n", next, rc ); return -EINVAL; } char** new_strings = (char**)realloc( *strings, sizeof(char**) * (*len + 2) ); regex_t* new_regexes = (regex_t*)realloc( *regexes, sizeof(regex_t) * (*len + 1) ); if( new_strings == NULL || new_regexes == NULL ) { return -ENOMEM; } new_strings[*len] = vdev_strdup_or_null( next ); new_strings[*len + 1] = NULL; new_regexes[*len] = reg; *strings = new_strings; *regexes = new_regexes; *len = *len + 1; return 0; }
// close: close as usual // NOTE: since this is backed by FUSE, this handler will only be called for regular files int vdevfs_close( struct fskit_core* core, struct fskit_match_group* grp, struct fskit_entry* fent, void* handle_cls ) { // only care about close() for files if( fskit_entry_get_type( fent ) == FSKIT_ENTRY_TYPE_DIR ) { // done! return 0; } // careful... int fd = 0; memcpy( &fd, &handle_cls, 4 ); int rc = 0; rc = close( fd ); if( rc < 0 ) { rc = -errno; vdev_error("close(%d '%s') rc = %d\n", fd, grp->path, rc ); return rc; } return rc; }
// unlink/rmdir: remove the file or device node from the underlying filesystem int vdevfs_detach( struct fskit_core* core, struct fskit_match_group* grp, struct fskit_entry* fent, void* inode_cls ) { int rc = 0; struct vdevfs* vdev = (struct vdevfs*)fskit_core_get_user_data( core ); struct fskit_fuse_state* fs_state = fskit_fuse_get_state(); char const* method = NULL; if( fskit_entry_get_type( fent ) == FSKIT_ENTRY_TYPE_DIR ) { method = "rmdir"; } else { method = "unlink"; } rc = vdevfs_access_check( vdev, fs_state, method, grp->path ); if( rc < 0 ) { // denied! return -ENOENT; } if( rc != 0 ) { rc = -errno; vdev_error("%s('%s', '%s') rc = %d\n", method, vdev->mountpoint, grp->path, rc ); return rc; } return 0; }
// read the kernel-given device subsystem from sysfs // return 0 on success, and set *subsystem // return -ENOMEM on OOM // return negative on readlink failure static int vdev_linux_sysfs_read_subsystem( struct vdev_linux_context* ctx, char const* devpath, char** subsystem ) { int rc = 0; char linkpath[PATH_MAX+1]; size_t linkpath_len = PATH_MAX; char* subsystem_path = NULL; memset( linkpath, 0, PATH_MAX+1 ); subsystem_path = vdev_linux_sysfs_fullpath( ctx->sysfs_mountpoint, devpath, "subsystem" ); if( subsystem_path == NULL ) { return -ENOMEM; } rc = readlink( subsystem_path, linkpath, linkpath_len ); if( rc < 0 ) { rc = -errno; vdev_error("readlink('%s') rc = %d\n", subsystem_path, rc ); free( subsystem_path ); return rc; } free( subsystem_path ); *subsystem = vdev_basename( linkpath, NULL ); if( *subsystem == NULL ) { return -ENOMEM; } return 0; }
/* * Validate a device, passing the bulk of the work off to libdiskmgt. */ int check_slice(const char *path, int force, boolean_t wholedisk, boolean_t isspare) { char *msg; int error = 0; int ret = 0; if (dm_inuse((char *)path, &msg, isspare ? DM_WHO_ZPOOL_SPARE : (force ? DM_WHO_ZPOOL_FORCE : DM_WHO_ZPOOL), &error) || error) { if (error != 0) { libdiskmgt_error(error); return (0); } else { vdev_error("%s", msg); free(msg); ret = -1; } } /* * If we're given a whole disk, ignore overlapping slices since we're * about to label it anyway. */ error = 0; if (!wholedisk && !force && (dm_isoverlapping((char *)path, &msg, &error) || error)) { if (error != 0) { libdiskmgt_error(error); return (0); } else { vdev_error("%s overlaps with %s\n", path, msg); free(msg); } ret = -1; } return (ret); }
// find sysfs mountpoint in /proc/mounts // this is apparently superfluous (it *should* be mounted at /sys), but you never know. // mountpoint must be big enough (PATH_MAX will do) // return 0 on success // return -ENOSYS if sysfs is not mounted // return -ENOMEM if the buffer isn't big enough // return -EINVAL if somehow we failed to parse a mount entry // return negative for some other errors (like access permission failures, or /proc not mounted) static int vdev_linux_find_sysfs_mountpoint( char* mountpoint, size_t mountpoint_len ) { FILE* f = NULL; int rc = 0; char mntbuf[4096]; struct mntent ment_buf; struct mntent* ment_ptr = NULL; int ent_count = 1; bool found = false; f = fopen( "/proc/mounts", "r" ); if( f == NULL ) { rc = -errno; fprintf(stderr, "Failed to open /proc/mounts, rc = %d\n", rc ); return rc; } // scan for sysfs mount type while( 1 ) { ment_ptr = getmntent_r( f, &ment_buf, mntbuf, 4096 ); if( ment_ptr == NULL ) { vdev_error("Failed on processing entry #%d of /proc/mounts\n", ent_count ); rc = -EINVAL; break; } if( strcmp( ment_ptr->mnt_type, "sysfs" ) == 0 ) { // found! strncpy( mountpoint, ment_ptr->mnt_dir, mountpoint_len - 1 ); found = true; rc = 0; break; } ent_count++; } fclose( f ); if( rc == 0 && !found ) { fprintf(stderr, "Failed to find mounted sysfs filesystem\n"); return -ENOSYS; } return rc; }
// print a uevent, either with debugging or error loglevels static int vdev_linux_log_uevent( char const* uevent_buf, size_t uevent_buf_len, bool debug ) { for( unsigned int i = 0; i < uevent_buf_len; ) { if( debug ) { vdev_debug("uevent '%s'\n", uevent_buf + i ); } else { vdev_error("uevent '%s'\n", uevent_buf + i ); } i += strlen(uevent_buf + i) + 1; } return 0; }
// parse a device number pair // return 0 on success, and set *major and *minor // return -EINVAL if we failed to parse static int vdev_linux_sysfs_parse_device_nums( char const* devbuf, unsigned int* major, unsigned int* minor ) { int rc = 0; // parse devpath rc = sscanf( devbuf, "%u:%u", major, minor ); if( rc != 2 ) { vdev_error("sscanf('%s') for major:minor rc = %d\n", devbuf, rc ); rc = -EINVAL; } else { rc = 0; } return rc; }
// does a path match any regexes in a list? // return the index of the match if so (>= 0) // return the size if not // return negative on error int vdev_match_first_regex( char const* path, regex_t* regexes, size_t num_regexes ) { int matched = 0; for( unsigned int i = 0; i < num_regexes; i++ ) { matched = vdev_match_regex( path, ®exes[i] ); if( matched > 0 ) { return i; } else if( matched < 0 ) { vdev_error("vdev_acl_regex_match(%s) rc = %d\n", path, matched ); return matched; } } return num_regexes; }
// creat: create the file as usual, but also write to the underlying filesystem as an emergency counter-measure // NOTE: since this is backed by FUSE, this handler will only be called for regular files int vdevfs_create( struct fskit_core* core, struct fskit_match_group* grp, struct fskit_entry* fent, mode_t mode, void** inode_cls, void** handle_cls ) { int fd = 0; struct vdevfs* vdev = (struct vdevfs*)fskit_core_get_user_data( core ); struct fskit_fuse_state* fs_state = fskit_fuse_get_state(); char const* path = NULL; int rc = 0; rc = vdevfs_access_check( vdev, fs_state, "create", grp->path ); if( rc < 0 ) { // denied! return -EACCES; } // must be relative path path = grp->path; while( *path == '/' && *path != '\0' ) { path++; } if( *path == '\0' ) { path = "."; } // success! fd = openat( vdev->mountpoint_dirfd, path, O_CREAT | O_WRONLY | O_TRUNC, mode ); if( fd < 0 ) { fd = -errno; vdev_error("openat('%s', '%s') rc = %d\n", vdev->mountpoint, path, fd ); return fd; } // careful... void* handle_data = NULL; memcpy( &handle_data, &fd, 4 ); *handle_cls = handle_data; return 0; }
// does a path match a regex? // return 1 if so, 0 if not, negative on error int vdev_match_regex( char const* path, regex_t* regex ) { int rc = 0; rc = regexec( regex, path, 0, NULL, 0 ); if( rc != 0 ) { if( rc == REG_NOMATCH ) { // no match return 0; } else { vdev_error("regexec(%s) rc = %d\n", path, rc ); return -abs(rc); } } // match! return 1; }
// sync: sync as usual // NOTE: since this is backed by FUSE, this handler will only be called for regular files int vdevfs_sync( struct fskit_core* core, struct fskit_match_group* grp, struct fskit_entry* fent ) { void* user_data = fskit_entry_get_user_data( fent ); // careful... int fd = 0; memcpy( &fd, &user_data, 4 ); int rc = 0; rc = fsync( fd ); if( rc < 0 ) { rc = -errno; vdev_error("fsync(%d '%s') rc = %d\n", fd, grp->path, rc ); return rc; } return rc; }
// set up a vdev os context int vdev_os_context_init( struct vdev_os_context* vos, struct vdev_state* state ) { int rc = 0; memset( vos, 0, sizeof(struct vdev_os_context) ); vos->state = state; // set up OS state rc = vdev_os_init( vos, &vos->os_cls ); if( rc != 0 ) { vdev_error("vdev_os_init rc = %d\n", rc ); memset( vos, 0, sizeof(struct vdev_os_context) ); return rc; } vos->running = true; return 0; }
// run! int main( int argc, char** argv ) { int rc = 0; pid_t pid = 0; struct vdevfs vdev; memset( &vdev, 0, sizeof(struct vdevfs) ); // set up global vdev state rc = vdevfs_init( &vdev, argc, argv ); if( rc != 0 ) { vdev_error("vdevfs_init rc = %d\n", rc ); exit(1); } // run! rc = vdevfs_main( &vdev, vdev.fuse_argc, vdev.fuse_argv ); vdevfs_shutdown( &vdev ); return rc; }
// mkdir: create the directory as normal, but also write to the underlying filesystem as an emergency counter-measure int vdevfs_mkdir( struct fskit_core* core, struct fskit_match_group* grp, struct fskit_entry* fent, mode_t mode, void** inode_cls ) { int rc = 0; struct vdevfs* vdev = (struct vdevfs*)fskit_core_get_user_data( core ); struct fskit_fuse_state* fs_state = fskit_fuse_get_state(); char const* path = NULL; rc = vdevfs_access_check( vdev, fs_state, "mkdir", grp->path ); if( rc < 0 ) { // denied! return -EACCES; } // must be relative path path = grp->path; while( *path == '/' && *path != '\0' ) { path++; } if( *path == '\0' ) { path = "."; } rc = mkdirat( vdev->mountpoint_dirfd, path, mode ); if( rc != 0 ) { rc = -errno; vdev_error("mkdirat('%s', '%s') rc = %d\n", vdev->mountpoint, path, rc ); return rc; } return 0; }
// get the mountpoint option, by parsing the FUSE command line static int vdev_get_mountpoint( int fuse_argc, char** fuse_argv, char** ret_mountpoint ) { struct fuse_args fargs = FUSE_ARGS_INIT(fuse_argc, fuse_argv); char* mountpoint = NULL; int unused_1; int unused_2; int rc = 0; // parse command-line... rc = fuse_parse_cmdline( &fargs, &mountpoint, &unused_1, &unused_2 ); if( rc < 0 ) { vdev_error("fuse_parse_cmdline rc = %d\n", rc ); fuse_opt_free_args(&fargs); return rc; } else { if( mountpoint != NULL ) { *ret_mountpoint = strdup( mountpoint ); free( mountpoint ); rc = 0; } else { rc = -ENOMEM; } fuse_opt_free_args(&fargs); } return 0; }
// callback to be fed int vdev_load_all_at. // builds up the children listing of vdevfs_scandirat_context.parent_dir. // if we find a directory, open it and enqueue its file descriptor to dir_queue. // return 0 on success // return -ENOMEM on OOM static int vdevfs_scandirat_context_callback( int dirfd, struct dirent* dent, void* cls ) { struct vdevfs_scandirat_context* ctx = (struct vdevfs_scandirat_context*)cls; int rc = 0; int fd = 0; struct stat sb; struct fskit_entry* child; char linkbuf[8193]; // for resolving an underlying symlink char const* method_name; // for logging char* joined_path = NULL; // skip . and .. if( strcmp( dent->d_name, "." ) == 0 || strcmp( dent->d_name, ".." ) == 0 ) { return 0; } // learn more... rc = fstatat( dirfd, dent->d_name, &sb, AT_SYMLINK_NOFOLLOW ); if( rc != 0 ) { rc = -errno; // mask errors; just log the serious ones if( rc != -ENOENT && rc != -EACCES ) { vdev_error("fstatat(%d, '%s') rc = %d\n", dirfd, dent->d_name, rc ); } return 0; } // directory? get an fd to it if so, so we can keep scanning if( S_ISDIR( sb.st_mode ) ) { // try to get at it fd = openat( dirfd, dent->d_name, O_RDONLY ); if( fd < 0 ) { rc = -errno; // mask errors; just log the serious ones if( rc != -ENOENT && rc != -EACCES ) { vdev_error("openat(%d, '%s') rc = %d\n", dirfd, dent->d_name, rc ); } return 0; } // woo! save it joined_path = vdev_fullpath( ctx->parent_path, dent->d_name, NULL ); if( joined_path == NULL ) { close( fd ); return -ENOMEM; } try { ctx->dir_paths->push( joined_path ); } catch( bad_alloc& ba ) { // OOM free( joined_path ); return -ENOMEM; } try { ctx->dir_queue->push( fd ); } catch( bad_alloc& ba ) { // OOM close( fd ); return -ENOMEM; } } // construct an inode for this entry child = VDEV_CALLOC( struct fskit_entry, 1 ); if( child == NULL ) { return -ENOMEM; } // regular file? if( S_ISREG( sb.st_mode ) ) { method_name = "fskit_entry_init_file"; rc = fskit_entry_init_file( child, sb.st_ino, dent->d_name, sb.st_uid, sb.st_gid, sb.st_mode & 0777 ); } // directory? else if( S_ISDIR( sb.st_mode ) ) { method_name = "fskit_entry_init_dir"; rc = fskit_entry_init_dir( child, ctx->parent_dir, sb.st_ino, dent->d_name, sb.st_uid, sb.st_gid, sb.st_mode & 0777 ); } // named pipe? else if( S_ISFIFO( sb.st_mode ) ) { method_name = "fskit_entry_init_fifo"; rc = fskit_entry_init_fifo( child, sb.st_ino, dent->d_name, sb.st_uid, sb.st_gid, sb.st_mode & 0777 ); } // unix domain socket? else if( S_ISSOCK( sb.st_mode ) ) { method_name = "fskit_entry_init_sock"; rc = fskit_entry_init_sock( child, sb.st_ino, dent->d_name, sb.st_uid, sb.st_gid, sb.st_mode & 0777 ); } // character device? else if( S_ISCHR( sb.st_mode ) ) { method_name = "fskit_entry_init_chr"; rc = fskit_entry_init_chr( child, sb.st_ino, dent->d_name, sb.st_uid, sb.st_gid, sb.st_mode, sb.st_rdev ); } // block device? else if( S_ISBLK( sb.st_mode ) ) { method_name = "fskit_entry_init_blk"; rc = fskit_entry_init_blk( child, sb.st_ino, dent->d_name, sb.st_uid, sb.st_gid, sb.st_mode, sb.st_rdev ); } // symbolic link? else if( S_ISLNK( sb.st_mode ) ) { // read the link first... memset( linkbuf, 0, 8193 ); rc = readlinkat( dirfd, dent->d_name, linkbuf, 8192 ); if( rc < 0 ) { rc = -errno; // mask error, but log serious ones. this link will not appear in the listing if( rc != -ENOENT && rc != -EACCES ) { vdev_error("readlinkat(%d, '%s') rc = %d\n", dirfd, dent->d_name, rc ); } free( child ); child = NULL; return 0; } method_name = "fskit_entry_init_symlink"; rc = fskit_entry_init_symlink( child, sb.st_ino, dent->d_name, linkbuf ); } // success? if( rc != 0 ) { vdev_error("%s( on %d, '%s' ) rc = %d\n", method_name, dirfd, dent->d_name, rc ); free( child ); child = NULL; return rc; } // insert into parent rc = fskit_entry_attach_lowlevel( ctx->parent_dir, child ); if( rc != 0 ) { // OOM fskit_entry_destroy( ctx->core, child, false ); free( child ); child = NULL; return rc; } // success! return rc; }
// initialize the filesystem front-end // call after vdev_init // return 0 on success // return -ENOMEM on OOM // return negative on error int vdevfs_init( struct vdevfs* vdev, int argc, char** argv ) { int rc = 0; int rh = 0; struct fskit_core* core = NULL; int fuse_argc = 0; char** fuse_argv = NULL; int dirfd = 0; // library setup vdev_setup_global(); struct fskit_fuse_state* fs = VDEV_CALLOC( struct fskit_fuse_state, 1 ); if( fs == NULL ) { return -ENOMEM; } fuse_argv = VDEV_CALLOC( char*, argc + 5 ); if( fuse_argv == NULL ) { free( fs ); return -ENOMEM; } // load config vdev->config = VDEV_CALLOC( struct vdev_config, 1 ); if( vdev->config == NULL ) { free( fs ); free( fuse_argv ); return -ENOMEM; } // init config rc = vdev_config_init( vdev->config ); if( rc != 0 ) { vdev_error("vdev_config_init rc = %d\n", rc ); vdevfs_shutdown( vdev ); free( fs ); free( fuse_argv ); return rc; } // parse opts rc = vdev_config_load_from_args( vdev->config, argc, argv, &fuse_argc, fuse_argv ); if( rc != 0 ) { vdev_error("vdev_opts_parse rc = %d\n", rc ); vdev_config_usage( argv[0] ); free( fs ); free( fuse_argv ); vdevfs_shutdown( vdev ); return rc; } // get the mountpoint, but from FUSE if( vdev->config->mountpoint != NULL ) { free( vdev->config->mountpoint ); } rc = vdev_get_mountpoint( fuse_argc, fuse_argv, &vdev->config->mountpoint ); if( rc != 0 ) { vdev_error("vdev_get_mountpoint rc = %d\n", rc ); vdev_config_usage( argv[0] ); free( fs ); free( fuse_argv ); return rc; } vdev_set_debug_level( vdev->config->debug_level ); vdev_set_error_level( vdev->config->error_level ); vdev_debug("Config file: %s\n", vdev->config->config_path ); rc = vdev_config_load( vdev->config->config_path, vdev->config ); if( rc != 0 ) { vdev_error("vdev_config_load('%s') rc = %d\n", vdev->config->config_path, rc ); vdevfs_shutdown( vdev ); free( fs ); free( fuse_argv ); return rc; } vdev_debug("vdev ACLs dir: %s\n", vdev->config->acls_dir ); // force -odev, since we'll create device nodes fuse_argv[fuse_argc] = (char*)vdev_fuse_odev; fuse_argc++; // force -oallow_other, since we'll want to expose this to everyone fuse_argv[fuse_argc] = (char*)vdev_fuse_allow_other; fuse_argc++; // force -ononempty, since we'll want to import the underlying filesystem fuse_argv[fuse_argc] = (char*)vdev_fuse_ononempty; fuse_argc++; vdev->mountpoint = vdev_strdup_or_null( vdev->config->mountpoint ); if( vdev->mountpoint == NULL ) { vdev_error("Failed to set mountpoint, config.mountpount = '%s'\n", vdev->config->mountpoint ); vdevfs_shutdown( vdev ); free( fuse_argv ); free( fs ); return -EINVAL; } else { vdev_debug("mountpoint: %s\n", vdev->mountpoint ); } vdev->argc = argc; vdev->argv = argv; vdev->fuse_argc = fuse_argc; vdev->fuse_argv = fuse_argv; fskit_set_debug_level( vdev->config->debug_level ); fskit_set_error_level( vdev->config->error_level ); // get mountpoint directory dirfd = open( vdev->mountpoint, O_DIRECTORY ); if( dirfd < 0 ) { rc = -errno; vdev_error("open('%s') rc = %d\n", vdev->mountpoint, rc ); free( fs ); vdevfs_shutdown( vdev ); return rc; } vdev->mountpoint_dirfd = dirfd; // set up fskit rc = fskit_fuse_init( fs, vdev ); if( rc != 0 ) { vdev_error("fskit_fuse_init rc = %d\n", rc ); free( fs ); vdevfs_shutdown( vdev ); return rc; } // load ACLs rc = vdev_acl_load_all( vdev->config->acls_dir, &vdev->acls, &vdev->num_acls ); if( rc != 0 ) { vdev_error("vdev_acl_load_all('%s') rc = %d\n", vdev->config->acls_dir, rc ); fskit_fuse_shutdown( fs, NULL ); free( fs ); vdevfs_shutdown( vdev ); return rc; } // make sure the fs can access its methods through the VFS fskit_fuse_setting_enable( fs, FSKIT_FUSE_SET_FS_ACCESS ); core = fskit_fuse_get_core( fs ); // add handlers. rh = fskit_route_readdir( core, FSKIT_ROUTE_ANY, vdevfs_readdir, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_readdir(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_stat( core, FSKIT_ROUTE_ANY, vdevfs_stat, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_stat(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_mknod( core, FSKIT_ROUTE_ANY, vdevfs_mknod, FSKIT_CONCURRENT ); if( rc < 0 ) { vdev_error("fskit_route_mknod(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_mkdir( core, FSKIT_ROUTE_ANY, vdevfs_mkdir, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_mkdir(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_create( core, FSKIT_ROUTE_ANY, vdevfs_create, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_create(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_open( core, FSKIT_ROUTE_ANY, vdevfs_open, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_open(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_read( core, FSKIT_ROUTE_ANY, vdevfs_read, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_read(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_write( core, FSKIT_ROUTE_ANY, vdevfs_write, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_write(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_close( core, FSKIT_ROUTE_ANY, vdevfs_close, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_close(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_sync( core, FSKIT_ROUTE_ANY, vdevfs_sync, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_sync(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } rh = fskit_route_detach( core, FSKIT_ROUTE_ANY, vdevfs_detach, FSKIT_CONCURRENT ); if( rh < 0 ) { vdev_error("fskit_route_detach(%s) rc = %d\n", FSKIT_ROUTE_ANY, rh ); goto vdev_route_fail; } vdev->fs = fs; vdev->close_rh = rh; // set the root to be owned by the effective UID and GID of user fskit_chown( core, "/", 0, 0, geteuid(), getegid() ); // import the underlying filesystem once we're mounted, but before taking requests. rc = fskit_fuse_postmount_callback( fs, vdevfs_dev_import, vdev ); if( rc != 0 ) { vdev_error("fskit_fuse_postmount_callback() rc = %d\n", rc ); vdev->fs = NULL; goto vdev_route_fail; } return 0; vdev_route_fail: fskit_fuse_shutdown( fs, NULL ); free( fs ); vdevfs_shutdown( vdev ); return rh; }
// 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; }
// callback to be fed int vdev_load_all_at. // builds up the children listing of vdevfs_scandirat_context.parent_dir. // if we find a directory, open it and enqueue its file descriptor to dir_queue. // return 0 on success // return -ENOMEM on OOM static int vdevfs_scandirat_context_callback( int dirfd, struct dirent* dent, void* cls ) { struct vdevfs_scandirat_context* ctx = (struct vdevfs_scandirat_context*)cls; int rc = 0; int fd = 0; struct stat sb; struct fskit_entry* child; char linkbuf[8193]; // for resolving an underlying symlink char const* method_name; // for logging char* joined_path = NULL; struct vdevfs_scandirat_queue* next = NULL; // skip . and .. if( strcmp( dent->d_name, "." ) == 0 || strcmp( dent->d_name, ".." ) == 0 ) { return 0; } // learn more... rc = fstatat( dirfd, dent->d_name, &sb, AT_SYMLINK_NOFOLLOW ); if( rc != 0 ) { rc = -errno; // mask errors; just log the serious ones if( rc != -ENOENT && rc != -EACCES ) { vdev_error("fstatat(%d, '%s') rc = %d\n", dirfd, dent->d_name, rc ); } return 0; } // directory? get an fd to it if so, so we can keep scanning if( S_ISDIR( sb.st_mode ) ) { // try to get at it fd = openat( dirfd, dent->d_name, O_RDONLY ); if( fd < 0 ) { rc = -errno; // mask errors; just log the serious ones if( rc != -ENOENT && rc != -EACCES ) { vdev_error("openat(%d, '%s') rc = %d\n", dirfd, dent->d_name, rc ); } return 0; } // woo! save it joined_path = vdev_fullpath( ctx->parent_path, dent->d_name, NULL ); if( joined_path == NULL ) { close( fd ); return -ENOMEM; } next = VDEV_CALLOC( struct vdevfs_scandirat_queue, 1 ); if( next == NULL ) { close( fd ); free( joined_path ); return -ENOMEM; } next->fd = fd; next->path = joined_path; next->next = NULL; ctx->tail->next = next; ctx->tail = ctx->tail->next; }
// open: open the file as usual, but from the underlying filesystem // NOTE: since this is backed by FUSE, this handler will only be called for regular files int vdevfs_open( struct fskit_core* core, struct fskit_match_group* grp, struct fskit_entry* fent, int flags, void** handle_cls ) { int fd = 0; struct vdevfs* vdev = (struct vdevfs*)fskit_core_get_user_data( core ); struct fskit_fuse_state* fs_state = fskit_fuse_get_state(); char const* path = NULL; int rc = 0; // dir or file? char const* method = NULL; if( fskit_entry_get_type( fent ) == FSKIT_ENTRY_TYPE_DIR ) { rc = vdevfs_access_check( vdev, fs_state, "opendir", grp->path ); } else { rc = vdevfs_access_check( vdev, fs_state, "open", grp->path ); } if( rc < 0 ) { // denied! return -ENOENT; } // only care about open() for files if( fskit_entry_get_type( fent ) == FSKIT_ENTRY_TYPE_DIR ) { // done! return 0; } // must be relative path path = grp->path; while( *path == '/' && *path != '\0' ) { path++; } if( *path == '\0' ) { path = "."; } fd = openat( vdev->mountpoint_dirfd, path, flags ); if( fd < 0 ) { fd = -errno; vdev_error("openat(%d, '%s') rc = %d\n", vdev->mountpoint_dirfd, path, fd ); return fd; } // careful... void* handle_data = NULL; memcpy( &handle_data, &fd, 4 ); *handle_cls = handle_data; return 0; }
// yield the next device event // return 0 on success // return 1 if there are no more devices // return -EAGAIN if vdev should try to get this device again // return -errno on failure to poll for devices or read the next device packet. int vdev_os_next_device( struct vdev_device_request* vreq, void* cls ) { int rc = 0; struct vdev_linux_context* ctx = (struct vdev_linux_context*)cls; char buf[VDEV_LINUX_NETLINK_BUF_MAX]; ssize_t len = 0; char cbuf[CMSG_SPACE(sizeof(struct ucred))]; struct cmsghdr *chdr = NULL; struct ucred *cred = NULL; struct msghdr hdr; struct iovec iov; struct sockaddr_nl cnls; pthread_mutex_lock( &ctx->initial_requests_lock ); // do we have initial requests? if( ctx->initial_requests != NULL ) { // next request struct vdev_device_request* req = ctx->initial_requests; // consume ctx->initial_requests = ctx->initial_requests->next; memcpy( vreq, req, sizeof(struct vdev_device_request) ); free( req ); pthread_mutex_unlock( &ctx->initial_requests_lock ); // was that the last of them? if( ctx->initial_requests == NULL ) { // tell vdevd that we've flushed all pending requests vdev_os_context_signal_flushed( ctx->os_ctx ); } return 0; } else if( ctx->os_ctx->state->once ) { // out of requests; die pthread_mutex_unlock( &ctx->initial_requests_lock ); return 1; } else { pthread_mutex_unlock( &ctx->initial_requests_lock ); } memset(&hdr, 0, sizeof(struct msghdr)); // next event (wait forever) // NOTE: this is a cancellation point! rc = poll( &ctx->pfd, 1, -1 ); if( rc < 0 ) { rc = -errno; if( rc == -EINTR ) { // try again return -EAGAIN; } vdev_error("FATAL: poll(%d) rc = %d\n", ctx->pfd.fd, rc ); return rc; } // get the event iov.iov_base = buf; iov.iov_len = VDEV_LINUX_NETLINK_BUF_MAX; hdr.msg_iov = &iov; hdr.msg_iovlen = 1; // get control-plane messages hdr.msg_control = cbuf; hdr.msg_controllen = sizeof(cbuf); hdr.msg_name = &cnls; hdr.msg_namelen = sizeof(cnls); // get the event len = recvmsg( ctx->pfd.fd, &hdr, 0 ); if( len < 0 ) { rc = -errno; vdev_error("FATAL: recvmsg(%d) rc = %d\n", ctx->pfd.fd, rc ); return rc; } // big enough? if( len < 32 || len >= VDEV_LINUX_NETLINK_BUF_MAX ) { vdev_error("Netlink message is %zd bytes; ignoring...\n", len ); return -EAGAIN; } // control message, for credentials chdr = CMSG_FIRSTHDR( &hdr ); if( chdr == NULL || chdr->cmsg_type != SCM_CREDENTIALS ) { vdev_error("%s", "Netlink message has no credentials\n"); return -EAGAIN; } // get the credentials cred = (struct ucred *)CMSG_DATA(chdr); // if not root, ignore if( cred->uid != 0 ) { vdev_error("Ignoring message from non-root ID %d\n", cred->uid ); return -EAGAIN; } // if udev, ignore if( memcmp( buf, VDEV_LINUX_NETLINK_UDEV_HEADER, VDEV_LINUX_NETLINK_UDEV_HEADER_LEN ) == 0 ) { // message from udev; ignore vdev_warn("%s", "Ignoring libudev message\n"); return -EAGAIN; } // kernel messages don't come from userspace if( cnls.nl_pid > 0 ) { // from userspace??? vdev_warn("Ignoring message from PID %d\n", (int)cnls.nl_pid ); return -EAGAIN; } // parse the event buffer vdev_debug("%p from netlink\n", vreq ); rc = vdev_linux_parse_request( ctx, vreq, buf, len ); if( rc != 0 ) { vdev_error("vdev_linux_parse_request rc = %d\n", rc ); return -EAGAIN; } return 0; }
// yield new devices int vdev_os_main( struct vdev_os_context* vos ) { int rc = 0; while( vos->running ) { // make a device request struct vdev_device_request* vreq = VDEV_CALLOC( struct vdev_device_request, 1 ); if( vreq == NULL ) { // OOM break; } // next device request rc = vdev_device_request_init( vreq, vos->state, VDEV_DEVICE_INVALID, NULL ); if( rc != 0 ) { if( rc == -EAGAIN ) { continue; } free( vreq ); vdev_error("vdev_device_request_init rc = %d\n", rc ); break; } // yield the next device rc = vdev_os_next_device( vreq, vos->os_cls ); if( rc != 0 ) { vdev_device_request_free( vreq ); free( vreq ); if( rc < 0 ) { vdev_error("vdev_os_next_device rc = %d\n", rc ); if( rc == -EAGAIN ) { // OS backend says try again continue; } else { // fatal error break; } } else { // exit on success rc = 0; break; } } vdev_debug("Next device: %p, type=%d path=%s major=%u minor=%u mode=%o\n", vreq, vreq->type, vreq->path, major(vreq->dev), minor(vreq->dev), vreq->mode ); /* struct sglib_vdev_params_iterator itr2; struct vdev_param_t* dp2 = NULL; printf("vreq %p: params:\n", vreq); for( dp2 = sglib_vdev_params_it_init_inorder( &itr2, vreq->params ); dp2 != NULL; dp2 = sglib_vdev_params_it_next( &itr2 ) ) { printf(" '%s' == '%s'\n", dp2->key, dp2->value ); } */ // post the event to the device work queue rc = vdev_device_request_enqueue( &vos->state->device_wq, vreq ); if( rc != 0 ) { vdev_device_request_free( vreq ); free( vreq ); vdev_error("vdev_device_request_add rc = %d\n", rc ); continue; } } return rc; }
// parse a uevent, and use the information to fill in a device request. // nlbuf must be a contiguous concatenation of null-terminated KEY=VALUE strings. // return 0 on success static int vdev_linux_parse_request( struct vdev_linux_context* ctx, struct vdev_device_request* vreq, char* nlbuf, ssize_t buflen ) { char* buf = nlbuf; char* key = NULL; char* value = NULL; int offset = 0; int rc = 0; unsigned int major = 0; unsigned int minor = 0; bool have_major = false; bool have_minor = false; mode_t dev_mode = 0; int line_count = 0; bool not_param = false; // if set to true, add as an OS-specific parameter to the vreq char* devpath = NULL; // sysfs devpath char* subsystem = NULL; // sysfs subsystem char* devname = (char*)VDEV_DEVICE_PATH_UNKNOWN; // DEVNAME from uevent vdev_device_request_t reqtype = VDEV_DEVICE_INVALID; vdev_debug("%p: uevent buffer\n", vreq ); vdev_linux_debug_uevent( nlbuf, buflen ); // sanity check: if the first line is $action@$devpath, then skip it (since the information // contained in the uevent will encode the very same bits of information) if( strchr(buf, '@') != NULL ) { // advance to the next line offset += strlen(buf) + 1; } // get key/value pairs while( offset < buflen ) { line_count++; not_param = false; rc = vdev_keyvalue_next( buf + offset, &key, &value ); if( rc < 0 ) { vdev_error("Invalid line %d (byte %d): '%s'\n", line_count, offset, buf + offset ); vdev_linux_error_uevent( nlbuf, buflen ); return -EINVAL; } offset += rc + 1; // count the \0 at the end rc = 0; // is this the action to take? if( strcmp(key, "ACTION") == 0 ) { reqtype = vdev_linux_parse_device_request_type( value ); if( reqtype == VDEV_DEVICE_INVALID ) { vdev_error("Invalid ACTION '%s'\n", value ); vdev_linux_error_uevent( nlbuf, buflen ); return -EINVAL; } vdev_device_request_set_type( vreq, reqtype ); not_param = true; } // is this the sysfs device path? else if( strcmp(key, "DEVPATH") == 0 ) { devpath = value; } // is this the devname? else if( strcmp(key, "DEVNAME") == 0 ) { devname = value; } // subsystem given? else if( strcmp(key, "SUBSYSTEM") == 0 ) { subsystem = vdev_strdup_or_null( value ); } // is this the major device number? else if( strcmp(key, "MAJOR") == 0 && !have_major ) { char* tmp = NULL; major = (int)strtol( value, &tmp, 10 ); if( *tmp != '\0' ) { vdev_error("Invalid 'MAJOR' value '%s'\n", value); vdev_linux_error_uevent( nlbuf, buflen ); return -EINVAL; } have_major = true; not_param = true; } // is this the minor device number? else if( strcmp(key, "MINOR") == 0 && !have_minor ) { char* tmp = NULL; minor = (int)strtol( value, &tmp, 10 ) ; if( *tmp != '\0' ) { vdev_error("Invalid 'MINOR' value '%s'\n", value ); vdev_linux_error_uevent( nlbuf, buflen ); return -EINVAL; } have_minor = true; not_param = true; } if( !not_param ) { // add to OS params rc = vdev_device_request_add_param( vreq, key, value ); if( rc != 0 ) { // could be OOM if( subsystem != NULL ) { free( subsystem ); } return rc; } } } if( reqtype == VDEV_DEVICE_INVALID ) { vdev_error("%s", "No ACTION given\n"); vdev_linux_error_uevent( nlbuf, buflen ); if( subsystem != NULL ) { free( subsystem ); } return -EINVAL; } if( (!have_major && have_minor) || (have_major && !have_minor) ) { vdev_error("Missing device information: major=%d, minor=%d\n", have_major, have_minor ); vdev_linux_error_uevent( nlbuf, buflen ); if( subsystem != NULL ) { free( subsystem ); } return -EINVAL; } if( have_major && have_minor ) { // explicit major and minor device numbers given vdev_device_request_set_dev( vreq, makedev(major, minor) ); } if( devname != NULL ) { // use this as the device's path vdev_device_request_set_path( vreq, devname ); } if( devpath != NULL ) { // get any remaining information from sysfs // check major/minor? if( !have_major || !have_minor ) { // see if we have major/minor device numbers for this device... rc = vdev_linux_sysfs_read_dev_nums( ctx, devpath, &major, &minor ); if( rc == 0 ) { // yup! vdev_device_request_set_dev( vreq, makedev(major, minor) ); have_major = true; have_minor = true; } else { // it's okay to not have dev numbers rc = 0; } } // subsystem? if( subsystem == NULL ) { // see if we have a subsystem rc = vdev_linux_sysfs_read_subsystem( ctx, devpath, &subsystem ); if( rc == 0 ) { // yup! rc = vdev_device_request_add_param( vreq, "SUBSYSTEM", subsystem ); if( rc != 0 ) { // OOM free( subsystem ); return rc; } } else if( rc != -ENOMEM ) { // this is weird... vdev_warn("no subsystem found for '%s'\n", devpath ); rc = 0; } } } if( have_major && have_minor ) { if( subsystem != NULL && strcasecmp(subsystem, "block") == 0 ) { // this is a block dev_mode = S_IFBLK; } else { // this is a character device--we have major/minor numbers dev_mode = S_IFCHR; } vdev_device_request_set_mode( vreq, dev_mode ); } vdev_debug("subsystem = '%s', have_major=%d, major = %u, have_minor=%d, minor = %u, mode = %o\n", subsystem, have_major, major, have_minor, minor, dev_mode ); if( subsystem != NULL ) { free( subsystem ); } // tell helpers where /sys is mounted rc = vdev_device_request_add_param( vreq, "SYSFS_MOUNTPOINT", ctx->sysfs_mountpoint ); if( rc != 0 ) { // OOM return rc; } return rc; }
// get a uevent from a uevent file // replace newlines with '\0', making the uevent look like it came from the netlink socket // (i.e. so it can be parsed by vdev_linux_parse_request) // return 0 on success // return -ENOMEM on OOM // return -errno on failure to stat or read static int vdev_linux_sysfs_read_uevent( char const* fp_uevent, char** ret_uevent_buf, size_t* ret_uevent_len ) { int rc = 0; struct stat sb; char* uevent_buf = NULL; size_t uevent_buf_len = 0; size_t uevent_len = 0; // get uevent size rc = stat( fp_uevent, &sb ); if( rc != 0 ) { rc = -errno; vdev_error("stat('%s') rc = %d\n", fp_uevent, rc ); return rc; } else { uevent_buf_len = sb.st_size; } // read the uevent if( fp_uevent != NULL ) { uevent_buf = VDEV_CALLOC( char, uevent_buf_len ); if( uevent_buf == NULL ) { return -ENOMEM; } rc = vdev_read_file( fp_uevent, uevent_buf, uevent_buf_len ); if( rc != 0 ) { // failed in this vdev_error("vdev_read_file('%s') rc = %d\n", fp_uevent, rc ); free( uevent_buf ); } else { for( unsigned int i = 0; i < uevent_buf_len; i++ ) { if( uevent_buf[i] == '\n' ) { uevent_buf[i] = '\0'; } } // NOTE: the stat size is an upper-bound. Find the exact number of bytes. for( uevent_len = 0; uevent_len < uevent_buf_len; ) { if( *(uevent_buf + uevent_len) == '\0' ) { break; } uevent_len += strlen( uevent_buf + uevent_len ) + 1; } *ret_uevent_buf = uevent_buf; *ret_uevent_len = uevent_len; } }