/* fuse subdir module: offset paths with a base directory Copyright (C) 2007 Miklos Szeredi This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB */ #include #include #include #include #include #include #include struct subdir { char *base; size_t baselen; int rellinks; struct fuse_fs *next; }; static struct subdir *subdir_get(void) { return fuse_get_context()->private_data; } static int subdir_addpath(struct subdir *d, const char *path, char **newpathp) { char *newpath = NULL; if (path != NULL) { unsigned newlen = d->baselen + strlen(path); newpath = malloc(newlen + 2); if (!newpath) return -ENOMEM; if (path[0] == '/') path++; strcpy(newpath, d->base); strcpy(newpath + d->baselen, path); if (!newpath[0]) strcpy(newpath, "."); } *newpathp = newpath; return 0; } static int subdir_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_getattr(d->next, newpath, stbuf, fi); free(newpath); } return err; } static int subdir_access(const char *path, int mask) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_access(d->next, newpath, mask); free(newpath); } return err; } static int count_components(const char *p) { int ctr; for (; *p == '/'; p++); for (ctr = 0; *p; ctr++) { for (; *p && *p != '/'; p++); for (; *p == '/'; p++); } return ctr; } static void strip_common(const char **sp, const char **tp) { const char *s = *sp; const char *t = *tp; do { for (; *s == '/'; s++); for (; *t == '/'; t++); *tp = t; *sp = s; for (; *s == *t && *s && *s != '/'; s++, t++); } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t)); } static void transform_symlink(struct subdir *d, const char *path, char *buf, size_t size) { const char *l = buf; size_t llen; char *s; int dotdots; int i; if (l[0] != '/' || d->base[0] != '/') return; strip_common(&l, &path); if (l - buf < (long) d->baselen) return; dotdots = count_components(path); if (!dotdots) return; dotdots--; llen = strlen(l); if (dotdots * 3 + llen + 2 > size) return; s = buf + dotdots * 3; if (llen) memmove(s, l, llen + 1); else if (!dotdots) strcpy(s, "."); else *s = '\0'; for (s = buf, i = 0; i < dotdots; i++, s += 3) memcpy(s, "../", 3); } static int subdir_readlink(const char *path, char *buf, size_t size) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_readlink(d->next, newpath, buf, size); if (!err && d->rellinks) transform_symlink(d, newpath, buf, size); free(newpath); } return err; } static int subdir_opendir(const char *path, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_opendir(d->next, newpath, fi); free(newpath); } return err; } static int subdir_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_readdir(d->next, newpath, buf, filler, offset, fi, flags); free(newpath); } return err; } static int subdir_releasedir(const char *path, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_releasedir(d->next, newpath, fi); free(newpath); } return err; } static int subdir_mknod(const char *path, mode_t mode, dev_t rdev) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_mknod(d->next, newpath, mode, rdev); free(newpath); } return err; } static int subdir_mkdir(const char *path, mode_t mode) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_mkdir(d->next, newpath, mode); free(newpath); } return err; } static int subdir_unlink(const char *path) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_unlink(d->next, newpath); free(newpath); } return err; } static int subdir_rmdir(const char *path) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_rmdir(d->next, newpath); free(newpath); } return err; } static int subdir_symlink(const char *from, const char *path) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_symlink(d->next, from, newpath); free(newpath); } return err; } static int subdir_rename(const char *from, const char *to, unsigned int flags) { struct subdir *d = subdir_get(); char *newfrom; char *newto; int err = subdir_addpath(d, from, &newfrom); if (!err) { err = subdir_addpath(d, to, &newto); if (!err) { err = fuse_fs_rename(d->next, newfrom, newto, flags); free(newto); } free(newfrom); } return err; } static int subdir_link(const char *from, const char *to) { struct subdir *d = subdir_get(); char *newfrom; char *newto; int err = subdir_addpath(d, from, &newfrom); if (!err) { err = subdir_addpath(d, to, &newto); if (!err) { err = fuse_fs_link(d->next, newfrom, newto); free(newto); } free(newfrom); } return err; } static int subdir_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_chmod(d->next, newpath, mode, fi); free(newpath); } return err; } static int subdir_chown(const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_chown(d->next, newpath, uid, gid, fi); free(newpath); } return err; } static int subdir_truncate(const char *path, off_t size, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_truncate(d->next, newpath, size, fi); free(newpath); } return err; } static int subdir_utimens(const char *path, const struct timespec ts[2], struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_utimens(d->next, newpath, ts, fi); free(newpath); } return err; } static int subdir_create(const char *path, mode_t mode, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_create(d->next, newpath, mode, fi); free(newpath); } return err; } static int subdir_open(const char *path, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_open(d->next, newpath, fi); free(newpath); } return err; } static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp, size_t size, off_t offset, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi); free(newpath); } return err; } static int subdir_write_buf(const char *path, struct fuse_bufvec *buf, off_t offset, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi); free(newpath); } return err; } static int subdir_statfs(const char *path, struct statvfs *stbuf) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_statfs(d->next, newpath, stbuf); free(newpath); } return err; } static int subdir_flush(const char *path, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_flush(d->next, newpath, fi); free(newpath); } return err; } static int subdir_release(const char *path, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_release(d->next, newpath, fi); free(newpath); } return err; } static int subdir_fsync(const char *path, int isdatasync, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_fsync(d->next, newpath, isdatasync, fi); free(newpath); } return err; } static int subdir_fsyncdir(const char *path, int isdatasync, struct fuse_file_info *fi) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi); free(newpath); } return err; } static int subdir_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_setxattr(d->next, newpath, name, value, size, flags); free(newpath); } return err; } static int subdir_getxattr(const char *path, const char *name, char *value, size_t size) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_getxattr(d->next, newpath, name, value, size); free(newpath); } return err; } static int subdir_listxattr(const char *path, char *list, size_t size) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_listxattr(d->next, newpath, list, size); free(newpath); } return err; } static int subdir_removexattr(const char *path, const char *name) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_removexattr(d->next, newpath, name); free(newpath); } return err; } static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd, struct flock *lock) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_lock(d->next, newpath, fi, cmd, lock); free(newpath); } return err; } static int subdir_flock(const char *path, struct fuse_file_info *fi, int op) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_flock(d->next, newpath, fi, op); free(newpath); } return err; } static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx) { struct subdir *d = subdir_get(); char *newpath; int err = subdir_addpath(d, path, &newpath); if (!err) { err = fuse_fs_bmap(d->next, newpath, blocksize, idx); free(newpath); } return err; } static off_t subdir_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi) { struct subdir *ic = subdir_get(); char *newpath; int res = subdir_addpath(ic, path, &newpath); if (!res) { res = fuse_fs_lseek(ic->next, newpath, off, whence, fi); free(newpath); } return res; } static void *subdir_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { struct subdir *d = subdir_get(); fuse_fs_init(d->next, conn, cfg); /* Don't touch cfg->nullpath_ok, we can work with either */ return d; } static void subdir_destroy(void *data) { struct subdir *d = data; fuse_fs_destroy(d->next); free(d->base); free(d); } static const struct fuse_operations subdir_oper = { .destroy = subdir_destroy, .init = subdir_init, .getattr = subdir_getattr, .access = subdir_access, .readlink = subdir_readlink, .opendir = subdir_opendir, .readdir = subdir_readdir, .releasedir = subdir_releasedir, .mknod = subdir_mknod, .mkdir = subdir_mkdir, .symlink = subdir_symlink, .unlink = subdir_unlink, .rmdir = subdir_rmdir, .rename = subdir_rename, .link = subdir_link, .chmod = subdir_chmod, .chown = subdir_chown, .truncate = subdir_truncate, .utimens = subdir_utimens, .create = subdir_create, .open = subdir_open, .read_buf = subdir_read_buf, .write_buf = subdir_write_buf, .statfs = subdir_statfs, .flush = subdir_flush, .release = subdir_release, .fsync = subdir_fsync, .fsyncdir = subdir_fsyncdir, .setxattr = subdir_setxattr, .getxattr = subdir_getxattr, .listxattr = subdir_listxattr, .removexattr = subdir_removexattr, .lock = subdir_lock, .flock = subdir_flock, .bmap = subdir_bmap, .lseek = subdir_lseek, }; static const struct fuse_opt subdir_opts[] = { FUSE_OPT_KEY("-h", 0), FUSE_OPT_KEY("--help", 0), { "subdir=%s", offsetof(struct subdir, base), 0 }, { "rellinks", offsetof(struct subdir, rellinks), 1 }, { "norellinks", offsetof(struct subdir, rellinks), 0 }, FUSE_OPT_END }; static void subdir_help(void) { printf( " -o subdir=DIR prepend this directory to all paths (mandatory)\n" " -o [no]rellinks transform absolute symlinks to relative\n"); } static int subdir_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { (void) data; (void) arg; (void) outargs; if (!key) { subdir_help(); return -1; } return 1; } static struct fuse_fs *subdir_new(struct fuse_args *args, struct fuse_fs *next[]) { struct fuse_fs *fs; struct subdir *d; d = calloc(1, sizeof(struct subdir)); if (d == NULL) { fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n"); return NULL; } if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1) goto out_free; if (!next[0] || next[1]) { fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n"); goto out_free; } if (!d->base) { fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n"); goto out_free; } if (d->base[0] && d->base[strlen(d->base)-1] != '/') { char *tmp = realloc(d->base, strlen(d->base) + 2); if (!tmp) { fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n"); goto out_free; } d->base = tmp; strcat(d->base, "/"); } d->baselen = strlen(d->base); d->next = next[0]; fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d); if (!fs) goto out_free; return fs; out_free: free(d->base); free(d); return NULL; } FUSE_REGISTER_MODULE(subdir, subdir_new);