Initial commit

This commit is contained in:
dan 2020-07-09 00:42:07 +02:00
commit a93e4ab988
8 changed files with 946 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
tagfs
zdir

20
makefile Normal file
View File

@ -0,0 +1,20 @@
.POSIX:
.PHONY: clean install
PREFIX=/usr/local
CC=gcc
CFLAGS=-Wall -Wextra -Wshadow -Wpedantic -std=c11 -O0 -g -Wno-unused-parameter
LDFLAGS=$(shell pkg-config --libs --cflags fuse3)
SRC=$(wildcard src/*.c)
tagfs: $(SRC)
$(CC) $(LDFLAGS) $? $(CFLAGS) -o $@
clean:
rm tagfs
install: tagfs
cp $? $(PREFIX)/bin/

101
shell/tagfs.sh Normal file
View File

@ -0,0 +1,101 @@
tagfs_nofs()
{
echo "No tagfs filesystem found in `pwd`"
}
tagfs_exists()
{
[ -d .tagfs ] && return 1 || return 0
}
tagfs_clear_filter()
{
rm .tagfs/filters/$$ 2> /dev/null
return 0
}
tagfs_list_filter()
{
cat .tagfs/filters/$$ 2> /dev/null
return 0
}
tagfs_list_tags()
{
/bin/ls -1 .tagfs/tags/
}
tagfs_is_tag()
{
[ -n "$(echo $1 | egrep '^[-+]')" ] && return 1 || return 0
}
filter()
{
tagfs_exists
[ $? -eq 0 ] && tagfs_nofs && return
[ $# -eq 1 ] && [ "$1" = "-" ] && tagfs_clear_filter && return
filter_file=".tagfs/filters/$$"
tmp_file=".tagfs/filters/tmp_$$"
for t in "$@"; do
case "$(echo $t | cut -b1)" in
+) [ -z "$(fgrep $t $filter_file 2> /dev/null)" ] && echo "${t#+}" >> $filter_file ;;
-) sed -e "/${t#-}/d" $filter_file > $tmp_file && mv $tmp_file $filter_file
echo nigga;;
esac
done
tagfs_list_filter
}
tags()
{
tagfs_exists
[ $? -eq 0 ] && tagfs_nofs && return
[ $# -eq 0 ] && tagfs_list_tags && return
tagfs_is_tag "$1"
if [ $? -eq 1 ]; then
for tag in "$@"; do
[ "$tag" = "-" ] && continue
case "$(echo $tag | cut -b1)" in
+) mkdir .tagfs/tags/${tag#+} ;;
-) rm -r .tagfs/tags/${tag#-} ;;
esac
done
else
for tag in "$@"; do
[ "$tag" = "$1" ] && continue
[ "$tag" = "-" ] && rm $(find -name "$1") && continue
case "$(echo $tag | cut -b1)" in
+) ln -s ../../../"$1" .tagfs/tags/"${tag#+}"/"$1" ;;
-) rm .tagfs/tags/"${tag#-}"/"$1" ;;
esac
done
fi
}
# Bash completion
# _filter_completion()
# {
# filter_file=".tagfs/filters/$$"
# list=""
#
# for t in $(cat $filter_file 2> /dev/null); do
# list+="-$t "
# done
#
# for t in $(ls .tagfs/tags/); do
# [ -n "$(fgrep $t $filter_file 2> /dev/null)" ] && continue
# list+="+$(basename $t) "
# done
#
# COMPREPLY=($(compgen -W "$list" -- "${COMP_WORDS[$COMP_CWORD]}"))
# }
#
# complete -F _filter_completion filter
#

19
src/defs.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef H_DEFS
#define H_DEFS
/* fusefs version */
#define FUSE_USE_VERSION 39
/* posix */
#define _POSIX_C_SOURCE 200809
/* path constants */
#define TAGFS_DIR ".tagfs"
#define TAGFS_TAG_DIR TAGFS_DIR "/tags"
#define TAGFS_FILTER_DIR TAGFS_DIR "/filters"
/* size constants */
#define TAG_MAX (32)
#endif

67
src/dirinfo.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef H_DIRINFO
#define H_DIRINFO
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
struct dirinfo
{
int fd;
DIR* dp;
struct stat stat;
};
static inline void
close_dirinfo(struct dirinfo* dirinfo)
{
if (dirinfo->fd > 0)
close(dirinfo->fd);
if (dirinfo->dp)
closedir(dirinfo->dp);
}
static inline int
openat_dirinfo(int dirfd, const char* path, struct dirinfo* dirinfo)
{
if ((dirinfo->fd = openat(dirfd, path, O_DIRECTORY|O_RDONLY)) == -1)
goto err;
if ((dirinfo->dp = fdopendir(dirinfo->fd)) == NULL)
goto err;
if (fstat(dirinfo->fd, &dirinfo->stat) == -1)
goto err;
return 1;
err:
close_dirinfo(dirinfo);
return 0;
}
static inline int
open_dirinfo(const char* path, struct dirinfo* dirinfo)
{
if ((dirinfo->fd = open(path, O_DIRECTORY|O_RDONLY)) == -1)
goto err;
if ((dirinfo->dp = fdopendir(dirinfo->fd)) == NULL)
goto err;
if (fstat(dirinfo->fd, &dirinfo->stat) == -1)
goto err;
return 1;
err:
close_dirinfo(dirinfo);
return 0;
}
#endif

238
src/filter.h Normal file
View File

@ -0,0 +1,238 @@
#ifndef H_FILTER
#define H_FILTER
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <limits.h>
#define MIN(a, b) (a > b? b : a)
#define MAX(a, b) (a > b? a : b)
struct filter
{
struct {
char** ptr;
size_t len;
} tags;
struct {
struct {
char** ptr;
int* in;
size_t len;
} included;
struct {
char** ptr;
size_t len;
} excluded;
} files;
};
static inline int
filter_include_count(struct filter* filter)
{
int count = 0;
for (size_t i = 0; i < filter->tags.len; ++i) {
if (filter->tags.ptr[i][0] != '@')
count++;
}
return count;
}
static inline int
filter_exclude_count(struct filter* filter)
{
int count = 0;
for (size_t i = 0; i < filter->tags.len; ++i) {
if (filter->tags.ptr[i][0] == '@')
count++;
}
return count;
}
static inline void
filter_free(struct filter* filter)
{
if (filter->tags.ptr) {
for (size_t i = 0; i < filter->tags.len; ++i) {
free(filter->tags.ptr[i]);
}
free(filter->tags.ptr);
}
if (filter->files.included.ptr) {
for (size_t i = 0; i < filter->files.included.len; ++i) {
free(filter->files.included.ptr[i]);
}
free(filter->files.included.ptr);
free(filter->files.included.in);
}
if (filter->files.excluded.ptr) {
for (size_t i = 0; i < filter->files.excluded.len; ++i) {
free(filter->files.excluded.ptr[i]);
}
free(filter->files.excluded.ptr);
}
}
static inline int
load_filter_files(struct filter* filter, struct dirinfo* tagdir)
{
memset(&filter->files, 0, sizeof filter->files);
for (size_t i = 0; i < filter->tags.len; ++i) {
const char* tag = filter->tags.ptr[i];
int excluded = (*tag == '@');
int fd = openat(tagdir->fd, tag + (excluded? 1 : 0), O_DIRECTORY|O_RDONLY);
if (fd == -1)
return 0;
DIR* dp = fdopendir(fd);
struct dirent* ent;
while ((ent = readdir(dp))) {
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
char name[PATH_MAX];
if (readlinkat(fd, ent->d_name, name, sizeof name) == -1) {
closedir(dp);
close(fd);
return 0;
}
const char* p = strrchr(name, '/') + 1;
if (excluded) {
size_t size = ++filter->files.excluded.len * sizeof *filter->files.excluded.ptr;
void* ptr = realloc(filter->files.excluded.ptr, size);
if (ptr == NULL) {
closedir(dp);
close(fd);
return 0;
}
filter->files.excluded.ptr = ptr;
char** str = &filter->files.excluded.ptr[filter->files.excluded.len - 1];
size = MIN(strlen(p), NAME_MAX);
*str = malloc(size + 1);
if (*str == NULL) {
closedir(dp);
close(fd);
return 0;
}
strncpy(*str, p, size);
(*str)[size] = '\0';
}
else {
int skip = 0;
for (size_t j = 0; j < filter->files.included.len; ++j) {
if (strcmp(filter->files.included.ptr[j], p) == 0) {
skip = 1;
filter->files.included.in[j] += 1;
}
}
if (skip)
continue;
size_t size = ++filter->files.included.len * sizeof *filter->files.included.ptr;
void* ptr = realloc(filter->files.included.ptr, size);
if (ptr == NULL) {
closedir(dp);
close(fd);
return 0;
}
filter->files.included.ptr = ptr;
size = filter->files.included.len * sizeof *filter->files.included.in;
ptr = realloc(filter->files.included.in, size);
if (ptr == NULL) {
closedir(dp);
close(fd);
return 0;
}
filter->files.included.in = ptr;
filter->files.included.in[filter->files.included.len - 1] = 1;
char** str = &filter->files.included.ptr[filter->files.included.len - 1];
size = MIN(strlen(p), NAME_MAX);
*str = malloc(size + 1);
if (*str == NULL) {
closedir(dp);
close(fd);
return 0;
}
strncpy(*str, p, size);
(*str)[size] = '\0';
}
}
closedir(dp);
close(fd);
}
return 1;
}
static inline int
load_filter_tags(int fd, struct filter* filter)
{
FILE* fp = fdopen(fd, "r");
char tag[TAG_MAX + 1];
memset(&filter->tags, 0, sizeof filter->tags);
while (fgets(tag, sizeof tag, fp)) {
size_t size = ++filter->tags.len * sizeof *filter->tags.ptr;
void* ptr = realloc(filter->tags.ptr, size);
if (ptr == NULL) {
fclose(fp);
return 0;
}
filter->tags.ptr = ptr;
char** str = &filter->tags.ptr[filter->tags.len - 1];
size = MIN(strlen(tag), TAG_MAX);
*str = malloc(size + 1);
if (*str == NULL) {
fclose(fp);
return 0;
}
if (tag[size - 1] == '\n')
tag[size - 1] = '\0';
strncpy(*str, tag, size);
(*str)[size] = '\0';
}
fclose(fp);
return 1;
}
#endif

49
src/proc.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef H_PROC
#define H_PROC
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <alloca.h>
#include <limits.h>
static inline int
is_pid_alive(unsigned pid)
{
char path[PATH_MAX];
snprintf(path, sizeof path, "/proc/%u", pid);
return access(path, F_OK) == 0;
}
static inline int
proc_get_ppid(unsigned pid)
{
char path[PATH_MAX];
snprintf(path, sizeof path, "/proc/%u/stat", pid);
FILE* fp;
if ((fp = fopen(path, "r")) == NULL)
goto err;
char stat[4096];
if (fread(stat, sizeof(char), sizeof stat, fp) == 0)
goto err;
fclose(fp);
char unused;
int ppid;
sscanf(strrchr(stat, ')') + 1, " %c %d", &unused, &ppid);
return ppid;
err:
if (fp) fclose(fp);
return -1;
}
#endif

450
src/tagfs.c Normal file
View File

@ -0,0 +1,450 @@
#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;
}