tagfs/src/tagfs.c

451 lines
9.6 KiB
C

#include "defs.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <unistd.h>
#include "proc.h"
#include "dirinfo.h"
#include <fuse.h>
#include <fuse_log.h>
#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;
}