qtbase/src/corelib/io/qstorageinfo_win.cpp
Martin Storsjö 6b6ddd377d Windows: Fix using NT APIs on i386
While the MS docs don't include the NTAPI attribute here, these
functions are presented with stdcall name mangling in the ntdll.lib
import library in the Windows SDK (and likewise in the mingw import
libraries). Therefore, add NTAPI (which expands to __stdcall) here
to fix linking.

Change-Id: I7d59b6a3c64823743498497a2ddc869e5556432c
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2025-04-06 04:47:33 +03:00

261 lines
8.8 KiB
C++

// Copyright (C) 2014 Ivan Komissarov <ABBAPOH@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qstorageinfo_p.h"
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qmutex.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/private/wcharhelpers_win_p.h>
#include "qfilesystementry_p.h"
#include "qntdll_p.h"
extern "C" NTSTATUS NTSYSCALLAPI NTAPI NtQueryVolumeInformationFile(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG,
FS_INFORMATION_CLASS);
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static const int defaultBufferSize = MAX_PATH + 1;
static QString canonicalPath(const QString &rootPath)
{
QString path = QDir::toNativeSeparators(QFileInfo(rootPath).canonicalFilePath());
if (path.isEmpty())
return path;
if (path.startsWith("\\\\?\\"_L1))
path.remove(0, 4);
if (path.length() < 2 || path.at(1) != u':')
return QString();
path[0] = path[0].toUpper();
if (!(path.at(0).unicode() >= 'A' && path.at(0).unicode() <= 'Z'))
return QString();
if (!path.endsWith(u'\\'))
path.append(u'\\');
return path;
}
void QStorageInfoPrivate::initRootPath()
{
// Do not unnecessarily call QFileInfo::canonicalFilePath() if the path is
// already a drive root since it may hang on network drives.
const QString path = QFileSystemEntry::isDriveRootPath(rootPath)
? QDir::toNativeSeparators(rootPath)
: canonicalPath(rootPath);
if (path.isEmpty()) {
valid = ready = false;
return;
}
// ### test if disk mounted to folder on other disk
wchar_t buffer[defaultBufferSize];
if (::GetVolumePathName(reinterpret_cast<const wchar_t *>(path.utf16()), buffer, defaultBufferSize))
rootPath = QDir::fromNativeSeparators(QString::fromWCharArray(buffer));
else
valid = ready = false;
}
static inline QByteArray getDevice(const QString &rootPath)
{
const QString path = QDir::toNativeSeparators(rootPath);
const UINT type = ::GetDriveType(reinterpret_cast<const wchar_t *>(path.utf16()));
if (type == DRIVE_REMOTE) {
QVarLengthArray<char, 256> buffer(256);
DWORD bufferLength = buffer.size();
DWORD result;
UNIVERSAL_NAME_INFO *remoteNameInfo;
do {
buffer.resize(bufferLength);
remoteNameInfo = reinterpret_cast<UNIVERSAL_NAME_INFO *>(buffer.data());
result = ::WNetGetUniversalName(reinterpret_cast<const wchar_t *>(path.utf16()),
UNIVERSAL_NAME_INFO_LEVEL,
remoteNameInfo,
&bufferLength);
} while (result == ERROR_MORE_DATA);
if (result == NO_ERROR)
return QString::fromWCharArray(remoteNameInfo->lpUniversalName).toUtf8();
return QByteArray();
}
wchar_t deviceBuffer[51];
if (::GetVolumeNameForVolumeMountPoint(reinterpret_cast<const wchar_t *>(path.utf16()),
deviceBuffer,
sizeof(deviceBuffer) / sizeof(wchar_t))) {
return QString::fromWCharArray(deviceBuffer).toLatin1();
}
return QByteArray();
}
void QStorageInfoPrivate::doStat()
{
valid = ready = true;
initRootPath();
if (!valid || !ready)
return;
retrieveVolumeInfo();
if (!valid || !ready)
return;
device = getDevice(rootPath);
retrieveDiskFreeSpace();
if (!queryStorageProperty())
queryFileFsSectorSizeInformation();
}
void QStorageInfoPrivate::retrieveVolumeInfo()
{
const UINT oldmode = ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
const QString path = QDir::toNativeSeparators(rootPath);
wchar_t nameBuffer[defaultBufferSize];
wchar_t fileSystemTypeBuffer[defaultBufferSize];
DWORD fileSystemFlags = 0;
const bool result = ::GetVolumeInformation(reinterpret_cast<const wchar_t *>(path.utf16()),
nameBuffer,
defaultBufferSize,
nullptr,
nullptr,
&fileSystemFlags,
fileSystemTypeBuffer,
defaultBufferSize);
if (!result) {
ready = false;
valid = ::GetLastError() == ERROR_NOT_READY;
} else {
fileSystemType = QString::fromWCharArray(fileSystemTypeBuffer).toLatin1();
name = QString::fromWCharArray(nameBuffer);
readOnly = (fileSystemFlags & FILE_READ_ONLY_VOLUME) != 0;
}
::SetErrorMode(oldmode);
}
void QStorageInfoPrivate::retrieveDiskFreeSpace()
{
const UINT oldmode = ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
const QString path = QDir::toNativeSeparators(rootPath);
ready = ::GetDiskFreeSpaceEx(reinterpret_cast<const wchar_t *>(path.utf16()),
PULARGE_INTEGER(&bytesAvailable),
PULARGE_INTEGER(&bytesTotal),
PULARGE_INTEGER(&bytesFree));
::SetErrorMode(oldmode);
}
QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
{
QList<QStorageInfo> volumes;
QString driveName = QStringLiteral("A:/");
const UINT oldmode = ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
quint32 driveBits = quint32(::GetLogicalDrives()) & 0x3ffffff;
::SetErrorMode(oldmode);
while (driveBits) {
if (driveBits & 1) {
QStorageInfo drive(driveName);
if (!drive.rootPath().isEmpty()) // drive exists, but not mounted
volumes.append(drive);
}
driveName[0] = QChar(driveName[0].unicode() + 1);
driveBits = driveBits >> 1;
}
return volumes;
}
bool QStorageInfoPrivate::queryStorageProperty()
{
QString path = QDir::toNativeSeparators(uR"(\\.\)" + rootPath);
if (path.endsWith(u'\\'))
path.chop(1);
HANDLE handle = CreateFile(qt_castToWchar(path),
0, // no access to the drive
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (handle == INVALID_HANDLE_VALUE)
return false;
STORAGE_PROPERTY_QUERY spq;
memset(&spq, 0, sizeof(spq));
spq.PropertyId = StorageAccessAlignmentProperty;
spq.QueryType = PropertyStandardQuery;
STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR saad;
memset(&saad, 0, sizeof(saad));
DWORD bytes = 0;
BOOL result = DeviceIoControl(handle,
IOCTL_STORAGE_QUERY_PROPERTY,
&spq, sizeof(spq),
&saad, sizeof(saad),
&bytes,
nullptr);
CloseHandle(handle);
if (result)
blockSize = int(saad.BytesPerPhysicalSector);
return result;
}
void QStorageInfoPrivate::queryFileFsSectorSizeInformation()
{
FILE_FS_SECTOR_SIZE_INFORMATION ffssi;
memset(&ffssi, 0, sizeof(ffssi));
HANDLE handle = nullptr;
OBJECT_ATTRIBUTES attrs;
memset(&attrs, 0, sizeof(attrs));
IO_STATUS_BLOCK isb;
memset(&isb, 0, sizeof(isb));
QString path = QDir::toNativeSeparators(uR"(\??\\)" + rootPath);
if (!path.endsWith(u'\\'))
path.append(u'\\');
UNICODE_STRING name;
::RtlInitUnicodeString(&name, qt_castToWchar(path));
InitializeObjectAttributes(&attrs, &name, 0, nullptr, nullptr);
NTSTATUS status = ::NtCreateFile(&handle,
FILE_READ_ATTRIBUTES,
&attrs,
&isb,
nullptr,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
0,
nullptr,
0);
if (!NT_SUCCESS(status))
return;
memset(&isb, 0, sizeof(isb));
status = ::NtQueryVolumeInformationFile(handle,
&isb,
&ffssi,
sizeof(ffssi),
FS_INFORMATION_CLASS(10)); // FileFsSectorSizeInformation
CloseHandle(handle);
if (NT_SUCCESS(status))
blockSize = ffssi.PhysicalBytesPerSectorForAtomicity;
}
QT_END_NAMESPACE