Skip to content
Commits on Source (4)
...@@ -97,6 +97,31 @@ struct __dirstream { ...@@ -97,6 +97,31 @@ struct __dirstream {
#include <client/preload_context.hpp> #include <client/preload_context.hpp>
#include <client/user_functions.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 struct GkfsDir { // Hypothetical structure that might be used if DIR is cast
int fd; int fd;
long int tell_pos; // for telldir/seekdir long int tell_pos; // for telldir/seekdir
...@@ -104,68 +129,4 @@ struct GkfsDir { // Hypothetical structure that might be used if DIR is cast ...@@ -104,68 +129,4 @@ struct GkfsDir { // Hypothetical structure that might be used if DIR is cast
// other members libc DIR might have // 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 #endif // GKFS_CLIENT_FUSE_CONTEXT_HPP
...@@ -39,47 +39,45 @@ ...@@ -39,47 +39,45 @@
#include <client/fuse/fuse_client.hpp> #include <client/fuse/fuse_client.hpp>
struct lo_inode { static struct fuse_lowlevel_ops ll_ops;
struct lo_inode* next; /* protected by lo->mutex */ static std::mutex ino_mutex;
struct lo_inode* prev; /* protected by lo->mutex */ static std::unordered_map<fuse_ino_t, Inode> ino_map;
int fd; static std::unordered_map<std::string, fuse_ino_t> path_map;
ino_t ino; static fuse_ino_t next_ino = 2; // reserve 1 for root
dev_t dev;
uint64_t refcount; /* protected by lo->mutex */ static fuse_ino_t
}; alloc_inode(const std::string& path) {
std::lock_guard<std::mutex> lk(ino_mutex);
enum { fuse_ino_t ino = next_ino++;
CACHE_NEVER, ino_map[ino] = {path, {}, 1};
CACHE_NORMAL, path_map[path] = ino;
CACHE_ALWAYS, return ino;
}; }
struct lo_data { static Inode*
pthread_mutex_t mutex; get_inode(fuse_ino_t ino) {
int debug; std::lock_guard<std::mutex> lk(ino_mutex);
int writeback; auto it = ino_map.find(ino);
int flock; return it != ino_map.end() ? &it->second : nullptr;
int xattr; }
char* source;
double timeout; static struct u_data*
int cache; udata(fuse_req_t req) {
int timeout_set; return (struct u_data*) fuse_req_userdata(req);
struct lo_inode root; /* protected by lo->mutex */ }
};
static const struct fuse_opt lo_opts[] = { static const struct fuse_opt lo_opts[] = {
{"writeback", offsetof(struct lo_data, writeback), 1}, {"writeback", offsetof(struct u_data, writeback), 1},
{"no_writeback", offsetof(struct lo_data, writeback), 0}, {"no_writeback", offsetof(struct u_data, writeback), 0},
{"source=%s", offsetof(struct lo_data, source), 0}, {"flock", offsetof(struct u_data, flock), 1},
{"flock", offsetof(struct lo_data, flock), 1}, {"no_flock", offsetof(struct u_data, flock), 0},
{"no_flock", offsetof(struct lo_data, flock), 0}, {"xattr", offsetof(struct u_data, xattr), 1},
{"xattr", offsetof(struct lo_data, xattr), 1}, {"no_xattr", offsetof(struct u_data, xattr), 0},
{"no_xattr", offsetof(struct lo_data, xattr), 0}, {"timeout=%lf", offsetof(struct u_data, timeout), 0},
{"timeout=%lf", offsetof(struct lo_data, timeout), 0}, {"timeout=", offsetof(struct u_data, timeout_set), 1},
{"timeout=", offsetof(struct lo_data, timeout_set), 1}, {"cache=never", offsetof(struct u_data, cache), CACHE_NEVER},
{"cache=never", offsetof(struct lo_data, cache), CACHE_NEVER}, {"cache=auto", offsetof(struct u_data, cache), CACHE_NORMAL},
{"cache=auto", offsetof(struct lo_data, cache), CACHE_NORMAL}, {"cache=always", offsetof(struct u_data, cache), CACHE_ALWAYS},
{"cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS},
FUSE_OPT_END}; FUSE_OPT_END};
...@@ -99,85 +97,58 @@ passthrough_ll_help(void) { ...@@ -99,85 +97,58 @@ passthrough_ll_help(void) {
" -o cache=always Cache always\n"); " -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 static void
lo_init(void* userdata, struct fuse_conn_info* conn) { init_handler(void* userdata, struct fuse_conn_info* conn) {
// TODO init gkfs fuse_log(FUSE_LOG_DEBUG, "init handler \n");
struct lo_data* lo = (struct lo_data*) userdata; // struct u_data* lo = (struct u_data*) userdata;
bool has_flag; // bool has_flag;
if(lo->writeback) { // TODO check other capabilities e.g. FUSE_CAP_READDIRPLUS
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); // if(lo->writeback) {
if(lo->debug && has_flag) // has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
fuse_log(FUSE_LOG_DEBUG, "lo_init: activating writeback\n"); // 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->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
if(lo->debug && has_flag) // has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
fuse_log(FUSE_LOG_DEBUG, "lo_init: activating flock locks\n"); // 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 */ /* Disable the receiving and processing of FUSE_INTERRUPT requests */
conn->no_interrupt = 1; // conn->no_interrupt = 1;
} }
// Simplified inode structure static void
struct Inode { destroy_handler(void* userdata) {
std::string path; fuse_log(FUSE_LOG_DEBUG, "destroy handler \n");
struct stat st; // userdata is GekkoFuse* if passed
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 static void
lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) { lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) {
fuse_log(FUSE_LOG_DEBUG, "lookup handler ino %u\n", parent); fuse_log(FUSE_LOG_DEBUG, "lookup handler ino %u\n", parent);
auto* ud = udata(req);
auto* parent_inode = get_inode(parent); auto* parent_inode = get_inode(parent);
if(!parent_inode) { if(!parent_inode) {
fuse_log(FUSE_LOG_DEBUG, "this error 1 \n", parent);
fuse_reply_err(req, ENOENT); fuse_reply_err(req, ENOENT);
return; return;
} }
std::string child = parent_inode->path + name; std::string child = parent_inode->path + name;
fuse_log(FUSE_LOG_DEBUG, "lookup %s\n", child.c_str()); 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; struct stat st;
int rc = gkfs::syscall::gkfs_stat(child, &st); int rc = gkfs::syscall::gkfs_stat(child, &st);
if(rc < 0) { if(rc < 0) {
...@@ -185,19 +156,19 @@ lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) { ...@@ -185,19 +156,19 @@ lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) {
fuse_reply_err(req, ENOENT); fuse_reply_err(req, ENOENT);
return; return;
} }
fuse_ino_t ino = alloc_inode(child);
ino_map[ino].st = st; ino_map[ino].st = st;
fuse_entry_param e = {}; fuse_entry_param e = {};
e.ino = ino; e.ino = ino;
e.attr = st; e.attr = st;
e.attr_timeout = 1.0; e.attr_timeout = ud->timeout;
e.entry_timeout = 1.0; e.entry_timeout = ud->timeout;
fuse_reply_entry(req, &e); fuse_reply_entry(req, &e);
} }
static void static void
getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
fuse_log(FUSE_LOG_DEBUG, "getattr handler \n"); fuse_log(FUSE_LOG_DEBUG, "getattr handler \n");
auto* ud = udata(req);
auto* inode = get_inode(ino); auto* inode = get_inode(ino);
if(!inode) { if(!inode) {
fuse_reply_err(req, ENOENT); fuse_reply_err(req, ENOENT);
...@@ -212,39 +183,42 @@ getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { ...@@ -212,39 +183,42 @@ getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
return; return;
} }
inode->st = st; inode->st = st;
fuse_reply_attr(req, &st, 1.0); fuse_reply_attr(req, &st, ud->timeout);
} }
static void static void
setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set, setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set,
struct fuse_file_info* fi) { 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); auto* inode = get_inode(ino);
if(!inode) { if(!inode) {
fuse_reply_err(req, ENOENT); fuse_reply_err(req, ENOENT);
return; return;
} }
// TODO how to change attr?
int rc = 0; if(to_set & FUSE_SET_ATTR_SIZE) {
if(rc) { off_t new_size = attr->st_size;
fuse_reply_err(req, rc); 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; 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_atim = attr->st_atim;
inode->st.st_blksize = attr->st_blksize; if(to_set & FUSE_SET_ATTR_MTIME)
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;
inode->st.st_mtim = attr->st_mtim; inode->st.st_mtim = attr->st_mtim;
inode->st.st_nlink = attr->st_nlink;
inode->st.st_rdev = attr->st_rdev; // TODO because we cannot save the attributes in gekko, we just return the
inode->st.st_size = attr->st_size; // buffered results of stat
inode->st.st_uid = attr->st_uid; fuse_reply_attr(req, &inode->st, ud->timeout);
fuse_reply_attr(req, &inode->st, 1.0); return;
} }
static void static void
...@@ -319,6 +293,7 @@ static void ...@@ -319,6 +293,7 @@ static void
create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode, create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode,
struct fuse_file_info* fi) { struct fuse_file_info* fi) {
fuse_log(FUSE_LOG_DEBUG, "create handler \n"); fuse_log(FUSE_LOG_DEBUG, "create handler \n");
auto* ud = udata(req);
auto* parent_inode = get_inode(parent); auto* parent_inode = get_inode(parent);
if(!parent_inode) { if(!parent_inode) {
fuse_reply_err(req, ENOENT); 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, ...@@ -346,12 +321,13 @@ create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode,
return; return;
} }
fuse_ino_t ino = alloc_inode(path); fuse_ino_t ino = alloc_inode(path);
fuse_log(FUSE_LOG_DEBUG, "create new inode ino %i\n", ino);
ino_map[ino].st = st; ino_map[ino].st = st;
fuse_entry_param e = {}; fuse_entry_param e = {};
e.ino = ino; e.ino = ino;
e.attr = st; e.attr = st;
e.attr_timeout = 1.0; e.attr_timeout = ud->timeout;
e.entry_timeout = 1.0; e.entry_timeout = ud->timeout;
fuse_reply_create(req, &e, fi); 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, ...@@ -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; size_t bytes_filled = 0;
off_t pos = off; size_t pos = off;
while(pos < open_dir->size()) { while(pos < open_dir->size()) {
auto de = open_dir->getdent(pos); 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, ...@@ -456,83 +432,109 @@ readdir_handler(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
free(buf); free(buf);
} }
/// releases file descriptor, not connected to lookup_count
static void static void
init_handler(void* userdata, struct fuse_conn_info* conn) { release_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
// userdata is GekkoFuse* if passed fuse_log(FUSE_LOG_DEBUG, "release handler \n");
// optional: adjust conn->max_write, enable writeback etc. auto* inode = get_inode(ino);
} if(!inode) {
fuse_log(FUSE_LOG_DEBUG, "release here \n");
static const struct fuse_lowlevel_ops lo_oper = { fuse_reply_err(req, ENOENT);
.init = init_handler, // lo_init, return;
//.destroy = lo_destroy, }
.lookup = lookup_handler, int lc = gkfs::syscall::gkfs_close(fi->fh);
//.forget = lo_forget, if(lc < 0) {
.getattr = getattr_handler, fuse_log(FUSE_LOG_DEBUG, "release there \n");
.setattr = setattr_handler, fuse_reply_err(req, 1);
//.readlink = lo_readlink, return;
//.mknod = lo_mknod, }
//.mkdir = lo_mkdir, fuse_log(FUSE_LOG_DEBUG, "release success \n");
//.unlink = lo_unlink, fuse_reply_err(req, 0);
//.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
};
int /// decrement lookup count
main(int argc, char* argv[]) { static void
struct fuse_args args = FUSE_ARGS_INIT(argc, argv); forget_handler(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) {
struct fuse_session* se; fuse_log(FUSE_LOG_DEBUG, "forget handler \n");
struct fuse_cmdline_opts opts;
struct fuse_loop_config* config;
struct lo_data lo = {.debug = 0, .writeback = 0};
int ret = -1;
/* Don't mask creation mode, kernel already did that */ auto it = ino_map.find(ino);
umask(0); // TODO do we need this and why? 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); if(inode.lookup_count == 0) { // && inode.open_count == 0
lo.root.next = lo.root.prev = &lo.root; path_map.erase(inode.path);
lo.root.fd = -1; ino_map.erase(it);
lo.cache = CACHE_NORMAL; 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 // TODO how to handle mount point
int res = gkfs_init(); int res = gkfs_init();
if(res != 0) { if(res != 0) {
...@@ -556,7 +558,107 @@ main(int argc, char* argv[]) { ...@@ -556,7 +558,107 @@ main(int argc, char* argv[]) {
ino_map[FUSE_ROOT_ID] = {root_path, {}, 1}; ino_map[FUSE_ROOT_ID] = {root_path, {}, 1};
ino_map[FUSE_ROOT_ID].st = st; ino_map[FUSE_ROOT_ID].st = st;
std::cout << "root node allocated" << std::endl; 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) if(fuse_parse_cmdline(&args, &opts) != 0)
return 1; return 1;
...@@ -565,86 +667,69 @@ main(int argc, char* argv[]) { ...@@ -565,86 +667,69 @@ main(int argc, char* argv[]) {
fuse_cmdline_help(); fuse_cmdline_help();
fuse_lowlevel_help(); fuse_lowlevel_help();
passthrough_ll_help(); passthrough_ll_help();
ret = 0; err_cleanup1(opts, args);
goto err_out1; return 0;
} else if(opts.show_version) { } else if(opts.show_version) {
printf("FUSE library version %s\n", fuse_pkgversion()); printf("FUSE library version %s\n", fuse_pkgversion());
fuse_lowlevel_version(); fuse_lowlevel_version();
ret = 0; ret = 0;
goto err_out1; err_cleanup1(opts, args);
return 0;
} }
if(opts.mountpoint == NULL) { if(opts.mountpoint == NULL) {
printf("usage: %s [options] <mountpoint>\n", argv[0]); printf("usage: %s [options] <mountpoint>\n", argv[0]);
printf(" %s --help\n", argv[0]); printf(" %s --help\n", argv[0]);
ret = 1; 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; return 1;
lo.debug = opts.debug; ud.debug = opts.debug;
lo.root.refcount = 2; if(!ud.timeout_set) {
if(lo.source) { switch(ud.cache) {
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) {
case CACHE_NEVER: case CACHE_NEVER:
lo.timeout = 0.0; ud.timeout = 0.0;
break; break;
case CACHE_NORMAL: case CACHE_NORMAL:
lo.timeout = 1.0; ud.timeout = 1.0;
break; break;
case CACHE_ALWAYS: case CACHE_ALWAYS:
lo.timeout = 86400.0; ud.timeout = 86400.0;
break; break;
} }
} else if(lo.timeout < 0) { } else if(ud.timeout < 0) {
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", lo.timeout); fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", ud.timeout);
exit(1); 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"); init_gekkofs();
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
if(se == NULL)
goto err_out1;
if(fuse_set_signal_handlers(se) != 0) se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), &ud);
goto err_out2; if(se == nullptr) {
err_cleanup1(opts, args);
return 0;
}
if(fuse_session_mount(se, opts.mountpoint) != 0) if(fuse_set_signal_handlers(se) != 0) {
goto err_out3; 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); fuse_daemonize(opts.foreground);
...@@ -668,20 +753,5 @@ main(int argc, char* argv[]) { ...@@ -668,20 +753,5 @@ main(int argc, char* argv[]) {
} }
fuse_session_unmount(se); 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; return ret ? 1 : 0;
} }
...@@ -143,6 +143,15 @@ if (GKFS_RENAME_SUPPORT) ...@@ -143,6 +143,15 @@ if (GKFS_RENAME_SUPPORT)
) )
endif () 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) if (GKFS_INSTALL_TESTS)
install(DIRECTORY harness install(DIRECTORY harness
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration
......
...@@ -34,7 +34,7 @@ from pathlib import Path ...@@ -34,7 +34,7 @@ from pathlib import Path
from harness.logger import logger, initialize_logging, finalize_logging from harness.logger import logger, initialize_logging, finalize_logging
from harness.cli import add_cli_options, set_default_log_formatter from harness.cli import add_cli_options, set_default_log_formatter
from harness.workspace import Workspace, FileCreator 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 from harness.reporter import report_test_status, report_test_headline, report_assertion_pass
def pytest_configure(config): def pytest_configure(config):
...@@ -225,3 +225,15 @@ def gkfwd_client_factory(test_workspace): ...@@ -225,3 +225,15 @@ def gkfwd_client_factory(test_workspace):
""" """
return FwdClientCreator(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' ...@@ -74,6 +74,7 @@ gkfwd_client_log_level = 'all'
gkfwd_client_log_syscall_filter = 'epoll_wait,epoll_create' gkfwd_client_log_syscall_filter = 'epoll_wait,epoll_create'
gkfwd_daemon_active_log_pattern = r'Startup successful. Daemon is ready.' gkfwd_daemon_active_log_pattern = r'Startup successful. Daemon is ready.'
gkfs_fuse_client = 'fuse_client'
def get_ip_addr(iface): def get_ip_addr(iface):
return netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr'] return netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']
...@@ -1637,3 +1638,78 @@ class ShellFwdClient: ...@@ -1637,3 +1638,78 @@ class ShellFwdClient:
@property @property
def cwd(self): def cwd(self):
return self._workspace.twd 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