Skip to content

allow passing libfuse.dylib path via env variable#98

Merged
billziss-gh merged 4 commits into
winfsp:masterfrom
adamvduke:allow-runtime-dylib-location
Aug 13, 2025
Merged

allow passing libfuse.dylib path via env variable#98
billziss-gh merged 4 commits into
winfsp:masterfrom
adamvduke:allow-runtime-dylib-location

Conversation

@adamvduke
Copy link
Copy Markdown
Contributor

I'd like to be able to specify the path to the libfuse dylib within the application bundle to support end users who do not have a fuse implementation installed or do not have admin rights to install a fuse implementation.

I was able to demonstrate that this approach works in a .app file on a system with no fuse implementation installed using fuse-t. The application sets the environment variable, and is then able to use a cgofuse implementation to mount a file system via fuse-t.

Please let me know if there are any style or naming issues that you'd like addressed.

We're using the commercial license, and need to be able to specify the
path to the libfuse dylib within the application bundle to support end
users who do not have a fuse implementation installed or do not have
admin rights to install a fuse implementation.
@billziss-gh
Copy link
Copy Markdown
Collaborator

Is there any existing environment variable (or other mechanism) that other FUSE-T apps use for this purpose?

@billziss-gh
Copy link
Copy Markdown
Collaborator

Some further thoughts to support this scenario:

If we were willing to take a dependency on CoreFoundation we could try something like (untested, no error checking):

char path[PATH_MAX];
bundle = CFBundleGetMainBundle();
allurl = CFBundleCopyPrivateFrameworksURL(bundle);
frmurl = CFURLCreateCopyAppendingPathComponent(0, allurl, CFSTR("fuse-t.framework/fuse-t"), 0);
CFURLGetFileSystemRepresentation(frmurl, 1, path, sizeof path);
CFRelease(frmurl);
CFRelease(allurl);

If we wanted to avoid the dependency on CoreFoundation we could use _NSGetExecutablePath and knowledge of the application bundle format to locate the bundle's private frameworks. This should succeed only if the executable was inside what looked like a macOS bundle (e.g. the Contents/Info.plist file exists, etc.)

@adamvduke
Copy link
Copy Markdown
Contributor Author

I'm not familiar with any other fuse-t based applications. My perception is that they would require the user to install fuse-t via its pkg installer, install fuse-t as part of the app installation, or this is a use case that hasn't been attempted before.

The fuse-t pkg installer requires admin privileges, because it installs to a system wide path owned by root. That admin requirement is something I need to avoid, so that non-privileged users can use it successfully.

Fuse-t implements fuse_mount_core, and within fuse_mount_core it uses fork and execv to launch a file server (nfs or smb) which is then mounted via the mount command. Fuse-t attempts to locate the server binary via the environment variable FUSE_NFSSRV_PATH, and if not set, falls back to a well known path /usr/local/bin/go-nfsv4 created by the fuse-t installer. https://github.com/macos-fuse-t/libfuse/blob/dee84509ccef2408505a6b8cb625d1cbb8842304/lib/mount_darwin.c#L469-L482

I'm also not familiar with the CFBundleGetMainBundle and associated APIs. At the moment, we don't plan on packaging the entire fuse-t.framework in the bundled application, only the libfuse-t.dylib and go-nfsv4 binary.

Is there a particular concern about the environment variable approach as opposed to something more sophisticated like using the CoreFoundation bundle APIs? (usability, security, discoverability, etc...)

The environment variable approach seems the most flexible, and allows for other approaches like the CFBundleGetMainBundle APIs, which can then set that environment variable before moving forward, and also moves the requirement of locating the dylib out of CGO. For instance, our intended use is a python frontend that sets the variable before creating a subprocess that launches a go program using cgofuse.

@billziss-gh
Copy link
Copy Markdown
Collaborator

billziss-gh commented Aug 12, 2025

I should start by saying that I am not intimately familiar with FUSE-T. For this reason some of my questions may show my confusion. I tried to educate myself by reading the source, but it seems that the project is not fully open-source.

Fuse-t attempts to locate the server binary via the environment variable FUSE_NFSSRV_PATH, and if not set, falls back to a well known path /usr/local/bin/go-nfsv4 created by the fuse-t installer.

This is very useful information. Thank you.

Could we use the FUSE_NFSSRV_PATH environment variable, that FUSE-T already uses to locate its NFS server component, in order to locate the dylib?

Is there a particular concern about the environment variable approach as opposed to something more sophisticated like using the CoreFoundation bundle APIs? (usability, security, discoverability, etc...)

In general I am not a big fan of environment variables:

  • They tend to pollute user profiles.
  • Their security is rather weak as they are practically accessible by all apps started in the same desktop/shell.

The fuse-t pkg installer requires admin privileges, because it installs to a system wide path owned by root. That admin requirement is something I need to avoid, so that non-privileged users can use it successfully.

This is a legitimate reason and we should try to support it. I agree that an environment variable may be the easiest fix here.

Presumably you are also including the FUSE-T NFS server in your package and that server listens to TCP/UDP ports that are not restricted to root (normally NFS servers use privileged ports). Do I understand correctly?

Comment thread fuse/host_cgo.go Outdated
return 0;

void *h;
void *h = NULL;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency please use:

void *h = 0;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread fuse/host_cgo.go Outdated
h = dlopen("/usr/local/lib/libfuse.2.dylib", RTLD_NOW); // MacFUSE/OSXFuse >= v4
// runtime path for bundled dylib in e.g. Awesome.app/Contents/Frameworks/libfuse.dylib
const char *dylib_path = getenv("CGOFUSE_LIBFUSE_PATH");
if(dylib_path)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency please use:

if (0 != dylib_path)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@adamvduke
Copy link
Copy Markdown
Contributor Author

I should start by saying that I am not intimately familiar with FUSE-T. For this reason some of my questions may show my confusion. I tried to educate myself by reading the source, but it seems that the project is not fully open-source.

Any confusion is totally reasonable in my opinion. As far as I can tell by reading source and communicating with the maintainer, fuse-t is open source except for the server binary that it mounts. The fuse-t.framework bundles its own fork of libfuse that does argument parsing and boots the file server as part of fuse_mount_core.

The framework does include an initializer that seems like it's intended to set the FUSE_NFSSRV_PATH environment variable, but the released versions of the framework do not include the initializer symbol at all based on output from nm. The initializer also does not work correctly when run on a system that hasn't had fuse-t installed by the pkg installer anyway.

Could we use the FUSE_NFSSRV_PATH environment variable, that FUSE-T already uses to locate its NFS server component, in order to locate the dylib?

This might work, but only if the fuse-t framework has been installed via its pkg installer, which requires admin privileges. The two things needed to make fuse-t work with cgofuse are the location of the dylib itself, and the location of the server binary that it launches during FUSE startup. In my particular case, the two files are installed on a CI server via the pkg installer, and then copied into the app bundle during the build before code signing. The location is somewhat arbitrary within the bounds of what is considered a valid app bundle. At startup, our user facing application locates the two files within the bundle, and sets the environment variables before creating a helper subprocess that leverages cgofuse.

In general I am not a big fan of environment variables:

  • They tend to pollute user profiles.
  • Their security is rather weak as they are practically accessible by all apps started in the same desktop/shell.

Heard on both points. In terms of security, I reasoned that we're not passing sensitive data, and if the goal was to alter the environment variable to point to a malicious dylib, at that point it's likely that the bad actor can just overwrite the dylib at the well known path anyway.

Presumably you are also including the FUSE-T NFS server in your package and that server listens to TCP/UDP ports that are not restricted to root (normally NFS servers use privileged ports). Do I understand correctly?

That's correct. The build phase of our app copies the libfuse-t.dylib and go-nfsv4 binary into its bundle at build time. When calling fuse_mount_core, fuse-t uses the environment variable FUSE_NFSSRV_PATH, or a well known path to find the binary for the NFS server, start it, and bind to a port in the 52XXX range. It calls the host OS /sbin/mount binary to mount the server, and then communicates to the libfuse dylib via some sockets that are named in the libfuse implementation with environment variables.

@billziss-gh billziss-gh merged commit e7ac0c0 into winfsp:master Aug 13, 2025
6 checks passed
@billziss-gh
Copy link
Copy Markdown
Collaborator

I have merged this in. Thank you for your contribution.

@adamvduke
Copy link
Copy Markdown
Contributor Author

Thanks for your work in maintaining the project.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants