/** * @file dll/fuse/fuse_intf.c * * @copyright 2015-2022 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 static NTSTATUS fsp_fuse_intf_GetReparsePointByName( FSP_FILE_SYSTEM *FileSystem, PVOID Context, PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize); static NTSTATUS fsp_fuse_intf_SetEaEntry( FSP_FILE_SYSTEM *FileSystem, PVOID Context, PFILE_FULL_EA_INFORMATION SingleEa); static inline VOID fsp_fuse_op_enter_lock(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) { switch (FileSystem->OpGuardStrategy) { case FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_FINE: if ((FspFsctlTransactCreateKind == Request->Kind && FILE_OPEN != ((Request->Req.Create.CreateOptions >> 24) & 0xff)) || FspFsctlTransactOverwriteKind == Request->Kind || (FspFsctlTransactCleanupKind == Request->Kind && Request->Req.Cleanup.Delete) || (FspFsctlTransactSetInformationKind == Request->Kind && (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass || 65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) || FspFsctlTransactSetVolumeInformationKind == Request->Kind || (FspFsctlTransactFlushBuffersKind == Request->Kind && 0 == Request->Req.FlushBuffers.UserContext && 0 == Request->Req.FlushBuffers.UserContext2) || /* FSCTL_SET_REPARSE_POINT manipulates namespace */ (FspFsctlTransactFileSystemControlKind == Request->Kind && FSCTL_SET_REPARSE_POINT == Request->Req.FileSystemControl.FsControlCode)) { AcquireSRWLockExclusive(&FileSystem->OpGuardLock); } else if (FspFsctlTransactCreateKind == Request->Kind || (FspFsctlTransactSetInformationKind == Request->Kind && (13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass || 64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) || FspFsctlTransactQueryDirectoryKind == Request->Kind || FspFsctlTransactQueryVolumeInformationKind == Request->Kind || /* FSCTL_GET_REPARSE_POINT may access namespace */ (FspFsctlTransactFileSystemControlKind == Request->Kind && FSCTL_GET_REPARSE_POINT == Request->Req.FileSystemControl.FsControlCode)) { AcquireSRWLockShared(&FileSystem->OpGuardLock); } break; case FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_COARSE: AcquireSRWLockExclusive(&FileSystem->OpGuardLock); break; } } static inline VOID fsp_fuse_op_leave_unlock(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) { switch (FileSystem->OpGuardStrategy) { case FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_FINE: if ((FspFsctlTransactCreateKind == Request->Kind && FILE_OPEN != ((Request->Req.Create.CreateOptions >> 24) & 0xff)) || FspFsctlTransactOverwriteKind == Request->Kind || (FspFsctlTransactCleanupKind == Request->Kind && Request->Req.Cleanup.Delete) || (FspFsctlTransactSetInformationKind == Request->Kind && (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass || 65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) || FspFsctlTransactSetVolumeInformationKind == Request->Kind || (FspFsctlTransactFlushBuffersKind == Request->Kind && 0 == Request->Req.FlushBuffers.UserContext && 0 == Request->Req.FlushBuffers.UserContext2) || /* FSCTL_SET_REPARSE_POINT manipulates namespace */ (FspFsctlTransactFileSystemControlKind == Request->Kind && FSCTL_SET_REPARSE_POINT == Request->Req.FileSystemControl.FsControlCode)) { ReleaseSRWLockExclusive(&FileSystem->OpGuardLock); } else if (FspFsctlTransactCreateKind == Request->Kind || (FspFsctlTransactSetInformationKind == Request->Kind && (13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass || 64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) || FspFsctlTransactQueryDirectoryKind == Request->Kind || FspFsctlTransactQueryVolumeInformationKind == Request->Kind || /* FSCTL_GET_REPARSE_POINT may access namespace */ (FspFsctlTransactFileSystemControlKind == Request->Kind && FSCTL_GET_REPARSE_POINT == Request->Req.FileSystemControl.FsControlCode)) { ReleaseSRWLockShared(&FileSystem->OpGuardLock); } break; case FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_COARSE: ReleaseSRWLockExclusive(&FileSystem->OpGuardLock); break; } } NTSTATUS fsp_fuse_op_enter(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) { struct fuse *f = FileSystem->UserContext; struct fuse_context *context; struct fsp_fuse_context_header *contexthdr; char *PosixPath = 0; UINT32 Uid = -1, Gid = -1, Pid = -1; PWSTR FileName = 0, Suffix; WCHAR Root[2] = L"\\"; UINT64 AccessToken = 0; NTSTATUS Result; if (FspFsctlTransactCreateKind == Request->Kind) { if (Request->Req.Create.OpenTargetDirectory) FspPathSuffix((PWSTR)Request->Buffer, &FileName, &Suffix, Root); else FileName = (PWSTR)Request->Buffer; AccessToken = Request->Req.Create.AccessToken; } else if (FspFsctlTransactSetInformationKind == Request->Kind && (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass || 65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) { FileName = (PWSTR)(Request->Buffer + Request->Req.SetInformation.Info.Rename.NewFileName.Offset); AccessToken = Request->Req.SetInformation.Info.Rename.AccessToken; } if (0 != FileName) { Result = FspPosixMapWindowsToPosixPath(FileName, &PosixPath); if (FspFsctlTransactCreateKind == Request->Kind && Request->Req.Create.OpenTargetDirectory) FspPathCombine((PWSTR)Request->Buffer, Suffix); if (!NT_SUCCESS(Result)) goto exit; } if (0 != AccessToken) { Result = fsp_fuse_get_token_uidgid( FSP_FSCTL_TRANSACT_REQ_TOKEN_HANDLE(AccessToken), TokenUser, &Uid, &Gid); if (!NT_SUCCESS(Result)) goto exit; Pid = FSP_FSCTL_TRANSACT_REQ_TOKEN_PID(AccessToken); } context = fsp_fuse_get_context(f->env); if (0 == context) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } fsp_fuse_op_enter_lock(FileSystem, Request, Response); context->fuse = f; context->private_data = f->data; context->uid = Uid; context->gid = Gid; context->pid = 0 != f->env->winpid_to_pid ? f->env->winpid_to_pid(Pid) : Pid; contexthdr = FSP_FUSE_HDR_FROM_CONTEXT(context); contexthdr->PosixPath = PosixPath; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result) && 0 != PosixPath) FspPosixDeletePath(PosixPath); return Result; } NTSTATUS fsp_fuse_op_leave(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) { struct fuse *f = FileSystem->UserContext; struct fuse_context *context; struct fsp_fuse_context_header *contexthdr; fsp_fuse_op_leave_unlock(FileSystem, Request, Response); context = fsp_fuse_get_context(f->env); context->fuse = 0; context->private_data = 0; context->uid = -1; context->gid = -1; context->pid = -1; contexthdr = FSP_FUSE_HDR_FROM_CONTEXT(context); if (0 != contexthdr->PosixPath) FspPosixDeletePath(contexthdr->PosixPath); memset(contexthdr, 0, sizeof *contexthdr); return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_NewHiddenName(FSP_FILE_SYSTEM *FileSystem, char *PosixPath, char **PPosixHiddenPath) { struct fuse *f = FileSystem->UserContext; NTSTATUS Result; char *PosixHiddenPath = 0; char *p, *lastp; size_t Size; RPC_STATUS RpcStatus; union { struct { UINT32 V[4]; } Values; UUID Uuid; } UuidBuf; struct fuse_stat_ex stbuf; int err, maxtries = 3; *PPosixHiddenPath = 0; p = PosixPath; for (;;) { while ('/' == *p) p++; lastp = p; while ('/' != *p) { if ('\0' == *p) goto loopend; p++; } } loopend:; Size = lastp - PosixPath + sizeof ".fuse_hidden0123456789abcdef"; PosixHiddenPath = MemAlloc(Size); if (0 == PosixHiddenPath) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } memcpy(PosixHiddenPath, PosixPath, lastp - PosixPath); do { RpcStatus = UuidCreate(&UuidBuf.Uuid); if (S_OK != RpcStatus && RPC_S_UUID_LOCAL_ONLY != RpcStatus) { Result = STATUS_INTERNAL_ERROR; goto exit; } wsprintfA(PosixHiddenPath + (lastp - PosixPath), ".fuse_hidden%08lx%08lx", UuidBuf.Values.V[0] ^ UuidBuf.Values.V[2], UuidBuf.Values.V[1] ^ UuidBuf.Values.V[3]); memset(&stbuf, 0, sizeof stbuf); if (0 != f->ops.getattr) err = f->ops.getattr(PosixHiddenPath, (void *)&stbuf); else err = -ENOSYS_(f->env); } while (0 == err && 0 < --maxtries); if (0 == err) { Result = STATUS_DEVICE_BUSY; // current EBUSY mapping goto exit; } *PPosixHiddenPath = PosixHiddenPath; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) MemFree(PosixHiddenPath); return Result; } static BOOLEAN fsp_fuse_intf_CheckSymlinkDirectory(FSP_FILE_SYSTEM *FileSystem, const char *PosixPath) { struct fuse *f = FileSystem->UserContext; if (FSP_FUSE_HAS_SLASHDOT(f)) { char *PosixDotPath = 0; size_t Length; struct fuse_stat_ex stbuf; int err; BOOLEAN Result = FALSE; Length = lstrlenA(PosixPath); PosixDotPath = MemAlloc(Length + 3); if (0 != PosixDotPath) { memcpy(PosixDotPath, PosixPath, Length); PosixDotPath[Length + 0] = '/'; PosixDotPath[Length + 1] = '.'; PosixDotPath[Length + 2] = '\0'; memset(&stbuf, 0, sizeof stbuf); if (0 != f->ops.getattr) err = f->ops.getattr(PosixDotPath, (void *)&stbuf); else err = -ENOSYS_(f->env); MemFree(PosixDotPath); Result = 0 == err && 0040000 == (stbuf.st_mode & 0170000); } return Result; } else { PWSTR WindowsPath = 0, P; char *PosixResolvedPath = 0; UINT32 ReparsePointIndex; UINT32 ResolveFileAttributes[2] = { FILE_ATTRIBUTE_REPARSE_POINT, -1 }; IO_STATUS_BLOCK IoStatus; union { REPARSE_DATA_BUFFER V; UINT8 B[FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + FSP_FSCTL_TRANSACT_PATH_SIZEMAX + sizeof(WCHAR)/* add space for term-0 */]; } ReparseDataBuf; SIZE_T ReparseDataSize; struct fuse_stat_ex stbuf; int err; NTSTATUS Result; Result = FspPosixMapPosixToWindowsPath(PosixPath, &WindowsPath); if (!NT_SUCCESS(Result)) goto exit; ReparsePointIndex = 0; for (P = WindowsPath; '\0' != *P; P++) if (L'\\' == *P) ReparsePointIndex = (UINT32)(P + 1 - WindowsPath); ReparseDataSize = sizeof ReparseDataBuf - sizeof(WCHAR)/* leave space for term-0 */; Result = FspFileSystemResolveReparsePoints(FileSystem, fsp_fuse_intf_GetReparsePointByName, ResolveFileAttributes, WindowsPath, ReparsePointIndex, TRUE, &IoStatus, &ReparseDataBuf, &ReparseDataSize); if (!NT_SUCCESS(Result)) goto exit; if (IO_REPARSE_TAG_SYMLINK != ReparseDataBuf.V.ReparseTag) { Result = STATUS_UNSUCCESSFUL; goto exit; } if (-1 != ResolveFileAttributes[1]) { Result = (FILE_ATTRIBUTE_DIRECTORY & ResolveFileAttributes[1]) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; goto exit; } P = (PWSTR)(ReparseDataBuf.V.SymbolicLinkReparseBuffer.PathBuffer + ReparseDataBuf.V.SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)); P[ReparseDataBuf.V.SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)] = L'\0'; Result = FspPosixMapWindowsToPosixPath(P, &PosixResolvedPath); if (!NT_SUCCESS(Result)) goto exit; memset(&stbuf, 0, sizeof stbuf); if (0 != f->ops.getattr) err = f->ops.getattr(PosixResolvedPath, (void *)&stbuf); else err = -ENOSYS_(f->env); Result = 0 == err && 0040000 == (stbuf.st_mode & 0170000) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; exit: if (0 != PosixResolvedPath) FspPosixDeletePath(PosixResolvedPath); if (0 != WindowsPath) FspPosixDeletePath(WindowsPath); return NT_SUCCESS(Result); } } static inline uint32_t fsp_fuse_intf_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 fsp_fuse_intf_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; } #define FUSE_FILE_INFO(IsDirectory, fi) ((IsDirectory) ? 0 : (fi)) #define fsp_fuse_intf_GetFileInfoEx(FileSystem, PosixPath, fi, PUid, PGid, PMode, FileInfo)\ fsp_fuse_intf_GetFileInfoFunnel(FileSystem, PosixPath, fi, 0, PUid, PGid, PMode, 0, TRUE, FileInfo) static NTSTATUS fsp_fuse_intf_GetFileInfoFunnel(FSP_FILE_SYSTEM *FileSystem, const char *PosixPath, struct fuse_file_info *fi, const void *stbufp, PUINT32 PUid, PUINT32 PGid, PUINT32 PMode, PUINT32 PDev, BOOLEAN CheckSymlinkDirectory, FSP_FSCTL_FILE_INFO *FileInfo) { struct fuse *f = FileSystem->UserContext; UINT64 AllocationUnit; struct fuse_stat_ex stbuf; BOOLEAN StatEx = 0 != (f->conn_want & FSP_FUSE_CAP_STAT_EX); memset(&stbuf, 0, sizeof stbuf); if (0 != stbufp) memcpy(&stbuf, stbufp, StatEx ? sizeof(struct fuse_stat_ex) : sizeof(struct fuse_stat)); else { int err; if (0 != f->ops.fgetattr && 0 != fi && -1 != fi->fh) err = f->ops.fgetattr(PosixPath, (void *)&stbuf, fi); else if (0 != f->ops.getattr) err = f->ops.getattr(PosixPath, (void *)&stbuf); else return STATUS_INVALID_DEVICE_REQUEST; if (0 != err) return fsp_fuse_ntstatus_from_errno(f->env, err); } if (f->set_umask) stbuf.st_mode = (stbuf.st_mode & 0170000) | (0777 & ~f->umask); if (f->set_uid) stbuf.st_uid = f->uid; if (f->set_gid) stbuf.st_gid = f->gid; *PUid = stbuf.st_uid; *PGid = stbuf.st_gid; *PMode = stbuf.st_mode; if (0 != PDev) *PDev = stbuf.st_rdev; AllocationUnit = (UINT64)f->VolumeParams.SectorSize * (UINT64)f->VolumeParams.SectorsPerAllocationUnit; switch (stbuf.st_mode & 0170000) { case 0040000: /* S_IFDIR */ FileInfo->FileAttributes = FILE_ATTRIBUTE_DIRECTORY; FileInfo->ReparseTag = 0; break; case 0010000: /* S_IFIFO */ case 0020000: /* S_IFCHR */ case 0060000: /* S_IFBLK */ case 0140000: /* S_IFSOCK */ FileInfo->FileAttributes = FILE_ATTRIBUTE_REPARSE_POINT; FileInfo->ReparseTag = IO_REPARSE_TAG_NFS; break; case 0120000: /* S_IFLNK */ if (FSP_FUSE_HAS_SYMLINKS(f)) { FileInfo->FileAttributes = FILE_ATTRIBUTE_REPARSE_POINT; FileInfo->ReparseTag = IO_REPARSE_TAG_SYMLINK; if (CheckSymlinkDirectory && fsp_fuse_intf_CheckSymlinkDirectory(FileSystem, PosixPath)) FileInfo->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY; break; } /* fall through */ default: FileInfo->FileAttributes = 0; FileInfo->ReparseTag = 0; break; } if (StatEx) FileInfo->FileAttributes |= fsp_fuse_intf_MapFlagsToFileAttributes(stbuf.st_flags); if (f->dothidden) { const char *basename = PosixPath; for (const char *p = PosixPath; '\0' != *p; p++) if ('/' == *p) basename = p + 1; if ('.' == basename[0]) FileInfo->FileAttributes |= FILE_ATTRIBUTE_HIDDEN; } FileInfo->FileSize = stbuf.st_size; FileInfo->AllocationSize = (FileInfo->FileSize + AllocationUnit - 1) / AllocationUnit * AllocationUnit; FspPosixUnixTimeToFileTime((void *)&stbuf.st_birthtim, &FileInfo->CreationTime); FspPosixUnixTimeToFileTime((void *)&stbuf.st_atim, &FileInfo->LastAccessTime); FspPosixUnixTimeToFileTime((void *)&stbuf.st_mtim, &FileInfo->LastWriteTime); FspPosixUnixTimeToFileTime((void *)&stbuf.st_ctim, &FileInfo->ChangeTime); FileInfo->IndexNumber = stbuf.st_ino; FileInfo->HardLinks = 0; FileInfo->EaSize = 0; return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_GetSecurityEx(FSP_FILE_SYSTEM *FileSystem, const char *PosixPath, struct fuse_file_info *fi, PUINT32 PFileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptorBuf, SIZE_T *PSecurityDescriptorSize) { struct fuse *f = FileSystem->UserContext; UINT32 Uid, Gid, Mode; FSP_FSCTL_FILE_INFO FileInfo; PSECURITY_DESCRIPTOR SecurityDescriptor = 0; SIZE_T SecurityDescriptorSize; NTSTATUS Result; Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, PosixPath, fi, &Uid, &Gid, &Mode, &FileInfo); if (!NT_SUCCESS(Result)) goto exit; if (0 != PSecurityDescriptorSize) { Result = FspPosixMergePermissionsToSecurityDescriptor(Uid, Gid, Mode, f->FileSecurity, &SecurityDescriptor); if (!NT_SUCCESS(Result)) goto exit; SecurityDescriptorSize = GetSecurityDescriptorLength(SecurityDescriptor); if (SecurityDescriptorSize > *PSecurityDescriptorSize) { *PSecurityDescriptorSize = SecurityDescriptorSize; Result = STATUS_BUFFER_OVERFLOW; goto exit; } *PSecurityDescriptorSize = SecurityDescriptorSize; if (0 != SecurityDescriptorBuf) memcpy(SecurityDescriptorBuf, SecurityDescriptor, SecurityDescriptorSize); } if (0 != PFileAttributes) *PFileAttributes = FileInfo.FileAttributes; Result = STATUS_SUCCESS; exit: if (0 != SecurityDescriptor) FspDeleteSecurityDescriptor(SecurityDescriptor, FspPosixMergePermissionsToSecurityDescriptor); return Result; } static NTSTATUS fsp_fuse_intf_GetReparsePointSymlink(FSP_FILE_SYSTEM *FileSystem, const char *PosixPath, PVOID Buffer, PSIZE_T PSize) { struct fuse *f = FileSystem->UserContext; char PosixTargetPath[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; PWSTR TargetPath = 0; ULONG TargetPathLength; int err; NTSTATUS Result; err = f->ops.readlink(PosixPath, PosixTargetPath, sizeof PosixTargetPath); if (-EINVAL/* same on MSVC and Cygwin */ == err) { Result = STATUS_NOT_A_REPARSE_POINT; goto exit; } else 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; } } Result = FspPosixMapPosixToWindowsPath(PosixTargetPath, &TargetPath); if (!NT_SUCCESS(Result)) goto exit; TargetPathLength = lstrlenW(TargetPath) * sizeof(WCHAR); if (TargetPathLength > *PSize) { Result = STATUS_BUFFER_TOO_SMALL; goto exit; } *PSize = TargetPathLength; memcpy(Buffer, TargetPath, TargetPathLength); Result = STATUS_SUCCESS; exit: if (0 != TargetPath) FspPosixDeletePath(TargetPath); return Result; } static NTSTATUS fsp_fuse_intf_GetReparsePointEx(FSP_FILE_SYSTEM *FileSystem, const char *PosixPath, struct fuse_file_info *fi, PVOID Buffer, PSIZE_T PSize, PUINT32 PResolveFileAttributes) { struct fuse *f = FileSystem->UserContext; UINT32 Uid, Gid, Mode, Dev; FSP_FSCTL_FILE_INFO FileInfo; PREPARSE_DATA_BUFFER ReparseData; USHORT ReparseDataLength; SIZE_T Size; NTSTATUS Result; if (0 != PResolveFileAttributes && FILE_ATTRIBUTE_REPARSE_POINT == PResolveFileAttributes[0]) { Mode = 0120000; memset(&FileInfo, 0, sizeof FileInfo); FileInfo.FileAttributes = PResolveFileAttributes[0]; FileInfo.ReparseTag = IO_REPARSE_TAG_SYMLINK; PResolveFileAttributes[0] = 0; goto skip_getattr; } Result = fsp_fuse_intf_GetFileInfoFunnel(FileSystem, PosixPath, fi, 0, &Uid, &Gid, &Mode, &Dev, FALSE, &FileInfo); if (!NT_SUCCESS(Result)) return Result; if (0 == (FILE_ATTRIBUTE_REPARSE_POINT & FileInfo.FileAttributes)) { if (0 != PResolveFileAttributes) PResolveFileAttributes[1] = FileInfo.FileAttributes; return STATUS_NOT_A_REPARSE_POINT; } skip_getattr: if (0 == Buffer) return STATUS_SUCCESS; switch (Mode & 0170000) { case 0010000: /* S_IFIFO */ ReparseDataLength = (USHORT)( FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) + 8); break; case 0020000: /* S_IFCHR */ ReparseDataLength = (USHORT)( FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) + 16); break; case 0060000: /* S_IFBLK */ ReparseDataLength = (USHORT)( FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) + 16); break; case 0140000: /* S_IFSOCK */ ReparseDataLength = (USHORT)( FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) + 8); break; case 0120000: /* S_IFLNK */ ReparseDataLength = (USHORT)( FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer)); break; default: /* cannot happen! */ return STATUS_NOT_A_REPARSE_POINT; } if ((SIZE_T)FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) + ReparseDataLength > *PSize) return STATUS_BUFFER_TOO_SMALL; ReparseData = (PREPARSE_DATA_BUFFER)Buffer; ReparseData->ReparseTag = FileInfo.ReparseTag; ReparseData->ReparseDataLength = ReparseDataLength; switch (Mode & 0170000) { case 0010000: /* S_IFIFO */ *(PUINT64)(ReparseData->GenericReparseBuffer.DataBuffer + 0) = NFS_SPECFILE_FIFO; break; case 0020000: /* S_IFCHR */ *(PUINT64)(ReparseData->GenericReparseBuffer.DataBuffer + 0) = NFS_SPECFILE_CHR; *(PUINT32)(ReparseData->GenericReparseBuffer.DataBuffer + 8) = (Dev >> 16) & 0xffff; *(PUINT32)(ReparseData->GenericReparseBuffer.DataBuffer + 12) = Dev & 0xffff; break; case 0060000: /* S_IFBLK */ *(PUINT64)(ReparseData->GenericReparseBuffer.DataBuffer + 0) = NFS_SPECFILE_BLK; *(PUINT32)(ReparseData->GenericReparseBuffer.DataBuffer + 8) = (Dev >> 16) & 0xffff; *(PUINT32)(ReparseData->GenericReparseBuffer.DataBuffer + 12) = Dev & 0xffff; break; case 0140000: /* S_IFSOCK */ *(PUINT64)(ReparseData->GenericReparseBuffer.DataBuffer + 0) = NFS_SPECFILE_SOCK; break; case 0120000: /* S_IFLNK */ Size = *PSize - FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer); Result = fsp_fuse_intf_GetReparsePointSymlink(FileSystem, PosixPath, ReparseData->SymbolicLinkReparseBuffer.PathBuffer, &Size); if (!NT_SUCCESS(Result)) return Result; ReparseData->ReparseDataLength += (USHORT)Size; ReparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0; ReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength = (USHORT)Size; ReparseData->SymbolicLinkReparseBuffer.PrintNameOffset = 0; ReparseData->SymbolicLinkReparseBuffer.PrintNameLength = (USHORT)Size; ReparseData->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE; break; default: /* cannot happen! */ return STATUS_NOT_A_REPARSE_POINT; } *PSize = FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) + ReparseData->ReparseDataLength; return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_GetVolumeInfo(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_VOLUME_INFO *VolumeInfo) { struct fuse *f = FileSystem->UserContext; struct fuse_statvfs stbuf; int err; memset(&stbuf, 0, sizeof stbuf); if (0 != f->ops.statfs) { err = f->ops.statfs("/", &stbuf); if (0 != err) return fsp_fuse_ntstatus_from_errno(f->env, err); } VolumeInfo->TotalSize = (UINT64)stbuf.f_blocks * (UINT64)stbuf.f_frsize; VolumeInfo->FreeSize = (UINT64)stbuf.f_bfree * (UINT64)stbuf.f_frsize; VolumeInfo->VolumeLabelLength = f->VolumeLabelLength; memcpy(&VolumeInfo->VolumeLabel, &f->VolumeLabel, f->VolumeLabelLength); return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_SetVolumeLabel(FSP_FILE_SYSTEM *FileSystem, PWSTR VolumeLabel, FSP_FSCTL_VOLUME_INFO *VolumeInfo) { /* * Implemented: there is no volume label concept in FUSE. * Perhaps we can emulate it with a xattr on "/" one day. */ return STATUS_INVALID_PARAMETER; } static NTSTATUS fsp_fuse_intf_GetSecurityByName(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, PUINT32 PFileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptorBuf, SIZE_T *PSecurityDescriptorSize) { struct fuse *f = FileSystem->UserContext; char *PosixPath = 0; NTSTATUS Result; Result = FspPosixMapWindowsToPosixPath(FileName, &PosixPath); if (!NT_SUCCESS(Result)) goto exit; Result = fsp_fuse_intf_GetSecurityEx(FileSystem, PosixPath, 0, PFileAttributes, SecurityDescriptorBuf, PSecurityDescriptorSize); if (!NT_SUCCESS(Result) && STATUS_OBJECT_NAME_NOT_FOUND != Result && STATUS_OBJECT_PATH_NOT_FOUND != Result) goto exit; if (FSP_FUSE_HAS_SYMLINKS(f) && FspFileSystemFindReparsePoint(FileSystem, fsp_fuse_intf_GetReparsePointByName, 0, FileName, PFileAttributes)) Result = STATUS_REPARSE; else if (NT_SUCCESS(Result)) Result = STATUS_SUCCESS; exit: if (0 != PosixPath) FspPosixDeletePath(PosixPath); return Result; } static VOID fsp_fuse_intf_GetOpenFileInfoPath( struct fuse *f, const char *PosixPath, struct fuse_file_info *fi, FSP_FSCTL_OPEN_FILE_INFO *OpenFileInfo) { char PosixNormalizedName[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; PWSTR NormalizedName; ULONG NormalizedNameSize; if (0 == f->ops.getpath(PosixPath, PosixNormalizedName, sizeof PosixNormalizedName, fi) && NT_SUCCESS(FspPosixMapPosixToWindowsPath(PosixNormalizedName, &NormalizedName))) { NormalizedNameSize = lstrlenW(NormalizedName) * sizeof(WCHAR); if (OpenFileInfo->NormalizedNameSize >= NormalizedNameSize) { OpenFileInfo->NormalizedNameSize = (UINT16)NormalizedNameSize; memcpy(OpenFileInfo->NormalizedName, NormalizedName, NormalizedNameSize); } FspPosixDeletePath(NormalizedName); } } static NTSTATUS fsp_fuse_intf_Create(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize, PVOID ExtraBuffer, ULONG ExtraLength, BOOLEAN ExtraBufferIsReparsePoint, PVOID *PFileDesc, FSP_FSCTL_FILE_INFO *FileInfo) { 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); UINT32 Uid, Gid, Mode; FSP_FSCTL_FILE_INFO FileInfoBuf; struct fsp_fuse_file_desc *filedesc = 0; struct fuse_file_info fi; BOOLEAN Opened = FALSE; int err; NTSTATUS Result; if (0 != ExtraBuffer) { if (!ExtraBufferIsReparsePoint) { if (0 == f->ops.listxattr || 0 == f->ops.getxattr || 0 == f->ops.setxattr || 0 == f->ops.removexattr) { Result = STATUS_EAS_NOT_SUPPORTED; goto exit; } } else { /* !!!: revisit */ Result = STATUS_INVALID_PARAMETER; goto exit; } } filedesc = MemAlloc(sizeof *filedesc); if (0 == filedesc) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Uid = context->uid; Gid = context->gid; Mode = 0777; if (0 != SecurityDescriptor) { Result = FspPosixMapSecurityDescriptorToPermissions(SecurityDescriptor, &Uid, &Gid, &Mode); if (!NT_SUCCESS(Result)) goto exit; } Mode &= ~context->umask; if (CreateOptions & FILE_DIRECTORY_FILE) { if (f->set_create_dir_umask) Mode = 0777 & ~f->create_dir_umask; else if (f->set_create_umask) Mode = 0777 & ~f->create_umask; } else { if (f->set_create_file_umask) Mode = 0777 & ~f->create_file_umask; else if (f->set_create_umask) Mode = 0777 & ~f->create_umask; } memset(&fi, 0, sizeof fi); if ('C' == f->env->environment) /* Cygwin */ fi.flags = 0x0200 | 0x0800 | 2 /*O_CREAT|O_EXCL|O_RDWR*/; else fi.flags = 0x0100 | 0x0400 | 2 /*O_CREAT|O_EXCL|O_RDWR*/; if (CreateOptions & FILE_DIRECTORY_FILE) { if (0 != f->ops.mkdir) { err = f->ops.mkdir(contexthdr->PosixPath, Mode); if (0 != err) { Result = fsp_fuse_ntstatus_from_errno(f->env, err); goto exit; } if (0 != f->ops.opendir) { err = f->ops.opendir(contexthdr->PosixPath, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else { fi.fh = -1; Result = STATUS_SUCCESS; } } else Result = STATUS_INVALID_DEVICE_REQUEST; } else { if (0 != f->ops.create) { err = f->ops.create(contexthdr->PosixPath, Mode, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else if (0 != f->ops.mknod && 0 != f->ops.open) { err = f->ops.mknod(contexthdr->PosixPath, Mode, 0); if (0 != err) { Result = fsp_fuse_ntstatus_from_errno(f->env, err); goto exit; } err = f->ops.open(contexthdr->PosixPath, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else Result = STATUS_INVALID_DEVICE_REQUEST; } if (!NT_SUCCESS(Result)) goto exit; Opened = TRUE; if (0 != FileAttributes && 0 != (f->conn_want & FSP_FUSE_CAP_STAT_EX) && 0 != f->ops.chflags) { err = f->ops.chflags(contexthdr->PosixPath, fsp_fuse_intf_MapFileAttributesToFlags(CreateOptions & FILE_DIRECTORY_FILE ? FileAttributes : FileAttributes | FILE_ATTRIBUTE_ARCHIVE)); Result = fsp_fuse_ntstatus_from_errno(f->env, err); if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result) goto exit; } if ((Uid != context->uid || Gid != context->gid) && 0 != f->ops.chown) { err = f->ops.chown(contexthdr->PosixPath, Uid, Gid); Result = fsp_fuse_ntstatus_from_errno(f->env, err); if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result) goto exit; } if (0 != ExtraBuffer) { if (!ExtraBufferIsReparsePoint) { Result = FspFileSystemEnumerateEa(FileSystem, fsp_fuse_intf_SetEaEntry, contexthdr->PosixPath, ExtraBuffer, ExtraLength); if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result) goto exit; } else { /* !!!: revisit: WslFeatures, GetFileInfoFunnel, GetReparsePointEx, SetReparsePoint */ Result = STATUS_INVALID_PARAMETER; goto exit; } } Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, contexthdr->PosixPath, FUSE_FILE_INFO(CreateOptions & FILE_DIRECTORY_FILE, &fi), &Uid, &Gid, &Mode, &FileInfoBuf); if (!NT_SUCCESS(Result)) goto exit; /* * Ignore fuse_file_info::direct_io, fuse_file_info::keep_cache. * NOTE: Originally WinFsp did not support disabling the cache manager * for an individual file. This is now possible and we should revisit. * * Ignore fuse_file_info::nonseekable. */ *PFileDesc = filedesc; memcpy(FileInfo, &FileInfoBuf, sizeof FileInfoBuf); filedesc->PosixPath = contexthdr->PosixPath; filedesc->IsDirectory = !!(FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_DIRECTORY); filedesc->IsReparsePoint = FALSE; filedesc->OpenFlags = fi.flags; filedesc->FileHandle = fi.fh; filedesc->DirBuffer = 0; contexthdr->PosixPath = 0; if (!f->VolumeParams.CaseSensitiveSearch && 0 != f->ops.getpath) fsp_fuse_intf_GetOpenFileInfoPath(f, filedesc->PosixPath, -1 != fi.fh ? &fi : 0, FspFileSystemGetOpenFileInfo(FileInfo)); Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) { if (Opened) { if (CreateOptions & FILE_DIRECTORY_FILE) { if (0 != f->ops.releasedir) f->ops.releasedir(contexthdr->PosixPath, &fi); } else { if (0 != f->ops.release) f->ops.release(contexthdr->PosixPath, &fi); } } MemFree(filedesc); } return Result; } static NTSTATUS fsp_fuse_intf_Open(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, PVOID *PFileDesc, FSP_FSCTL_FILE_INFO *FileInfo) { 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); UINT32 Uid, Gid, Mode; FSP_FSCTL_FILE_INFO FileInfoBuf; struct fsp_fuse_file_desc *filedesc = 0; struct fuse_file_info fi; int err; NTSTATUS Result; if (0 != (CreateOptions & FILE_DELETE_ON_CLOSE) && 0 != (f->conn_want & FSP_FUSE_CAP_DELETE_ACCESS) && 0 != f->ops.access) { err = f->ops.access(contexthdr->PosixPath, FSP_FUSE_DELETE_OK); Result = fsp_fuse_ntstatus_from_errno(f->env, err); if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result) { if (STATUS_ACCESS_DENIED == Result) Result = STATUS_CANNOT_DELETE; goto exit; } } Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, contexthdr->PosixPath, 0, &Uid, &Gid, &Mode, &FileInfoBuf); if (!NT_SUCCESS(Result)) goto exit; filedesc = MemAlloc(sizeof *filedesc); if (0 == filedesc) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } memset(&fi, 0, sizeof fi); switch (GrantedAccess & (FILE_READ_DATA | FILE_WRITE_DATA)) { default: case FILE_READ_DATA: fi.flags = 0/*O_RDONLY*/; break; case FILE_WRITE_DATA: fi.flags = 1/*O_WRONLY*/; break; case FILE_READ_DATA | FILE_WRITE_DATA: fi.flags = 2/*O_RDWR*/; break; } if (FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { fi.fh = -1; Result = STATUS_SUCCESS; } else if (FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (0 != f->ops.opendir) { err = f->ops.opendir(contexthdr->PosixPath, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else { fi.fh = -1; Result = STATUS_SUCCESS; } } else { /* * Some Windows applications (notably Go programs) specify FILE_APPEND_DATA without * FILE_WRITE_DATA when opening files for appending. This caused the WinFsp-FUSE layer * to erroneously pass O_RDONLY to the FUSE file system in such cases. We add a test * for FILE_APPEND_DATA to ensure that either O_WRONLY or O_RDWR is specified. */ if (GrantedAccess & FILE_APPEND_DATA) { if (fi.flags == 0) fi.flags = 1; /* need O_WRONLY as a bare minimum in order to append */ } if (0 != f->ops.open) { err = f->ops.open(contexthdr->PosixPath, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else Result = STATUS_INVALID_DEVICE_REQUEST; } if (!NT_SUCCESS(Result)) goto exit; /* * Ignore fuse_file_info::direct_io, fuse_file_info::keep_cache. * NOTE: Originally WinFsp did not support disabling the cache manager * for an individual file. This is now possible and we should revisit. * * Ignore fuse_file_info::nonseekable. */ *PFileDesc = filedesc; memcpy(FileInfo, &FileInfoBuf, sizeof FileInfoBuf); filedesc->PosixPath = contexthdr->PosixPath; filedesc->IsDirectory = !!(FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_DIRECTORY); filedesc->IsReparsePoint = !!(FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT); filedesc->OpenFlags = fi.flags; filedesc->FileHandle = fi.fh; filedesc->DirBuffer = 0; contexthdr->PosixPath = 0; if (!f->VolumeParams.CaseSensitiveSearch && 0 != f->ops.getpath) fsp_fuse_intf_GetOpenFileInfoPath(f, filedesc->PosixPath, -1 != fi.fh ? &fi : 0, FspFileSystemGetOpenFileInfo(FileInfo)); Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) MemFree(filedesc); return Result; } static NTSTATUS fsp_fuse_intf_Overwrite(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize, PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, FSP_FSCTL_FILE_INFO *FileInfo) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; UINT32 Uid, Gid, Mode; struct fuse_file_info fi; int err; NTSTATUS Result; if (filedesc->IsDirectory || filedesc->IsReparsePoint) return STATUS_ACCESS_DENIED; if (0 != Ea) { char names[3 * 1024]; int namesize; if (0 == f->ops.listxattr || 0 == f->ops.getxattr || 0 == f->ops.setxattr || 0 == f->ops.removexattr) return STATUS_EAS_NOT_SUPPORTED; namesize = f->ops.listxattr(filedesc->PosixPath, names, sizeof names); if (0 < namesize) for (char *p = names, *endp = p + namesize; endp > p; p += namesize) { namesize = lstrlenA(p) + 1; f->ops.removexattr(filedesc->PosixPath, p); } Result = FspFileSystemEnumerateEa(FileSystem, fsp_fuse_intf_SetEaEntry, filedesc->PosixPath, Ea, EaLength); if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result) return Result; } if (0 != f->ops.ftruncate) { memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; err = f->ops.ftruncate(filedesc->PosixPath, 0, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else if (0 != f->ops.truncate) { err = f->ops.truncate(filedesc->PosixPath, 0); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else Result = STATUS_INVALID_DEVICE_REQUEST; if (!NT_SUCCESS(Result)) return Result; if (0 != FileAttributes && 0 != (f->conn_want & FSP_FUSE_CAP_STAT_EX) && 0 != f->ops.chflags) { /* * The code below is not strictly correct. File attributes should be * replaced when ReplaceFileAttributes is TRUE and merged (or'ed) when * ReplaceFileAttributes is FALSE. I am punting on this detail for now. */ err = f->ops.chflags(filedesc->PosixPath, fsp_fuse_intf_MapFileAttributesToFlags(FileAttributes | FILE_ATTRIBUTE_ARCHIVE)); Result = fsp_fuse_ntstatus_from_errno(f->env, err); if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result) return Result; } return fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, FileInfo); } static VOID fsp_fuse_intf_Cleanup(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PWSTR FileName, ULONG Flags) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; /* * In Windows a DeleteFile/RemoveDirectory is the sequence of the following: * Create(FILE_OPEN) * SetInformation(Disposition) * Cleanup * Close * * The FSD maintains a count of how many handles are currently open for a file. When the * last handle is closed *and* the disposition flag is set the FSD sends us a Cleanup with * the Delete flag set. * * Notice that when we receive a Cleanup with Delete set there can be no open handles other * than ours. [Even if there is a concurrent Open of this file, the FSD will fail it with * STATUS_DELETE_PENDING.] This means that we do not need to worry about the hard_remove * FUSE option and can safely remove the file at this time. * * * NOTE: * * Since WinFsp 2022 Beta4 (v1.10B4) it is possible to handle handles open other than ours * because of the new POSIX unlink semantics. Although we still do not provide the hard_remove * option, file systems that would need the hard_remove option can instead use the * LegacyUnlinkRename option to opt out of the POSIX unlink semantics. */ if (Flags & FspCleanupDelete) if (filedesc->IsDirectory && !filedesc->IsReparsePoint) { if (0 != f->ops.rmdir) f->ops.rmdir(filedesc->PosixPath); } else { if (0 != f->ops.unlink) f->ops.unlink(filedesc->PosixPath); } } static VOID fsp_fuse_intf_Close(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_file_info fi; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; if (filedesc->IsReparsePoint) { /* reparse points are not opened, nothing to do! */ } else if (filedesc->IsDirectory) { if (0 != f->ops.releasedir) f->ops.releasedir(filedesc->PosixPath, &fi); } else { if (0 != f->ops.flush) f->ops.flush(filedesc->PosixPath, &fi); if (0 != f->ops.release) f->ops.release(filedesc->PosixPath, &fi); } FspFileSystemDeleteDirectoryBuffer(&filedesc->DirBuffer); MemFree(filedesc->PosixPath); MemFree(filedesc); } static NTSTATUS fsp_fuse_intf_Read(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PVOID Buffer, UINT64 Offset, ULONG Length, PULONG PBytesTransferred) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_file_info fi; int bytes; NTSTATUS Result; if (filedesc->IsDirectory || filedesc->IsReparsePoint) return STATUS_ACCESS_DENIED; if (0 == f->ops.read) return STATUS_INVALID_DEVICE_REQUEST; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; bytes = f->ops.read(filedesc->PosixPath, Buffer, Length, Offset, &fi); if (0 < bytes) { *PBytesTransferred = bytes; Result = STATUS_SUCCESS; } else if (0 == bytes) Result = STATUS_END_OF_FILE; else Result = fsp_fuse_ntstatus_from_errno(f->env, bytes); return Result; } static NTSTATUS fsp_fuse_intf_Write(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PVOID Buffer, UINT64 Offset, ULONG Length, BOOLEAN WriteToEndOfFile, BOOLEAN ConstrainedIo, PULONG PBytesTransferred, FSP_FSCTL_FILE_INFO *FileInfo) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; UINT32 Uid, Gid, Mode; struct fuse_file_info fi; FSP_FSCTL_FILE_INFO FileInfoBuf; UINT64 EndOffset, AllocationUnit; int bytes; NTSTATUS Result; if (filedesc->IsDirectory || filedesc->IsReparsePoint) return STATUS_ACCESS_DENIED; if (0 == f->ops.write) return STATUS_INVALID_DEVICE_REQUEST; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, &FileInfoBuf); if (!NT_SUCCESS(Result)) return Result; if (ConstrainedIo) { if (Offset >= FileInfoBuf.FileSize) goto success; EndOffset = Offset + Length; if (EndOffset > FileInfoBuf.FileSize) EndOffset = FileInfoBuf.FileSize; } else { if (WriteToEndOfFile) Offset = FileInfoBuf.FileSize; EndOffset = Offset + Length; } bytes = f->ops.write(filedesc->PosixPath, Buffer, (size_t)(EndOffset - Offset), Offset, &fi); if (0 > bytes) return fsp_fuse_ntstatus_from_errno(f->env, bytes); *PBytesTransferred = bytes; AllocationUnit = (UINT64)f->VolumeParams.SectorSize * (UINT64)f->VolumeParams.SectorsPerAllocationUnit; if (Offset + bytes > FileInfoBuf.FileSize) FileInfoBuf.FileSize = Offset + bytes; FileInfoBuf.AllocationSize = (FileInfoBuf.FileSize + AllocationUnit - 1) / AllocationUnit * AllocationUnit; success: memcpy(FileInfo, &FileInfoBuf, sizeof FileInfoBuf); return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_Flush(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, FSP_FSCTL_FILE_INFO *FileInfo) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; UINT32 Uid, Gid, Mode; struct fuse_file_info fi; FSP_FSCTL_FILE_INFO FileInfoBuf; int err; NTSTATUS Result; if (0 == filedesc) return STATUS_SUCCESS; /* FUSE cannot flush volumes */ memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; Result = STATUS_SUCCESS; /* just say success, if fs does not support fsync */ if (filedesc->IsReparsePoint) Result = STATUS_ACCESS_DENIED; else if (filedesc->IsDirectory) { if (0 != f->ops.fsyncdir) { err = f->ops.fsyncdir(filedesc->PosixPath, 0, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } } else { if (0 != f->ops.fsync) { err = f->ops.fsync(filedesc->PosixPath, 0, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } } if (!NT_SUCCESS(Result)) return Result; Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, &FileInfoBuf); if (!NT_SUCCESS(Result)) return Result; memcpy(FileInfo, &FileInfoBuf, sizeof FileInfoBuf); return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_GetFileInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, FSP_FSCTL_FILE_INFO *FileInfo) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; UINT32 Uid, Gid, Mode; struct fuse_file_info fi; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; return fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, FileInfo); } static NTSTATUS fsp_fuse_intf_SetBasicInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, UINT32 FileAttributes, UINT64 CreationTime, UINT64 LastAccessTime, UINT64 LastWriteTime, UINT64 ChangeTime, FSP_FSCTL_FILE_INFO *FileInfo) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; UINT32 Uid, Gid, Mode; struct fuse_file_info fi; FSP_FSCTL_FILE_INFO FileInfoBuf; struct fuse_timespec tv[2]; struct fuse_utimbuf timbuf; int err; NTSTATUS Result; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; if (INVALID_FILE_ATTRIBUTES != FileAttributes && 0 != (f->conn_want & FSP_FUSE_CAP_STAT_EX) && 0 != f->ops.chflags) { err = f->ops.chflags(filedesc->PosixPath, fsp_fuse_intf_MapFileAttributesToFlags(FileAttributes)); Result = fsp_fuse_ntstatus_from_errno(f->env, err); if (!NT_SUCCESS(Result)) return Result; } if ((0 != LastAccessTime || 0 != LastWriteTime) && (0 != f->ops.utimens || 0 != f->ops.utime)) { if (0 == LastAccessTime || 0 == LastWriteTime) { Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, &FileInfoBuf); if (!NT_SUCCESS(Result)) return Result; if (0 == LastAccessTime) LastAccessTime = FileInfoBuf.LastAccessTime; if (0 == LastWriteTime) LastWriteTime = FileInfoBuf.LastWriteTime; } FspPosixFileTimeToUnixTime(LastAccessTime, (void *)&tv[0]); FspPosixFileTimeToUnixTime(LastWriteTime, (void *)&tv[1]); if (0 != f->ops.utimens) { err = f->ops.utimens(filedesc->PosixPath, tv); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else { timbuf.actime = tv[0].tv_sec; timbuf.modtime = tv[1].tv_sec; err = f->ops.utime(filedesc->PosixPath, &timbuf); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } if (!NT_SUCCESS(Result)) return Result; } if (0 != CreationTime && 0 != f->ops.setcrtime) { FspPosixFileTimeToUnixTime(CreationTime, (void *)&tv[0]); err = f->ops.setcrtime(filedesc->PosixPath, &tv[0]); Result = fsp_fuse_ntstatus_from_errno(f->env, err); if (!NT_SUCCESS(Result)) return Result; } if (0 != ChangeTime && 0 != f->ops.setchgtime) { FspPosixFileTimeToUnixTime(ChangeTime, (void *)&tv[0]); err = f->ops.setchgtime(filedesc->PosixPath, &tv[0]); Result = fsp_fuse_ntstatus_from_errno(f->env, err); if (!NT_SUCCESS(Result)) return Result; } return fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, FileInfo); } static NTSTATUS fsp_fuse_intf_SetFileSize(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, UINT64 NewSize, BOOLEAN SetAllocationSize, FSP_FSCTL_FILE_INFO *FileInfo) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; UINT32 Uid, Gid, Mode; struct fuse_file_info fi; FSP_FSCTL_FILE_INFO FileInfoBuf; UINT64 AllocationUnit; int err; NTSTATUS Result; if (filedesc->IsDirectory || filedesc->IsReparsePoint) return STATUS_ACCESS_DENIED; if (0 == f->ops.ftruncate && 0 == f->ops.truncate) return STATUS_INVALID_DEVICE_REQUEST; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, &FileInfoBuf); if (!NT_SUCCESS(Result)) return Result; if (!SetAllocationSize || FileInfoBuf.FileSize > NewSize) { /* * "FileInfoBuf.FileSize > NewSize" explanation: * FUSE 2.8 does not support allocation size. However if the new AllocationSize * is less than the current FileSize we must truncate the file. */ if (0 != f->ops.ftruncate) { err = f->ops.ftruncate(filedesc->PosixPath, NewSize, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else { err = f->ops.truncate(filedesc->PosixPath, NewSize); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } if (!NT_SUCCESS(Result)) return Result; AllocationUnit = (UINT64)f->VolumeParams.SectorSize * (UINT64)f->VolumeParams.SectorsPerAllocationUnit; FileInfoBuf.FileSize = NewSize; FileInfoBuf.AllocationSize = (FileInfoBuf.FileSize + AllocationUnit - 1) / AllocationUnit * AllocationUnit; } memcpy(FileInfo, &FileInfoBuf, sizeof FileInfoBuf); return STATUS_SUCCESS; } /* !static: used by fuse2to3 */ int fsp_fuse_intf_CanDeleteAddDirInfo(void *buf, const char *name, const struct fuse_stat *stbuf, fuse_off_t off) { struct fuse_dirhandle *dh = buf; if ('.' == name[0] && ('\0' == name[1] || ('.' == name[1] && '\0' == name[2]))) { dh->DotFiles = TRUE; return 0; } else { dh->HasChild = TRUE; return 1; } } static int fsp_fuse_intf_CanDeleteAddDirInfoOld(fuse_dirh_t dh, const char *name, int type, fuse_ino_t ino) { return fsp_fuse_intf_CanDeleteAddDirInfo(dh, name, 0, 0) ? -ENOMEM : 0; } static NTSTATUS fsp_fuse_intf_CanDelete(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PWSTR FileName) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_file_info fi; struct fuse_dirhandle dh; int err; if (0 != (f->conn_want & FSP_FUSE_CAP_DELETE_ACCESS) && 0 != f->ops.access) { NTSTATUS Result; err = f->ops.access(filedesc->PosixPath, FSP_FUSE_DELETE_OK); Result = fsp_fuse_ntstatus_from_errno(f->env, err); if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result) { if (STATUS_ACCESS_DENIED == Result) Result = STATUS_CANNOT_DELETE; return Result; } } if (filedesc->IsDirectory && !filedesc->IsReparsePoint) { /* check that directory is empty! */ memset(&dh, 0, sizeof dh); if (0 != f->ops.readdir) { memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; err = f->ops.readdir(filedesc->PosixPath, &dh, fsp_fuse_intf_CanDeleteAddDirInfo, 0, &fi); } else if (0 != f->ops.getdir) err = f->ops.getdir(filedesc->PosixPath, &dh, fsp_fuse_intf_CanDeleteAddDirInfoOld); else err = 0; if (dh.HasChild) return STATUS_DIRECTORY_NOT_EMPTY; else if (dh.DotFiles) return STATUS_SUCCESS; else return fsp_fuse_ntstatus_from_errno(f->env, err); } return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_Rename(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PWSTR FileName, PWSTR NewFileName, BOOLEAN ReplaceIfExists) { 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); UINT32 Uid, Gid, Mode; FSP_FSCTL_FILE_INFO FileInfoBuf; struct fsp_fuse_file_desc *filedesc = FileDesc; int err; NTSTATUS Result; Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, contexthdr->PosixPath, 0, &Uid, &Gid, &Mode, &FileInfoBuf); if (!NT_SUCCESS(Result) && STATUS_OBJECT_NAME_NOT_FOUND != Result && STATUS_OBJECT_PATH_NOT_FOUND != Result) return Result; if (NT_SUCCESS(Result) && (f->VolumeParams.CaseSensitiveSearch || 0 != invariant_wcsicmp(FileName, NewFileName))) { if (!ReplaceIfExists) return STATUS_OBJECT_NAME_COLLISION; if (FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) return STATUS_ACCESS_DENIED; } err = f->ops.rename(filedesc->PosixPath, contexthdr->PosixPath); return fsp_fuse_ntstatus_from_errno(f->env, err); } static NTSTATUS fsp_fuse_intf_GetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PSECURITY_DESCRIPTOR SecurityDescriptorBuf, SIZE_T *PSecurityDescriptorSize) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_file_info fi; UINT32 FileAttributes; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; return fsp_fuse_intf_GetSecurityEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &FileAttributes, SecurityDescriptorBuf, PSecurityDescriptorSize); } static NTSTATUS fsp_fuse_intf_SetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR ModificationDescriptor) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_file_info fi; UINT32 Uid, Gid, Mode, NewUid, NewGid, NewMode; FSP_FSCTL_FILE_INFO FileInfo; PSECURITY_DESCRIPTOR SecurityDescriptor, NewSecurityDescriptor; int err; NTSTATUS Result; if (0 == f->ops.chmod) return STATUS_INVALID_DEVICE_REQUEST; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, &FileInfo); if (!NT_SUCCESS(Result)) goto exit; Result = FspPosixMergePermissionsToSecurityDescriptor(Uid, Gid, Mode, f->FileSecurity, &SecurityDescriptor); if (!NT_SUCCESS(Result)) goto exit; Result = FspSetSecurityDescriptor( SecurityDescriptor, SecurityInformation, ModificationDescriptor, &NewSecurityDescriptor); if (!NT_SUCCESS(Result)) goto exit; Result = FspPosixMapSecurityDescriptorToPermissions(NewSecurityDescriptor, &NewUid, &NewGid, &NewMode); if (!NT_SUCCESS(Result)) goto exit; if (NewMode != Mode) { err = f->ops.chmod(filedesc->PosixPath, NewMode); if (0 != err) { Result = fsp_fuse_ntstatus_from_errno(f->env, err); goto exit; } } if (NewUid != Uid || NewGid != Gid) if (0 != f->ops.chown) { err = f->ops.chown(filedesc->PosixPath, NewUid, NewGid); if (0 != err) { Result = fsp_fuse_ntstatus_from_errno(f->env, err); goto exit; } } Result = STATUS_SUCCESS; exit: if (0 != NewSecurityDescriptor) FspDeleteSecurityDescriptor(NewSecurityDescriptor, FspSetSecurityDescriptor); if (0 != SecurityDescriptor) FspDeleteSecurityDescriptor(SecurityDescriptor, FspPosixMergePermissionsToSecurityDescriptor); return Result; } static VOID fsp_fuse_intf_LogBadDirInfo( const char *PosixPath, const char *PosixName, const char *Message) { static LONG Count = 0; ULONG NewCount; NewCount = (ULONG)InterlockedIncrement(&Count); /* log only the first 5 such warnings to avoid warning overload */ if (5 >= NewCount) FspDebugLog("%S[TID=%04lx]: WARN: readdir(\"%s\"): name=\"%s\": %s\n", FspDiagIdent(), GetCurrentThreadId(), PosixPath, PosixName, Message); } /* !static: used by fuse2to3 */ int fsp_fuse_intf_AddDirInfo(void *buf, const char *name, const struct fuse_stat *stbuf, fuse_off_t off) { struct fuse_dirhandle *dh = buf; struct fsp_fuse_file_desc *filedesc = dh->filedesc; union { FSP_FSCTL_DIR_INFO V; UINT8 B[sizeof(FSP_FSCTL_DIR_INFO) + 255 * sizeof(WCHAR)]; } DirInfoBuf; FSP_FSCTL_DIR_INFO *DirInfo = &DirInfoBuf.V; ULONG SizeA, SizeW; if ('/' == filedesc->PosixPath[0] && '\0' == filedesc->PosixPath[1]) { /* if this is the root directory do not add the dot entries */ if ('.' == name[0] && ('\0' == name[1] || ('.' == name[1] && '\0' == name[2]))) return 0; } SizeA = lstrlenA(name); if (SizeA > 1020) { fsp_fuse_intf_LogBadDirInfo(filedesc->PosixPath, name, "too long"); return 0; } SizeW = MultiByteToWideChar(CP_UTF8, 0, name, SizeA, DirInfo->FileNameBuf, 255); if (0 == SizeW) { fsp_fuse_intf_LogBadDirInfo(filedesc->PosixPath, name, "MultiByteToWideChar failed"); return 0; } else if (255 == SizeW) { if (255 < MultiByteToWideChar(CP_UTF8, 0, name, SizeA, NULL, 0)) { fsp_fuse_intf_LogBadDirInfo(filedesc->PosixPath, name, "too long"); return 0; } } memset(DirInfo, 0, sizeof *DirInfo); DirInfo->Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + SizeW * sizeof(WCHAR)); if (dh->ReaddirPlus && 0 != stbuf && 0120000/* S_IFLNK */ != (stbuf->st_mode & 0170000)) { UINT32 Uid, Gid, Mode; NTSTATUS Result0; Result0 = fsp_fuse_intf_GetFileInfoFunnel(dh->FileSystem, name, 0, stbuf, &Uid, &Gid, &Mode, 0, TRUE, &DirInfo->FileInfo); if (NT_SUCCESS(Result0)) DirInfo->Padding[0] = 1; /* HACK: remember that the FileInfo is valid */ } return !FspFileSystemFillDirectoryBuffer(&filedesc->DirBuffer, DirInfo, &dh->Result); } static int fsp_fuse_intf_AddDirInfoOld(fuse_dirh_t dh, const char *name, int type, fuse_ino_t ino) { return fsp_fuse_intf_AddDirInfo(dh, name, 0, 0) ? -ENOMEM : 0; } static NTSTATUS fsp_fuse_intf_FixDirInfo(FSP_FILE_SYSTEM *FileSystem, struct fsp_fuse_file_desc *filedesc) { char *PosixPath = 0, *PosixName, *PosixPathEnd, SavedPathChar; ULONG SizeA, SizeW; PUINT8 Buffer; PULONG Index, IndexEnd; ULONG Count; FSP_FSCTL_DIR_INFO *DirInfo; UINT32 Uid, Gid, Mode; NTSTATUS Result; SizeA = lstrlenA(filedesc->PosixPath); PosixPath = MemAlloc(SizeA + 1 + 1020 + 1); if (0 == PosixPath) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } memcpy(PosixPath, filedesc->PosixPath, SizeA); if (1 < SizeA) /* if not root */ PosixPath[SizeA++] = '/'; PosixPath[SizeA] = '\0'; PosixName = PosixPath + SizeA; FspFileSystemPeekInDirectoryBuffer(&filedesc->DirBuffer, &Buffer, &Index, &Count); for (IndexEnd = Index + Count; IndexEnd > Index; Index++) { DirInfo = (FSP_FSCTL_DIR_INFO *)(Buffer + *Index); SizeW = (DirInfo->Size - sizeof *DirInfo) / sizeof(WCHAR); if (DirInfo->Padding[0]) { /* DirInfo has been filled already! */ DirInfo->Padding[0] = 0; } else { if (1 == SizeW && L'.' == DirInfo->FileNameBuf[0]) { PosixPathEnd = 1 < PosixName - PosixPath ? PosixName - 1 : PosixName; SavedPathChar = *PosixPathEnd; *PosixPathEnd = '\0'; } else if (2 == SizeW && L'.' == DirInfo->FileNameBuf[0] && L'.' == DirInfo->FileNameBuf[1]) { PosixPathEnd = 1 < PosixName - PosixPath ? PosixName - 2 : PosixName; while (PosixPath < PosixPathEnd && '/' != *PosixPathEnd) PosixPathEnd--; if (PosixPath == PosixPathEnd) PosixPathEnd++; SavedPathChar = *PosixPathEnd; *PosixPathEnd = '\0'; } else { PosixPathEnd = 0; SizeA = WideCharToMultiByte(CP_UTF8, 0, DirInfo->FileNameBuf, SizeW, PosixName, 1020, 0, 0); if (0 == SizeA) { /* this should never happen because we just converted using MultiByteToWideChar */ Result = STATUS_OBJECT_NAME_INVALID; goto exit; } PosixName[SizeA] = '\0'; } Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, PosixPath, 0, &Uid, &Gid, &Mode, &DirInfo->FileInfo); if (!NT_SUCCESS(Result)) { /* mark the directory buffer entry as invalid */ *Index = FspFileSystemDirectoryBufferEntryInvalid; fsp_fuse_intf_LogBadDirInfo(filedesc->PosixPath, PosixName, "getattr failed"); } if (0 != PosixPathEnd) *PosixPathEnd = SavedPathChar; } FspPosixDecodeWindowsPath(DirInfo->FileNameBuf, SizeW); } Result = STATUS_SUCCESS; exit: MemFree(PosixPath); return Result; } static NTSTATUS fsp_fuse_intf_ReadDirectory(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PWSTR Pattern, PWSTR Marker, PVOID Buffer, ULONG Length, PULONG PBytesTransferred) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_dirhandle dh; struct fuse_file_info fi; int err; NTSTATUS Result; if (!filedesc->IsDirectory || filedesc->IsReparsePoint) return STATUS_ACCESS_DENIED; if (FspFileSystemAcquireDirectoryBuffer(&filedesc->DirBuffer, 0 == Marker, &Result)) { memset(&dh, 0, sizeof dh); dh.filedesc = filedesc; dh.FileSystem = FileSystem; dh.ReaddirPlus = 0 != (f->conn_want & FSP_FUSE_CAP_READDIR_PLUS); dh.Result = STATUS_SUCCESS; if (0 != f->ops.readdir) { memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; err = f->ops.readdir(filedesc->PosixPath, &dh, fsp_fuse_intf_AddDirInfo, 0, &fi); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else if (0 != f->ops.getdir) { err = f->ops.getdir(filedesc->PosixPath, &dh, fsp_fuse_intf_AddDirInfoOld); Result = fsp_fuse_ntstatus_from_errno(f->env, err); } else Result = STATUS_INVALID_DEVICE_REQUEST; if (NT_SUCCESS(Result)) { Result = dh.Result; if (NT_SUCCESS(Result)) Result = fsp_fuse_intf_FixDirInfo(FileSystem, filedesc); } FspFileSystemReleaseDirectoryBuffer(&filedesc->DirBuffer); } if (!NT_SUCCESS(Result)) return Result; FspFileSystemReadDirectoryBuffer(&filedesc->DirBuffer, Marker, Buffer, Length, PBytesTransferred); return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_GetDirInfoByName(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PWSTR FileName, FSP_FSCTL_DIR_INFO *DirInfo) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; char *PosixName = 0; char PosixPath[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; int ParentLength, FSlashLength, PosixNameLength; UINT32 Uid, Gid, Mode; char PosixNormalizedName[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; PWSTR NormalizedName, NormalizedNameSuffix; ULONG NormalizedNameSize; BOOLEAN Normalized = FALSE; NTSTATUS Result; if (!filedesc->IsDirectory || filedesc->IsReparsePoint) return STATUS_ACCESS_DENIED; Result = FspPosixMapWindowsToPosixPath(FileName, &PosixName); if (!NT_SUCCESS(Result)) { Result = STATUS_OBJECT_NAME_NOT_FOUND; //Result? goto exit; } ParentLength = lstrlenA(filedesc->PosixPath); FSlashLength = 1 < ParentLength; PosixNameLength = lstrlenA(PosixName); if (FSP_FSCTL_TRANSACT_PATH_SIZEMAX <= (ParentLength + FSlashLength + PosixNameLength) * sizeof(WCHAR)) { Result = STATUS_OBJECT_NAME_NOT_FOUND; //STATUS_OBJECT_NAME_INVALID? goto exit; } memcpy(PosixPath, filedesc->PosixPath, ParentLength); memcpy(PosixPath + ParentLength, "/", FSlashLength); memcpy(PosixPath + ParentLength + FSlashLength, PosixName, PosixNameLength + 1); Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, PosixPath, 0, &Uid, &Gid, &Mode, &DirInfo->FileInfo); if (!NT_SUCCESS(Result)) { Result = STATUS_OBJECT_NAME_NOT_FOUND; //Result? goto exit; } //memset(DirInfo->Padding, 0, sizeof DirInfo->Padding); if (!f->VolumeParams.CaseSensitiveSearch && 0 != f->ops.getpath) { if (0 == f->ops.getpath(PosixPath, PosixNormalizedName, sizeof PosixNormalizedName, 0) && NT_SUCCESS(FspPosixMapPosixToWindowsPath(PosixNormalizedName, &NormalizedName))) { NormalizedNameSuffix = NormalizedName; for (PWSTR P = NormalizedNameSuffix; *P; P++) if (L'\\' == *P) NormalizedNameSuffix = P + 1; NormalizedNameSize = lstrlenW(NormalizedNameSuffix) * sizeof(WCHAR); if (f->VolumeParams.MaxComponentLength * sizeof(WCHAR) >= NormalizedNameSize) { DirInfo->Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + NormalizedNameSize); memcpy(DirInfo->FileNameBuf, NormalizedNameSuffix, NormalizedNameSize); Normalized = TRUE; } FspPosixDeletePath(NormalizedName); } } if (!Normalized) { DirInfo->Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + lstrlenW(FileName) * sizeof(WCHAR)); memcpy(DirInfo->FileNameBuf, FileName, DirInfo->Size - sizeof(FSP_FSCTL_DIR_INFO)); } Result = STATUS_SUCCESS; exit: if (0 != PosixName) FspPosixDeletePath(PosixName); return Result; } static NTSTATUS fsp_fuse_intf_ResolveReparsePoints(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 ReparsePointIndex, BOOLEAN ResolveLastPathComponent, PIO_STATUS_BLOCK PIoStatus, PVOID Buffer, PSIZE_T PSize) { return FspFileSystemResolveReparsePoints(FileSystem, fsp_fuse_intf_GetReparsePointByName, 0, FileName, ReparsePointIndex, ResolveLastPathComponent, PIoStatus, Buffer, PSize); } static NTSTATUS fsp_fuse_intf_GetReparsePointByName( FSP_FILE_SYSTEM *FileSystem, PVOID Context, PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize) { struct fuse *f = FileSystem->UserContext; char *PosixPath = 0; NTSTATUS Result; Result = FspPosixMapWindowsToPosixPath(FileName, &PosixPath); if (!NT_SUCCESS(Result)) goto exit; Result = fsp_fuse_intf_GetReparsePointEx(FileSystem, PosixPath, 0, Buffer, PSize, Context); exit: if (0 != PosixPath) FspPosixDeletePath(PosixPath); return Result; } static NTSTATUS fsp_fuse_intf_GetReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PWSTR FileName, PVOID Buffer, PSIZE_T PSize) { struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_file_info fi; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; return fsp_fuse_intf_GetReparsePointEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), Buffer, PSize, 0); } static NTSTATUS fsp_fuse_intf_SetReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PWSTR FileName, PVOID Buffer, SIZE_T Size) { struct fuse *f = FileSystem->UserContext; struct fuse_context *context = fsp_fuse_get_context(f->env); struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_file_info fi; UINT32 Uid, Gid, Mode, Dev; FSP_FSCTL_FILE_INFO FileInfo; PREPARSE_DATA_BUFFER ReparseData; PWSTR ReparseTargetPath; SIZE_T ReparseTargetPathLength; WCHAR TargetPath[FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)]; char *PosixTargetPath = 0, *PosixHiddenPath = 0; BOOLEAN IsSymlink; int err; NTSTATUS Result; /* * How we implement this is probably one of the worst aspects of this FUSE implementation. * * In Windows a CreateSymbolicLink is the sequence of the following: * Create * DeviceIoControl(FSCTL_SET_REPARSE_POINT) * Cleanup * Close * * The Create call creates the new file and the DeviceIoControl(FSCTL_SET_REPARSE_POINT) * call is supposed to convert it into a reparse point. However FUSE mknod/symlink will * fail with -EEXIST in this case. * * We must therefore find a solution using rename, which is unreliable and error-prone. * Note that this will also result in a change of the inode number for the reparse point! */ ReparseData = (PREPARSE_DATA_BUFFER)Buffer; if (IO_REPARSE_TAG_SYMLINK == ReparseData->ReparseTag || ( IO_REPARSE_TAG_NFS == ReparseData->ReparseTag && NFS_SPECFILE_LNK == *(PUINT64)ReparseData->GenericReparseBuffer.DataBuffer)) { if (filedesc->IsDirectory && 0 == f->ops.rmdir) return STATUS_INVALID_DEVICE_REQUEST; if (0 == f->ops.symlink || 0 == f->ops.rename || 0 == f->ops.unlink) return STATUS_INVALID_DEVICE_REQUEST; IsSymlink = TRUE; } else if (IO_REPARSE_TAG_NFS == ReparseData->ReparseTag) { /* FUSE cannot make a directory into an NFS reparse point */ if (filedesc->IsDirectory) return STATUS_ACCESS_DENIED; if (0 == f->ops.mknod || 0 == f->ops.rename || 0 == f->ops.unlink) return STATUS_INVALID_DEVICE_REQUEST; IsSymlink = FALSE; } else return STATUS_IO_REPARSE_TAG_MISMATCH; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, &FileInfo); if (!NT_SUCCESS(Result)) return Result; if (IsSymlink) { if (IO_REPARSE_TAG_SYMLINK == ReparseData->ReparseTag) { ReparseTargetPath = ReparseData->SymbolicLinkReparseBuffer.PathBuffer + ReparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR); ReparseTargetPathLength = ReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength; /* is this an absolute path? */ if (0 == (ReparseData->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) && ReparseTargetPathLength >= sizeof(WCHAR) && L'\\' == ReparseTargetPath[0]) { FSP_FSCTL_TRANSACT_REQ *Request = FspFileSystemGetOperationContext()->Request; /* we do not support absolute paths that point outside this file system */ if (0 == Request->Req.FileSystemControl.TargetOnFileSystem) return STATUS_ACCESS_DENIED; ReparseTargetPath += Request->Req.FileSystemControl.TargetOnFileSystem / sizeof(WCHAR); ReparseTargetPathLength -= Request->Req.FileSystemControl.TargetOnFileSystem; } } else { /* the PATH is in POSIX format (UTF-16 encoding) */ ReparseTargetPath = (PVOID)(ReparseData->GenericReparseBuffer.DataBuffer + 8); ReparseTargetPathLength = ReparseData->ReparseDataLength - 8; } memcpy(TargetPath, ReparseTargetPath, ReparseTargetPathLength); TargetPath[ReparseTargetPathLength / sizeof(WCHAR)] = L'\0'; /* * From this point forward we must jump to the EXIT label on failure. */ Result = FspPosixMapWindowsToPosixPathEx(TargetPath, &PosixTargetPath, IO_REPARSE_TAG_SYMLINK == ReparseData->ReparseTag); if (!NT_SUCCESS(Result)) goto exit; Result = fsp_fuse_intf_NewHiddenName(FileSystem, filedesc->PosixPath, &PosixHiddenPath); if (!NT_SUCCESS(Result)) goto exit; context->uid = Uid, context->gid = Gid; err = f->ops.symlink(PosixTargetPath, PosixHiddenPath); context->uid = -1, context->gid = -1; if (0 != err) { Result = fsp_fuse_ntstatus_from_errno(f->env, err); goto exit; } } else { switch (*(PUINT64)ReparseData->GenericReparseBuffer.DataBuffer) { case NFS_SPECFILE_FIFO: Mode = (Mode & ~0170000) | 0010000; Dev = 0; break; case NFS_SPECFILE_SOCK: Mode = (Mode & ~0170000) | 0140000; Dev = 0; break; case NFS_SPECFILE_CHR: Mode = (Mode & ~0170000) | 0020000; Dev = (*(PUINT32)(ReparseData->GenericReparseBuffer.DataBuffer + 8) << 16) | (*(PUINT32)(ReparseData->GenericReparseBuffer.DataBuffer + 12)); break; case NFS_SPECFILE_BLK: Mode = (Mode & ~0170000) | 0060000; Dev = (*(PUINT32)(ReparseData->GenericReparseBuffer.DataBuffer + 8) << 16) | (*(PUINT32)(ReparseData->GenericReparseBuffer.DataBuffer + 12)); break; default: return STATUS_IO_REPARSE_DATA_INVALID; } /* * From this point forward we must jump to the EXIT label on failure. */ Result = fsp_fuse_intf_NewHiddenName(FileSystem, filedesc->PosixPath, &PosixHiddenPath); if (!NT_SUCCESS(Result)) goto exit; context->uid = Uid, context->gid = Gid; err = f->ops.mknod(PosixHiddenPath, Mode, Dev); context->uid = -1, context->gid = -1; if (0 != err) { Result = fsp_fuse_ntstatus_from_errno(f->env, err); goto exit; } } if (filedesc->IsDirectory) { err = f->ops.rmdir(filedesc->PosixPath); if (0 == err) err = f->ops.rename(PosixHiddenPath, filedesc->PosixPath); } else err = f->ops.rename(PosixHiddenPath, filedesc->PosixPath); if (0 != err) { f->ops.unlink(PosixHiddenPath); Result = fsp_fuse_ntstatus_from_errno(f->env, err); goto exit; } if (filedesc->IsDirectory) { if (0 != f->ops.releasedir) f->ops.releasedir(filedesc->PosixPath, &fi); } else { if (0 != f->ops.release) f->ops.release(filedesc->PosixPath, &fi); } filedesc->IsReparsePoint = TRUE; filedesc->FileHandle = -1; Result = STATUS_SUCCESS; exit: MemFree(PosixHiddenPath); if (0 != PosixTargetPath) FspPosixDeletePath(PosixTargetPath); return Result; } static NTSTATUS fsp_fuse_intf_DeleteReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PWSTR FileName, PVOID Buffer, SIZE_T Size) { /* we were asked to delete the reparse point? no can do! */ return STATUS_ACCESS_DENIED; } static NTSTATUS fsp_fuse_intf_Control(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, UINT32 ControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, PULONG PBytesTransferred) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; struct fuse_file_info fi; int cmd; int err; if (0 == f->ops.ioctl) return STATUS_INVALID_DEVICE_REQUEST; if (FSP_FUSE_DEVICE_TYPE != DEVICE_TYPE_FROM_CTL_CODE(ControlCode)) return STATUS_INVALID_DEVICE_REQUEST; if (0 != InputBufferLength && 0 != OutputBufferLength && InputBufferLength != OutputBufferLength) return STATUS_INVALID_DEVICE_REQUEST; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; /* construct a Linux compatible ioctl code */ cmd = FSP_FUSE_IOCTL((ControlCode >> 2) & 0xfff, InputBufferLength, OutputBufferLength); if (0 == OutputBufferLength) err = f->ops.ioctl(filedesc->PosixPath, cmd, 0, &fi, 0, InputBuffer); else { if (0 != InputBufferLength) // OutputBuffer points to Response->Buffer which is FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX long memcpy(OutputBuffer, InputBuffer, InputBufferLength); err = f->ops.ioctl(filedesc->PosixPath, cmd, 0, &fi, 0, OutputBuffer); } *PBytesTransferred = OutputBufferLength; return fsp_fuse_ntstatus_from_errno(f->env, err); } static NTSTATUS fsp_fuse_intf_GetEa(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PFILE_FULL_EA_INFORMATION Ea0, ULONG EaLength, PULONG PBytesTransferred) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; char names[3 * 1024]; int namesize, valuesize; PFILE_FULL_EA_INFORMATION Ea = Ea0, PrevEa = 0; PUINT8 EaEnd = (PUINT8)Ea + EaLength, EaValue; if (0 == f->ops.listxattr || 0 == f->ops.getxattr) return STATUS_INVALID_DEVICE_REQUEST; namesize = f->ops.listxattr(filedesc->PosixPath, names, sizeof names); if (0 >= namesize) { *PBytesTransferred = 0; return fsp_fuse_ntstatus_from_errno(f->env, namesize); } for (char *p = names, *endp = p + namesize; endp > p; p += namesize) { namesize = lstrlenA(p) + 1; EaValue = (PUINT8)Ea + FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + namesize; if (EaValue >= EaEnd) /* if there is no space (at least 1 byte) for a value bail out */ break; valuesize = f->ops.getxattr(filedesc->PosixPath, p, EaValue, EaEnd - EaValue); if (0 >= valuesize) continue; Ea->NextEntryOffset = 0; Ea->Flags = 0; Ea->EaNameLength = namesize - 1; Ea->EaValueLength = valuesize; memcpy(Ea->EaName, p, namesize); if (0 != PrevEa) PrevEa->NextEntryOffset = (ULONG)((PUINT8)Ea - (PUINT8)PrevEa); PrevEa = Ea; *PBytesTransferred = (ULONG)((PUINT8)EaValue - (PUINT8)Ea0 + valuesize); Ea = (PVOID)((PUINT8)Ea + FSP_FSCTL_ALIGN_UP( FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + namesize + valuesize, sizeof(ULONG))); } return STATUS_SUCCESS; } static NTSTATUS fsp_fuse_intf_SetEaEntry( FSP_FILE_SYSTEM *FileSystem, PVOID Context, PFILE_FULL_EA_INFORMATION SingleEa) { struct fuse *f = FileSystem->UserContext; const char *PosixPath = Context; int err; if (0 != SingleEa->EaValueLength) { err = f->ops.setxattr(PosixPath, SingleEa->EaName, SingleEa->EaName + SingleEa->EaNameLength + 1, SingleEa->EaValueLength, 0); return fsp_fuse_ntstatus_from_errno(f->env, err); } else { err = f->ops.removexattr(PosixPath, SingleEa->EaName); /* ignore errors */ return STATUS_SUCCESS; } } static NTSTATUS fsp_fuse_intf_SetEa(FSP_FILE_SYSTEM *FileSystem, PVOID FileDesc, PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, FSP_FSCTL_FILE_INFO *FileInfo) { struct fuse *f = FileSystem->UserContext; struct fsp_fuse_file_desc *filedesc = FileDesc; UINT32 Uid, Gid, Mode; struct fuse_file_info fi; NTSTATUS Result; if (0 == f->ops.setxattr || 0 == f->ops.removexattr) return STATUS_INVALID_DEVICE_REQUEST; Result = FspFileSystemEnumerateEa(FileSystem, fsp_fuse_intf_SetEaEntry, filedesc->PosixPath, Ea, EaLength); if (!NT_SUCCESS(Result)) return Result; memset(&fi, 0, sizeof fi); fi.flags = filedesc->OpenFlags; fi.fh = filedesc->FileHandle; return fsp_fuse_intf_GetFileInfoEx(FileSystem, filedesc->PosixPath, FUSE_FILE_INFO(filedesc->IsDirectory, &fi), &Uid, &Gid, &Mode, FileInfo); } FSP_FILE_SYSTEM_INTERFACE fsp_fuse_intf = { fsp_fuse_intf_GetVolumeInfo, fsp_fuse_intf_SetVolumeLabel, fsp_fuse_intf_GetSecurityByName, 0, fsp_fuse_intf_Open, 0, fsp_fuse_intf_Cleanup, fsp_fuse_intf_Close, fsp_fuse_intf_Read, fsp_fuse_intf_Write, fsp_fuse_intf_Flush, fsp_fuse_intf_GetFileInfo, fsp_fuse_intf_SetBasicInfo, fsp_fuse_intf_SetFileSize, fsp_fuse_intf_CanDelete, fsp_fuse_intf_Rename, fsp_fuse_intf_GetSecurity, fsp_fuse_intf_SetSecurity, fsp_fuse_intf_ReadDirectory, fsp_fuse_intf_ResolveReparsePoints, fsp_fuse_intf_GetReparsePoint, fsp_fuse_intf_SetReparsePoint, fsp_fuse_intf_DeleteReparsePoint, 0, fsp_fuse_intf_GetDirInfoByName, fsp_fuse_intf_Control, 0, fsp_fuse_intf_Create, fsp_fuse_intf_Overwrite, fsp_fuse_intf_GetEa, fsp_fuse_intf_SetEa, }; /* * Utility */ NTSTATUS fsp_fuse_get_token_uidgid( HANDLE Token, TOKEN_INFORMATION_CLASS UserOrOwnerClass, /* TokenUser|TokenOwner */ PUINT32 PUid, PUINT32 PGid) { UINT32 Uid, Gid; union { TOKEN_USER V; UINT8 B[128]; } UserInfoBuf; PTOKEN_USER UserInfo = &UserInfoBuf.V; union { TOKEN_OWNER V; UINT8 B[128]; } OwnerInfoBuf; PTOKEN_OWNER OwnerInfo = &OwnerInfoBuf.V; union { TOKEN_PRIMARY_GROUP V; UINT8 B[128]; } GroupInfoBuf; PTOKEN_PRIMARY_GROUP GroupInfo = &GroupInfoBuf.V; DWORD Size; NTSTATUS Result; if (0 != PUid && TokenUser == UserOrOwnerClass) { if (!GetTokenInformation(Token, TokenUser, UserInfo, sizeof UserInfoBuf, &Size)) { if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } UserInfo = MemAlloc(Size); if (0 == UserInfo) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!GetTokenInformation(Token, TokenUser, UserInfo, Size, &Size)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } Result = FspPosixMapSidToUid(UserInfo->User.Sid, &Uid); if (!NT_SUCCESS(Result)) goto exit; } else if (0 != PUid && TokenOwner == UserOrOwnerClass) { if (!GetTokenInformation(Token, TokenOwner, OwnerInfo, sizeof OwnerInfoBuf, &Size)) { if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } OwnerInfo = MemAlloc(Size); if (0 == OwnerInfo) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!GetTokenInformation(Token, TokenOwner, OwnerInfo, Size, &Size)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } Result = FspPosixMapSidToUid(OwnerInfo->Owner, &Uid); if (!NT_SUCCESS(Result)) goto exit; } if (0 != PGid) { if (!GetTokenInformation(Token, TokenPrimaryGroup, GroupInfo, sizeof GroupInfoBuf, &Size)) { if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } GroupInfo = MemAlloc(Size); if (0 == GroupInfo) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!GetTokenInformation(Token, TokenPrimaryGroup, GroupInfo, Size, &Size)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } } Result = FspPosixMapSidToUid(GroupInfo->PrimaryGroup, &Gid); if (!NT_SUCCESS(Result)) goto exit; } if (0 != PUid) *PUid = Uid; if (0 != PGid) *PGid = Gid; Result = STATUS_SUCCESS; exit: if (UserInfo != &UserInfoBuf.V) MemFree(UserInfo); if (OwnerInfo != &OwnerInfoBuf.V) MemFree(OwnerInfo); if (GroupInfo != &GroupInfoBuf.V) MemFree(GroupInfo); return Result; }