winfsp/src/dll/fuse/fuse.c
2016-06-08 22:15:24 -07:00

648 lines
19 KiB
C

/**
* @file dll/fuse/fuse.c
*
* @copyright 2015-2016 Bill Zissimopoulos
*/
/*
* This file is part of WinFsp.
*
* You can redistribute it and/or modify it under the terms of the
* GNU Affero 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 <dll/fuse/library.h>
#define FSP_FUSE_SECTORSIZE_MIN 512
#define FSP_FUSE_SECTORSIZE_MAX 4096
struct fuse_chan
{
PWSTR MountPoint;
UINT8 Buffer[];
};
#define FSP_FUSE_CORE_OPT(n, f, v) { n, offsetof(struct fsp_fuse_core_opt_data, f), v }
struct fsp_fuse_core_opt_data
{
struct fsp_fuse_env *env;
int help, debug;
int hard_remove,
use_ino, readdir_ino,
set_umask, umask,
set_uid, uid,
set_gid, gid,
set_attr_timeout, attr_timeout;
int set_FileInfoTimeout;
int CaseInsensitiveSearch, PersistentAcls,
ReparsePoints, NamedStreams,
ReadOnlyVolume;
FSP_FSCTL_VOLUME_PARAMS VolumeParams;
};
static struct fuse_opt fsp_fuse_core_opts[] =
{
FUSE_OPT_KEY("-h", 'h'),
FUSE_OPT_KEY("--help", 'h'),
FUSE_OPT_KEY("-V", 'V'),
FUSE_OPT_KEY("--version", 'V'),
FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP),
FSP_FUSE_CORE_OPT("-d", debug, 1),
FSP_FUSE_CORE_OPT("debug", debug, 1),
FSP_FUSE_CORE_OPT("hard_remove", hard_remove, 1),
FSP_FUSE_CORE_OPT("use_ino", use_ino, 1),
FSP_FUSE_CORE_OPT("readdir_ino", readdir_ino, 1),
FUSE_OPT_KEY("direct_io", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("kernel_cache", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("auto_cache", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("noauto_cache", FUSE_OPT_KEY_DISCARD),
FSP_FUSE_CORE_OPT("umask=", set_umask, 1),
FSP_FUSE_CORE_OPT("umask=%o", umask, 0),
FSP_FUSE_CORE_OPT("uid=", set_uid, 1),
FSP_FUSE_CORE_OPT("uid=%d", uid, 0),
FSP_FUSE_CORE_OPT("gid=", set_gid, 1),
FSP_FUSE_CORE_OPT("gid=%d", gid, 0),
FUSE_OPT_KEY("entry_timeout", FUSE_OPT_KEY_DISCARD),
FSP_FUSE_CORE_OPT("attr_timeout=", set_attr_timeout, 1),
FSP_FUSE_CORE_OPT("attr_timeout=%d", attr_timeout, 0),
FUSE_OPT_KEY("ac_attr_timeout", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("negative_timeout", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("noforget", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("intr", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("intr_signal=", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("modules=", FUSE_OPT_KEY_DISCARD),
FSP_FUSE_CORE_OPT("SectorSize=%hu", VolumeParams.SectorSize, 4096),
FSP_FUSE_CORE_OPT("SectorsPerAllocationUnit=%hu", VolumeParams.SectorsPerAllocationUnit, 1),
FSP_FUSE_CORE_OPT("MaxComponentLength=%hu", VolumeParams.MaxComponentLength, 0),
FSP_FUSE_CORE_OPT("VolumeCreationTime=%lli", VolumeParams.VolumeCreationTime, 0),
FSP_FUSE_CORE_OPT("VolumeSerialNumber=%lx", VolumeParams.VolumeSerialNumber, 0),
FSP_FUSE_CORE_OPT("TransactTimeout=%u", VolumeParams.TransactTimeout, 0),
FSP_FUSE_CORE_OPT("IrpTimeout=%u", VolumeParams.IrpTimeout, 0),
FSP_FUSE_CORE_OPT("IrpCapacity=%u", VolumeParams.IrpCapacity, 0),
FSP_FUSE_CORE_OPT("FileInfoTimeout=", set_FileInfoTimeout, 1),
FSP_FUSE_CORE_OPT("FileInfoTimeout=%d", VolumeParams.FileInfoTimeout, 0),
FSP_FUSE_CORE_OPT("CaseInsensitiveSearch", CaseInsensitiveSearch, 1),
FSP_FUSE_CORE_OPT("PersistentAcls", PersistentAcls, 1),
FSP_FUSE_CORE_OPT("ReparsePoints", ReparsePoints, 1),
FSP_FUSE_CORE_OPT("NamedStreams", NamedStreams, 1),
FUSE_OPT_KEY("HardLinks", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("ExtendedAttributes", FUSE_OPT_KEY_DISCARD),
FSP_FUSE_CORE_OPT("ReadOnlyVolume", ReadOnlyVolume, 1),
FUSE_OPT_KEY("--UNC=", 'U'),
FUSE_OPT_KEY("--VolumePrefix=", 'U'),
FUSE_OPT_END,
};
static DWORD fsp_fuse_tlskey = TLS_OUT_OF_INDEXES;
static INIT_ONCE fsp_fuse_initonce_v = INIT_ONCE_STATIC_INIT;
VOID fsp_fuse_initialize(BOOLEAN Dynamic)
{
}
VOID fsp_fuse_finalize(BOOLEAN Dynamic)
{
/*
* This function is called during DLL_PROCESS_DETACH. We must therefore keep
* finalization tasks to a minimum.
*
* We must free our TLS key (if any). We only do so if the library
* is being explicitly unloaded (rather than the process exiting).
*/
if (Dynamic && TLS_OUT_OF_INDEXES != fsp_fuse_tlskey)
{
/* !!!:
* We should also free all thread local contexts, which means putting them in a list,
* protected with a critical section, etc. Arghhh!
*
* I am too lazy and I am not going to do that, unless people start using this
* DLL dynamically (LoadLibrary/FreeLibrary).
*/
TlsFree(fsp_fuse_tlskey);
}
}
VOID fsp_fuse_finalize_thread(VOID)
{
struct fuse_context *context;
if (TLS_OUT_OF_INDEXES != fsp_fuse_tlskey)
{
context = TlsGetValue(fsp_fuse_tlskey);
if (0 != context)
{
MemFree(context);
TlsSetValue(fsp_fuse_tlskey, 0);
}
}
}
static BOOL WINAPI fsp_fuse_initonce_f(
PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context)
{
fsp_fuse_tlskey = TlsAlloc();
return TRUE;
}
static inline VOID fsp_fuse_initonce(VOID)
{
InitOnceExecuteOnce(&fsp_fuse_initonce_v, fsp_fuse_initonce_f, 0, 0);
}
FSP_FUSE_API int fsp_fuse_version(struct fsp_fuse_env *env)
{
return FUSE_VERSION;
}
FSP_FUSE_API struct fuse_chan *fsp_fuse_mount(struct fsp_fuse_env *env,
const char *mountpoint, struct fuse_args *args)
{
struct fuse_chan *ch = 0;
int Size;
if (0 == mountpoint)
mountpoint = "";
Size = MultiByteToWideChar(CP_UTF8, 0, mountpoint, -1, 0, 0);
if (0 == Size)
goto fail;
ch = MemAlloc(sizeof *ch + Size * sizeof(WCHAR));
if (0 == ch)
goto fail;
ch->MountPoint = (PVOID)ch->Buffer;
Size = MultiByteToWideChar(CP_UTF8, 0, mountpoint, -1, ch->MountPoint, Size);
if (0 == Size)
goto fail;
return ch;
fail:
MemFree(ch);
return 0;
}
FSP_FUSE_API void fsp_fuse_unmount(struct fsp_fuse_env *env,
const char *mountpoint, struct fuse_chan *ch)
{
MemFree(ch);
}
FSP_FUSE_API int fsp_fuse_is_lib_option(struct fsp_fuse_env *env,
const char *opt)
{
return fsp_fuse_opt_match(env, fsp_fuse_core_opts, opt);
}
static void fsp_fuse_cleanup(struct fuse *f);
static NTSTATUS fsp_fuse_preflight(struct fuse *f)
{
NTSTATUS Result;
Result = FspFsctlPreflight(f->VolumeParams.Prefix[0] ?
L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME);
if (!NT_SUCCESS(Result))
return Result;
if (L'\0' != f->MountPoint)
{
if ((
(L'A' <= f->MountPoint[0] && f->MountPoint[0] <= L'Z') ||
(L'a' <= f->MountPoint[0] && f->MountPoint[0] <= L'z')
) &&
L':' == f->MountPoint[1] || L'\0' == f->MountPoint[2])
{
if (GetLogicalDrives() & (1 << ((f->MountPoint[0] & ~0x20) - 'a')))
return STATUS_OBJECT_NAME_COLLISION;
}
else
if (L'*' == f->MountPoint[0] && L'\0' == f->MountPoint[1])
;
else
return STATUS_OBJECT_NAME_INVALID;
}
return STATUS_SUCCESS;
}
static NTSTATUS fsp_fuse_svcstart(FSP_SERVICE *Service, ULONG argc, PWSTR *argv)
{
struct fuse *f = Service->UserContext;
struct fuse_context *context;
struct fuse_conn_info conn;
NTSTATUS Result;
context = fsp_fuse_get_context(0);
if (0 == context)
{
Result = STATUS_INSUFFICIENT_RESOURCES;
goto fail;
}
context->fuse = f;
context->private_data = f->data;
context->uid = context->gid = -1;
memset(&conn, 0, sizeof conn);
conn.proto_major = 7; /* pretend that we are FUSE kernel protocol 7.12 */
conn.proto_minor = 12; /* which was current at the time of FUSE 2.8 */
conn.async_read = 1;
conn.max_write = UINT_MAX;
conn.capable =
FUSE_CAP_ASYNC_READ |
//FUSE_CAP_POSIX_LOCKS | /* WinFsp handles locking in the FSD currently */
//FUSE_CAP_ATOMIC_O_TRUNC | /* due to Windows/WinFsp design, no support */
//FUSE_CAP_EXPORT_SUPPORT | /* not needed in Windows/WinFsp */
FUSE_CAP_BIG_WRITES |
FUSE_CAP_DONT_MASK;
if (0 != f->ops.init)
context->private_data = f->data = f->ops.init(&conn);
f->fsinit = TRUE;
if (0 != f->ops.statfs)
{
struct fuse_statvfs stbuf;
int err;
memset(&stbuf, 0, sizeof stbuf);
err = f->ops.statfs("/", &stbuf);
if (0 != err)
goto fail;
if (stbuf.f_frsize > FSP_FUSE_SECTORSIZE_MAX)
stbuf.f_frsize = FSP_FUSE_SECTORSIZE_MAX;
if (0 == f->VolumeParams.SectorSize)
f->VolumeParams.SectorSize = (UINT16)stbuf.f_frsize;
if (0 == f->VolumeParams.MaxComponentLength)
f->VolumeParams.MaxComponentLength = (UINT16)stbuf.f_namemax;
}
if (0 != f->ops.getattr)
{
struct fuse_stat stbuf;
int err;
memset(&stbuf, 0, sizeof stbuf);
err = f->ops.getattr("/", (void *)&stbuf);
if (0 != err)
goto fail;
if (0 == f->VolumeParams.VolumeCreationTime)
{
if (0 != stbuf.st_birthtim.tv_sec)
f->VolumeParams.VolumeCreationTime =
Int32x32To64(stbuf.st_birthtim.tv_sec, 10000000) + 116444736000000000 +
stbuf.st_birthtim.tv_nsec / 100;
else
if (0 != stbuf.st_ctim.tv_sec)
f->VolumeParams.VolumeCreationTime =
Int32x32To64(stbuf.st_ctim.tv_sec, 10000000) + 116444736000000000 +
stbuf.st_ctim.tv_nsec / 100;
}
}
/* the FSD does not currently limit these VolumeParams fields; do so here! */
if (f->VolumeParams.SectorSize < FSP_FUSE_SECTORSIZE_MIN)
f->VolumeParams.SectorSize = FSP_FUSE_SECTORSIZE_MIN;
if (f->VolumeParams.SectorSize > FSP_FUSE_SECTORSIZE_MAX)
f->VolumeParams.SectorSize = FSP_FUSE_SECTORSIZE_MAX;
if (f->VolumeParams.MaxComponentLength > 255)
f->VolumeParams.MaxComponentLength = 255;
if (0 == f->VolumeParams.VolumeCreationTime)
{
FILETIME FileTime;
GetSystemTimeAsFileTime(&FileTime);
f->VolumeParams.VolumeCreationTime = *(PUINT64)&FileTime;
}
if (0 == f->VolumeParams.VolumeSerialNumber)
f->VolumeParams.VolumeSerialNumber =
((PLARGE_INTEGER)&f->VolumeParams.VolumeCreationTime)->HighPart ^
((PLARGE_INTEGER)&f->VolumeParams.VolumeCreationTime)->LowPart;
Result = FspFileSystemCreate(
f->VolumeParams.Prefix[0] ?
L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME,
&f->VolumeParams, &fsp_fuse_intf,
&f->FileSystem);
if (!NT_SUCCESS(Result))
{
FspServiceLog(EVENTLOG_ERROR_TYPE,
L"Cannot create " FSP_FUSE_LIBRARY_NAME " file system.");
goto fail;
}
f->FileSystem->UserContext = f;
FspFileSystemSetOperationGuard(f->FileSystem, fsp_fuse_op_enter, fsp_fuse_op_leave);
FspFileSystemSetDebugLog(f->FileSystem, f->DebugLog);
if (L'\0' != f->MountPoint)
{
Result = FspFileSystemSetMountPoint(f->FileSystem,
L'*' == f->MountPoint[0] && L'\0' == f->MountPoint[1] ? 0 : f->MountPoint);
if (!NT_SUCCESS(Result))
{
FspServiceLog(EVENTLOG_ERROR_TYPE,
L"Cannot set " FSP_FUSE_LIBRARY_NAME " file system mount point.");
goto fail;
}
}
Result = FspFileSystemStartDispatcher(f->FileSystem, 0);
if (!NT_SUCCESS(Result))
{
FspServiceLog(EVENTLOG_ERROR_TYPE,
L"Cannot start " FSP_FUSE_LIBRARY_NAME " file system dispatcher.");
goto fail;
}
return STATUS_SUCCESS;
fail:
fsp_fuse_cleanup(f);
return Result;
}
static NTSTATUS fsp_fuse_svcstop(FSP_SERVICE *Service)
{
struct fuse *f = Service->UserContext;
FspFileSystemStopDispatcher(f->FileSystem);
fsp_fuse_cleanup(f);
return STATUS_SUCCESS;
}
static void fsp_fuse_cleanup(struct fuse *f)
{
if (0 != f->FileSystem)
{
FspFileSystemDelete(f->FileSystem);
f->FileSystem = 0;
}
if (f->fsinit)
{
if (f->ops.destroy)
f->ops.destroy(f->data);
f->fsinit = FALSE;
}
}
static int fsp_fuse_core_opt_proc(void *opt_data0, const char *arg, int key,
struct fuse_args *outargs)
{
struct fsp_fuse_core_opt_data *opt_data = opt_data0;
switch (key)
{
default:
return 1;
case 'h':
FspServiceLog(EVENTLOG_ERROR_TYPE, L""
FSP_FUSE_LIBRARY_NAME " options:\n"
" -o SectorSize=N sector size for Windows (512-4096, deflt: 512)\n"
" -o SectorsPerAllocationUnit=N allocation unit size (deflt: 1*SectorSize)\n"
" -o MaxComponentLength=N max file name component length (deflt: 255)\n"
" -o VolumeCreationTime=T volume creation time (FILETIME hex format)\n"
" -o VolumeSerialNumber=N 32-bit wide\n"
" -o FileInfoTimeout=N FileInfo/Security/VolumeInfo timeout (millisec)\n"
" -o CaseInsensitiveSearch file system supports case-insensitive file names\n"
" -o PersistentAcls file system preserves and enforces ACL's\n"
//" -o ReparsePoints file system supports reparse points\n"
//" -o NamedStreams file system supports named streams\n"
//" -o ReadOnlyVolume file system is read only\n"
" --UNC=U --VolumePrefix=U UNC prefix (\\Server\\Share)\n");
opt_data->help = 1;
return 1;
case 'V':
FspServiceLog(EVENTLOG_ERROR_TYPE, L""
FSP_FUSE_LIBRARY_NAME " version %d.%d",
FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION);
opt_data->help = 1;
return 1;
case 'U':
if ('U' == arg[2])
arg += sizeof "--UNC" - 1;
else if ('V' == arg[2])
arg += sizeof "--VolumePrefix" - 1;
if (0 == MultiByteToWideChar(CP_UTF8, 0, arg, -1,
opt_data->VolumeParams.Prefix, sizeof opt_data->VolumeParams.Prefix / sizeof(WCHAR)))
return -1;
return 0;
}
}
FSP_FUSE_API struct fuse *fsp_fuse_new(struct fsp_fuse_env *env,
struct fuse_chan *ch, struct fuse_args *args,
const struct fuse_operations *ops, size_t opsize, void *data)
{
struct fuse *f = 0;
struct fsp_fuse_core_opt_data opt_data;
ULONG Size;
PWSTR ErrorMessage = L".";
NTSTATUS Result;
if (opsize > sizeof(struct fuse_operations))
opsize = sizeof(struct fuse_operations);
memset(&opt_data, 0, sizeof opt_data);
opt_data.env = env;
if (-1 == fsp_fuse_opt_parse(env, args, &opt_data, fsp_fuse_core_opts, fsp_fuse_core_opt_proc))
return 0;
if (opt_data.help)
return 0;
if (!opt_data.set_FileInfoTimeout && opt_data.set_attr_timeout)
opt_data.VolumeParams.FileInfoTimeout = opt_data.set_attr_timeout * 1000;
opt_data.VolumeParams.CaseSensitiveSearch = !opt_data.CaseInsensitiveSearch;
opt_data.VolumeParams.PersistentAcls = !!opt_data.PersistentAcls;
opt_data.VolumeParams.ReparsePoints = !!opt_data.ReparsePoints;
opt_data.VolumeParams.NamedStreams = !!opt_data.NamedStreams;
opt_data.VolumeParams.ReadOnlyVolume = !!opt_data.ReadOnlyVolume;
f = MemAlloc(sizeof *f);
if (0 == f)
goto fail;
memset(f, 0, sizeof *f);
f->env = env;
memcpy(&f->ops, ops, opsize);
f->data = data;
f->DebugLog = opt_data.debug ? -1 : 0;
memcpy(&f->VolumeParams, &opt_data.VolumeParams, sizeof opt_data.VolumeParams);
Size = (lstrlenW(ch->MountPoint) + 1) * sizeof(WCHAR);
f->MountPoint = MemAlloc(Size);
if (0 == f->MountPoint)
goto fail;
memcpy(f->MountPoint, ch->MountPoint, Size);
Result = FspServiceCreate(FspDiagIdent(), fsp_fuse_svcstart, fsp_fuse_svcstop, 0, &f->Service);
if (!NT_SUCCESS(Result))
goto fail;
FspServiceAllowConsoleMode(f->Service);
f->Service->UserContext = f;
Result = fsp_fuse_preflight(f);
if (!NT_SUCCESS(Result))
{
switch (Result)
{
case STATUS_ACCESS_DENIED:
ErrorMessage = L": access denied.";
break;
case STATUS_NO_SUCH_DEVICE:
ErrorMessage = L": FSD not found.";
break;
case STATUS_OBJECT_NAME_INVALID:
ErrorMessage = L": invalid mount point.";
break;
case STATUS_OBJECT_NAME_COLLISION:
ErrorMessage = L": mount point in use.";
break;
default:
ErrorMessage = L": unspecified error.";
break;
}
goto fail;
}
return f;
fail:
FspServiceLog(EVENTLOG_ERROR_TYPE,
L"Cannot create " FSP_FUSE_LIBRARY_NAME " file system%s",
ErrorMessage);
if (0 != f)
fsp_fuse_destroy(env, f);
return 0;
}
FSP_FUSE_API void fsp_fuse_destroy(struct fsp_fuse_env *env,
struct fuse *f)
{
fsp_fuse_cleanup(f);
if (0 != f->Service)
FspServiceDelete(f->Service);
MemFree(f->MountPoint);
MemFree(f);
}
static int fsp_fuse_loop_internal(struct fsp_fuse_env *env,
struct fuse *f, FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY GuardStrategy)
{
NTSTATUS Result;
ULONG ExitCode;
FspFileSystemSetOperationGuardStrategy(f->FileSystem, GuardStrategy);
Result = FspServiceLoop(f->Service);
ExitCode = FspServiceGetExitCode(f->Service);
if (!NT_SUCCESS(Result))
goto fail;
if (0 != ExitCode)
FspServiceLog(EVENTLOG_WARNING_TYPE,
L"The service %s has exited (ExitCode=%ld).", f->Service->ServiceName, ExitCode);
return 0;
fail:
FspServiceLog(EVENTLOG_ERROR_TYPE,
L"The service %s has failed to run (Status=%lx).", f->Service->ServiceName, Result);
return -1;
}
FSP_FUSE_API int fsp_fuse_loop(struct fsp_fuse_env *env,
struct fuse *f)
{
return fsp_fuse_loop_internal(env, f,
FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_COARSE);
}
FSP_FUSE_API int fsp_fuse_loop_mt(struct fsp_fuse_env *env,
struct fuse *f)
{
return fsp_fuse_loop_internal(env, f,
FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_FINE);
}
FSP_FUSE_API void fsp_fuse_exit(struct fsp_fuse_env *env,
struct fuse *f)
{
FspServiceStop(f->Service);
}
FSP_FUSE_API struct fuse_context *fsp_fuse_get_context(struct fsp_fuse_env *reserved)
{
struct fuse_context *context;
fsp_fuse_initonce();
if (TLS_OUT_OF_INDEXES == fsp_fuse_tlskey)
return 0;
context = TlsGetValue(fsp_fuse_tlskey);
if (0 == context)
{
context = MemAlloc(sizeof *context);
if (0 == context)
return 0;
memset(context, 0, sizeof *context);
context->pid = -1;
TlsSetValue(fsp_fuse_tlskey, context);
}
return context;
}
FSP_FUSE_API NTSTATUS fsp_fuse_ntstatus_from_errno(struct fsp_fuse_env *env,
int err)
{
if ('C' == env->environment)
switch (err)
{
#undef FSP_FUSE_ERRNO
#define FSP_FUSE_ERRNO 67
#include "errno.i"
default:
return STATUS_ACCESS_DENIED;
}
else
switch (err)
{
#undef FSP_FUSE_ERRNO
#define FSP_FUSE_ERRNO 87
#include "errno.i"
default:
return STATUS_ACCESS_DENIED;
}
}