diff --git a/build/VStudio/installer/Product.wxs b/build/VStudio/installer/Product.wxs
index b10f28ba..cd2501bd 100644
--- a/build/VStudio/installer/Product.wxs
+++ b/build/VStudio/installer/Product.wxs
@@ -382,6 +382,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -592,6 +615,13 @@
+
+
+
+
+
+
+
diff --git a/tst/memfs-fuse3/.gitignore b/tst/memfs-fuse3/.gitignore
new file mode 100644
index 00000000..a7872d37
--- /dev/null
+++ b/tst/memfs-fuse3/.gitignore
@@ -0,0 +1,10 @@
+build
+*.ncb
+*.suo
+*.vcproj.*
+*.vcxproj.user
+*.VC.db
+*.VC.opendb
+.vs
+*.exe
+*.install
diff --git a/tst/memfs-fuse3/Makefile b/tst/memfs-fuse3/Makefile
new file mode 100644
index 00000000..5bbe98b8
--- /dev/null
+++ b/tst/memfs-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: memfs-cygfuse3
+
+winfsp-fuse3: memfs-winfsp-fuse3
+
+memfs-cygfuse3: memfs-fuse3.cpp
+ g++ $^ -o $@ -g -Wall `pkg-config fuse3 --cflags --libs`
+
+memfs-winfsp-fuse3: export PKG_CONFIG_PATH=$(PWD)/winfsp.install/lib
+memfs-winfsp-fuse3: memfs-fuse3.cpp
+ ln -nsf "`regtool --wow32 get '/HKLM/Software/WinFsp/InstallDir' | cygpath -au -f -`" winfsp.install
+ g++ $^ -o $@ -g -Wall `pkg-config fuse3 --cflags --libs`
diff --git a/tst/memfs-fuse3/README.md b/tst/memfs-fuse3/README.md
new file mode 100644
index 00000000..ccfc716e
--- /dev/null
+++ b/tst/memfs-fuse3/README.md
@@ -0,0 +1,7 @@
+`Memfs-fuse3` is an in-memory FUSE3 file system.
+
+It can be built with the following tools:
+
+- Using Visual Studio (`memfs-fuse3.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/memfs-fuse3/compat.h b/tst/memfs-fuse3/compat.h
new file mode 100644
index 00000000..c2e05ed2
--- /dev/null
+++ b/tst/memfs-fuse3/compat.h
@@ -0,0 +1,97 @@
+/**
+ * @file compat.h
+ *
+ * @copyright 2015-2019 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 software
+ * in accordance with the commercial license agreement provided in
+ * conjunction with the software. The terms and conditions of any such
+ * commercial license agreement shall govern, supersede, and render
+ * ineffective any application of the GPLv3 license to this software,
+ * notwithstanding of any reference thereto in the software or
+ * associated repository.
+ */
+
+#ifndef COMPAT_H_INCLUDED
+#define COMPAT_H_INCLUDED
+
+#if !defined(_WIN32) && !defined(fuse_stat)
+
+#define fuse_uid_t uid_t
+#define fuse_gid_t gid_t
+#define fuse_pid_t pid_t
+
+#define fuse_dev_t dev_t
+#define fuse_mode_t mode_t
+#define fuse_nlink_t nlink_t
+#define fuse_off_t off_t
+
+#define fuse_fsblkcnt_t fsblkcnt_t
+#define fuse_fsfilcnt_t fsfilcnt_t
+#define fuse_blksize_t blksize_t
+#define fuse_blkcnt_t blkcnt_t
+
+#define fuse_timespec timespec
+
+#define fuse_stat stat
+
+#define fuse_statvfs statvfs
+
+#define fuse_flock flock
+
+#define fuse_iovec iovec
+
+#endif
+
+#if !defined(S_IFMT)
+#define S_IFMT 0170000
+#endif
+#if !defined(S_IFDIR)
+#define S_IFDIR 0040000
+#endif
+#if !defined(S_IFCHR)
+#define S_IFCHR 0020000
+#endif
+#if !defined(S_IFBLK)
+#define S_IFBLK 0060000
+#endif
+#if !defined(S_IFREG)
+#define S_IFREG 0100000
+#endif
+#if !defined(S_IFLNK)
+#define S_IFLNK 0120000
+#endif
+#if !defined(S_IFSOCK)
+#define S_IFSOCK 0140000
+#endif
+#if !defined(S_IFIFO)
+#define S_IFIFO 0010000
+#endif
+
+#if defined(__APPLE__)
+#define st_atim st_atimespec
+#define st_ctim st_ctimespec
+#define st_mtim st_mtimespec
+#endif
+
+#if defined(__APPLE__) || defined(__linux__) || defined(__CYGWIN__)
+#include
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(_WIN32)
+#define XATTR_CREATE 1
+#define XATTR_REPLACE 2
+#endif
+
+#if !defined(ENOATTR)
+#define ENOATTR ENODATA
+#elif !defined(ENODATA)
+#define ENODATA ENOATTR
+#endif
+
+#endif
diff --git a/tst/memfs-fuse3/memfs-fuse3.cpp b/tst/memfs-fuse3/memfs-fuse3.cpp
new file mode 100644
index 00000000..6c49d5b3
--- /dev/null
+++ b/tst/memfs-fuse3/memfs-fuse3.cpp
@@ -0,0 +1,606 @@
+/**
+ * @file memfs-fuse3.c
+ *
+ * @copyright 2015-2019 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 software
+ * in accordance with the commercial license agreement provided in
+ * conjunction with the software. The terms and conditions of any such
+ * commercial license agreement shall govern, supersede, and render
+ * ineffective any application of the GPLv3 license to this software,
+ * notwithstanding of any reference thereto in the software or
+ * associated repository.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include "compat.h"
+
+class memfs
+{
+public:
+ memfs() : _ino(1), _root(std::make_shared(_ino, S_IFDIR | 00777, 0, 0))
+ {
+ }
+
+ int main(int argc, char *argv[])
+ {
+ static fuse_operations ops =
+ {
+ getattr,
+ readlink,
+ mknod,
+ mkdir,
+ unlink,
+ rmdir,
+ symlink,
+ rename,
+ link,
+ chmod,
+ chown,
+ truncate,
+ open,
+ read,
+ write,
+ statfs,
+ flush,
+ release,
+ 0, // fsync
+ setxattr,
+ getxattr,
+ listxattr,
+ removexattr,
+ opendir,
+ readdir,
+ releasedir,
+ 0, // fsyncdir
+ init,
+ 0, // destroy
+ 0, // access
+ 0, // create
+ 0, // lock
+ utimens,
+ 0, // bmap
+ ioctl,
+ };
+ return fuse_main(argc, argv, &ops, this);
+ }
+
+private:
+ struct node_t
+ {
+ node_t(fuse_ino_t ino, fuse_mode_t mode, fuse_uid_t uid, fuse_gid_t gid, fuse_dev_t dev = 0)
+ : stat()
+ {
+ stat.st_ino = ino;
+ stat.st_mode = mode;
+ stat.st_nlink = 1;
+ stat.st_uid = uid;
+ stat.st_gid = gid;
+ stat.st_rdev = dev;
+ stat.st_atim = stat.st_mtim = stat.st_ctim = now();
+ }
+
+ void resize(size_t size, bool capacity)
+ {
+ if (capacity)
+ {
+ const size_t unit = 64 * 1024;
+ size_t newcap = (size + unit - 1) / unit * unit;
+ size_t oldcap = data.capacity();
+ if (newcap > oldcap)
+ data.reserve(newcap);
+ else if (newcap < oldcap)
+ {
+ data.resize(newcap);
+ data.shrink_to_fit();
+ }
+ }
+ data.resize(size);
+ stat.st_size = size;
+ }
+
+ struct fuse_stat stat;
+ std::vector data;
+ std::unordered_map> childmap;
+ std::unordered_map> xattrmap;
+ };
+
+ static fuse_timespec now()
+ {
+ fuse_timespec ts = { static_cast(std::time(0)) };
+ return ts;
+ }
+
+ static memfs *getself()
+ {
+ return static_cast(fuse_get_context()->private_data);
+ }
+
+ static int getattr(const char *path, struct fuse_stat *stbuf, struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path, fi);
+ if (!node)
+ return -ENOENT;
+ *stbuf = node->stat;
+ return 0;
+ }
+
+ static int readlink(const char *path, char *buf, size_t size)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path);
+ if (!node)
+ return -ENOENT;
+ if (S_IFLNK != (node->stat.st_mode & S_IFMT))
+ return EINVAL;
+ size = std::min(size - 1, node->data.size());
+ std::memcpy(buf, node->data.data(), size);
+ buf[size] = '\0';
+ return 0;
+ }
+
+ static int mknod(const char *path, fuse_mode_t mode, fuse_dev_t dev)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->make_node(path, mode, dev);
+ }
+
+ static int mkdir(const char *path, fuse_mode_t mode)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->make_node(path, S_IFDIR | (mode & 07777), 0);
+ }
+
+ static int unlink(const char *path)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->remove_node(path, false);
+ }
+
+ static int rmdir(const char *path)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->remove_node(path, true);
+ }
+
+ static int symlink(const char *dstpath, const char *srcpath)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->make_node(srcpath, S_IFLNK | 00777, 0, dstpath);
+ }
+
+ static int rename(const char *oldpath, const char *newpath, unsigned int flags)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto oldlookup = self->lookup_node(oldpath);
+ auto oldprnt = std::get<0>(oldlookup);
+ auto oldname = std::get<1>(oldlookup);
+ auto oldnode = std::get<2>(oldlookup);
+ if (!oldnode)
+ return -ENOENT;
+ auto newlookup = self->lookup_node(newpath);
+ auto newprnt = std::get<0>(newlookup);
+ auto newname = std::get<1>(newlookup);
+ auto newnode = std::get<2>(newlookup);
+ if (!newprnt)
+ return -ENOENT;
+ if (newname.empty())
+ // guard against directory loop creation
+ return -EINVAL;
+ if (oldprnt == newprnt && oldname == newname)
+ return 0;
+ if (newnode)
+ {
+ if (int errc = self->remove_node(newpath, S_IFDIR == (oldnode->stat.st_mode & S_IFMT)))
+ return errc;
+ }
+ oldprnt->childmap.erase(oldname);
+ newprnt->childmap[newname] = oldnode;
+ return 0;
+ }
+
+ static int link(const char *oldpath, const char *newpath)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto oldlookup = self->lookup_node(oldpath);
+ auto oldnode = std::get<2>(oldlookup);
+ if (!oldnode)
+ return -ENOENT;
+ auto newlookup = self->lookup_node(newpath);
+ auto newprnt = std::get<0>(newlookup);
+ auto newname = std::get<1>(newlookup);
+ auto newnode = std::get<2>(newlookup);
+ if (!newprnt)
+ return -ENOENT;
+ if (newnode)
+ return -EEXIST;
+ oldnode->stat.st_nlink++;
+ newprnt->childmap[newname] = oldnode;
+ oldnode->stat.st_ctim = newprnt->stat.st_ctim = newprnt->stat.st_mtim = now();
+ return 0;
+ }
+
+ static int chmod(const char *path, fuse_mode_t mode,
+ struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path, fi);
+ if (!node)
+ return -ENOENT;
+ node->stat.st_mode = (node->stat.st_mode & S_IFMT) | (mode & 07777);
+ node->stat.st_ctim = now();
+ return 0;
+ }
+
+ static int chown(const char *path, fuse_uid_t uid, fuse_gid_t gid,
+ struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path, fi);
+ if (!node)
+ return -ENOENT;
+ if (-1 != uid)
+ node->stat.st_uid = uid;
+ if (-1 != gid)
+ node->stat.st_gid = gid;
+ node->stat.st_ctim = now();
+ return 0;
+ }
+
+ static int truncate(const char *path, fuse_off_t size,
+ struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path, fi);
+ if (!node)
+ return -ENOENT;
+ if (SIZE_MAX < size)
+ return -EFBIG;
+ node->resize(static_cast(size), true);
+ node->stat.st_ctim = node->stat.st_mtim = now();
+ return 0;
+ }
+
+ static int open(const char *path, struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->open_node(path, false, fi);
+ }
+
+ static int read(const char *path, char *buf, size_t size, fuse_off_t off,
+ struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path, fi);
+ if (!node)
+ return -ENOENT;
+ fuse_off_t endoff = std::min(
+ off + static_cast(size), static_cast(node->data.size()));
+ if (off > endoff)
+ return 0;
+ std::memcpy(buf, node->data.data() + off, static_cast(endoff - off));
+ node->stat.st_atim = now();
+ return static_cast(endoff - off);
+ }
+
+ static int write(const char *path, const char *buf, size_t size, fuse_off_t off,
+ struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path, fi);
+ if (!node)
+ return -ENOENT;
+ fuse_off_t endoff = off + static_cast(size);
+ if (SIZE_MAX < endoff)
+ return -EFBIG;
+ if (node->data.size() < endoff)
+ node->resize(static_cast(endoff), true);
+ std::memcpy(node->data.data() + off, buf, static_cast(endoff - off));
+ node->stat.st_ctim = node->stat.st_mtim = now();
+ return static_cast(endoff - off);
+ }
+
+ static int statfs(const char *path, struct fuse_statvfs *stbuf)
+ {
+ return -ENOSYS;
+ }
+
+ static int flush(const char *path, struct fuse_file_info *fi)
+ {
+ return -ENOSYS;
+ }
+
+ static int release(const char *path, struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->close_node(fi);
+ }
+
+ static int setxattr(const char *path, const char *name0, const char *value, size_t size,
+ int flags)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path);
+ if (!node)
+ return -ENOENT;
+ if (0 == std::strcmp("com.apple.ResourceFork", name0))
+ return -ENOTSUP;
+ std::string name = name0;
+ if (XATTR_CREATE == flags)
+ {
+ if (node->xattrmap.end() != node->xattrmap.find(name))
+ return -EEXIST;
+ }
+ else if (XATTR_REPLACE == flags)
+ {
+ if (node->xattrmap.end() == node->xattrmap.find(name))
+ return -ENOATTR;
+ }
+ node->xattrmap[name].assign(value, value + size);
+ return 0;
+ }
+
+ static int getxattr(const char *path, const char *name0, char *value, size_t size)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path);
+ if (!node)
+ return -ENOENT;
+ if (0 == std::strcmp("com.apple.ResourceFork", name0))
+ return -ENOTSUP;
+ std::string name = name0;
+ auto iter = node->xattrmap.find(name);
+ if (node->xattrmap.end() == iter)
+ return -ENOATTR;
+ if (0 != size)
+ {
+ if (iter->second.size() > size)
+ return -ERANGE;
+ std::memcpy(value, iter->second.data(), iter->second.size());
+ }
+ return static_cast(iter->second.size());
+ }
+
+ static int listxattr(const char *path, char *namebuf, size_t size)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path);
+ if (!node)
+ return -ENOENT;
+ size_t copysize = 0;
+ for (auto elem : node->xattrmap)
+ {
+ size_t namesize = elem.first.size() + 1;
+ if (0 != size)
+ {
+ if (copysize + namesize > size)
+ return -ERANGE;
+ std::memcpy(namebuf + copysize, elem.first.c_str(), namesize);
+ copysize += namesize;
+ }
+ }
+ return static_cast(copysize);
+ }
+
+ static int removexattr(const char *path, const char *name0)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path);
+ if (!node)
+ return -ENOENT;
+ if (0 == std::strcmp("com.apple.ResourceFork", name0))
+ return -ENOTSUP;
+ std::string name = name0;
+ return node->xattrmap.erase(name) ? 0 : -ENOATTR;
+ }
+
+ static int opendir(const char *path, struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->open_node(path, true, fi);
+ }
+
+ static int readdir(const char *path, void *buf, fuse_fill_dir_t filler, fuse_off_t off,
+ struct fuse_file_info *fi, enum fuse_readdir_flags)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path, fi);
+ if (!node)
+ return -ENOENT;
+ filler(buf, ".", &node->stat, 0, FUSE_FILL_DIR_PLUS);
+ filler(buf, "..", nullptr, 0, FUSE_FILL_DIR_PLUS);
+ for (auto elem : node->childmap)
+ if (!filler(buf, elem.first.c_str(), &elem.second->stat, 0, FUSE_FILL_DIR_PLUS))
+ break;
+ return 0;
+ }
+
+ static int releasedir(const char *path, struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ return self->close_node(fi);
+ }
+
+ static void *init(struct fuse_conn_info *conn,
+ struct fuse_config *conf)
+ {
+ conn->want |= (conn->capable & FUSE_CAP_READDIRPLUS);
+ return getself();
+ }
+
+ static int utimens(const char *path, const struct fuse_timespec tmsp[2],
+ struct fuse_file_info *fi)
+ {
+ auto self = getself();
+ std::lock_guard lock(self->_mutex);
+ auto node = self->get_node(path, fi);
+ if (!node)
+ return -ENOENT;
+ if (tmsp)
+ {
+ node->stat.st_ctim = now();
+ node->stat.st_atim = tmsp[0];
+ node->stat.st_mtim = tmsp[1];
+ }
+ else
+ node->stat.st_ctim = node->stat.st_atim = node->stat.st_mtim = now();
+ return 0;
+ }
+
+ static int ioctl(const char *path, int cmd, void *arg, struct fuse_file_info *fi,
+ unsigned int flags, void *data)
+ {
+ return -ENOSYS;
+ }
+
+ std::tuple, std::string, std::shared_ptr>
+ lookup_node(const char *path, node_t *ancestor = nullptr)
+ {
+ auto prnt = _root;
+ std::string name;
+ auto node = prnt;
+ for (const char *part = path, *p; *part; part = p + !!(*p))
+ {
+ for (p = part; *p && '/' != *p; p++)
+ ;
+ if (part == p)
+ continue;
+ prnt = node;
+ if (!node)
+ break;
+ name.assign(part, p);
+ auto iter = node->childmap.find(name);
+ node = node->childmap.end() != iter ? iter->second : nullptr;
+ if (ancestor && node.get() == ancestor)
+ {
+ name.assign(""); // special case loop condition
+ break;
+ }
+ }
+ return std::make_tuple(prnt, name, node);
+ }
+
+ int make_node(const char *path, fuse_mode_t mode, fuse_dev_t dev, const char *data = nullptr)
+ {
+ auto lookup = lookup_node(path);
+ auto prnt = std::get<0>(lookup);
+ auto name = std::get<1>(lookup);
+ auto node = std::get<2>(lookup);
+ if (!prnt)
+ return -ENOENT;
+ if (node)
+ return -EEXIST;
+ fuse_context *context = fuse_get_context();
+ node = std::make_shared(++_ino, mode, context->uid, context->gid, dev);
+ if (data)
+ {
+ node->resize(std::strlen(data), false);
+ std::memcpy(node->data.data(), data, node->data.size());
+ }
+ prnt->childmap[name] = node;
+ prnt->stat.st_ctim = prnt->stat.st_mtim = node->stat.st_ctim;
+ return 0;
+ }
+
+ int remove_node(const char *path, bool dir)
+ {
+ auto lookup = lookup_node(path);
+ auto prnt = std::get<0>(lookup);
+ auto name = std::get<1>(lookup);
+ auto node = std::get<2>(lookup);
+ if (!node)
+ return -ENOENT;
+ if (!dir && S_IFDIR == (node->stat.st_mode & S_IFMT))
+ return -EISDIR;
+ if (dir && S_IFDIR == (node->stat.st_mode & S_IFMT))
+ return -ENOTDIR;
+ if (0 < node->childmap.size())
+ return -ENOTEMPTY;
+ node->stat.st_nlink--;
+ prnt->childmap.erase(name);
+ node->stat.st_ctim = prnt->stat.st_ctim = prnt->stat.st_mtim = now();
+ return 0;
+ }
+
+ int open_node(const char *path, bool dir, struct fuse_file_info *fi)
+ {
+ auto node = std::get<2>(lookup_node(path));
+ if (!node)
+ return -ENOENT;
+ if (!dir && S_IFDIR == (node->stat.st_mode & S_IFMT))
+ return -EISDIR;
+ if (dir && S_IFDIR == (node->stat.st_mode & S_IFMT))
+ return -ENOTDIR;
+ // A file descriptor is a raw pointer to a shared_ptr.
+ // This has the effect of incrementing the shared_ptr
+ // refcount, thus keeping an open node around even
+ // if the node is unlinked.
+ fi->fh = (uint64_t)(uintptr_t)new std::shared_ptr(node);
+ return 0;
+ }
+
+ int close_node(struct fuse_file_info *fi)
+ {
+ delete (std::shared_ptr *)(uintptr_t)fi->fh;
+ return 0;
+ }
+
+ std::shared_ptr get_node(const char *path, struct fuse_file_info *fi = nullptr)
+ {
+ if (!fi)
+ return std::get<2>(lookup_node(path));
+ else
+ return *(std::shared_ptr *)(uintptr_t)fi->fh;
+ }
+
+private:
+ std::mutex _mutex;
+ fuse_ino_t _ino;
+ std::shared_ptr _root;
+};
+
+int main(int argc, char *argv[])
+{
+ return memfs().main(argc, argv);
+}
diff --git a/tst/memfs-fuse3/memfs-fuse3.sln b/tst/memfs-fuse3/memfs-fuse3.sln
new file mode 100644
index 00000000..9e3b6efa
--- /dev/null
+++ b/tst/memfs-fuse3/memfs-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}") = "memfs-fuse3", "memfs-fuse3.vcxproj", "{CF538F42-C714-4653-B351-E72FD7B0B217}"
+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
+ {CF538F42-C714-4653-B351-E72FD7B0B217}.Debug|x64.ActiveCfg = Debug|x64
+ {CF538F42-C714-4653-B351-E72FD7B0B217}.Debug|x64.Build.0 = Debug|x64
+ {CF538F42-C714-4653-B351-E72FD7B0B217}.Debug|x86.ActiveCfg = Debug|Win32
+ {CF538F42-C714-4653-B351-E72FD7B0B217}.Debug|x86.Build.0 = Debug|Win32
+ {CF538F42-C714-4653-B351-E72FD7B0B217}.Release|x64.ActiveCfg = Release|x64
+ {CF538F42-C714-4653-B351-E72FD7B0B217}.Release|x64.Build.0 = Release|x64
+ {CF538F42-C714-4653-B351-E72FD7B0B217}.Release|x86.ActiveCfg = Release|Win32
+ {CF538F42-C714-4653-B351-E72FD7B0B217}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/tst/memfs-fuse3/memfs-fuse3.vcxproj b/tst/memfs-fuse3/memfs-fuse3.vcxproj
new file mode 100644
index 00000000..ca26671f
--- /dev/null
+++ b/tst/memfs-fuse3/memfs-fuse3.vcxproj
@@ -0,0 +1,189 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {CF538F42-C714-4653-B351-E72FD7B0B217}
+ Win32Proj
+ memfsfuse3
+ $(LatestTargetPlatformVersion)
+
+
+
+ Application
+ true
+ $(DefaultPlatformToolset)
+ Unicode
+
+
+ Application
+ false
+ $(DefaultPlatformToolset)
+ true
+ Unicode
+
+
+ Application
+ true
+ $(DefaultPlatformToolset)
+ Unicode
+
+
+ Application
+ false
+ $(DefaultPlatformToolset)
+ 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
+ MultiThreadedDebug
+ 4018
+
+
+ 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
+ MultiThreadedDebug
+ 4018
+
+
+ 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
+ 4018
+
+
+ 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
+ 4018
+
+
+ Console
+ true
+ true
+ true
+ $(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies)
+ winfsp-$(PlatformTarget).dll
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tst/memfs-fuse3/memfs-fuse3.vcxproj.filters b/tst/memfs-fuse3/memfs-fuse3.vcxproj.filters
new file mode 100644
index 00000000..cc996b8d
--- /dev/null
+++ b/tst/memfs-fuse3/memfs-fuse3.vcxproj.filters
@@ -0,0 +1,19 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+
+
+ Source
+
+
+
+
+ Source
+
+
+
\ No newline at end of file