Skip to content
Commits on Source (4)
......@@ -97,6 +97,31 @@ struct __dirstream {
#include <client/preload_context.hpp>
#include <client/user_functions.hpp>
// TODO do we really need the stat here? no i dont think so
struct Inode {
std::string path;
struct stat st;
uint64_t lookup_count;
};
enum {
CACHE_NEVER,
CACHE_NORMAL,
CACHE_ALWAYS,
};
struct u_data {
pthread_mutex_t mutex;
int debug;
int writeback;
int flock;
int xattr;
char* source;
double timeout;
int cache;
int timeout_set;
};
struct GkfsDir { // Hypothetical structure that might be used if DIR is cast
int fd;
long int tell_pos; // for telldir/seekdir
......@@ -104,68 +129,4 @@ struct GkfsDir { // Hypothetical structure that might be used if DIR is cast
// other members libc DIR might have
};
/*
* Creates files on the underlying file system in response to a FUSE_MKNOD
* operation
*/
static inline int
mknod_wrapper(int dirfd, const char* path, const char* link, int mode,
dev_t rdev) {
fuse_log(FUSE_LOG_DEBUG, "mknod_wrapper \n");
int res = -1;
if(S_ISREG(mode)) {
// res = lol_openat(dirfd, path, mode, O_CREAT | O_EXCL | O_WRONLY);
fuse_log(FUSE_LOG_DEBUG, "lol_openat internal %s\n", res);
if(res >= 0)
res = gkfs::syscall::gkfs_close(res);
} else if(S_ISDIR(mode)) {
// GKFS_PATH_OPERATION(create, dirfd, path, mode | S_IFDIR)
// res = gkfs::syscall::gkfs_create(resolved, mode | S_IFDIR);
// res = mkdirat(dirfd, path, mode);
} else if(S_ISLNK(mode) && link != NULL) {
fuse_log(FUSE_LOG_ERR, "fifo in mknod_wrapper not supported\n");
errno = ENOTSUP;
return -1;
// res = symlinkat(link, dirfd, path);
} else if(S_ISFIFO(mode)) {
fuse_log(FUSE_LOG_ERR, "fifo in mknod_wrapper not supported\n");
errno = ENOTSUP;
return -1;
// res = mkfifoat(dirfd, path, mode);
#ifdef __FreeBSD__
} else if(S_ISSOCK(mode)) {
struct sockaddr_un su;
int fd;
if(strlen(path) >= sizeof(su.sun_path)) {
errno = ENAMETOOLONG;
return -1;
}
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if(fd >= 0) {
/*
* We must bind the socket to the underlying file
* system to create the socket file, even though
* we'll never listen on this socket.
*/
su.sun_family = AF_UNIX;
strncpy(su.sun_path, path, sizeof(su.sun_path));
res = bindat(dirfd, fd, (struct sockaddr*) &su, sizeof(su));
if(res == 0)
close(fd);
} else {
res = -1;
}
#endif
} else {
fuse_log(FUSE_LOG_ERR, "mknodat in mknod_wrapper not supported\n");
errno = ENOTSUP;
return -1;
// res = mknodat(dirfd, path, mode, rdev);
}
return res;
}
#endif // GKFS_CLIENT_FUSE_CONTEXT_HPP
......@@ -39,47 +39,45 @@
#include <client/fuse/fuse_client.hpp>
struct lo_inode {
struct lo_inode* next; /* protected by lo->mutex */
struct lo_inode* prev; /* protected by lo->mutex */
int fd;
ino_t ino;
dev_t dev;
uint64_t refcount; /* protected by lo->mutex */
};
enum {
CACHE_NEVER,
CACHE_NORMAL,
CACHE_ALWAYS,
};
struct lo_data {
pthread_mutex_t mutex;
int debug;
int writeback;
int flock;
int xattr;
char* source;
double timeout;
int cache;
int timeout_set;
struct lo_inode root; /* protected by lo->mutex */
};
static struct fuse_lowlevel_ops ll_ops;
static std::mutex ino_mutex;
static std::unordered_map<fuse_ino_t, Inode> ino_map;
static std::unordered_map<std::string, fuse_ino_t> path_map;
static fuse_ino_t next_ino = 2; // reserve 1 for root
static fuse_ino_t
alloc_inode(const std::string& path) {
std::lock_guard<std::mutex> lk(ino_mutex);
fuse_ino_t ino = next_ino++;
ino_map[ino] = {path, {}, 1};
path_map[path] = ino;
return ino;
}
static Inode*
get_inode(fuse_ino_t ino) {
std::lock_guard<std::mutex> lk(ino_mutex);
auto it = ino_map.find(ino);
return it != ino_map.end() ? &it->second : nullptr;
}
static struct u_data*
udata(fuse_req_t req) {
return (struct u_data*) fuse_req_userdata(req);
}
static const struct fuse_opt lo_opts[] = {
{"writeback", offsetof(struct lo_data, writeback), 1},
{"no_writeback", offsetof(struct lo_data, writeback), 0},
{"source=%s", offsetof(struct lo_data, source), 0},
{"flock", offsetof(struct lo_data, flock), 1},
{"no_flock", offsetof(struct lo_data, flock), 0},
{"xattr", offsetof(struct lo_data, xattr), 1},
{"no_xattr", offsetof(struct lo_data, xattr), 0},
{"timeout=%lf", offsetof(struct lo_data, timeout), 0},
{"timeout=", offsetof(struct lo_data, timeout_set), 1},
{"cache=never", offsetof(struct lo_data, cache), CACHE_NEVER},
{"cache=auto", offsetof(struct lo_data, cache), CACHE_NORMAL},
{"cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS},
{"writeback", offsetof(struct u_data, writeback), 1},
{"no_writeback", offsetof(struct u_data, writeback), 0},
{"flock", offsetof(struct u_data, flock), 1},
{"no_flock", offsetof(struct u_data, flock), 0},
{"xattr", offsetof(struct u_data, xattr), 1},
{"no_xattr", offsetof(struct u_data, xattr), 0},
{"timeout=%lf", offsetof(struct u_data, timeout), 0},
{"timeout=", offsetof(struct u_data, timeout_set), 1},
{"cache=never", offsetof(struct u_data, cache), CACHE_NEVER},
{"cache=auto", offsetof(struct u_data, cache), CACHE_NORMAL},
{"cache=always", offsetof(struct u_data, cache), CACHE_ALWAYS},
FUSE_OPT_END};
......@@ -99,85 +97,58 @@ passthrough_ll_help(void) {
" -o cache=always Cache always\n");
}
static struct lo_data*
lo_data(fuse_req_t req) {
return (struct lo_data*) fuse_req_userdata(req);
}
static struct lo_inode*
lo_inode(fuse_req_t req, fuse_ino_t ino) {
if(ino == FUSE_ROOT_ID)
return &lo_data(req)->root;
else
return (struct lo_inode*) (uintptr_t) ino;
}
static int
lo_fd(fuse_req_t req, fuse_ino_t ino) {
return lo_inode(req, ino)->fd;
}
static bool
lo_debug(fuse_req_t req) {
return lo_data(req)->debug != 0;
}
static void
lo_init(void* userdata, struct fuse_conn_info* conn) {
// TODO init gkfs
struct lo_data* lo = (struct lo_data*) userdata;
bool has_flag;
if(lo->writeback) {
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
if(lo->debug && has_flag)
fuse_log(FUSE_LOG_DEBUG, "lo_init: activating writeback\n");
}
if(lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
if(lo->debug && has_flag)
fuse_log(FUSE_LOG_DEBUG, "lo_init: activating flock locks\n");
}
init_handler(void* userdata, struct fuse_conn_info* conn) {
fuse_log(FUSE_LOG_DEBUG, "init handler \n");
// struct u_data* lo = (struct u_data*) userdata;
// bool has_flag;
// TODO check other capabilities e.g. FUSE_CAP_READDIRPLUS
// if(lo->writeback) {
// has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
// if(lo->debug && has_flag)
// fuse_log(FUSE_LOG_DEBUG, "init_handler: activating writeback\n");
// }
// if(lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
// has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
// if(lo->debug && has_flag)
// fuse_log(FUSE_LOG_DEBUG, "init_handler: activating flock
// locks\n");
// }
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
conn->no_interrupt = 1;
// conn->no_interrupt = 1;
}
// Simplified inode structure
struct Inode {
std::string path;
struct stat st;
uint64_t lookup_count;
};
static std::mutex ino_mutex;
static std::unordered_map<fuse_ino_t, Inode> ino_map;
static fuse_ino_t next_ino = 2; // reserve 1 for root
static fuse_ino_t
alloc_inode(const std::string& path) {
std::lock_guard<std::mutex> lk(ino_mutex);
fuse_ino_t ino = next_ino++;
ino_map[ino] = {path, {}, 1};
return ino;
}
static Inode*
get_inode(fuse_ino_t ino) {
std::lock_guard<std::mutex> lk(ino_mutex);
auto it = ino_map.find(ino);
return it != ino_map.end() ? &it->second : nullptr;
static void
destroy_handler(void* userdata) {
fuse_log(FUSE_LOG_DEBUG, "destroy handler \n");
// userdata is GekkoFuse* if passed
}
static void
lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) {
fuse_log(FUSE_LOG_DEBUG, "lookup handler ino %u\n", parent);
auto* ud = udata(req);
auto* parent_inode = get_inode(parent);
if(!parent_inode) {
fuse_log(FUSE_LOG_DEBUG, "this error 1 \n", parent);
fuse_reply_err(req, ENOENT);
return;
}
std::string child = parent_inode->path + name;
fuse_log(FUSE_LOG_DEBUG, "lookup %s\n", child.c_str());
// See if we already have this path
auto it = path_map.find(child);
fuse_ino_t ino;
if(it != path_map.end()) {
ino = it->second;
ino_map[ino].lookup_count++;
} else {
ino = alloc_inode(child);
}
struct stat st;
int rc = gkfs::syscall::gkfs_stat(child, &st);
if(rc < 0) {
......@@ -185,19 +156,19 @@ lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) {
fuse_reply_err(req, ENOENT);
return;
}
fuse_ino_t ino = alloc_inode(child);
ino_map[ino].st = st;
fuse_entry_param e = {};
e.ino = ino;
e.attr = st;
e.attr_timeout = 1.0;
e.entry_timeout = 1.0;
e.attr_timeout = ud->timeout;
e.entry_timeout = ud->timeout;
fuse_reply_entry(req, &e);
}
static void
getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
fuse_log(FUSE_LOG_DEBUG, "getattr handler \n");
auto* ud = udata(req);
auto* inode = get_inode(ino);
if(!inode) {
fuse_reply_err(req, ENOENT);
......@@ -212,39 +183,42 @@ getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
return;
}
inode->st = st;
fuse_reply_attr(req, &st, 1.0);
fuse_reply_attr(req, &st, ud->timeout);
}
static void
setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set,
struct fuse_file_info* fi) {
fuse_log(FUSE_LOG_DEBUG, "setattr handler \n");
fuse_log(FUSE_LOG_DEBUG, "setattr handler ino %u\n", ino);
auto* ud = udata(req);
auto* inode = get_inode(ino);
if(!inode) {
fuse_reply_err(req, ENOENT);
return;
}
// TODO how to change attr?
int rc = 0;
if(rc) {
fuse_reply_err(req, rc);
if(to_set & FUSE_SET_ATTR_SIZE) {
off_t new_size = attr->st_size;
int res = gkfs::syscall::gkfs_truncate(inode->path, new_size);
if(res < 0) {
fuse_log(FUSE_LOG_DEBUG, "setattr truncate failed on %s\n",
inode->path.c_str());
fuse_reply_err(req, EIO);
return;
}
// TODO thats not in gkfs!!!
// Update cached stat so users see the new size
inode->st.st_size = new_size;
}
if(to_set & FUSE_SET_ATTR_ATIME)
inode->st.st_atim = attr->st_atim;
inode->st.st_blksize = attr->st_blksize;
inode->st.st_blocks = attr->st_blocks;
inode->st.st_ctim = attr->st_ctim;
inode->st.st_dev = attr->st_dev;
inode->st.st_gid = attr->st_gid;
inode->st.st_ino = attr->st_ino;
inode->st.st_mode = attr->st_mode;
if(to_set & FUSE_SET_ATTR_MTIME)
inode->st.st_mtim = attr->st_mtim;
inode->st.st_nlink = attr->st_nlink;
inode->st.st_rdev = attr->st_rdev;
inode->st.st_size = attr->st_size;
inode->st.st_uid = attr->st_uid;
fuse_reply_attr(req, &inode->st, 1.0);
// TODO because we cannot save the attributes in gekko, we just return the
// buffered results of stat
fuse_reply_attr(req, &inode->st, ud->timeout);
return;
}
static void
......@@ -319,6 +293,7 @@ static void
create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode,
struct fuse_file_info* fi) {
fuse_log(FUSE_LOG_DEBUG, "create handler \n");
auto* ud = udata(req);
auto* parent_inode = get_inode(parent);
if(!parent_inode) {
fuse_reply_err(req, ENOENT);
......@@ -346,12 +321,13 @@ create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode,
return;
}
fuse_ino_t ino = alloc_inode(path);
fuse_log(FUSE_LOG_DEBUG, "create new inode ino %i\n", ino);
ino_map[ino].st = st;
fuse_entry_param e = {};
e.ino = ino;
e.attr = st;
e.attr_timeout = 1.0;
e.entry_timeout = 1.0;
e.attr_timeout = ud->timeout;
e.entry_timeout = ud->timeout;
fuse_reply_create(req, &e, fi);
}
......@@ -427,7 +403,7 @@ readdir_handler(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
}
size_t bytes_filled = 0;
off_t pos = off;
size_t pos = off;
while(pos < open_dir->size()) {
auto de = open_dir->getdent(pos);
......@@ -456,83 +432,109 @@ readdir_handler(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
free(buf);
}
/// releases file descriptor, not connected to lookup_count
static void
init_handler(void* userdata, struct fuse_conn_info* conn) {
// userdata is GekkoFuse* if passed
// optional: adjust conn->max_write, enable writeback etc.
}
static const struct fuse_lowlevel_ops lo_oper = {
.init = init_handler, // lo_init,
//.destroy = lo_destroy,
.lookup = lookup_handler,
//.forget = lo_forget,
.getattr = getattr_handler,
.setattr = setattr_handler,
//.readlink = lo_readlink,
//.mknod = lo_mknod,
//.mkdir = lo_mkdir,
//.unlink = lo_unlink,
//.rmdir = lo_rmdir,
//.symlink = lo_symlink,
//.rename = lo_rename,
//.link = lo_link,
.open = open_handler,
.read = read_handler,
.write = write_handler,
//.flush = lo_flush,
//.release = lo_release,
//.fsync = lo_fsync,
.opendir = opendir_handler,
.readdir = readdir_handler,
//.releasedir = lo_releasedir,
//.fsyncdir = lo_fsyncdir,
//.statfs = lo_statfs,
//.setxattr = lo_setxattr,
//.getxattr = lo_getxattr,
//.listxattr = lo_listxattr,
//.removexattr = lo_removexattr,
// access
.create = create_handler,
// getlk
// setlk
// bmap
// ioctl
//.write_buf = lo_write_buf,
// poll
// retrive_reply
//.forget_multi = lo_forget_multi,
//.flock = lo_flock,
//.fallocate = lo_fallocate,
//.readdirplus = lo_readdirplus,
#ifdef HAVE_COPY_FILE_RANGE
//.copy_file_range = lo_copy_file_range,
#endif
//.lseek = lo_lseek,
//.tmpfile = lo_tmpfile,
#ifdef HAVE_STATX
//.statx = lo_statx,
#endif
};
release_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
fuse_log(FUSE_LOG_DEBUG, "release handler \n");
auto* inode = get_inode(ino);
if(!inode) {
fuse_log(FUSE_LOG_DEBUG, "release here \n");
fuse_reply_err(req, ENOENT);
return;
}
int lc = gkfs::syscall::gkfs_close(fi->fh);
if(lc < 0) {
fuse_log(FUSE_LOG_DEBUG, "release there \n");
fuse_reply_err(req, 1);
return;
}
fuse_log(FUSE_LOG_DEBUG, "release success \n");
fuse_reply_err(req, 0);
}
int
main(int argc, char* argv[]) {
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct fuse_session* se;
struct fuse_cmdline_opts opts;
struct fuse_loop_config* config;
struct lo_data lo = {.debug = 0, .writeback = 0};
int ret = -1;
/// decrement lookup count
static void
forget_handler(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) {
fuse_log(FUSE_LOG_DEBUG, "forget handler \n");
/* Don't mask creation mode, kernel already did that */
umask(0); // TODO do we need this and why?
auto it = ino_map.find(ino);
if(it == ino_map.end()) {
fuse_reply_none(req);
return;
}
Inode& inode = it->second;
if(inode.lookup_count > nlookup)
inode.lookup_count -= nlookup;
else
inode.lookup_count = 0;
pthread_mutex_init(&lo.mutex, NULL);
lo.root.next = lo.root.prev = &lo.root;
lo.root.fd = -1;
lo.cache = CACHE_NORMAL;
if(inode.lookup_count == 0) { // && inode.open_count == 0
path_map.erase(inode.path);
ino_map.erase(it);
fuse_log(FUSE_LOG_DEBUG, "reached lookup_count 0 \n");
}
fuse_reply_none(req);
// fuse_reply_err(req, 0);
}
// init gekkofs
static void
flush_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
fuse_log(FUSE_LOG_DEBUG, "flush handler \n");
auto* inode = get_inode(ino);
if(!inode) {
fuse_log(FUSE_LOG_DEBUG, "flush here \n");
fuse_reply_err(req, ENOENT);
return;
}
int lc = gkfs::syscall::gkfs_fsync(fi->fh);
if(lc < 0) {
fuse_log(FUSE_LOG_DEBUG, "flush there \n");
fuse_reply_err(req, 1);
return;
}
fuse_log(FUSE_LOG_DEBUG, "flush success \n");
fuse_reply_err(req, 0);
}
static void
mkdir_handler(fuse_req_t req, fuse_ino_t parent, const char* name,
mode_t mode) {
auto* ud = udata(req);
auto* parent_inode = get_inode(parent);
if(!parent_inode) {
fuse_reply_err(req, ENOENT);
return;
}
std::string path = parent_inode->path + name;
int rc = gkfs::syscall::gkfs_create(path, mode | S_IFDIR);
if(rc == -1) {
fuse_reply_err(req, 1);
return;
}
struct stat st;
int sc = gkfs::syscall::gkfs_stat(path, &st);
if(sc == -1) {
fuse_log(FUSE_LOG_DEBUG, "thats why its not allowed \n");
fuse_reply_err(req, 1);
return;
}
fuse_ino_t ino = alloc_inode(path);
fuse_log(FUSE_LOG_DEBUG, "create new inode ino %i\n", ino);
ino_map[ino].st = st;
fuse_entry_param e = {};
e.ino = ino;
e.attr = st;
e.attr_timeout = ud->timeout;
e.entry_timeout = ud->timeout;
fuse_reply_entry(req, &e);
fuse_log(FUSE_LOG_DEBUG, "flush success \n");
fuse_reply_err(req, 0);
}
static void
init_gekkofs() {
// TODO how to handle mount point
int res = gkfs_init();
if(res != 0) {
......@@ -556,7 +558,107 @@ main(int argc, char* argv[]) {
ino_map[FUSE_ROOT_ID] = {root_path, {}, 1};
ino_map[FUSE_ROOT_ID].st = st;
std::cout << "root node allocated" << std::endl;
}
static void
init_ll_ops(fuse_lowlevel_ops* ops) {
// file
ops->getattr = getattr_handler;
ops->setattr = setattr_handler;
ops->open = open_handler;
ops->create = create_handler;
// ops->unlink
ops->forget = forget_handler;
// ops->forget_multi
// ops->readlink
// ops->mknod
// ops->symlink
// ops->rename
// ops->link
ops->flush = flush_handler;
ops->release = release_handler;
// ops->fsync
// ops->write_buf
// xattr
// ops->setxattr
// ops->getxattr
// ops->listxattr
// ops->removexattr
// directory
ops->lookup = lookup_handler;
ops->mkdir = mkdir_handler;
// ops->rmdir
ops->readdir = readdir_handler;
ops->opendir = opendir_handler;
// ops->releasedir
// ops->fsyncdir = nullptr;
// ops->readdirplus
// I/O
ops->write = write_handler;
ops->read = read_handler;
// permission
// ops->access
// misc
ops->init = init_handler;
ops->destroy = destroy_handler;
ops->statfs = nullptr;
// ops->flock
// ops->getlk
// ops->setlk
// ops->bmap
// ops->ioctl
// ops->poll
// ops->retrive_reply
// ops->fallocate
// ops->copy_file_range
// ops->lseek
// ops->tmpfile
// ops->statx
}
void
err_cleanup1(fuse_cmdline_opts opts, fuse_args& args) {
free(opts.mountpoint);
fuse_opt_free_args(&args);
std::cout << "# Resources released" << std::endl;
}
void
err_cleanup2(fuse_session& se) {
fuse_session_destroy(&se);
std::cout << "# Fuse session destroyed" << std::endl;
}
void
err_cleanup3(fuse_session& se) {
fuse_remove_signal_handlers(&se);
std::cout << "# Signal handlers removed" << std::endl;
}
int
main(int argc, char* argv[]) {
init_ll_ops(&ll_ops);
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct fuse_session* se;
struct fuse_cmdline_opts opts;
struct fuse_loop_config* config;
struct u_data ud{};
ud.debug = 0;
ud.writeback = 0;
int ret = -1;
/* Don't mask creation mode, kernel already did that */
umask(0); // TODO do we need this and why?
pthread_mutex_init(&ud.mutex, NULL);
ud.cache = CACHE_NORMAL;
if(fuse_parse_cmdline(&args, &opts) != 0)
return 1;
......@@ -565,86 +667,69 @@ main(int argc, char* argv[]) {
fuse_cmdline_help();
fuse_lowlevel_help();
passthrough_ll_help();
ret = 0;
goto err_out1;
err_cleanup1(opts, args);
return 0;
} else if(opts.show_version) {
printf("FUSE library version %s\n", fuse_pkgversion());
fuse_lowlevel_version();
ret = 0;
goto err_out1;
err_cleanup1(opts, args);
return 0;
}
if(opts.mountpoint == NULL) {
printf("usage: %s [options] <mountpoint>\n", argv[0]);
printf(" %s --help\n", argv[0]);
ret = 1;
goto err_out1;
err_cleanup1(opts, args);
return 0;
}
if(fuse_opt_parse(&args, &lo, lo_opts, NULL) == -1)
if(fuse_opt_parse(&args, &ud, lo_opts, NULL) == -1)
return 1;
lo.debug = opts.debug;
lo.root.refcount = 2;
if(lo.source) {
struct stat stat;
int res;
res = lstat(lo.source, &stat);
if(res == -1) {
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
lo.source);
exit(1);
}
if(!S_ISDIR(stat.st_mode)) {
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
exit(1);
}
} else {
lo.source = strdup("/");
if(!lo.source) {
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
exit(1);
}
}
if(!lo.timeout_set) {
switch(lo.cache) {
ud.debug = opts.debug;
if(!ud.timeout_set) {
switch(ud.cache) {
case CACHE_NEVER:
lo.timeout = 0.0;
ud.timeout = 0.0;
break;
case CACHE_NORMAL:
lo.timeout = 1.0;
ud.timeout = 1.0;
break;
case CACHE_ALWAYS:
lo.timeout = 86400.0;
ud.timeout = 86400.0;
break;
}
} else if(lo.timeout < 0) {
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", lo.timeout);
} else if(ud.timeout < 0) {
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", ud.timeout);
exit(1);
}
// TODO do we still want this? what do we want?
fuse_log(FUSE_LOG_DEBUG, "hier 1\n");
lo.root.fd = gkfs::syscall::gkfs_open(lo.source, 755, O_PATH);
if(lo.root.fd == -1) {
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n", lo.source);
exit(1);
}
fuse_log(FUSE_LOG_DEBUG, "hier 2\n");
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
if(se == NULL)
goto err_out1;
init_gekkofs();
if(fuse_set_signal_handlers(se) != 0)
goto err_out2;
se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), &ud);
if(se == nullptr) {
err_cleanup1(opts, args);
return 0;
}
if(fuse_session_mount(se, opts.mountpoint) != 0)
goto err_out3;
if(fuse_set_signal_handlers(se) != 0) {
err_cleanup2(*se);
err_cleanup1(opts, args);
return 0;
}
if(fuse_session_mount(se, opts.mountpoint) != 0) {
err_cleanup3(*se);
err_cleanup2(*se);
err_cleanup1(opts, args);
return 0;
}
fuse_daemonize(opts.foreground);
......@@ -668,20 +753,5 @@ main(int argc, char* argv[]) {
}
fuse_session_unmount(se);
err_out3:
fuse_log(FUSE_LOG_DEBUG, "hier 3\n");
fuse_remove_signal_handlers(se);
err_out2:
fuse_log(FUSE_LOG_DEBUG, "hier 4\n");
fuse_session_destroy(se);
err_out1:
fuse_log(FUSE_LOG_DEBUG, "hier 5\n");
free(opts.mountpoint);
fuse_opt_free_args(&args);
if(lo.root.fd >= 0)
close(lo.root.fd);
free(lo.source);
return ret ? 1 : 0;
}
......@@ -143,6 +143,15 @@ if (GKFS_RENAME_SUPPORT)
)
endif ()
if (GKFS_BUILD_FUSE)
gkfs_add_python_test(
NAME test_fuse_client
PYTHON_VERSION 3.6
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integration
SOURCE fuse/
)
endif ()
if (GKFS_INSTALL_TESTS)
install(DIRECTORY harness
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration
......
......@@ -34,7 +34,7 @@ from pathlib import Path
from harness.logger import logger, initialize_logging, finalize_logging
from harness.cli import add_cli_options, set_default_log_formatter
from harness.workspace import Workspace, FileCreator
from harness.gkfs import Daemon, Client, ClientLibc, Proxy, ShellClient, ShellClientLibc, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator
from harness.gkfs import Daemon, Client, ClientLibc, Proxy, ShellClient, ShellClientLibc, FwdDaemon, FwdClient, ShellFwdClient, FwdDaemonCreator, FwdClientCreator, FuseClient
from harness.reporter import report_test_status, report_test_headline, report_assertion_pass
def pytest_configure(config):
......@@ -225,3 +225,15 @@ def gkfwd_client_factory(test_workspace):
"""
return FwdClientCreator(test_workspace)
@pytest.fixture
def fuse_client(test_workspace):
"""
Sets up a gekkofs fuse client environment so that
operations (system calls, library calls, ...) can
be requested from a co-running daemon.
"""
fuse_client = FuseClient(test_workspace)
yield fuse_client.run()
fuse_client.shutdown()
################################################################################
# Copyright 2018-2025, Barcelona Supercomputing Center (BSC), Spain #
# Copyright 2015-2025, Johannes Gutenberg Universitaet Mainz, Germany #
# #
# This software was partially supported by the #
# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). #
# #
# This software was partially supported by the #
# ADA-FS project under the SPPEXA project funded by the DFG. #
# #
# This file is part of GekkoFS. #
# #
# GekkoFS 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 3 of the License, or #
# (at your option) any later version. #
# #
# GekkoFS 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 GekkoFS. If not, see <https://www.gnu.org/licenses/>. #
# #
# SPDX-License-Identifier: GPL-3.0-or-later #
################################################################################
import harness
from pathlib import Path
import errno
import stat
import os
import ctypes
import sh
import sys
import pytest
import time
from harness.logger import logger
nonexisting = "nonexisting"
def test_read(gkfs_daemon, fuse_client):
file = gkfs_daemon.mountdir / "file"
file2 = gkfs_daemon.mountdir / "file2"
dir = gkfs_daemon.mountdir / "dir"
sh.bash("-c", "echo baum > " + str(file))
assert sh.ls(fuse_client.mountdir) == "file\n"
assert sh.cat(file) == "baum\n"
sh.touch(str(file2))
assert sh.wc("-c", str(file2)) == "0 " + str(file2) + "\n"
sh.truncate("-s", "20", str(file2))
assert sh.wc("-c", str(file2)) == "20 " + str(file2) + "\n"
sh.mkdir(str(dir))
......@@ -74,6 +74,7 @@ gkfwd_client_log_level = 'all'
gkfwd_client_log_syscall_filter = 'epoll_wait,epoll_create'
gkfwd_daemon_active_log_pattern = r'Startup successful. Daemon is ready.'
gkfs_fuse_client = 'fuse_client'
def get_ip_addr(iface):
return netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']
......@@ -1637,3 +1638,78 @@ class ShellFwdClient:
@property
def cwd(self):
return self._workspace.twd
class FuseClient:
def __init__(self, workspace):
self._workspace = workspace
#self._cmd = sh.Command("printenv", ["/usr/bin/"])#self._workspace.bindirs)
self._cmd = sh.Command(gkfs_fuse_client, self._workspace.bindirs)
self._env = os.environ.copy()
self._metadir = self.rootdir
libdirs = ':'.join(
filter(None, [os.environ.get('LD_LIBRARY_PATH', '')] +
[str(p) for p in self._workspace.libdirs]))
self._patched_env = {
'LD_LIBRARY_PATH' : libdirs,
'GKFS_HOSTS_FILE' : str(self.cwd / gkfs_hosts_file),
'LIBGKFS_HOSTS_FILE' : str(self.cwd / gkfs_hosts_file), # TODO wtf why? see gkfs::env::HOSTS_FILE
}
self._env.update(self._patched_env)
def run(self):
args = [ "-f", "-s", self._workspace.mountdir, "-o", "auto_unmount" ]
print(f"spawning fuse client")
print(f"cmdline: {self._cmd} " + " ".join(map(str, args)))
print(f"patched env:\n{pformat(self._patched_env)}")
self._proc = self._cmd(
args,
_env=self._env,
_out='/dev/null',
_err='/dev/null',
_bg=True,
_ok_code=list(range(0, 256))
)
print(f"fuse client process spawned (PID={self._proc.pid})")
time.sleep(2) # give fuse time to register mount
return self
def shutdown(self):
try:
self._proc.terminate()
err = self._proc.wait()
except sh.SignalException_SIGTERM:
pass
except Exception:
raise
@property
def cwd(self):
return self._workspace.twd
@property
def rootdir(self):
return self._workspace.rootdir
@property
def mountdir(self):
return self._workspace.mountdir
@property
def bindirs(self):
return self._workspace.bindirs
@property
def logdir(self):
return self._workspace.logdir
@property
def env(self):
return self._env