diff --git a/inc/winfsp/fsctl.h b/inc/winfsp/fsctl.h index efc1dcec..263c2ceb 100644 --- a/inc/winfsp/fsctl.h +++ b/inc/winfsp/fsctl.h @@ -59,6 +59,8 @@ extern const __declspec(selectany) GUID FspFsvrtDeviceClassGuid = #define FSP_FSCTL_VOLUME_PREFIX_SIZE (64 * sizeof(WCHAR)) #define FSP_FSCTL_VOLUME_NAME_SIZEMAX (FSP_FSCTL_VOLUME_NAME_SIZE + FSP_FSCTL_VOLUME_PREFIX_SIZE) +#define FSP_FSCTL_TRANSACT_PATH_SIZEMAX 2048 + #define FSP_FSCTL_TRANSACT_REQ_SIZEMAX (4096 - 64) /* 64: size for internal request header */ #define FSP_FSCTL_TRANSACT_RSP_SIZEMAX (4096 - 64) /* symmetry! */ #define FSP_FSCTL_TRANSACT_BATCH_BUFFER_SIZEMIN 16384 @@ -295,6 +297,7 @@ typedef struct UINT64 UserContext2; UINT32 FsControlCode; FSP_FSCTL_TRANSACT_BUF Buffer; + UINT32 TargetOnFileSystem:1;/* the target of the symbolic link is on this file system */ } FileSystemControl; struct { diff --git a/src/dll/fsop.c b/src/dll/fsop.c index 820421bf..00a1ccd4 100644 --- a/src/dll/fsop.c +++ b/src/dll/fsop.c @@ -910,7 +910,6 @@ FSP_API NTSTATUS FspFileSystemOpFileSystemControl(FSP_FILE_SYSTEM *FileSystem, { Size = FSP_FSCTL_TRANSACT_RSP_SIZEMAX - FIELD_OFFSET(FSP_FSCTL_TRANSACT_RSP, Buffer) - sizeof(*SymlinkReparseData); - Size /= 2; /* need space for SubstituteName and PrintName */ Result = FileSystem->Interface->GetReparsePoint(FileSystem, Request, (PVOID)Request->Req.FileSystemControl.UserContext, (PWSTR)Request->Buffer, @@ -921,8 +920,8 @@ FSP_API NTSTATUS FspFileSystemOpFileSystemControl(FSP_FILE_SYSTEM *FileSystem, Offset = 0; if (Size > 4 * sizeof(WCHAR) && '\\' == SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer[0] && - '?' == SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer[1] && - '?' == SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer[2] && + '?' == SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer[1] && + '?' == SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer[2] && '\\' == SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer[3]) Offset = 4 * sizeof(WCHAR); @@ -930,23 +929,17 @@ FSP_API NTSTATUS FspFileSystemOpFileSystemControl(FSP_FILE_SYSTEM *FileSystem, SymlinkReparseData->ReparseDataLength = (USHORT)( FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer) + - Size + Size - Offset); + Size); SymlinkReparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; SymlinkReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength = (USHORT)Size; SymlinkReparseData->SymbolicLinkReparseBuffer.PrintNameOffset = - (USHORT)Size; + (USHORT)Offset; SymlinkReparseData->SymbolicLinkReparseBuffer.PrintNameLength = (USHORT)(Size - Offset); - SymlinkReparseData->SymbolicLinkReparseBuffer.Flags = - Size > 1 * sizeof(WCHAR) && - '\\' == SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer[0] ? - 0 : SYMLINK_FLAG_RELATIVE; - RtlMoveMemory( - SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer + Size / sizeof(WCHAR), - SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer + Offset / sizeof(WCHAR), - (DWORD)(Size - Offset)); + SymlinkReparseData->SymbolicLinkReparseBuffer.Flags = 0 == Offset ? + 0 : SYMLINK_FLAG_RELATIVE; Response->Rsp.FileSystemControl.Buffer.Size = (UINT16)Size; } @@ -974,7 +967,7 @@ FSP_API NTSTATUS FspFileSystemOpFileSystemControl(FSP_FILE_SYSTEM *FileSystem, (PVOID)Request->Req.FileSystemControl.UserContext, (PWSTR)Request->Buffer, SymlinkReparseData->SymbolicLinkReparseBuffer.PathBuffer + - SymlinkReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR), + SymlinkReparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), SymlinkReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength); } else diff --git a/src/dll/fuse/fuse.c b/src/dll/fuse/fuse.c index 715bf59a..6d1c5bac 100644 --- a/src/dll/fuse/fuse.c +++ b/src/dll/fuse/fuse.c @@ -37,10 +37,12 @@ struct fsp_fuse_core_opt_data set_umask, umask, set_uid, uid, set_gid, gid, - set_attr_timeout, attr_timeout; + set_attr_timeout, attr_timeout, + rellinks; int set_FileInfoTimeout; - int CaseInsensitiveSearch, ReparsePoints, - NamedStreams, ReadOnlyVolume; + int CaseInsensitiveSearch, + NamedStreams, + ReadOnlyVolume; FSP_FSCTL_VOLUME_PARAMS VolumeParams; }; @@ -78,6 +80,9 @@ static struct fuse_opt fsp_fuse_core_opts[] = FUSE_OPT_KEY("intr_signal=", FUSE_OPT_KEY_DISCARD), FUSE_OPT_KEY("modules=", FUSE_OPT_KEY_DISCARD), + FSP_FUSE_CORE_OPT("rellinks", rellinks, 1), + FSP_FUSE_CORE_OPT("norellinks", rellinks, 0), + 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), @@ -89,11 +94,11 @@ static struct fuse_opt fsp_fuse_core_opts[] = 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("ReparsePoints", ReparsePoints, 1), FSP_FUSE_CORE_OPT("NamedStreams", NamedStreams, 1), + FSP_FUSE_CORE_OPT("ReadOnlyVolume", ReadOnlyVolume, 1), + FUSE_OPT_KEY("ReparsePoints", FUSE_OPT_KEY_DISCARD), 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'), @@ -449,7 +454,6 @@ static int fsp_fuse_core_opt_proc(void *opt_data0, const char *arg, int key, " -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 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"); @@ -498,7 +502,9 @@ FSP_FUSE_API struct fuse *fsp_fuse_new(struct fsp_fuse_env *env, opt_data.VolumeParams.FileInfoTimeout = opt_data.set_attr_timeout * 1000; opt_data.VolumeParams.CaseSensitiveSearch = !opt_data.CaseInsensitiveSearch; opt_data.VolumeParams.PersistentAcls = TRUE; - opt_data.VolumeParams.ReparsePoints = !!opt_data.ReparsePoints; + opt_data.VolumeParams.ReparsePoints = FALSE; /* see FSP_FUSE_HAS_SYMLINKS use below */ + opt_data.VolumeParams.ReparsePointsSymbolicLinks = TRUE; + opt_data.VolumeParams.ReparsePointsPrivilegeCheck = FALSE; opt_data.VolumeParams.NamedStreams = !!opt_data.NamedStreams; opt_data.VolumeParams.ReadOnlyVolume = !!opt_data.ReadOnlyVolume; @@ -510,6 +516,7 @@ FSP_FUSE_API struct fuse *fsp_fuse_new(struct fsp_fuse_env *env, f->set_umask = opt_data.set_umask; f->umask = opt_data.umask; f->set_uid = opt_data.set_uid; f->uid = opt_data.uid; f->set_gid = opt_data.set_gid; f->gid = opt_data.gid; + f->rellinks = opt_data.rellinks; memcpy(&f->ops, ops, opsize); f->data = data; f->DebugLog = opt_data.debug ? -1 : 0; @@ -550,6 +557,8 @@ FSP_FUSE_API struct fuse *fsp_fuse_new(struct fsp_fuse_env *env, goto fail; } + f->VolumeParams.ReparsePoints = FSP_FUSE_HAS_SYMLINKS(f); + return f; fail: diff --git a/src/dll/fuse/fuse_intf.c b/src/dll/fuse/fuse_intf.c index 9baa1fd4..ad1b2774 100644 --- a/src/dll/fuse/fuse_intf.c +++ b/src/dll/fuse/fuse_intf.c @@ -17,6 +17,14 @@ #include +/* + * FSP_FUSE_REPARSE_OPTHACK + * + * Define this macro to enable the ResolveReparsePoints optimization hack. + * See fsp_fuse_intf_GetSecurityByName for details. + */ +#define FSP_FUSE_REPARSE_OPTHACK + NTSTATUS fsp_fuse_op_enter(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) { @@ -218,8 +226,25 @@ static NTSTATUS fsp_fuse_intf_GetFileInfoEx(FSP_FILE_SYSTEM *FileSystem, AllocationUnit = (UINT64)f->VolumeParams.SectorSize * (UINT64)f->VolumeParams.SectorsPerAllocationUnit; - FileInfo->FileAttributes = (stbuf.st_mode & 0040000) ? FILE_ATTRIBUTE_DIRECTORY : 0; - FileInfo->ReparseTag = 0; + switch (stbuf.st_mode & 0170000) + { + case 0040000: + FileInfo->FileAttributes = FILE_ATTRIBUTE_DIRECTORY; + FileInfo->ReparseTag = 0; + break; + case 0120000: + if (FSP_FUSE_HAS_SYMLINKS(f)) + { + FileInfo->FileAttributes = FILE_ATTRIBUTE_REPARSE_POINT; + FileInfo->ReparseTag = IO_REPARSE_TAG_SYMLINK; + break; + } + /* fall through */ + default: + FileInfo->FileAttributes = 0; + FileInfo->ReparseTag = 0; + break; + } FileInfo->FileSize = stbuf.st_size; FileInfo->AllocationSize = (FileInfo->FileSize + AllocationUnit - 1) / AllocationUnit * AllocationUnit; @@ -289,6 +314,44 @@ exit: return Result; } +static char *fsp_fuse_intf_GetSymlinkPointer(FSP_FILE_SYSTEM *FileSystem, + char *PosixPath) +{ + struct fuse *f = FileSystem->UserContext; + char *p, *lastp; + struct fuse_stat stbuf; + int err; + + if (0 == f->ops.getattr) + return 0; + + p = PosixPath; + for (;;) + { + while ('/' == *p) + p++; + lastp = p; + while ('/' != *p) + { + if ('\0' == *p) + return 0; + p++; + } + + *p = '\0'; + memset(&stbuf, 0, sizeof stbuf); + err = f->ops.getattr(PosixPath, (void *)&stbuf); + *p = '/'; + + if (0 != err) + return 0; + else if (0120000 == (stbuf.st_mode & 0170000)) + return lastp; + } + + return 0; +} + static NTSTATUS fsp_fuse_intf_GetVolumeInfo(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_VOLUME_INFO *VolumeInfo) @@ -330,7 +393,7 @@ static NTSTATUS fsp_fuse_intf_GetSecurityByName(FSP_FILE_SYSTEM *FileSystem, PSECURITY_DESCRIPTOR SecurityDescriptorBuf, SIZE_T *PSecurityDescriptorSize) { struct fuse *f = FileSystem->UserContext; - char *PosixPath = 0; + char *PosixPath = 0, *PosixSymlinkPointer; NTSTATUS Result; Result = FspPosixMapWindowsToPosixPath(FileName, &PosixPath); @@ -342,7 +405,31 @@ static NTSTATUS fsp_fuse_intf_GetSecurityByName(FSP_FILE_SYSTEM *FileSystem, if (!NT_SUCCESS(Result)) goto exit; - Result = STATUS_SUCCESS; + if (FSP_FUSE_HAS_SYMLINKS(f) && + 0 != (PosixSymlinkPointer = fsp_fuse_intf_GetSymlinkPointer(FileSystem, PosixPath))) + { +#if defined(FSP_FUSE_REPARSE_OPTHACK) + /* OPTHACK: This is a rather gross hack for optimization purposes only. + * + * If the FileName in this GetSecurityByName call and the PosixPath we computed + * matches the one in the FSD request, then remember where the first symlink is + * for use in ResolveReparsePoints later on. This avoids going through every path + * component twice (doing both a getattr and readlink). + */ + + struct fuse_context *context = fsp_fuse_get_context(f->env); + struct fsp_fuse_context_header *contexthdr = FSP_FUSE_HDR_FROM_CONTEXT(context); + + if (FspFsctlTransactCreateKind == contexthdr->Request->Kind && + (PUINT8)FileName == contexthdr->Request->Buffer && + 0 == lstrcmpA(PosixPath, contexthdr->PosixPath)) + contexthdr->SymlinkIndex = PosixSymlinkPointer - PosixPath; +#endif + + Result = STATUS_REPARSE; + } + else + Result = STATUS_SUCCESS; exit: if (0 != PosixPath) @@ -1422,6 +1509,337 @@ exit: return Result; } +static NTSTATUS fsp_fuse_intf_ResolveReparsePoints(FSP_FILE_SYSTEM *FileSystem, + PWSTR FileName, BOOLEAN OpenReparsePoint, + PIO_STATUS_BLOCK PIoStatus, PVOID Buffer, PSIZE_T PSize) +{ + struct fuse *f = FileSystem->UserContext; + struct fuse_context *context = fsp_fuse_get_context(f->env); + struct fsp_fuse_context_header *contexthdr = FSP_FUSE_HDR_FROM_CONTEXT(context); + char c, *p, *lastp; + char PosixTargetPath[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; + char PosixTargetLink[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; + PWSTR TargetPath = 0; + ULONG Length, MaxTries = 32; + int err; + NTSTATUS Result; + + if (0 == f->ops.readlink) + return STATUS_INVALID_DEVICE_REQUEST; + + Length = lstrlenA(contexthdr->PosixPath); + if (Length > sizeof PosixTargetPath - 1) + return STATUS_OBJECT_NAME_INVALID; + memcpy(PosixTargetPath, contexthdr->PosixPath, Length + 1); + + p = PosixTargetPath + + contexthdr->SymlinkIndex; /* NOTE: if FSP_FUSE_REPARSE_OPTHACK is undefined, this will be 0 */ + c = *p; + for (;;) + { + while ('/' == *p) + p++; + lastp = p; + while ('/' != *p) + { + if ('\0' == *p) + { + if (!OpenReparsePoint) + goto end; + OpenReparsePoint = FALSE; + break; + } + p++; + } + + /* handle dot and dotdot! */ + if ('.' == lastp[0]) + { + if (p == lastp + 1) + /* dot */ + continue; + + if ('.' == lastp[1] && p == lastp + 1) + { + /* dotdot */ + p = lastp; + while (PosixTargetPath < p) + { + p--; + if ('/' != *p) + break; + } + while (PosixTargetPath < p) + { + p--; + if ('/' == *p) + break; + } + continue; + } + } + + c = *p; + *p = '\0'; + err = f->ops.readlink(contexthdr->PosixPath, PosixTargetLink, sizeof PosixTargetLink); + *p = c; + + if (EINVAL/* same on MSVC and Cygwin */ == err) + /* it was not a symlink; continue */ + continue; + else if (0 != err) + return fsp_fuse_ntstatus_from_errno(f->env, err); + + /* found a symlink */ + if (0 == --MaxTries) + return STATUS_REPARSE_POINT_NOT_RESOLVED; + Length = lstrlenA(PosixTargetLink); + if ('/' == PosixTargetLink[0]) + { + /* we do not support absolute paths without the rellinks option */ + if (!f->rellinks) + { + Result = STATUS_OBJECT_NAME_NOT_FOUND; + goto exit; + } + + /* absolute symlink; replace whole path */ + memcpy(PosixTargetPath, PosixTargetLink, Length + 1); + p = PosixTargetPath; + } + else + { + /* relative symlink; replace last path component seen */ + if (Length + 1 > (p + sizeof PosixTargetPath) - lastp) + return STATUS_OBJECT_NAME_INVALID; + memcpy(lastp, PosixTargetLink, Length + 1); + p = lastp; + } + } + +end: + Result = FspPosixMapPosixToWindowsPath(PosixTargetPath, &TargetPath); + if (!NT_SUCCESS(Result)) + return Result; + + Length = lstrlenW(TargetPath); + if (Length > *PSize) + { + Result = STATUS_OBJECT_NAME_INVALID; + goto exit; + } + *PSize = Length; + memcpy(Buffer, TargetPath, Length * sizeof(WCHAR)); + + PIoStatus->Status = STATUS_REPARSE; + PIoStatus->Information = 0/*IO_REPARSE*/; + Result = STATUS_REPARSE; + +exit: + if (0 != TargetPath) + FspPosixDeletePath(TargetPath); + + return Result; +} + +static NTSTATUS fsp_fuse_intf_GetReparsePoint(FSP_FILE_SYSTEM *FileSystem, + FSP_FSCTL_TRANSACT_REQ *Request, + PVOID FileNode, + PWSTR FileName, PVOID Buffer, PSIZE_T PSize) +{ + struct fuse *f = FileSystem->UserContext; + struct fsp_fuse_file_desc *filedesc = + (PVOID)(UINT_PTR)Request->Req.FileSystemControl.UserContext2; + char PosixTargetPath[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; + PWSTR TargetPath = 0; + ULONG Length; + int err; + NTSTATUS Result; + + if (0 == f->ops.readlink) + return STATUS_INVALID_DEVICE_REQUEST; + + err = f->ops.readlink(filedesc->PosixPath, PosixTargetPath, sizeof PosixTargetPath); + if (0 != err) + { + Result = fsp_fuse_ntstatus_from_errno(f->env, err); + goto exit; + } + + /* is this an absolute path? */ + if ('/' == PosixTargetPath[0]) + { + /* we do not support absolute paths without the rellinks option */ + if (!f->rellinks) + { + Result = STATUS_ACCESS_DENIED; + goto exit; + } + + /* + * Transform absolute path to relative. + */ + + char *p, *t, *pstem, *tstem; + unsigned index, count; + + p = pstem = filedesc->PosixPath; + t = tstem = PosixTargetPath; + + /* skip common prefix */ + for (;;) + { + while ('/' == *p) + p++; + while ('/' == *t) + t++; + pstem = p, tstem = t; + while ('/' != *p) + { + if (*p != *t) + goto common_prefix_end; + else if ('\0' == *p) + { + while ('/' == *t) + t++; + pstem = p, tstem = t; + goto common_prefix_end; + } + p++, t++; + } + } + common_prefix_end: + p = pstem; + t = tstem; + + /* count path components */ + for (count = 0; '\0' != *p; count++) + { + while ('/' == *p) + p++; + while ('/' != *p && '\0' != *p) + p++; + } + + /* make relative path */ + if (0 == count) + { + /* special case symlink loop: a -> a/stem */ + while (PosixTargetPath < tstem) + { + tstem--; + if ('/' != *tstem) + break; + } + while (PosixTargetPath < tstem) + { + tstem--; + if ('/' == *tstem) + { + tstem++; + break; + } + } + } + Length = lstrlenA(tstem); + Length += !!Length; /* add tstem term-0 */ + if (3 * count + Length > sizeof PosixTargetPath) + { + Result = STATUS_IO_REPARSE_DATA_INVALID; + goto exit; + } + memmove(PosixTargetPath + 3 * count, tstem, Length); + for (index = 0; count > index; index++) + { + PosixTargetPath[index * 3 + 0] = '.'; + PosixTargetPath[index * 3 + 1] = '.'; + PosixTargetPath[index * 3 + 2] = '/'; + } + if (0 == Length) + PosixTargetPath[(count - 1) * 3 + 2] = '\0'; + } + + Result = FspPosixMapPosixToWindowsPath(PosixTargetPath, &TargetPath); + if (!NT_SUCCESS(Result)) + goto exit; + + Length = lstrlenW(TargetPath); + if (Length > *PSize) + { + Result = STATUS_BUFFER_TOO_SMALL; + goto exit; + } + *PSize = Length; + memcpy(Buffer, TargetPath, Length * sizeof(WCHAR)); + + Result = STATUS_SUCCESS; + +exit: + if (0 != TargetPath) + FspPosixDeletePath(TargetPath); + + return Result; +} + +static NTSTATUS fsp_fuse_intf_SetReparsePoint(FSP_FILE_SYSTEM *FileSystem, + FSP_FSCTL_TRANSACT_REQ *Request, + PVOID FileNode, + PWSTR FileName, PVOID Buffer, SIZE_T Size) +{ + struct fuse *f = FileSystem->UserContext; + struct fsp_fuse_file_desc *filedesc = + (PVOID)(UINT_PTR)Request->Req.FileSystemControl.UserContext2; + WCHAR TargetPath[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; + char *PosixTargetPath = 0; + int err; + NTSTATUS Result; + + if (0 == f->ops.symlink) + return STATUS_INVALID_DEVICE_REQUEST; + + /* is this an absolute path? */ + if (Size > 4 * sizeof(WCHAR) && + '\\' == ((PWSTR)Buffer)[0] && + '?' == ((PWSTR)Buffer)[1] && + '?' == ((PWSTR)Buffer)[2] && + '\\' == ((PWSTR)Buffer)[3]) + { + /* we do not support absolute paths that point outside this file system */ + if (!Request->Req.FileSystemControl.TargetOnFileSystem) + { + Result = STATUS_ACCESS_DENIED; + goto exit; + } + } + + if (Size > sizeof TargetPath - sizeof(WCHAR)) + { + Result = STATUS_IO_REPARSE_DATA_INVALID; + goto exit; + } + memcpy(TargetPath, Buffer, Size); + TargetPath[Size / sizeof(WCHAR)] = L'\0'; + + Result = FspPosixMapWindowsToPosixPath(TargetPath, &PosixTargetPath); + if (!NT_SUCCESS(Result)) + goto exit; + + err = f->ops.symlink(PosixTargetPath, filedesc->PosixPath); + if (0 != err) + { + Result = fsp_fuse_ntstatus_from_errno(f->env, err); + goto exit; + } + + Result = STATUS_SUCCESS; + +exit: + if (0 != PosixTargetPath) + FspPosixDeletePath(PosixTargetPath); + + return Result; +} + FSP_FILE_SYSTEM_INTERFACE fsp_fuse_intf = { fsp_fuse_intf_GetVolumeInfo, @@ -1443,4 +1861,7 @@ FSP_FILE_SYSTEM_INTERFACE fsp_fuse_intf = fsp_fuse_intf_GetSecurity, fsp_fuse_intf_SetSecurity, fsp_fuse_intf_ReadDirectory, + fsp_fuse_intf_ResolveReparsePoints, + fsp_fuse_intf_GetReparsePoint, + fsp_fuse_intf_SetReparsePoint, }; diff --git a/src/dll/fuse/library.h b/src/dll/fuse/library.h index a2fb9924..fef7b635 100644 --- a/src/dll/fuse/library.h +++ b/src/dll/fuse/library.h @@ -29,12 +29,15 @@ #define FSP_FUSE_CONTEXT_FROM_HDR(h) \ (struct fuse_context *)((PUINT8)(h) + sizeof(struct fsp_fuse_context_header)) +#define FSP_FUSE_HAS_SYMLINKS(f) (0 != (f)->ops.readlink) + struct fuse { struct fsp_fuse_env *env; int set_umask, umask; int set_uid, uid; int set_gid, gid; + int rellinks; struct fuse_operations ops; void *data; UINT32 DebugLog; @@ -51,6 +54,7 @@ struct fsp_fuse_context_header FSP_FSCTL_TRANSACT_REQ *Request; FSP_FSCTL_TRANSACT_RSP *Response; char *PosixPath; + ptrdiff_t SymlinkIndex; __declspec(align(MEMORY_ALLOCATION_ALIGNMENT)) UINT8 ContextBuf[]; }; diff --git a/src/shared/minimal.h b/src/shared/minimal.h index b5db2559..3499d4d7 100644 --- a/src/shared/minimal.h +++ b/src/shared/minimal.h @@ -50,8 +50,14 @@ NTSYSAPI VOID NTAPI RtlFillMemory(VOID *Destination, DWORD Length, BYTE Fill); NTSYSAPI VOID NTAPI RtlMoveMemory(VOID *Destination, CONST VOID *Source, DWORD Length); -#pragma function(memcpy) #pragma function(memset) +#pragma function(memcpy) +static inline +void *memset(void *dst, int val, size_t siz) +{ + RtlFillMemory(dst, (DWORD)siz, val); + return dst; +} static inline void *memcpy(void *dst, const void *src, size_t siz) { @@ -59,9 +65,9 @@ void *memcpy(void *dst, const void *src, size_t siz) return dst; } static inline -void *memset(void *dst, int val, size_t siz) +void *memmove(void *dst, const void *src, size_t siz) { - RtlFillMemory(dst, (DWORD)siz, val); + RtlMoveMemory(dst, src, (DWORD)siz); return dst; } diff --git a/src/sys/fsctl.c b/src/sys/fsctl.c index 0152c2fe..d8ec6f33 100644 --- a/src/sys/fsctl.c +++ b/src/sys/fsctl.c @@ -103,7 +103,9 @@ static NTSTATUS FspFsvolFileSystemControlReparsePoint( PVOID OutputBuffer = Irp->UserBuffer; ULONG InputBufferLength = IrpSp->Parameters.FileSystemControl.InputBufferLength; ULONG OutputBufferLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength; - ULONG ReparseTag; + PREPARSE_DATA_BUFFER ReparseData; + PWSTR PathBuffer; + BOOLEAN TargetOnFileSystem = FALSE; FSP_FSCTL_TRANSACT_REQ *Request; ASSERT(FileNode == FileDesc->FileNode); @@ -123,16 +125,53 @@ static NTSTATUS FspFsvolFileSystemControlReparsePoint( if (!NT_SUCCESS(Result)) return Result; - ReparseTag = ((PREPARSE_DATA_BUFFER)InputBuffer)->ReparseTag; + ReparseData = (PREPARSE_DATA_BUFFER)InputBuffer; - /* NTFS severely limits symbolic links; we will not do that unless our file system asks */ - if (FsvolDeviceExtension->VolumeParams.ReparsePointsPrivilegeCheck) + if (IO_REPARSE_TAG_SYMLINK == ReparseData->ReparseTag) { - if (IO_REPARSE_TAG_SYMLINK == ReparseTag && - KernelMode != Irp->RequestorMode && - SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_CREATE_SYMBOLIC_LINK_PRIVILEGE), - UserMode)) - return STATUS_ACCESS_DENIED; + /* NTFS severely limits symbolic links; we will not do that unless our file system asks */ + if (FsvolDeviceExtension->VolumeParams.ReparsePointsPrivilegeCheck) + { + if (KernelMode != Irp->RequestorMode && + SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_CREATE_SYMBOLIC_LINK_PRIVILEGE), + UserMode)) + return STATUS_ACCESS_DENIED; + } + + /* determine if target resides on same device as link (convenience for user mode) */ + PathBuffer = ReparseData->SymbolicLinkReparseBuffer.PathBuffer + + ReparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR); + if (ReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength > 4 * sizeof(WCHAR) && + '\\' == PathBuffer[0] && + '?' == PathBuffer[1] && + '?' == PathBuffer[2] && + '\\' == PathBuffer[3]) + { + UNICODE_STRING TargetDeviceName; + PFILE_OBJECT TargetDeviceFile; + PDEVICE_OBJECT TargetDeviceObject; + + RtlInitEmptyUnicodeString(&TargetDeviceName, + PathBuffer, + ReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength); + + /* the first path component is assumed to be the device name */ + TargetDeviceName.Length = 4 * sizeof(WCHAR); + while (TargetDeviceName.Length < TargetDeviceName.MaximumLength && + '\\' != TargetDeviceName.Buffer[TargetDeviceName.Length / sizeof(WCHAR)]) + TargetDeviceName.Length += sizeof(WCHAR); + + Result = IoGetDeviceObjectPointer(&TargetDeviceName, + FILE_READ_ATTRIBUTES, &TargetDeviceFile, &TargetDeviceObject); + if (!NT_SUCCESS(Result)) + goto target_check_exit; + + TargetOnFileSystem = IoGetRelatedDeviceObject(FileObject) == TargetDeviceObject; + ObDereferenceObject(TargetDeviceFile); + + target_check_exit: + ; + } } } else @@ -169,6 +208,8 @@ static NTSTATUS FspFsvolFileSystemControlReparsePoint( Request->Req.FileSystemControl.Buffer.Size = (UINT16)InputBufferLength; RtlCopyMemory(Request->Buffer + Request->Req.FileSystemControl.Buffer.Offset, InputBuffer, InputBufferLength); + + Request->Req.FileSystemControl.TargetOnFileSystem = TargetOnFileSystem; } FspFileNodeSetOwner(FileNode, Full, Request);