Source code

Revision control

Copy as Markdown

Other Tools

/* 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 old update directory
// (i.e. C:\ProgramData\<OLD_ROOT_UPDATE_DIR_NAME>)
# define OLD_ROOT_UPDATE_DIR_NAME "Mozilla"
// This is the name of the current update directory
// (i.e. C:\ProgramData\<ROOT_UPDATE_DIR_NAME>)
// It is really important that we properly set the permissions on this
// directory at creation time. Thus, it is really important that this code be
// the creator of this directory. We had many problems with the old update
// directory having been previously created by old versions of Firefox. To avoid
// this problem in the future, we are including a UUID in the root update
// directory name to attempt to ensure that it will be created by this code and
// won't already exist with the wrong permissions.
# define ROOT_UPDATE_DIR_NAME "Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38"
// 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"
enum class WhichUpdateDir {
CurrentUpdateDir,
UnmigratedUpdateDir,
};
/**
* 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 bool GetCachedHash(const char16_t* installPath, HKEY rootKey,
const SimpleAutoString& regPath,
mozilla::UniquePtr<NS_tchar[]>& result);
static HRESULT GetUpdateDirectory(const wchar_t* installPath,
WhichUpdateDir whichDir,
mozilla::UniquePtr<wchar_t[]>& result);
static HRESULT GeneratePermissions(AutoPerms& result);
static HRESULT MakeDir(const SimpleAutoString& path, const AutoPerms& perms);
#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 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.
* @return true if successful and false otherwise.
*/
bool GetInstallHash(const char16_t* installPath,
mozilla::UniquePtr<NS_tchar[]>& result) {
MOZ_ASSERT(installPath != nullptr,
"Install path must not be null in GetInstallHash");
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 =
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);
// 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.
*
* @param installPath
* Must be the null-terminated path to the installation directory (i.e.
* the directory that contains the binary). The path must not include a
* trailing slash.
* @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,
mozilla::UniquePtr<wchar_t[]>& result) {
return GetUpdateDirectory(installPath, WhichUpdateDir::CurrentUpdateDir,
result);
}
/**
* This function is identical to the function above except that it gets the
* "old" (pre-migration) update directory.
*
* The other difference is that this function does not create the directory.
*/
HRESULT
GetOldUpdateDirectory(const wchar_t* installPath,
mozilla::UniquePtr<wchar_t[]>& result) {
return GetUpdateDirectory(installPath, WhichUpdateDir::UnmigratedUpdateDir,
result);
}
/**
* This is a 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, 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.
*
* For information on the parameters and return value, see
* GetCommonUpdateDirectory.
*/
static HRESULT GetUpdateDirectory(const wchar_t* installPath,
WhichUpdateDir whichDir,
mozilla::UniquePtr<wchar_t[]>& result) {
MOZ_ASSERT(installPath != nullptr,
"Install path must not be null in GetUpdateDirectory");
AutoPerms perms;
HRESULT hrv = GeneratePermissions(perms);
if (FAILED(hrv)) {
return hrv;
}
PWSTR baseDirParentPath;
hrv = SHGetKnownFolderPath(FOLDERID_ProgramData, 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::UnmigratedUpdateDir) {
const wchar_t baseDirLiteral[] = NS_T(OLD_ROOT_UPDATE_DIR_NAME);
hrv = baseDir.CopyFrom(baseDirLiteral);
} else {
const wchar_t baseDirLiteral[] = NS_T(ROOT_UPDATE_DIR_NAME);
hrv = baseDir.CopyFrom(baseDirLiteral);
}
if (FAILED(hrv)) {
return hrv;
}
// Generate the base path
// (C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38)
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;
}
if (whichDir == WhichUpdateDir::CurrentUpdateDir) {
hrv = MakeDir(basePath, perms);
if (FAILED(hrv)) {
return hrv;
}
}
// Generate what we are going to call the mid-path
// (C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38\updates)
const wchar_t midPathDirName[] = NS_T(UPDATE_PATH_MID_DIR_NAME);
size_t midPathLen =
basePath.Length() + 1 /* path separator */ + wcslen(midPathDirName);
SimpleAutoString midPath;
midPath.AllocAndAssignSprintf(midPathLen, L"%s\\%s", basePath.String(),
midPathDirName);
if (midPath.Length() != midPathLen) {
return E_FAIL;
}
mozilla::UniquePtr<NS_tchar[]> hash;
// The Windows installer caches this hash value in the registry
bool gotHash = false;
SimpleAutoString regPath;
regPath.AutoAllocAndAssignSprintf(L"SOFTWARE\\Mozilla\\%S\\TaskBarIDs",
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);
}
}
// If we couldn't get it out of the registry, we'll just have to regenerate
// it.
if (!gotHash) {
bool success =
GetInstallHash(reinterpret_cast<const char16_t*>(installPath), hash);
if (!success) {
return E_FAIL;
}
}
size_t updatePathLen =
midPath.Length() + 1 /* path separator */ + wcslen(hash.get());
SimpleAutoString updatePath;
updatePath.AllocAndAssignSprintf(updatePathLen, L"%s\\%s", midPath.String(),
hash.get());
if (updatePath.Length() != updatePathLen) {
return E_FAIL;
}
updatePath.SwapBufferWith(result);
return S_OK;
}
/**
* 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.
*/
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 S_OK;
}
return HRESULT_FROM_WIN32(error);
}
#endif // XP_WIN