Skip to content
GitLab
Explore
Sign in
Commits on Source (4)
fix mkdir, improve test
· 5d44c3de
sevenuz
authored
Aug 12, 2025
5d44c3de
unlink, rmdir, releasedir and access handlers
· d3451423
sevenuz
authored
Aug 12, 2025
d3451423
lseek
· 6926bcdc
sevenuz
authored
Aug 12, 2025
6926bcdc
symlink support, fsync, tmpfile (not supported though)
· 2ff62a64
sevenuz
authored
Aug 13, 2025
2ff62a64
Show whitespace changes
Inline
Side-by-side
include/client/fuse/fuse_client.hpp
View file @
2ff62a64
...
...
@@ -81,6 +81,7 @@ struct __dirstream {
#include
<unordered_map>
#include
<string>
#include
<mutex>
#include
<cstdlib>
#ifdef __FreeBSD__
#include
<sys/socket.h>
...
...
src/client/fuse/fuse_client.cpp
View file @
2ff62a64
...
...
@@ -66,6 +66,18 @@ udata(fuse_req_t req) {
return
(
struct
u_data
*
)
fuse_req_userdata
(
req
);
}
static
std
::
string
get_path
(
const
Inode
*
inode
,
const
char
*
name
)
{
if
(
name
[
0
]
==
'/'
)
{
return
std
::
string
(
name
);
}
if
(
inode
->
path
==
"/"
)
{
return
inode
->
path
+
name
;
}
else
{
return
inode
->
path
+
"/"
+
name
;
}
}
static
const
struct
fuse_opt
lo_opts
[]
=
{
{
"writeback"
,
offsetof
(
struct
u_data
,
writeback
),
1
},
{
"no_writeback"
,
offsetof
(
struct
u_data
,
writeback
),
0
},
...
...
@@ -135,7 +147,7 @@ lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) {
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
std
::
string
child
=
parent_inode
->
path
+
name
;
std
::
string
child
=
get_path
(
parent_inode
,
name
)
;
fuse_log
(
FUSE_LOG_DEBUG
,
"lookup %s
\n
"
,
child
.
c_str
());
...
...
@@ -152,7 +164,6 @@ lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) {
struct
stat
st
;
int
rc
=
gkfs
::
syscall
::
gkfs_stat
(
child
,
&
st
);
if
(
rc
<
0
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"that error 2
\n
"
,
parent
);
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
...
...
@@ -240,6 +251,18 @@ open_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
fuse_reply_open
(
req
,
fi
);
}
static
void
lseek_handler
(
fuse_req_t
req
,
fuse_ino_t
ino
,
off_t
off
,
int
whence
,
struct
fuse_file_info
*
fi
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"lseek handler
\n
"
);
int
lc
=
gkfs
::
syscall
::
gkfs_lseek
(
fi
->
fh
,
off
,
whence
);
if
(
lc
<
0
)
{
fuse_reply_err
(
req
,
1
);
return
;
}
fuse_reply_lseek
(
req
,
lc
);
}
static
void
read_handler
(
fuse_req_t
req
,
fuse_ino_t
ino
,
size_t
size
,
off_t
off
,
struct
fuse_file_info
*
fi
)
{
...
...
@@ -299,7 +322,7 @@ create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode,
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
std
::
string
path
=
parent_inode
->
path
+
name
;
std
::
string
path
=
get_path
(
parent_inode
,
name
)
;
int
rc
=
gkfs
::
syscall
::
gkfs_create
(
path
,
mode
);
if
(
rc
==
-
1
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"here here mode %i flags %i
\n
"
,
mode
,
...
...
@@ -331,6 +354,34 @@ create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode,
fuse_reply_create
(
req
,
&
e
,
fi
);
}
// create a normal file with generated name?
// fake tmp file because it will be listed in the directory it is created in
static
void
tmpfile_handler
(
fuse_req_t
req
,
fuse_ino_t
parent
,
mode_t
mode
,
struct
fuse_file_info
*
fi
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"tmpfile handler
\n
"
);
fuse_reply_err
(
req
,
ENOTSUP
);
}
/// TODO normally, the file should only be removed if the lookup count is zero,
/// problem?
static
void
unlink_handler
(
fuse_req_t
req
,
fuse_ino_t
parent
,
const
char
*
name
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"unlink handler
\n
"
);
auto
*
parent_inode
=
get_inode
(
parent
);
if
(
!
parent_inode
)
{
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
std
::
string
path
=
get_path
(
parent_inode
,
name
);
int
rc
=
gkfs
::
syscall
::
gkfs_remove
(
path
);
if
(
rc
==
-
1
)
{
fuse_reply_err
(
req
,
1
);
return
;
}
fuse_reply_err
(
req
,
0
);
}
static
void
opendir_handler
(
fuse_req_t
req
,
fuse_ino_t
ino
,
struct
fuse_file_info
*
fi
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"opendir handler
\n
"
);
...
...
@@ -432,6 +483,24 @@ readdir_handler(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
free
(
buf
);
}
static
void
releasedir_handler
(
fuse_req_t
req
,
fuse_ino_t
ino
,
struct
fuse_file_info
*
fi
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"releasedir handler
\n
"
);
GkfsDir
*
dir_ptr
=
reinterpret_cast
<
GkfsDir
*>
(
fi
->
fh
);
if
(
CTX
->
interception_enabled
()
&&
CTX
->
file_map
()
->
exist
(
dir_ptr
->
fd
))
{
int
fd
=
dir_ptr
->
fd
;
int
ret
=
gkfs
::
syscall
::
gkfs_close
(
fd
);
// Close GekkoFS internal FD
if
(
dir_ptr
->
path
)
{
// Check if path was strdup'd
free
(
dir_ptr
->
path
);
}
free
(
dir_ptr
);
// Free the DIR struct itself
fuse_reply_err
(
req
,
ret
);
return
;
}
fuse_reply_err
(
req
,
0
);
}
/// releases file descriptor, not connected to lookup_count
static
void
release_handler
(
fuse_req_t
req
,
fuse_ino_t
ino
,
struct
fuse_file_info
*
fi
)
{
...
...
@@ -484,17 +553,37 @@ 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
fsync_handler
(
fuse_req_t
req
,
fuse_ino_t
ino
,
int
datasync
,
struct
fuse_file_info
*
fi
)
{
// on datasync > 0, metadata should not be flushed
flush_handler
(
req
,
ino
,
fi
);
}
static
void
access_handler
(
fuse_req_t
req
,
fuse_ino_t
ino
,
int
mask
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"access handler
\n
"
);
auto
*
inode
=
get_inode
(
ino
);
if
(
!
inode
)
{
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
int
lc
=
gkfs
::
syscall
::
gkfs_access
(
inode
->
path
,
mask
,
true
);
if
(
lc
<
0
)
{
fuse_reply_err
(
req
,
1
);
return
;
}
fuse_reply_err
(
req
,
0
);
}
...
...
@@ -507,7 +596,9 @@ mkdir_handler(fuse_req_t req, fuse_ino_t parent, const char* name,
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
std
::
string
path
=
parent_inode
->
path
+
name
;
std
::
string
path
=
get_path
(
parent_inode
,
name
);
fuse_log
(
FUSE_LOG_DEBUG
,
"mkdir parent %s name %s
\n
"
,
parent_inode
->
path
.
c_str
(),
name
);
int
rc
=
gkfs
::
syscall
::
gkfs_create
(
path
,
mode
|
S_IFDIR
);
if
(
rc
==
-
1
)
{
fuse_reply_err
(
req
,
1
);
...
...
@@ -529,10 +620,99 @@ mkdir_handler(fuse_req_t req, fuse_ino_t parent, const char* name,
e
.
attr_timeout
=
ud
->
timeout
;
e
.
entry_timeout
=
ud
->
timeout
;
fuse_reply_entry
(
req
,
&
e
);
fuse_log
(
FUSE_LOG_DEBUG
,
"flush success
\n
"
);
}
static
void
rmdir_handler
(
fuse_req_t
req
,
fuse_ino_t
parent
,
const
char
*
name
)
{
auto
*
parent_inode
=
get_inode
(
parent
);
if
(
!
parent_inode
)
{
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
std
::
string
path
=
get_path
(
parent_inode
,
name
);
int
rc
=
gkfs
::
syscall
::
gkfs_rmdir
(
path
);
if
(
rc
==
-
1
)
{
fuse_reply_err
(
req
,
1
);
return
;
}
fuse_reply_err
(
req
,
0
);
}
static
void
readlink_handler
(
fuse_req_t
req
,
fuse_ino_t
ino
)
{
#ifdef HAS_SYMLINKS
auto
*
inode
=
get_inode
(
ino
);
if
(
!
inode
)
{
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
char
link
[
PATH_MAX
];
int
rc
=
gkfs
::
syscall
::
gkfs_readlink
(
inode
->
path
,
link
,
PATH_MAX
);
if
(
rc
==
-
1
)
{
fuse_reply_err
(
req
,
1
);
return
;
}
link
[
rc
]
=
'\0'
;
fuse_reply_readlink
(
req
,
link
);
#else
fuse_reply_err
(
req
,
ENOTSUP
);
#endif
}
static
void
symlink_handler
(
fuse_req_t
req
,
const
char
*
linkname
,
fuse_ino_t
parent
,
const
char
*
name
)
{
#ifdef HAS_SYMLINKS
fuse_log
(
FUSE_LOG_DEBUG
,
"symlink handler linkname %s name %s
\n
"
,
linkname
,
name
);
auto
*
ud
=
udata
(
req
);
auto
*
parent_inode
=
get_inode
(
parent
);
if
(
!
parent_inode
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"symlink parent inode ino %i
\n
"
,
parent
);
fuse_reply_err
(
req
,
ENOENT
);
return
;
}
std
::
string
path
=
get_path
(
parent_inode
,
name
);
std
::
string
target
=
get_path
(
parent_inode
,
linkname
);
if
(
target
.
rfind
(
ud
->
source
,
0
)
==
0
)
{
// starts with mount path
target
=
get_path
(
parent_inode
,
target
.
substr
(
strlen
(
ud
->
source
)).
c_str
());
}
fuse_log
(
FUSE_LOG_DEBUG
,
"mk symlink path %s target %s
\n
"
,
path
.
c_str
(),
target
.
c_str
());
int
rc
=
gkfs
::
syscall
::
gkfs_mk_symlink
(
path
,
target
);
if
(
rc
<
0
)
{
fuse_reply_err
(
req
,
1
);
return
;
}
// Stat the new symlink so we can reply with entry info
struct
stat
st
;
if
(
gkfs
::
syscall
::
gkfs_stat
(
path
,
&
st
)
<
0
)
{
fuse_log
(
FUSE_LOG_DEBUG
,
"stat failed
\n
"
);
fuse_reply_err
(
req
,
errno
);
return
;
}
fuse_log
(
FUSE_LOG_DEBUG
,
"stat mode %i, iflink %i
\n
"
,
st
.
st_mode
,
S_IFLNK
);
// TODO this meta is not saved and therefore on restart gone
// this shows the link on ls -l
st
.
st_mode
=
S_IFLNK
|
0777
;
// mark as symlink + full perms
fuse_entry_param
e
=
{};
e
.
ino
=
alloc_inode
(
path
);
e
.
attr
=
st
;
st
.
st_size
=
strlen
(
target
.
c_str
());
e
.
attr_timeout
=
ud
->
timeout
;
e
.
entry_timeout
=
ud
->
timeout
;
fuse_reply_entry
(
req
,
&
e
);
#else
fuse_reply_err
(
req
,
ENOTSUP
);
#endif
}
static
void
init_gekkofs
()
{
// TODO how to handle mount point
...
...
@@ -557,6 +737,7 @@ init_gekkofs() {
}
ino_map
[
FUSE_ROOT_ID
]
=
{
root_path
,
{},
1
};
ino_map
[
FUSE_ROOT_ID
].
st
=
st
;
path_map
[
root_path
]
=
FUSE_ROOT_ID
;
std
::
cout
<<
"root node allocated"
<<
std
::
endl
;
}
...
...
@@ -567,18 +748,19 @@ init_ll_ops(fuse_lowlevel_ops* ops) {
ops
->
setattr
=
setattr_handler
;
ops
->
open
=
open_handler
;
ops
->
create
=
create_handler
;
//
ops->unlink
ops
->
unlink
=
unlink_handler
;
ops
->
forget
=
forget_handler
;
// ops->forget_multi
//
ops->readlink
ops
->
readlink
=
readlink_handler
;
// ops->mknod
//
ops->symlink
ops
->
symlink
=
symlink_handler
;
// ops->rename
// ops->link
ops
->
flush
=
flush_handler
;
ops
->
release
=
release_handler
;
//
ops->fsync
ops
->
fsync
=
fsync_handler
;
// ops->write_buf
ops
->
lseek
=
lseek_handler
;
// xattr
// ops->setxattr
...
...
@@ -589,10 +771,10 @@ init_ll_ops(fuse_lowlevel_ops* ops) {
// directory
ops
->
lookup
=
lookup_handler
;
ops
->
mkdir
=
mkdir_handler
;
//
ops->rmdir
ops
->
rmdir
=
rmdir_handler
;
ops
->
readdir
=
readdir_handler
;
ops
->
opendir
=
opendir_handler
;
//
ops->releasedir
ops
->
releasedir
=
releasedir_handler
;
// ops->fsyncdir = nullptr;
// ops->readdirplus
...
...
@@ -601,13 +783,14 @@ init_ll_ops(fuse_lowlevel_ops* ops) {
ops
->
read
=
read_handler
;
// permission
//
ops->access
ops
->
access
=
access_handler
;
// misc
ops
->
init
=
init_handler
;
ops
->
destroy
=
destroy_handler
;
ops
->
statfs
=
nullpt
r
;
ops
->
tmpfile
=
tmpfile_handle
r
;
// ops->statfs = nullptr;
// ops->flock
// ops->getlk
// ops->setlk
...
...
@@ -617,14 +800,13 @@ init_ll_ops(fuse_lowlevel_ops* ops) {
// 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
)
{
err_cleanup1
(
fuse_cmdline_opts
opts
,
fuse_args
&
args
,
struct
u_data
&
ud
)
{
free
(
opts
.
mountpoint
);
free
(
ud
.
source
);
fuse_opt_free_args
(
&
args
);
std
::
cout
<<
"# Resources released"
<<
std
::
endl
;
}
...
...
@@ -667,13 +849,13 @@ main(int argc, char* argv[]) {
fuse_cmdline_help
();
fuse_lowlevel_help
();
passthrough_ll_help
();
err_cleanup1
(
opts
,
args
);
err_cleanup1
(
opts
,
args
,
ud
);
return
0
;
}
else
if
(
opts
.
show_version
)
{
printf
(
"FUSE library version %s
\n
"
,
fuse_pkgversion
());
fuse_lowlevel_version
();
ret
=
0
;
err_cleanup1
(
opts
,
args
);
err_cleanup1
(
opts
,
args
,
ud
);
return
0
;
}
...
...
@@ -681,7 +863,7 @@ main(int argc, char* argv[]) {
printf
(
"usage: %s [options] <mountpoint>
\n
"
,
argv
[
0
]);
printf
(
" %s --help
\n
"
,
argv
[
0
]);
ret
=
1
;
err_cleanup1
(
opts
,
args
);
err_cleanup1
(
opts
,
args
,
ud
);
return
0
;
}
...
...
@@ -709,25 +891,26 @@ main(int argc, char* argv[]) {
}
ud
.
source
=
strdup
(
opts
.
mountpoint
);
init_gekkofs
();
se
=
fuse_session_new
(
&
args
,
&
ll_ops
,
sizeof
(
ll_ops
),
&
ud
);
if
(
se
==
nullptr
)
{
err_cleanup1
(
opts
,
args
);
err_cleanup1
(
opts
,
args
,
ud
);
return
0
;
}
if
(
fuse_set_signal_handlers
(
se
)
!=
0
)
{
err_cleanup2
(
*
se
);
err_cleanup1
(
opts
,
args
);
err_cleanup1
(
opts
,
args
,
ud
);
return
0
;
}
if
(
fuse_session_mount
(
se
,
opts
.
mountpoint
)
!=
0
)
{
err_cleanup3
(
*
se
);
err_cleanup2
(
*
se
);
err_cleanup1
(
opts
,
args
);
err_cleanup1
(
opts
,
args
,
ud
);
return
0
;
}
...
...
src/client/gkfs_functions.cpp
View file @
2ff62a64
...
...
@@ -809,7 +809,7 @@ gkfs_statvfs(struct statvfs* buf) {
* @param fd
* @param offset
* @param whence
* @return
0
on success, -1 on failure
* @return
position
on success, -1 on failure
*/
off_t
gkfs_lseek
(
unsigned
int
fd
,
off_t
offset
,
unsigned
int
whence
)
{
...
...
@@ -822,7 +822,7 @@ gkfs_lseek(unsigned int fd, off_t offset, unsigned int whence) {
* @param gkfs_fd
* @param offset
* @param whence
* @return
0
on success, -1 on failure
* @return
position
on success, -1 on failure
*/
off_t
gkfs_lseek
(
shared_ptr
<
gkfs
::
filemap
::
OpenFile
>
gkfs_fd
,
off_t
offset
,
...
...
@@ -1835,6 +1835,10 @@ gkfs_mk_symlink(const std::string& path, const std::string& target_path) {
errno
=
ENOTSUP
;
return
-
1
;
}
}
else
{
// target is not in gekkofs
errno
=
ENOENT
;
return
-
1
;
}
if
(
check_parent_dir
(
path
))
{
...
...
@@ -1871,7 +1875,7 @@ gkfs_mk_symlink(const std::string& path, const std::string& target_path) {
* @param path
* @param buf
* @param bufsize
* @return
0
on success or -1 on error
* @return
path size
on success or -1 on error
*/
int
gkfs_readlink
(
const
std
::
string
&
path
,
char
*
buf
,
int
bufsize
)
{
...
...
tests/integration/conftest.template
View file @
2ff62a64
...
...
@@ -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()
tests/integration/fuse/test_basic_operations.py
View file @
2ff62a64
...
...
@@ -40,6 +40,7 @@ from harness.logger import logger
nonexisting
=
"
nonexisting
"
# somehow are multiple test causing an error in the fuse client...
def
test_read
(
gkfs_daemon
,
fuse_client
):
file
=
gkfs_daemon
.
mountdir
/
"
file
"
...
...
@@ -47,6 +48,7 @@ def test_read(gkfs_daemon, fuse_client):
dir
=
gkfs_daemon
.
mountdir
/
"
dir
"
# creation and removal
sh
.
bash
(
"
-c
"
,
"
echo baum >
"
+
str
(
file
))
assert
sh
.
ls
(
fuse_client
.
mountdir
)
==
"
file
\n
"
assert
sh
.
cat
(
file
)
==
"
baum
\n
"
...
...
@@ -55,3 +57,32 @@ def test_read(gkfs_daemon, fuse_client):
sh
.
truncate
(
"
-s
"
,
"
20
"
,
str
(
file2
))
assert
sh
.
wc
(
"
-c
"
,
str
(
file2
))
==
"
20
"
+
str
(
file2
)
+
"
\n
"
sh
.
mkdir
(
str
(
dir
))
assert
sh
.
ls
(
fuse_client
.
mountdir
)
==
"
dir file file2
\n
"
sh
.
cd
(
str
(
dir
))
assert
sh
.
pwd
()
==
str
(
dir
)
+
"
\n
"
sh
.
mkdir
(
"
-p
"
,
"
foo/bar
"
)
assert
sh
.
ls
()
==
"
foo
\n
"
sh
.
cd
(
"
foo
"
)
sh
.
rmdir
(
"
bar
"
)
sh
.
cd
(
"
..
"
)
sh
.
rmdir
(
"
foo
"
)
sh
.
rm
(
str
(
file2
))
assert
sh
.
ls
(
fuse_client
.
mountdir
)
==
"
dir file
\n
"
# lseek test (TODO doesn't use the lseek handler)
path
=
gkfs_daemon
.
mountdir
/
"
lseek_file
"
with
open
(
path
,
"
wb
"
)
as
f
:
f
.
write
(
b
"
HelloWorld
"
)
# 10 bytes
fd
=
os
.
open
(
path
,
os
.
O_RDONLY
)
pos
=
os
.
lseek
(
fd
,
5
,
os
.
SEEK_SET
)
# absolute seek
assert
pos
==
5
data
=
os
.
read
(
fd
,
5
)
assert
data
==
b
"
World
"
os
.
close
(
fd
)
os
.
remove
(
path
)
# symlink
assert
sh
.
pwd
()
==
str
(
dir
)
+
"
\n
"
sh
.
ln
(
"
-s
"
,
str
(
file
),
"
link
"
)
assert
sh
.
ls
(
str
(
dir
))
==
"
link
\n
"
assert
sh
.
cat
(
"
link
"
)
==
"
baum
\n
"