FUSE 2 API: struct fuse_operations has getdir before readlink (libfuse: readlink before getdir) - is this intended?
Bug Report
Not sure whether this is a bug or intended, thus the question: WinFsp's FUSE 2 struct fuse_operations has the getdir and readlink members in a different order than libfuse 2.x has them.
WinFsp, inc/fuse/fuse.h:
struct fuse_operations
{
/* S - supported by WinFsp */
/* S */ int (*getattr)(const char *path, struct fuse_stat *stbuf);
/* S */ int (*getdir)(const char *path, fuse_dirh_t h, fuse_dirfil_t filler);
/* S */ int (*readlink)(const char *path, char *buf, size_t size);
/* S */ int (*mknod)(const char *path, fuse_mode_t mode, fuse_dev_t dev);libfuse 2.9.9, include/fuse.h:
struct fuse_operations {
int (*getattr) (const char *, struct stat *);
int (*readlink) (const char *, char *, size_t);
/* Deprecated, use readdir() instead */
int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
int (*mknod) (const char *, mode_t, dev_t);All the other members are in the same order as in libfuse (and WinFsp's FUSE 3 struct fuse3_operations has no getdir, so there is no such difference there).
For C / C++ file systems that get compiled against WinFsp's header, this does not matter. But it does for language bindings that describe the struct layout themselves, following libfuse: fusepy / mfusepy (Python, ctypes) have getattr, readlink, getdir. With that:
- the binding's
readlinkcallback ends up in WinFsp'sgetdirslot. WinFsp only callsgetdirif there is noreaddir, so nothing crashes and this easily goes unnoticed. - the binding's
getdir(NULL) ends up in WinFsp'sreadlinkslot, so0 != f->ops.readlinkinfsp_fuse_loop_startis false,has_symlinksstays false and allS_IFLNKfiles are reported as regular files (no reparse point).
I have proposed to use WinFsp's order in mfusepy for Windows (mxmlnkn/mfusepy#52), so this is not urgent for us - but I wanted to ask:
- Is the different order intended (then the bindings just have to follow it, maybe it could be mentioned in the docs / in the header), or is it an accident from the early days (the header has it that way since 2016)?
- If it is an accident: I guess it can not be changed without breaking the ABI for all existing binaries, so it would rather be something to document?
How to Reproduce
- a FUSE file system using mfusepy 3.1.1 (or fusepy) that implements
getattr,readdir,readlinkand reportsS_IFLNKfor some path. I found this withborg mount(borgbackup/borg#10391) on a backup archive containing symlinks. - mount it, look at the symlinks.
Behaviors
Expected: the symlinks show up as symlinks (reparse points), readlink gets called.
Actual: readlink never gets called (not even the readlink("/") probe when the file system starts), the symlinks show up as regular files (FileAttributes=0, ReparseTag=0 in the WinFsp debug log for the Create of a symlink).
With getdir / readlink swapped in the binding's struct definition, everything works as expected (file symlinks, directory symlinks, dangling symlinks).
Environment
- OS version and build: Windows 11 Pro, 10.0.26200
- WinFsp version and build: 2.1
Source: winfsp/winfsp