diff --git a/tst/passthrough-fuse3/.gitignore b/tst/passthrough-fuse3/.gitignore new file mode 100644 index 00000000..e7b44729 --- /dev/null +++ b/tst/passthrough-fuse3/.gitignore @@ -0,0 +1,7 @@ +build +*.ncb +*.suo +*.vcproj.* +*.vcxproj.user +*.exe +*.install diff --git a/tst/passthrough-fuse3/Makefile b/tst/passthrough-fuse3/Makefile new file mode 100644 index 00000000..e729a044 --- /dev/null +++ b/tst/passthrough-fuse3/Makefile @@ -0,0 +1,18 @@ +usage: + @echo "make cygfuse3|winfsp-fuse3" 1>&2 + @echo "" 1>&2 + @echo " cygfuse3 Link with CYGFUSE3" 1>&2 + @echo " winfsp-fuse3 Link with WinFsp-FUSE3" 1>&2 + @exit 2 + +cygfuse3: passthrough-cygfuse3 + +winfsp-fuse3: passthrough-winfsp-fuse3 + +passthrough-cygfuse3: passthrough-fuse3.c + gcc $^ -o $@ -g -Wall `pkg-config fuse3 --cflags --libs` + +passthrough-winfsp-fuse3: export PKG_CONFIG_PATH=$(PWD)/winfsp.install/lib +passthrough-winfsp-fuse3: passthrough-fuse3.c + ln -nsf "`regtool --wow32 get '/HKLM/Software/WinFsp/InstallDir' | cygpath -au -f -`" winfsp.install + gcc $^ -o $@ -g -Wall `pkg-config fuse3 --cflags --libs` diff --git a/tst/passthrough-fuse3/README.md b/tst/passthrough-fuse3/README.md new file mode 100644 index 00000000..3cf08b70 --- /dev/null +++ b/tst/passthrough-fuse3/README.md @@ -0,0 +1,7 @@ +`Passthrough-fuse3` is a simple FUSE3 file system that passes all file system operations to an underlying file system. + +It can be built with the following tools: + +- Using Visual Studio (`winfsp.sln`). +- Using Cygwin GCC and linking directly with the WinFsp DLL (`make winfsp-fuse3`). +- Using Cygwin GCC and linking to CYGFUSE3 (`make cygfuse3`). diff --git a/tst/passthrough-fuse3/passthrough-fuse3.c b/tst/passthrough-fuse3/passthrough-fuse3.c new file mode 100644 index 00000000..aa72b746 --- /dev/null +++ b/tst/passthrough-fuse3/passthrough-fuse3.c @@ -0,0 +1,331 @@ +/** + * @file passthrough-fuse.c + * + * @copyright 2015-2018 Bill Zissimopoulos + */ +/* + * This file is part of WinFsp. + * + * You can redistribute it and/or modify it under the terms of the GNU + * General Public License version 3 as published by the Free Software + * Foundation. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * software. + */ + +#include +#include +#include +#include +#include + +#include + +#if defined(_WIN64) || defined(_WIN32) +#include "winposix.h" +#else +#include +#include +#endif + +#define FSNAME "passthrough" +#define PROGNAME "passthrough-fuse" + +#define concat_path(ptfs, fn, fp) (sizeof fp > (unsigned)snprintf(fp, sizeof fp, "%s%s", ptfs->rootdir, fn)) + +#define fi_dirbit (0x8000000000000000ULL) +#define fi_fh(fi, MASK) ((fi)->fh & (MASK)) +#define fi_setfh(fi, FH, MASK) ((fi)->fh = (intptr_t)(FH) | (MASK)) +#define fi_fd(fi) (fi_fh(fi, fi_dirbit) ? \ + dirfd((DIR *)(intptr_t)fi_fh(fi, ~fi_dirbit)) : (int)fi_fh(fi, ~fi_dirbit)) +#define fi_dirp(fi) ((DIR *)(intptr_t)fi_fh(fi, ~fi_dirbit)) +#define fi_setfd(fi, fd) (fi_setfh(fi, fd, 0)) +#define fi_setdirp(fi, dirp) (fi_setfh(fi, dirp, fi_dirbit)) + +#define ptfs_impl_fullpath(n) \ + char full ## n[PATH_MAX]; \ + if (!concat_path(((PTFS *)fuse_get_context()->private_data), n, full ## n))\ + return -ENAMETOOLONG; \ + n = full ## n + +typedef struct +{ + const char *rootdir; +} PTFS; + +static int ptfs_getattr(const char *path, struct fuse_stat *stbuf, struct fuse_file_info *fi) +{ + if (0 == fi) + { + ptfs_impl_fullpath(path); + + return -1 != lstat(path, stbuf) ? 0 : -errno; + } + else + { + int fd = fi_fd(fi); + + return -1 != fstat(fd, stbuf) ? 0 : -errno; + } +} + +static int ptfs_mkdir(const char *path, fuse_mode_t mode) +{ + ptfs_impl_fullpath(path); + + return -1 != mkdir(path, mode) ? 0 : -errno; +} + +static int ptfs_unlink(const char *path) +{ + ptfs_impl_fullpath(path); + + return -1 != unlink(path) ? 0 : -errno; +} + +static int ptfs_rmdir(const char *path) +{ + ptfs_impl_fullpath(path); + + return -1 != rmdir(path) ? 0 : -errno; +} + +static int ptfs_rename(const char *oldpath, const char *newpath, unsigned int flags) +{ + ptfs_impl_fullpath(newpath); + ptfs_impl_fullpath(oldpath); + + return -1 != rename(oldpath, newpath) ? 0 : -errno; +} + +static int ptfs_chmod(const char *path, fuse_mode_t mode, struct fuse_file_info *fi) +{ + ptfs_impl_fullpath(path); + + return -1 != chmod(path, mode) ? 0 : -errno; +} + +static int ptfs_chown(const char *path, fuse_uid_t uid, fuse_gid_t gid, struct fuse_file_info *fi) +{ + ptfs_impl_fullpath(path); + + return -1 != lchown(path, uid, gid) ? 0 : -errno; +} + +static int ptfs_truncate(const char *path, fuse_off_t size, struct fuse_file_info *fi) +{ + if (0 == fi) + { + ptfs_impl_fullpath(path); + + return -1 != truncate(path, size) ? 0 : -errno; + } + else + { + int fd = fi_fd(fi); + + return -1 != ftruncate(fd, size) ? 0 : -errno; + } +} + +static int ptfs_open(const char *path, struct fuse_file_info *fi) +{ + ptfs_impl_fullpath(path); + + int fd; + return -1 != (fd = open(path, fi->flags)) ? (fi_setfd(fi, fd), 0) : -errno; +} + +static int ptfs_read(const char *path, char *buf, size_t size, fuse_off_t off, + struct fuse_file_info *fi) +{ + int fd = fi_fd(fi); + + int nb; + return -1 != (nb = pread(fd, buf, size, off)) ? nb : -errno; +} + +static int ptfs_write(const char *path, const char *buf, size_t size, fuse_off_t off, + struct fuse_file_info *fi) +{ + int fd = fi_fd(fi); + + int nb; + return -1 != (nb = pwrite(fd, buf, size, off)) ? nb : -errno; +} + +static int ptfs_statfs(const char *path, struct fuse_statvfs *stbuf) +{ + ptfs_impl_fullpath(path); + + return -1 != statvfs(path, stbuf) ? 0 : -errno; +} + +static int ptfs_release(const char *path, struct fuse_file_info *fi) +{ + int fd = fi_fd(fi); + + close(fd); + return 0; +} + +static int ptfs_fsync(const char *path, int datasync, struct fuse_file_info *fi) +{ + int fd = fi_fd(fi); + + return -1 != fsync(fd) ? 0 : -errno; +} + +static int ptfs_opendir(const char *path, struct fuse_file_info *fi) +{ + ptfs_impl_fullpath(path); + + DIR *dirp; + return 0 != (dirp = opendir(path)) ? (fi_setdirp(fi, dirp), 0) : -errno; +} + +static int ptfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, fuse_off_t off, + struct fuse_file_info *fi, enum fuse_readdir_flags flags) +{ + DIR *dirp = fi_dirp(fi); + struct dirent *de; + + rewinddir(dirp); + for (;;) + { + errno = 0; + if (0 == (de = readdir(dirp))) + break; +#if defined(_WIN64) || defined(_WIN32) + if (0 != filler(buf, de->d_name, &de->d_stat, 0, FUSE_FILL_DIR_PLUS)) +#else + if (0 != filler(buf, de->d_name, 0, 0, 0)) +#endif + return -ENOMEM; + } + + return -errno; +} + +static int ptfs_releasedir(const char *path, struct fuse_file_info *fi) +{ + DIR *dirp = fi_dirp(fi); + + return -1 != closedir(dirp) ? 0 : -errno; +} + +static void *ptfs_init(struct fuse_conn_info *conn, struct fuse_config *conf) +{ + conn->want |= (conn->capable & FUSE_CAP_READDIRPLUS); + +#if defined(FSP_FUSE_CAP_CASE_INSENSITIVE) + conn->want |= (conn->capable & FSP_FUSE_CAP_CASE_INSENSITIVE); +#endif + + return fuse_get_context()->private_data; +} + +static int ptfs_create(const char *path, fuse_mode_t mode, struct fuse_file_info *fi) +{ + ptfs_impl_fullpath(path); + + int fd; + return -1 != (fd = open(path, fi->flags, mode)) ? (fi_setfd(fi, fd), 0) : -errno; +} + +static int ptfs_utimens(const char *path, const struct fuse_timespec tv[2], struct fuse_file_info *fi) +{ + ptfs_impl_fullpath(path); + + return -1 != utimensat(AT_FDCWD, path, tv, AT_SYMLINK_NOFOLLOW) ? 0 : -errno; +} + +static struct fuse_operations ptfs_ops = +{ + .getattr = ptfs_getattr, + .mkdir = ptfs_mkdir, + .unlink = ptfs_unlink, + .rmdir = ptfs_rmdir, + .rename = ptfs_rename, + .chmod = ptfs_chmod, + .chown = ptfs_chown, + .truncate = ptfs_truncate, + .open = ptfs_open, + .read = ptfs_read, + .write = ptfs_write, + .statfs = ptfs_statfs, + .release = ptfs_release, + .fsync = ptfs_fsync, + .opendir = ptfs_opendir, + .readdir = ptfs_readdir, + .releasedir = ptfs_releasedir, + .init = ptfs_init, + .create = ptfs_create, + .utimens = ptfs_utimens, +}; + +static void usage(void) +{ + fprintf(stderr, "usage: " PROGNAME " [FUSE options] rootdir mountpoint\n"); + exit(2); +} + +int main(int argc, char *argv[]) +{ + PTFS ptfs = { 0 }; + + if (3 <= argc && '-' != argv[argc - 2][0] && '-' != argv[argc - 1][0]) + { + ptfs.rootdir = realpath(argv[argc - 2], 0); /* memory freed at process end */ + argv[argc - 2] = argv[argc - 1]; + argc--; + } + +#if defined(_WIN64) || defined(_WIN32) + /* + * When building for Windows (rather than Cygwin or POSIX OS) + * allow the path to be specified using the --VolumePrefix + * switch using the syntax \\passthrough-fuse\C$\Path. This + * allows us to run the file system under the WinFsp.Launcher + * and start it using commands like: + * + * net use z: \\passthrough-fuse\C$\Path + */ + if (0 == ptfs.rootdir) + for (int argi = 1; argc > argi; argi++) + { + int strncmp(const char *a, const char *b, size_t length); + char *strchr(const char *s, int c); + char *p = 0; + + if (0 == strncmp("--UNC=", argv[argi], sizeof "--UNC=" - 1)) + p = argv[argi] + sizeof "--UNC=" - 1; + else if (0 == strncmp("--VolumePrefix=", argv[argi], sizeof "--VolumePrefix=" - 1)) + p = argv[argi] + sizeof "--VolumePrefix=" - 1; + + if (0 != p && '\\' != p[1]) + { + p = strchr(p + 1, '\\'); + if (0 != p && + ( + ('A' <= p[1] && p[1] <= 'Z') || + ('a' <= p[1] && p[1] <= 'z') + ) && + '$' == p[2]) + { + p[2] = ':'; + ptfs.rootdir = realpath(p + 1, 0); /* memory freed at process end */ + p[2] = '$'; + break; + } + } + } +#endif + + if (0 == ptfs.rootdir) + usage(); + + return fuse_main(argc, argv, &ptfs_ops, &ptfs); +} diff --git a/tst/passthrough-fuse3/passthrough-fuse3.sln b/tst/passthrough-fuse3/passthrough-fuse3.sln new file mode 100644 index 00000000..d562ff36 --- /dev/null +++ b/tst/passthrough-fuse3/passthrough-fuse3.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "passthrough-fuse3", "passthrough-fuse3.vcxproj", "{5E99498C-D30C-48EF-A04A-7977C0305FAC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E99498C-D30C-48EF-A04A-7977C0305FAC}.Debug|x64.ActiveCfg = Debug|x64 + {5E99498C-D30C-48EF-A04A-7977C0305FAC}.Debug|x64.Build.0 = Debug|x64 + {5E99498C-D30C-48EF-A04A-7977C0305FAC}.Debug|x86.ActiveCfg = Debug|Win32 + {5E99498C-D30C-48EF-A04A-7977C0305FAC}.Debug|x86.Build.0 = Debug|Win32 + {5E99498C-D30C-48EF-A04A-7977C0305FAC}.Release|x64.ActiveCfg = Release|x64 + {5E99498C-D30C-48EF-A04A-7977C0305FAC}.Release|x64.Build.0 = Release|x64 + {5E99498C-D30C-48EF-A04A-7977C0305FAC}.Release|x86.ActiveCfg = Release|Win32 + {5E99498C-D30C-48EF-A04A-7977C0305FAC}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tst/passthrough-fuse3/passthrough-fuse3.vcxproj b/tst/passthrough-fuse3/passthrough-fuse3.vcxproj new file mode 100644 index 00000000..b49187c0 --- /dev/null +++ b/tst/passthrough-fuse3/passthrough-fuse3.vcxproj @@ -0,0 +1,190 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {5E99498C-D30C-48EF-A04A-7977C0305FAC} + Win32Proj + passthroughfuse3 + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)build\$(Configuration)\ + $(SolutionDir)build\$(ProjectName).build\$(Configuration)\$(PlatformTarget)\ + $(ProjectName)-$(PlatformTarget) + + + true + $(SolutionDir)build\$(Configuration)\ + $(SolutionDir)build\$(ProjectName).build\$(Configuration)\$(PlatformTarget)\ + $(ProjectName)-$(PlatformTarget) + + + false + $(SolutionDir)build\$(Configuration)\ + $(SolutionDir)build\$(ProjectName).build\$(Configuration)\$(PlatformTarget)\ + $(ProjectName)-$(PlatformTarget) + + + false + $(SolutionDir)build\$(Configuration)\ + $(SolutionDir)build\$(ProjectName).build\$(Configuration)\$(PlatformTarget)\ + $(ProjectName)-$(PlatformTarget) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(MSBuildProgramFiles32)\WinFsp\inc\fuse3;$(MSBuildProgramFiles32)\WinFsp\inc + MultiThreaded + 4996 + + + Console + true + $(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies) + winfsp-$(PlatformTarget).dll + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(MSBuildProgramFiles32)\WinFsp\inc\fuse3;$(MSBuildProgramFiles32)\WinFsp\inc + MultiThreaded + 4996 + + + Console + true + $(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies) + winfsp-$(PlatformTarget).dll + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(MSBuildProgramFiles32)\WinFsp\inc\fuse3;$(MSBuildProgramFiles32)\WinFsp\inc + MultiThreaded + 4996 + + + Console + true + true + true + $(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies) + winfsp-$(PlatformTarget).dll + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(MSBuildProgramFiles32)\WinFsp\inc\fuse3;$(MSBuildProgramFiles32)\WinFsp\inc + MultiThreaded + 4996 + + + Console + true + true + true + $(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies) + winfsp-$(PlatformTarget).dll + + + + + + + + + + + + + \ No newline at end of file diff --git a/tst/passthrough-fuse3/passthrough-fuse3.vcxproj.filters b/tst/passthrough-fuse3/passthrough-fuse3.vcxproj.filters new file mode 100644 index 00000000..63fc03d5 --- /dev/null +++ b/tst/passthrough-fuse3/passthrough-fuse3.vcxproj.filters @@ -0,0 +1,21 @@ + + + + + {bfbcc136-ea14-4445-8f9b-1fa7f8aedc71} + + + + + Source + + + Source + + + + + Source + + + \ No newline at end of file diff --git a/tst/passthrough-fuse3/winposix.c b/tst/passthrough-fuse3/winposix.c new file mode 100644 index 00000000..c4905670 --- /dev/null +++ b/tst/passthrough-fuse3/winposix.c @@ -0,0 +1,634 @@ +/** + * @file winposix.c + * + * @copyright 2015-2018 Bill Zissimopoulos + */ +/* + * This file is part of WinFsp. + * + * You can redistribute it and/or modify it under the terms of the GNU + * General Public License version 3 as published by the Free Software + * Foundation. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * software. + */ + +/* + * This is a very simple Windows POSIX layer. It handles all the POSIX + * file API's required to implement passthrough-fuse in POSIX, however + * the API handling is rather unsophisticated. + * + * Ways to improve it: use the FspPosix* API's to properly handle + * file names and security. + */ + +#include +#include +#include +#include "winposix.h" + +struct _DIR +{ + HANDLE h, fh; + struct dirent de; + char path[]; +}; + +#if defined(FSP_FUSE_USE_STAT_EX) +static inline uint32_t MapFileAttributesToFlags(UINT32 FileAttributes) +{ + uint32_t flags = 0; + + if (FileAttributes & FILE_ATTRIBUTE_READONLY) + flags |= FSP_FUSE_UF_READONLY; + if (FileAttributes & FILE_ATTRIBUTE_HIDDEN) + flags |= FSP_FUSE_UF_HIDDEN; + if (FileAttributes & FILE_ATTRIBUTE_SYSTEM) + flags |= FSP_FUSE_UF_SYSTEM; + if (FileAttributes & FILE_ATTRIBUTE_ARCHIVE) + flags |= FSP_FUSE_UF_ARCHIVE; + + return flags; +} + +static inline UINT32 MapFlagsToFileAttributes(uint32_t flags) +{ + UINT32 FileAttributes = 0; + + if (flags & FSP_FUSE_UF_READONLY) + FileAttributes |= FILE_ATTRIBUTE_READONLY; + if (flags & FSP_FUSE_UF_HIDDEN) + FileAttributes |= FILE_ATTRIBUTE_HIDDEN; + if (flags & FSP_FUSE_UF_SYSTEM) + FileAttributes |= FILE_ATTRIBUTE_SYSTEM; + if (flags & FSP_FUSE_UF_ARCHIVE) + FileAttributes |= FILE_ATTRIBUTE_ARCHIVE; + + return FileAttributes; +} +#endif + +static int maperror(int winerrno); + +static inline void *error0(void) +{ + errno = maperror(GetLastError()); + return 0; +} + +static inline int error(void) +{ + errno = maperror(GetLastError()); + return -1; +} + +char *realpath(const char *path, char *resolved) +{ + char *result; + + if (0 == resolved) + { + result = malloc(PATH_MAX); /* sets errno */ + if (0 == result) + return 0; + } + else + result = resolved; + + int err = 0; + DWORD len = GetFullPathNameA(path, PATH_MAX, result, 0); + if (0 == len) + err = GetLastError(); + else if (PATH_MAX < len) + err = ERROR_INVALID_PARAMETER; + + if (0 == err) + { + HANDLE h = CreateFileA(result, + FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + if (INVALID_HANDLE_VALUE != h) + CloseHandle(h); + else + err = GetLastError(); + } + + if (0 != err) + { + if (result != resolved) + free(result); + + errno = maperror(err); + result = 0; + } + + return result; +} + +int statvfs(const char *path, struct fuse_statvfs *stbuf) +{ + char root[PATH_MAX]; + DWORD + VolumeSerialNumber, + MaxComponentLength, + SectorsPerCluster, + BytesPerSector, + NumberOfFreeClusters, + TotalNumberOfClusters; + + if (!GetVolumePathNameA(path, root, PATH_MAX) || + !GetVolumeInformationA(root, 0, 0, &VolumeSerialNumber, &MaxComponentLength, 0, 0, 0) || + !GetDiskFreeSpaceA(root, &SectorsPerCluster, &BytesPerSector, + &NumberOfFreeClusters, &TotalNumberOfClusters)) + { + return error(); + } + + memset(stbuf, 0, sizeof *stbuf); + stbuf->f_bsize = SectorsPerCluster * BytesPerSector; + stbuf->f_frsize = SectorsPerCluster * BytesPerSector; + stbuf->f_blocks = TotalNumberOfClusters; + stbuf->f_bfree = NumberOfFreeClusters; + stbuf->f_bavail = TotalNumberOfClusters; + stbuf->f_fsid = VolumeSerialNumber; + stbuf->f_namemax = MaxComponentLength; + + return 0; +} + +int open(const char *path, int oflag, ...) +{ + static DWORD da[] = { GENERIC_READ, GENERIC_WRITE, GENERIC_READ | GENERIC_WRITE, 0 }; + static DWORD cd[] = { OPEN_EXISTING, OPEN_ALWAYS, TRUNCATE_EXISTING, CREATE_ALWAYS }; + DWORD DesiredAccess = 0 == (oflag & _O_APPEND) ? + da[oflag & (_O_RDONLY | _O_WRONLY | _O_RDWR)] : + (da[oflag & (_O_RDONLY | _O_WRONLY | _O_RDWR)] & ~FILE_WRITE_DATA) | FILE_APPEND_DATA; + DWORD CreationDisposition = (_O_CREAT | _O_EXCL) == (oflag & (_O_CREAT | _O_EXCL)) ? + CREATE_NEW : + cd[(oflag & (_O_CREAT | _O_TRUNC)) >> 8]; + + HANDLE h = CreateFileA(path, + DesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0/* default security */, + CreationDisposition, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 0); + + if (INVALID_HANDLE_VALUE == h) + return error(); + + return (int)(intptr_t)h; +} + +int fstat(int fd, struct fuse_stat *stbuf) +{ + HANDLE h = (HANDLE)(intptr_t)fd; + BY_HANDLE_FILE_INFORMATION FileInfo; + + if (!GetFileInformationByHandle(h, &FileInfo)) + return error(); + + memset(stbuf, 0, sizeof *stbuf); + stbuf->st_mode = 0777 | + ((FileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 0040000/* S_IFDIR */ : 0); + stbuf->st_nlink = 1; + stbuf->st_size = ((UINT64)FileInfo.nFileSizeHigh << 32) | ((UINT64)FileInfo.nFileSizeLow); + FspPosixFileTimeToUnixTime(*(PUINT64)&FileInfo.ftCreationTime, (void *)&stbuf->st_birthtim); + FspPosixFileTimeToUnixTime(*(PUINT64)&FileInfo.ftLastAccessTime, (void *)&stbuf->st_atim); + FspPosixFileTimeToUnixTime(*(PUINT64)&FileInfo.ftLastWriteTime, (void *)&stbuf->st_mtim); + FspPosixFileTimeToUnixTime(*(PUINT64)&FileInfo.ftLastWriteTime, (void *)&stbuf->st_ctim); +#if defined(FSP_FUSE_USE_STAT_EX) + stbuf->st_flags = MapFileAttributesToFlags(FileInfo.dwFileAttributes); +#endif + + return 0; +} + +int ftruncate(int fd, fuse_off_t size) +{ + HANDLE h = (HANDLE)(intptr_t)fd; + FILE_END_OF_FILE_INFO EndOfFileInfo; + + EndOfFileInfo.EndOfFile.QuadPart = size; + + if (!SetFileInformationByHandle(h, FileEndOfFileInfo, &EndOfFileInfo, sizeof EndOfFileInfo)) + return error(); + + return 0; +} + +int pread(int fd, void *buf, size_t nbyte, fuse_off_t offset) +{ + HANDLE h = (HANDLE)(intptr_t)fd; + OVERLAPPED Overlapped = { 0 }; + DWORD BytesTransferred; + + Overlapped.Offset = (DWORD)offset; + Overlapped.OffsetHigh = (DWORD)(offset >> 32); + + if (!ReadFile(h, buf, (DWORD)nbyte, &BytesTransferred, &Overlapped)) + { + if (ERROR_HANDLE_EOF == GetLastError()) + return 0; + return error(); + } + + return BytesTransferred; +} + +int pwrite(int fd, const void *buf, size_t nbyte, fuse_off_t offset) +{ + HANDLE h = (HANDLE)(intptr_t)fd; + OVERLAPPED Overlapped = { 0 }; + DWORD BytesTransferred; + + Overlapped.Offset = (DWORD)offset; + Overlapped.OffsetHigh = (DWORD)(offset >> 32); + + if (!WriteFile(h, buf, (DWORD)nbyte, &BytesTransferred, &Overlapped)) + return error(); + + return BytesTransferred; +} + +int fsync(int fd) +{ + HANDLE h = (HANDLE)(intptr_t)fd; + + if (!FlushFileBuffers(h)) + return error(); + + return 0; +} + +int close(int fd) +{ + HANDLE h = (HANDLE)(intptr_t)fd; + + if (!CloseHandle(h)) + return error(); + + return 0; +} + +int lstat(const char *path, struct fuse_stat *stbuf) +{ + HANDLE h = CreateFileA(path, + FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + if (INVALID_HANDLE_VALUE == h) + return error(); + + int res = fstat((int)(intptr_t)h, stbuf); + + CloseHandle(h); + + return res; +} + +int chmod(const char *path, fuse_mode_t mode) +{ + /* we do not support file security */ + return 0; +} + +int lchown(const char *path, fuse_uid_t uid, fuse_gid_t gid) +{ + /* we do not support file security */ + return 0; +} + +int lchflags(const char *path, uint32_t flags) +{ +#if defined(FSP_FUSE_USE_STAT_EX) + UINT32 FileAttributes = MapFlagsToFileAttributes(flags); + + if (0 == FileAttributes) + FileAttributes = FILE_ATTRIBUTE_NORMAL; + + if (!SetFileAttributesA(path, FileAttributes)) + return error(); +#endif + + return 0; +} + +int truncate(const char *path, fuse_off_t size) +{ + HANDLE h = CreateFileA(path, + FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + if (INVALID_HANDLE_VALUE == h) + return error(); + + int res = ftruncate((int)(intptr_t)h, size); + + CloseHandle(h); + + return res; +} + +int utime(const char *path, const struct fuse_utimbuf *timbuf) +{ + if (0 == timbuf) + return utimensat(AT_FDCWD, path, 0, AT_SYMLINK_NOFOLLOW); + else + { + struct fuse_timespec times[2]; + times[0].tv_sec = timbuf->actime; + times[0].tv_nsec = 0; + times[1].tv_sec = timbuf->modtime; + times[1].tv_nsec = 0; + return utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW); + } +} + +int utimensat(int dirfd, const char *path, const struct fuse_timespec times[2], int flag) +{ + /* ignore dirfd and assume that it is always AT_FDCWD */ + /* ignore flag and assume that it is always AT_SYMLINK_NOFOLLOW */ + + HANDLE h = CreateFileA(path, + FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + if (INVALID_HANDLE_VALUE == h) + return error(); + + UINT64 LastAccessTime, LastWriteTime; + if (0 == times) + { + FILETIME FileTime; + GetSystemTimeAsFileTime(&FileTime); + LastAccessTime = LastWriteTime = *(PUINT64)&FileTime; + } + else + { + FspPosixUnixTimeToFileTime((void *)×[0], &LastAccessTime); + FspPosixUnixTimeToFileTime((void *)×[1], &LastWriteTime); + } + + int res = SetFileTime(h, + 0, (PFILETIME)&LastAccessTime, (PFILETIME)&LastWriteTime) ? 0 : error(); + + CloseHandle(h); + + return res; +} + +int setcrtime(const char *path, const struct fuse_timespec *tv) +{ + HANDLE h = CreateFileA(path, + FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + if (INVALID_HANDLE_VALUE == h) + return error(); + + UINT64 CreationTime; + FspPosixUnixTimeToFileTime((void *)tv, &CreationTime); + + int res = SetFileTime(h, + (PFILETIME)&CreationTime, 0, 0) ? 0 : error(); + + CloseHandle(h); + + return res; +} + +int unlink(const char *path) +{ + if (!DeleteFileA(path)) + return error(); + + return 0; +} + +int rename(const char *oldpath, const char *newpath) +{ + if (!MoveFileExA(oldpath, newpath, MOVEFILE_REPLACE_EXISTING)) + return error(); + + return 0; +} + +int mkdir(const char *path, fuse_mode_t mode) +{ + if (!CreateDirectoryA(path, 0/* default security */)) + return error(); + + return 0; +} + +int rmdir(const char *path) +{ + if (!RemoveDirectoryA(path)) + return error(); + + return 0; +} + +DIR *opendir(const char *path) +{ + HANDLE h = CreateFileA(path, + FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + if (INVALID_HANDLE_VALUE == h) + return error0(); + + size_t pathlen = strlen(path); + if (0 < pathlen && '/' == path[pathlen - 1]) + pathlen--; + + DIR *dirp = malloc(sizeof *dirp + pathlen + 3); /* sets errno */ + if (0 == dirp) + { + CloseHandle(h); + return 0; + } + + memset(dirp, 0, sizeof *dirp); + dirp->h = h; + dirp->fh = INVALID_HANDLE_VALUE; + memcpy(dirp->path, path, pathlen); + dirp->path[pathlen + 0] = '/'; + dirp->path[pathlen + 1] = '*'; + dirp->path[pathlen + 2] = '\0'; + + return dirp; +} + +int dirfd(DIR *dirp) +{ + return (int)(intptr_t)dirp->h; +} + +void rewinddir(DIR *dirp) +{ + if (INVALID_HANDLE_VALUE != dirp->fh) + { + FindClose(dirp->fh); + dirp->fh = INVALID_HANDLE_VALUE; + } +} + +struct dirent *readdir(DIR *dirp) +{ + WIN32_FIND_DATAA FindData; + struct fuse_stat *stbuf = &dirp->de.d_stat; + + if (INVALID_HANDLE_VALUE == dirp->fh) + { + dirp->fh = FindFirstFileA(dirp->path, &FindData); + if (INVALID_HANDLE_VALUE == dirp->fh) + return error0(); + } + else + { + if (!FindNextFileA(dirp->fh, &FindData)) + { + if (ERROR_NO_MORE_FILES == GetLastError()) + return 0; + return error0(); + } + } + + memset(stbuf, 0, sizeof *stbuf); + stbuf->st_mode = 0777 | + ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 0040000/* S_IFDIR */ : 0); + stbuf->st_nlink = 1; + stbuf->st_size = ((UINT64)FindData.nFileSizeHigh << 32) | ((UINT64)FindData.nFileSizeLow); + FspPosixFileTimeToUnixTime(*(PUINT64)&FindData.ftCreationTime, (void *)&stbuf->st_birthtim); + FspPosixFileTimeToUnixTime(*(PUINT64)&FindData.ftLastAccessTime, (void *)&stbuf->st_atim); + FspPosixFileTimeToUnixTime(*(PUINT64)&FindData.ftLastWriteTime, (void *)&stbuf->st_mtim); + FspPosixFileTimeToUnixTime(*(PUINT64)&FindData.ftLastWriteTime, (void *)&stbuf->st_ctim); +#if defined(FSP_FUSE_USE_STAT_EX) + stbuf->st_flags = MapFileAttributesToFlags(FindData.dwFileAttributes); +#endif + + strcpy(dirp->de.d_name, FindData.cFileName); + + return &dirp->de; +} + +int closedir(DIR *dirp) +{ + if (INVALID_HANDLE_VALUE != dirp->fh) + FindClose(dirp->fh); + + CloseHandle(dirp->h); + free(dirp); + + return 0; +} + +static int maperror(int winerrno) +{ + switch (winerrno) + { + case ERROR_INVALID_FUNCTION: + return EINVAL; + case ERROR_FILE_NOT_FOUND: + return ENOENT; + case ERROR_PATH_NOT_FOUND: + return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE; + case ERROR_ACCESS_DENIED: + return EACCES; + case ERROR_INVALID_HANDLE: + return EBADF; + case ERROR_ARENA_TRASHED: + return ENOMEM; + case ERROR_NOT_ENOUGH_MEMORY: + return ENOMEM; + case ERROR_INVALID_BLOCK: + return ENOMEM; + case ERROR_BAD_ENVIRONMENT: + return E2BIG; + case ERROR_BAD_FORMAT: + return ENOEXEC; + case ERROR_INVALID_ACCESS: + return EINVAL; + case ERROR_INVALID_DATA: + return EINVAL; + case ERROR_INVALID_DRIVE: + return ENOENT; + case ERROR_CURRENT_DIRECTORY: + return EACCES; + case ERROR_NOT_SAME_DEVICE: + return EXDEV; + case ERROR_NO_MORE_FILES: + return ENOENT; + case ERROR_LOCK_VIOLATION: + return EACCES; + case ERROR_BAD_NETPATH: + return ENOENT; + case ERROR_NETWORK_ACCESS_DENIED: + return EACCES; + case ERROR_BAD_NET_NAME: + return ENOENT; + case ERROR_FILE_EXISTS: + return EEXIST; + case ERROR_CANNOT_MAKE: + return EACCES; + case ERROR_FAIL_I24: + return EACCES; + case ERROR_INVALID_PARAMETER: + return EINVAL; + case ERROR_NO_PROC_SLOTS: + return EAGAIN; + case ERROR_DRIVE_LOCKED: + return EACCES; + case ERROR_BROKEN_PIPE: + return EPIPE; + case ERROR_DISK_FULL: + return ENOSPC; + case ERROR_INVALID_TARGET_HANDLE: + return EBADF; + case ERROR_WAIT_NO_CHILDREN: + return ECHILD; + case ERROR_CHILD_NOT_COMPLETE: + return ECHILD; + case ERROR_DIRECT_ACCESS_HANDLE: + return EBADF; + case ERROR_NEGATIVE_SEEK: + return EINVAL; + case ERROR_SEEK_ON_DEVICE: + return EACCES; + case ERROR_DIR_NOT_EMPTY: + return ENOTEMPTY; + case ERROR_NOT_LOCKED: + return EACCES; + case ERROR_BAD_PATHNAME: + return ENOENT; + case ERROR_MAX_THRDS_REACHED: + return EAGAIN; + case ERROR_LOCK_FAILED: + return EACCES; + case ERROR_ALREADY_EXISTS: + return EEXIST; + case ERROR_FILENAME_EXCED_RANGE: + return ENOENT; + case ERROR_NESTING_NOT_ALLOWED: + return EAGAIN; + case ERROR_NOT_ENOUGH_QUOTA: + return ENOMEM; + default: + if (ERROR_WRITE_PROTECT <= winerrno && winerrno <= ERROR_SHARING_BUFFER_EXCEEDED) + return EACCES; + else if (ERROR_INVALID_STARTING_CODESEG <= winerrno && winerrno <= ERROR_INFLOOP_IN_RELOC_CHAIN) + return ENOEXEC; + else + return EINVAL; + } +} + +long WinFspLoad(void) +{ + return FspLoad(0); +} diff --git a/tst/passthrough-fuse3/winposix.h b/tst/passthrough-fuse3/winposix.h new file mode 100644 index 00000000..9f3e68f8 --- /dev/null +++ b/tst/passthrough-fuse3/winposix.h @@ -0,0 +1,77 @@ +/** + * @file winposix.h + * + * @copyright 2015-2018 Bill Zissimopoulos + */ +/* + * This file is part of WinFsp. + * + * You can redistribute it and/or modify it under the terms of the GNU + * General Public License version 3 as published by the Free Software + * Foundation. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * software. + */ + +#ifndef WINPOSIX_H_INCLUDED +#define WINPOSIX_H_INCLUDED + +#define O_RDONLY _O_RDONLY +#define O_WRONLY _O_WRONLY +#define O_RDWR _O_RDWR +#define O_APPEND _O_APPEND +#define O_CREAT _O_CREAT +#define O_EXCL _O_EXCL +#define O_TRUNC _O_TRUNC + +#define PATH_MAX 1024 +#define AT_FDCWD -2 +#define AT_SYMLINK_NOFOLLOW 2 + +typedef struct _DIR DIR; +struct dirent +{ + struct fuse_stat d_stat; + char d_name[255]; +}; + +char *realpath(const char *path, char *resolved); + +int statvfs(const char *path, struct fuse_statvfs *stbuf); + +int open(const char *path, int oflag, ...); +int fstat(int fd, struct fuse_stat *stbuf); +int ftruncate(int fd, fuse_off_t size); +int pread(int fd, void *buf, size_t nbyte, fuse_off_t offset); +int pwrite(int fd, const void *buf, size_t nbyte, fuse_off_t offset); +int fsync(int fd); +int close(int fd); + +int lstat(const char *path, struct fuse_stat *stbuf); +int chmod(const char *path, fuse_mode_t mode); +int lchown(const char *path, fuse_uid_t uid, fuse_gid_t gid); +int lchflags(const char *path, uint32_t flags); +int truncate(const char *path, fuse_off_t size); +int utime(const char *path, const struct fuse_utimbuf *timbuf); +int utimensat(int dirfd, const char *path, const struct fuse_timespec times[2], int flag); +int setcrtime(const char *path, const struct fuse_timespec *tv); +int unlink(const char *path); +int rename(const char *oldpath, const char *newpath); + +int mkdir(const char *path, fuse_mode_t mode); +int rmdir(const char *path); + +DIR *opendir(const char *path); +int dirfd(DIR *dirp); +void rewinddir(DIR *dirp); +struct dirent *readdir(DIR *dirp); +int closedir(DIR *dirp); + +long WinFspLoad(void); +#undef fuse_main +#define fuse_main(argc, argv, ops, data)\ + (WinFspLoad(), fuse_main_real(argc, argv, ops, sizeof *(ops), data)) + +#endif