mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 08:23:05 -05:00
139 lines
7.4 KiB
Plaintext
139 lines
7.4 KiB
Plaintext
= SSHFS Port Case Study
|
|
|
|
This document is a case study in porting SSHFS to Windows and WinFsp. At the time of this writing WinFsp has a native API, but no FUSE compatible API. The main purpose of this case study is to develop a FUSE compatible API for WinFsp.
|
|
|
|
== Step 1: Gather Information about SSHFS
|
|
|
|
The SSHFS project is one of the early FUSE projects. The project was originally written by Miklos Szeredi who is also the author of FUSE. SSHFS provides a file system interface on top of SFTP (Secure File Transfer Protocol).
|
|
|
|
The project's website is at https://github.com/libfuse/sshfs. A quick perusal of the source code shows that this is a POSIX program, the file `configure.ac` further shows that it depends on GLib and FUSE.
|
|
|
|
Luckily Cygwin on Windows provides a POSIX interface and it also includes GLib and pkg-config. We are missing FUSE of course. Let's try it anyway:
|
|
|
|
----
|
|
billziss@windows:~/Projects/ext$ git clone https://github.com/libfuse/sshfs.git
|
|
Cloning into 'sshfs'...
|
|
[snip]
|
|
billziss@windows:~/Projects/ext$ cd sshfs/
|
|
billziss@windows:~/Projects/ext/sshfs [master]$ autoreconf -i
|
|
[snip]
|
|
billziss@windows:~/Projects/ext/sshfs [master]$ ./configure
|
|
[snip]
|
|
configure: error: Package requirements (fuse >= 2.3 glib-2.0 gthread-2.0) were not met:
|
|
|
|
No package 'fuse' found
|
|
----
|
|
|
|
As expected we get an error because there is no package named FUSE. So let's create one.
|
|
|
|
== Step 2: Create a FUSE Compatible Package
|
|
|
|
After a few days of development there exists now an initial FUSE implementation within WinFsp. Most of the FUSE API's from the header files `fuse.h`, `fuse_common.h` and `fuse_opt.h` have been implemented. However none of the `fuse_operations` currently work as the necessary work to translate WinFsp requests to FUSE requests has not happened yet.
|
|
|
|
=== Challenges
|
|
|
|
- The FUSE API is old and somewhat hairy. There are multiple versions of it and choosing the right one was not easy. In the end version 2.8 of the API was chosen for implementation.
|
|
|
|
- The FUSE API uses a number of OS specific types (notably `struct stat`). Sometimes these types have multiple definitions even within the same OS (e.g. `struct stat` and `struct stat64`). For this reason it was decided to define our own `fuse_*` types (e.g. `struct fuse_stat`) instead of relying on the ones that come with MSVC. Care was taken to ensure that these types remain compatible with Cygwin as it is one of our primary target environments.
|
|
|
|
- The WinFsp DLL does *not* use the MSVCRT and uses its own memory allocator (`HeapAlloc`, `HeapFree`). Even if it used the MSVCRT `malloc`, it does not have access to the Cygwin `malloc`. The FUSE API has a few cases where users are expected to use `free` to deallocate memory (e.g. `fuse_opt_add_opt`). But which `free` is that for a Cygwin program? The Cygwin `free`, the MSVCRT `free` or our own `MemFree`?
|
|
+
|
|
To solve this problem we use the following pattern: every FUSE API is implemented as a `static inline` function that calls a WinFsp-FUSE API and passes it an extra argument that describes the environment:
|
|
+
|
|
----
|
|
static inline int fuse_opt_add_opt(char **opts, const char *opt)
|
|
{
|
|
return fsp_fuse_opt_add_opt(fsp_fuse_env(), opts, opt);
|
|
}
|
|
----
|
|
+
|
|
The `fsp_fuse_env` function is another `static inline` function that simply "captures" the current environment (things like the environment's `malloc` and `free`).
|
|
+
|
|
----
|
|
...
|
|
#elif defined(__CYGWIN__)
|
|
...
|
|
#define FSP_FUSE_ENV_INIT \
|
|
{ \
|
|
'C', \
|
|
malloc, free, \
|
|
fsp_fuse_daemonize, \
|
|
fsp_fuse_set_signal_handlers, \
|
|
fsp_fuse_remove_signal_handlers,\
|
|
}
|
|
...
|
|
#else
|
|
...
|
|
|
|
static inline struct fsp_fuse_env *fsp_fuse_env(void)
|
|
{
|
|
static struct fsp_fuse_env env = FSP_FUSE_ENV_INIT;
|
|
return &env;
|
|
}
|
|
----
|
|
|
|
- The implementation of `fuse_opt` proved an unexpected challenge. The function `fuse_opt_parse` is very flexible, but it also has a lot of quirks. It took a lot of trial and error to arrive at a clean reimplementation.
|
|
|
|
=== Things that worked rather nicely
|
|
|
|
- The pattern `fuse_new` / `fuse_loop` / `fuse_destroy` fits nicely to the WinFsp service model: `FspServiceCreate` / `FspServiceLoop` / `FspServiceDelete`. This means that every (high-level) FUSE file system can rather easily be converted into a Windows service if desired.
|
|
|
|
=== Integrating with Cygwin
|
|
|
|
It remains to show how to use the WinFsp-FUSE implementation from Cygwin and SSHFS. SSHFS uses `pkg-config` for its build configuration. `Pkg-config` requires a `fuse.pc` file:
|
|
|
|
----
|
|
arch=x64
|
|
prefix=${pcfiledir}/..
|
|
incdir=${prefix}/inc/fuse
|
|
implib=${prefix}/bin/winfsp-${arch}.dll
|
|
|
|
Name: fuse
|
|
Description: WinFsp FUSE compatible API
|
|
Version: 2.8
|
|
URL: http://www.secfs.net/winfsp/
|
|
Libs: "${implib}"
|
|
Cflags: -I"${incdir}"
|
|
----
|
|
|
|
The WinFsp installer has been modified to place this file within its installation directory. It remains to point `pkg-config` to the appropriate location (using `PKG_CONFIG_PATH`) and the SSHFS configuration process can now find the FUSE package.
|
|
|
|
=== SSHFS-Win
|
|
|
|
The sshfs-win open-source project (work in progress) can be found here: https://bitbucket.org/billziss/sshfs-win
|
|
|
|
== Step 3: Mapping Windows to POSIX
|
|
|
|
It would seem that we are now ready to start implementing the `fuse_operations`. However there is another matter that we need to attend to first and that is mapping the Windows file system view of the world to the POSIX one and vice-versa.
|
|
|
|
=== Mapping Paths
|
|
|
|
The Windows and POSIX file systems both use paths to address files. The path conventions are different, so we need a technique to convert between the two. This goes beyond a simple translation of the backslash character (`\`) to slash (`/`), because several characters are reserved and cannot be used in a Windows file path, but are legal when used in a POSIX path.
|
|
|
|
The reserved Windows characters are:
|
|
|
|
----
|
|
< > : " / \ | ? *
|
|
any character between 0 and 31
|
|
----
|
|
|
|
POSIX only has two reserved characters: slash (`/`) and `NUL`.
|
|
|
|
So how do we map between the two? Luckily this problem has been solved before by "Services for Macintosh" (SFM), "Services for UNIX" (SFU) and Gygwin. The solution involves the use of the Unicode "private use area". When mapping a POSIX path to Windows, if we encounter any of the Windows reserved characters we simply map it to the Unicode range U+F000 - U+F0FF. The reverse mapping from Windows to POSIX is obvious.
|
|
|
|
=== Mapping Security
|
|
|
|
Mapping Windows security to POSIX (and vice-versa) is a much more interesting (and difficult) problem. We have the following requirements:
|
|
|
|
- We need a method to map a Windows SID (Security Identifier) to a POSIX uid/gid.
|
|
- We need a method to map a Windows ACL (Access Control List) to a POSIX permission set.
|
|
- We want any mapping method we come up with to be bijective (to the extent that it is possible).
|
|
|
|
Luckily "Services for UNIX" (and Cygwin) come to the rescue again. The following Cygwin document describes in great detail a method to map a Windows SID to a POSIX uid/gid that is compatible with SFU: https://cygwin.com/cygwin-ug-net/ntsec.html. A different document from SFU describes how to map a Windows ACL to POSIX permissions: https://technet.microsoft.com/en-us/library/bb463216.aspx.
|
|
|
|
The mappings provided are not perfect, but they come pretty close. They are also proven as they have been used in SFU and Cygwin for years.
|
|
|
|
=== WinFsp Implementation
|
|
|
|
A WinFsp implementation of the above mappings can be found in the file `src/dll/posix.c`.
|