/* * FUSE calls its equivalent of stat(2) "getattr". This just gets stat * information, e.g. file size and permissions. */ static int brp_getattr(const char *in_path, struct stat *stbuf) { SET_CALLER_UID(); struct out_item *out_item; struct in_item *in_item; char *tail; char *config_str; int ret, fd; int stratum_id; if (in_path[0] == '/' && in_path[1] == '\0') { memcpy(stbuf, &parent_stat, sizeof(parent_stat)); return 0; } if (strcmp(in_path, "/reparse_config") == 0) { memcpy(stbuf, &reparse_stat, sizeof(reparse_stat)); config_str = config_contents(); if (config_str) { stbuf->st_size = strlen(config_str); free(config_str); return 0; } else { return -ENOMEM; } } if ( (ret = corresponding((char*)in_path, &fd, stbuf, &out_item, &stratum_id, &in_item, &tail)) >= 0) { stat_filter(stbuf, fd, out_item->filter, stratum_id, in_item, tail); return 0; } else { return ret; } }
/* * Read file contents. */ static int brp_read(const char *in_path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { (void)fi; SET_CALLER_UID(); struct out_item *out_item; int stratum_id; struct in_item *in_item; char *tail; char *config_str; struct stat stbuf; int ret, fd; if (strcmp(in_path, "/reparse_config") == 0) { config_str = config_contents(); if (!config_str) { return -ENOMEM; } ret = MIN(strlen(config_str + offset), size); memcpy(buf, config_str + offset, ret); free(config_str); return ret; } ret = corresponding((char*) in_path, &fd, &stbuf, &out_item, &stratum_id, &in_item, &tail); if (ret < 0) { return ret; } return read_filter(fd, out_item->filter, stratum_id, in_item, tail, buf, size, offset); }
static int bru_truncate(const char *path, off_t length){ SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = truncate(new_path, length); SET_RET_ERRNO(); return ret; }
static int bru_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { SET_CALLER_UID(); int ret = fstat(fi->fh, stbuf); SET_RET_ERRNO(); return ret; }
static int bru_ftruncate(const char *path, off_t length, struct fuse_file_info *fi) { SET_CALLER_UID(); int ret = ftruncate(fi->fh, length); SET_RET_ERRNO(); return ret; }
/* * FUSE uses the word "release" rather than "close". */ static int bru_release(const char *path, struct fuse_file_info *fi) { SET_CALLER_UID(); int ret = close(fi->fh); SET_RET_ERRNO(); return ret; }
static int bru_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { SET_CALLER_UID(); int ret = pwrite(fi->fh, buf, size, offset); SET_RET_ERRNO(); return ret; }
static int bru_chown(const char *path, uid_t owner, gid_t group){ SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = lchown(new_path, owner, group); SET_RET_ERRNO(); return ret; }
static int bru_chmod(const char *path, mode_t mode){ SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = chmod(new_path, mode); SET_RET_ERRNO(); return ret; }
static int bru_removexattr(const char *path, const char *name) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = lremovexattr(new_path, name); SET_RET_ERRNO(); return ret; }
static int bru_mknod(const char *path, mode_t mode, dev_t dev) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = mknod(new_path, mode, dev); SET_RET_ERRNO(); return ret; }
static int bru_listxattr(const char *path, char *list, size_t size) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = llistxattr(new_path, list, size); SET_RET_ERRNO(); return ret; }
static int bru_getxattr(const char *path, const char *name, char *value, size_t size) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = lgetxattr(new_path, name, value, size); SET_RET_ERRNO(); return ret; }
/* * Using statvfs instead of statfs, per FUSE API. */ static int bru_statfs(const char *path, struct statvfs *buf) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = statvfs(new_path, buf); SET_RET_ERRNO(); return ret; }
static int bru_rmdir(const char *path) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = rmdir(new_path); SET_RET_ERRNO(); return ret; }
static int bru_link(const char *old_path, const char *new_path){ SET_CALLER_UID(); REDIR_PATH(old_path, redir_old_path); REDIR_PATH(new_path, redir_new_path); int ret = link(redir_old_path, redir_new_path); SET_RET_ERRNO(); return ret; }
static int bru_symlink(const char *symlink_string, const char *path) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = symlink(symlink_string, new_path); SET_RET_ERRNO(); return ret; }
static int bru_getattr(const char *path, struct stat *stbuf) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = lstat(new_path, stbuf); SET_RET_ERRNO(); return ret; }
/* * This is typically used to indicate a file should be shortened. Like * write(), it is only being used here as an indication to reload the * configuration and stratum information. */ static int brp_truncate(const char *in_path, off_t length) { SET_CALLER_UID(); if (write_attempt(in_path) == 0) { return 0; } else { return -EACCES; } }
/* * TODO: To simplify things, we're mandating absolute paths. We should * probably properly handle relative paths for this later and remove this * restriction. Given this, the first argument to utimensat() is ignored. */ static int bru_utimens(const char *path, const struct timespec *times) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = utimensat(0, new_path, times, AT_SYMLINK_NOFOLLOW); SET_RET_ERRNO(); return ret; }
/* * Yes, FUSE uses creat*e* and POSIX uses creat. */ static int bru_create(const char *path, mode_t mode, struct fuse_file_info *fi) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = 0; if ((fi->fh = creat(new_path, mode)) < 0) ret = -errno; return ret; }
/* * FUSE uses the word "release" rather than "close". */ static int bru_releasedir(const char *path, struct fuse_file_info *fi) { SET_CALLER_UID(); /* * FUSE provides an "uint64_t" rather than DIR* */ int ret = closedir((DIR *) fi->fh); SET_RET_ERRNO(); return ret; }
/* * This is typically used to write to a file, just as you'd expect from the * name. However, for this filesystem, we only use it as an indication to * reload the configuration and stratum information. */ static int brp_write(const char *in_path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { (void)size; (void)offset; (void)fi; SET_CALLER_UID(); if (write_attempt(in_path) == 0) { return strlen(buf); } else { return -EACCES; } }
/* * There is no POSIX fsyncdir - presumably this is just fsync when called on a * directory. Mimicking code from (non-dir) fsync. */ static int bru_fsyncdir(const char *path, int datasync, struct fuse_file_info *fi) { SET_CALLER_UID(); int ret; if(datasync) ret = fdatasync(fi->fh); else ret = fsync(fi->fh); SET_RET_ERRNO(); return ret; }
/* * We cannot use POSIX access() for two reasons: * 1. It uses real uid, rather than effective or filesystem uid. * 2. It dereferences symlinks. * Instead, we're using faccessat(). * TODO: To simplify things, we're mandating absolute paths. We should * probably properly handle relative paths for this later and remove this * restriction. Given this, the first argument to faccessat() is ignored. * TODO: POSIX faccessat() doesn't support AT_SYMLINK_NOFOLLOW, and neither * does musl. See if we can upstream support into musl. Utilizing * AT_SYMLINK_NOFOLLOW is disabled for now so it will compile against musl. */ static int bru_access(const char *path, int mask) { SET_CALLER_UID(); REDIR_PATH(path, new_path); /* * Disabling AT_SYMLINK_NOFOLLOW since musl does not (yet?) support it. * int ret = faccessat(0, new_path, mask, AT_EACCESS | AT_SYMLINK_NOFOLLOW); */ int ret = faccessat(0, new_path, mask, AT_EACCESS); SET_RET_ERRNO(); return ret; }
/* * Unlike POSIX open(), it seems the return value should be 0 for success, not * the file descriptor. */ static int bru_open(const char *path, struct fuse_file_info *fi) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret = open(new_path, fi->flags); if (ret < 0) { ret = -errno; } else { fi->fh = ret; ret = 0; } return ret; }
/* * FUSE uses this primarily for a permissions check. Actually returning a * file handler is optional. Unlike POSIX, this does not directly return * the file handler, but rather returns indictation of whether or not the user * may use opendir(). */ static int bru_opendir(const char *path, struct fuse_file_info *fi) { SET_CALLER_UID(); REDIR_PATH(path, new_path); int ret; DIR *d = opendir(new_path); /* * It seems FUSE wants an int pointer, not a directory stream pointer. */ fi->fh = (intptr_t) d; if (d) ret = 0; else ret = -errno; return ret; }
static int bru_readlink(const char *path, char *buf, size_t bufsize) { SET_CALLER_UID(); REDIR_PATH(path, new_path); /* * Alternative approach zero out out the buffer: * memset(buf, '\0', bufsize); * TODO: Benchmark if this is faster. */ int bytes_read = readlink(new_path, buf, bufsize); int ret = 0; if(bytes_read < 0) ret = -errno; else if(bytes_read <= bufsize) buf[bytes_read] = '\0'; return ret; }
/* * Check if user has permissions to do something with file. e.g. read or write. */ static int brp_open(const char *in_path, struct fuse_file_info *fi) { SET_CALLER_UID(); struct out_item *out_item; int stratum_id; struct in_item *in_item; char *tail; int ret; struct stat stbuf; /* * /reparse_config is the only file which could possibly be written to. * Get that out of the way here so we can assume everything else later is * only being read. */ if (strcmp(in_path, "/reparse_config") == 0) { struct fuse_context *context = fuse_get_context(); if (context->uid != 0) { /* Non-root users cannot do anything with this file. */ return -EACCES; } else { return 0; } } /* * Everything else in this filesystem is read-only. If the user requested * anything else, return EACCES. * * Note the way permissions are stored in fi->flags do *not* have a single * bit flag for read or write, hence the unusual looking check below. See * `man 2 open`. */ if ((fi->flags & 3) != O_RDONLY ) { return -EACCES; } if ( (ret = corresponding((char*)in_path, NULL, &stbuf, &out_item, &stratum_id, &in_item, &tail)) >= 0) { return 0; } return -ENOENT; }
/* * Provides contents of a directory, e.g. as used by `ls`. */ static int brp_readdir(const char *in_path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { SET_CALLER_UID(); (void) offset; (void) fi; char out_path[PATH_MAX+1]; size_t i, j; int st; size_t in_path_len = strlen(in_path); /* handle root directory specially */ if (in_path_len == 1) { in_path_len = 0; } struct stat stbuf; struct in_item *in_item; int ret_val = -ENOENT; char *slash; int *stratum_root_fd = malloc(sizeof(int)*nstratum); int ret = chdir(STRATA_ROOT); if (unlikely(ret < 0)) { // strata root missing?! perror("Failed to chdir to strata root"); exit(1); } for (st = 0; st < nstratum; st++) stratum_root_fd[st] = open(stratum[st], O_RDONLY|O_DIRECTORY); struct dirent *dir; struct str_vec v; str_vec_new(&v); for (st = 0; st < nstratum; st++) { if (unlikely(stratum_root_fd[st] < 0)) continue; ret = seteuid(0); if (unlikely(ret < 0)) return -errno; ret = fchdir(stratum_root_fd[st]); if (unlikely(ret < 0)) continue; ret = chroot("."); if (unlikely(ret < 0)) continue; SET_CALLER_UID(); for (i = 0; i < out_item_count; i++) { /* * Check for contents of one of the configured directories */ in_item = out_items[i].in_items; if (strncmp(in_path, out_items[i].path, out_items[i].path_len) || (in_path[out_items[i].path_len] != '\0' && in_path[out_items[i].path_len] != '/') || out_items[i].file_type != FILE_TYPE_DIRECTORY) continue; for (j = 0; j < out_items[i].in_item_count; j++) { if (in_item[j].stratum_id >= 0 && in_item[j].stratum_id != st) continue; if (in_item[j].path_len+ in_path_len-out_items[i].path_len > PATH_MAX) continue; strcpy(out_path, in_item[j].path); strcat(out_path, in_path+out_items[i].path_len); ret = stat(out_path, &stbuf); if (ret < 0) continue; if (S_ISDIR(stbuf.st_mode)) { DIR *d = opendir(out_path); if (!d) { perror("opendir()"); continue; } while ( (dir = readdir(d)) ) { str_vec_append(&v, dir->d_name); } closedir(d); } else { if (strrchr(out_path, '/')) { str_vec_append(&v, strrchr(out_path, '/')+1); } else { str_vec_append(&v, out_path); } } } } for (i = 0; i < out_item_count; i++) { /* * Check for a match directly on one of the configured items or a virtual parent directory */ if (strncmp(out_items[i].path, in_path, in_path_len) || out_items[i].path[in_path_len] != '/') continue; in_item = out_items[i].in_items; for (j = 0; j < out_items[i].in_item_count; j++) { if (in_item[j].stratum_id >= 0 && in_item[j].stratum_id != st) continue; ret = stat(in_item[j].path, &stbuf); if (ret < 0) continue; if (out_items[i].path_len-in_path_len-1 > PATH_MAX) continue; strcpy(out_path, out_items[i].path+in_path_len+1); if ( (slash = strchr(out_path, '/')) ) { *slash = '\0'; } str_vec_append(&v, out_path); break; } } } /* * Handle reparse_config on root */ if (in_path[0] == '/' && in_path[1] == '\0') { str_vec_append(&v, "reparse_config"); } str_vec_uniq(&v); for (i = 0; i < v.len; i++) { if (v.array[i][0] != '\0') { filler(buf, v.array[i], NULL, 0); ret_val = 0; } } str_vec_free(&v); for (st = 0; st < nstratum; st++) if (stratum_root_fd[st] >= 0) close(stratum_root_fd[st]); free(stratum_root_fd); // If anything goes wrong here, we are stuck in a different root. // In that case we give up and quit. ret = seteuid(0); if (unlikely(ret < 0)) { perror("seteuid"); exit(1); } ret = fchdir(brp_orig_root); if (unlikely(ret < 0)) { perror("Failed to chdir to original root"); exit(1); } ret = chroot("."); if (unlikely(ret < 0)) { perror("Failed to chroot to original root"); exit(1); } ret = fchdir(brp_orig_cwd); if (unlikely(ret < 0)) { perror("Failed to change back to old cwd"); exit(1); } return ret_val; }