#include "defs.h" #include #include #include #include #include #include #include "proc.h" #include "dirinfo.h" #include #include #include "filter.h" struct tagfs_private_data { struct dirinfo mntdir; struct dirinfo fsdir; struct dirinfo tagdir; struct dirinfo filterdir; }; #define TAGFS_DATA ((struct tagfs_private_data*)fuse_get_context()->private_data) int tagfs_op_open(const char* path, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "open(%s)\n", path); if (*path == '\0') return -EACCES; int fd; if ((fd = openat(TAGFS_DATA->mntdir.fd, path + 1, fi->flags)) == -1) return -errno; fi->fh = fd; return 0; } int tagfs_op_create(const char* path, mode_t mode, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "create(%s)\n", path); int fd = -1; int flags = O_CREAT|O_RDWR; if (strrchr(path, '/') == path) { fd = openat(TAGFS_DATA->mntdir.fd, path + 1, flags, mode); } else if (strncmp(path, "/" TAGFS_FILTER_DIR, sizeof("/" TAGFS_FILTER_DIR) - 1) == 0) { const char* p = strrchr(path, '/'); fd = openat(TAGFS_DATA->filterdir.fd, p + 1, flags, 0644); } if (fd == -1) return -1; fi->fh = fd; return 0; } int tagfs_op_rename(const char* oldpath, const char* newpath, unsigned int flags) { (void) flags; return renameat(TAGFS_DATA->mntdir.fd, oldpath + 1, TAGFS_DATA->mntdir.fd, newpath + 1); } int tagfs_op_mkdir(const char* path, mode_t mode) { fuse_log(FUSE_LOG_DEBUG, "mkdir(%s)\n", path); if (strncmp(path, "/" TAGFS_TAG_DIR, sizeof("/" TAGFS_TAG_DIR) - 1) != 0) return -1; const char* p = strrchr(path, '/'); return mkdirat(TAGFS_DATA->tagdir.fd, p + 1, mode); } int tagfs_op_rmdir(const char* path) { fuse_log(FUSE_LOG_DEBUG, "rmdir(%s)\n", path); if (strncmp(path, "/" TAGFS_TAG_DIR, sizeof("/" TAGFS_TAG_DIR) - 1) != 0) return -1; const char* p = strrchr(path, '/'); return unlinkat(TAGFS_DATA->tagdir.fd, p + 1, AT_REMOVEDIR); } int tagfs_op_release(const char* path, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "release(%s)\n", path); if (fi) close(fi->fh); return 0; } int tagfs_op_read(const char* path, char* buf, size_t size, off_t off, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "read(%s)\n", path); ssize_t r = pread(fi->fh, buf, size, off); return r; } int tagfs_op_write(const char* path, const char* buf, size_t size, off_t off, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "write(%s)\n", path); ssize_t r = pwrite(fi->fh, buf, size, off); return r; } void remove_from_tags(const char* name) { rewinddir(TAGFS_DATA->tagdir.dp); struct dirent* tagent; while ((tagent = readdir(TAGFS_DATA->tagdir.dp))) { int fd = openat(TAGFS_DATA->tagdir.fd, tagent->d_name, O_DIRECTORY|O_RDONLY); if (fd == -1) continue; DIR* dp = fdopendir(fd); struct dirent* ent; while ((ent = readdir(dp))) { if (strcmp(name, ent->d_name) == 0) unlinkat(fd, ent->d_name, 0); } closedir(dp); close(fd); } } int tagfs_op_unlink(const char* path) { fuse_log(FUSE_LOG_DEBUG, "unlink(%s)\n", path); if (strrchr(path, '/') == path) { remove_from_tags(path + 1); } unlinkat(TAGFS_DATA->mntdir.fd, path + 1, 0); return 0; } void fill_readdir(struct dirinfo* dir, void* buf, fuse_fill_dir_t filler, int skip_dirs) { rewinddir(dir->dp); struct dirent* ent; while ((ent = readdir(dir->dp))) { struct stat stbuf; if (fstatat(dir->fd, ent->d_name, &stbuf, 0) == 0 && skip_dirs) { if ((stbuf.st_mode & S_IFDIR) == S_IFDIR) continue; } filler(buf, ent->d_name, &stbuf, 0, 0); } } /* Removes filter files for dead processes, run at access and readdir */ void remove_dead_filters(void) { DIR* dp = TAGFS_DATA->filterdir.dp; rewinddir(dp); struct dirent* ent; while ((ent = readdir(dp))) { char* end; int pid = strtol(ent->d_name, &end, 10); if (end && *end == 0 && !is_pid_alive(pid)) { unlinkat(TAGFS_DATA->filterdir.fd, ent->d_name, 0); } } } int open_process_filter(unsigned pid) { char path[PATH_MAX]; snprintf(path, sizeof path, "%u", pid); return openat(TAGFS_DATA->filterdir.fd, path, O_RDONLY); } int fill_readdir_filtered(int filterfd, void* buf, fuse_fill_dir_t filler) { struct filter filter = { 0 }; if (!load_filter_tags(filterfd, &filter)) { fuse_log(FUSE_LOG_DEBUG, "load_tags failed\n"); filter_free(&filter); return 0; } if (!load_filter_files(&filter, &TAGFS_DATA->tagdir)) { fuse_log(FUSE_LOG_DEBUG, "load_files failed\n"); filter_free(&filter); return 0; } int include_count = filter_include_count(&filter); int exclude_count = filter_exclude_count(&filter); if (include_count > 0) { for (size_t i = 0; i < filter.files.included.len; ++i) { fuse_log(FUSE_LOG_DEBUG, "- %s\n", filter.files.included.ptr[i]); if (filter.files.included.in[i] != include_count) continue; int skip = 0; for (size_t j = 0; j < filter.files.excluded.len; ++j) { skip = strcmp(filter.files.included.ptr[i], filter.files.excluded.ptr[j]) == 0; } if (skip) continue; filler(buf, filter.files.included.ptr[i], NULL, 0, 0); } } else if (exclude_count > 0) { int fd = TAGFS_DATA->mntdir.fd; DIR* dp = TAGFS_DATA->mntdir.dp; rewinddir(dp); struct dirent* ent; while ((ent = readdir(dp))) { struct stat stbuf; if (fstatat(fd, ent->d_name, &stbuf, 0) == 0 && (stbuf.st_mode & S_IFDIR) == S_IFDIR) continue; int skip = 0; for (size_t i = 0; i < filter.files.excluded.len; ++i) { if ((skip = strcmp(ent->d_name, filter.files.excluded.ptr[i]) == 0)) break; } if (skip) continue; filler(buf, ent->d_name, NULL, 0, 0); } } filter_free(&filter); return 1; } int tagfs_op_readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t off, struct fuse_file_info* fi, enum fuse_readdir_flags flags) { fuse_log(FUSE_LOG_DEBUG, "readdir(%s)\n", path); remove_dead_filters(); if (strcmp(path, "/") == 0) { unsigned pid = fuse_get_context()->pid; int fd = open_process_filter(pid); while (fd == -1 && pid != 1) { pid = proc_get_ppid(pid); fd = open_process_filter(pid); } if (fd == -1) { filler(buf, TAGFS_DIR, NULL, 0, 0); fill_readdir(&TAGFS_DATA->mntdir, buf, filler, 1); } else { filler(buf, TAGFS_DIR, NULL, 0, 0); if (!fill_readdir_filtered(fd, buf, filler)) close(fd); } } else if (strcmp(path, "/" TAGFS_DIR) == 0) { fill_readdir(&TAGFS_DATA->fsdir, buf, filler, 0); } else if (strcmp(path, "/" TAGFS_TAG_DIR) == 0) { fill_readdir(&TAGFS_DATA->tagdir, buf, filler, 0); } else if (strncmp(path, "/" TAGFS_TAG_DIR, sizeof("/" TAGFS_TAG_DIR) - 1) == 0) { const char* rpath = strrchr(path, '/') + 1; int fd = openat(TAGFS_DATA->tagdir.fd, rpath, O_DIRECTORY|O_RDONLY); if (fd == -1) return -1; DIR* dp = fdopendir(fd); struct dirinfo dir = { fd, dp, (struct stat){0} }; fill_readdir(&dir, buf, filler, 0); closedir(dp); close(fd); } else if (strcmp(path, "/" TAGFS_FILTER_DIR) == 0) { fill_readdir(&TAGFS_DATA->filterdir, buf, filler, 0); } return 0; } int tagfs_op_readlink(const char* path, char* buf, size_t length) { fuse_log(FUSE_LOG_DEBUG, "readlink(%s)\n", path); if (strncmp(path, "/" TAGFS_TAG_DIR, sizeof("/" TAGFS_TAG_DIR) - 1) != 0) return -1; const char* rpath = &path[sizeof("/" TAGFS_TAG_DIR)]; fuse_log(FUSE_LOG_DEBUG, "- rpath(%s)\n", rpath); readlinkat(TAGFS_DATA->tagdir.fd, rpath, buf, length); return 0; } int tagfs_op_symlink(const char* target, const char* path) { fuse_log(FUSE_LOG_DEBUG, "symlink(%s, %s)\n", target, path); if (strncmp(path, "/" TAGFS_TAG_DIR, sizeof("/" TAGFS_TAG_DIR) - 1) != 0) return -1; const char* rpath = &path[sizeof("/" TAGFS_TAG_DIR)]; symlinkat(target, TAGFS_DATA->tagdir.fd, rpath); return 0; } int tagfs_op_getattr(const char* path, struct stat* st, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "getattr(%s)\n", path); if (strcmp(path, "/") == 0) { *st = TAGFS_DATA->mntdir.stat; return 0; } struct stat stbuf; if (fstatat(TAGFS_DATA->mntdir.fd, path + 1, &stbuf, 0) == -1) return -errno; *st = stbuf; return 0; } int tagfs_op_access(const char* path, int mask) { fuse_log(FUSE_LOG_DEBUG, "access(%s)\n", path); if (strcmp(path, "/") == 0) { return faccessat(TAGFS_DATA->mntdir.fd, ".", mask, 0); } return faccessat(TAGFS_DATA->mntdir.fd, path + 1, mask, 0); } void init_tagfs_dir(int dirfd) { mode_t mode = 0774; mkdirat(dirfd, TAGFS_DIR, mode); mkdirat(dirfd, TAGFS_TAG_DIR, mode); mkdirat(dirfd, TAGFS_FILTER_DIR, mode); } int main(int argc, char** argv) { struct fuse_operations tagfs_ops = { .open = tagfs_op_open, .release = tagfs_op_release, .read = tagfs_op_read, .write = tagfs_op_write, .create = tagfs_op_create, .rename = tagfs_op_rename, .mkdir = tagfs_op_mkdir, .rmdir = tagfs_op_rmdir, .unlink = tagfs_op_unlink, .readdir = tagfs_op_readdir, .readlink= tagfs_op_readlink, .symlink = tagfs_op_symlink, /*.chmod = tagfs_op_chmod, .chown = tagfs_op_chown,*/ .access = tagfs_op_access, .getattr = tagfs_op_getattr, }; const char* mntpoint = argv[argc - 1]; struct tagfs_private_data data; if (!open_dirinfo(mntpoint, &data.mntdir)) err(1, "open_dirinfo(mntpoint)"); init_tagfs_dir(data.mntdir.fd); if (!openat_dirinfo(data.mntdir.fd, TAGFS_DIR, &data.fsdir)) err(1, "openat_dirinfo(fsdir)"); if (!openat_dirinfo(data.mntdir.fd, TAGFS_TAG_DIR, &data.tagdir)) err(1, "openat_dirinfo(tagdir)"); if (!openat_dirinfo(data.mntdir.fd, TAGFS_FILTER_DIR, &data.filterdir)) err(1, "openat_dirinfo(filterdir)"); // FIXME: check if mount point is availeble, otherwise exit fuse_main(argc, argv, &tagfs_ops, &data); return 0; }