// 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; }
// 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; }
// 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; }