Skip to content

FUSE 2 API: struct fuse_operations has getdir before readlink (libfuse: readlink before getdir) - is this intended? #693

Description

@ThomasWaldmann

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 readlink callback ends up in WinFsp's getdir slot. WinFsp only calls getdir if there is no readdir, so nothing crashes and this easily goes unnoticed.
  • the binding's getdir (NULL) ends up in WinFsp's readlink slot, so 0 != f->ops.readlink in fsp_fuse_loop_start is false, has_symlinks stays false and all S_IFLNK files 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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions