Bug 902587 - Part 2B: filename method for PoisonIOInterposer; r=BenWa,ehsan
authorAaron Klotz <aklotz@mozilla.com>
Fri, 31 Jan 2014 20:14:03 -0700
changeset 182510 951fb38a568d2ca646b38dd25ad81cfea111d2d6
parent 182509 dffbfd00ac4e67e06211b57d877d7d05466b4e9f
child 182511 61a591c422b7e436c717a04a3545c355315c273b
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBenWa, ehsan
bugs902587
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 902587 - Part 2B: filename method for PoisonIOInterposer; r=BenWa,ehsan
tools/profiler/IOInterposer.cpp
tools/profiler/IOInterposer.h
tools/profiler/NSPRInterposer.cpp
xpcom/build/PoisonIOInterposerMac.cpp
xpcom/build/PoisonIOInterposerWin.cpp
xpcom/io/FileUtilsWin.cpp
xpcom/io/FileUtilsWin.h
xpcom/io/moz.build
xpcom/tests/windows/Makefile.in
xpcom/tests/windows/TestNtPathToDosPath.cpp
xpcom/tests/windows/moz.build
--- a/tools/profiler/IOInterposer.cpp
+++ b/tools/profiler/IOInterposer.cpp
@@ -84,16 +84,50 @@ void VectorRemove(std::vector<T>& vector
 {
   typename std::vector<T>::iterator newEnd = std::remove(vector.begin(),
                                                          vector.end(), element);
   vector.erase(newEnd, vector.end());
 }
 
 } // 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;
 
 /* static */ void IOInterposer::Init()
 {
   // Don't initialize twice...
   if (sObserverLists) {
--- a/tools/profiler/IOInterposer.h
+++ b/tools/profiler/IOInterposer.h
@@ -30,26 +30,35 @@ public:
     OpWriteFSync = (OpWrite | OpFSync),
     OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose)
   };
 
   /** A representation of an I/O observation  */
   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,
      * combinations of these flags are only used when registering observers.
      */
     Operation ObservedOperation() const
     {
       return mOperation;
@@ -87,29 +96,34 @@ public:
      * I.e. typically the platform specific function that did the IO.
      */
     const char* Reference() const
     {
       return mReference;
     }
 
     /** Request filename associated with the I/O operation, null if unknown */
-    virtual const char* Filename()
+    virtual const char16_t* Filename()
     {
       return nullptr;
     }
 
     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
   };
 
   /**
    * Invoked whenever an implementation of the IOInterposeObserver should
    * observe aObservation. Implement this and do your thing...
    * But do consider if it is wise to use IO functions in this method, they are
    * likely to cause recursion :)
    * At least, see PoisonIOInterposer.h and register your handle as a debug file
--- a/tools/profiler/NSPRInterposer.cpp
+++ b/tools/profiler/NSPRInterposer.cpp
@@ -23,38 +23,24 @@ PRFileInfo64FN sFileInfo64Fn = nullptr;
 /**
  * RAII class for timing the duration of an NSPR I/O call and reporting the
  * result to the IOInterposeObserver API.
  */
 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)
 {
   // If we don't have a valid original function pointer something is very wrong.
   NS_ASSERTION(sCloseFn, "NSPR IO Interposing: sCloseFn is NULL");
 
   NSPRIOAutoObservation timer(IOInterposeObserver::OpClose);
--- a/xpcom/build/PoisonIOInterposerMac.cpp
+++ b/xpcom/build/PoisonIOInterposerMac.cpp
@@ -16,25 +16,26 @@
 #include "mozilla/Scoped.h"
 #include "mozilla/Telemetry.h"
 #include "nsPrintfCString.h"
 #include "nsStackWalk.h"
 #include "nsTraceRefcntImpl.h"
 #include "plstr.h"
 #include "prio.h"
 
+#include <algorithm>
 #include <vector>
-#include <algorithm>
-#include <string.h>
 
+#include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/uio.h>
 #include <aio.h>
 #include <dlfcn.h>
+#include <fcntl.h>
 
 namespace {
 
 using namespace mozilla;
 
 // Bit tracking if poisoned writes are enabled
 static bool sIsEnabled = false;
 
@@ -49,58 +50,76 @@ bool IsIPCWrite(int fd, const struct sta
 
 /**
  * RAII class for timing the duration of an I/O call and reporting the result
  * to the IOInterposeObserver API.
  */
 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
 // implemented with file descriptors, so filter them out.
 bool IsIPCWrite(int fd, const struct stat &buf) {
   if ((buf.st_mode & S_IFMT) == S_IFIFO) {
     return true;
   }
@@ -189,31 +208,28 @@ struct FuncData {
                          // 'Function' after it has been replaced.
 };
 
 // Wrap aio_write. We have not seen it before, so just assert/report it.
 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);
 }
 
 // Wrap pwrite-like functions.
 // We have not seen them before, so just assert/report it.
 typedef ssize_t (*pwrite_t)(int fd, const void *buf, size_t nbyte, off_t offset);
 template<FuncData &foo>
 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);
 }
 
 // Define a FuncData for a pwrite-like functions.
 #define DEFINE_PWRITE_DATA(X, NAME)                                        \
 FuncData X ## _data = { NAME, (void*) wrap_pwrite_temp<X ## _data> };      \
 
@@ -224,19 +240,17 @@ DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2
 DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003");
 // This exists on 64 bit OS X
 DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL");
 
 
 typedef ssize_t (*writev_t)(int fd, const struct iovec *iov, int iovcnt);
 template<FuncData &foo>
 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);
 }
 
 // Define a FuncData for a writev-like functions.
 #define DEFINE_WRITEV_DATA(X, NAME)                                   \
 FuncData X ## _data = { NAME, (void*) wrap_writev_temp<X ## _data> }; \
 
@@ -246,19 +260,17 @@ DEFINE_WRITEV_DATA(writev, "writev");
 DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
 DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003");
 // This exists on 64 bit OS X
 DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL");
 
 typedef ssize_t (*write_t)(int fd, const void *buf, size_t count);
 template<FuncData &foo>
 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);
 }
 
 // Define a FuncData for a write-like functions.
 #define DEFINE_WRITE_DATA(X, NAME)                                   \
 FuncData X ## _data = { NAME, (void*) wrap_write_temp<X ## _data> }; \
 
--- a/xpcom/build/PoisonIOInterposerWin.cpp
+++ b/xpcom/build/PoisonIOInterposerWin.cpp
@@ -10,16 +10,17 @@
 #include <stdio.h>
 #include <vector>
 
 #include <io.h>
 #include <windows.h>
 #include <winternl.h>
 
 #include "mozilla/Assertions.h"
+#include "mozilla/FileUtilsWin.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/TimeStamp.h"
 #include "nsTArray.h"
 #include "nsWindowsDllInterceptor.h"
 #include "plstr.h"
 
 using namespace mozilla;
@@ -71,42 +72,87 @@ typedef NTSTATUS (WINAPI *NtWriteFileGat
 /**
  * RAII class for timing the duration of an I/O call and reporting the result
  * to the IOInterposeObserver API.
  */
 class WinIOAutoObservation : public IOInterposeObserver::Observation
 {
 public:
   WinIOAutoObservation(IOInterposeObserver::Operation aOp,
-                       const char* aReference, HANDLE aFileHandle)
-    : mFileHandle(aFileHandle),
-      mShouldObserve(IOInterposer::IsObservedOperation(aOp) &&
-                     !IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle)))
+                       HANDLE aFileHandle, const LARGE_INTEGER* aOffset)
+    : IOInterposeObserver::Observation(aOp, sReference,
+                                       !IsDebugFile(reinterpret_cast<intptr_t>(
+                                           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
 static NtWriteFileFn          gOriginalNtWriteFile;
 static NtWriteFileGatherFn    gOriginalNtWriteFileGather;
 
 // Interposed NtWriteFile function
 static NTSTATUS WINAPI InterposedNtWriteFile(
@@ -116,18 +162,18 @@ static NTSTATUS WINAPI InterposedNtWrite
   PVOID                         aApcCtx,
   PIO_STATUS_BLOCK              aIoStatus,
   PVOID                         aBuffer,
   ULONG                         aLength,
   PLARGE_INTEGER                aOffset,
   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);
 
   // Execute original function
   return gOriginalNtWriteFile(
     aFileHandle,
     aEvent,
@@ -149,18 +195,18 @@ static NTSTATUS WINAPI InterposedNtWrite
   PVOID                         aApcCtx,
   PIO_STATUS_BLOCK              aIoStatus,
   FILE_SEGMENT_ELEMENT*         aSegments,
   ULONG                         aLength,
   PLARGE_INTEGER                aOffset,
   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);
 
   // Execute original function
   return gOriginalNtWriteFileGather(
     aFileHandle,
     aEvent,
new file mode 100644
--- /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 <windows.h>
+#include <psapi.h>
+
+#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<ScopedMappedViewTraits> 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
+
new file mode 100644
--- /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 <windows.h>
+
+#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
--- a/xpcom/io/moz.build
+++ b/xpcom/io/moz.build
@@ -49,17 +49,21 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'co
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'os2':
     EXPORTS += ['nsLocalFileOS2.h']
     SOURCES += [
         'nsLocalFileOS2.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXPORTS += ['nsLocalFileWin.h']
+    EXPORTS.mozilla += [
+      'FileUtilsWin.h',
+    ]
     SOURCES += [
+        'FileUtilsWin.cpp',
         'nsLocalFileWin.cpp',
     ]
 else:
     EXPORTS += ['nsLocalFileUnix.h']
     SOURCES += [
         'nsLocalFileUnix.cpp',
     ]
 
--- a/xpcom/tests/windows/Makefile.in
+++ b/xpcom/tests/windows/Makefile.in
@@ -1,8 +1,8 @@
 #
 # 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 $(topsrcdir)/config/rules.mk
 
-OS_LIBS	+= $(call EXPAND_LIBNAME,rpcrt4 uuid)
+OS_LIBS	+= $(call EXPAND_LIBNAME,rpcrt4 uuid mpr)
new file mode 100644
--- /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 <windows.h>
+#include <winnetwk.h>
+
+#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;
+}
+
--- a/xpcom/tests/windows/moz.build
+++ b/xpcom/tests/windows/moz.build
@@ -1,10 +1,11 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 CPP_UNIT_TESTS += [
     'TestCOM.cpp',
+    'TestNtPathToDosPath.cpp',
 ]