I have been developing [my own
wrapper](https://code.dlang.org/packages/oxfuse) around **libfuse
v3** for more than 3 years. I originally needed it for [my
semantic filesystem project](https://codeberg.org/os-18/vitis).
Existing solutions such as **dfuse** and **fuse-d** were based on
the deprecated **libfuse v2** and very limited functionally.
Using some ideas from **dfuse**, I developed a much more advanced
wrapper around **libfuse** named **OXFuse** (Object-oriented
eXtension for FUSE) To create a filesystem, you inherit from the
`FileSystem` class and override the required methods representing
file operations. Developing a filesystem with **OXFuse** requires
solid Linux systems programming knowledge. To get started, you
can look at the code in the `examples/` directory at the root of
the project.
### libfuse issues
Unfortunately for the FS development community, libfuse has
broken backward binary compatibility multiple times by adding new
fields to some structures and/or changing their location
(althrough it seems this should no longer happen). Because of
this, I had to introduce multiple versions of the
`fuse_file_info` and `fuse_config` structures inside OXFuse.
During compilation, the current libfuse version is detected
automatically, and the correct structure versions are selected
for the final build. Developers are not expected to use these
structures directly - **OXFuse** provides the `FileInfo` and
`FuseConfig` classes as abstractions instead.
### Type for paths
One of the fundamental design decisions I made very early on was
to use `string` in filesystem operation signatures. For example:
```d
void rename(string src, string dst, uint flags);
```
I know that in similar cases it is customary to use
`const(char)[]`, but I never found convincing reasons to do so.
My experience developing a real filesystem with **OXFuse** has
shown that this decision was the right one.
Strictly speaking, paths should probably be represented as raw
byte sequences such as `const(ubyte)[]`. However, `string` is
still fundamentally just a byte sequence (not programmatically
tied to any specific encoding), while also providing proper
thread safety through immutability, improved readability, and
mush better ergonomics in everiday use.
### Pthread and D-Runtime
libfuse constantly spawns new POSIX threads when file operations
are invoked. The main challenge when writing a D wrapper is
correctly attaching those threads to the D-Runtime and detaching
them afterward. D provides `thread_attachThis` and
`thread_detachThis` for this purpose.
The difficult part is determing when a C thread created by
libfuse is actually terminating. In **dfuse** this issue was
never fully solved, and I myself arrived at a proper solution for
**OXFuse** only recently:
```d
bool attached;
__gshared pthread_key_t detachKey;
shared static this() {
pthread_key_create(&detachKey, &detach);
}
extern(C) void detach(void* ptr) nothrow {
attached = false;
collectException(thread_detachThis());
}
void attach() nothrow {
if (!attached) {
collectException(thread_attachThis());
// create an association between current thread and the
specified key
pthread_setspecific(detachKey, cast(void*)1);
attached = true;
}
}
```
The `attach()` function is called for every file operation. When
the underlying C thread is destroyed, our `detach()` function is
guaranteed to be called because of the association with
`pthread_key_t`.
As a result, the GC works correctly and there are no longer
issues related to controlled filesystem shutdown. I am mentioning
this here specifically for people who have run into problems
coordinating C (pthread) threads with the D-Runtime.