/** * Normalizes the give mode mask. * * It will create the missing unix or dos mask from the other (one * of them is required by all APIs), and guess the file type if that's * missing. * * @returns Normalized file mode. * @param fMode The mode mask that may contain a partial/incomplete mask. * @param pszName The filename which this applies to (exe check). * @param cbName The length of that filename. (optional, set 0) */ RTFMODE rtFsModeNormalize(RTFMODE fMode, const char *pszName, size_t cbName) { if (!(fMode & RTFS_UNIX_MASK)) fMode = rtFsModeFromDos(fMode, pszName, cbName); else if (!(fMode & RTFS_DOS_MASK)) fMode = rtFsModeFromUnix(fMode, pszName, cbName); else if (!(fMode & RTFS_TYPE_MASK)) fMode |= fMode & RTFS_DOS_DIRECTORY ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE; else if (RTFS_IS_DIRECTORY(fMode)) fMode |= RTFS_DOS_DIRECTORY; return fMode; }
/** * 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); 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); }
RTDECL(int) RTDirReadEx(PRTDIR pDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) { 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=%d (min %d)\n", *pcbDirEntry, RT_OFFSETOF(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_OFFSETOF(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); } #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 = ~0U; pDirEntry->Info.Attr.u.Unix.gid = ~0U; pDirEntry->Info.Attr.u.Unix.cHardlinks = 1; pDirEntry->Info.Attr.u.Unix.INodeIdDevice = 0; /** @todo Use the volume serial number (see GetFileInformationByHandle). */ pDirEntry->Info.Attr.u.Unix.INodeId = 0; /** @todo Use the fileid (see GetFileInformationByHandle). */ 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 = ~0U; 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 = ~0U; 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) RTDirReadEx(PRTDIR pDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags) { /** @todo Symlinks: Find[First|Next]FileW will return info about the link, so RTPATH_F_FOLLOW_LINK is not handled correctly. */ /* * Validate input. */ if (!pDir || pDir->u32Magic != RTDIR_MAGIC) { AssertMsgFailed(("Invalid pDir=%p\n", pDir)); return VERR_INVALID_PARAMETER; } if (!pDirEntry) { AssertMsgFailed(("Invalid pDirEntry=%p\n", pDirEntry)); return VERR_INVALID_PARAMETER; } if ( enmAdditionalAttribs < RTFSOBJATTRADD_NOTHING || enmAdditionalAttribs > RTFSOBJATTRADD_LAST) { AssertMsgFailed(("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs)); return 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; if (cbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName[2])) { AssertMsgFailed(("Invalid *pcbDirEntry=%d (min %d)\n", *pcbDirEntry, RT_OFFSETOF(RTDIRENTRYEX, szName[2]))); return VERR_INVALID_PARAMETER; } } /* * Fetch data? */ if (!pDir->fDataUnread) { RTStrFree(pDir->pszName); pDir->pszName = NULL; BOOL fRc = FindNextFileW(pDir->hDir, &pDir->Data); if (!fRc) { int iErr = GetLastError(); if (pDir->hDir == INVALID_HANDLE_VALUE || iErr == ERROR_NO_MORE_FILES) return VERR_NO_MORE_FILES; return RTErrConvertFromWin32(iErr); } } /* * Convert the filename to UTF-8. */ if (!pDir->pszName) { int rc = RTUtf16ToUtf8((PCRTUTF16)pDir->Data.cFileName, &pDir->pszName); if (RT_FAILURE(rc)) { pDir->pszName = NULL; return rc; } pDir->cchName = strlen(pDir->pszName); } /* * 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_OFFSETOF(RTDIRENTRYEX, szName[1]) + cchName; if (pcbDirEntry) *pcbDirEntry = cbRequired; if (cbRequired > cbDirEntry) return VERR_BUFFER_OVERFLOW; /* * Setup the returned data. */ pDir->fDataUnread = false; pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName); memcpy(pDirEntry->szName, pszName, cchName + 1); if (pDir->Data.cAlternateFileName[0]) { /* copy and calc length */ PCRTUTF16 pwszSrc = (PCRTUTF16)pDir->Data.cAlternateFileName; PRTUTF16 pwszDst = pDirEntry->wszShortName; uint32_t off = 0; while (off < RT_ELEMENTS(pDirEntry->wszShortName) - 1U && pwszSrc[off]) { pwszDst[off] = pwszSrc[off]; off++; } pDirEntry->cwcShortName = (uint16_t)off; /* zero the rest */ do pwszDst[off++] = '\0'; while (off < RT_ELEMENTS(pDirEntry->wszShortName)); } else { memset(pDirEntry->wszShortName, 0, sizeof(pDirEntry->wszShortName)); pDirEntry->cwcShortName = 0; } pDirEntry->Info.cbObject = ((uint64_t)pDir->Data.nFileSizeHigh << 32) | (uint64_t)pDir->Data.nFileSizeLow; pDirEntry->Info.cbAllocated = pDirEntry->Info.cbObject; Assert(sizeof(uint64_t) == sizeof(pDir->Data.ftCreationTime)); RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, *(uint64_t *)&pDir->Data.ftCreationTime); RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, *(uint64_t *)&pDir->Data.ftLastAccessTime); RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, *(uint64_t *)&pDir->Data.ftLastWriteTime); pDirEntry->Info.ChangeTime = pDirEntry->Info.ModificationTime; pDirEntry->Info.Attr.fMode = rtFsModeFromDos((pDir->Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, pszName, cchName); /* * Requested attributes (we cannot provide anything actually). */ switch (enmAdditionalAttribs) { case RTFSOBJATTRADD_EASIZE: pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; pDirEntry->Info.Attr.u.EASize.cb = 0; break; case RTFSOBJATTRADD_UNIX: pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; pDirEntry->Info.Attr.u.Unix.uid = ~0U; pDirEntry->Info.Attr.u.Unix.gid = ~0U; pDirEntry->Info.Attr.u.Unix.cHardlinks = 1; pDirEntry->Info.Attr.u.Unix.INodeIdDevice = 0; /** @todo Use the volume serial number (see GetFileInformationByHandle). */ pDirEntry->Info.Attr.u.Unix.INodeId = 0; /** @todo Use the fileid (see GetFileInformationByHandle). */ 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 = ~0U; 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 = ~0U; pDirEntry->Info.Attr.u.UnixGroup.szName[0] = '\0'; break; default: AssertMsgFailed(("Impossible!\n")); return VERR_INTERNAL_ERROR; } return VINF_SUCCESS; }
RTR3DECL(int) RTFileQueryInfo(RTFILE hFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs) { /* * Validate input. */ if (hFile == NIL_RTFILE) { AssertMsgFailed(("Invalid hFile=%RTfile\n", hFile)); return VERR_INVALID_PARAMETER; } if (!pObjInfo) { AssertMsgFailed(("Invalid pObjInfo=%p\n", pObjInfo)); return VERR_INVALID_PARAMETER; } if ( enmAdditionalAttribs < RTFSOBJATTRADD_NOTHING || enmAdditionalAttribs > RTFSOBJATTRADD_LAST) { AssertMsgFailed(("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs)); return VERR_INVALID_PARAMETER; } /* * Query file info. */ BY_HANDLE_FILE_INFORMATION Data; if (!GetFileInformationByHandle((HANDLE)RTFileToNative(hFile), &Data)) { DWORD dwErr = GetLastError(); /* Only return if we *really* don't have a valid handle value, * everything else is fine here ... */ if (dwErr == ERROR_INVALID_HANDLE) return RTErrConvertFromWin32(dwErr); RT_ZERO(Data); Data.dwFileAttributes = RTFS_DOS_NT_DEVICE; } /* * Setup the returned data. */ pObjInfo->cbObject = ((uint64_t)Data.nFileSizeHigh << 32) | (uint64_t)Data.nFileSizeLow; pObjInfo->cbAllocated = pObjInfo->cbObject; Assert(sizeof(uint64_t) == sizeof(Data.ftCreationTime)); RTTimeSpecSetNtTime(&pObjInfo->BirthTime, *(uint64_t *)&Data.ftCreationTime); RTTimeSpecSetNtTime(&pObjInfo->AccessTime, *(uint64_t *)&Data.ftLastAccessTime); RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, *(uint64_t *)&Data.ftLastWriteTime); pObjInfo->ChangeTime = pObjInfo->ModificationTime; pObjInfo->Attr.fMode = rtFsModeFromDos((Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, "", 0); /* * Requested attributes (we cannot provide anything actually). */ switch (enmAdditionalAttribs) { case RTFSOBJATTRADD_NOTHING: pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; break; case RTFSOBJATTRADD_UNIX: pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX; pObjInfo->Attr.u.Unix.uid = ~0U; pObjInfo->Attr.u.Unix.gid = ~0U; pObjInfo->Attr.u.Unix.cHardlinks = Data.nNumberOfLinks ? Data.nNumberOfLinks : 1; pObjInfo->Attr.u.Unix.INodeIdDevice = 0; /** @todo Use the volume serial number (see GetFileInformationByHandle). */ pObjInfo->Attr.u.Unix.INodeId = 0; /** @todo Use the fileid (see GetFileInformationByHandle). */ pObjInfo->Attr.u.Unix.fFlags = 0; pObjInfo->Attr.u.Unix.GenerationId = 0; pObjInfo->Attr.u.Unix.Device = 0; break; case RTFSOBJATTRADD_UNIX_OWNER: pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; pObjInfo->Attr.u.UnixOwner.uid = ~0U; 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 = ~0U; 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")); return VERR_INTERNAL_ERROR; } return VINF_SUCCESS; }
/** * 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*/); 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); 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); /* * Query file info. */ WIN32_FILE_ATTRIBUTE_DATA Data; PRTUTF16 pwszPath; int rc = RTStrToUtf16(pszPath, &pwszPath); if (RT_FAILURE(rc)) return rc; if (!GetFileAttributesExW(pwszPath, GetFileExInfoStandard, &Data)) { /* Fallback to FindFileFirst in case of sharing violation. */ if (GetLastError() == ERROR_SHARING_VIOLATION) { WIN32_FIND_DATAW FindData; HANDLE hDir = FindFirstFileW(pwszPath, &FindData); if (hDir == INVALID_HANDLE_VALUE) { rc = RTErrConvertFromWin32(GetLastError()); RTUtf16Free(pwszPath); return rc; } FindClose(hDir); Data.dwFileAttributes = FindData.dwFileAttributes; Data.ftCreationTime = FindData.ftCreationTime; Data.ftLastAccessTime = FindData.ftLastAccessTime; Data.ftLastWriteTime = FindData.ftLastWriteTime; Data.nFileSizeHigh = FindData.nFileSizeHigh; Data.nFileSizeLow = FindData.nFileSizeLow; } else { rc = RTErrConvertFromWin32(GetLastError()); RTUtf16Free(pwszPath); return rc; } } /* * Getting the information for the link target is a bit annoying and * subject to the same access violation mess as above.. :/ */ /** @todo we're too lazy wrt to error paths here... */ if ( (fFlags & RTPATH_F_FOLLOW_LINK) && (Data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { HANDLE hFinal = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hFinal != INVALID_HANDLE_VALUE) { BY_HANDLE_FILE_INFORMATION FileData; if (GetFileInformationByHandle(hFinal, &FileData)) { Data.dwFileAttributes = FileData.dwFileAttributes; Data.ftCreationTime = FileData.ftCreationTime; Data.ftLastAccessTime = FileData.ftLastAccessTime; Data.ftLastWriteTime = FileData.ftLastWriteTime; Data.nFileSizeHigh = FileData.nFileSizeHigh; Data.nFileSizeLow = FileData.nFileSizeLow; } CloseHandle(hFinal); } else if (GetLastError() != ERROR_SHARING_VIOLATION) { rc = RTErrConvertFromWin32(GetLastError()); RTUtf16Free(pwszPath); return rc; } } RTUtf16Free(pwszPath); /* * Setup the returned data. */ pObjInfo->cbObject = ((uint64_t)Data.nFileSizeHigh << 32) | (uint64_t)Data.nFileSizeLow; pObjInfo->cbAllocated = pObjInfo->cbObject; Assert(sizeof(uint64_t) == sizeof(Data.ftCreationTime)); RTTimeSpecSetNtTime(&pObjInfo->BirthTime, *(uint64_t *)&Data.ftCreationTime); RTTimeSpecSetNtTime(&pObjInfo->AccessTime, *(uint64_t *)&Data.ftLastAccessTime); RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, *(uint64_t *)&Data.ftLastWriteTime); pObjInfo->ChangeTime = pObjInfo->ModificationTime; pObjInfo->Attr.fMode = rtFsModeFromDos((Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, pszPath, strlen(pszPath)); /* * Requested attributes (we cannot provide anything actually). */ switch (enmAdditionalAttribs) { case RTFSOBJATTRADD_NOTHING: pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; break; case RTFSOBJATTRADD_UNIX: pObjInfo->Attr.enmAdditional = 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; /** @todo use volume serial number */ pObjInfo->Attr.u.Unix.INodeId = 0; /** @todo use fileid (see GetFileInformationByHandle). */ pObjInfo->Attr.u.Unix.fFlags = 0; pObjInfo->Attr.u.Unix.GenerationId = 0; pObjInfo->Attr.u.Unix.Device = 0; break; case RTFSOBJATTRADD_UNIX_OWNER: pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; pObjInfo->Attr.u.UnixOwner.uid = ~0U; 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 = ~0U; 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")); return VERR_INTERNAL_ERROR; } return VINF_SUCCESS; }