summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/common/commonupdatedir.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/mozapps/update/common/commonupdatedir.cpp1899
1 files changed, 1899 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/common/commonupdatedir.cpp b/toolkit/mozapps/update/common/commonupdatedir.cpp
new file mode 100644
index 0000000000..826ec48b51
--- /dev/null
+++ b/toolkit/mozapps/update/common/commonupdatedir.cpp
@@ -0,0 +1,1899 @@
+/* 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/. */
+
+/*
+ * This file does not use many of the features Firefox provides such as
+ * nsAString and nsIFile because code in this file will be included not only
+ * in Firefox, but also in the Mozilla Maintenance Service, the Mozilla
+ * Maintenance Service installer, and TestAUSHelper.
+ */
+
+#include <cinttypes>
+#include <cwchar>
+#include <string>
+#include "city.h"
+#include "commonupdatedir.h"
+#include "updatedefines.h"
+
+#ifdef XP_WIN
+# include <accctrl.h>
+# include <aclapi.h>
+# include <cstdarg>
+# include <errno.h>
+# include <objbase.h>
+# include <shellapi.h>
+# include <shlobj.h>
+# include <strsafe.h>
+# include <winerror.h>
+# include "nsWindowsHelpers.h"
+# include "updateutils_win.h"
+#endif
+
+#ifdef XP_WIN
+// This is the name of the directory to be put in the application data directory
+// if no vendor or application name is specified.
+// (i.e. C:\ProgramData\<FALLBACK_VENDOR_NAME>)
+# define FALLBACK_VENDOR_NAME "Mozilla"
+// This describes the directory between the "Mozilla" directory and the install
+// path hash (i.e. C:\ProgramData\Mozilla\<UPDATE_PATH_MID_DIR_NAME>\<hash>)
+# define UPDATE_PATH_MID_DIR_NAME "updates"
+// This describes the directory between the update directory and the patch
+// directory.
+// (i.e. C:\ProgramData\Mozilla\updates\<hash>\<UPDATE_SUBDIRECTORY>\0)
+# define UPDATE_SUBDIRECTORY "updates"
+// This defines the leaf update directory, where the MAR file is downloaded to
+// (i.e. C:\ProgramData\Mozilla\updates\<hash>\updates\<PATCH_DIRECTORY>)
+# define PATCH_DIRECTORY "0"
+// This defines the prefix of files created to lock a directory
+# define LOCK_FILE_PREFIX "mozlock."
+
+enum class WhichUpdateDir {
+ CommonAppData,
+ UserAppData,
+};
+
+/**
+ * This is a very simple string class.
+ *
+ * This class has some substantial limitations for the sake of simplicity. It
+ * has no support whatsoever for modifying a string that already has data. There
+ * is, therefore, no append function and no support for automatically resizing
+ * strings.
+ *
+ * Error handling is also done in a slightly unusual manner. If there is ever
+ * a failure allocating or assigning to a string, it will do the simplest
+ * possible recovery: truncate itself to 0-length.
+ * This coupled with the fact that the length is cached means that an effective
+ * method of error checking is to attempt assignment and then check the length
+ * of the result.
+ */
+class SimpleAutoString {
+ private:
+ size_t mLength;
+ // Unique pointer frees the buffer when the class is deleted or goes out of
+ // scope.
+ mozilla::UniquePtr<wchar_t[]> mString;
+
+ /**
+ * Allocates enough space to store a string of the specified length.
+ */
+ bool AllocLen(size_t len) {
+ mString = mozilla::MakeUnique<wchar_t[]>(len + 1);
+ return mString.get() != nullptr;
+ }
+
+ /**
+ * Allocates a buffer of the size given.
+ */
+ bool AllocSize(size_t size) {
+ mString = mozilla::MakeUnique<wchar_t[]>(size);
+ return mString.get() != nullptr;
+ }
+
+ public:
+ SimpleAutoString() : mLength(0) {}
+
+ /*
+ * Allocates enough space for a string of the given length and formats it as
+ * an empty string.
+ */
+ bool AllocEmpty(size_t len) {
+ bool success = AllocLen(len);
+ Truncate();
+ return success;
+ }
+
+ /**
+ * These functions can potentially return null if no buffer has yet been
+ * allocated. After changing a string retrieved with MutableString, the Check
+ * method should be called to synchronize other members (ex: mLength) with the
+ * new buffer.
+ */
+ wchar_t* MutableString() { return mString.get(); }
+ const wchar_t* String() const { return mString.get(); }
+
+ size_t Length() const { return mLength; }
+
+ /**
+ * This method should be called after manually changing the string's buffer
+ * via MutableString to synchronize other members (ex: mLength) with the
+ * new buffer.
+ * Returns true if the string is now in a valid state.
+ */
+ bool Check() {
+ mLength = wcslen(mString.get());
+ return true;
+ }
+
+ void SwapBufferWith(mozilla::UniquePtr<wchar_t[]>& other) {
+ mString.swap(other);
+ if (mString) {
+ mLength = wcslen(mString.get());
+ } else {
+ mLength = 0;
+ }
+ }
+
+ void Swap(SimpleAutoString& other) {
+ mString.swap(other.mString);
+ size_t newLength = other.mLength;
+ other.mLength = mLength;
+ mLength = newLength;
+ }
+
+ /**
+ * Truncates the string to the length specified. This must not be greater than
+ * or equal to the size of the string's buffer.
+ */
+ void Truncate(size_t len = 0) {
+ if (len > mLength) {
+ return;
+ }
+ mLength = len;
+ if (mString) {
+ mString.get()[len] = L'\0';
+ }
+ }
+
+ /**
+ * Assigns a string and ensures that the resulting string is valid and has its
+ * length set properly.
+ *
+ * Note that although other similar functions in this class take length, this
+ * function takes buffer size instead because it is intended to be follow the
+ * input convention of sprintf.
+ *
+ * Returns the new length, which will be 0 if there was any failure.
+ *
+ * This function does no allocation or reallocation. If the buffer is not
+ * large enough to hold the new string, the call will fail.
+ */
+ size_t AssignSprintf(size_t bufferSize, const wchar_t* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ size_t returnValue = AssignVsprintf(bufferSize, format, ap);
+ va_end(ap);
+ return returnValue;
+ }
+ /**
+ * Same as the above, but takes a va_list like vsprintf does.
+ */
+ size_t AssignVsprintf(size_t bufferSize, const wchar_t* format, va_list ap) {
+ if (!mString) {
+ Truncate();
+ return 0;
+ }
+
+ int charsWritten = vswprintf(mString.get(), bufferSize, format, ap);
+ if (charsWritten < 0 || static_cast<size_t>(charsWritten) >= bufferSize) {
+ // charsWritten does not include the null terminator. If charsWritten is
+ // equal to the buffer size, we do not have a null terminator nor do we
+ // have room for one.
+ Truncate();
+ return 0;
+ }
+ mString.get()[charsWritten] = L'\0';
+
+ mLength = charsWritten;
+ return mLength;
+ }
+
+ /**
+ * Allocates enough space for the string and assigns a value to it with
+ * sprintf. Takes, as an argument, the maximum length that the string is
+ * expected to use (which, after adding 1 for the null byte, is the amount of
+ * space that will be allocated).
+ *
+ * Returns the new length, which will be 0 on any failure.
+ */
+ size_t AllocAndAssignSprintf(size_t maxLength, const wchar_t* format, ...) {
+ if (!AllocLen(maxLength)) {
+ Truncate();
+ return 0;
+ }
+ va_list ap;
+ va_start(ap, format);
+ size_t charsWritten = AssignVsprintf(maxLength + 1, format, ap);
+ va_end(ap);
+ return charsWritten;
+ }
+
+ /*
+ * Allocates enough for the formatted text desired. Returns maximum storable
+ * length of a string in the allocated buffer on success, or 0 on failure.
+ */
+ size_t AllocFromScprintf(const wchar_t* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ size_t returnValue = AllocFromVscprintf(format, ap);
+ va_end(ap);
+ return returnValue;
+ }
+ /**
+ * Same as the above, but takes a va_list like vscprintf does.
+ */
+ size_t AllocFromVscprintf(const wchar_t* format, va_list ap) {
+ int len = _vscwprintf(format, ap);
+ if (len < 0) {
+ Truncate();
+ return 0;
+ }
+ if (!AllocEmpty(len)) {
+ // AllocEmpty will Truncate, no need to call it here.
+ return 0;
+ }
+ return len;
+ }
+
+ /**
+ * Automatically determines how much space is necessary, allocates that much
+ * for the string, and assigns the data using swprintf. Returns the resulting
+ * length of the string, which will be 0 if the function fails.
+ */
+ size_t AutoAllocAndAssignSprintf(const wchar_t* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ size_t len = AllocFromVscprintf(format, ap);
+ va_end(ap);
+ if (len == 0) {
+ // AllocFromVscprintf will Truncate, no need to call it here.
+ return 0;
+ }
+
+ va_start(ap, format);
+ size_t charsWritten = AssignVsprintf(len + 1, format, ap);
+ va_end(ap);
+
+ if (len != charsWritten) {
+ Truncate();
+ return 0;
+ }
+ return charsWritten;
+ }
+
+ /**
+ * The following CopyFrom functions take various types of strings, allocate
+ * enough space to hold them, and then copy them into that space.
+ *
+ * They return an HRESULT that should be interpreted with the SUCCEEDED or
+ * FAILED macro.
+ */
+ HRESULT CopyFrom(const wchar_t* src) {
+ mLength = wcslen(src);
+ if (!AllocLen(mLength)) {
+ Truncate();
+ return E_OUTOFMEMORY;
+ }
+ HRESULT hrv = StringCchCopyW(mString.get(), mLength + 1, src);
+ if (FAILED(hrv)) {
+ Truncate();
+ }
+ return hrv;
+ }
+ HRESULT CopyFrom(const SimpleAutoString& src) {
+ if (!src.mString) {
+ Truncate();
+ return S_OK;
+ }
+ mLength = src.mLength;
+ if (!AllocLen(mLength)) {
+ Truncate();
+ return E_OUTOFMEMORY;
+ }
+ HRESULT hrv = StringCchCopyW(mString.get(), mLength + 1, src.mString.get());
+ if (FAILED(hrv)) {
+ Truncate();
+ }
+ return hrv;
+ }
+ HRESULT CopyFrom(const char* src) {
+ int bufferSize =
+ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, nullptr, 0);
+ if (bufferSize == 0) {
+ Truncate();
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ if (!AllocSize(bufferSize)) {
+ Truncate();
+ return E_OUTOFMEMORY;
+ }
+ int charsWritten = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
+ -1, mString.get(), bufferSize);
+ if (charsWritten == 0) {
+ Truncate();
+ return HRESULT_FROM_WIN32(GetLastError());
+ } else if (charsWritten != bufferSize) {
+ Truncate();
+ return E_FAIL;
+ }
+ mLength = charsWritten - 1;
+ return S_OK;
+ }
+
+ bool StartsWith(const SimpleAutoString& prefix) const {
+ if (!mString) {
+ return (prefix.mLength == 0);
+ }
+ if (!prefix.mString) {
+ return true;
+ }
+ if (prefix.mLength > mLength) {
+ return false;
+ }
+ return (wcsncmp(mString.get(), prefix.mString.get(), prefix.mLength) == 0);
+ }
+};
+
+// Deleter for use with UniquePtr
+struct CoTaskMemFreeDeleter {
+ void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
+};
+
+/**
+ * A lot of data goes into constructing an ACL and security attributes, and the
+ * Windows documentation does not make it very clear what can be safely freed
+ * after these objects are constructed. This struct holds all of the
+ * construction data in one place so that it can be passed around and freed
+ * properly.
+ */
+struct AutoPerms {
+ SID_IDENTIFIER_AUTHORITY sidIdentifierAuthority;
+ UniqueSidPtr usersSID;
+ UniqueSidPtr adminsSID;
+ UniqueSidPtr systemSID;
+ EXPLICIT_ACCESS_W ea[3];
+ mozilla::UniquePtr<ACL, LocalFreeDeleter> acl;
+ mozilla::UniquePtr<uint8_t[]> securityDescriptorBuffer;
+ PSECURITY_DESCRIPTOR securityDescriptor;
+ SECURITY_ATTRIBUTES securityAttributes;
+};
+
+static HRESULT GetFilename(SimpleAutoString& path, SimpleAutoString& filename);
+
+enum class Tristate { False, True, Unknown };
+
+enum class Lockstate { Locked, Unlocked };
+
+/**
+ * This class will look up and store some data about the file or directory at
+ * the path given.
+ * The path can additionally be locked. For files, this is done by holding a
+ * handle to that file. For directories, this is done by holding a handle to a
+ * file within the directory.
+ */
+class FileOrDirectory {
+ private:
+ Tristate mIsHardLink;
+ DWORD mAttributes;
+ nsAutoHandle mLockHandle;
+ // This stores the name of the lock file. We need to keep track of this for
+ // directories, which are locked via a randomly named lock file inside. But
+ // we do not store a value here for files, as they do not have a separate lock
+ // file.
+ SimpleAutoString mDirLockFilename;
+
+ /**
+ * Locks the path. For directories, this is done by opening a file in the
+ * directory and storing its handle in mLockHandle. For files, we just open
+ * the file itself and store the handle.
+ * Returns true on success and false on failure.
+ *
+ * Calling this function will result in mAttributes being updated.
+ *
+ * This function is private to prevent callers from locking the directory
+ * after its attributes have been read. Part of the purpose of locking a
+ * directory is to ensure that its attributes are what we think they are and
+ * that they don't change while we hold the lock. If we get the lock after
+ * attributes are looked up, we can no longer provide that guarantee.
+ * If you think you want to call Lock(), you probably actually want to call
+ * Reset().
+ */
+ bool Lock(const wchar_t* path) {
+ mAttributes = GetFileAttributesW(path);
+ Tristate isDir = IsDirectory();
+ if (isDir == Tristate::Unknown) {
+ return false;
+ }
+
+ if (isDir == Tristate::True) {
+ SimpleAutoString lockPath;
+ if (!lockPath.AllocEmpty(MAX_PATH)) {
+ return false;
+ }
+ BOOL success = GetUUIDTempFilePath(path, NS_T(LOCK_FILE_PREFIX),
+ lockPath.MutableString());
+ if (!success || !lockPath.Check()) {
+ return false;
+ }
+
+ HRESULT hrv = GetFilename(lockPath, mDirLockFilename);
+ if (FAILED(hrv) || mDirLockFilename.Length() == 0) {
+ return false;
+ }
+
+ mLockHandle.own(CreateFileW(lockPath.String(), 0, 0, nullptr, OPEN_ALWAYS,
+ FILE_FLAG_DELETE_ON_CLOSE, nullptr));
+ } else { // If path is not a directory
+ // The usual reason for us to lock a file is to read and change the
+ // permissions so, unlike the directory lock file, make sure we request
+ // the access necessary to read and write permissions.
+ mLockHandle.own(CreateFileW(path, WRITE_DAC | READ_CONTROL, 0, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+ nullptr));
+ }
+ if (!IsLocked()) {
+ return false;
+ }
+ mAttributes = GetFileAttributesW(path);
+ // Directories and files are locked in different ways. If we think that we
+ // just locked one but we actually locked the other, our lock will be
+ // ineffective and we should not tell callers that this is locked.
+ // (This should fail earlier, since files cannot have children and
+ // directories cannot be opened with FILE_ATTRIBUTE_NORMAL. But just to be
+ // safe...)
+ if (isDir != IsDirectory()) {
+ Unlock();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Helper function to normalize the access mask by converting generic access
+ * flags to specific ones to make it easier to check if permissions match.
+ */
+ void NormalizeAccessMask(ACCESS_MASK& mask) {
+ if ((mask & GENERIC_ALL) == GENERIC_ALL) {
+ mask &= ~GENERIC_ALL;
+ mask |= FILE_ALL_ACCESS;
+ }
+ if ((mask & GENERIC_READ) == GENERIC_READ) {
+ mask &= ~GENERIC_READ;
+ mask |= FILE_GENERIC_READ;
+ }
+ if ((mask & GENERIC_WRITE) == GENERIC_WRITE) {
+ mask &= ~GENERIC_WRITE;
+ mask |= FILE_GENERIC_WRITE;
+ }
+ if ((mask & GENERIC_EXECUTE) == GENERIC_EXECUTE) {
+ mask &= ~GENERIC_EXECUTE;
+ mask |= FILE_GENERIC_EXECUTE;
+ }
+ }
+
+ public:
+ FileOrDirectory()
+ : mIsHardLink(Tristate::Unknown),
+ mAttributes(INVALID_FILE_ATTRIBUTES),
+ mLockHandle(INVALID_HANDLE_VALUE) {}
+
+ /**
+ * If shouldLock is Locked:Locked, the file or directory will be locked.
+ * Note that locking is fallible and success should be checked via the
+ * IsLocked method.
+ */
+ FileOrDirectory(const SimpleAutoString& path, Lockstate shouldLock)
+ : FileOrDirectory() {
+ Reset(path, shouldLock);
+ }
+
+ /**
+ * Initializes the FileOrDirectory to the file with the path given.
+ *
+ * If shouldLock is Locked:Locked, the file or directory will be locked.
+ * Note that locking is fallible and success should be checked via the
+ * IsLocked method.
+ */
+ void Reset(const SimpleAutoString& path, Lockstate shouldLock) {
+ Unlock();
+ mDirLockFilename.Truncate();
+ if (shouldLock == Lockstate::Locked) {
+ // This will also update mAttributes.
+ Lock(path.String());
+ } else {
+ mAttributes = GetFileAttributesW(path.String());
+ }
+
+ mIsHardLink = Tristate::Unknown;
+ nsAutoHandle autoHandle;
+ HANDLE handle;
+ if (IsLocked() && IsDirectory() == Tristate::False) {
+ // If the path is a file and we locked it, we already have a handle to it.
+ // No need to open it again.
+ handle = mLockHandle.get();
+ } else {
+ handle = CreateFileW(path.String(), 0, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+ // Make sure this handle gets freed automatically.
+ autoHandle.own(handle);
+ }
+
+ Tristate isLink = Tristate::Unknown;
+ if (handle != INVALID_HANDLE_VALUE) {
+ BY_HANDLE_FILE_INFORMATION info;
+ BOOL success = GetFileInformationByHandle(handle, &info);
+ if (success) {
+ if (info.nNumberOfLinks > 1) {
+ isLink = Tristate::True;
+ } else {
+ isLink = Tristate::False;
+ }
+ }
+ }
+
+ mIsHardLink = Tristate::Unknown;
+ Tristate isSymLink = IsSymLink();
+ if (isLink == Tristate::False || isSymLink == Tristate::True) {
+ mIsHardLink = Tristate::False;
+ } else if (isLink == Tristate::True && isSymLink == Tristate::False) {
+ mIsHardLink = Tristate::True;
+ }
+ }
+
+ void Unlock() { mLockHandle.own(INVALID_HANDLE_VALUE); }
+
+ bool IsLocked() const { return mLockHandle.get() != INVALID_HANDLE_VALUE; }
+
+ Tristate IsSymLink() const {
+ if (mAttributes == INVALID_FILE_ATTRIBUTES) {
+ return Tristate::Unknown;
+ }
+ if (mAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ return Tristate::True;
+ }
+ return Tristate::False;
+ }
+
+ Tristate IsHardLink() const { return mIsHardLink; }
+
+ Tristate IsLink() const {
+ Tristate isSymLink = IsSymLink();
+ if (mIsHardLink == Tristate::True || isSymLink == Tristate::True) {
+ return Tristate::True;
+ }
+ if (mIsHardLink == Tristate::Unknown || isSymLink == Tristate::Unknown) {
+ return Tristate::Unknown;
+ }
+ return Tristate::False;
+ }
+
+ Tristate IsDirectory() const {
+ if (mAttributes == INVALID_FILE_ATTRIBUTES) {
+ return Tristate::Unknown;
+ }
+ if (mAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ return Tristate::True;
+ }
+ return Tristate::False;
+ }
+
+ Tristate IsReadonly() const {
+ if (mAttributes == INVALID_FILE_ATTRIBUTES) {
+ return Tristate::Unknown;
+ }
+ if (mAttributes & FILE_ATTRIBUTE_READONLY) {
+ return Tristate::True;
+ }
+ return Tristate::False;
+ }
+
+ DWORD Attributes() const { return mAttributes; }
+
+ /**
+ * Sets the permissions to those passed. For this to be done safely, the file
+ * must be locked and must not be a directory or a link. If these conditions
+ * are not met, the function will fail.
+ * Without locking, we can't guarantee that the file is the one we think it
+ * is. Someone might have replaced a component of the path with a symlink.
+ * With directories, setting the permissions can have the effect of setting
+ * the permissions of a malicious hardlink within.
+ */
+ HRESULT SetPerms(const AutoPerms& perms) {
+ if (IsDirectory() != Tristate::False || !IsLocked() ||
+ IsHardLink() != Tristate::False) {
+ return E_FAIL;
+ }
+
+ DWORD drv = SetSecurityInfo(mLockHandle.get(), SE_FILE_OBJECT,
+ DACL_SECURITY_INFORMATION, nullptr, nullptr,
+ perms.acl.get(), nullptr);
+ return HRESULT_FROM_WIN32(drv);
+ }
+
+ /**
+ * Checks the permissions of a file to make sure that they match the expected
+ * permissions.
+ */
+ Tristate PermsOk(const SimpleAutoString& path, const AutoPerms& perms) {
+ nsAutoHandle autoHandle;
+ HANDLE handle;
+ if (IsDirectory() == Tristate::False && IsLocked()) {
+ handle = mLockHandle.get();
+ } else {
+ handle =
+ CreateFileW(path.String(), READ_CONTROL, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+ // Make sure this handle gets freed automatically.
+ autoHandle.own(handle);
+ }
+
+ PACL dacl = nullptr;
+ SECURITY_DESCRIPTOR* securityDescriptor = nullptr;
+ DWORD drv = GetSecurityInfo(
+ handle, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr,
+ &dacl, nullptr,
+ reinterpret_cast<PSECURITY_DESCRIPTOR*>(&securityDescriptor));
+ // Store the security descriptor in a UniquePtr so that it automatically
+ // gets freed properly. We don't need to worry about dacl, since it will
+ // point within the security descriptor.
+ mozilla::UniquePtr<SECURITY_DESCRIPTOR, LocalFreeDeleter>
+ autoSecurityDescriptor(securityDescriptor);
+ if (drv == ERROR_ACCESS_DENIED) {
+ // If access was denied reading the permissions, it seems pretty safe to
+ // say that the permissions are wrong.
+ return Tristate::False;
+ }
+ if (drv != ERROR_SUCCESS || dacl == nullptr) {
+ return Tristate::Unknown;
+ }
+
+ size_t eaLen = sizeof(perms.ea) / sizeof(perms.ea[0]);
+ for (size_t eaIndex = 0; eaIndex < eaLen; ++eaIndex) {
+ PTRUSTEE_W trustee = const_cast<PTRUSTEE_W>(&perms.ea[eaIndex].Trustee);
+ ACCESS_MASK expectedMask = perms.ea[eaIndex].grfAccessPermissions;
+ ACCESS_MASK actualMask;
+ drv = GetEffectiveRightsFromAclW(dacl, trustee, &actualMask);
+ if (drv == ERROR_ACCESS_DENIED) {
+ return Tristate::False;
+ }
+ if (drv != ERROR_SUCCESS) {
+ return Tristate::Unknown;
+ }
+ NormalizeAccessMask(expectedMask);
+ NormalizeAccessMask(actualMask);
+ if ((actualMask & expectedMask) != expectedMask) {
+ return Tristate::False;
+ }
+ }
+
+ return Tristate::True;
+ }
+
+ /**
+ * Valid only if IsDirectory() == True.
+ * Checks to see if the string given matches the filename of the lock file.
+ */
+ bool LockFilenameMatches(const wchar_t* filename) {
+ if (mDirLockFilename.Length() == 0) {
+ return false;
+ }
+ return wcscmp(filename, mDirLockFilename.String()) == 0;
+ }
+};
+
+static bool GetCachedHash(const char16_t* installPath, HKEY rootKey,
+ const SimpleAutoString& regPath,
+ mozilla::UniquePtr<NS_tchar[]>& result);
+static HRESULT GetUpdateDirectory(const wchar_t* installPath,
+ const char* vendor, const char* appName,
+ WhichUpdateDir whichDir,
+ SetPermissionsOf permsToSet,
+ mozilla::UniquePtr<wchar_t[]>& result);
+static HRESULT EnsureUpdateDirectoryPermissions(
+ const SimpleAutoString& basePath, const SimpleAutoString& updatePath,
+ bool fullUpdatePath, SetPermissionsOf permsToSet);
+static HRESULT GeneratePermissions(AutoPerms& result);
+static HRESULT MakeDir(const SimpleAutoString& path, const AutoPerms& perms);
+static HRESULT RemoveRecursive(const SimpleAutoString& path,
+ FileOrDirectory& file);
+static HRESULT MoveConflicting(const SimpleAutoString& path,
+ FileOrDirectory& file,
+ SimpleAutoString* outPath);
+static HRESULT EnsureCorrectPermissions(SimpleAutoString& path,
+ FileOrDirectory& file,
+ const SimpleAutoString& leafUpdateDir,
+ const AutoPerms& perms,
+ SetPermissionsOf permsToSet);
+static HRESULT FixDirectoryPermissions(const SimpleAutoString& path,
+ FileOrDirectory& directory,
+ const AutoPerms& perms,
+ bool& permissionsFixed);
+static HRESULT MoveFileOrDir(const SimpleAutoString& moveFrom,
+ const SimpleAutoString& moveTo,
+ const AutoPerms& perms);
+static HRESULT SplitPath(const SimpleAutoString& path,
+ SimpleAutoString& parentPath,
+ SimpleAutoString& filename);
+static bool PathConflictsWithLeaf(const SimpleAutoString& path,
+ const SimpleAutoString& leafPath);
+#endif // XP_WIN
+
+/**
+ * Returns a hash of the install path, suitable for uniquely identifying the
+ * particular Firefox installation that is running.
+ *
+ * This function includes a compatibility mode that should NOT be used except by
+ * GetUserUpdateDirectory. Previous implementations of this function could
+ * return a value inconsistent with what our installer would generate. When the
+ * update directory was migrated, this function was re-implemented to return
+ * values consistent with those generated by the installer. The compatibility
+ * mode is retained only so that we can properly get the old update directory
+ * when migrating it.
+ *
+ * @param installPath
+ * The null-terminated path to the installation directory (i.e. the
+ * directory that contains the binary). Must not be null. The path must
+ * not include a trailing slash.
+ * @param vendor
+ * A pointer to a null-terminated string containing the vendor name, or
+ * null. This is only used to look up a registry key on Windows. On
+ * other platforms, the value has no effect. If null is passed on
+ * Windows, "Mozilla" will be used.
+ * @param result
+ * The out parameter that will be set to contain the resulting hash.
+ * The value is wrapped in a UniquePtr to make cleanup easier on the
+ * caller.
+ * @param useCompatibilityMode
+ * Enables compatibility mode. Defaults to false.
+ * @return true if successful and false otherwise.
+ */
+bool GetInstallHash(const char16_t* installPath, const char* vendor,
+ mozilla::UniquePtr<NS_tchar[]>& result,
+ bool useCompatibilityMode /* = false */) {
+ MOZ_ASSERT(installPath != nullptr,
+ "Install path must not be null in GetInstallHash");
+
+ // Unable to get the cached hash, so compute it.
+ size_t pathSize =
+ std::char_traits<char16_t>::length(installPath) * sizeof(*installPath);
+ uint64_t hash =
+ CityHash64(reinterpret_cast<const char*>(installPath), pathSize);
+
+ size_t hashStrSize = sizeof(hash) * 2 + 1; // 2 hex digits per byte + null
+ result = mozilla::MakeUnique<NS_tchar[]>(hashStrSize);
+ int charsWritten;
+ if (useCompatibilityMode) {
+ // This behavior differs slightly from the default behavior.
+ // When the default output would be "1234567800000009", this instead
+ // produces "123456789".
+ charsWritten = NS_tsnprintf(result.get(), hashStrSize,
+ NS_T("%") NS_T(PRIX32) NS_T("%") NS_T(PRIX32),
+ static_cast<uint32_t>(hash >> 32),
+ static_cast<uint32_t>(hash));
+ } else {
+ charsWritten =
+ NS_tsnprintf(result.get(), hashStrSize, NS_T("%") NS_T(PRIX64), hash);
+ }
+ return !(charsWritten < 1 ||
+ static_cast<size_t>(charsWritten) > hashStrSize - 1);
+}
+
+#ifdef XP_WIN
+/**
+ * Returns true if the registry key was successfully found and read into result.
+ */
+static bool GetCachedHash(const char16_t* installPath, HKEY rootKey,
+ const SimpleAutoString& regPath,
+ mozilla::UniquePtr<NS_tchar[]>& result) {
+ // Find the size of the string we are reading before we read it so we can
+ // allocate space.
+ unsigned long bufferSize;
+ LSTATUS lrv = RegGetValueW(rootKey, regPath.String(),
+ reinterpret_cast<const wchar_t*>(installPath),
+ RRF_RT_REG_SZ, nullptr, nullptr, &bufferSize);
+ if (lrv != ERROR_SUCCESS) {
+ return false;
+ }
+ result = mozilla::MakeUnique<NS_tchar[]>(bufferSize);
+ if (!result) {
+ return false;
+ }
+ // Now read the actual value from the registry.
+ lrv = RegGetValueW(rootKey, regPath.String(),
+ reinterpret_cast<const wchar_t*>(installPath),
+ RRF_RT_REG_SZ, nullptr, result.get(), &bufferSize);
+ return (lrv == ERROR_SUCCESS);
+}
+
+/**
+ * Returns the update directory path. The update directory needs to have
+ * different permissions from the default, so we don't really want anyone using
+ * the path without the directory already being created with the correct
+ * permissions. Therefore, this function also ensures that the base directory
+ * that needs permissions set already exists. If it does not exist, it is
+ * created with the needed permissions.
+ * The desired permissions give Full Control to SYSTEM, Administrators, and
+ * Users.
+ *
+ * vendor and appName are passed as char*, not because we want that (we don't,
+ * we want wchar_t), but because of the expected origin of the data. If this
+ * data is available, it is probably available via XREAppData::vendor and
+ * XREAppData::name.
+ *
+ * @param installPath
+ * The null-terminated path to the installation directory (i.e. the
+ * directory that contains the binary). The path must not include a
+ * trailing slash. If null is passed for this value, the entire update
+ * directory path cannot be retrieved, so the function will return the
+ * update directory without the installation-specific leaf directory.
+ * This feature exists for when the caller wants to use this function
+ * to set directory permissions and does not need the full update
+ * directory path.
+ * @param vendor
+ * A pointer to a null-terminated string containing the vendor name.
+ * Will default to "Mozilla" if null is passed.
+ * @param appName
+ * A pointer to a null-terminated string containing the application
+ * name, or null.
+ * @param permsToSet
+ * Determines how aggressive to be when setting permissions.
+ * This is the behavior by value:
+ * BaseDirIfNotExists - Sets the permissions on the base
+ * directory, but only if it does not
+ * already exist.
+ * AllFilesAndDirs - Recurses through the base directory,
+ * setting the permissions on all files
+ * and directories contained. Symlinks
+ * are removed. Files with names
+ * conflicting with the creation of the
+ * update directory are moved or removed.
+ * FilesAndDirsWithBadPerms - Same as AllFilesAndDirs, but does not
+ * attempt to fix permissions if they
+ * cannot be determined.
+ * @param result
+ * The out parameter that will be set to contain the resulting path.
+ * The value is wrapped in a UniquePtr to make cleanup easier on the
+ * caller.
+ *
+ * @return An HRESULT that should be tested with SUCCEEDED or FAILED.
+ */
+HRESULT
+GetCommonUpdateDirectory(const wchar_t* installPath,
+ SetPermissionsOf permsToSet,
+ mozilla::UniquePtr<wchar_t[]>& result) {
+ return GetUpdateDirectory(installPath, nullptr, nullptr,
+ WhichUpdateDir::CommonAppData, permsToSet, result);
+}
+
+/**
+ * This function is identical to the function above except that it gets the
+ * "old" (pre-migration) update directory that is located in the user's app data
+ * directory, rather than the new one in the common app data directory.
+ *
+ * The other difference is that this function does not create or change the
+ * permissions of the update directory since the default permissions on this
+ * directory are acceptable as they are.
+ */
+HRESULT
+GetUserUpdateDirectory(const wchar_t* installPath, const char* vendor,
+ const char* appName,
+ mozilla::UniquePtr<wchar_t[]>& result) {
+ return GetUpdateDirectory(
+ installPath, vendor, appName, WhichUpdateDir::UserAppData,
+ SetPermissionsOf::BaseDirIfNotExists, // Arbitrary value
+ result);
+}
+
+/**
+ * This is a much more limited version of the GetCommonUpdateDirectory that can
+ * be called from Rust.
+ * The result parameter must be a valid pointer to a buffer of length
+ * MAX_PATH + 1
+ */
+extern "C" HRESULT get_common_update_directory(const wchar_t* installPath,
+ wchar_t* result) {
+ mozilla::UniquePtr<wchar_t[]> uniqueResult;
+ HRESULT hr = GetCommonUpdateDirectory(
+ installPath, SetPermissionsOf::BaseDirIfNotExists, uniqueResult);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ return StringCchCopyW(result, MAX_PATH + 1, uniqueResult.get());
+}
+
+/**
+ * This is a helper function that does all of the work for
+ * GetCommonUpdateDirectory and GetUserUpdateDirectory. It partially exists to
+ * prevent callers of GetUserUpdateDirectory from having to pass a useless
+ * SetPermissionsOf argument, which will be ignored if whichDir is UserAppData.
+ *
+ * For information on the parameters and return value, see
+ * GetCommonUpdateDirectory.
+ */
+static HRESULT GetUpdateDirectory(const wchar_t* installPath,
+ const char* vendor, const char* appName,
+ WhichUpdateDir whichDir,
+ SetPermissionsOf permsToSet,
+ mozilla::UniquePtr<wchar_t[]>& result) {
+ PWSTR baseDirParentPath;
+ REFKNOWNFOLDERID folderID = (whichDir == WhichUpdateDir::CommonAppData)
+ ? FOLDERID_ProgramData
+ : FOLDERID_LocalAppData;
+ HRESULT hrv = SHGetKnownFolderPath(folderID, KF_FLAG_CREATE, nullptr,
+ &baseDirParentPath);
+ // Free baseDirParentPath when it goes out of scope.
+ mozilla::UniquePtr<wchar_t, CoTaskMemFreeDeleter> baseDirParentPathUnique(
+ baseDirParentPath);
+ if (FAILED(hrv)) {
+ return hrv;
+ }
+
+ SimpleAutoString baseDir;
+ if (whichDir == WhichUpdateDir::UserAppData && (vendor || appName)) {
+ const char* rawBaseDir = vendor ? vendor : appName;
+ hrv = baseDir.CopyFrom(rawBaseDir);
+ } else {
+ const wchar_t baseDirLiteral[] = NS_T(FALLBACK_VENDOR_NAME);
+ hrv = baseDir.CopyFrom(baseDirLiteral);
+ }
+ if (FAILED(hrv)) {
+ return hrv;
+ }
+
+ // Generate the base path (C:\ProgramData\Mozilla)
+ SimpleAutoString basePath;
+ size_t basePathLen =
+ wcslen(baseDirParentPath) + 1 /* path separator */ + baseDir.Length();
+ basePath.AllocAndAssignSprintf(basePathLen, L"%s\\%s", baseDirParentPath,
+ baseDir.String());
+ if (basePath.Length() != basePathLen) {
+ return E_FAIL;
+ }
+
+ // Generate the update directory path. This is the value to be returned by
+ // this function.
+ SimpleAutoString updatePath;
+ if (installPath) {
+ mozilla::UniquePtr<NS_tchar[]> hash;
+
+ // The Windows installer caches this hash value in the registry
+ bool gotHash = false;
+ SimpleAutoString regPath;
+ regPath.AutoAllocAndAssignSprintf(L"SOFTWARE\\%S\\%S\\TaskBarIDs",
+ vendor ? vendor : "Mozilla",
+ MOZ_APP_BASENAME);
+ if (regPath.Length() != 0) {
+ gotHash = GetCachedHash(reinterpret_cast<const char16_t*>(installPath),
+ HKEY_LOCAL_MACHINE, regPath, hash);
+ if (!gotHash) {
+ gotHash = GetCachedHash(reinterpret_cast<const char16_t*>(installPath),
+ HKEY_CURRENT_USER, regPath, hash);
+ }
+ }
+ bool success = true;
+ if (!gotHash) {
+ bool useCompatibilityMode = (whichDir == WhichUpdateDir::UserAppData);
+ success = GetInstallHash(reinterpret_cast<const char16_t*>(installPath),
+ vendor, hash, useCompatibilityMode);
+ }
+ if (success) {
+ const wchar_t midPathDirName[] = NS_T(UPDATE_PATH_MID_DIR_NAME);
+ size_t updatePathLen = basePath.Length() + 1 /* path separator */ +
+ wcslen(midPathDirName) + 1 /* path separator */ +
+ wcslen(hash.get());
+ updatePath.AllocAndAssignSprintf(updatePathLen, L"%s\\%s\\%s",
+ basePath.String(), midPathDirName,
+ hash.get());
+ // Permissions can still be set without this string, so wait until after
+ // setting permissions to return failure if the string assignment failed.
+ }
+ }
+
+ if (whichDir == WhichUpdateDir::CommonAppData) {
+ if (updatePath.Length() > 0) {
+ hrv = EnsureUpdateDirectoryPermissions(basePath, updatePath, true,
+ permsToSet);
+ } else {
+ hrv = EnsureUpdateDirectoryPermissions(basePath, basePath, false,
+ permsToSet);
+ }
+ if (FAILED(hrv)) {
+ return hrv;
+ }
+ }
+
+ if (!installPath) {
+ basePath.SwapBufferWith(result);
+ return S_OK;
+ }
+
+ if (updatePath.Length() == 0) {
+ return E_FAIL;
+ }
+ updatePath.SwapBufferWith(result);
+ return S_OK;
+}
+
+/**
+ * If the basePath does not exist, it is created with the expected permissions.
+ *
+ * It used to be that if basePath exists and SetPermissionsOf::AllFilesAndDirs
+ * was passed in, this function would aggressively set the permissions of
+ * the directory and everything in it. But that caused a problem: There does not
+ * seem to be a good way to ensure that, when setting permissions on a
+ * directory, a malicious process does not sneak a hard link into that directory
+ * (causing it to inherit the permissions set on the directory).
+ *
+ * To address that issue, this function now takes a different approach.
+ * To prevent abuse, permissions of directories will not be changed.
+ * Instead, directories with bad permissions are deleted and re-created with the
+ * correct permissions.
+ *
+ * @param basePath
+ * The top directory within the application data directory.
+ * Typically "C:\ProgramData\Mozilla".
+ * @param updatePath
+ * The update directory to be checked for conflicts. If files
+ * conflicting with this directory structure exist, they may be moved
+ * or deleted depending on the value of permsToSet.
+ * @param fullUpdatePath
+ * Set to true if updatePath is the full update path. If set to false,
+ * it means that we don't have the installation-specific path
+ * component.
+ * @param permsToSet
+ * See the documentation for GetCommonUpdateDirectory for the
+ * descriptions of the effects of each SetPermissionsOf value.
+ */
+static HRESULT EnsureUpdateDirectoryPermissions(
+ const SimpleAutoString& basePath, const SimpleAutoString& updatePath,
+ bool fullUpdatePath, SetPermissionsOf permsToSet) {
+ HRESULT returnValue = S_OK; // Stores the value that will eventually be
+ // returned. If errors occur, this is set to the
+ // first error encountered.
+
+ Lockstate shouldLock = permsToSet == SetPermissionsOf::BaseDirIfNotExists
+ ? Lockstate::Unlocked
+ : Lockstate::Locked;
+ FileOrDirectory baseDir(basePath, shouldLock);
+ // validBaseDir will be true if the basePath exists, and is a non-symlinked
+ // directory.
+ bool validBaseDir = baseDir.IsDirectory() == Tristate::True &&
+ baseDir.IsLink() == Tristate::False;
+
+ // The most common case when calling this function is when the caller of
+ // GetCommonUpdateDirectory just wants the update directory path, and passes
+ // in the least aggressive option for setting permissions.
+ // The most common environment is that the update directory already exists.
+ // Optimize for this case.
+ if (permsToSet == SetPermissionsOf::BaseDirIfNotExists && validBaseDir) {
+ return S_OK;
+ }
+
+ AutoPerms perms;
+ HRESULT hrv = GeneratePermissions(perms);
+ if (FAILED(hrv)) {
+ // Fatal error. There is no real way to recover from this.
+ return hrv;
+ }
+
+ if (permsToSet == SetPermissionsOf::BaseDirIfNotExists) {
+ // We know that the base directory is invalid, because otherwise we would
+ // have exited already.
+ // Ignore errors here. It could be that the directory doesn't exist at all.
+ // And ultimately, we are only interested in whether or not we successfully
+ // create the new directory.
+ MoveConflicting(basePath, baseDir, nullptr);
+
+ hrv = MakeDir(basePath, perms);
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+ return returnValue;
+ }
+
+ // We need to pass a mutable basePath to EnsureCorrectPermissions, so copy it.
+ SimpleAutoString mutBasePath;
+ hrv = mutBasePath.CopyFrom(basePath);
+ if (FAILED(hrv) || mutBasePath.Length() == 0) {
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+ return returnValue;
+ }
+
+ if (fullUpdatePath) {
+ // When we are doing a full permissions reset, we are also ensuring that no
+ // files are in the way of our required directory structure. Generate the
+ // path of the furthest leaf in our directory structure so that we can check
+ // for conflicting files.
+ SimpleAutoString leafDirPath;
+ wchar_t updateSubdirectoryName[] = NS_T(UPDATE_SUBDIRECTORY);
+ wchar_t patchDirectoryName[] = NS_T(PATCH_DIRECTORY);
+ size_t leafDirLen = updatePath.Length() + wcslen(updateSubdirectoryName) +
+ wcslen(patchDirectoryName) + 2; /* 2 path separators */
+ leafDirPath.AllocAndAssignSprintf(
+ leafDirLen, L"%s\\%s\\%s", updatePath.String(), updateSubdirectoryName,
+ patchDirectoryName);
+ if (leafDirPath.Length() == leafDirLen) {
+ hrv = EnsureCorrectPermissions(mutBasePath, baseDir, leafDirPath, perms,
+ permsToSet);
+ } else {
+ // If we cannot generate the leaf path, just do the best we can by using
+ // the updatePath.
+ returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+ hrv = EnsureCorrectPermissions(mutBasePath, baseDir, updatePath, perms,
+ permsToSet);
+ }
+ } else {
+ hrv = EnsureCorrectPermissions(mutBasePath, baseDir, updatePath, perms,
+ permsToSet);
+ }
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+
+ // EnsureCorrectPermissions does its best to remove links and conflicting
+ // files but, in doing so, it may leave us without a base update directory.
+ // Rather than checking whether it exists first, just try to create it. If
+ // successful, the directory now exists with the right permissions and no
+ // contents, which this function considers a success. If unsuccessful,
+ // most likely the directory just already exists. But we need to verify that
+ // before we can return success.
+ BOOL success = CreateDirectoryW(
+ basePath.String(),
+ const_cast<LPSECURITY_ATTRIBUTES>(&perms.securityAttributes));
+ if (success) {
+ return S_OK;
+ }
+ if (SUCCEEDED(returnValue)) {
+ baseDir.Reset(basePath, Lockstate::Unlocked);
+ if (baseDir.IsDirectory() != Tristate::True ||
+ baseDir.IsLink() != Tristate::False ||
+ baseDir.PermsOk(basePath, perms) != Tristate::True) {
+ return E_FAIL;
+ }
+ }
+
+ return returnValue;
+}
+
+/**
+ * Generates the permission set that we want to be applied to the update
+ * directory and its contents. Returns the permissions data via the result
+ * outparam.
+ *
+ * These are also the permissions that will be used to check that file
+ * permissions are correct.
+ */
+static HRESULT GeneratePermissions(AutoPerms& result) {
+ result.sidIdentifierAuthority = SECURITY_NT_AUTHORITY;
+ ZeroMemory(&result.ea, sizeof(result.ea));
+
+ // Make Users group SID and add it to the Explicit Access List.
+ PSID usersSID = nullptr;
+ BOOL success = AllocateAndInitializeSid(
+ &result.sidIdentifierAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_USERS, 0, 0, 0, 0, 0, 0, &usersSID);
+ result.usersSID.reset(usersSID);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ result.ea[0].grfAccessPermissions = FILE_ALL_ACCESS;
+ result.ea[0].grfAccessMode = SET_ACCESS;
+ result.ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ result.ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ result.ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+ result.ea[0].Trustee.ptstrName = static_cast<LPWSTR>(usersSID);
+
+ // Make Administrators group SID and add it to the Explicit Access List.
+ PSID adminsSID = nullptr;
+ success = AllocateAndInitializeSid(
+ &result.sidIdentifierAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminsSID);
+ result.adminsSID.reset(adminsSID);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ result.ea[1].grfAccessPermissions = FILE_ALL_ACCESS;
+ result.ea[1].grfAccessMode = SET_ACCESS;
+ result.ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ result.ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ result.ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+ result.ea[1].Trustee.ptstrName = static_cast<LPWSTR>(adminsSID);
+
+ // Make SYSTEM user SID and add it to the Explicit Access List.
+ PSID systemSID = nullptr;
+ success = AllocateAndInitializeSid(&result.sidIdentifierAuthority, 1,
+ SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0,
+ 0, 0, &systemSID);
+ result.systemSID.reset(systemSID);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ result.ea[2].grfAccessPermissions = FILE_ALL_ACCESS;
+ result.ea[2].grfAccessMode = SET_ACCESS;
+ result.ea[2].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ result.ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ result.ea[2].Trustee.TrusteeType = TRUSTEE_IS_USER;
+ result.ea[2].Trustee.ptstrName = static_cast<LPWSTR>(systemSID);
+
+ PACL acl = nullptr;
+ DWORD drv = SetEntriesInAclW(3, result.ea, nullptr, &acl);
+ // Put the ACL in a unique pointer so that LocalFree is called when it goes
+ // out of scope
+ result.acl.reset(acl);
+ if (drv != ERROR_SUCCESS) {
+ return HRESULT_FROM_WIN32(drv);
+ }
+
+ result.securityDescriptorBuffer =
+ mozilla::MakeUnique<uint8_t[]>(SECURITY_DESCRIPTOR_MIN_LENGTH);
+ if (!result.securityDescriptorBuffer) {
+ return E_OUTOFMEMORY;
+ }
+ result.securityDescriptor = reinterpret_cast<PSECURITY_DESCRIPTOR>(
+ result.securityDescriptorBuffer.get());
+ success = InitializeSecurityDescriptor(result.securityDescriptor,
+ SECURITY_DESCRIPTOR_REVISION);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ success =
+ SetSecurityDescriptorDacl(result.securityDescriptor, TRUE, acl, FALSE);
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ result.securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+ result.securityAttributes.lpSecurityDescriptor = result.securityDescriptor;
+ result.securityAttributes.bInheritHandle = FALSE;
+ return S_OK;
+}
+
+/**
+ * Creates a directory with the permissions specified. If the directory already
+ * exists, this function will return success as long as it is a non-link
+ * directory.
+ */
+static HRESULT MakeDir(const SimpleAutoString& path, const AutoPerms& perms) {
+ BOOL success = CreateDirectoryW(
+ path.String(),
+ const_cast<LPSECURITY_ATTRIBUTES>(&perms.securityAttributes));
+ if (success) {
+ return S_OK;
+ }
+ DWORD error = GetLastError();
+ if (error != ERROR_ALREADY_EXISTS) {
+ return HRESULT_FROM_WIN32(error);
+ }
+ FileOrDirectory dir(path, Lockstate::Unlocked);
+ if (dir.IsDirectory() == Tristate::True && dir.IsLink() == Tristate::False) {
+ return S_OK;
+ }
+ return HRESULT_FROM_WIN32(error);
+}
+
+/**
+ * Attempts to move the file or directory to the Windows Recycle Bin.
+ * If removal fails with an ERROR_FILE_NOT_FOUND, the file must not exist, so
+ * this will return success in that case.
+ *
+ * The file will be unlocked in order to remove it.
+ *
+ * Whether this function succeeds or fails, the file parameter should no longer
+ * be considered accurate. If it succeeds, it will be inaccurate because the
+ * file no longer exists. If it fails, it may be inaccurate due to this function
+ * potentially setting file attributes.
+ */
+static HRESULT RemoveRecursive(const SimpleAutoString& path,
+ FileOrDirectory& file) {
+ file.Unlock();
+ if (file.IsReadonly() != Tristate::False) {
+ // Ignore errors setting attributes. We only care if it was successfully
+ // deleted.
+ DWORD attributes = file.Attributes();
+ if (attributes == INVALID_FILE_ATTRIBUTES) {
+ SetFileAttributesW(path.String(), FILE_ATTRIBUTE_NORMAL);
+ } else {
+ SetFileAttributesW(path.String(), attributes & ~FILE_ATTRIBUTE_READONLY);
+ }
+ }
+
+ // The SHFILEOPSTRUCTW expects a list of paths. The list is simply one long
+ // string separated by null characters. The end of the list is designated by
+ // two null characters.
+ SimpleAutoString pathList;
+ pathList.AllocAndAssignSprintf(path.Length() + 1, L"%s\0", path.String());
+
+ SHFILEOPSTRUCTW fileOperation;
+ fileOperation.hwnd = nullptr;
+ fileOperation.wFunc = FO_DELETE;
+ fileOperation.pFrom = pathList.String();
+ fileOperation.pTo = nullptr;
+ fileOperation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI;
+ fileOperation.lpszProgressTitle = nullptr;
+
+ int rv = SHFileOperationW(&fileOperation);
+ if (rv == 0 || rv == ERROR_FILE_NOT_FOUND) {
+ return S_OK;
+ }
+
+ // Some files such as hard links can't be deleted properly with
+ // SHFileOperation, so additionally try DeleteFile.
+ BOOL success = DeleteFileW(path.String());
+ return success ? S_OK : HRESULT_FROM_WIN32(GetLastError());
+}
+
+/**
+ * Attempts to move the file or directory to a path that will not conflict with
+ * our directory structure. If this fails, the path will instead be deleted.
+ *
+ * If an attempt results in the error ERROR_FILE_NOT_FOUND, this function
+ * considers the file to no longer be a conflict and returns success.
+ *
+ * The file will be unlocked in order to move it. Strictly speaking, it may be
+ * possible to move non-directories without unlocking them, but this function
+ * will unconditionally unlock the file.
+ *
+ * If a non-null pointer is passed for outPath, the path that the file was moved
+ * to will be stored there. If the file was removed, an empty string will be
+ * stored. Note that if outPath is set to an empty string, it may not have a
+ * buffer allocated, so outPath.Length() should be checked before using
+ * outPath.String().
+ * It is ok for outPath to point to the path parameter.
+ * This function guarantees that if failure is returned, outPath will not be
+ * modified.
+ */
+static HRESULT MoveConflicting(const SimpleAutoString& path,
+ FileOrDirectory& file,
+ SimpleAutoString* outPath) {
+ file.Unlock();
+ // Try to move the file to a backup location
+ SimpleAutoString newPath;
+ unsigned int maxTries = 3;
+ const wchar_t newPathFormat[] = L"%s.bak%u";
+ size_t newPathMaxLength =
+ newPath.AllocFromScprintf(newPathFormat, path.String(), maxTries);
+ if (newPathMaxLength > 0) {
+ for (unsigned int suffix = 0; suffix <= maxTries; ++suffix) {
+ newPath.AssignSprintf(newPathMaxLength + 1, newPathFormat, path.String(),
+ suffix);
+ if (newPath.Length() == 0) {
+ // If we failed to make this string, we probably aren't going to
+ // succeed on the next one.
+ break;
+ }
+ BOOL success;
+ if (suffix < maxTries) {
+ success = MoveFileW(path.String(), newPath.String());
+ } else {
+ // Moving a file can sometimes work when deleting a file does not. If
+ // there are already the maximum number of backed up files, try
+ // overwriting the last backup before we fall back to deleting the
+ // original.
+ success = MoveFileExW(path.String(), newPath.String(),
+ MOVEFILE_REPLACE_EXISTING);
+ }
+ if (success) {
+ if (outPath) {
+ outPath->Swap(newPath);
+ }
+ return S_OK;
+ }
+ DWORD drv = GetLastError();
+ if (drv == ERROR_FILE_NOT_FOUND) {
+ if (outPath) {
+ outPath->Truncate();
+ }
+ return S_OK;
+ }
+ // If the move failed because newPath already exists, loop to try a new
+ // suffix. If the move failed for any other reason, a new suffix will
+ // probably not help.
+ // Sometimes, however, if we cannot read the existing file due to lack of
+ // permissions, we may get an "Access Denied" error. So retry in that case
+ // too.
+ if (drv != ERROR_ALREADY_EXISTS && drv != ERROR_ACCESS_DENIED) {
+ break;
+ }
+ }
+ }
+
+ // Moving failed. Try to delete.
+ HRESULT hrv = RemoveRecursive(path, file);
+ if (SUCCEEDED(hrv)) {
+ if (outPath) {
+ outPath->Truncate();
+ }
+ }
+ return hrv;
+}
+
+/**
+ * This function will ensure that the specified path and all contained files and
+ * subdirectories have the correct permissions.
+ * Files will have their permissions set to match those specified.
+ * Unfortunately, setting the permissions on directories is prone to abuse,
+ * since it can potentially result in a hard link within the directory
+ * inheriting those permissions. To get around this issue, directories will not
+ * have their permissions changed. Instead, the directory will be moved
+ * elsewhere so that it can be recreated with the correct permissions and its
+ * contents moved back in.
+ *
+ * Symlinks and hard links are removed from the checked directories.
+ *
+ * This function also ensures that nothing is in the way of leafUpdateDir.
+ * Non-directory files that conflict with this are moved or deleted.
+ *
+ * This function's second argument must receive a locked FileOrDirectory to
+ * ensure that it is not tampered with while fixing the permissions of the
+ * file/directory and any contents.
+ *
+ * If we cannot successfully determine if the path is a file or directory, we
+ * simply attempt to delete it.
+ *
+ * Note that the path parameter is not constant. Its contents may be changed by
+ * this function.
+ */
+static HRESULT EnsureCorrectPermissions(SimpleAutoString& path,
+ FileOrDirectory& file,
+ const SimpleAutoString& leafUpdateDir,
+ const AutoPerms& perms,
+ SetPermissionsOf permsToSet) {
+ HRESULT returnValue = S_OK; // Stores the value that will eventually be
+ // returned. If errors occur, this is set to the
+ // first error encountered.
+ HRESULT hrv;
+ bool conflictsWithLeaf = PathConflictsWithLeaf(path, leafUpdateDir);
+ if (file.IsDirectory() != Tristate::True ||
+ file.IsLink() != Tristate::False) {
+ // We want to keep track of the result of trying to set the permissions
+ // separately from returnValue. If we later remove the file, we should not
+ // report an error to set permissions.
+ // SetPerms will automatically abort and return failure if it is unsafe to
+ // set the permissions on the file (for example, if it is a hard link).
+ HRESULT permSetResult = file.SetPerms(perms);
+
+ bool removed = false;
+ if (file.IsLink() != Tristate::False) {
+ hrv = RemoveRecursive(path, file);
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+ if (SUCCEEDED(hrv)) {
+ removed = true;
+ }
+ }
+
+ if (FAILED(permSetResult) && !removed) {
+ returnValue = FAILED(returnValue) ? returnValue : permSetResult;
+ }
+
+ if (conflictsWithLeaf && !removed) {
+ hrv = MoveConflicting(path, file, nullptr);
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+ }
+ return returnValue;
+ }
+
+ // If the permissions cannot be read, only try to fix them if the most
+ // aggressive permission-setting option was passed. If Firefox is experiencing
+ // problems updating, it makes sense to try to force the permissions back to
+ // being correct. But there are other times when this is run more proactively,
+ // and we don't really want to move everything around unnecessarily in those
+ // cases.
+ Tristate permissionsOk = file.PermsOk(path, perms);
+ if (permissionsOk == Tristate::False ||
+ (permissionsOk == Tristate::Unknown &&
+ permsToSet == SetPermissionsOf::AllFilesAndDirs)) {
+ bool permissionsFixed;
+ hrv = FixDirectoryPermissions(path, file, perms, permissionsFixed);
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+ // We only need to move conflicting directories if they have bad permissions
+ // that we are unable to fix. If its permissions are correct, it isn't
+ // conflicting with the leaf path, it is a component of the leaf path.
+ if (!permissionsFixed && conflictsWithLeaf) {
+ // No need to check for error here. returnValue is already a failure code
+ // because FixDirectoryPermissions failed. MoveConflicting will ensure
+ // that path is correct (or empty, on deletion) whether it succeeds or
+ // fails.
+ MoveConflicting(path, file, &path);
+ if (path.Length() == 0) {
+ // Path has been deleted.
+ return returnValue;
+ }
+ }
+ if (!file.IsLocked()) {
+ // FixDirectoryPermissions or MoveConflicting may have left the directory
+ // unlocked, but we still want to recurse into it, so re-lock it.
+ file.Reset(path, Lockstate::Locked);
+ }
+ } else if (permissionsOk != Tristate::True) {
+ // If we are skipping permission setting, we want to report failure since
+ // this function did not do its job.
+ returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+ }
+
+ // We MUST not recurse into unlocked directories or links.
+ if (!file.IsLocked() || file.IsLink() != Tristate::False ||
+ file.IsDirectory() != Tristate::True) {
+ returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+ return returnValue;
+ }
+
+ // Recurse into the directory.
+ DIR directoryHandle(path.String());
+ errno = 0;
+ for (dirent* entry = readdir(&directoryHandle); entry;
+ entry = readdir(&directoryHandle)) {
+ if (wcscmp(entry->d_name, L".") == 0 || wcscmp(entry->d_name, L"..") == 0 ||
+ file.LockFilenameMatches(entry->d_name)) {
+ continue;
+ }
+
+ SimpleAutoString childBuffer;
+ if (!childBuffer.AllocEmpty(MAX_PATH)) {
+ // Just return on this failure rather than continuing. It is unlikely that
+ // this error will go away for the next path we try.
+ return FAILED(returnValue) ? returnValue : E_OUTOFMEMORY;
+ }
+
+ childBuffer.AssignSprintf(MAX_PATH + 1, L"%s\\%s", path.String(),
+ entry->d_name);
+ if (childBuffer.Length() == 0) {
+ returnValue = FAILED(returnValue)
+ ? returnValue
+ : HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+ continue;
+ }
+
+ FileOrDirectory child(childBuffer, Lockstate::Locked);
+ hrv = EnsureCorrectPermissions(childBuffer, child, leafUpdateDir, perms,
+ permsToSet);
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+
+ // Before looping, clear any errors that might have been encountered so we
+ // can correctly get errors from readdir.
+ errno = 0;
+ }
+ if (errno != 0) {
+ returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+ }
+
+ return returnValue;
+}
+
+/**
+ * This function fixes directory permissions without setting them directly.
+ * The reasoning behind this is that if someone puts a hardlink in the
+ * directory before we set the permissions, the permissions of the linked file
+ * will be changed too. To prevent this, we will instead move the directory,
+ * recreate it with the correct permissions, and move the contents back in.
+ *
+ * The new directory will be locked with the directory parameter so that the
+ * caller can safely use the new directory. If the function fails, the directory
+ * parameter may be left locked or unlocked. However, the function will never
+ * leave the directory parameter locking something invalid. In other words, if
+ * the directory parameter is locked after this function exits, it is safe to
+ * assume that it is a locked non-link directory at the same location as the
+ * original path.
+ *
+ * The permissionsFixed outparam serves as sort of a supplement to the return
+ * value. The return value will be an error code if any part of this function
+ * fails. But the function can fail at some parts while still completing its
+ * main goal of fixing the directory permissions. To distinguish between these,
+ * this value will be set to true if the directory permissions were successfully
+ * fixed.
+ */
+static HRESULT FixDirectoryPermissions(const SimpleAutoString& path,
+ FileOrDirectory& directory,
+ const AutoPerms& perms,
+ bool& permissionsFixed) {
+ permissionsFixed = false;
+
+ SimpleAutoString parent;
+ SimpleAutoString dirName;
+ HRESULT hrv = SplitPath(path, parent, dirName);
+ if (FAILED(hrv)) {
+ return E_FAIL;
+ }
+
+ SimpleAutoString tempPath;
+ if (!tempPath.AllocEmpty(MAX_PATH)) {
+ return E_FAIL;
+ }
+ BOOL success = GetUUIDTempFilePath(parent.String(), dirName.String(),
+ tempPath.MutableString());
+ if (!success || !tempPath.Check() || tempPath.Length() == 0) {
+ return E_FAIL;
+ }
+
+ directory.Unlock();
+ success = MoveFileW(path.String(), tempPath.String());
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ success = CreateDirectoryW(path.String(), const_cast<LPSECURITY_ATTRIBUTES>(
+ &perms.securityAttributes));
+ if (!success) {
+ return E_FAIL;
+ }
+ directory.Reset(path, Lockstate::Locked);
+ if (!directory.IsLocked() || directory.IsLink() != Tristate::False ||
+ directory.IsDirectory() != Tristate::True ||
+ directory.PermsOk(path, perms) != Tristate::True) {
+ // Don't leave an invalid file locked when we return.
+ directory.Unlock();
+ return E_FAIL;
+ }
+ permissionsFixed = true;
+
+ FileOrDirectory tempDir(tempPath, Lockstate::Locked);
+ if (!tempDir.IsLocked() || tempDir.IsLink() != Tristate::False ||
+ tempDir.IsDirectory() != Tristate::True) {
+ return E_FAIL;
+ }
+
+ SimpleAutoString moveFrom;
+ SimpleAutoString moveTo;
+ if (!moveFrom.AllocEmpty(MAX_PATH) || !moveTo.AllocEmpty(MAX_PATH)) {
+ return E_OUTOFMEMORY;
+ }
+
+ // If we fail to move one file, we still want to try for the others. This will
+ // store the first error we encounter so it can be returned.
+ HRESULT returnValue = S_OK;
+
+ // Copy the contents of tempDir back to the original directory.
+ DIR directoryHandle(tempPath.String());
+ errno = 0;
+ for (dirent* entry = readdir(&directoryHandle); entry;
+ entry = readdir(&directoryHandle)) {
+ if (wcscmp(entry->d_name, L".") == 0 || wcscmp(entry->d_name, L"..") == 0 ||
+ tempDir.LockFilenameMatches(entry->d_name)) {
+ continue;
+ }
+
+ moveFrom.AssignSprintf(MAX_PATH + 1, L"%s\\%s", tempPath.String(),
+ entry->d_name);
+ if (moveFrom.Length() == 0) {
+ returnValue = FAILED(returnValue)
+ ? returnValue
+ : HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+ continue;
+ }
+
+ moveTo.AssignSprintf(MAX_PATH + 1, L"%s\\%s", path.String(), entry->d_name);
+ if (moveTo.Length() == 0) {
+ returnValue = FAILED(returnValue)
+ ? returnValue
+ : HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+ continue;
+ }
+
+ hrv = MoveFileOrDir(moveFrom, moveTo, perms);
+ if (FAILED(hrv)) {
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+ }
+
+ // Before looping, clear any errors that might have been encountered so we
+ // can correctly get errors from readdir.
+ errno = 0;
+ }
+ if (errno != 0) {
+ returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+ }
+
+ hrv = RemoveRecursive(tempPath, tempDir);
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+
+ return returnValue;
+}
+
+/**
+ * This function moves a file or directory from one location to another.
+ * Sometimes it cannot be moved because something (probably anti-virus) has
+ * opened it. In that case, we copy the file and attempt to remove the original.
+ *
+ * If the file cannot be copied, this function will try to remove the original
+ * anyway.
+ */
+static HRESULT MoveFileOrDir(const SimpleAutoString& moveFrom,
+ const SimpleAutoString& moveTo,
+ const AutoPerms& perms) {
+ BOOL success = MoveFileW(moveFrom.String(), moveTo.String());
+ if (success) {
+ return S_OK;
+ }
+
+ FileOrDirectory fileToMove(moveFrom, Lockstate::Locked);
+
+ // If we fail to move one file, we still want to try for the others. This will
+ // store the first error we encounter so it can be returned.
+ HRESULT returnValue = S_OK;
+
+ if (fileToMove.IsDirectory() != Tristate::True) {
+ fileToMove.Unlock();
+ if (fileToMove.IsLink() == Tristate::False) {
+ success = CopyFileW(moveFrom.String(), moveTo.String(), TRUE);
+ if (!success) {
+ returnValue = FAILED(returnValue) ? returnValue
+ : HRESULT_FROM_WIN32(GetLastError());
+ }
+ }
+ success = DeleteFileW(moveFrom.String());
+ if (!success) {
+ // If we failed to delete it, try having it removed at reboot.
+ success =
+ MoveFileExW(moveFrom.String(), nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
+ if (!success) {
+ returnValue = FAILED(returnValue) ? returnValue
+ : HRESULT_FROM_WIN32(GetLastError());
+ }
+ }
+ return returnValue;
+ } // Done handling files. The rest of this function is for moving a
+ // directory.
+
+ success = CreateDirectoryW(moveTo.String(), const_cast<LPSECURITY_ATTRIBUTES>(
+ &perms.securityAttributes));
+ if (!success) {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ FileOrDirectory destDir(moveTo, Lockstate::Locked);
+
+ SimpleAutoString childPath;
+ SimpleAutoString childDestPath;
+ if (!childPath.AllocEmpty(MAX_PATH) || !childDestPath.AllocEmpty(MAX_PATH)) {
+ return E_OUTOFMEMORY;
+ }
+
+ if (!fileToMove.IsLocked() || !destDir.IsLocked() ||
+ destDir.IsDirectory() != Tristate::True ||
+ destDir.IsLink() != Tristate::False) {
+ returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+ } else if (fileToMove.IsLink() == Tristate::False) {
+ DIR directoryHandle(moveFrom.String());
+ errno = 0;
+ for (dirent* entry = readdir(&directoryHandle); entry;
+ entry = readdir(&directoryHandle)) {
+ if (wcscmp(entry->d_name, L".") == 0 ||
+ wcscmp(entry->d_name, L"..") == 0 ||
+ fileToMove.LockFilenameMatches(entry->d_name)) {
+ continue;
+ }
+
+ childPath.AssignSprintf(MAX_PATH + 1, L"%s\\%s", moveFrom.String(),
+ entry->d_name);
+ if (childPath.Length() == 0) {
+ returnValue = FAILED(returnValue)
+ ? returnValue
+ : HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+ continue;
+ }
+
+ childDestPath.AssignSprintf(MAX_PATH + 1, L"%s\\%s", moveTo.String(),
+ entry->d_name);
+ if (childDestPath.Length() == 0) {
+ returnValue = FAILED(returnValue)
+ ? returnValue
+ : HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+ continue;
+ }
+
+ HRESULT hrv = MoveFileOrDir(childPath, childDestPath, perms);
+ if (FAILED(hrv)) {
+ returnValue = FAILED(returnValue) ? returnValue : hrv;
+ }
+
+ // Before looping, clear any errors that might have been encountered so we
+ // can correctly get errors from readdir.
+ errno = 0;
+ }
+ if (errno != 0) {
+ returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+ }
+ }
+
+ // Everything has been copied out of the directory. Now remove it.
+ HRESULT hrv = RemoveRecursive(moveFrom, fileToMove);
+ if (FAILED(hrv)) {
+ // If we failed to remove it, try having it removed on reboot.
+ success =
+ MoveFileExW(moveFrom.String(), nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
+ if (!success) {
+ returnValue = FAILED(returnValue) ? returnValue
+ : HRESULT_FROM_WIN32(GetLastError());
+ }
+ }
+
+ return returnValue;
+}
+
+/**
+ * Splits an absolute path into its parent directory and filename.
+ * For example, splits path="C:\foo\bar" into parentPath="C:\foo" and
+ * filename="bar".
+ */
+static HRESULT SplitPath(const SimpleAutoString& path,
+ SimpleAutoString& parentPath,
+ SimpleAutoString& filename) {
+ HRESULT hrv = parentPath.CopyFrom(path);
+ if (FAILED(hrv) || parentPath.Length() == 0) {
+ return hrv;
+ }
+
+ hrv = GetFilename(parentPath, filename);
+ if (FAILED(hrv)) {
+ return hrv;
+ }
+
+ size_t parentPathLen = parentPath.Length();
+ if (parentPathLen < filename.Length() + 1) {
+ return E_FAIL;
+ }
+ parentPathLen -= filename.Length() + 1;
+ parentPath.Truncate(parentPathLen);
+ if (parentPath.Length() == 0) {
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Gets the filename of the given path. Also removes trailing path separators
+ * from the input path.
+ * Ex: If path="C:\foo\bar", filename="bar"
+ */
+static HRESULT GetFilename(SimpleAutoString& path, SimpleAutoString& filename) {
+ // Remove trailing path separators.
+ size_t pathLen = path.Length();
+ if (pathLen == 0) {
+ return E_FAIL;
+ }
+ wchar_t lastChar = path.String()[pathLen - 1];
+ while (lastChar == '/' || lastChar == '\\') {
+ --pathLen;
+ path.Truncate(pathLen);
+ if (pathLen == 0) {
+ return E_FAIL;
+ }
+ lastChar = path.String()[pathLen - 1];
+ }
+
+ const wchar_t* separator1 = wcsrchr(path.String(), '/');
+ const wchar_t* separator2 = wcsrchr(path.String(), '\\');
+ const wchar_t* separator =
+ (separator1 > separator2) ? separator1 : separator2;
+ if (separator == nullptr) {
+ return E_FAIL;
+ }
+
+ HRESULT hrv = filename.CopyFrom(separator + 1);
+ if (FAILED(hrv) || filename.Length() == 0) {
+ return E_FAIL;
+ }
+ return S_OK;
+}
+
+/**
+ * Returns true if the path conflicts with the leaf path.
+ */
+static bool PathConflictsWithLeaf(const SimpleAutoString& path,
+ const SimpleAutoString& leafPath) {
+ if (!leafPath.StartsWith(path)) {
+ return false;
+ }
+ // Make sure that the next character after the path ends is a path separator
+ // or the end of the string. We don't want to say that "C:\f" conflicts with
+ // "C:\foo\bar".
+ wchar_t charAfterPath = leafPath.String()[path.Length()];
+ return (charAfterPath == L'\\' || charAfterPath == L'\0');
+}
+#endif // XP_WIN