diff --git a/tools/profiler/IOInterposer.cpp b/tools/profiler/IOInterposer.cpp index 8570e6d5a4d..9f6ed9cd2e5 100644 --- a/tools/profiler/IOInterposer.cpp +++ b/tools/profiler/IOInterposer.cpp @@ -89,6 +89,40 @@ void VectorRemove(std::vector& vector, const T& element) } // anonymous namespace +IOInterposeObserver::Observation::Observation(Operation aOperation, + const char* aReference, + bool aShouldReport) + : mOperation(aOperation) + , mReference(aReference) + , mShouldReport(IOInterposer::IsObservedOperation(aOperation) && + aShouldReport) +{ + if (mShouldReport) { + mStart = TimeStamp::Now(); + } +} + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const TimeStamp& aStart, + const TimeStamp& aEnd, + const char* aReference) + : mOperation(aOperation) + , mStart(aStart) + , mEnd(aEnd) + , mReference(aReference) + , mShouldReport(false) +{ +} + +void +IOInterposeObserver::Observation::Report() +{ + if (mShouldReport) { + mEnd = TimeStamp::Now(); + IOInterposer::Report(*this); + } +} + // Flags tracking which operations are being observed IOInterposeObserver::Operation IOInterposer::sObservedOperations = IOInterposeObserver::OpNone; diff --git a/tools/profiler/IOInterposer.h b/tools/profiler/IOInterposer.h index 0d09ae67b75..10ac05c6a00 100644 --- a/tools/profiler/IOInterposer.h +++ b/tools/profiler/IOInterposer.h @@ -35,16 +35,25 @@ public: class Observation { protected: - Observation() - { - } + /** + * This constructor is for use by subclasses that are intended to take + * timing measurements via RAII. The |aShouldReport| parameter may be + * used to make the measurement and reporting conditional on the + * satisfaction of an arbitrary predicate that was evaluated + * in the subclass. Note that IOInterposer::IsObservedOperation() is + * always ANDed with aShouldReport, so the subclass does not need to + * include a call to that function explicitly. + */ + Observation(Operation aOperation, const char* aReference, + bool aShouldReport = true); + public: + /** + * Since this constructor accepts start and end times, it does *not* take + * its own timings, nor does it report itself. + */ Observation(Operation aOperation, const TimeStamp& aStart, - const TimeStamp& aEnd, const char* aReference) - : mOperation(aOperation), mStart(aStart), mEnd(aEnd), - mReference(aReference) - { - } + const TimeStamp& aEnd, const char* aReference); /** * Operation observed, this is either OpRead, OpWrite or OpFSync, @@ -92,7 +101,7 @@ public: } /** Request filename associated with the I/O operation, null if unknown */ - virtual const char* Filename() + virtual const char16_t* Filename() { return nullptr; } @@ -100,11 +109,16 @@ public: virtual ~Observation() { } + protected: + void + Report(); + Operation mOperation; TimeStamp mStart; TimeStamp mEnd; - const char* mReference; + const char* mReference; // Identifies the source of the Observation + bool mShouldReport; // Measure and report if true }; /** diff --git a/tools/profiler/NSPRInterposer.cpp b/tools/profiler/NSPRInterposer.cpp index 3999d414068..dfa57b20fa1 100644 --- a/tools/profiler/NSPRInterposer.cpp +++ b/tools/profiler/NSPRInterposer.cpp @@ -28,28 +28,14 @@ class NSPRIOAutoObservation : public IOInterposeObserver::Observation { public: NSPRIOAutoObservation(IOInterposeObserver::Operation aOp) - : mShouldObserve(IOInterposer::IsObservedOperation(aOp)) + : IOInterposeObserver::Observation(aOp, "NSPRIOInterposer") { - if (mShouldObserve) { - mOperation = aOp; - mStart = TimeStamp::Now(); - } } ~NSPRIOAutoObservation() { - if (mShouldObserve) { - mEnd = TimeStamp::Now(); - const char* ref = "NSPRIOInterposing"; - mReference = ref; - - // Report this auto observation - IOInterposer::Report(*this); - } + Report(); } - -private: - bool mShouldObserve; }; PRStatus PR_CALLBACK interposedClose(PRFileDesc* aFd) diff --git a/xpcom/build/PoisonIOInterposerMac.cpp b/xpcom/build/PoisonIOInterposerMac.cpp index 0b92ef6c4e1..24c5140ed62 100644 --- a/xpcom/build/PoisonIOInterposerMac.cpp +++ b/xpcom/build/PoisonIOInterposerMac.cpp @@ -21,15 +21,16 @@ #include "plstr.h" #include "prio.h" -#include #include -#include +#include +#include #include #include #include #include #include +#include namespace { @@ -54,48 +55,66 @@ bool IsIPCWrite(int fd, const struct stat &buf); class MacIOAutoObservation : public IOInterposeObserver::Observation { public: - MacIOAutoObservation(IOInterposeObserver::Operation aOp, - const char* aReference, int aFd) - : mShouldObserve(sIsEnabled && IOInterposer::IsObservedOperation(aOp) && - !IsDebugFile(aFd)) + MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd) + : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && + !IsDebugFile(aFd)) + , mFd(aFd) + , mHasQueriedFilename(false) + , mFilename(nullptr) { - if (mShouldObserve) { - mOperation = aOp; - mReference = aReference; - mStart = TimeStamp::Now(); - } } - MacIOAutoObservation(IOInterposeObserver::Operation aOp, - const char* aReference, int aFd, const void *aBuf, - size_t aCount) - : mShouldObserve(sIsEnabled && IOInterposer::IsObservedOperation(aOp) && - !IsDebugFile(aFd)) + MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd, + const void *aBuf, size_t aCount) + : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && + !IsDebugFile(aFd) && + IsValidWrite(aFd, aBuf, aCount)) + , mFd(aFd) + , mHasQueriedFilename(false) + , mFilename(nullptr) { - if (mShouldObserve) { - mShouldObserve = IsValidWrite(aFd, aBuf, aCount); - if (mShouldObserve) { - mOperation = aOp; - mReference = aReference; - mStart = TimeStamp::Now(); - } - } } + // Custom implementation of IOInterposeObserver::Observation::Filename + const char16_t* Filename() MOZ_OVERRIDE; + ~MacIOAutoObservation() { - if (mShouldObserve) { - mEnd = TimeStamp::Now(); - - // Report this observation - IOInterposer::Report(*this); + Report(); + if (mFilename) { + NS_Free(mFilename); + mFilename = nullptr; } } private: - bool mShouldObserve; + int mFd; + bool mHasQueriedFilename; + char16_t* mFilename; + static const char* sReference; }; +const char* MacIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +const char16_t* MacIOAutoObservation::Filename() +{ + // If mHasQueriedFilename is true, then we already have it + if (mHasQueriedFilename) { + return mFilename; + } + char filename[MAXPATHLEN]; + if (fcntl(mFd, F_GETPATH, filename) != -1) { + mFilename = UTF8ToNewUnicode(nsDependentCString(filename)); + } else { + mFilename = nullptr; + } + mHasQueriedFilename = true; + + // Return filename + return mFilename; +} + /****************************** Write Validation ******************************/ // We want to detect "actual" writes, not IPC. Some IPC mechanisms are @@ -194,9 +213,7 @@ typedef ssize_t (*aio_write_t)(struct aiocb *aiocbp); ssize_t wrap_aio_write(struct aiocb *aiocbp); FuncData aio_write_data = { 0, (void*) wrap_aio_write, (void*) aio_write }; ssize_t wrap_aio_write(struct aiocb *aiocbp) { - const char* ref = "aio_write"; - MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, - aiocbp->aio_fildes); + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aiocbp->aio_fildes); aio_write_t old_write = (aio_write_t) aio_write_data.Buffer; return old_write(aiocbp); @@ -207,8 +224,7 @@ ssize_t wrap_aio_write(struct aiocb *aiocbp) { typedef ssize_t (*pwrite_t)(int fd, const void *buf, size_t nbyte, off_t offset); template ssize_t wrap_pwrite_temp(int fd, const void *buf, size_t nbyte, off_t offset) { - const char* ref = "pwrite_*"; - MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd); + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd); pwrite_t old_write = (pwrite_t) foo.Buffer; return old_write(fd, buf, nbyte, offset); } @@ -229,9 +245,7 @@ DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL"); typedef ssize_t (*writev_t)(int fd, const struct iovec *iov, int iovcnt); template ssize_t wrap_writev_temp(int fd, const struct iovec *iov, int iovcnt) { - const char* ref = "pwrite_*"; - MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd, nullptr, - iovcnt); + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd, nullptr, iovcnt); writev_t old_write = (writev_t) foo.Buffer; return old_write(fd, iov, iovcnt); } @@ -251,9 +265,7 @@ DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL"); typedef ssize_t (*write_t)(int fd, const void *buf, size_t count); template ssize_t wrap_write_temp(int fd, const void *buf, size_t count) { - const char* ref = "pwrite_*"; - MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd, buf, - count); + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd, buf, count); write_t old_write = (write_t) foo.Buffer; return old_write(fd, buf, count); } diff --git a/xpcom/build/PoisonIOInterposerWin.cpp b/xpcom/build/PoisonIOInterposerWin.cpp index 22715a7aad8..bbee032eb7e 100644 --- a/xpcom/build/PoisonIOInterposerWin.cpp +++ b/xpcom/build/PoisonIOInterposerWin.cpp @@ -15,6 +15,7 @@ #include #include "mozilla/Assertions.h" +#include "mozilla/FileUtilsWin.h" #include "mozilla/IOInterposer.h" #include "mozilla/Mutex.h" #include "mozilla/TimeStamp.h" @@ -76,32 +77,77 @@ class WinIOAutoObservation : public IOInterposeObserver::Observation { public: WinIOAutoObservation(IOInterposeObserver::Operation aOp, - const char* aReference, HANDLE aFileHandle) - : mFileHandle(aFileHandle), - mShouldObserve(IOInterposer::IsObservedOperation(aOp) && - !IsDebugFile(reinterpret_cast(aFileHandle))) + HANDLE aFileHandle, const LARGE_INTEGER* aOffset) + : IOInterposeObserver::Observation(aOp, sReference, + !IsDebugFile(reinterpret_cast( + aFileHandle))) + , mFileHandle(aFileHandle) + , mHasQueriedFilename(false) + , mFilename(nullptr) { - if (mShouldObserve) { - mOperation = aOp; - mReference = aReference; - mStart = TimeStamp::Now(); + if (mShouldReport) { + mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0; } } + WinIOAutoObservation(IOInterposeObserver::Operation aOp, nsAString& aFilename) + : IOInterposeObserver::Observation(aOp, sReference) + , mFileHandle(nullptr) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + if (mShouldReport) { + nsAutoString dosPath; + if (NtPathToDosPath(aFilename, dosPath)) { + mFilename = ToNewUnicode(dosPath); + mHasQueriedFilename = true; + } + mOffset.QuadPart = 0; + } + } + + // Custom implementation of IOInterposeObserver::Observation::Filename + const char16_t* Filename() MOZ_OVERRIDE; + ~WinIOAutoObservation() { - if (mShouldObserve) { - mEnd = TimeStamp::Now(); - // Report this observation - IOInterposer::Report(*this); + Report(); + if (mFilename) { + MOZ_ASSERT(mHasQueriedFilename); + NS_Free(mFilename); + mFilename = nullptr; } } private: HANDLE mFileHandle; - bool mShouldObserve; + LARGE_INTEGER mOffset; + bool mHasQueriedFilename; + char16_t* mFilename; + static const char* sReference; }; +const char* WinIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +const char16_t* WinIOAutoObservation::Filename() +{ + // If mHasQueriedFilename is true, then filename is already stored in mFilename + if (mHasQueriedFilename) { + return mFilename; + } + + nsAutoString utf16Filename; + if (HandleToFilename(mFileHandle, mOffset, utf16Filename)) { + // Heap allocate with leakable memory + mFilename = ToNewUnicode(utf16Filename); + } + mHasQueriedFilename = true; + + // Return filename + return mFilename; +} + /*************************** IO Interposing Methods ***************************/ // Function pointers to original functions @@ -121,8 +167,8 @@ static NTSTATUS WINAPI InterposedNtWriteFile( PULONG aKey) { // Report IO - const char* ref = "NtWriteFile"; - WinIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, aFileHandle); + WinIOAutoObservation timer(IOInterposeObserver::OpWrite, aFileHandle, + aOffset); // Something is badly wrong if this function is undefined MOZ_ASSERT(gOriginalNtWriteFile); @@ -154,8 +200,8 @@ static NTSTATUS WINAPI InterposedNtWriteFileGather( PULONG aKey) { // Report IO - const char* ref = "NtWriteFileGather"; - WinIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, aFileHandle); + WinIOAutoObservation timer(IOInterposeObserver::OpWrite, aFileHandle, + aOffset); // Something is badly wrong if this function is undefined MOZ_ASSERT(gOriginalNtWriteFileGather); diff --git a/xpcom/io/FileUtilsWin.cpp b/xpcom/io/FileUtilsWin.cpp new file mode 100644 index 00000000000..3e9c5019e1c --- /dev/null +++ b/xpcom/io/FileUtilsWin.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileUtilsWin.h" + +#include +#include + +#include "nsWindowsHelpers.h" + +namespace { + +// Scoped type used by HandleToFilename +struct ScopedMappedViewTraits +{ + typedef void* type; + static void* empty() { return nullptr; } + static void release(void* ptr) { UnmapViewOfFile(ptr); } +}; +typedef mozilla::Scoped ScopedMappedView; + +} // anonymous namespace + +namespace mozilla { + +bool +HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset, + nsAString& aFilename) +{ + aFilename.Truncate(); + // This implementation is nice because it uses fully documented APIs that + // are available on all Windows versions that we support. + nsAutoHandle fileMapping(CreateFileMapping(aHandle, nullptr, PAGE_READONLY, + 0, 1, nullptr)); + if (!fileMapping) { + return false; + } + ScopedMappedView view(MapViewOfFile(fileMapping, FILE_MAP_READ, + aOffset.HighPart, aOffset.LowPart, 1)); + if (!view) { + return false; + } + nsAutoString mappedFilename; + DWORD len = 0; + SetLastError(ERROR_SUCCESS); + do { + mappedFilename.SetLength(mappedFilename.Length() + MAX_PATH); + len = GetMappedFileNameW(GetCurrentProcess(), view, + mappedFilename.BeginWriting(), + mappedFilename.Length()); + } while (!len && GetLastError() == ERROR_INSUFFICIENT_BUFFER); + if (!len) { + return false; + } + mappedFilename.Truncate(len); + return NtPathToDosPath(mappedFilename, aFilename); +} + +} // namespace mozilla + diff --git a/xpcom/io/FileUtilsWin.h b/xpcom/io/FileUtilsWin.h new file mode 100644 index 00000000000..01a551fa7a1 --- /dev/null +++ b/xpcom/io/FileUtilsWin.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_FileUtilsWin_h +#define mozilla_FileUtilsWin_h + +#include + +#include "mozilla/Scoped.h" +#include "nsStringGlue.h" + +namespace mozilla { + +inline bool +NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath) +{ + aDosPath.Truncate(); + if (aNtPath.IsEmpty()) { + return true; + } + NS_NAMED_LITERAL_STRING(symLinkPrefix, "\\??\\"); + uint32_t ntPathLen = aNtPath.Length(); + uint32_t symLinkPrefixLen = symLinkPrefix.Length(); + if (ntPathLen >= 6 && aNtPath.CharAt(5) == L':' && + ntPathLen >= symLinkPrefixLen && + Substring(aNtPath, 0, symLinkPrefixLen).Equals(symLinkPrefix)) { + // Symbolic link for DOS device. Just strip off the prefix. + aDosPath = aNtPath; + aDosPath.Cut(0, 4); + return true; + } + nsAutoString logicalDrives; + DWORD len = 0; + while(true) { + len = GetLogicalDriveStringsW(len, logicalDrives.BeginWriting()); + if (!len) { + return false; + } else if (len > logicalDrives.Length()) { + logicalDrives.SetLength(len); + } else { + break; + } + } + const char16_t* cur = logicalDrives.BeginReading(); + const char16_t* end = logicalDrives.EndReading(); + nsString targetPath; + targetPath.SetLength(MAX_PATH); + wchar_t driveTemplate[] = L" :"; + do { + // Unfortunately QueryDosDevice doesn't support the idiom for querying the + // output buffer size, so it may require retries. + driveTemplate[0] = *cur; + DWORD targetPathLen = 0; + SetLastError(ERROR_SUCCESS); + while (true) { + targetPathLen = QueryDosDeviceW(driveTemplate, targetPath.BeginWriting(), + targetPath.Length()); + if (targetPathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + break; + } + targetPath.SetLength(targetPath.Length() * 2); + } + if (targetPathLen) { + // Need to use wcslen here because targetPath contains embedded NULL chars + size_t firstTargetPathLen = wcslen(targetPath.get()); + const char16_t* pathComponent = aNtPath.BeginReading() + + firstTargetPathLen; + bool found = _wcsnicmp(aNtPath.BeginReading(), targetPath.get(), + firstTargetPathLen) == 0 && + *pathComponent == L'\\'; + if (found) { + aDosPath = driveTemplate; + aDosPath += pathComponent; + return true; + } + } + // Advance to the next NUL character in logicalDrives + while (*cur++); + } while (cur != end); + // Code for handling UNC paths would go here, if eventually required. +#if defined(DEBUG) + NS_NAMED_LITERAL_STRING(deviceMupPrefix, "\\Device\\Mup\\"); + uint32_t deviceMupPrefixLen = deviceMupPrefix.Length(); + if (ntPathLen >= deviceMupPrefixLen && + Substring(aNtPath, 0, deviceMupPrefixLen).Equals(deviceMupPrefix)) { + NS_WARNING("UNC paths not yet supported in NtPathToDosPath"); + } +#endif // defined(DEBUG) + return false; +} + +bool +HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset, + nsAString& aFilename); + +} // namespace mozilla + +#endif // mozilla_FileUtilsWin_h diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build index 74d1e8e1a92..998127cd6b0 100644 --- a/xpcom/io/moz.build +++ b/xpcom/io/moz.build @@ -54,7 +54,11 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'os2': ] elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': EXPORTS += ['nsLocalFileWin.h'] + EXPORTS.mozilla += [ + 'FileUtilsWin.h', + ] SOURCES += [ + 'FileUtilsWin.cpp', 'nsLocalFileWin.cpp', ] else: diff --git a/xpcom/tests/windows/Makefile.in b/xpcom/tests/windows/Makefile.in index e03fbaedaad..d06750c6bd2 100644 --- a/xpcom/tests/windows/Makefile.in +++ b/xpcom/tests/windows/Makefile.in @@ -5,4 +5,4 @@ include $(topsrcdir)/config/rules.mk -OS_LIBS += $(call EXPAND_LIBNAME,rpcrt4 uuid) +OS_LIBS += $(call EXPAND_LIBNAME,rpcrt4 uuid mpr) diff --git a/xpcom/tests/windows/TestNtPathToDosPath.cpp b/xpcom/tests/windows/TestNtPathToDosPath.cpp new file mode 100644 index 00000000000..4533f7528df --- /dev/null +++ b/xpcom/tests/windows/TestNtPathToDosPath.cpp @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestHarness.h" + +#include +#include + +#include "mozilla/FileUtilsWin.h" + +class DriveMapping +{ +public: + DriveMapping(const nsAString& aRemoteUNCPath); + ~DriveMapping(); + + bool + Init(); + bool + ChangeDriveLetter(); + wchar_t + GetDriveLetter() { return mDriveLetter; } + +private: + bool + DoMapping(); + void + Disconnect(wchar_t aDriveLetter); + + wchar_t mDriveLetter; + nsString mRemoteUNCPath; +}; + +DriveMapping::DriveMapping(const nsAString& aRemoteUNCPath) + : mRemoteUNCPath(aRemoteUNCPath) + , mDriveLetter(0) +{ +} + +bool +DriveMapping::Init() +{ + if (mDriveLetter) { + return false; + } + return DoMapping(); +} + +bool +DriveMapping::DoMapping() +{ + wchar_t drvTemplate[] = L" :"; + NETRESOURCEW netRes = {0}; + netRes.dwType = RESOURCETYPE_DISK; + netRes.lpLocalName = drvTemplate; + netRes.lpRemoteName = mRemoteUNCPath.BeginWriting(); + wchar_t driveLetter = L'D'; + DWORD result = NO_ERROR; + do { + drvTemplate[0] = driveLetter; + result = WNetAddConnection2W(&netRes, nullptr, nullptr, CONNECT_TEMPORARY); + } while (result == ERROR_ALREADY_ASSIGNED && ++driveLetter <= L'Z'); + if (result != NO_ERROR) { + return false; + } + mDriveLetter = driveLetter; + return true; +} + +bool +DriveMapping::ChangeDriveLetter() +{ + wchar_t prevDriveLetter = mDriveLetter; + bool result = DoMapping(); + MOZ_ASSERT(mDriveLetter != prevDriveLetter); + if (result && prevDriveLetter) { + Disconnect(prevDriveLetter); + } + return result; +} + +void +DriveMapping::Disconnect(wchar_t aDriveLetter) +{ + wchar_t drvTemplate[] = {aDriveLetter, L':', L'\0'}; + DWORD result = WNetCancelConnection2W(drvTemplate, 0, TRUE); + MOZ_ASSERT(result == NO_ERROR); +} + +DriveMapping::~DriveMapping() +{ + if (mDriveLetter) { + Disconnect(mDriveLetter); + } +} + +bool +DriveToNtPath(const wchar_t aDriveLetter, nsAString& aNtPath) +{ + const wchar_t drvTpl[] = {aDriveLetter, L':', L'\0'}; + aNtPath.SetLength(MAX_PATH); + DWORD pathLen; + while (true) { + pathLen = QueryDosDeviceW(drvTpl, aNtPath.BeginWriting(), aNtPath.Length()); + if (pathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + break; + } + aNtPath.SetLength(aNtPath.Length() * 2); + } + if (!pathLen) { + return false; + } + // aNtPath contains embedded NULLs, so we need to figure out the real length + // via wcslen. + aNtPath.SetLength(wcslen(aNtPath.BeginReading())); + return true; +} + +bool +TestNtPathToDosPath(const wchar_t* aNtPath, + const wchar_t* aExpectedDosPath) +{ + nsAutoString output; + bool result = mozilla::NtPathToDosPath(nsDependentString(aNtPath), output); + return result && output == aExpectedDosPath; +} + +int main(int argc, char* argv[]) +{ + ScopedXPCOM xpcom("NtPathToDosPath"); + if (xpcom.failed()) { + fail("XPCOM Startup"); + return 1; + } + nsAutoString cDrive; + if (!DriveToNtPath(L'C', cDrive)) { + fail("Querying for this machine's C:"); + return 1; + } + + int result = 0; + + // empty string + if (!TestNtPathToDosPath(L"", L"")) { + fail("Empty string"); + result = 1; + } + // non-existent device, must fail + if (TestNtPathToDosPath(L"\\Device\\ThisDeviceDoesNotExist\\Foo", nullptr)) { + fail("Non-existent device"); + result = 1; + } + // base case + nsAutoString testPath(cDrive); + testPath.Append(L"\\Foo"); + if (!TestNtPathToDosPath(testPath.get(), L"C:\\Foo")) { + fail("Base case"); + result = 1; + } + // drive letters as symbolic links (NtCreateFile uses these) + if (!TestNtPathToDosPath(L"\\??\\C:\\Foo", L"C:\\Foo")) { + fail("Path specified as symbolic link"); + result = 1; + } + // other symbolic links (should fail) + if (TestNtPathToDosPath(L"\\??\\MountPointManager", nullptr)) { + fail("Other symbolic link"); + result = 1; + } + // socket (should fail) + if (TestNtPathToDosPath(L"\\Device\\Afd\\Endpoint", nullptr)) { + fail("Socket"); + result = 1; + } + // currently UNC paths that are not mapped to drive letters are unsupported, + // so this should fail + if (TestNtPathToDosPath(L"\\Device\\Mup\\127.0.0.1\\C$", nullptr)) { + fail("Unmapped UNC path"); + result = 1; + } + DriveMapping drvMapping(NS_LITERAL_STRING("\\\\127.0.0.1\\C$")); + // Only run these tests if we were able to map; some machines don't have perms + if (drvMapping.Init()) { + wchar_t expected[] = L" :\\"; + expected[0] = drvMapping.GetDriveLetter(); + nsAutoString networkPath; + if (!DriveToNtPath(drvMapping.GetDriveLetter(), networkPath)) { + fail("Querying network drive"); + return 1; + } + networkPath += L"\\"; + if (!TestNtPathToDosPath(networkPath.get(), expected)) { + fail("Mapped UNC path"); + result = 1; + } + // NtPathToDosPath must correctly handle paths whose drive letter mapping has + // changed. We need to test this because the APIs called by NtPathToDosPath + // return different info if this has happened. + if (!drvMapping.ChangeDriveLetter()) { + fail("Change drive letter"); + return 1; + } + expected[0] = drvMapping.GetDriveLetter(); + if (!DriveToNtPath(drvMapping.GetDriveLetter(), networkPath)) { + fail("Querying second network drive"); + return 1; + } + networkPath += L"\\"; + if (!TestNtPathToDosPath(networkPath.get(), expected)) { + fail("Re-mapped UNC path"); + result = 1; + } + } + + return result; +} + diff --git a/xpcom/tests/windows/moz.build b/xpcom/tests/windows/moz.build index 745d1173a96..8d7955e6b3e 100644 --- a/xpcom/tests/windows/moz.build +++ b/xpcom/tests/windows/moz.build @@ -6,5 +6,6 @@ CPP_UNIT_TESTS += [ 'TestCOM.cpp', + 'TestNtPathToDosPath.cpp', ]