/* Copyright 2006,2007,2008,2009,2010 Martin Pärtel This file is part of bindfs. bindfs is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. bindfs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with bindfs. If not, see . This file is based on fusexmp_fh.c from FUSE 2.5.3, which had the following notice: --- FUSE: Filesystem in Userspace Copyright (C) 2001-2006 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. --- */ #include /* For pread/pwrite */ #define _XOPEN_SOURCE 500 #include #include #include #include #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_SETXATTR #include #endif #include #include #include "debug.h" #include "permchain.h" #include "userinfo.h" #include "misc.h" /* SETTINGS */ static struct settings { const char *progname; struct permchain *permchain; /* permission bit rules. see permchain.h */ uid_t new_uid; /* user-specified uid */ gid_t new_gid; /* user-specified gid */ uid_t create_for_uid; gid_t create_for_gid; const char *mntsrc; const char *mntdest; int mntsrc_fd; enum CreatePolicy { CREATE_AS_USER, CREATE_AS_MOUNTER } create_policy; struct permchain *create_permchain; /* the --create-with-perms option */ enum ChownPolicy { CHOWN_NORMAL, CHOWN_IGNORE, CHOWN_DENY } chown_policy; enum ChgrpPolicy { CHGRP_NORMAL, CHGRP_IGNORE, CHGRP_DENY } chgrp_policy; enum ChmodPolicy { CHMOD_NORMAL, CHMOD_IGNORE, CHMOD_DENY } chmod_policy; int chmod_allow_x; enum XAttrPolicy { XATTR_UNIMPLEMENTED, XATTR_READ_ONLY, XATTR_READ_WRITE } xattr_policy; int mirrored_users_only; uid_t* mirrored_users; int num_mirrored_users; gid_t *mirrored_members; int num_mirrored_members; int ctime_from_mtime; int hide_hard_links; } settings; /* PROTOTYPES */ static int is_mirroring_enabled(); /* Checks whether the uid is to be the mirrored owner of all files. */ static int is_mirrored_user(uid_t uid); /* Processes the virtual path to a real path. Don't free() the result. */ static const char *process_path(const char *path); /* The common parts of getattr and fgetattr */ static int getattr_common(const char *path, struct stat *stbuf); /* FUSE callbacks */ static void *bindfs_init(); static void bindfs_destroy(void *private_data); static int bindfs_getattr(const char *path, struct stat *stbuf); static int bindfs_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi); static int bindfs_readlink(const char *path, char *buf, size_t size); static int bindfs_opendir(const char *path, struct fuse_file_info *fi); static inline DIR *get_dirp(struct fuse_file_info *fi); static int bindfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi); static int bindfs_releasedir(const char *path, struct fuse_file_info *fi); static int bindfs_mknod(const char *path, mode_t mode, dev_t rdev); static int bindfs_mkdir(const char *path, mode_t mode); static int bindfs_unlink(const char *path); static int bindfs_rmdir(const char *path); static int bindfs_symlink(const char *from, const char *to); static int bindfs_rename(const char *from, const char *to); static int bindfs_link(const char *from, const char *to); static int bindfs_chmod(const char *path, mode_t mode); static int bindfs_chown(const char *path, uid_t uid, gid_t gid); static int bindfs_truncate(const char *path, off_t size); static int bindfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi); static int bindfs_utime(const char *path, struct utimbuf *buf); static int bindfs_create(const char *path, mode_t mode, struct fuse_file_info *fi); static int bindfs_open(const char *path, struct fuse_file_info *fi); static int bindfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi); static int bindfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi); static int bindfs_statfs(const char *path, struct statvfs *stbuf); static int bindfs_release(const char *path, struct fuse_file_info *fi); static int bindfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi); static void print_usage(const char *progname); static void atexit_func(); static int process_option(void *data, const char *arg, int key, struct fuse_args *outargs); static int is_mirroring_enabled() { return settings.num_mirrored_users + settings.num_mirrored_members > 0; } static int is_mirrored_user(uid_t uid) { int i; for (i = 0; i < settings.num_mirrored_users; ++i) { if (settings.mirrored_users[i] == uid) break; } if (i < settings.num_mirrored_users) { /* found in mirrored_users */ return 1; } else { for (i = 0; i < settings.num_mirrored_members; ++i) { if (user_belongs_to_group(uid, settings.mirrored_members[i])) break; } if (i < settings.num_mirrored_members) /* found in mirrored_members */ return 1; } return 0; } static const char *process_path(const char *path) { if (path == NULL) /* possible? */ return NULL; while (*path == '/') ++path; if (*path == '\0') return "."; else return path; } static int getattr_common(const char *procpath, struct stat *stbuf) { struct fuse_context *fc = fuse_get_context(); /* Copy mtime (file content modification time) to ctime (inode/status change time) if the user asked for that */ if (settings.ctime_from_mtime) stbuf->st_ctime = stbuf->st_mtime; /* Report user-defined owner/group if specified */ if (settings.new_uid != -1) stbuf->st_uid = settings.new_uid; if (settings.new_gid != -1) stbuf->st_gid = settings.new_gid; /* Mirrored user? */ if (is_mirroring_enabled() && is_mirrored_user(fc->uid)) { stbuf->st_uid = fc->uid; } else if (settings.mirrored_users_only && fc->uid != 0) { stbuf->st_mode &= ~0777; /* Deny all access if mirror-only and not root */ return 0; } if ((stbuf->st_mode & S_IFLNK) == S_IFLNK) return 0; /* don't bother with symlink permissions -- they don't matter */ /* Apply user-defined permission bit modifications */ stbuf->st_mode = permchain_apply(settings.permchain, stbuf->st_mode); /* Check that we can really do what we promise */ if (access(procpath, R_OK) == -1) stbuf->st_mode &= ~0444; if (access(procpath, W_OK) == -1) stbuf->st_mode &= ~0222; if (access(procpath, X_OK) == -1) stbuf->st_mode &= ~0111; /* Hide hard links */ if (settings.hide_hard_links) stbuf->st_nlink = 1; return 0; } static void *bindfs_init() { assert(settings.permchain != NULL); assert(settings.mntsrc_fd > 0); if (fchdir(settings.mntsrc_fd) != 0) { fprintf( stderr, "Could not change working directory to '%s': %s\n", settings.mntsrc, strerror(errno) ); fuse_exit(fuse_get_context()->fuse); } return NULL; } static void bindfs_destroy(void *private_data) { } static int bindfs_getattr(const char *path, struct stat *stbuf) { path = process_path(path); if (lstat(path, stbuf) == -1) return -errno; return getattr_common(path, stbuf); } static int bindfs_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { path = process_path(path); if (fstat(fi->fh, stbuf) == -1) return -errno; return getattr_common(path, stbuf); } static int bindfs_readlink(const char *path, char *buf, size_t size) { int res; path = process_path(path); /* No need to check for access to the link itself, since symlink permissions don't matter. Access to the path components of the symlink are automatically queried by FUSE. */ res = readlink(path, buf, size - 1); if (res == -1) return -errno; buf[res] = '\0'; return 0; } static int bindfs_opendir(const char *path, struct fuse_file_info *fi) { DIR *dp; path = process_path(path); dp = opendir(path); if (dp == NULL) return -errno; fi->fh = (unsigned long) dp; return 0; } static inline DIR *get_dirp(struct fuse_file_info *fi) { return (DIR *) (uintptr_t) fi->fh; } static int bindfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { DIR *dp = get_dirp(fi); struct dirent *de; (void) path; seekdir(dp, offset); while ((de = readdir(dp)) != NULL) { struct stat st; memset(&st, 0, sizeof(st)); st.st_ino = de->d_ino; st.st_mode = de->d_type << 12; if (filler(buf, de->d_name, &st, telldir(dp))) break; } return 0; } static int bindfs_releasedir(const char *path, struct fuse_file_info *fi) { DIR *dp = get_dirp(fi); (void) path; closedir(dp); return 0; } static int bindfs_mknod(const char *path, mode_t mode, dev_t rdev) { int res; struct fuse_context *fc; uid_t file_owner = -1; gid_t file_group = -1; path = process_path(path); mode = permchain_apply(settings.create_permchain, mode); if (S_ISFIFO(mode)) res = mkfifo(path, mode); else res = mknod(path, mode, rdev); if (res == -1) return -errno; if (settings.create_policy == CREATE_AS_USER) { fc = fuse_get_context(); file_owner = fc->uid; file_group = fc->gid; } if (settings.create_for_uid != -1) file_owner = settings.create_for_uid; if (settings.create_for_gid != -1) file_group = settings.create_for_gid; if ((file_owner != -1) || (file_group != -1)) { if (chown(path, file_owner, file_group) == -1) { DPRINTF("Failed to chown new device node (%d)", errno); } } return 0; } static int bindfs_mkdir(const char *path, mode_t mode) { int res; struct fuse_context *fc; uid_t file_owner = -1; gid_t file_group = -1; path = process_path(path); mode |= S_IFDIR; /* tell permchain_apply this is a directory */ mode = permchain_apply(settings.create_permchain, mode); res = mkdir(path, mode & 0777); if (res == -1) return -errno; if (settings.create_policy == CREATE_AS_USER) { fc = fuse_get_context(); file_owner = fc->uid; file_group = fc->gid; } if (settings.create_for_uid != -1) file_owner = settings.create_for_uid; if (settings.create_for_gid != -1) file_group = settings.create_for_gid; if ((file_owner != -1) || (file_group != -1)) { if (chown(path, file_owner, file_group) == -1) { DPRINTF("Failed to chown new directory (%d)", errno); } } return 0; } static int bindfs_unlink(const char *path) { int res; path = process_path(path); res = unlink(path); if (res == -1) return -errno; return 0; } static int bindfs_rmdir(const char *path) { int res; path = process_path(path); res = rmdir(path); if (res == -1) return -errno; return 0; } static int bindfs_symlink(const char *from, const char *to) { int res; struct fuse_context *fc; uid_t file_owner = -1; gid_t file_group = -1; to = process_path(to); res = symlink(from, to); if (res == -1) return -errno; if (settings.create_policy == CREATE_AS_USER) { fc = fuse_get_context(); file_owner = fc->uid; file_group = fc->gid; } if (settings.create_for_uid != -1) file_owner = settings.create_for_uid; if (settings.create_for_gid != -1) file_group = settings.create_for_gid; if ((file_owner != -1) || (file_group != -1)) { if (lchown(to, file_owner, file_group) == -1) { DPRINTF("Failed to lchown new symlink (%d)", errno); } } return 0; } static int bindfs_rename(const char *from, const char *to) { int res; from = process_path(from); to = process_path(to); res = rename(from, to); if (res == -1) return -errno; return 0; } static int bindfs_link(const char *from, const char *to) { int res; from = process_path(from); to = process_path(to); res = link(from, to); if (res == -1) return -errno; return 0; } static int bindfs_chmod(const char *path, mode_t mode) { int file_execute_only = 0; struct stat st; mode_t diff = 0; path = process_path(path); if (settings.chmod_allow_x) { /* Get the old permission bits and see which bits would change. */ if (lstat(path, &st) == -1) return -errno; if (S_ISREG(st.st_mode)) { diff = (st.st_mode & 07777) ^ (mode & 07777); file_execute_only = 1; } } switch (settings.chmod_policy) { case CHMOD_NORMAL: if (chmod(path, mode) == -1) return -errno; return 0; case CHMOD_IGNORE: if (file_execute_only) { diff &= 00111; /* See which execute bits were flipped. Forget about other differences. */ if (chmod(path, st.st_mode ^ diff) == -1) return -errno; } return 0; case CHMOD_DENY: if (file_execute_only) { if ((diff & 07666) == 0) { /* Only execute bits have changed, so we can allow this. */ if (chmod(path, mode) == -1) return -errno; return 0; } } return -EPERM; default: assert(0); } } static int bindfs_chown(const char *path, uid_t uid, gid_t gid) { int res; if (uid != -1) { switch (settings.chown_policy) { case CHOWN_NORMAL: break; case CHOWN_IGNORE: uid = -1; break; case CHOWN_DENY: return -EPERM; } } if (gid != -1) { switch (settings.chgrp_policy) { case CHGRP_NORMAL: break; case CHGRP_IGNORE: gid = -1; break; case CHGRP_DENY: return -EPERM; } } if (uid != -1 || gid != -1) { path = process_path(path); res = lchown(path, uid, gid); if (res == -1) return -errno; } return 0; } static int bindfs_truncate(const char *path, off_t size) { int res; path = process_path(path); res = truncate(path, size); if (res == -1) return -errno; return 0; } static int bindfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) { int res; (void) path; res = ftruncate(fi->fh, size); if (res == -1) return -errno; return 0; } static int bindfs_utime(const char *path, struct utimbuf *buf) { int res; path = process_path(path); res = utime(path, buf); if (res == -1) return -errno; return 0; } static int bindfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) { int fd; struct fuse_context *fc; uid_t file_owner = -1; gid_t file_group = -1; path = process_path(path); mode |= S_IFREG; /* tell permchain_apply this is a regular file */ mode = permchain_apply(settings.create_permchain, mode); fd = open(path, fi->flags, mode & 0777); if (fd == -1) return -errno; if (settings.create_policy == CREATE_AS_USER) { fc = fuse_get_context(); file_owner = fc->uid; file_group = fc->gid; } if (settings.create_for_uid != -1) file_owner = settings.create_for_uid; if (settings.create_for_gid != -1) file_group = settings.create_for_gid; if ((file_owner != -1) || (file_group != -1)) { if (chown(path, file_owner, file_group) == -1) { DPRINTF("Failed to chown new file (%d)", errno); } } fi->fh = fd; return 0; } static int bindfs_open(const char *path, struct fuse_file_info *fi) { int fd; path = process_path(path); fd = open(path, fi->flags); if (fd == -1) return -errno; fi->fh = fd; return 0; } static int bindfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int res; (void) path; res = pread(fi->fh, buf, size, offset); if (res == -1) res = -errno; return res; } static int bindfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int res; (void) path; res = pwrite(fi->fh, buf, size, offset); if (res == -1) res = -errno; return res; } static int bindfs_statfs(const char *path, struct statvfs *stbuf) { int res; path = process_path(path); res = statvfs(path, stbuf); if (res == -1) return -errno; return 0; } static int bindfs_release(const char *path, struct fuse_file_info *fi) { (void) path; close(fi->fh); return 0; } static int bindfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi) { int res; (void) path; #ifndef HAVE_FDATASYNC (void) isdatasync; #else if (isdatasync) res = fdatasync(fi->fh); else #endif res = fsync(fi->fh); if (res == -1) return -errno; return 0; } #ifdef HAVE_SETXATTR /* If HAVE_L*XATTR is not defined, we assume Mac/BSD -style *xattr() */ static int bindfs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { DPRINTF("setxattr %s %s=%s", path, name, value); if (settings.xattr_policy == XATTR_READ_ONLY) return -EACCES; /* fuse checks permissions for us */ path = process_path(path); #ifdef HAVE_LSETXATTR if (lsetxattr(path, name, value, size, flags) == -1) #else if (setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW) == -1) #endif return -errno; return 0; } static int bindfs_getxattr(const char *path, const char *name, char *value, size_t size) { int res; DPRINTF("getxattr %s %s", path, name); path = process_path(path); /* fuse checks permissions for us */ #ifdef HAVE_LGETXATTR res = lgetxattr(path, name, value, size); #else res = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); #endif if (res == -1) return -errno; return res; } static int bindfs_listxattr(const char *path, char *list, size_t size) { int res; DPRINTF("listxattr %s", path); path = process_path(path); /* fuse checks permissions for us */ #ifdef HAVE_LLISTXATTR res = llistxattr(path, list, size); #else res = listxattr(path, list, size, XATTR_NOFOLLOW); #endif if (res == -1) return -errno; return res; } static int bindfs_removexattr(const char *path, const char *name) { DPRINTF("removexattr %s %s", path, name); if (settings.xattr_policy == XATTR_READ_ONLY) return -EACCES; path = process_path(path); /* fuse checks permissions for us */ #ifdef HAVE_LREMOVEXATTR if (lremovexattr(path, name) == -1) #else if (removexattr(path, name, XATTR_NOFOLLOW) == -1) #endif return -errno; return 0; } #endif /* HAVE_SETXATTR */ static struct fuse_operations bindfs_oper = { .init = bindfs_init, .destroy = bindfs_destroy, .getattr = bindfs_getattr, .fgetattr = bindfs_fgetattr, /* no access() since we always use -o default_permissions */ .readlink = bindfs_readlink, .opendir = bindfs_opendir, .readdir = bindfs_readdir, .releasedir = bindfs_releasedir, .mknod = bindfs_mknod, .mkdir = bindfs_mkdir, .symlink = bindfs_symlink, .unlink = bindfs_unlink, .rmdir = bindfs_rmdir, .rename = bindfs_rename, .link = bindfs_link, .chmod = bindfs_chmod, .chown = bindfs_chown, .truncate = bindfs_truncate, .ftruncate = bindfs_ftruncate, .utime = bindfs_utime, .create = bindfs_create, .open = bindfs_open, .read = bindfs_read, .write = bindfs_write, .statfs = bindfs_statfs, .release = bindfs_release, .fsync = bindfs_fsync, #ifdef HAVE_SETXATTR .setxattr = bindfs_setxattr, .getxattr = bindfs_getxattr, .listxattr = bindfs_listxattr, .removexattr= bindfs_removexattr, #endif }; static void print_usage(const char *progname) { if (progname == NULL) progname = "bindfs"; printf("\n" "Usage: %s [options] dir mountpoint\n" "Information:\n" " -h --help Print this and exit.\n" " -V --version Print version number and exit.\n" "\n" "Options:\n" " -u --user, --owner Set file owner.\n" " -g --group Set file group.\n" " -m --mirror Comma-separated list of users who will see\n" " themselves as the owners of all files.\n" " -M --mirror-only Like --mirror but disallow access for\n" " all other users.\n" " -n --no-allow-other Do not add -o allow_other to fuse options.\n" "\n" "Permission bits:\n" " -p --perms Specify permissions, similar to chmod\n" " e.g. og-x,og+rD,u=rwX,g+rw or 0644,a+X\n" "\n" "File creation policy:\n" " --create-as-user New files owned by creator (default for root). *\n" " --create-as-mounter New files owned by fs mounter (default for users).\n" " --create-for-user New files owned by specified user. *\n" " --create-for-group New files owned by specified group. *\n" " --create-with-perms Alter permissions of new files.\n" "\n" "Chown policy:\n" " --chown-normal Try to chown the original files (the default).\n" " --chown-ignore Have all chowns fail silently.\n" " --chown-deny Have all chowns fail with 'permission denied'.\n" "\n" "Chgrp policy:\n" " --chgrp-normal Try to chgrp the original files (the default).\n" " --chgrp-ignore Have all chgrps fail silently.\n" " --chgrp-deny Have all chgrps fail with 'permission denied'.\n" "\n" "Chmod policy:\n" " --chmod-normal Try to chmod the original files (the default).\n" " --chmod-ignore Have all chmods fail silently.\n" " --chmod-deny Have all chmods fail with 'permission denied'.\n" " --chmod-allow-x Allow changing file execute bits in any case.\n" "\n" "Extended attribute policy:\n" " --xattr-none Do not implement xattr operations.\n" " --xattr-ro Read-only xattr operations.\n" " --xattr-rw Read-write xattr operations (the default).\n" "\n" "Workarounds:\n" " --ctime-from-mtime Read file properties' change time\n" " from file content modification time.\n" " --hide-hard-links Always report a hard link count of 0.\n" "\n" "FUSE options:\n" " -o opt[,opt,...] Mount options.\n" " -r -o ro Mount strictly read-only.\n" " -d -o debug Enable debug output (implies -f).\n" " -f Foreground operation.\n" " -s Disable multithreaded operation.\n" "\n" "(*: root only)\n" "\n", progname); } static void atexit_func() { permchain_destroy(settings.permchain); settings.permchain = NULL; permchain_destroy(settings.create_permchain); settings.create_permchain = NULL; free(settings.mirrored_users); settings.mirrored_users = NULL; free(settings.mirrored_members); settings.mirrored_members = NULL; } enum OptionKey { OPTKEY_NONOPTION = -2, OPTKEY_UNKNOWN = -1, OPTKEY_HELP, OPTKEY_VERSION, OPTKEY_CREATE_AS_USER, OPTKEY_CREATE_AS_MOUNTER, OPTKEY_CHOWN_NORMAL, OPTKEY_CHOWN_IGNORE, OPTKEY_CHOWN_DENY, OPTKEY_CHGRP_NORMAL, OPTKEY_CHGRP_IGNORE, OPTKEY_CHGRP_DENY, OPTKEY_CHMOD_NORMAL, OPTKEY_CHMOD_IGNORE, OPTKEY_CHMOD_DENY, OPTKEY_CHMOD_ALLOW_X, OPTKEY_XATTR_NONE, OPTKEY_XATTR_READ_ONLY, OPTKEY_XATTR_READ_WRITE, OPTKEY_CTIME_FROM_MTIME, OPTKEY_HIDE_HARD_LINKS }; static int process_option(void *data, const char *arg, int key, struct fuse_args *outargs) { switch ((enum OptionKey)key) { case OPTKEY_HELP: print_usage(my_basename(settings.progname)); exit(0); case OPTKEY_VERSION: printf("%s\n", PACKAGE_STRING); exit(0); case OPTKEY_CREATE_AS_USER: if (getuid() == 0) { settings.create_policy = CREATE_AS_USER; } else { fprintf(stderr, "Error: You need to be root to use --create-as-user !\n"); return -1; } return 0; case OPTKEY_CREATE_AS_MOUNTER: settings.create_policy = CREATE_AS_MOUNTER; return 0; case OPTKEY_CHOWN_NORMAL: settings.chown_policy = CHOWN_NORMAL; return 0; case OPTKEY_CHOWN_IGNORE: settings.chown_policy = CHOWN_IGNORE; return 0; case OPTKEY_CHOWN_DENY: settings.chown_policy = CHOWN_DENY; return 0; case OPTKEY_CHGRP_NORMAL: settings.chgrp_policy = CHGRP_NORMAL; return 0; case OPTKEY_CHGRP_IGNORE: settings.chgrp_policy = CHGRP_IGNORE; return 0; case OPTKEY_CHGRP_DENY: settings.chgrp_policy = CHGRP_DENY; return 0; case OPTKEY_CHMOD_NORMAL: settings.chmod_policy = CHMOD_NORMAL; return 0; case OPTKEY_CHMOD_IGNORE: settings.chmod_policy = CHMOD_IGNORE; return 0; case OPTKEY_CHMOD_DENY: settings.chmod_policy = CHMOD_DENY; return 0; case OPTKEY_CHMOD_ALLOW_X: settings.chmod_allow_x = 1; return 0; case OPTKEY_XATTR_NONE: settings.xattr_policy = XATTR_UNIMPLEMENTED; return 0; case OPTKEY_XATTR_READ_ONLY: settings.xattr_policy = XATTR_READ_ONLY; return 0; case OPTKEY_XATTR_READ_WRITE: settings.xattr_policy = XATTR_READ_WRITE; return 0; case OPTKEY_CTIME_FROM_MTIME: settings.ctime_from_mtime = 1; return 0; case OPTKEY_HIDE_HARD_LINKS: settings.hide_hard_links = 1; return 0; case OPTKEY_NONOPTION: if (!settings.mntsrc) { settings.mntsrc = arg; return 0; } else if (!settings.mntdest) { settings.mntdest = arg; return 1; /* leave this argument for fuse_main */ } else { fprintf(stderr, "Too many arguments given\n"); return -1; } default: return 1; } } int main(int argc, char *argv[]) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); /* Fuse's option parser will store things here. */ static struct OptionData { char *user; char *group; char *perms; char *mirror; char *mirror_only; char *create_for_user; char *create_for_group; char *create_with_perms; int no_allow_other; } od = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0}; #define OPT2(one, two, key) \ FUSE_OPT_KEY(one, key), \ FUSE_OPT_KEY(two, key) #define OPT_OFFSET2(one, two, offset, key) \ {one, offsetof(struct OptionData, offset), key}, \ {two, offsetof(struct OptionData, offset), key} #define OPT_OFFSET3(one, two, three, offset, key) \ {one, offsetof(struct OptionData, offset), key}, \ {two, offsetof(struct OptionData, offset), key}, \ {three, offsetof(struct OptionData, offset), key} static const struct fuse_opt options[] = { OPT2("-h", "--help", OPTKEY_HELP), OPT2("-V", "--version", OPTKEY_VERSION), OPT_OFFSET2("-u %s", "--user=%s", user, -1), OPT_OFFSET2( "--owner=%s", "owner=%s", user, -1), OPT_OFFSET3("-g %s", "--group=%s", "group=%s", group, -1), OPT_OFFSET3("-p %s", "--perms=%s", "perms=%s", perms, -1), OPT_OFFSET3("-m %s", "--mirror=%s", "mirror=%s", mirror, -1), OPT_OFFSET3("-M %s", "--mirror-only=%s", "mirror-only=%s", mirror_only, -1), OPT_OFFSET3("-n", "--no-allow-other", "no-allow-other", no_allow_other, -1), OPT2("--create-as-user", "create-as-user", OPTKEY_CREATE_AS_USER), OPT2("--create-as-mounter", "create-as-mounter", OPTKEY_CREATE_AS_MOUNTER), OPT_OFFSET2("--create-for-user=%s", "create-for-user=%s", create_for_user, -1), OPT_OFFSET2("--create-for-group=%s", "create-for-group=%s", create_for_group, -1), OPT_OFFSET2("--create-with-perms=%s", "create-with-perms=%s", create_with_perms, -1), OPT2("--chown-normal", "chown-normal", OPTKEY_CHOWN_NORMAL), OPT2("--chown-ignore", "chown-ignore", OPTKEY_CHOWN_IGNORE), OPT2("--chown-deny", "chown-deny", OPTKEY_CHOWN_DENY), OPT2("--chgrp-normal", "chgrp-normal", OPTKEY_CHGRP_NORMAL), OPT2("--chgrp-ignore", "chgrp-ignore", OPTKEY_CHGRP_IGNORE), OPT2("--chgrp-deny", "chgrp-deny", OPTKEY_CHGRP_DENY), OPT2("--chmod-normal", "chmod-normal", OPTKEY_CHMOD_NORMAL), OPT2("--chmod-ignore", "chmod-ignore", OPTKEY_CHMOD_IGNORE), OPT2("--chmod-deny", "chmod-deny", OPTKEY_CHMOD_DENY), OPT2("--chmod-allow-x", "chmod-allow-x", OPTKEY_CHMOD_ALLOW_X), OPT2("--xattr-none", "xattr-none", OPTKEY_XATTR_NONE), OPT2("--xattr-ro", "xattr-ro", OPTKEY_XATTR_READ_ONLY), OPT2("--xattr-rw", "xattr-rw", OPTKEY_XATTR_READ_WRITE), OPT2("--ctime-from-mtime", "ctime-from-mtime", OPTKEY_CTIME_FROM_MTIME), OPT2("--hide-hard-links", "hide-hard-links", OPTKEY_HIDE_HARD_LINKS), FUSE_OPT_END }; /* General-purpose variables */ int i, j; char *p, *tmpstr; int fuse_main_return; /* Initialize settings */ settings.progname = argv[0]; settings.permchain = permchain_create(); settings.new_uid = -1; settings.new_gid = -1; settings.create_for_uid = -1; settings.create_for_gid = -1; settings.mntsrc = NULL; settings.mntdest = NULL; settings.create_policy = (getuid() == 0) ? CREATE_AS_USER : CREATE_AS_MOUNTER; settings.create_permchain = permchain_create(); settings.chown_policy = CHOWN_NORMAL; settings.chgrp_policy = CHGRP_NORMAL; settings.chmod_policy = CHMOD_NORMAL; settings.chmod_allow_x = 0; settings.xattr_policy = XATTR_READ_WRITE; settings.mirrored_users_only = 0; settings.mirrored_users = NULL; settings.num_mirrored_users = 0; settings.mirrored_members = NULL; settings.num_mirrored_members = 0; settings.ctime_from_mtime = 0; settings.hide_hard_links = 0; atexit(&atexit_func); /* Parse options */ if (fuse_opt_parse(&args, &od, options, &process_option) == -1) return 1; /* Check that a source directory and a mount point was given */ if (!settings.mntsrc || !settings.mntdest) { print_usage(my_basename(argv[0])); return 1; } /* Parse new owner and group */ if (od.user) { if (!user_uid(od.user, &settings.new_uid)) { fprintf(stderr, "Not a valid user ID: %s\n", od.user); return 1; } } if (od.group) { if (!group_gid(od.group, &settings.new_gid)) { fprintf(stderr, "Not a valid group ID: %s\n", od.group); return 1; } } /* Parse user and group for new creates */ if (od.create_for_user) { if (getuid() != 0) { fprintf(stderr, "Error: You need to be root to use --create-for-user !\n"); return 1; } if (!user_uid(od.create_for_user, &settings.create_for_uid)) { fprintf(stderr, "Not a valid user ID: %s\n", od.create_for_user); return 1; } } if (od.create_for_group) { if (getuid() != 0) { fprintf(stderr, "Error: You need to be root to use --create-for-group !\n"); return 1; } if (!group_gid(od.create_for_group, &settings.create_for_gid)) { fprintf(stderr, "Not a valid group ID: %s\n", od.create_for_group); return 1; } } /* Parse mirrored users and groups */ if (od.mirror && od.mirror_only) { fprintf(stderr, "Cannot specify both -m|--mirror and -M|--mirror-only\n"); return 1; } if (od.mirror_only) { settings.mirrored_users_only = 1; od.mirror = od.mirror_only; } if (od.mirror) { settings.num_mirrored_users = count_chars(od.mirror, ',') + count_chars(od.mirror, ':') + 1; settings.num_mirrored_members = ((*od.mirror == '@') ? 1 : 0) + count_substrs(od.mirror, ",@") + count_substrs(od.mirror, ":@"); settings.num_mirrored_users -= settings.num_mirrored_members; settings.mirrored_users = malloc(settings.num_mirrored_users*sizeof(uid_t)); settings.mirrored_members = malloc(settings.num_mirrored_members*sizeof(gid_t)); i = 0; /* iterate over mirrored_users */ j = 0; /* iterate over mirrored_members */ p = od.mirror; while (i < settings.num_mirrored_users || j < settings.num_mirrored_members) { tmpstr = strdup_until(p, ",:"); if (*tmpstr == '@') { /* This is a group name */ if (!group_gid(tmpstr + 1, &settings.mirrored_members[j++])) { fprintf(stderr, "Invalid group ID: '%s'\n", tmpstr + 1); free(tmpstr); return 1; } } else { if (!user_uid(tmpstr, &settings.mirrored_users[i++])) { fprintf(stderr, "Invalid user ID: '%s'\n", tmpstr); free(tmpstr); return 1; } } free(tmpstr); while (*p != '\0' && *p != ',' && *p != ':') ++p; if (*p != '\0') ++p; else { /* Done. The counters should match. */ assert(i == settings.num_mirrored_users); assert(j == settings.num_mirrored_members); } } } /* Parse permission bits */ if (od.perms) { if (add_chmod_rules_to_permchain(od.perms, settings.permchain) != 0) { fprintf(stderr, "Invalid permission specification: '%s'\n", od.perms); return 1; } } if (od.create_with_perms) { if (add_chmod_rules_to_permchain(od.create_with_perms, settings.create_permchain) != 0) { fprintf(stderr, "Invalid permission specification: '%s'\n", od.create_with_perms); return 1; } } /* Add default fuse options */ if (!od.no_allow_other) { fuse_opt_add_arg(&args, "-oallow_other"); } /* We want the kernel to do our access checks for us based on what getattr gives it. */ fuse_opt_add_arg(&args, "-odefault_permissions"); /* We need to disable the attribute cache whenever two users can see different attributes. For now, only mirroring can do that. */ if (is_mirroring_enabled()) { fuse_opt_add_arg(&args, "-oattr_timeout=0"); } /* If the mount source and destination directories are the same then don't require that the directory be empty. */ if (strcmp(settings.mntsrc, settings.mntdest) == 0) fuse_opt_add_arg(&args, "-ononempty"); /* Open mount source for chrooting in bindfs_init */ settings.mntsrc_fd = open(settings.mntsrc, O_RDONLY); if (settings.mntsrc_fd == -1) { fprintf(stderr, "Could not open source directory\n"); return 1; } /* Ignore the umask of the mounter on file creation */ umask(0); /* Remove xattr implementation if the user doesn't want it */ if (settings.xattr_policy == XATTR_UNIMPLEMENTED) { bindfs_oper.setxattr = NULL; bindfs_oper.getxattr = NULL; bindfs_oper.listxattr = NULL; bindfs_oper.removexattr = NULL; } fuse_main_return = fuse_main(args.argc, args.argv, &bindfs_oper); fuse_opt_free_args(&args); close(settings.mntsrc_fd); return fuse_main_return; }