diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Runtime/r3/nt/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/RTFileQueryFsSizes-nt.cpp | 102 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/RTFileSetMode-r3-nt.cpp | 92 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/RTPathQueryInfo-nt.cpp | 680 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/RTPathSetMode-r3-nt.cpp | 98 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/RTProcQueryParent-r3-nt.cpp | 103 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/direnum-r3-nt.cpp | 1011 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/dirrel-r3-nt.cpp | 651 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/fs-nt.cpp | 279 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/internal-r3-nt.h | 92 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/pathint-nt.cpp | 1161 | ||||
-rw-r--r-- | src/VBox/Runtime/r3/nt/time-nt.cpp | 228 |
12 files changed, 4497 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/nt/Makefile.kup b/src/VBox/Runtime/r3/nt/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/r3/nt/Makefile.kup diff --git a/src/VBox/Runtime/r3/nt/RTFileQueryFsSizes-nt.cpp b/src/VBox/Runtime/r3/nt/RTFileQueryFsSizes-nt.cpp new file mode 100644 index 00000000..63a88211 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTFileQueryFsSizes-nt.cpp @@ -0,0 +1,102 @@ +/* $Id: RTFileQueryFsSizes-nt.cpp $ */ +/** @file + * IPRT - RTFileQueryFsSizes, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include "internal-r3-nt.h" + +#include <iprt/file.h> +#include <iprt/errcore.h> + + + +RTR3DECL(int) RTFileQueryFsSizes(RTFILE hFile, PRTFOFF pcbTotal, RTFOFF *pcbFree, + uint32_t *pcbBlock, uint32_t *pcbSector) +{ + int rc; + + /* + * Get the volume information. + */ + FILE_FS_SIZE_INFORMATION FsSizeInfo; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile((HANDLE)RTFileToNative(hFile), &Ios, + &FsSizeInfo, sizeof(FsSizeInfo), FileFsSizeInformation); + if (NT_SUCCESS(rcNt)) + { + /* + * Calculate the return values. + */ + if (pcbTotal) + { + *pcbTotal = FsSizeInfo.TotalAllocationUnits.QuadPart + * FsSizeInfo.SectorsPerAllocationUnit + * FsSizeInfo.BytesPerSector; + if ( *pcbTotal / FsSizeInfo.SectorsPerAllocationUnit / FsSizeInfo.BytesPerSector + != FsSizeInfo.TotalAllocationUnits.QuadPart) + *pcbTotal = UINT64_MAX; + } + + if (pcbFree) + { + *pcbFree = FsSizeInfo.AvailableAllocationUnits.QuadPart + * FsSizeInfo.SectorsPerAllocationUnit + * FsSizeInfo.BytesPerSector; + if ( *pcbFree / FsSizeInfo.SectorsPerAllocationUnit / FsSizeInfo.BytesPerSector + != FsSizeInfo.AvailableAllocationUnits.QuadPart) + *pcbFree = UINT64_MAX; + } + + rc = VINF_SUCCESS; + if (pcbBlock) + { + *pcbBlock = FsSizeInfo.SectorsPerAllocationUnit * FsSizeInfo.BytesPerSector; + if (*pcbBlock / FsSizeInfo.BytesPerSector != FsSizeInfo.SectorsPerAllocationUnit) + rc = VERR_OUT_OF_RANGE; + } + + if (pcbSector) + *pcbSector = FsSizeInfo.BytesPerSector; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/RTFileSetMode-r3-nt.cpp b/src/VBox/Runtime/r3/nt/RTFileSetMode-r3-nt.cpp new file mode 100644 index 00000000..2ec31d14 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTFileSetMode-r3-nt.cpp @@ -0,0 +1,92 @@ +/* $Id: RTFileSetMode-r3-nt.cpp $ */ +/** @file + * IPRT - RTFileSetMode, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include "internal-r3-nt.h" + +#include <iprt/file.h> +#include <iprt/err.h> + +#include "internal/fs.h" + + +/** + * Common worker for RTFileSetMode, RTPathSetMode and RTDirRelPathSetMode. + * + * @returns IPRT status code. + * @param hNativeFile The NT handle to the file system object. + * @param fMode Valid and normalized file mode mask to set. + */ +DECLHIDDEN(int) rtNtFileSetModeWorker(HANDLE hNativeFile, RTFMODE fMode) +{ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + FILE_BASIC_INFORMATION BasicInfo; + BasicInfo.CreationTime.QuadPart = 0; + BasicInfo.ChangeTime.QuadPart = 0; + BasicInfo.LastAccessTime.QuadPart = 0; + BasicInfo.LastWriteTime.QuadPart = 0; + BasicInfo.FileAttributes = (fMode & ~( RTFS_DOS_NT_ENCRYPTED + | RTFS_DOS_NT_COMPRESSED + | RTFS_DOS_NT_REPARSE_POINT + | RTFS_DOS_NT_SPARSE_FILE + | RTFS_DOS_NT_DEVICE + | RTFS_DOS_DIRECTORY) + & RTFS_DOS_MASK_NT) + >> RTFS_DOS_SHIFT; + Assert(!(BasicInfo.FileAttributes & ~0x31a7U /* FILE_ATTRIBUTE_VALID_SET_FLAGS */)); + if (!BasicInfo.FileAttributes) + BasicInfo.FileAttributes |= FILE_ATTRIBUTE_NORMAL; + + NTSTATUS rcNt = NtSetInformationFile(hNativeFile, &Ios, &BasicInfo, sizeof(BasicInfo), FileBasicInformation); + if (NT_SUCCESS(rcNt)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(rcNt); +} + + +RTDECL(int) RTFileSetMode(RTFILE hFile, RTFMODE fMode) +{ + HANDLE hNative = (HANDLE)RTFileToNative(hFile); + AssertReturn(hNative != RTNT_INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + fMode = rtFsModeNormalize(fMode, NULL, 0, RTFS_TYPE_FILE); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + + return rtNtFileSetModeWorker(hNative, fMode); +} diff --git a/src/VBox/Runtime/r3/nt/RTPathQueryInfo-nt.cpp b/src/VBox/Runtime/r3/nt/RTPathQueryInfo-nt.cpp new file mode 100644 index 00000000..a7b6459b --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTPathQueryInfo-nt.cpp @@ -0,0 +1,680 @@ +/* $Id: RTPathQueryInfo-nt.cpp $ */ +/** @file + * IPRT - RTPathQueryInfo[Ex], Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include "internal-r3-nt.h" + +#include <iprt/path.h> +#include <iprt/err.h> +#include <iprt/time.h> +#include "internal/fs.h" +#include "internal/path.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Helper for comparing a UNICODE_STRING with a string litteral. */ +#define ARE_UNICODE_STRINGS_EQUAL(a_UniStr, a_wszType) \ + ( (a_UniStr)->Length == sizeof(a_wszType) - sizeof(RTUTF16) \ + && memcmp((a_UniStr)->Buffer, a_wszType, sizeof(a_wszType) - sizeof(RTUTF16)) == 0) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +typedef NTSTATUS (NTAPI *PFNNTQUERYFULLATTRIBUTESFILE)(struct _OBJECT_ATTRIBUTES *, struct _FILE_NETWORK_OPEN_INFORMATION *); +extern PFNNTQUERYFULLATTRIBUTESFILE g_pfnNtQueryFullAttributesFile; /* init-win.cpp */ + + +/* ASSUMES FileID comes after ShortName and the structs are identical up to that point. */ +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, NextEntryOffset, FILE_ID_BOTH_DIR_INFORMATION, NextEntryOffset); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileIndex , FILE_ID_BOTH_DIR_INFORMATION, FileIndex ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, CreationTime , FILE_ID_BOTH_DIR_INFORMATION, CreationTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastAccessTime , FILE_ID_BOTH_DIR_INFORMATION, LastAccessTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastWriteTime , FILE_ID_BOTH_DIR_INFORMATION, LastWriteTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ChangeTime , FILE_ID_BOTH_DIR_INFORMATION, ChangeTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EndOfFile , FILE_ID_BOTH_DIR_INFORMATION, EndOfFile ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, AllocationSize , FILE_ID_BOTH_DIR_INFORMATION, AllocationSize ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileAttributes , FILE_ID_BOTH_DIR_INFORMATION, FileAttributes ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileNameLength , FILE_ID_BOTH_DIR_INFORMATION, FileNameLength ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EaSize , FILE_ID_BOTH_DIR_INFORMATION, EaSize ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortNameLength, FILE_ID_BOTH_DIR_INFORMATION, ShortNameLength); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortName , FILE_ID_BOTH_DIR_INFORMATION, ShortName ); + + + +/** + * Splits up an NT path into directory and filename. + * + * @param pNtName The path to split. + * @param pNtParent Where to return the directory path. + * @param pNtFilename Where to return the filename part. + * @param fNoParentDirSlash Whether to make sure the directory path doesn't + * end with a slash (except root). + */ +static void ntPathNtSplitName(UNICODE_STRING const *pNtName, UNICODE_STRING *pNtParent, UNICODE_STRING *pNtFilename, + bool fNoParentDirSlash) +{ + PRTUTF16 pwszBuffer = pNtName->Buffer; + size_t off = pNtName->Length / sizeof(RTUTF16); + + /* Skip trailing slash if present. */ + if ( off > 0 + && pwszBuffer[off - 1] == '\\') + off--; + + /* Find the slash before that. */ + RTUTF16 wc; + while ( off > 0 + && (wc = pwszBuffer[off - 1]) != '\\' + && wc != '/') + off--; + if (off != 0) + { + pNtParent->Buffer = pwszBuffer; + pNtParent->MaximumLength = pNtParent->Length = (USHORT)(off * sizeof(RTUTF16)); + } + else + { + AssertFailed(); /* This is impossible and won't work (NT doesn't know '.' or '..'). */ + /** @todo query the whole path as it is possible relative. Use the buffer for + * temporary name storage. */ + pNtParent->Buffer = L"."; + pNtParent->Length = 1 * sizeof(RTUTF16); + pNtParent->MaximumLength = 2 * sizeof(RTUTF16); + } + + pNtFilename->Buffer = &pwszBuffer[off]; + pNtFilename->Length = pNtName->Length - (USHORT)(off * sizeof(RTUTF16)); + pNtFilename->MaximumLength = pNtName->MaximumLength - (USHORT)(off * sizeof(RTUTF16)); + + while ( fNoParentDirSlash + && pNtParent->Length > sizeof(RTUTF16) + && pNtParent->Buffer[pNtParent->Length / sizeof(RTUTF16) - 1] == '\\') + pNtParent->Length -= sizeof(RTUTF16); +} + + +/** + * Deals with enmAddAttr != RTFSOBJATTRADD_UNIX. + * + * @returns IPRT status code (usually @a rc). + * @param rc The return code. + * @param pObjInfo The info to complete. + * @param enmAddAttr What to complete it with. Caller should fill in + * RTFSOBJATTRADD_UNIX. + */ +static int rtPathNtQueryInfoFillInDummyData(int rc, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) +{ + switch (enmAddAttr) + { + case RTFSOBJATTRADD_UNIX: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + break; + + case RTFSOBJATTRADD_NOTHING: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID; + pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID; + pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; + pObjInfo->Attr.u.EASize.cb = 0; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + rc = VERR_INTERNAL_ERROR; + } + return rc; +} + + +/** + * Deal with getting info about something that could be in a directory object. + * + * @returns IPRT status code + * @param pObjAttr The NT object attribute. + * @param pObjInfo Where to return the info. + * @param enmAddAttr Which extra attributes to get (/fake). + * @param fFlags The flags. + * @param pvBuf Query buffer space. + * @param cbBuf Size of the buffer. ASSUMES lots of space. + * @param rcNtCaller The status code that got us here. + */ +static int rtPathNtQueryInfoInDirectoryObject(OBJECT_ATTRIBUTES *pObjAttr, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, uint32_t fFlags, + void *pvBuf, size_t cbBuf, NTSTATUS rcNtCaller) +{ + RT_NOREF(fFlags); + + /* + * Special case: Root dir. + */ + if ( pObjAttr->RootDirectory == NULL + && pObjAttr->ObjectName->Length == sizeof(RTUTF16) + && pObjAttr->ObjectName->Buffer[0] == '\\') + { + pObjInfo->cbObject = 0; + pObjInfo->cbAllocated = 0; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, 0); + pObjInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777; + return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + + /* + * We must open and scan the parent directory object. + */ + UNICODE_STRING NtDirName; + UNICODE_STRING NtDirEntry; + ntPathNtSplitName(pObjAttr->ObjectName, &NtDirName, &NtDirEntry, true /*fNoParentDirSlash*/); + + while ( NtDirEntry.Length > sizeof(RTUTF16) + && NtDirEntry.Buffer[NtDirEntry.Length / sizeof(RTUTF16) - 1] == '\\') + NtDirEntry.Length -= sizeof(RTUTF16); + + pObjAttr->ObjectName = &NtDirName; + HANDLE hDir = RTNT_INVALID_HANDLE_VALUE; + NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, pObjAttr); + if (NT_SUCCESS(rcNt)) + { + ULONG uObjDirCtx = 0; + for (;;) + { + ULONG cbReturned = 0; + rcNt = NtQueryDirectoryObject(hDir, + pvBuf, + (ULONG)cbBuf, + FALSE /*ReturnSingleEntry */, + FALSE /*RestartScan*/, + &uObjDirCtx, + &cbReturned); + if (!NT_SUCCESS(rcNt)) + break; + + for (POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)pvBuf; + pObjDir->Name.Length != 0; + pObjDir++) + { + if ( pObjDir->Name.Length == NtDirEntry.Length + && memcmp(pObjDir->Name.Buffer, NtDirEntry.Buffer, NtDirEntry.Length) == 0) + { + /* + * Find it. Fill in the info we've got and return (see similar code in direnum-r3-nt.cpp). + */ + NtClose(hDir); + + pObjInfo->cbObject = 0; + pObjInfo->cbAllocated = 0; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, 0); + + if (ARE_UNICODE_STRINGS_EQUAL(&pObjDir->TypeName, L"Directory")) + pObjInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777; + else if (ARE_UNICODE_STRINGS_EQUAL(&pObjDir->TypeName, L"SymbolicLink")) + pObjInfo->Attr.fMode = RTFS_DOS_NT_REPARSE_POINT | RTFS_TYPE_SYMLINK | 0777; + else if (ARE_UNICODE_STRINGS_EQUAL(&pObjDir->TypeName, L"Device")) + pObjInfo->Attr.fMode = RTFS_DOS_NT_DEVICE | RTFS_TYPE_DEV_CHAR | 0666; + else + pObjInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | 0666; + + pObjInfo->Attr.enmAdditional = enmAddAttr; + return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + } + } + + NtClose(hDir); + if (rcNt == STATUS_NO_MORE_FILES || rcNt == STATUS_NO_MORE_ENTRIES || rcNt == STATUS_NO_SUCH_FILE) + return VERR_FILE_NOT_FOUND; + } + else + return RTErrConvertFromNtStatus(rcNtCaller); + return RTErrConvertFromNtStatus(rcNt); +} + + +/** + * Queries information from a file or directory handle. + * + * This is shared between the RTPathQueryInfo, RTFileQueryInfo and + * RTDirQueryInfo code. + * + * @returns IPRT status code. + * @param hFile The handle to query information from. Must have + * the necessary privileges. + * @param pvBuf Pointer to a scratch buffer. + * @param cbBuf The size of the buffer. This must be large + * enough to hold a FILE_ALL_INFORMATION struct. + * @param pObjInfo Where to return information about the handle. + * @param enmAddAttr What extra info to return. + * @param pszPath The path if this is a file (for exe detect). + * @param uReparseTag The reparse tag number (0 if not applicable) for + * symlink detection/whatnot. + */ +DECLHIDDEN(int) rtPathNtQueryInfoFromHandle(HANDLE hFile, void *pvBuf, size_t cbBuf, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, const char *pszPath, ULONG uReparseTag) +{ + Assert(cbBuf >= sizeof(FILE_ALL_INFORMATION)); + + /** @todo Try optimize this for when RTFSOBJATTRADD_UNIX isn't set? */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pvBuf, sizeof(FILE_ALL_INFORMATION), FileAllInformation); + if ( NT_SUCCESS(rcNt) + || rcNt == STATUS_BUFFER_OVERFLOW) + { + FILE_ALL_INFORMATION *pAllInfo = (FILE_ALL_INFORMATION *)pvBuf; + pObjInfo->cbObject = pAllInfo->StandardInformation.EndOfFile.QuadPart; + pObjInfo->cbAllocated = pAllInfo->StandardInformation.AllocationSize.QuadPart; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, pAllInfo->BasicInformation.CreationTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, pAllInfo->BasicInformation.LastAccessTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, pAllInfo->BasicInformation.LastWriteTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, pAllInfo->BasicInformation.ChangeTime.QuadPart); + pObjInfo->Attr.fMode = rtFsModeFromDos( (pAllInfo->BasicInformation.FileAttributes << RTFS_DOS_SHIFT) + & RTFS_DOS_MASK_NT, + pszPath, pszPath ? strlen(pszPath) : 0, uReparseTag, 0); + pObjInfo->Attr.enmAdditional = enmAddAttr; + if (enmAddAttr == RTFSOBJATTRADD_UNIX) + { + pObjInfo->Attr.u.Unix.uid = ~0U; + pObjInfo->Attr.u.Unix.gid = ~0U; + pObjInfo->Attr.u.Unix.cHardlinks = RT_MAX(1, pAllInfo->StandardInformation.NumberOfLinks); + pObjInfo->Attr.u.Unix.INodeIdDevice = 0; /* below */ + pObjInfo->Attr.u.Unix.INodeId = pAllInfo->InternalInformation.IndexNumber.QuadPart; + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + + /* Get the serial number. */ + rcNt = NtQueryVolumeInformationFile(hFile, &Ios, pvBuf, (ULONG)RT_MIN(cbBuf, _2K), FileFsVolumeInformation); + if (NT_SUCCESS(rcNt) || rcNt == STATUS_BUFFER_OVERFLOW) + { + FILE_FS_VOLUME_INFORMATION *pVolInfo = (FILE_FS_VOLUME_INFORMATION *)pvBuf; + pObjInfo->Attr.u.Unix.INodeIdDevice = pVolInfo->VolumeSerialNumber; + } + } + + return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + return RTErrConvertFromNtStatus(rcNt); +} + + +/** + * Worker for RTPathQueryInfoEx and RTDirRelPathQueryInfo. + * + * @returns IPRT status code. + * @param hRootDir The root directory that pNtName is relative to. + * @param pNtName The NT path which we want to query info for. + * @param pObjInfo Where to return the info. + * @param enmAddAttr What additional info to get/fake. + * @param fFlags Query flags (RTPATH_F_XXX). + * @param pszPath The path for detecting executables and such. + * Pass empty string if not applicable/available. + */ +DECLHIDDEN(int) rtPathNtQueryInfoWorker(HANDLE hRootDir, UNICODE_STRING *pNtName, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, uint32_t fFlags, const char *pszPath) +{ + /* + * There are a three different ways of doing this: + * 1. Use NtQueryFullAttributesFile to the get basic file info. + * 2. Open whatever the path points to and use NtQueryInformationFile. + * 3. Open the parent directory and use NtQueryDirectoryFile like RTDirReadEx. + * + * The first two options may fail with sharing violations or access denied, + * in which case we must use the last one as fallback. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt; + OBJECT_ATTRIBUTES ObjAttr; + union + { + FILE_NETWORK_OPEN_INFORMATION NetOpenInfo; + FILE_ALL_INFORMATION AllInfo; + FILE_FS_VOLUME_INFORMATION VolInfo; + FILE_BOTH_DIR_INFORMATION Both; + FILE_ID_BOTH_DIR_INFORMATION BothId; + uint8_t abPadding[sizeof(FILE_ID_BOTH_DIR_INFORMATION) + RTPATH_MAX * sizeof(wchar_t)]; + } uBuf; + + /* + * We can only use the first option if no additional UNIX attribs are + * requested and it isn't a symbolic link. NT directory object + */ + int rc = VINF_TRY_AGAIN; + if ( enmAddAttr != RTFSOBJATTRADD_UNIX + && g_pfnNtQueryFullAttributesFile) + { + InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL); + rcNt = g_pfnNtQueryFullAttributesFile(&ObjAttr, &uBuf.NetOpenInfo); + if (NT_SUCCESS(rcNt)) + { + if (!(uBuf.NetOpenInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + { + pObjInfo->cbObject = uBuf.NetOpenInfo.EndOfFile.QuadPart; + pObjInfo->cbAllocated = uBuf.NetOpenInfo.AllocationSize.QuadPart; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, uBuf.NetOpenInfo.CreationTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, uBuf.NetOpenInfo.LastAccessTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, uBuf.NetOpenInfo.LastWriteTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, uBuf.NetOpenInfo.ChangeTime.QuadPart); + pObjInfo->Attr.fMode = rtFsModeFromDos((uBuf.NetOpenInfo.FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, + pszPath, strlen(pszPath), 0 /*uReparseTag*/, 0); + pObjInfo->Attr.enmAdditional = enmAddAttr; + + return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + } + else if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + || rcNt == STATUS_OBJECT_NAME_INVALID + || rcNt == STATUS_INVALID_PARAMETER) + { + rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt); + if (RT_SUCCESS(rc)) + return rc; + } + else if ( rcNt != STATUS_ACCESS_DENIED + && rcNt != STATUS_SHARING_VIOLATION) + rc = RTErrConvertFromNtStatus(rcNt); + else + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + } + + /* + * Try the 2nd option. We might have to redo this if not following symbolic + * links and the reparse point isn't a symbolic link but a mount point or similar. + * We want to return information about the mounted root directory if we can, not + * the directory in which it was mounted. + */ + if (rc == VINF_TRY_AGAIN) + { + static int volatile g_fReparsePoints = -1; + uint32_t fOptions = FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT; + int fReparsePoints = g_fReparsePoints; + if (fReparsePoints != 0 && !(fFlags & RTPATH_F_FOLLOW_LINK)) + fOptions |= FILE_OPEN_REPARSE_POINT; + + InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL); + rcNt = NtCreateFile(&hFile, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*pcbFile*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + fOptions, + NULL /*pvEaBuffer*/, + 0 /*cbEa*/); + if ( ( rcNt == STATUS_INVALID_PARAMETER + || rcNt == STATUS_INVALID_PARAMETER_9) + && fReparsePoints == -1 + && (fOptions & FILE_OPEN_REPARSE_POINT)) + { + fOptions &= ~FILE_OPEN_REPARSE_POINT; + rcNt = NtCreateFile(&hFile, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*pcbFile*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + fOptions, + NULL /*pvEaBuffer*/, + 0 /*cbEa*/); + if (rcNt != STATUS_INVALID_PARAMETER) + g_fReparsePoints = fReparsePoints = 0; + } + if (NT_SUCCESS(rcNt)) + { + /* Query tag information first in order to try re-open non-symlink reparse points. */ + FILE_ATTRIBUTE_TAG_INFORMATION TagInfo; + rcNt = NtQueryInformationFile(hFile, &Ios, &TagInfo, sizeof(TagInfo), FileAttributeTagInformation); + if (!NT_SUCCESS(rcNt)) + TagInfo.FileAttributes = TagInfo.ReparseTag = 0; + if ( !(TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + || TagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK + || (fFlags & RTPATH_F_FOLLOW_LINK)) + { /* likely */ } + else + { + /* Reparse point that isn't a symbolic link, try follow the reparsing. */ + HANDLE hFile2; + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtCreateFile(&hFile2, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*pcbFile*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*pvEaBuffer*/, + 0 /*cbEa*/); + if (NT_SUCCESS(rcNt)) + { + NtClose(hFile); + hFile = hFile2; + TagInfo.FileAttributes = TagInfo.ReparseTag = 0; + } + } + + /* + * Get the information we need and convert it. + */ + rc = rtPathNtQueryInfoFromHandle(hFile, &uBuf, sizeof(uBuf), pObjInfo, enmAddAttr, pszPath, TagInfo.ReparseTag); + NtClose(hFile); + if (RT_SUCCESS(rc)) + return rc; + + if (RT_FAILURE(rc)) + rc = VINF_TRY_AGAIN; + } + else if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + || rcNt == STATUS_OBJECT_NAME_INVALID + /*|| rcNt == STATUS_INVALID_PARAMETER*/) + { + rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt); + if (RT_SUCCESS(rc)) + return rc; + } + else if ( rcNt != STATUS_ACCESS_DENIED + && rcNt != STATUS_SHARING_VIOLATION) + rc = RTErrConvertFromNtStatus(rcNt); + else + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + } + + /* + * Try the 3rd option if none of the other worked. + * If none of the above worked, try open the directory and enumerate + * the file we're after. This + */ + if (rc == VINF_TRY_AGAIN) + { + /* Split up the name into parent directory path and filename. */ + UNICODE_STRING NtDirName; + UNICODE_STRING NtFilter; + ntPathNtSplitName(pNtName, &NtDirName, &NtFilter, false /*fNoParentDirSlash*/); + + /* Try open the directory. */ + InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, hRootDir, NULL); + rcNt = NtCreateFile(&hFile, + FILE_LIST_DIRECTORY | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*pcbFile*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*pvEaBuffer*/, + 0 /*cbEa*/); + if (NT_SUCCESS(rcNt)) + { + FILE_INFORMATION_CLASS enmInfoClass; + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) > RT_MAKE_U64(0,5) /* > W2K */) + enmInfoClass = FileIdBothDirectoryInformation; /* Introduced in XP, from I can tell. */ + else + enmInfoClass = FileBothDirectoryInformation; + rcNt = NtQueryDirectoryFile(hFile, + NULL /* Event */, + NULL /* ApcRoutine */, + NULL /* ApcContext */, + &Ios, + &uBuf, + RT_MIN(sizeof(uBuf), 0xfff0), + enmInfoClass, + TRUE /*ReturnSingleEntry */, + &NtFilter, + FALSE /*RestartScan */); + if (NT_SUCCESS(rcNt)) + { + pObjInfo->cbObject = uBuf.Both.EndOfFile.QuadPart; + pObjInfo->cbAllocated = uBuf.Both.AllocationSize.QuadPart; + + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, uBuf.Both.CreationTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, uBuf.Both.LastAccessTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, uBuf.Both.LastWriteTime.QuadPart); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, uBuf.Both.ChangeTime.QuadPart); + + pObjInfo->Attr.fMode = rtFsModeFromDos((uBuf.Both.FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, + pszPath, strlen(pszPath), uBuf.Both.EaSize, 0); + + pObjInfo->Attr.enmAdditional = enmAddAttr; + if (enmAddAttr == RTFSOBJATTRADD_UNIX) + { + pObjInfo->Attr.u.Unix.uid = ~0U; + pObjInfo->Attr.u.Unix.gid = ~0U; + pObjInfo->Attr.u.Unix.cHardlinks = 1; + pObjInfo->Attr.u.Unix.INodeIdDevice = 0; /* below */ + pObjInfo->Attr.u.Unix.INodeId = enmInfoClass == FileIdBothDirectoryInformation + ? uBuf.BothId.FileId.QuadPart : 0; + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + + /* Get the serial number. */ + rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &uBuf, RT_MIN(sizeof(uBuf), _2K), + FileFsVolumeInformation); + if (NT_SUCCESS(rcNt)) + pObjInfo->Attr.u.Unix.INodeIdDevice = uBuf.VolInfo.VolumeSerialNumber; + } + + rc = rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + NtClose(hFile); + } + /* + * Quite possibly a object directory. + */ + else if ( rcNt == STATUS_OBJECT_NAME_INVALID /* with trailing slash */ + || rcNt == STATUS_OBJECT_TYPE_MISMATCH /* without trailing slash */ ) + { + InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL); + rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt); + if (RT_FAILURE(rc)) + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + } + + return rc; +} + + +RTR3DECL(int) RTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + AssertMsgReturn( enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING + && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + ("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs), + VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + + /* + * Convert the input path and call common worker. + */ + HANDLE hRootDir; + UNICODE_STRING NtName; + int rc = RTNtPathFromWinUtf8(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + rc = rtPathNtQueryInfoWorker(hRootDir, &NtName, pObjInfo, enmAdditionalAttribs, fFlags, pszPath); + RTNtPathFree(&NtName, &hRootDir); + } + return rc; +} + + +RTR3DECL(int) RTPathQueryInfo(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + return RTPathQueryInfoEx(pszPath, pObjInfo, enmAdditionalAttribs, RTPATH_F_ON_LINK); +} + diff --git a/src/VBox/Runtime/r3/nt/RTPathSetMode-r3-nt.cpp b/src/VBox/Runtime/r3/nt/RTPathSetMode-r3-nt.cpp new file mode 100644 index 00000000..d6fb0444 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTPathSetMode-r3-nt.cpp @@ -0,0 +1,98 @@ +/* $Id: RTPathSetMode-r3-nt.cpp $ */ +/** @file + * IPRT - RTPathSetMode, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FILE +#include "internal-r3-nt.h" + +#include <iprt/path.h> +#include <iprt/err.h> + +#include "internal/fs.h" + + + +RTDECL(int) RTPathSetMode(const char *pszPath, RTFMODE fMode) +{ + fMode = rtFsModeNormalize(fMode, pszPath, 0, 0); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRootDir; + int rc = RTNtPathFromWinUtf8(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + HANDLE hPath = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRootDir, NULL); + + ULONG fOpenOptions = FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT; + //if (fFlags & RTPATH_F_ON_LINK) + // fOpenOptions |= FILE_OPEN_REPARSE_POINT; + NTSTATUS rcNt = NtCreateFile(&hPath, + FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + fOpenOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + rc = rtNtFileSetModeWorker(hPath, fMode); + + rcNt = NtClose(hPath); + if (!NT_SUCCESS(rcNt) && RT_SUCCESS(rc)) + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathFree(&NtName, &hRootDir); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/RTProcQueryParent-r3-nt.cpp b/src/VBox/Runtime/r3/nt/RTProcQueryParent-r3-nt.cpp new file mode 100644 index 00000000..650131d1 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/RTProcQueryParent-r3-nt.cpp @@ -0,0 +1,103 @@ +/* $Id: RTProcQueryParent-r3-nt.cpp $ */ +/** @file + * IPRT - Process, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PROCESS +#include <iprt/nt/nt.h> + +#include <iprt/process.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/errcore.h> + + + +RTR3DECL(int) RTProcQueryParent(RTPROCESS hProcess, PRTPROCESS phParent) +{ + NTSTATUS rcNt; + HANDLE hClose = RTNT_INVALID_HANDLE_VALUE; + HANDLE hNtProc; + + /* + * Open the process. We take a shortcut if it's the current process. + */ + if (hProcess == RTProcSelf()) + hNtProc = NtCurrentProcess(); + else + { + CLIENT_ID ClientId; + ClientId.UniqueProcess = (HANDLE)(uintptr_t)hProcess; + ClientId.UniqueThread = NULL; + + OBJECT_ATTRIBUTES ObjAttrs; + InitializeObjectAttributes(&ObjAttrs, NULL, OBJ_CASE_INSENSITIVE, NULL, NULL); + + rcNt = NtOpenProcess(&hClose, PROCESS_QUERY_LIMITED_INFORMATION, &ObjAttrs, &ClientId); + if (!NT_SUCCESS(rcNt)) + rcNt = NtOpenProcess(&hClose, PROCESS_QUERY_INFORMATION, &ObjAttrs, &ClientId); + if (!NT_SUCCESS(rcNt)) + return RTErrConvertFromNtStatus(rcNt); + hNtProc = hClose; + } + + /* + * Query the information. + */ + int rc; + PROCESS_BASIC_INFORMATION BasicInfo; + ULONG cbIgn; + rcNt = NtQueryInformationProcess(hNtProc, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), &cbIgn); + if (NT_SUCCESS(rcNt)) + { + *phParent = BasicInfo.InheritedFromUniqueProcessId; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + /* + * Clean up. + */ + if (hClose != RTNT_INVALID_HANDLE_VALUE) + NtClose(hClose); + + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/direnum-r3-nt.cpp b/src/VBox/Runtime/r3/nt/direnum-r3-nt.cpp new file mode 100644 index 00000000..0325e842 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/direnum-r3-nt.cpp @@ -0,0 +1,1011 @@ +/* $Id: direnum-r3-nt.cpp $ */ +/** @file + * IPRT - Directory Enumeration, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include "internal-r3-nt.h" + +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/log.h> +#include <iprt/utf16.h> +#include "internal/fs.h" +#include "internal/dir.h" +#include "internal/path.h" +#include "../win/internal-r3-win.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Whether to return a single record (TRUE) or multiple (FALSE). */ +#define RTDIR_NT_SINGLE_RECORD FALSE + +/** Go hard on record chaining (has slight performance impact). */ +#ifdef RT_STRICT +# define RTDIR_NT_STRICT +#endif + + +/* ASSUMES FileID comes after ShortName and the structs are identical up to that point. */ +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, NextEntryOffset, FILE_ID_BOTH_DIR_INFORMATION, NextEntryOffset); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileIndex , FILE_ID_BOTH_DIR_INFORMATION, FileIndex ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, CreationTime , FILE_ID_BOTH_DIR_INFORMATION, CreationTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastAccessTime , FILE_ID_BOTH_DIR_INFORMATION, LastAccessTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastWriteTime , FILE_ID_BOTH_DIR_INFORMATION, LastWriteTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ChangeTime , FILE_ID_BOTH_DIR_INFORMATION, ChangeTime ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EndOfFile , FILE_ID_BOTH_DIR_INFORMATION, EndOfFile ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, AllocationSize , FILE_ID_BOTH_DIR_INFORMATION, AllocationSize ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileAttributes , FILE_ID_BOTH_DIR_INFORMATION, FileAttributes ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileNameLength , FILE_ID_BOTH_DIR_INFORMATION, FileNameLength ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EaSize , FILE_ID_BOTH_DIR_INFORMATION, EaSize ); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortNameLength, FILE_ID_BOTH_DIR_INFORMATION, ShortNameLength); +AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortName , FILE_ID_BOTH_DIR_INFORMATION, ShortName ); + + + +size_t rtDirNativeGetStructSize(const char *pszPath) +{ + NOREF(pszPath); + return sizeof(RTDIRINTERNAL); +} + + +int rtDirNativeOpen(PRTDIRINTERNAL pDir, uintptr_t hRelativeDir, void *pvNativeRelative) +{ + /* + * Convert the filter to UTF-16. + */ + int rc; + pDir->pNtFilterStr = NULL; + if ( pDir->cchFilter > 0 + && pDir->enmFilter == RTDIRFILTER_WINNT) + { + PRTUTF16 pwszTmp; + rc = RTStrToUtf16(pDir->pszFilter, &pwszTmp); + if (RT_FAILURE(rc)) + return rc; + pDir->NtFilterStr.Buffer = pwszTmp; + pDir->NtFilterStr.Length = pDir->NtFilterStr.MaximumLength = (uint16_t)(RTUtf16Len(pwszTmp) * sizeof(RTUTF16)); + pDir->pNtFilterStr = &pDir->NtFilterStr; + } + + /* + * Try open the directory + */ +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + bool fObjDir = false; +#endif + if (hRelativeDir != ~(uintptr_t)0 && pvNativeRelative == NULL) + { + /* Caller already opened it, easy! */ + pDir->hDir = (HANDLE)hRelativeDir; + rc = VINF_SUCCESS; + } + else + { + /* + * If we have to check for reparse points, this gets complicated! + */ + static int volatile g_fReparsePoints = -1; + uint32_t fOptions = FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT; + int fReparsePoints = g_fReparsePoints; + if ( fReparsePoints != 0 + && (pDir->fFlags & RTDIR_F_NO_FOLLOW) + && !pDir->fDirSlash) + fOptions |= FILE_OPEN_REPARSE_POINT; + + ACCESS_MASK fDesiredAccess = FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_TRAVERSE | SYNCHRONIZE; + for (;;) + { + if (pvNativeRelative == NULL) + rc = RTNtPathOpenDir(pDir->pszPath, + fDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE, + fOptions, + OBJ_CASE_INSENSITIVE, + &pDir->hDir, +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + &fObjDir +#else + NULL +#endif + ); + else + rc = RTNtPathOpenDirEx((HANDLE)hRelativeDir, + (struct _UNICODE_STRING *)pvNativeRelative, + fDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE, + fOptions, + OBJ_CASE_INSENSITIVE, + &pDir->hDir, +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + &fObjDir +#else + NULL +#endif + ); + if ( rc == VERR_ACCESS_DENIED /* Seen with c:\windows\system32\com\dmp on w7 & w10 (admin mode). */ + && (fDesiredAccess & FILE_TRAVERSE)) + { + fDesiredAccess &= ~FILE_TRAVERSE; + continue; + } + if ( !(fOptions & FILE_OPEN_REPARSE_POINT) + || (rc != VINF_SUCCESS && rc != VERR_INVALID_PARAMETER) ) + break; + if (rc == VINF_SUCCESS) + { + if (fReparsePoints == -1) + g_fReparsePoints = 1; + + /* + * We now need to check if we opened a symbolic directory link. + * (These can be enumerated, but contains only '.' and '..'.) + */ + FILE_ATTRIBUTE_TAG_INFORMATION TagInfo = { 0, 0 }; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryInformationFile(pDir->hDir, &Ios, &TagInfo, sizeof(TagInfo), FileAttributeTagInformation); + AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + if (!NT_SUCCESS(rcNt)) + TagInfo.FileAttributes = TagInfo.ReparseTag = 0; + if (!(TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + break; + + NtClose(pDir->hDir); + pDir->hDir = RTNT_INVALID_HANDLE_VALUE; + + if (TagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK) + { + rc = VERR_IS_A_SYMLINK; + break; + } + + /* Reparse point that isn't a symbolic link, try follow the reparsing. */ + } + else if (fReparsePoints == -1) + g_fReparsePoints = fReparsePoints = 0; + fOptions &= ~FILE_OPEN_REPARSE_POINT; + } + + } + if (RT_SUCCESS(rc)) + { + /* + * Init data. + */ + pDir->fDataUnread = false; /* spelling it out */ + pDir->uDirDev = 0; +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (fObjDir) + pDir->enmInfoClass = FileMaximumInformation; /* object directory. */ +#endif + } + return rc; +} + + +RTDECL(int) RTDirClose(RTDIR hDir) +{ + PRTDIRINTERNAL pDir = hDir; + + /* + * Validate input. + */ + if (!pDir) + return VERR_INVALID_PARAMETER; + if (pDir->u32Magic != RTDIR_MAGIC) + { + AssertMsgFailed(("Invalid pDir=%p\n", pDir)); + return VERR_INVALID_PARAMETER; + } + + /* + * Close the handle. + */ + pDir->u32Magic = ~RTDIR_MAGIC; + if (pDir->hDir != RTNT_INVALID_HANDLE_VALUE) + { + int rc = RTNtPathClose(pDir->hDir); + AssertRC(rc); + pDir->hDir = RTNT_INVALID_HANDLE_VALUE; + } + RTStrFree(pDir->pszName); + pDir->pszName = NULL; + RTUtf16Free(pDir->NtFilterStr.Buffer); + pDir->NtFilterStr.Buffer = NULL; + RTMemFree(pDir->pabBuffer); + pDir->pabBuffer = NULL; + RTMemFree(pDir); + + return VINF_SUCCESS; +} + + +/** + * Checks the validity of the current record. + * + * @returns IPRT status code + * @param pThis The directory instance data. + */ +static int rtDirNtCheckRecord(PRTDIRINTERNAL pThis) +{ +#if defined(RTDIR_NT_STRICT) || defined(RT_ARCH_X86) +# ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pThis->enmInfoClass != FileMaximumInformation) +# endif + { + uintptr_t uEndAddr; + if (pThis->enmInfoClass == FileIdBothDirectoryInformation) + uEndAddr = (uintptr_t)&pThis->uCurData.pBothId->FileName[0]; + else + uEndAddr = (uintptr_t)&pThis->uCurData.pBoth->FileName[0]; + +# ifdef RT_ARCH_X86 + /* Workaround for NT 3.1 bug where FAT returns a too short buffer length. + Including all NT 3.x versions in case it bug was fixed till NT 4. */ + uintptr_t const uEndBuffer = (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer]; + if ( uEndAddr < uEndBuffer + && uEndAddr + pThis->uCurData.pBoth->FileNameLength <= uEndBuffer) + { /* likely */ } + else if ( ( g_enmWinVer == kRTWinOSType_NT310 + || g_enmWinVer == kRTWinOSType_NT350 // not sure when it was fixed... + || g_enmWinVer == kRTWinOSType_NT351) + && pThis->enmInfoClass == FileBothDirectoryInformation) + { + size_t cbLeft = (uintptr_t)&pThis->pabBuffer[pThis->cbBufferAlloc] - (uintptr_t)pThis->uCurData.pBoth; + if ( cbLeft >= RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName) + && pThis->uCurData.pBoth->FileNameLength > 0 + && cbLeft >= RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName) + pThis->uCurData.pBoth->FileNameLength) + { + pThis->cbBuffer = ((uintptr_t)&pThis->uCurData.pBoth->FileName[0] + pThis->uCurData.pBoth->FileNameLength) + - (uintptr_t)&pThis->pabBuffer[0]; + } + } +# endif + +# ifdef RTDIR_NT_STRICT + AssertReturn(uEndAddr < (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer], VERR_IO_GEN_FAILURE); + AssertReturn(pThis->uCurData.pBoth->FileNameLength < _64K, VERR_FILENAME_TOO_LONG); + AssertReturn((pThis->uCurData.pBoth->FileNameLength & 1) == 0, VERR_IO_GEN_FAILURE); + + uEndAddr += pThis->uCurData.pBoth->FileNameLength; + AssertReturn(uEndAddr <= (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer], VERR_IO_GEN_FAILURE); + + AssertReturn((unsigned)pThis->uCurData.pBoth->ShortNameLength <= sizeof(pThis->uCurData.pBoth->ShortName), + VERR_IO_GEN_FAILURE); +# endif + } +#else + RT_NOREF_PV(pThis); +#endif + + return VINF_SUCCESS; +} + + +/** + * Advances the buffer pointer. + * + * @param pThis The directory instance data. + */ +static int rtDirNtAdvanceBuffer(PRTDIRINTERNAL pThis) +{ + int rc; + +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pThis->enmInfoClass == FileMaximumInformation) + { + pThis->uCurData.pObjDir++; + pThis->fDataUnread = pThis->uCurData.pObjDir->Name.Length != 0; + return VINF_SUCCESS; + } +#endif + + pThis->fDataUnread = false; + + uint32_t const offNext = pThis->uCurData.pBoth->NextEntryOffset; + if (offNext == 0) + return VINF_SUCCESS; + +#ifdef RTDIR_NT_STRICT + /* Make sure the next-record offset is beyond the current record. */ + size_t cbRec; + if (pThis->enmInfoClass == FileIdBothDirectoryInformation) + cbRec = RT_UOFFSETOF(FILE_ID_BOTH_DIR_INFORMATION, FileName); + else + cbRec = RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName); + cbRec += pThis->uCurData.pBoth->FileNameLength; + AssertReturn(offNext >= cbRec, VERR_IO_GEN_FAILURE); +#endif + pThis->uCurData.u += offNext; + + rc = rtDirNtCheckRecord(pThis); + pThis->fDataUnread = RT_SUCCESS(rc); + return rc; +} + + +/** + * Fetches more data from the file system. + * + * @returns IPRT status code + * @param pThis The directory instance data. + */ +static int rtDirNtFetchMore(PRTDIRINTERNAL pThis) +{ + Assert(!pThis->fDataUnread); + + /* + * Allocate the buffer the first time around. + * We do this in lazy fashion as some users of RTDirOpen will not actually + * list any files, just open it for various reasons. + * + * We also reduce the buffer size for networked devices as the windows 7-8.1, + * server 2012, ++ CIFS servers or/and IFSes screws up buffers larger than 64KB. + * There is an alternative hack below, btw. We'll leave both in for now. + */ + bool fFirst = false; + if (!pThis->pabBuffer) + { + pThis->cbBufferAlloc = _256K; + if (true) /** @todo skip for known local devices, like the boot device? */ + { + IO_STATUS_BLOCK Ios2 = RTNT_IO_STATUS_BLOCK_INITIALIZER; + FILE_FS_DEVICE_INFORMATION Info = { 0, 0 }; + NTSTATUS rcNt2 = NtQueryVolumeInformationFile(pThis->hDir, &Ios2, &Info, sizeof(Info), FileFsDeviceInformation); + if ( !NT_SUCCESS(rcNt2) + || (Info.Characteristics & FILE_REMOTE_DEVICE) + || Info.DeviceType == FILE_DEVICE_NETWORK + || Info.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM + || Info.DeviceType == FILE_DEVICE_NETWORK_REDIRECTOR + || Info.DeviceType == FILE_DEVICE_SMB) + pThis->cbBufferAlloc = _64K; + } + + fFirst = false; + pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc); + if (!pThis->pabBuffer) + { + do + { + pThis->cbBufferAlloc /= 4; + pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc); + } while (pThis->pabBuffer == NULL && pThis->cbBufferAlloc > _4K); + if (!pThis->pabBuffer) + return VERR_NO_MEMORY; + } + + /* + * Also try determining the device number. + */ + PFILE_FS_VOLUME_INFORMATION pVolInfo = (PFILE_FS_VOLUME_INFORMATION)pThis->pabBuffer; + pVolInfo->VolumeSerialNumber = 0; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile(pThis->hDir, &Ios, + pVolInfo, RT_MIN(_2K, pThis->cbBufferAlloc), + FileFsVolumeInformation); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + pThis->uDirDev = pVolInfo->VolumeSerialNumber; + else + pThis->uDirDev = 0; + AssertCompile(sizeof(pThis->uDirDev) == sizeof(pVolInfo->VolumeSerialNumber)); + /** @todo Grow RTDEV to 64-bit and add low dword of VolumeCreationTime to the top of uDirDev. */ + } + + /* + * Read more. + */ + NTSTATUS rcNt; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + if (pThis->enmInfoClass != (FILE_INFORMATION_CLASS)0) + { +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pThis->enmInfoClass == FileMaximumInformation) + { + Ios.Information = 0; + Ios.Status = rcNt = NtQueryDirectoryObject(pThis->hDir, + pThis->pabBuffer, + pThis->cbBufferAlloc, + RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, + pThis->fRestartScan, + &pThis->uObjDirCtx, + (PULONG)&Ios.Information); + } + else +#endif + rcNt = NtQueryDirectoryFile(pThis->hDir, + NULL /* Event */, + NULL /* ApcRoutine */, + NULL /* ApcContext */, + &Ios, + pThis->pabBuffer, + pThis->cbBufferAlloc, + pThis->enmInfoClass, + RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, + pThis->pNtFilterStr, + pThis->fRestartScan); + } + else + { + /* + * The first time around we have to figure which info class we can use + * as well as the right buffer size. We prefer an info class which + * gives us file IDs (Vista+ IIRC) and we prefer large buffers (for long + * ReFS file names and such), but we'll settle for whatever works... + * + * The windows 7 thru 8.1 CIFS servers have been observed to have + * trouble with large buffers, but weirdly only when listing large + * directories. Seems 0x10000 is the max. (Samba does not exhibit + * these problems, of course.) + * + * This complicates things. The buffer size issues causes an + * STATUS_INVALID_PARAMETER error. Now, you would expect the lack of + * FileIdBothDirectoryInformation support to return + * STATUS_INVALID_INFO_CLASS, but I'm not entirely sure if we can 100% + * depend on third IFSs to get that right. Nor, am I entirely confident + * that we can depend on them to check the class before the buffer size. + * + * Thus the mess. + */ + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) > RT_MAKE_U64(0,5) /* > W2K */) + pThis->enmInfoClass = FileIdBothDirectoryInformation; /* Introduced in XP, from I can tell. */ + else + pThis->enmInfoClass = FileBothDirectoryInformation; + rcNt = NtQueryDirectoryFile(pThis->hDir, + NULL /* Event */, + NULL /* ApcRoutine */, + NULL /* ApcContext */, + &Ios, + pThis->pabBuffer, + pThis->cbBufferAlloc, + pThis->enmInfoClass, + RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, + pThis->pNtFilterStr, + pThis->fRestartScan); + if (NT_SUCCESS(rcNt)) + { /* likely */ } + else + { + bool fRestartScan = pThis->fRestartScan; + for (unsigned iRetry = 0; iRetry < 2; iRetry++) + { + if ( rcNt == STATUS_INVALID_INFO_CLASS + || rcNt == STATUS_INVALID_PARAMETER_8 + || iRetry != 0) + pThis->enmInfoClass = FileBothDirectoryInformation; + + uint32_t cbBuffer = pThis->cbBufferAlloc; + if ( rcNt == STATUS_INVALID_PARAMETER + || rcNt == STATUS_INVALID_PARAMETER_7 + || rcNt == STATUS_INVALID_NETWORK_RESPONSE + || iRetry != 0) + { + cbBuffer = RT_MIN(cbBuffer / 2, _64K); + fRestartScan = true; + } + + for (;;) + { + rcNt = NtQueryDirectoryFile(pThis->hDir, + NULL /* Event */, + NULL /* ApcRoutine */, + NULL /* ApcContext */, + &Ios, + pThis->pabBuffer, + cbBuffer, + pThis->enmInfoClass, + RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, + pThis->pNtFilterStr, + fRestartScan); + if ( NT_SUCCESS(rcNt) + || cbBuffer == pThis->cbBufferAlloc + || cbBuffer <= sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260) + break; + + /* Reduce the buffer size agressivly and try again. We fall back to + FindFirstFile values for the final lap. This means we'll do 4 rounds + with the current initial buffer size (64KB, 8KB, 1KB, 0x278/0x268). */ + cbBuffer /= 8; + if (cbBuffer < 1024) + cbBuffer = pThis->enmInfoClass == FileIdBothDirectoryInformation + ? sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260 + : sizeof(*pThis->uCurData.pBoth) + sizeof(WCHAR) * 260; + } + if (NT_SUCCESS(rcNt)) + { + pThis->cbBufferAlloc = cbBuffer; + break; + } + } + } + } + if (!NT_SUCCESS(rcNt)) + { + /* Note! VBoxSVR and CIFS file systems both ends up with STATUS_NO_SUCH_FILE here instead of STATUS_NO_MORE_FILES. */ + if (rcNt == STATUS_NO_MORE_FILES || rcNt == STATUS_NO_MORE_ENTRIES || rcNt == STATUS_NO_SUCH_FILE) + return VERR_NO_MORE_FILES; + return RTErrConvertFromNtStatus(rcNt); + } + pThis->fRestartScan = false; + AssertMsg( Ios.Information + > (pThis->enmInfoClass == FileMaximumInformation ? sizeof(*pThis->uCurData.pObjDir) : sizeof(*pThis->uCurData.pBoth)), + ("Ios.Information=%#x\n", Ios.Information)); + + /* + * Set up the data members. + */ + pThis->uCurData.u = (uintptr_t)pThis->pabBuffer; + pThis->cbBuffer = Ios.Information; + + int rc = rtDirNtCheckRecord(pThis); + pThis->fDataUnread = RT_SUCCESS(rc); + + return rc; +} + + +/** + * Converts the name from UTF-16 to UTF-8. + * + * Fortunately, the names are relative to the directory, so we won't have to do + * any sweaty path style coversion. :-) + * + * @returns IPRT status code + * @param pThis The directory instance data. + * @param cbName The file name length in bytes. + * @param pwsName The file name, not terminated. + */ +static int rtDirNtConvertName(PRTDIRINTERNAL pThis, uint32_t cbName, PCRTUTF16 pwsName) +{ + int rc = RTUtf16ToUtf8Ex(pwsName, cbName / 2, &pThis->pszName, pThis->cbNameAlloc, &pThis->cchName); + if (RT_SUCCESS(rc)) + { + if (!pThis->cbNameAlloc) + pThis->cbNameAlloc = pThis->cchName + 1; + } + else if (rc == VERR_BUFFER_OVERFLOW) + { + RTStrFree(pThis->pszName); + pThis->pszName = NULL; + pThis->cbNameAlloc = 0; + + rc = RTUtf16ToUtf8Ex(pwsName, cbName / 2, &pThis->pszName, pThis->cbNameAlloc, &pThis->cchName); + if (RT_SUCCESS(rc)) + pThis->cbNameAlloc = pThis->cchName + 1; + } + Assert(RT_SUCCESS(rc) ? pThis->pszName != NULL : pThis->pszName == NULL); + return rc; +} + + +/** + * Converts the name of the current record. + * + * @returns IPRT status code. + * @param pThis The directory instance data. + */ +static int rtDirNtConvertCurName(PRTDIRINTERNAL pThis) +{ + switch (pThis->enmInfoClass) + { + case FileIdBothDirectoryInformation: + return rtDirNtConvertName(pThis, pThis->uCurData.pBothId->FileNameLength, pThis->uCurData.pBothId->FileName); + case FileBothDirectoryInformation: + return rtDirNtConvertName(pThis, pThis->uCurData.pBoth->FileNameLength, pThis->uCurData.pBoth->FileName); +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + case FileMaximumInformation: + return rtDirNtConvertName(pThis, pThis->uCurData.pObjDir->Name.Length, pThis->uCurData.pObjDir->Name.Buffer); +#endif + + default: + AssertFailedReturn(VERR_INTERNAL_ERROR_3); + } +} + + +RTDECL(int) RTDirRead(RTDIR hDir, PRTDIRENTRY pDirEntry, size_t *pcbDirEntry) +{ + PRTDIRINTERNAL pDir = hDir; + int rc; + + /* + * Validate input. + */ + AssertPtrReturn(pDir, VERR_INVALID_POINTER); + AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER); + size_t cbDirEntry = sizeof(*pDirEntry); + if (pcbDirEntry) + { + cbDirEntry = *pcbDirEntry; + AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRY, szName[2]), + ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRY, szName[2])), + VERR_INVALID_PARAMETER); + } + + /* + * Fetch data? + */ + if (!pDir->fDataUnread) + { + rc = rtDirNtFetchMore(pDir); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Convert the filename to UTF-8. + */ + rc = rtDirNtConvertCurName(pDir); + if (RT_FAILURE(rc)) + return rc; + + /* + * Check if we've got enough space to return the data. + */ + const char *pszName = pDir->pszName; + const size_t cchName = pDir->cchName; + const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRY, szName[1]) + cchName; + if (pcbDirEntry) + *pcbDirEntry = cbRequired; + if (cbRequired > cbDirEntry) + return VERR_BUFFER_OVERFLOW; + + /* + * Setup the returned data. + */ + pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName); + memcpy(pDirEntry->szName, pszName, cchName + 1); + + pDirEntry->INodeId = pDir->enmInfoClass == FileIdBothDirectoryInformation + ? pDir->uCurData.pBothId->FileId.QuadPart : 0; + +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pDir->enmInfoClass != FileMaximumInformation) +#endif + { + switch ( pDir->uCurData.pBoth->FileAttributes + & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) + { + default: + AssertFailed(); + case 0: + pDirEntry->enmType = RTDIRENTRYTYPE_FILE; + break; + + case FILE_ATTRIBUTE_DIRECTORY: + pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY; + break; + + case FILE_ATTRIBUTE_REPARSE_POINT: + case FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY: + /* EaSize is here reused for returning the repharse tag value. */ + if (pDir->uCurData.pBoth->EaSize == IO_REPARSE_TAG_SYMLINK) + pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK; + break; + } + } +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + else + { + pDirEntry->enmType = RTDIRENTRYTYPE_UNKNOWN; + if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("Directory"))) + pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY; + else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("SymbolicLink"))) + pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK; + } +#endif + + return rtDirNtAdvanceBuffer(pDir); +} + + +RTDECL(int) RTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, + RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) +{ + PRTDIRINTERNAL pDir = hDir; + int rc; + + /* + * Validate input. + */ + AssertPtrReturn(pDir, VERR_INVALID_POINTER); + AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER); + + AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + VERR_INVALID_PARAMETER); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + size_t cbDirEntry = sizeof(*pDirEntry); + if (pcbDirEntry) + { + cbDirEntry = *pcbDirEntry; + AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRYEX, szName[2]), + ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2])), + VERR_INVALID_PARAMETER); + } + + /* + * Fetch data? + */ + if (!pDir->fDataUnread) + { + rc = rtDirNtFetchMore(pDir); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Convert the filename to UTF-8. + */ + rc = rtDirNtConvertCurName(pDir); + if (RT_FAILURE(rc)) + return rc; + + /* + * Check if we've got enough space to return the data. + */ + const char *pszName = pDir->pszName; + const size_t cchName = pDir->cchName; + const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRYEX, szName[1]) + cchName; + if (pcbDirEntry) + *pcbDirEntry = cbRequired; + if (cbRequired > cbDirEntry) + return VERR_BUFFER_OVERFLOW; + + /* + * Setup the returned data. + */ + PFILE_BOTH_DIR_INFORMATION pBoth = pDir->uCurData.pBoth; + + pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName); + memcpy(pDirEntry->szName, pszName, cchName + 1); + memset(pDirEntry->wszShortName, 0, sizeof(pDirEntry->wszShortName)); +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pDir->enmInfoClass != FileMaximumInformation) +#endif + { + uint8_t cbShort = pBoth->ShortNameLength; + if (cbShort > 0) + { + AssertStmt(cbShort < sizeof(pDirEntry->wszShortName), cbShort = sizeof(pDirEntry->wszShortName) - 2); + memcpy(pDirEntry->wszShortName, pBoth->ShortName, cbShort); + pDirEntry->cwcShortName = cbShort / 2; + } + else + pDirEntry->cwcShortName = 0; + + pDirEntry->Info.cbObject = pBoth->EndOfFile.QuadPart; + pDirEntry->Info.cbAllocated = pBoth->AllocationSize.QuadPart; + + Assert(sizeof(uint64_t) == sizeof(pBoth->CreationTime)); + RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, pBoth->CreationTime.QuadPart); + RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, pBoth->LastAccessTime.QuadPart); + RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, pBoth->LastWriteTime.QuadPart); + RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, pBoth->ChangeTime.QuadPart); + + pDirEntry->Info.Attr.fMode = rtFsModeFromDos((pBoth->FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, + pszName, cchName, pBoth->EaSize, 0); + } +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + else + { + pDirEntry->cwcShortName = 0; + pDirEntry->Info.cbObject = 0; + pDirEntry->Info.cbAllocated = 0; + RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, 0); + RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, 0); + RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, 0); + RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, 0); + + if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("Directory"))) + pDirEntry->Info.Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777; + else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("SymbolicLink"))) + pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_REPARSE_POINT | RTFS_TYPE_SYMLINK | 0777; + else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length, + RT_STR_TUPLE("Device"))) + pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_DEVICE | RTFS_TYPE_DEV_CHAR | 0666; + else + pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | 0666; + } +#endif + + /* + * Requested attributes (we cannot provide anything actually). + */ + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_EASIZE: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if (pDir->enmInfoClass == FileMaximumInformation) + pDirEntry->Info.Attr.u.EASize.cb = 0; + else +#endif + pDirEntry->Info.Attr.u.EASize.cb = pBoth->EaSize; + break; + + case RTFSOBJATTRADD_UNIX: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + pDirEntry->Info.Attr.u.Unix.uid = NIL_RTUID; + pDirEntry->Info.Attr.u.Unix.gid = NIL_RTGID; + pDirEntry->Info.Attr.u.Unix.cHardlinks = 1; + pDirEntry->Info.Attr.u.Unix.INodeIdDevice = pDir->uDirDev; + pDirEntry->Info.Attr.u.Unix.INodeId = 0; + if ( pDir->enmInfoClass == FileIdBothDirectoryInformation + && pDir->uCurData.pBothId->FileId.QuadPart != UINT64_MAX) + pDirEntry->Info.Attr.u.Unix.INodeId = pDir->uCurData.pBothId->FileId.QuadPart; + pDirEntry->Info.Attr.u.Unix.fFlags = 0; + pDirEntry->Info.Attr.u.Unix.GenerationId = 0; + pDirEntry->Info.Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_NOTHING: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pDirEntry->Info.Attr.u.UnixOwner.uid = NIL_RTUID; + pDirEntry->Info.Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pDirEntry->Info.Attr.u.UnixGroup.gid = NIL_RTGID; + pDirEntry->Info.Attr.u.UnixGroup.szName[0] = '\0'; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR; + } + + /* + * Follow links if requested. + */ + if ( (fFlags & RTPATH_F_FOLLOW_LINK) + && RTFS_IS_SYMLINK(fFlags)) + { + /** @todo Symlinks: Find[First|Next]FileW will return info about + the link, so RTPATH_F_FOLLOW_LINK is not handled correctly. */ + } + + /* + * Finally advance the buffer. + */ + return rtDirNtAdvanceBuffer(pDir); +} + + +RTDECL(int) RTDirRewind(RTDIR hDir) +{ + /* + * Validate and digest input. + */ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * The work is done on the next call to rtDirNtFetchMore. + */ + pThis->fRestartScan = true; + pThis->fDataUnread = false; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTDirQueryInfo(RTDIR hDir, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) +{ + PRTDIRINTERNAL pDir = hDir; + AssertPtrReturn(pDir, VERR_INVALID_POINTER); + AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST, + VERR_INVALID_PARAMETER); + + if (pDir->enmInfoClass == FileMaximumInformation) + { + /* + * Directory object (see similar code above and rtPathNtQueryInfoInDirectoryObject). + */ + pObjInfo->cbObject = 0; + pObjInfo->cbAllocated = 0; + RTTimeSpecSetNtTime(&pObjInfo->BirthTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->AccessTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, 0); + RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, 0); + pObjInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777; + pObjInfo->Attr.enmAdditional = enmAdditionalAttribs; + switch (enmAdditionalAttribs) + { + case RTFSOBJATTRADD_NOTHING: + case RTFSOBJATTRADD_UNIX: + pObjInfo->Attr.u.Unix.uid = NIL_RTUID; + pObjInfo->Attr.u.Unix.gid = NIL_RTGID; + pObjInfo->Attr.u.Unix.cHardlinks = 1; + pObjInfo->Attr.u.Unix.INodeIdDevice = pDir->uDirDev; + pObjInfo->Attr.u.Unix.INodeId = 0; + pObjInfo->Attr.u.Unix.fFlags = 0; + pObjInfo->Attr.u.Unix.GenerationId = 0; + pObjInfo->Attr.u.Unix.Device = 0; + break; + + case RTFSOBJATTRADD_EASIZE: + pObjInfo->Attr.u.EASize.cb = 0; + break; + + case RTFSOBJATTRADD_UNIX_OWNER: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; + pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID; + pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */ + break; + + case RTFSOBJATTRADD_UNIX_GROUP: + pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; + pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID; + pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; + break; + + default: + AssertMsgFailed(("Impossible!\n")); + return VERR_INTERNAL_ERROR_2; + } + return VINF_SUCCESS; + } + + /* + * Regular directory file. + */ + uint8_t abBuf[_2K]; + return rtPathNtQueryInfoFromHandle(pDir->hDir, abBuf, sizeof(abBuf), pObjInfo, enmAdditionalAttribs, "", 0); +} + diff --git a/src/VBox/Runtime/r3/nt/dirrel-r3-nt.cpp b/src/VBox/Runtime/r3/nt/dirrel-r3-nt.cpp new file mode 100644 index 00000000..a4130725 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/dirrel-r3-nt.cpp @@ -0,0 +1,651 @@ +/* $Id: dirrel-r3-nt.cpp $ */ +/** @file + * IPRT - Directory relative base APIs, NT implementation + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DIR +#include <iprt/dir.h> +#include "internal-r3-nt.h" + +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/symlink.h> +#include <iprt/utf16.h> +#include "internal/dir.h" +#include "internal/file.h" +#include "internal/fs.h" +#include "internal/path.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Getst the RTNTPATHRELATIVEASCENT value for RTNtPathRelativeFromUtf8. */ +#define RTDIRREL_NT_GET_ASCENT(a_pThis) \ + ( !(pThis->fFlags & RTDIR_F_DENY_ASCENT) ? kRTNtPathRelativeAscent_Allow : kRTNtPathRelativeAscent_Fail ) + + + +/** + * Helper that builds a full path for a directory relative path. + * + * @returns IPRT status code. + * @param pThis The directory. + * @param pszPathDst The destination buffer. + * @param cbPathDst The size of the destination buffer. + * @param pszRelPath The relative path. + */ +static int rtDirRelBuildFullPath(PRTDIRINTERNAL pThis, char *pszPathDst, size_t cbPathDst, const char *pszRelPath) +{ + AssertMsgReturn(!RTPathStartsWithRoot(pszRelPath), ("pszRelPath='%s'\n", pszRelPath), VERR_PATH_IS_NOT_RELATIVE); + + /* + * Let's hope we can avoid checking for ascension. + * + * Note! We don't take symbolic links into account here. That can be + * done later if desired. + */ + if ( !(pThis->fFlags & RTDIR_F_DENY_ASCENT) + || strstr(pszRelPath, "..") == NULL) + { + size_t const cchRelPath = strlen(pszRelPath); + size_t const cchDirPath = pThis->cchPath; + if (cchDirPath + cchRelPath < cbPathDst) + { + memcpy(pszPathDst, pThis->pszPath, cchDirPath); + memcpy(&pszPathDst[cchDirPath], pszRelPath, cchRelPath); + pszPathDst[cchDirPath + cchRelPath] = '\0'; + return VINF_SUCCESS; + } + return VERR_FILENAME_TOO_LONG; + } + + /* + * Calc the absolute path using the directory as a base, then check if the result + * still starts with the full directory path. + * + * This ASSUMES that pThis->pszPath is an absolute path. + */ + int rc = RTPathAbsEx(pThis->pszPath, pszRelPath, RTPATH_STR_F_STYLE_HOST, pszPathDst, &cbPathDst); + if (RT_SUCCESS(rc)) + { + if (RTPathStartsWith(pszPathDst, pThis->pszPath)) + return VINF_SUCCESS; + return VERR_PATH_NOT_FOUND; + } + return rc; +} + + +/* + * + * + * RTFile stuff. + * RTFile stuff. + * RTFile stuff. + * + * + */ + + +RTDECL(int) RTDirRelFileOpen(RTDIR hDir, const char *pszRelFilename, uint64_t fOpen, PRTFILE phFile) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Validate and convert flags. + */ + uint32_t fDesiredAccess; + uint32_t fObjAttribs; + uint32_t fFileAttribs; + uint32_t fShareAccess; + uint32_t fCreateDisposition; + uint32_t fCreateOptions; + int rc = rtFileNtValidateAndConvertFlags(fOpen, &fDesiredAccess, &fObjAttribs, &fFileAttribs, + &fShareAccess, &fCreateDisposition, &fCreateOptions); + if (RT_SUCCESS(rc)) + { + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelFilename, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, fObjAttribs, hRoot, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + fFileAttribs, + fShareAccess, + fCreateDisposition, + fCreateOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + rc = RTFileFromNative(phFile, (uintptr_t)hFile); + if (RT_FAILURE(rc)) + NtClose(hFile); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + RTNtPathFree(&NtName, NULL); + } + } + return rc; +} + + + +/* + * + * + * RTDir stuff. + * RTDir stuff. + * RTDir stuff. + * + * + */ + + +/** + * Helper for cooking up a path string for rtDirOpenRelativeOrHandle. + * + * @returns IPRT status code. + * @param pszDst The destination buffer. + * @param cbDst The size of the destination buffer. + * @param pThis The directory this is relative to. + * @param pNtPath The NT path with a possibly relative path. + * @param fRelative Whether @a pNtPath is relative or not. + * @param pszPath The input path. + */ +static int rtDirRelJoinPathForDirOpen(char *pszDst, size_t cbDst, PRTDIRINTERNAL pThis, + PUNICODE_STRING pNtPath, bool fRelative, const char *pszPath) +{ + int rc; + if (fRelative) + { + size_t cchRel = 0; + rc = RTUtf16CalcUtf8LenEx(pNtPath->Buffer, pNtPath->Length / sizeof(RTUTF16), &cchRel); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (pThis->cchPath + cchRel < cbDst) + { + size_t cchBase = pThis->cchPath; + memcpy(pszDst, pThis->pszPath, cchBase); + pszDst += cchBase; + cbDst -= cchBase; + rc = RTUtf16ToUtf8Ex(pNtPath->Buffer, pNtPath->Length / sizeof(RTUTF16), &pszDst, cbDst, NULL); + } + else + rc = VERR_FILENAME_TOO_LONG; + } + } + else + { + /** @todo would be better to convert pNtName to DOS/WIN path here, + * as it is absolute and doesn't need stuff resolved. */ + rc = RTPathJoin(pszDst, cbDst, pThis->pszPath, pszPath); + } + return rc; +} + +RTDECL(int) RTDirRelDirOpen(RTDIR hDir, const char *pszDir, RTDIR *phDir) +{ + return RTDirRelDirOpenFiltered(hDir, pszDir, RTDIRFILTER_NONE, 0 /*fFlags*/, phDir); +} + + +RTDECL(int) RTDirRelDirOpenFiltered(RTDIR hDir, const char *pszDirAndFilter, RTDIRFILTER enmFilter, + uint32_t fFlags, RTDIR *phDir) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszDirAndFilter, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + char szAbsDirAndFilter[RTPATH_MAX]; + rc = rtDirRelJoinPathForDirOpen(szAbsDirAndFilter, sizeof(szAbsDirAndFilter), pThis, + &NtName, hRoot != NULL, pszDirAndFilter); + if (RT_SUCCESS(rc)) + { + /* Drop the filter from the NT name. */ + switch (enmFilter) + { + case RTDIRFILTER_NONE: + break; + case RTDIRFILTER_WINNT: + case RTDIRFILTER_UNIX: + case RTDIRFILTER_UNIX_UPCASED: + { + size_t cwc = NtName.Length / sizeof(RTUTF16); + while ( cwc > 0 + && NtName.Buffer[cwc - 1] != '\\') + cwc--; + NtName.Buffer[cwc] = '\0'; + NtName.Length = (uint16_t)(cwc * sizeof(RTUTF16)); + break; + } + default: + AssertFailedBreak(); + } + + rc = rtDirOpenRelativeOrHandle(phDir, szAbsDirAndFilter, enmFilter, fFlags, (uintptr_t)hRoot, &NtName); + } + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +RTDECL(int) RTDirRelDirCreate(RTDIR hDir, const char *pszRelPath, RTFMODE fMode, uint32_t fCreate, RTDIR *phSubDir) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fCreate & ~RTDIRCREATE_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + fMode = rtFsModeNormalize(fMode, pszRelPath, 0, RTFS_TYPE_DIRECTORY); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + AssertPtrNullReturn(phSubDir, VERR_INVALID_POINTER); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelPath, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + HANDLE hNewDir = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRoot, NULL); + + ULONG fDirAttribs = (fCreate & RTFS_DOS_MASK_NT) >> RTFS_DOS_SHIFT; + if (!(fCreate & RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET)) + fDirAttribs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + if (!fDirAttribs) + fDirAttribs = FILE_ATTRIBUTE_NORMAL; + + NTSTATUS rcNt = NtCreateFile(&hNewDir, + phSubDir + ? FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE + : SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + fDirAttribs, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_CREATE, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + + /* Just in case someone takes offence at FILE_ATTRIBUTE_NOT_CONTENT_INDEXED. */ + if ( ( rcNt == STATUS_INVALID_PARAMETER + || rcNt == STATUS_INVALID_PARAMETER_7) + && (fDirAttribs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) + && (fCreate & RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL) ) + { + fDirAttribs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + if (!fDirAttribs) + fDirAttribs = FILE_ATTRIBUTE_NORMAL; + rcNt = NtCreateFile(&hNewDir, + phSubDir + ? FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE + : SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + fDirAttribs, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_CREATE, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + } + + if (NT_SUCCESS(rcNt)) + { + if (!phSubDir) + { + NtClose(hNewDir); + rc = VINF_SUCCESS; + } + else + { + char szAbsDirAndFilter[RTPATH_MAX]; + rc = rtDirRelJoinPathForDirOpen(szAbsDirAndFilter, sizeof(szAbsDirAndFilter), pThis, + &NtName, hRoot != NULL, pszRelPath); + if (RT_SUCCESS(rc)) + rc = rtDirOpenRelativeOrHandle(phSubDir, pszRelPath, RTDIRFILTER_NONE, 0 /*fFlags*/, + (uintptr_t)hNewDir, NULL /*pvNativeRelative*/); + if (RT_FAILURE(rc)) + NtClose(hNewDir); + } + } + else + rc = RTErrConvertFromNtStatus(rcNt); + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +RTDECL(int) RTDirRelDirRemove(RTDIR hDir, const char *pszRelPath) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelPath, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + HANDLE hSubDir = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRoot, NULL); + + NTSTATUS rcNt = NtCreateFile(&hSubDir, + DELETE | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + FILE_DISPOSITION_INFORMATION DispInfo; + DispInfo.DeleteFile = TRUE; + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtSetInformationFile(hSubDir, &Ios, &DispInfo, sizeof(DispInfo), FileDispositionInformation); + + NTSTATUS rcNt2 = NtClose(hSubDir); + if (!NT_SUCCESS(rcNt2) && NT_SUCCESS(rcNt)) + rcNt = rcNt2; + } + + if (NT_SUCCESS(rcNt)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +/* + * + * RTPath stuff. + * RTPath stuff. + * RTPath stuff. + * + * + */ + + +RTDECL(int) RTDirRelPathQueryInfo(RTDIR hDir, const char *pszRelPath, PRTFSOBJINFO pObjInfo, + RTFSOBJATTRADD enmAddAttr, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + /* + * Validate and convert flags. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelPath, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + if (NtName.Length != 0 || hRoot == NULL) + rc = rtPathNtQueryInfoWorker(hRoot, &NtName, pObjInfo, enmAddAttr, fFlags, pszRelPath); + else + rc = RTDirQueryInfo(hDir, pObjInfo, enmAddAttr); + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +RTDECL(int) RTDirRelPathSetMode(RTDIR hDir, const char *pszRelPath, RTFMODE fMode, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + fMode = rtFsModeNormalize(fMode, pszRelPath, 0, 0); + AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE); + AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_FLAGS); + + /* + * Convert and normalize the path. + */ + UNICODE_STRING NtName; + HANDLE hRoot = pThis->hDir; + int rc = RTNtPathRelativeFromUtf8(&NtName, &hRoot, pszRelPath, RTDIRREL_NT_GET_ASCENT(pThis), + pThis->enmInfoClass == FileMaximumInformation); + if (RT_SUCCESS(rc)) + { + HANDLE hSubDir = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, 0 /*fAttrib*/, hRoot, NULL); + + ULONG fOpenOptions = FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT; + if (fFlags & RTPATH_F_ON_LINK) + fOpenOptions |= FILE_OPEN_REPARSE_POINT; + NTSTATUS rcNt = NtCreateFile(&hSubDir, + FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /*AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + fOpenOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + rc = rtNtFileSetModeWorker(hSubDir, fMode); + + rcNt = NtClose(hSubDir); + if (!NT_SUCCESS(rcNt) && RT_SUCCESS(rc)) + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathFree(&NtName, NULL); + } + return rc; +} + + +RTDECL(int) RTDirRelPathSetTimes(RTDIR hDir, const char *pszRelPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, + PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + rc = RTPathSetTimesEx(szPath, pAccessTime, pModificationTime, pChangeTime, pBirthTime, fFlags); + return rc; +} + + +RTDECL(int) RTDirRelPathSetOwner(RTDIR hDir, const char *pszRelPath, uint32_t uid, uint32_t gid, uint32_t fFlags) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + { +#ifndef RT_OS_WINDOWS + rc = RTPathSetOwnerEx(szPath, uid, gid, fFlags); +#else + rc = VERR_NOT_IMPLEMENTED; + RT_NOREF(uid, gid, fFlags); +#endif + } + return rc; +} + + +RTDECL(int) RTDirRelPathRename(RTDIR hDirSrc, const char *pszSrc, RTDIR hDirDst, const char *pszDst, unsigned fRename) +{ + PRTDIRINTERNAL pThis = hDirSrc; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + PRTDIRINTERNAL pThat = hDirDst; + if (pThat != pThis) + { + AssertPtrReturn(pThat, VERR_INVALID_HANDLE); + AssertReturn(pThat->u32Magic != RTDIR_MAGIC, VERR_INVALID_HANDLE); + } + + char szSrcPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szSrcPath, sizeof(szSrcPath), pszSrc); + if (RT_SUCCESS(rc)) + { + char szDstPath[RTPATH_MAX]; + rc = rtDirRelBuildFullPath(pThis, szDstPath, sizeof(szDstPath), pszDst); + if (RT_SUCCESS(rc)) + rc = RTPathRename(szSrcPath, szDstPath, fRename); + } + return rc; +} + + +RTDECL(int) RTDirRelPathUnlink(RTDIR hDir, const char *pszRelPath, uint32_t fUnlink) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszRelPath); + if (RT_SUCCESS(rc)) + rc = RTPathUnlink(szPath, fUnlink); + return rc; +} + + +/* + * + * RTSymlink stuff. + * RTSymlink stuff. + * RTSymlink stuff. + * + * + */ + + +RTDECL(int) RTDirRelSymlinkCreate(RTDIR hDir, const char *pszSymlink, const char *pszTarget, + RTSYMLINKTYPE enmType, uint32_t fCreate) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszSymlink); + if (RT_SUCCESS(rc)) + rc = RTSymlinkCreate(szPath, pszTarget, enmType, fCreate); + return rc; +} + + +RTDECL(int) RTDirRelSymlinkRead(RTDIR hDir, const char *pszSymlink, char *pszTarget, size_t cbTarget, uint32_t fRead) +{ + PRTDIRINTERNAL pThis = hDir; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE); + + char szPath[RTPATH_MAX]; + int rc = rtDirRelBuildFullPath(pThis, szPath, sizeof(szPath), pszSymlink); + if (RT_SUCCESS(rc)) + rc = RTSymlinkRead(szPath, pszTarget, cbTarget, fRead); + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/fs-nt.cpp b/src/VBox/Runtime/r3/nt/fs-nt.cpp new file mode 100644 index 00000000..70add1ee --- /dev/null +++ b/src/VBox/Runtime/r3/nt/fs-nt.cpp @@ -0,0 +1,279 @@ +/* $Id: fs-nt.cpp $ */ +/** @file + * IPRT - File System, Native NT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal-r3-nt.h" + +#include <iprt/fs.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/param.h> +#include <iprt/errcore.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include "internal/fs.h" + + + + +RTR3DECL(int) RTFsQuerySizes(const char *pszFsPath, RTFOFF *pcbTotal, RTFOFF *pcbFree, + uint32_t *pcbBlock, uint32_t *pcbSector) +{ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + + /* + * Open the file/dir/whatever. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFsPath, + GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + RTFILE hIprtFile = NIL_RTFILE; + rc = RTFileFromNative(&hIprtFile, (RTHCINTPTR)hFile); + AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = RTFileQueryFsSizes(hIprtFile, pcbTotal, pcbFree, pcbBlock, pcbSector); + + RTNtPathClose(hFile); + } + return rc; +} + + +RTR3DECL(int) RTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial) +{ + /* + * Validate & get valid root path. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertPtrReturn(pu32Serial, VERR_INVALID_POINTER); + + /* + * Open the file/dir/whatever. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFsPath, + GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + /* + * Get the volume information. + */ + union + { + FILE_FS_VOLUME_INFORMATION FsVolInfo; + uint8_t abBuf[sizeof(FILE_FS_VOLUME_INFORMATION) + 4096]; + } u; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &u, sizeof(u), FileFsVolumeInformation); + if (NT_SUCCESS(rcNt)) + *pu32Serial = u.FsVolInfo.VolumeSerialNumber; + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathClose(hFile); + } + return rc; +} + + +RTR3DECL(int) RTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties) +{ + /* + * Validate & get valid root path. + */ + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertPtrReturn(pProperties, VERR_INVALID_POINTER); + + /* + * Open the file/dir/whatever. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFsPath, + GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + /* + * Get the volume information. + */ + union + { + FILE_FS_ATTRIBUTE_INFORMATION FsAttrInfo; + uint8_t abBuf[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + 4096]; + } u; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &u, sizeof(u), FileFsAttributeInformation); + if (NT_SUCCESS(rcNt)) + { + FILE_FS_DEVICE_INFORMATION FsDevInfo; + rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &FsDevInfo, sizeof(FsDevInfo), FileFsDeviceInformation); + if (NT_SUCCESS(rcNt)) + { + /* + * Fill in the return structure. + */ + memset(pProperties, 0, sizeof(*pProperties)); + pProperties->cbMaxComponent = u.FsAttrInfo.MaximumComponentNameLength; + pProperties->fFileCompression = !!(u.FsAttrInfo.FileSystemAttributes & FILE_FILE_COMPRESSION); + pProperties->fCompressed = !!(u.FsAttrInfo.FileSystemAttributes & FILE_VOLUME_IS_COMPRESSED); + pProperties->fReadOnly = !!(u.FsAttrInfo.FileSystemAttributes & FILE_READ_ONLY_VOLUME); + pProperties->fSupportsUnicode = !!(u.FsAttrInfo.FileSystemAttributes & FILE_UNICODE_ON_DISK); + pProperties->fCaseSensitive = false; /* win32 is case preserving only */ + /** @todo r=bird: What about FILE_CASE_SENSITIVE_SEARCH ? Is this set for NTFS + * as well perchance? If so, better mention it instead of just setting + * fCaseSensitive to false. */ + + /* figure the remote stuff */ + pProperties->fRemote = RT_BOOL(FsDevInfo.Characteristics & FILE_REMOTE_DEVICE); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + RTNtPathClose(hFile); + } + return rc; +} + + +RTR3DECL(bool) RTFsIsCaseSensitive(const char *pszFsPath) +{ + RT_NOREF_PV(pszFsPath); + return false; +} + + +int rtNtQueryFsType(HANDLE hHandle, PRTFSTYPE penmType) +{ + /* + * Get the file system name. + */ + union + { + FILE_FS_ATTRIBUTE_INFORMATION FsAttrInfo; + uint8_t abBuf[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + 4096]; + } u; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryVolumeInformationFile(hHandle, &Ios, &u, sizeof(u), FileFsAttributeInformation); + if (NT_SUCCESS(rcNt)) + { +#define IS_FS(a_szName) rtNtCompWideStrAndAscii(u.FsAttrInfo.FileSystemName, u.FsAttrInfo.FileSystemNameLength, RT_STR_TUPLE(a_szName)) + if (IS_FS("NTFS")) + *penmType = RTFSTYPE_NTFS; + else if (IS_FS("FAT")) + *penmType = RTFSTYPE_FAT; + else if (IS_FS("FAT32")) + *penmType = RTFSTYPE_FAT; + else if (IS_FS("exFAT")) + *penmType = RTFSTYPE_EXFAT; + else if (IS_FS("UDF")) + *penmType = RTFSTYPE_UDF; + else if (IS_FS("CDFS")) + *penmType = RTFSTYPE_ISO9660; + else if (IS_FS("HPFS")) + *penmType = RTFSTYPE_HPFS; + else if (IS_FS("ReFS")) /** @todo verify ReFS signature. */ + *penmType = RTFSTYPE_REFS; + else if (IS_FS("VBoxSharedFolderFS")) + *penmType = RTFSTYPE_VBOXSHF; +#undef IS_FS + return VINF_SUCCESS; + } + + *penmType = RTFSTYPE_UNKNOWN; + return RTErrConvertFromNtStatus(rcNt); +} + + +RTR3DECL(int) RTFsQueryType(const char *pszFsPath, PRTFSTYPE penmType) +{ + /* + * Validate input. + */ + *penmType = RTFSTYPE_UNKNOWN; + AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER); + AssertReturn(*pszFsPath, VERR_INVALID_PARAMETER); + + /* + * Open the file/dir/whatever. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFsPath, + GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_OPEN_FOR_BACKUP_INTENT, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + rc = rtNtQueryFsType(hFile, penmType); + RTNtPathClose(hFile); + } + return rc; +} + diff --git a/src/VBox/Runtime/r3/nt/internal-r3-nt.h b/src/VBox/Runtime/r3/nt/internal-r3-nt.h new file mode 100644 index 00000000..c76a3f6b --- /dev/null +++ b/src/VBox/Runtime/r3/nt/internal-r3-nt.h @@ -0,0 +1,92 @@ +/* $Id: internal-r3-nt.h $ */ +/** @file + * IPRT - Internal Header for the Native NT code. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_r3_nt_internal_r3_nt_h +#define IPRT_INCLUDED_SRC_r3_nt_internal_r3_nt_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef IN_SUP_HARDENED_R3 +# include <iprt/nt/nt-and-windows.h> +#else +# include <iprt/nt/nt.h> +#endif +#include "internal/iprt.h" + + +#if 1 +/** Enables the "\\!\" NT path pass thru as well as hacks for listing NT object + * directories. */ +# define IPRT_WITH_NT_PATH_PASSTHRU 1 +#endif + + + +/** + * Internal helper for comparing a WCHAR string with a char string. + * + * @returns @c true if equal, @c false if not. + * @param pwsz1 The first string. + * @param cch1 The length of the first string, in bytes. + * @param psz2 The second string. + * @param cch2 The length of the second string. + */ +DECLINLINE(bool) rtNtCompWideStrAndAscii(WCHAR const *pwsz1, size_t cch1, const char *psz2, size_t cch2) +{ + if (cch1 != cch2 * 2) + return false; + while (cch2-- > 0) + { + unsigned ch1 = *pwsz1++; + unsigned ch2 = (unsigned char)*psz2++; + if (ch1 != ch2) + return false; + } + return true; +} + +#endif /* !IPRT_INCLUDED_SRC_r3_nt_internal_r3_nt_h */ + +/** + * Common worker for RTFileSetMode, RTPathSetMode and RTDirRelPathSetMode. + * + * @returns IPRT status code. + * @param hNativeFile The NT handle to the file system object. + * @param fMode Valid and normalized file mode mask to set. + */ +DECLHIDDEN(int) rtNtFileSetModeWorker(HANDLE hNativeFile, RTFMODE fMode); + diff --git a/src/VBox/Runtime/r3/nt/pathint-nt.cpp b/src/VBox/Runtime/r3/nt/pathint-nt.cpp new file mode 100644 index 00000000..95281ca7 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/pathint-nt.cpp @@ -0,0 +1,1161 @@ +/* $Id: pathint-nt.cpp $ */ +/** @file + * IPRT - Native NT, Internal Path stuff. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal-r3-nt.h" + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static char const g_szPrefixUnc[] = "\\??\\UNC\\"; +static char const g_szPrefix[] = "\\??\\"; +static char const g_szPrefixNt3xUnc[] = "\\DosDevices\\UNC\\"; +static char const g_szPrefixNt3x[] = "\\DosDevices\\"; + + +/** + * Handles the pass thru case for UTF-8 input. + * Win32 path uses "\\?\" prefix which is converted to "\??\" NT prefix. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. + * @param phRootDir Where to return the root handle, if applicable. + * @param pszPath The UTF-8 path. + */ +static int rtNtPathFromWinUtf8PassThru(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath) +{ + PRTUTF16 pwszPath = NULL; + size_t cwcLen; + int rc = RTStrToUtf16Ex(pszPath, RTSTR_MAX, &pwszPath, 0, &cwcLen); + if (RT_SUCCESS(rc)) + { + if (cwcLen < _32K - 1) + { + *phRootDir = NULL; + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) >= RT_MAKE_U64(0, 4)) + { + pwszPath[0] = '\\'; + pwszPath[1] = '?'; + pwszPath[2] = '?'; + pwszPath[3] = '\\'; + + pNtName->Buffer = pwszPath; + pNtName->Length = (uint16_t)(cwcLen * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + return VINF_SUCCESS; + } + + rc = RTUtf16Realloc(&pwszPath, cwcLen + sizeof(g_szPrefixNt3x)); + if (RT_SUCCESS(rc)) + { + memmove(&pwszPath[sizeof(g_szPrefixNt3x) - 1], &pwszPath[4], (cwcLen - 4 + 1) * sizeof(RTUTF16)); + for (uint32_t i = 0; i < sizeof(g_szPrefixNt3x) - 1; i++) + pwszPath[i] = g_szPrefixNt3x[i]; + + pNtName->Buffer = pwszPath; + pNtName->Length = (uint16_t)((cwcLen - 4 + sizeof(g_szPrefixNt3x) - 1) * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + return VINF_SUCCESS; + } + } + + RTUtf16Free(pwszPath); + rc = VERR_FILENAME_TOO_LONG; + } + return rc; +} + + +/** + * Handles the pass thru case for UTF-16 input. + * Win32 path uses "\\?\" prefix which is converted to "\??\" NT prefix. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. + * @param phRootDir Stores NULL here, as we don't use it. + * @param pwszWinPath The UTF-16 windows-style path. + * @param cwcWinPath The length of the windows-style input path. + */ +static int rtNtPathFromWinUtf16PassThru(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, + PCRTUTF16 pwszWinPath, size_t cwcWinPath) +{ + /* Check length and allocate memory for it. */ + int rc; + if (cwcWinPath < _32K - 1) + { + + size_t const cwcExtraPrefix = RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) + >= RT_MAKE_U64(0, 4) + ? 0 : sizeof(g_szPrefixNt3x) - 1 - 4; + PRTUTF16 pwszNtPath = (PRTUTF16)RTUtf16Alloc((cwcExtraPrefix + cwcWinPath + 1) * sizeof(RTUTF16)); + if (pwszNtPath) + { + /* Intialize the path. */ + if (!cwcExtraPrefix) + { + pwszNtPath[0] = '\\'; + pwszNtPath[1] = '?'; + pwszNtPath[2] = '?'; + pwszNtPath[3] = '\\'; + } + else + for (uint32_t i = 0; i < sizeof(g_szPrefixNt3x) - 1; i++) + pwszNtPath[i] = g_szPrefixNt3x[i]; + memcpy(pwszNtPath + cwcExtraPrefix + 4, pwszWinPath + 4, (cwcWinPath - 4) * sizeof(RTUTF16)); + pwszNtPath[cwcExtraPrefix + cwcWinPath] = '\0'; + + /* Initialize the return values. */ + pNtName->Buffer = pwszNtPath; + pNtName->Length = (uint16_t)(cwcExtraPrefix + cwcWinPath * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + *phRootDir = NULL; + + rc = VINF_SUCCESS; + } + else + rc = VERR_NO_UTF16_MEMORY; + } + else + rc = VERR_FILENAME_TOO_LONG; + return rc; +} + + + + + +/** + * Converts the path to UTF-16 and sets all the return values. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. + * @param phRootDir Where to return the root handle, if applicable. + * @param pszPath The UTF-8 path. + */ +static int rtNtPathUtf8ToUniStr(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath) +{ + PRTUTF16 pwszPath = NULL; + size_t cwcLen; + int rc = RTStrToUtf16Ex(pszPath, RTSTR_MAX, &pwszPath, 0, &cwcLen); + if (RT_SUCCESS(rc)) + { + if (cwcLen < _32K - 1) + { + pNtName->Buffer = pwszPath; + pNtName->Length = (uint16_t)(cwcLen * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + *phRootDir = NULL; + return VINF_SUCCESS; + } + + RTUtf16Free(pwszPath); + rc = VERR_FILENAME_TOO_LONG; + } + return rc; +} + + +/** + * Converts a windows-style path to NT format and encoding. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. Free using + * rtTNtPathToNative. + * @param phRootDir Where to return the root handle, if applicable. + * @param pszPath The UTF-8 path. + */ +static int rtNtPathToNative(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath) +{ +/** @todo This code sucks a bit performance wise, esp. calling + * generic RTPathAbs. Too many buffers involved, I think. */ + + /* + * Very simple conversion of a win32-like path into an NT path. + */ + const char *pszPrefix; + size_t cchPrefix; + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) >= RT_MAKE_U64(0, 4)) + { + pszPrefix = g_szPrefix; + cchPrefix = sizeof(g_szPrefix) - 1; + } + else + { + pszPrefix = g_szPrefixNt3x; + cchPrefix = sizeof(g_szPrefixNt3x) - 1; + } + + size_t cchSkip = 0; + if ( RTPATH_IS_SLASH(pszPath[0]) + && RTPATH_IS_SLASH(pszPath[1]) + && !RTPATH_IS_SLASH(pszPath[2]) + && pszPath[2]) + { +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + /* + * Special trick: The path starts with RTPATH_NT_PASSTHRU_PREFIX, we will skip past the bang and pass it thru. + */ + if ( pszPath[2] == ':' + && pszPath[3] == 'i' + && pszPath[4] == 'p' + && pszPath[5] == 'r' + && pszPath[6] == 't' + && pszPath[7] == 'n' + && pszPath[8] == 't' + && pszPath[9] == ':' + && RTPATH_IS_SLASH(pszPath[10])) + return rtNtPathUtf8ToUniStr(pNtName, phRootDir, pszPath + 10); +#endif + + if ( pszPath[2] == '?' + && RTPATH_IS_SLASH(pszPath[3])) + return rtNtPathFromWinUtf8PassThru(pNtName, phRootDir, pszPath); + + if ( pszPath[2] == '.' + && RTPATH_IS_SLASH(pszPath[3])) + { + /* + * Device path. + * Note! I suspect \\.\stuff\..\otherstuff may be handled differently by windows. + */ + cchSkip = 4; + } + else + { + /* UNC */ + if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) >= RT_MAKE_U64(0, 4)) + { + pszPrefix = g_szPrefixUnc; + cchPrefix = sizeof(g_szPrefixUnc) - 1; + } + else + { + pszPrefix = g_szPrefixNt3xUnc; + cchPrefix = sizeof(g_szPrefixNt3xUnc) - 1; + } + cchSkip = 2; + } + } + + /* + * Straighten out all .. and uncessary . references and convert slashes. + */ + char szAbsPathBuf[RTPATH_MAX]; + size_t cbAbsPath = sizeof(szAbsPathBuf) - (cchPrefix - cchSkip); + char *pszAbsPath = szAbsPathBuf; + char *pszAbsPathFree = NULL; + int rc = RTPathAbsEx(NULL, pszPath, RTPATH_STR_F_STYLE_DOS, &pszAbsPath[cchPrefix - cchSkip], &cbAbsPath); + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (rc == VERR_BUFFER_OVERFLOW) + { + unsigned cTries = 8; + size_t cbAbsPathBuf = RTPATH_MAX; + for (;;) + { + cbAbsPathBuf = RT_MAX(RT_ALIGN_Z((cchPrefix - cchSkip) + cbAbsPath + 32, 64), cbAbsPathBuf + 256); + if (cTries == 1) + cbAbsPathBuf = RT_MAX(cbAbsPathBuf, RTPATH_BIG_MAX * 2); + pszAbsPathFree = pszAbsPath = (char *)RTMemTmpAlloc(cbAbsPathBuf); + if (!pszAbsPath) + return VERR_NO_TMP_MEMORY; + + cbAbsPath = cbAbsPathBuf - (cchPrefix - cchSkip); + rc = RTPathAbsEx(NULL, pszPath, RTPATH_STR_F_STYLE_DOS, &pszAbsPath[cchPrefix - cchSkip], &cbAbsPath); + if (RT_SUCCESS(rc)) + break; + RTMemTmpFree(pszAbsPathFree); + pszAbsPathFree = NULL; + if (rc != VERR_BUFFER_OVERFLOW) + return rc; + if (--cTries == 0) + return VERR_FILENAME_TOO_LONG; + } + } + else + return rc; + + /* + * Add prefix and convert it to UTF16. + */ + memcpy(pszAbsPath, pszPrefix, cchPrefix); + rc = rtNtPathUtf8ToUniStr(pNtName, phRootDir, pszAbsPath); + + if (pszAbsPathFree) + RTMemTmpFree(pszAbsPathFree); + return rc; +} + + +/** + * Converts a windows-style path to NT format and encoding. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. Free using + * RTNtPathToNative. + * @param phRootDir Where to return the root handle, if applicable. + * @param pszPath The UTF-8 path. + */ +RTDECL(int) RTNtPathFromWinUtf8(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath) +{ + return rtNtPathToNative(pNtName, phRootDir, pszPath); +} + + +/** + * Converts a UTF-16 windows-style path to NT format. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. Free using + * RTNtPathFree. + * @param phRootDir Where to return the root handle, if applicable. + * @param pwszWinPath The UTF-16 windows-style path. + * @param cwcWinPath The max length of the windows-style path in + * RTUTF16 units. Use RTSTR_MAX if unknown and @a + * pwszWinPath is correctly terminated. + */ +RTDECL(int) RTNtPathFromWinUtf16Ex(struct _UNICODE_STRING *pNtName, HANDLE *phRootDir, PCRTUTF16 pwszWinPath, size_t cwcWinPath) +{ + /* + * Validate the input, calculating the correct length. + */ + if (cwcWinPath == 0 || *pwszWinPath == '\0') + return VERR_INVALID_NAME; + + RTUtf16NLenEx(pwszWinPath, cwcWinPath, &cwcWinPath); + int rc = RTUtf16ValidateEncodingEx(pwszWinPath, cwcWinPath, 0); + if (RT_FAILURE(rc)) + return rc; + + /* + * Very simple conversion of a win32-like path into an NT path. + */ + const char *pszPrefix = g_szPrefix; + size_t cchPrefix = sizeof(g_szPrefix) - 1; + size_t cchSkip = 0; + + if ( RTPATH_IS_SLASH(pwszWinPath[0]) + && cwcWinPath >= 3 + && RTPATH_IS_SLASH(pwszWinPath[1]) + && !RTPATH_IS_SLASH(pwszWinPath[2]) ) + { +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + /* + * Special trick: The path starts with RTPATH_NT_PASSTHRU_PREFIX, we will skip past the bang and pass it thru. + */ + if ( cwcWinPath >= sizeof(RTPATH_NT_PASSTHRU_PREFIX) - 1U + && pwszWinPath[2] == ':' + && pwszWinPath[3] == 'i' + && pwszWinPath[4] == 'p' + && pwszWinPath[5] == 'r' + && pwszWinPath[6] == 't' + && pwszWinPath[7] == 'n' + && pwszWinPath[8] == 't' + && pwszWinPath[9] == ':' + && RTPATH_IS_SLASH(pwszWinPath[10]) ) + { + pwszWinPath += 10; + cwcWinPath -= 10; + if (cwcWinPath < _32K - 1) + { + PRTUTF16 pwszNtPath = RTUtf16Alloc((cwcWinPath + 1) * sizeof(RTUTF16)); + if (pwszNtPath) + { + memcpy(pwszNtPath, pwszWinPath, cwcWinPath * sizeof(RTUTF16)); + pwszNtPath[cwcWinPath] = '\0'; + pNtName->Buffer = pwszNtPath; + pNtName->Length = (uint16_t)(cwcWinPath * sizeof(RTUTF16)); + pNtName->MaximumLength = pNtName->Length + sizeof(RTUTF16); + *phRootDir = NULL; + return VINF_SUCCESS; + } + rc = VERR_NO_UTF16_MEMORY; + } + else + rc = VERR_FILENAME_TOO_LONG; + return rc; + } +#endif + + if ( pwszWinPath[2] == '?' + && cwcWinPath >= 4 + && RTPATH_IS_SLASH(pwszWinPath[3])) + return rtNtPathFromWinUtf16PassThru(pNtName, phRootDir, pwszWinPath, cwcWinPath); + + if ( pwszWinPath[2] == '.' + && cwcWinPath >= 4 + && RTPATH_IS_SLASH(pwszWinPath[3])) + { + /* + * Device path. + * Note! I suspect \\.\stuff\..\otherstuff may be handled differently by windows. + */ + cchSkip = 4; + } + else + { + /* UNC */ + pszPrefix = g_szPrefixUnc; + cchPrefix = sizeof(g_szPrefixUnc) - 1; + cchSkip = 2; + } + } + + /* + * Straighten out all .. and unnecessary . references and convert slashes. + */ + /* UTF-16 -> UTF-8 (relative path) */ + char szRelPath[RTPATH_MAX]; + char *pszRelPathFree = NULL; + char *pszRelPath = szRelPath; + size_t cchRelPath; + rc = RTUtf16ToUtf8Ex(pwszWinPath, cwcWinPath, &pszRelPath, sizeof(szRelPath), &cchRelPath); + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (rc == VERR_BUFFER_OVERFLOW) + { + pszRelPath = NULL; + rc = RTUtf16ToUtf8Ex(pwszWinPath, cwcWinPath, &pszRelPath, 0, &cchRelPath); + if (RT_SUCCESS(rc)) + pszRelPathFree = pszRelPath; + } + if (RT_SUCCESS(rc)) + { + /* Relative -> Absolute. */ + char szAbsPathBuf[RTPATH_MAX]; + char *pszAbsPathFree = NULL; + char *pszAbsPath = szAbsPathBuf; + size_t cbAbsPath = sizeof(szAbsPathBuf) - (cchPrefix - cchSkip); + rc = RTPathAbsEx(NULL, szRelPath, RTPATH_STR_F_STYLE_DOS, &pszAbsPath[cchPrefix - cchSkip], &cbAbsPath); + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (rc == VERR_BUFFER_OVERFLOW) + { + unsigned cTries = 8; + size_t cbAbsPathBuf = RTPATH_MAX; + for (;;) + { + cbAbsPathBuf = RT_MAX(RT_ALIGN_Z((cchPrefix - cchSkip) + cbAbsPath + 32, 64), cbAbsPathBuf + 256); + if (cTries == 1) + cbAbsPathBuf = RT_MAX(cbAbsPathBuf, RTPATH_BIG_MAX * 2); + pszAbsPathFree = pszAbsPath = (char *)RTMemTmpAlloc(cbAbsPathBuf); + if (!pszAbsPath) + { + rc = VERR_NO_TMP_MEMORY; + break; + } + + cbAbsPath = cbAbsPathBuf - (cchPrefix - cchSkip); + rc = RTPathAbsEx(NULL, szRelPath, RTPATH_STR_F_STYLE_DOS, &pszAbsPath[cchPrefix - cchSkip], &cbAbsPath); + if (RT_SUCCESS(rc)) + break; + + RTMemTmpFree(pszAbsPathFree); + pszAbsPathFree = NULL; + if (rc != VERR_BUFFER_OVERFLOW) + break; + if (--cTries == 0) + { + rc = VERR_FILENAME_TOO_LONG; + break; + } + } + + } + if (pszRelPathFree) + RTStrFree(pszRelPathFree); + + if (RT_SUCCESS(rc)) + { + /* + * Add prefix + */ + memcpy(pszAbsPath, pszPrefix, cchPrefix); + + /* + * Remove trailing '.' that is used to specify no extension in the Win32/DOS world. + */ + size_t cchAbsPath = strlen(pszAbsPath); + if ( cchAbsPath > 2 + && pszAbsPath[cchAbsPath - 1] == '.') + { + char const ch = pszAbsPath[cchAbsPath - 2]; + if ( ch != '/' + && ch != '\\' + && ch != ':' + && ch != '.') + pszAbsPath[--cchAbsPath] = '\0'; + } + + /* + * Finally convert to UNICODE_STRING. + */ + rc = rtNtPathUtf8ToUniStr(pNtName, phRootDir, pszAbsPath); + + if (pszAbsPathFree) + RTMemTmpFree(pszAbsPathFree); + } + } + return rc; +} + + +/** + * Ensures that the NT string has sufficient storage to hold @a cwcMin RTUTF16 + * chars plus a terminator. + * + * The NT string must have been returned by RTNtPathFromWinUtf8 or + * RTNtPathFromWinUtf16Ex. + * + * @returns IPRT status code. + * @param pNtName The NT path string. + * @param cwcMin The minimum number of RTUTF16 chars. Max 32767. + * @sa RTNtPathFree + */ +RTDECL(int) RTNtPathEnsureSpace(struct _UNICODE_STRING *pNtName, size_t cwcMin) +{ + if (pNtName->MaximumLength / sizeof(RTUTF16) > cwcMin) + return VINF_SUCCESS; + + AssertReturn(cwcMin < _64K / sizeof(RTUTF16), VERR_OUT_OF_RANGE); + + size_t const cbMin = (cwcMin + 1) * sizeof(RTUTF16); + int rc = RTUtf16Realloc(&pNtName->Buffer, cbMin); + if (RT_SUCCESS(rc)) + pNtName->MaximumLength = (uint16_t)cbMin; + return rc; +} + + +/** + * Gets the NT path to the object represented by the given handle. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT path. Free using + * RTNtPathFree. + * @param hHandle The handle. + * @param cwcExtra How much extra space is needed. + */ +RTDECL(int) RTNtPathFromHandle(struct _UNICODE_STRING *pNtName, HANDLE hHandle, size_t cwcExtra) +{ + /* + * Query the name into a buffer. + */ + ULONG cbBuf = _2K; + PUNICODE_STRING pUniStrBuf = (PUNICODE_STRING)RTMemTmpAllocZ(cbBuf); + if (!pUniStrBuf) + return VERR_NO_TMP_MEMORY; + + ULONG cbNameBuf = cbBuf; + NTSTATUS rcNt = NtQueryObject(hHandle, ObjectNameInformation, pUniStrBuf, cbBuf, &cbNameBuf); + while ( rcNt == STATUS_BUFFER_OVERFLOW + || rcNt == STATUS_BUFFER_TOO_SMALL) + { + do + cbBuf *= 2; + while (cbBuf <= cbNameBuf); + RTMemTmpFree(pUniStrBuf); + pUniStrBuf = (PUNICODE_STRING)RTMemTmpAllocZ(cbBuf); + if (!pUniStrBuf) + return VERR_NO_TMP_MEMORY; + + cbNameBuf = cbBuf; + rcNt = NtQueryObject(hHandle, ObjectNameInformation, pUniStrBuf, cbBuf, &cbNameBuf); + } + int rc; + if (NT_SUCCESS(rcNt)) + { + /* + * Copy the result into the return string. + */ + size_t cbNeeded = cwcExtra * sizeof(RTUTF16) + pUniStrBuf->Length + sizeof(RTUTF16); + if (cbNeeded < _64K) + { + pNtName->Length = pUniStrBuf->Length; + pNtName->MaximumLength = (uint16_t)cbNeeded; + pNtName->Buffer = RTUtf16Alloc(cbNeeded); + if (pNtName->Buffer) + { + memcpy(pNtName->Buffer, pUniStrBuf->Buffer, pUniStrBuf->Length); + pNtName->Buffer[pUniStrBuf->Length / sizeof(RTUTF16)] = '\0'; + rc = VINF_SUCCESS; + } + else + rc = VERR_NO_UTF16_MEMORY; + } + else + rc = VERR_FILENAME_TOO_LONG; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + RTMemTmpFree(pUniStrBuf); + return rc; +} + +static int rtNtPathRelativeToAbs(struct _UNICODE_STRING *pNtName, HANDLE *phRootDir) +{ + int rc; + if (pNtName->Length == 0) + { + RTUtf16Free(pNtName->Buffer); + rc = RTNtPathFromHandle(pNtName, *phRootDir, pNtName->Length / sizeof(RTUTF16) + 2); + if (RT_SUCCESS(rc)) + { + *phRootDir = NULL; + return VINF_SUCCESS; + } + } + else + { + + UNICODE_STRING RootDir; + size_t const cwcAppend = pNtName->Length / sizeof(RTUTF16); + rc = RTNtPathFromHandle(&RootDir, *phRootDir, cwcAppend + 2); + if (RT_SUCCESS(rc)) + { + size_t cwcRoot = RootDir.Length / sizeof(RTUTF16); + if (RootDir.Buffer[cwcRoot - 1] != '\\') + RootDir.Buffer[cwcRoot++] = '\\'; + memcpy(&RootDir.Buffer[cwcRoot], pNtName->Buffer, cwcAppend * sizeof(RTUTF16)); + RTUtf16Free(pNtName->Buffer); + pNtName->Length = (uint16_t)((cwcRoot + cwcAppend) * sizeof(RTUTF16)); + pNtName->MaximumLength = RootDir.MaximumLength; + pNtName->Buffer = RootDir.Buffer; + + *phRootDir = NULL; + return VINF_SUCCESS; + } + RTUtf16Free(pNtName->Buffer); + } + pNtName->Length = 0; + pNtName->MaximumLength = 0; + pNtName->Buffer = NULL; + return rc; +} + + +/** + * Rewinds the path back to the start of the previous component. + * + * Will preserve root slash. + * + * @returns Pointer to character after the start-of-component slash, or + * pwszStart. + * @param pwcEnd The current end of the path. + * @param pwszStart The start of the path. + */ +static PRTUTF16 rtNtPathGetPrevComponent(PRTUTF16 pwcEnd, PRTUTF16 pwszStart) +{ + if ((uintptr_t)pwcEnd > (uintptr_t)pwszStart) + { + RTUTF16 wc = pwcEnd[-1]; + if ( (wc == '\\' || wc == '/') + && (uintptr_t)(pwcEnd - pwszStart) != 1) + pwcEnd--; + + while ( (uintptr_t)pwcEnd > (uintptr_t)pwszStart + && (wc = pwcEnd[-1]) != '\\' + && (wc = pwcEnd[-1]) != '/') + pwcEnd--; + } + return pwcEnd; +} + + +/** + * Converts a relative windows-style path to relative NT format and encoding. + * + * @returns IPRT status code. + * @param pNtName Where to return the NT name. Free using + * rtTNtPathToNative with phRootDir set to NULL. + * @param phRootDir On input, the handle to the directory the path + * is relative to. On output, the handle to + * specify as root directory in the object + * attributes when accessing the path. If + * enmAscent is kRTNtPathRelativeAscent_Allow, it + * may have been set to NULL. + * @param pszPath The relative UTF-8 path. + * @param enmAscent How to handle ascent. + * @param fMustReturnAbsolute Must convert to an absolute path. This + * is necessary if the root dir is a NT directory + * object (e.g. /Devices) since they cannot parse + * relative paths it seems. + */ +RTDECL(int) RTNtPathRelativeFromUtf8(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir, const char *pszPath, + RTNTPATHRELATIVEASCENT enmAscent, bool fMustReturnAbsolute) +{ + size_t cwcMax; + int rc = RTStrCalcUtf16LenEx(pszPath, RTSTR_MAX, &cwcMax); + if (RT_FAILURE(rc)) + return rc; + if (cwcMax + 2 >= _32K) + return VERR_FILENAME_TOO_LONG; + + PRTUTF16 pwszDst; + pNtName->Length = 0; + pNtName->MaximumLength = (uint16_t)((cwcMax + 2) * sizeof(RTUTF16)); + pNtName->Buffer = pwszDst = RTUtf16Alloc((cwcMax + 2) * sizeof(RTUTF16)); + if (!pwszDst) + return VERR_NO_UTF16_MEMORY; + + PRTUTF16 pwszDstCur = pwszDst; + PRTUTF16 pwszDstComp = pwszDst; + for (;;) + { + RTUNICP uc; + rc = RTStrGetCpEx(&pszPath, &uc); + if (RT_SUCCESS(rc)) + { + switch (uc) + { + default: + pwszDstCur = RTUtf16PutCp(pwszDstCur, uc); + break; + + case '\\': + case '/': + if (pwszDstCur != pwszDstComp) + pwszDstComp = pwszDstCur = RTUtf16PutCp(pwszDstCur, '\\'); + /* else: only one slash between components. */ + break; + + case '.': + if (pwszDstCur == pwszDstComp) + { + /* + * Single dot changes nothing. + */ + char ch2 = *pszPath; + if (ch2 == '\0') + { + /* Trailing single dot means we need to drop trailing slash. */ + if (pwszDstCur != pwszDst) + pwszDstCur--; + *pwszDstCur = '\0'; + pNtName->Length = (uint16_t)((uintptr_t)pwszDstCur - (uintptr_t)pwszDst); + if (!fMustReturnAbsolute || *phRootDir == NULL) + return VINF_SUCCESS; + return rtNtPathRelativeToAbs(pNtName, phRootDir); + } + + if (ch2 == '\\' || ch2 == '/') + { + pszPath++; /* Ignore lone dot followed but another component. */ + break; + } + + /* + * Two dots drops off the last directory component. This gets complicated + * when we start out without any path and we need to consult enmAscent. + */ + if (ch2 == '.') + { + char ch3 = pszPath[1]; + if ( ch3 == '\\' + || ch3 == '/' + || ch3 == '\0') + { + /* Drop a path component. */ + if (pwszDstComp != pwszDst) + pwszDstComp = pwszDstCur = rtNtPathGetPrevComponent(pwszDstCur, pwszDst); + /* Hit the start, which is a bit complicated. */ + else + switch (enmAscent) + { + case kRTNtPathRelativeAscent_Allow: + if (*phRootDir != NULL) + { + RTUtf16Free(pwszDst); + rc = RTNtPathFromHandle(pNtName, *phRootDir, cwcMax + 2); + if (RT_FAILURE(rc)) + return rc; + + *phRootDir = NULL; + pwszDst = pNtName->Buffer; + pwszDstCur = &pwszDst[pNtName->Length / sizeof(RTUTF16)]; + if ( pwszDst != pwszDstCur + && pwszDstCur[-1] != '\\' + && pwszDstCur[-1] != '/') + *pwszDstCur++ = '\\'; + pwszDstComp = pwszDstCur = rtNtPathGetPrevComponent(pwszDstCur, pwszDst); + } + /* else: ignore attempt to ascend beyond the NT root (won't get here). */ + break; + + case kRTNtPathRelativeAscent_Ignore: + /* nothing to do here */ + break; + + default: + case kRTNtPathRelativeAscent_Fail: + RTUtf16Free(pwszDst); + return VERR_PATH_NOT_FOUND; + } + + if (ch3 == '\0') + { + *pwszDstCur = '\0'; + pNtName->Length = (uint16_t)((uintptr_t)pwszDstCur - (uintptr_t)pwszDst); + if (!fMustReturnAbsolute || *phRootDir == NULL) + return VINF_SUCCESS; + return rtNtPathRelativeToAbs(pNtName, phRootDir); + } + pszPath += 2; + break; + } + } + } + + /* Neither '.' nor '..'. */ + pwszDstCur = RTUtf16PutCp(pwszDstCur, '.'); + break; + + case '\0': + *pwszDstCur = '\0'; + pNtName->Length = (uint16_t)((uintptr_t)pwszDstCur - (uintptr_t)pwszDst); + if (!fMustReturnAbsolute || *phRootDir == NULL) + return VINF_SUCCESS; + return rtNtPathRelativeToAbs(pNtName, phRootDir); + } + } + } +} + + +/** + * Frees the native path and root handle. + * + * @param pNtName The NT path after a successful rtNtPathToNative + * call or RTNtPathRelativeFromUtf8. + * @param phRootDir The root handle variable from rtNtPathToNative, + * but NOT RTNtPathRelativeFromUtf8. + */ +static void rtNtPathFreeNative(struct _UNICODE_STRING *pNtName, PHANDLE phRootDir) +{ + RTUtf16Free(pNtName->Buffer); + pNtName->Buffer = NULL; + + RT_NOREF_PV(phRootDir); /* never returned by rtNtPathToNative, shouldn't be freed in connection with RTNtPathRelativeFromUtf8 */ +} + + +/** + * Frees the native path and root handle. + * + * @param pNtName The NT path after a successful rtNtPathToNative + * call or RTNtPathRelativeFromUtf8. + * @param phRootDir The root handle variable from rtNtPathToNative, + */ +RTDECL(void) RTNtPathFree(struct _UNICODE_STRING *pNtName, HANDLE *phRootDir) +{ + rtNtPathFreeNative(pNtName, phRootDir); +} + + +/** + * Wrapper around NtCreateFile. + * + * @returns IPRT status code. + * @param pszPath The UTF-8 path. + * @param fDesiredAccess See NtCreateFile. + * @param fFileAttribs See NtCreateFile. + * @param fShareAccess See NtCreateFile. + * @param fCreateDisposition See NtCreateFile. + * @param fCreateOptions See NtCreateFile. + * @param fObjAttribs The OBJECT_ATTRIBUTES::Attributes value, see + * NtCreateFile and InitializeObjectAttributes. + * @param phHandle Where to return the handle. + * @param puAction Where to return the action taken. Optional. + */ +RTDECL(int) RTNtPathOpen(const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fFileAttribs, ULONG fShareAccess, + ULONG fCreateDisposition, ULONG fCreateOptions, ULONG fObjAttribs, + PHANDLE phHandle, PULONG_PTR puAction) +{ + *phHandle = RTNT_INVALID_HANDLE_VALUE; + + HANDLE hRootDir; + UNICODE_STRING NtName; + int rc = rtNtPathToNative(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, fObjAttribs, hRootDir, NULL); + + NTSTATUS rcNt = NtCreateFile(&hFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + fFileAttribs, + fShareAccess, + fCreateDisposition, + fCreateOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + if (puAction) + *puAction = Ios.Information; + *phHandle = hFile; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromNtStatus(rcNt); + rtNtPathFreeNative(&NtName, &hRootDir); + } + return rc; +} + + +/** + * Wrapper around NtCreateFile. + * + * @returns IPRT status code. + * @param pszPath The UTF-8 path. + * @param fDesiredAccess See NtCreateFile. + * @param fShareAccess See NtCreateFile. + * @param fCreateOptions See NtCreateFile. + * @param fObjAttribs The OBJECT_ATTRIBUTES::Attributes value, see + * NtCreateFile and InitializeObjectAttributes. + * @param phHandle Where to return the handle. + * @param pfObjDir If not NULL, the variable pointed to will be set + * to @c true if we opened an object directory and + * @c false if we opened an directory file (normal + * directory). + */ +RTDECL(int) RTNtPathOpenDir(const char *pszPath, ACCESS_MASK fDesiredAccess, ULONG fShareAccess, ULONG fCreateOptions, + ULONG fObjAttribs, PHANDLE phHandle, bool *pfObjDir) +{ + *phHandle = RTNT_INVALID_HANDLE_VALUE; + + HANDLE hRootDir; + UNICODE_STRING NtName; + int rc = rtNtPathToNative(&NtName, &hRootDir, pszPath); + if (RT_SUCCESS(rc)) + { + if (pfObjDir) + { + *pfObjDir = false; +#ifdef IPRT_WITH_NT_PATH_PASSTHRU + if ( !RTPATH_IS_SLASH(pszPath[0]) + || !RTPATH_IS_SLASH(pszPath[1]) + || pszPath[2] != ':' + || pszPath[3] != 'i' + || pszPath[4] != 'p' + || pszPath[5] != 'r' + || pszPath[6] != 't' + || pszPath[7] != 'n' + || pszPath[8] != 't' + || pszPath[9] != ':' + || !RTPATH_IS_SLASH(pszPath[10]) ) +#endif + pfObjDir = NULL; + } + rc = RTNtPathOpenDirEx(hRootDir, &NtName, fDesiredAccess, fShareAccess, fCreateOptions, fObjAttribs, phHandle, pfObjDir); + rtNtPathFreeNative(&NtName, &hRootDir); + } + return rc; +} + + + +/** + * Wrapper around NtCreateFile, extended version. + * + * @returns IPRT status code. + * @param hRootDir The root director the path is relative to. NULL + * if none. + * @param pNtName The NT path. + * @param fDesiredAccess See NtCreateFile. + * @param fShareAccess See NtCreateFile. + * @param fCreateOptions See NtCreateFile. + * @param fObjAttribs The OBJECT_ATTRIBUTES::Attributes value, see + * NtCreateFile and InitializeObjectAttributes. + * @param phHandle Where to return the handle. + * @param pfObjDir If not NULL, the variable pointed to will be set + * to @c true if we opened an object directory and + * @c false if we opened an directory file (normal + * directory). + */ +RTDECL(int) RTNtPathOpenDirEx(HANDLE hRootDir, struct _UNICODE_STRING *pNtName, ACCESS_MASK fDesiredAccess, ULONG fShareAccess, + ULONG fCreateOptions, ULONG fObjAttribs, PHANDLE phHandle, bool *pfObjDir) +{ + *phHandle = RTNT_INVALID_HANDLE_VALUE; + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, pNtName, fObjAttribs, hRootDir, NULL); + + NTSTATUS rcNt = NtCreateFile(&hFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + fShareAccess, + FILE_OPEN, + fCreateOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + { + if (pfObjDir) + *pfObjDir = false; + *phHandle = hFile; + return VINF_SUCCESS; + } + + /* + * Try add a slash in case this is a device object with a file system attached. + */ + if ( rcNt == STATUS_INVALID_PARAMETER + && pNtName->Length < _64K - 4 + && ( pNtName->Length == 0 + || pNtName->Buffer[pNtName->Length / sizeof(RTUTF16)] != '\\') ) + { + UNICODE_STRING NtTmp; + NtTmp.Length = pNtName->Length + 2; + NtTmp.MaximumLength = NtTmp.Length + 2; + NtTmp.Buffer = (PRTUTF16)RTMemTmpAlloc(NtTmp.MaximumLength); + if (NtTmp.Buffer) + { + memcpy(NtTmp.Buffer, pNtName->Buffer, pNtName->Length); + NtTmp.Buffer[pNtName->Length / sizeof(RTUTF16)] = '\\'; + NtTmp.Buffer[pNtName->Length / sizeof(RTUTF16) + 1] = '\0'; + + hFile = RTNT_INVALID_HANDLE_VALUE; + Ios.Status = -1; + Ios.Information = 0; + ObjAttr.ObjectName = &NtTmp; + + rcNt = NtCreateFile(&hFile, + fDesiredAccess, + &ObjAttr, + &Ios, + NULL /* AllocationSize*/, + FILE_ATTRIBUTE_NORMAL, + fShareAccess, + FILE_OPEN, + fCreateOptions, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + RTMemTmpFree(NtTmp.Buffer); + if (NT_SUCCESS(rcNt)) + { + if (pfObjDir) + *pfObjDir = false; + *phHandle = hFile; + return VINF_SUCCESS; + } + ObjAttr.ObjectName = pNtName; + } + } + + /* + * Try open it as a directory object if it makes sense. + */ + if ( pfObjDir + && ( rcNt == STATUS_OBJECT_NAME_INVALID + || rcNt == STATUS_OBJECT_TYPE_MISMATCH )) + { + /* Strip trailing slash. */ + struct _UNICODE_STRING NtName2 = *pNtName; + if ( NtName2.Length > 2 + && RTPATH_IS_SLASH(NtName2.Buffer[(NtName2.Length / 2) - 1])) + NtName2.Length -= 2; + ObjAttr.ObjectName = &NtName2; + + /* Rought conversion of the access flags. */ + ULONG fObjDesiredAccess = 0; + if ( (fDesiredAccess & GENERIC_ALL) + || (fDesiredAccess & STANDARD_RIGHTS_ALL) == STANDARD_RIGHTS_ALL) + fObjDesiredAccess = DIRECTORY_ALL_ACCESS; + else + { + if (fDesiredAccess & (GENERIC_WRITE | STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA)) + fObjDesiredAccess |= DIRECTORY_CREATE_OBJECT | DIRECTORY_CREATE_OBJECT | DIRECTORY_CREATE_SUBDIRECTORY; + + if ( (fDesiredAccess & (GENERIC_READ | STANDARD_RIGHTS_READ | FILE_LIST_DIRECTORY)) + || !fObjDesiredAccess) + fObjDesiredAccess |= DIRECTORY_QUERY; + + if (fDesiredAccess & FILE_TRAVERSE) + fObjDesiredAccess |= DIRECTORY_TRAVERSE; + } + + rcNt = NtOpenDirectoryObject(&hFile, fObjDesiredAccess, &ObjAttr); + if (NT_SUCCESS(rcNt)) + { + *pfObjDir = true; + *phHandle = hFile; + return VINF_SUCCESS; + } + } + + return RTErrConvertFromNtStatus(rcNt); +} + + + +/** + * Closes an handled open by rtNtPathOpen. + * + * @returns IPRT status code + * @param hHandle The handle value. + */ +RTDECL(int) RTNtPathClose(HANDLE hHandle) +{ + NTSTATUS rcNt = NtClose(hHandle); + if (NT_SUCCESS(rcNt)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(rcNt); +} + diff --git a/src/VBox/Runtime/r3/nt/time-nt.cpp b/src/VBox/Runtime/r3/nt/time-nt.cpp new file mode 100644 index 00000000..f60250e6 --- /dev/null +++ b/src/VBox/Runtime/r3/nt/time-nt.cpp @@ -0,0 +1,228 @@ +/* $Id: time-nt.cpp $ */ +/** @file + * IPRT - Time, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_TIME +#include "internal-r3-nt.h" + +#include <iprt/time.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/ldr.h> +#include <iprt/uint128.h> +#include "internal/time.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Whether we've tried to resolve g_pfnRtlGetSystemTimePrecise or not. */ +static bool g_fInitialized = false; +/** Pointer to RtlGetSystemTimePrecise, added in 6.2 (windows 8). */ +static PFNRTLGETSYSTEMTIMEPRECISE g_pfnRtlGetSystemTimePrecise = NULL; + + +/** + * Initializes globals. + */ +static void rtTimeNtInitialize(void) +{ + /* + * Make sure we don't recurse here when calling into RTLdr. + */ + if (ASMAtomicCmpXchgBool(&g_fInitialized, true, false)) + { + void *pvFunc = RTLdrGetSystemSymbol("ntdll.dll", "RtlGetSystemTimePrecise"); + if (pvFunc) + ASMAtomicWritePtr((void * volatile *)&g_pfnRtlGetSystemTimePrecise, pvFunc); + ASMCompilerBarrier(); + } +} + + +static uint64_t rtTimeGetSystemNanoTS(void) +{ + if (RT_UNLIKELY(!g_fInitialized)) + rtTimeNtInitialize(); + + KUSER_SHARED_DATA volatile *pUserSharedData = (KUSER_SHARED_DATA volatile *)MM_SHARED_USER_DATA_VA; + +#if 1 + /* + * If there is precise time, get the precise system time and calculate the + * interrupt time from it. (Microsoft doesn't expose interrupt time to user + * application, which is very unfortunate as there are a lot place where + * monotonic time is applicable but developer is "forced" to use wall clock.) + */ + if (g_pfnRtlGetSystemTimePrecise) + { + for (;;) + { + uint64_t uUpdateLockBefore; + while ((uUpdateLockBefore = pUserSharedData->TimeUpdateLock) & 1) + ASMNopPause(); + + uint64_t uInterruptTime = *(uint64_t volatile *)&pUserSharedData->InterruptTime; + uint64_t uBaselineInterruptTimeQpc = pUserSharedData->BaselineInterruptTimeQpc; + uint64_t uQpcInterruptTimeIncrement = pUserSharedData->QpcInterruptTimeIncrement; + uint8_t uQpcInterruptTimeIncrementShift = pUserSharedData->QpcInterruptTimeIncrementShift; + LARGE_INTEGER QpcValue; + RtlQueryPerformanceCounter(&QpcValue); + + if (pUserSharedData->TimeUpdateLock == uUpdateLockBefore) + { + uint64_t uQpcValue = QpcValue.QuadPart; + if (uQpcValue <= uBaselineInterruptTimeQpc) + return uInterruptTime * 100; + + /* Calc QPC delta since base line. */ + uQpcValue -= uBaselineInterruptTimeQpc; + uQpcValue--; + + /* Multiply by 10 million. */ + uQpcValue *= UINT32_C(10000000); + + /* Multiply by QPC interrupt time increment value. */ + RTUINT128U Tmp128; + RTUInt128MulU64ByU64(&Tmp128, uQpcValue, uQpcInterruptTimeIncrement); + + /* Shift the upper 64 bits by the increment shift factor. */ + uint64_t uResult = Tmp128.s.Hi >> uQpcInterruptTimeIncrementShift; + + /* Add to base interrupt time value. */ + uResult += uInterruptTime; + + /* Convert from NT unit to nano seconds. */ + return uResult * 100; + } + + ASMNopPause(); + } + } +#endif + + /* + * Just read interrupt time. + */ +#if ARCH_BITS >= 64 + uint64_t uRet = *(uint64_t volatile *)&pUserSharedData->InterruptTime; /* This is what KeQueryInterruptTime does. */ + uRet *= 100; + return uRet; +#else + + LARGE_INTEGER NtTime; + do + { + NtTime.HighPart = pUserSharedData->InterruptTime.High1Time; + NtTime.LowPart = pUserSharedData->InterruptTime.LowPart; + } while (pUserSharedData->InterruptTime.High2Time != NtTime.HighPart); + + return (uint64_t)NtTime.QuadPart * 100; +#endif +} + + +RTDECL(uint64_t) RTTimeSystemNanoTS(void) +{ + return rtTimeGetSystemNanoTS(); +} + + +RTDECL(uint64_t) RTTimeSystemMilliTS(void) +{ + return rtTimeGetSystemNanoTS() / RT_NS_1MS; +} + + +RTDECL(PRTTIMESPEC) RTTimeNow(PRTTIMESPEC pTime) +{ + /* + * Get the precise time if possible. + */ + if (RT_UNLIKELY(!g_fInitialized)) + rtTimeNtInitialize(); + if (g_pfnRtlGetSystemTimePrecise != NULL) + return RTTimeSpecSetNtTime(pTime, g_pfnRtlGetSystemTimePrecise()); + + /* + * Just read system time. + */ + KUSER_SHARED_DATA volatile *pUserSharedData = (KUSER_SHARED_DATA volatile *)MM_SHARED_USER_DATA_VA; +#ifdef RT_ARCH_AMD64 + uint64_t uRet = *(uint64_t volatile *)&pUserSharedData->SystemTime; /* This is what KeQuerySystemTime does. */ + return RTTimeSpecSetNtTime(pTime, uRet); +#else + + LARGE_INTEGER NtTime; + do + { + NtTime.HighPart = pUserSharedData->SystemTime.High1Time; + NtTime.LowPart = pUserSharedData->SystemTime.LowPart; + } while (pUserSharedData->SystemTime.High2Time != NtTime.HighPart); + return RTTimeSpecSetNtTime(pTime, NtTime.QuadPart); +#endif +} + + +RTDECL(PRTTIMESPEC) RTTimeLocalNow(PRTTIMESPEC pTime) +{ + return RTTimeSpecAddNano(RTTimeNow(pTime), RTTimeLocalDeltaNano()); +} + + +RTDECL(int64_t) RTTimeLocalDeltaNano(void) +{ + /* + * UTC = local + TimeZoneBias; The bias is given in NT units. + */ + KUSER_SHARED_DATA volatile *pUserSharedData = (KUSER_SHARED_DATA volatile *)MM_SHARED_USER_DATA_VA; + LARGE_INTEGER Delta; +#if ARCH_BITS == 64 + Delta.QuadPart = *(int64_t volatile *)&pUserSharedData->TimeZoneBias; +#else + do + { + Delta.HighPart = pUserSharedData->TimeZoneBias.High1Time; + Delta.LowPart = pUserSharedData->TimeZoneBias.LowPart; + } while (pUserSharedData->TimeZoneBias.High2Time != Delta.HighPart); +#endif + return Delta.QuadPart * -100; +} + |