/* afuse - An automounter using FUSE Copyright (C) 2006 Jacob Bower Portions of this program derive from examples provided with FUSE-2.5.2. This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ #include #ifdef linux // For pread()/pwrite() #define _XOPEN_SOURCE 500 #endif #include #include // for mkdtemp #define __USE_BSD #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SETXATTR #include #endif #include "fd_list.h" #include "dir_list.h" #include "utils.h" #include "variable_pairing_heap.h" #define TMP_DIR_TEMPLATE "/tmp/afuse-XXXXXX" char *mount_point_directory; dev_t mount_point_dev; // Data structure filled in when parsing command line args struct user_options_t { char *mount_command_template; char *unmount_command_template; bool flush_writes; uint64_t auto_unmount_delay; } user_options = {NULL, NULL, false, UINT64_MAX}; typedef struct _mount_list_t { struct _mount_list_t *next; struct _mount_list_t *prev; char *root_name; char *mount_point; fd_list_t *fd_list; dir_list_t *dir_list; PH_NEW_LINK(struct _mount_list_t) auto_unmount_ph_node; /* This is the sort key for the auto_unmount_ph heap. It will equal UINT64_MAX if this node is not in the heap. */ int64_t auto_unmount_time; } mount_list_t; PH_DECLARE_TYPE(auto_unmount_ph, mount_list_t) PH_DEFINE_TYPE(auto_unmount_ph, mount_list_t, auto_unmount_ph_node, auto_unmount_time) #define BLOCK_SIGALRM \ sigset_t block_sigalrm_oldset, block_sigalrm_set; \ sigemptyset(&block_sigalrm_set); \ sigaddset(&block_sigalrm_set, SIGALRM); \ sigprocmask(SIG_BLOCK, &block_sigalrm_set, &block_sigalrm_oldset) #define UNBLOCK_SIGALRM \ sigprocmask(SIG_SETMASK, &block_sigalrm_oldset, NULL) auto_unmount_ph_t auto_unmount_ph; int64_t auto_unmount_next_timeout = INT64_MAX; static int64_t from_timeval(const struct timeval *tv) { return (int64_t)tv->tv_sec * 1000000 + ((int64_t)tv->tv_usec); } static void to_timeval(struct timeval *tv, int64_t usec) { tv->tv_sec = usec / 1000000; tv->tv_usec = usec % 1000000; } static int get_retval(int res) { if (res == -1) return -errno; return 0; } static int check_mount(mount_list_t *mount) { struct stat buf; if (lstat(mount->mount_point, &buf) == -1) return 0; if (buf.st_dev == mount_point_dev) return 0; return 1; } static void update_auto_unmount(mount_list_t *mount) { if (user_options.auto_unmount_delay == UINT64_MAX) return; /* Get the current time */ struct timeval tv; mount_list_t *min_mount; int64_t cur_time, next_time; gettimeofday(&tv, NULL); cur_time = from_timeval(&tv); if (mount) { /* Always remove first */ if (mount->auto_unmount_time != INT64_MAX) auto_unmount_ph_remove(&auto_unmount_ph, mount); if (!mount->fd_list && !mount->dir_list) { mount->auto_unmount_time = cur_time + user_options.auto_unmount_delay; auto_unmount_ph_insert(&auto_unmount_ph, mount); } else { mount->auto_unmount_time = INT64_MAX; } } min_mount = auto_unmount_ph_min(&auto_unmount_ph); next_time = min_mount ? min_mount->auto_unmount_time : INT64_MAX; if (next_time != auto_unmount_next_timeout) { struct itimerval itv; auto_unmount_next_timeout = next_time; if (next_time != INT64_MAX) { if (next_time > cur_time) to_timeval(&itv.it_value, next_time - cur_time); else /* Timer is set to expire immediately --- set it to 1 instead*/ to_timeval(&itv.it_value, 1); } else { /* Disable the timer */ to_timeval(&itv.it_value, 0); } to_timeval(&itv.it_interval, 0); if (setitimer(ITIMER_REAL, &itv, NULL) != 0) { perror("Error setting timer"); } } } static void handle_auto_unmount_timer(int x) { /* Get the current time */ struct timeval tv; int64_t cur_time; mount_list_t *mount; gettimeofday(&tv, NULL); cur_time = from_timeval(&tv); while ((mount = auto_unmount_ph_min(&auto_unmount_ph)) != NULL && mount->auto_unmount_time <= cur_time) { do_umount(mount); } update_auto_unmount(NULL); } mount_list_t *mount_list = NULL; mount_list_t *find_mount(const char *root_name) { mount_list_t *current_mount = mount_list; while(current_mount) { if( strcmp(root_name, current_mount->root_name) == 0) return current_mount; current_mount = current_mount->next; } return NULL; } int is_mount(const char *root_name) { return find_mount(root_name) ? 1 : 0; } mount_list_t *add_mount(const char *root_name, char *mount_point) { mount_list_t *new_mount; new_mount = (mount_list_t *)my_malloc( sizeof(mount_list_t) ); new_mount->root_name = my_strdup(root_name); new_mount->mount_point = mount_point; new_mount->next = mount_list; new_mount->prev = NULL; new_mount->fd_list = NULL; new_mount->dir_list = NULL; new_mount->auto_unmount_time = INT64_MAX; if(mount_list) mount_list->prev = new_mount; mount_list = new_mount; update_auto_unmount(new_mount); return new_mount; } void remove_mount(mount_list_t *current_mount) { if (current_mount->auto_unmount_time != INT64_MAX) auto_unmount_ph_remove(&auto_unmount_ph, current_mount); free(current_mount->root_name); free(current_mount->mount_point); if(current_mount->prev) current_mount->prev->next = current_mount->next; else mount_list = current_mount->next; if(current_mount->next) current_mount->next->prev = current_mount->prev; free(current_mount); update_auto_unmount(NULL); } char *make_mount_point(const char *root_name) { char *dir_tmp; int i; // Create the mount point dir_tmp = my_malloc(strlen(mount_point_directory) + 2 + strlen(root_name)); strcpy(dir_tmp, mount_point_directory); strcat(dir_tmp, "/"); strcat(dir_tmp, root_name); if(mkdir(dir_tmp, 0700) == -1 && errno != EEXIST) { fprintf(stderr, "Cannot create directory: %s (%s)\n", dir_tmp, strerror(errno)); free(dir_tmp); return NULL; } return dir_tmp; } // !!FIXME!! allow escaping of %'s // Note: this method strips out quotes and applies them itself as should be appropriate char *expand_template(const char *template, const char *mount_point, const char *root_name) { int len = 0; int i; char *expanded_name; char *expanded_name_start; // calculate length for(i = 0; template[i]; i++) if(template[i] == '%') { switch(template[i + 1]) { case 'm': len += strlen(mount_point) + 2; i++; break; case 'r': len += strlen(root_name) + 2; i++; break; } } else if(template[i] != '"') len++; expanded_name_start = expanded_name = my_malloc(len + 1); for(i = 0; template[i]; i++) if(template[i] == '%') { int j = 0; switch(template[i + 1]) { case 'm': *expanded_name++ = '"'; while(mount_point[j]) *expanded_name++ = mount_point[j++]; *expanded_name++ = '"'; i++; break; case 'r': *expanded_name++ = '"'; while(root_name[j]) *expanded_name++ = root_name[j++]; *expanded_name++ = '"'; i++; break; } } else if(template[i] != '"') *expanded_name++ = template[i]; *expanded_name = '\0'; return expanded_name_start; } mount_list_t *do_mount(const char *root_name) { char *mount_point; char *mount_command; mount_list_t *mount; int sysret; fprintf(stderr, "Mounting: %s\n", root_name); if( !(mount_point = make_mount_point(root_name)) ) { fprintf(stderr, "Failed to create mount point directory: %s/%s\n", mount_point_directory, root_name); return NULL; } mount_command = expand_template(user_options.mount_command_template, mount_point, root_name); sysret = system(mount_command); fprintf(stderr, "sysret: %.8x\n", sysret); if(sysret) { fprintf(stderr, "Failed to invoke mount command: '%s' (%s)\n", mount_command, sysret != -1 ? "Error executing mount" : strerror(errno)); // remove the now unused directory if( rmdir(mount_point) == -1 ) fprintf(stderr, "Failed to remove mount point dir: %s (%s)", mount_point, strerror(errno)); free(mount_command); free(mount_point); return NULL; } mount = add_mount(root_name, mount_point); free(mount_command); return mount; } int do_umount(mount_list_t *mount) { char *unmount_command; int sysret; fprintf(stderr, "Unmounting: %s\n", mount->root_name); unmount_command = expand_template(user_options.unmount_command_template, mount->mount_point, mount->root_name); sysret = system(unmount_command); if(sysret) { fprintf(stderr, "Failed to invoke unmount command: '%s' (%s)\n", unmount_command, sysret != -1 ? "Error executing mount" : strerror(errno)); /* Still unmount anyway */ } if( rmdir(mount->mount_point) == -1 ) fprintf(stderr, "Failed to remove mount point dir: %s (%s)", mount->mount_point, strerror(errno)); remove_mount(mount); free(unmount_command); return 1; } void unmount_all(void) { fprintf(stderr, "Attemping to unmount all filesystems:\n"); while(mount_list) { fprintf(stderr, "\tUnmounting: %s\n", mount_list->root_name); do_umount(mount_list); } fprintf(stderr, "done.\n"); } void shutdown(void) { BLOCK_SIGALRM; unmount_all(); UNBLOCK_SIGALRM; if(rmdir(mount_point_directory) == -1) fprintf(stderr, "Failed to remove temporary mount point directory: %s (%s)\n", mount_point_directory, strerror(errno)); } int max_path_out_len(const char *path_in) { return strlen(mount_point_directory) + strlen(path_in) + 2; } // returns true if path is the a child directory of a root node // e.g. /a/b is a child, /a is not. int extract_root_name(const char *path, char *root_name) { int i; int is_child; for(i = 1; path[i] && path[i] != '/'; i++) root_name[i - 1] = path[i]; root_name[i - 1] = '\0'; return strlen(&path[i]); } typedef enum {PROC_PATH_FAILED, PROC_PATH_ROOT_DIR, PROC_PATH_PROXY_DIR} proc_result_t; proc_result_t process_path(const char *path_in, char *path_out, char *root_name, int attempt_mount, mount_list_t **out_mount) { int i; char *path_out_base; int is_child; int len; mount_list_t *mount = NULL; *out_mount = NULL; fprintf(stderr, "Path in: %s\n", path_in); is_child = extract_root_name(path_in, root_name); fprintf(stderr, "root_name is: %s\n", root_name); // Mount filesystem if neccessary // the combination of is_child and attempt_mount prevent inappropriate // mounting of a filesystem for example if the user tries to mknod // in the afuse root this should cause an error not a mount. // !!FIXME!! this is broken on FUSE < 2.5 (?) because a getattr // on the root node seems to occur with every single access. if( /*(is_child || attempt_mount ) && */ strlen(root_name) > 0 && !(mount = find_mount(root_name)) && !(mount = do_mount(root_name))) return PROC_PATH_FAILED; if (mount && !check_mount(mount)) { do_umount(mount); mount = do_mount(root_name); if (!mount) return PROC_PATH_FAILED; } // construct path_out (mount_point_directory + '/' + path_in + '\0') path_out_base = path_out; len = strlen(mount_point_directory); memcpy(path_out, mount_point_directory, len); path_out += len; *path_out++ = '/'; len = strlen(path_in) - 1; memcpy(path_out, path_in + 1, len); path_out += len; *path_out = '\0'; fprintf(stderr, "Path out: %s\n", path_out_base); *out_mount = mount; return strlen(root_name) ? PROC_PATH_PROXY_DIR : PROC_PATH_ROOT_DIR; } static int afuse_getattr(const char *path, struct stat *stbuf) { int res; char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); int retval; mount_list_t *mount; BLOCK_SIGALRM; fprintf(stderr, "> GetAttr\n"); switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: fprintf(stderr, "Getattr on: (%s) - %s\n", path, root_name); if(mount || strlen(root_name) == 0) { stbuf->st_mode = S_IFDIR | 0700; stbuf->st_nlink = 1; stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); stbuf->st_size = 0; stbuf->st_blksize = 0; stbuf->st_blocks = 0; stbuf->st_atime = 0; stbuf->st_mtime = 0; stbuf->st_ctime = 0; retval = 0; } else retval = -ENOENT; break; case PROC_PATH_PROXY_DIR: retval = get_retval(lstat(real_path, stbuf)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_readlink(const char *path, char *buf, size_t size) { int res; char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); int retval; mount_list_t *mount; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOENT; break; case PROC_PATH_PROXY_DIR: res = readlink(real_path, buf, size - 1); if (res == -1) { retval = -errno; break; } buf[res] = '\0'; retval = 0; break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_opendir(const char *path, struct fuse_file_info *fi) { DIR *dp; char *root_name = alloca( strlen(path) ); mount_list_t *mount; char *real_path = alloca( max_path_out_len(path) ); int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = 0; break; case PROC_PATH_PROXY_DIR: dp = opendir(real_path); if (dp == NULL) { retval = -errno; break; } fi->fh = (unsigned long) dp; if(mount) dir_list_add(&mount->dir_list, dp); retval = 0; break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static inline DIR *get_dirp(struct fuse_file_info *fi) { return (DIR *) (uintptr_t) fi->fh; } static int afuse_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; char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount, *next; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); for(mount = mount_list; mount; mount = next) { next = mount->next; /* Check for dead mounts. */ if (!check_mount(mount)) do_umount(mount); else filler(buf, mount->root_name, NULL, 0); } mount = NULL; retval = 0; break; case PROC_PATH_PROXY_DIR: 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; } retval = 0; break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_releasedir(const char *path, struct fuse_file_info *fi) { DIR *dp = get_dirp(fi); mount_list_t *mount; char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = 0; break; case PROC_PATH_PROXY_DIR: if (mount) dir_list_remove(&mount->dir_list, dp); closedir(dp); retval = 0; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_mknod(const char *path, mode_t mode, dev_t rdev) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; fprintf(stderr, "> Mknod\n"); switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: if (S_ISFIFO(mode)) retval = get_retval(mkfifo(real_path, mode)); else retval = get_retval(mknod(real_path, mode, rdev)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_mkdir(const char *path, mode_t mode) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); int retval; mount_list_t *mount; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(mkdir(real_path, mode)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_unlink(const char *path) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(unlink(real_path)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_rmdir(const char *path) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: if (!extract_root_name(path, root_name)) { /* Unmount */ if (mount->dir_list || mount->fd_list) retval = -EBUSY; else { do_umount(mount); mount = NULL; retval = 0; } } else retval = get_retval(rmdir(real_path)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_symlink(const char *from, const char *to) { char *root_name_to = alloca( strlen(to) ); char *real_to_path = alloca( max_path_out_len(to) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(to, real_to_path, root_name_to, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(symlink(from, real_to_path)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_rename(const char *from, const char *to) { char *root_name_from = alloca( strlen(from) ); char *root_name_to = alloca( strlen(to) ); char *real_from_path = alloca( max_path_out_len(from) ); char *real_to_path = alloca( max_path_out_len(to) ); mount_list_t *mount_from, *mount_to = NULL; int retval; BLOCK_SIGALRM; switch( process_path(from, real_from_path, root_name_from, 0, &mount_from) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: switch( process_path(to, real_to_path, root_name_to, 0, &mount_to) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(rename(real_from_path, real_to_path)); break; } break; } if (mount_to) update_auto_unmount(mount_to); if (mount_from && mount_from != mount_to) update_auto_unmount(mount_from); UNBLOCK_SIGALRM; return retval; } static int afuse_link(const char *from, const char *to) { char *root_name_from = alloca( strlen(from) ); char *root_name_to = alloca( strlen(to) ); char *real_from_path = alloca( max_path_out_len(from) ); char *real_to_path = alloca( max_path_out_len(to) ); mount_list_t *mount_to = NULL, *mount_from; int retval; BLOCK_SIGALRM; switch( process_path(from, real_from_path, root_name_from, 0, &mount_from) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: switch( process_path(to, real_to_path, root_name_to, 0, &mount_to) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(link(real_from_path, real_to_path)); break; } break; } if (mount_to) update_auto_unmount(mount_to); if (mount_from && mount_from != mount_to) update_auto_unmount(mount_from); UNBLOCK_SIGALRM; return retval; } static int afuse_chmod(const char *path, mode_t mode) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(chmod(real_path, mode)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_chown(const char *path, uid_t uid, gid_t gid) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(lchown(real_path, uid, gid)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_truncate(const char *path, off_t size) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(truncate(real_path, size)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_utime(const char *path, struct utimbuf *buf) { int res; char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(utime(real_path, buf)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_open(const char *path, struct fuse_file_info *fi) { int fd; char *root_name = alloca( strlen(path) ); mount_list_t *mount; char *real_path = alloca( max_path_out_len(path) ); int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOENT; break; case PROC_PATH_PROXY_DIR: fd = open(real_path, fi->flags); if (fd == -1) { retval = -errno; break; } fi->fh = fd; if(mount) fd_list_add(&mount->fd_list, fd); retval = 0; break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_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 afuse_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; if(user_options.flush_writes) fsync(fi->fh); return res; } static int afuse_release(const char *path, struct fuse_file_info *fi) { char *root_name = alloca( strlen(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; extract_root_name(path, root_name); mount = find_mount(root_name); retval = get_retval(close(fi->fh)); if(mount) { fd_list_remove(&mount->fd_list, fi->fh); update_auto_unmount(mount); } UNBLOCK_SIGALRM; return retval; } static int afuse_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); return get_retval(res); } #if FUSE_VERSION >= 25 static int afuse_access(const char *path, int mask) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: case PROC_PATH_PROXY_DIR: retval = get_retval(access(real_path, mask)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) { (void) path; return get_retval(ftruncate(fi->fh, size)); } static int afuse_create(const char *path, mode_t mode, struct fuse_file_info *fi) { int fd; char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: fd = open(real_path, fi->flags, mode); if (fd == -1) { retval = -errno; break; } fi->fh = fd; retval = 0; break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { (void) path; return get_retval(fstat(fi->fh, stbuf)); } #endif #if FUSE_VERSION >= 25 static int afuse_statfs(const char *path, struct statvfs *stbuf) #else static int afuse_statfs(const char *path, struct statfs *stbuf) #endif { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: #if FUSE_VERSION >= 25 stbuf->f_namemax = 0x7fffffff; stbuf->f_frsize = 512; #else stbuf->f_namelen = 0x7fffffff; #endif stbuf->f_bsize = 1024; stbuf->f_blocks = 0; stbuf->f_bfree = 0; stbuf->f_bavail = 0; stbuf->f_files = 0; stbuf->f_ffree = 0; retval = 0; break; case PROC_PATH_PROXY_DIR: retval = get_retval(statvfs(real_path, stbuf)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } void afuse_destroy(void *p) { shutdown(); } #ifdef HAVE_SETXATTR /* xattr operations are optional and can safely be left unimplemented */ static int afuse_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOENT; break; case PROC_PATH_PROXY_DIR: retval = get_retval(lsetxattr(real_path, name, value, size, flags)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_getxattr(const char *path, const char *name, char *value, size_t size) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(lgetxattr(real_path, name, value, size)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_listxattr(const char *path, char *list, size_t size) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 1, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(llistxattr(real_path, list, size)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } static int afuse_removexattr(const char *path, const char *name) { char *root_name = alloca( strlen(path) ); char *real_path = alloca( max_path_out_len(path) ); mount_list_t *mount; int retval; BLOCK_SIGALRM; switch( process_path(path, real_path, root_name, 0, &mount) ) { case PROC_PATH_FAILED: retval = -ENXIO; break; case PROC_PATH_ROOT_DIR: retval = -ENOTSUP; break; case PROC_PATH_PROXY_DIR: retval = get_retval(lremovexattr(real_path, name)); break; } if (mount) update_auto_unmount(mount); UNBLOCK_SIGALRM; return retval; } #endif /* HAVE_SETXATTR */ static struct fuse_operations afuse_oper = { .getattr = afuse_getattr, .readlink = afuse_readlink, .opendir = afuse_opendir, .readdir = afuse_readdir, .releasedir = afuse_releasedir, .mknod = afuse_mknod, .mkdir = afuse_mkdir, .symlink = afuse_symlink, .unlink = afuse_unlink, .rmdir = afuse_rmdir, .rename = afuse_rename, .link = afuse_link, .chmod = afuse_chmod, .chown = afuse_chown, .truncate = afuse_truncate, .utime = afuse_utime, .open = afuse_open, .read = afuse_read, .write = afuse_write, .release = afuse_release, .fsync = afuse_fsync, .statfs = afuse_statfs, #if FUSE_VERSION >= 25 .access = afuse_access, .create = afuse_create, .ftruncate = afuse_ftruncate, .fgetattr = afuse_fgetattr, #endif .destroy = afuse_destroy, #ifdef HAVE_SETXATTR .setxattr = afuse_setxattr, .getxattr = afuse_getxattr, .listxattr = afuse_listxattr, .removexattr = afuse_removexattr, #endif }; enum { KEY_HELP, KEY_FLUSHWRITES }; #define AFUSE_OPT(t, p, v) { t, offsetof(struct user_options_t, p), v } static struct fuse_opt afuse_opts[] = { AFUSE_OPT("mount_template=%s", mount_command_template, 0), AFUSE_OPT("unmount_template=%s", unmount_command_template, 0), AFUSE_OPT("timeout=%Lu", auto_unmount_delay, 0), FUSE_OPT_KEY("flushwrites", KEY_FLUSHWRITES), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_END }; static void usage(const char *progname) { fprintf(stderr, "Usage: %s mountpoint [options]\n" "\n" " -o opt,[opt...] mount options\n" " -h --help print help\n" " -V --version print FUSE version information\n" "\n" "afuse options:\n" " -o mount_template=CMD template for CMD to execute to mount (*)\n" " -o unmount_template=CMD template for CMD to execute to unmount (*) (**)\n" " -o timeout=TIMEOUT automatically unmount after TIMEOUT microseconds\n" " -o flushwrites flushes data to disk for all file writes\n" "\n\n" " (*) - When executed, %%r and %%m are expanded in templates to the root\n" " directory name for the new mount point, and the actual directory to\n" " mount onto respectively to mount onto. Both templates are REQUIRED.\n" "\n" " (**) - The unmount command must perform a lazy unmount operation. E.g. the\n" " -u -z options to fusermount, or -l for regular mount.\n" "\n", progname); } static int afuse_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { (void) arg; switch(key) { case KEY_HELP: usage(outargs->argv[0]); fuse_opt_add_arg(outargs, "-ho"); fuse_main(outargs->argc, outargs->argv, &afuse_oper); exit(1); case KEY_FLUSHWRITES: user_options.flush_writes = true; return 0; default: return 1; } } int main(int argc, char *argv[]) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); char *temp_dir_name = my_malloc(strlen(TMP_DIR_TEMPLATE)); strcpy(temp_dir_name, TMP_DIR_TEMPLATE); if(fuse_opt_parse(&args, &user_options, afuse_opts, afuse_opt_proc) == -1) return 1; // !!FIXME!! force single-threading for now as datastructures are not locked fuse_opt_add_arg(&args, "-s"); auto_unmount_ph_init(&auto_unmount_ph); /** * Install the SIGALRM signal handler. */ { struct sigaction act; act.sa_handler = &handle_auto_unmount_timer; sigemptyset(&act.sa_mask); act.sa_flags = 0; while (sigaction(SIGALRM, &act, NULL) == -1 && errno == EINTR) continue; } // Check for required parameters if(!user_options.mount_command_template || !user_options.unmount_command_template) { fprintf(stderr, "(Un)Mount command templates missing.\n\n"); usage(argv[0]); fuse_opt_add_arg(&args, "-ho"); fuse_main(args.argc, args.argv, &afuse_oper); return 1; } if( !(mount_point_directory = mkdtemp(temp_dir_name)) ) { fprintf(stderr, "Failed to create temporary mount point dir.\n"); return 1; } { struct stat buf; if (lstat(mount_point_directory, &buf) == -1) { fprintf(stderr, "Failed to stat temporary mount point dir.\n"); return 1; } mount_point_dev = buf.st_dev; } umask(0); // !!FIXME!! death by signal doesn't unmount fs return fuse_main(args.argc, args.argv, &afuse_oper); } /* Local Variables: c-basic-offset: 4 indent-tabs-mode: t End: */