gecko-dev/widget/windows/WinHeaderOnlyUtils.h
Masatoshi Kimura 58a64c3e17 Bug 1496387 - Avoid changing the path format back and forth in order to reduce possibility of failures. r=mhowell
QueryFullProcessImageNameW may fail in some environments when dwFlags is zero
due to NT-to-DOS path conversions. CreateFile will convert the argumant back to
an NT path anyway.

--HG--
extra : source : 54778cf89825c24f656fcf8ebcef5aa3bf4e6839
extra : intermediate-source : b40d295b3c43d5cffa453bc0536af2bada4eeac2
2018-10-05 22:30:37 +09:00

225 lines
5.9 KiB
C++

/* -*- 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 https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_WinHeaderOnlyUtils_h
#define mozilla_WinHeaderOnlyUtils_h
#include <windows.h>
#include <winternl.h>
#include "mozilla/Maybe.h"
#include "mozilla/WindowsVersion.h"
#include "nsWindowsHelpers.h"
/**
* This header is intended for self-contained, header-only, utility code for
* Win32. It may be used outside of xul.dll, in places such as firefox.exe or
* mozglue.dll. If your code creates dependencies on Mozilla libraries, you
* should put it elsewhere.
*/
#if _WIN32_WINNT < _WIN32_WINNT_WIN8
typedef struct _FILE_ID_INFO
{
ULONGLONG VolumeSerialNumber;
FILE_ID_128 FileId;
} FILE_ID_INFO;
#define FileIdInfo ((FILE_INFO_BY_HANDLE_CLASS)18)
#endif // _WIN32_WINNT < _WIN32_WINNT_WIN8
namespace mozilla {
// How long to wait for a created process to become available for input,
// to prevent that process's windows being forced to the background.
// This is used across update, restart, and the launcher.
const DWORD kWaitForInputIdleTimeoutMS = 10*1000;
/**
* Wait for a child GUI process to become "idle." Idle means that the process
* has created its message queue and has begun waiting for user input.
*
* Note that this must only be used when the child process is going to display
* GUI! Otherwise you're going to be waiting for a very long time ;-)
*
* @return true if we successfully waited for input idle;
* false if we timed out or failed to wait.
*/
inline bool
WaitForInputIdle(HANDLE aProcess, DWORD aTimeoutMs = kWaitForInputIdleTimeoutMS)
{
const DWORD kSleepTimeMs = 10;
const DWORD waitStart = aTimeoutMs == INFINITE ? 0 : ::GetTickCount();
DWORD elapsed = 0;
while (true) {
if (aTimeoutMs != INFINITE) {
elapsed = ::GetTickCount() - waitStart;
}
if (elapsed >= aTimeoutMs) {
return false;
}
DWORD waitResult = ::WaitForInputIdle(aProcess, aTimeoutMs - elapsed);
if (!waitResult) {
return true;
}
if (waitResult == WAIT_FAILED && ::GetLastError() == ERROR_NOT_GUI_PROCESS) {
::Sleep(kSleepTimeMs);
continue;
}
return false;
}
}
enum PathType {
eNtPath,
eDosPath,
};
class FileUniqueId final
{
public:
explicit FileUniqueId(const wchar_t* aPath, PathType aPathType)
: mId()
{
if (!aPath) {
return;
}
nsAutoHandle file(INVALID_HANDLE_VALUE);
switch (aPathType) {
default:
return;
case eNtPath:
{
UNICODE_STRING unicodeString;
::RtlInitUnicodeString(&unicodeString, aPath);
OBJECT_ATTRIBUTES objectAttributes;
InitializeObjectAttributes(&objectAttributes, &unicodeString,
OBJ_CASE_INSENSITIVE, nullptr, nullptr);
IO_STATUS_BLOCK ioStatus = {};
HANDLE ntHandle;
NTSTATUS status = ::NtOpenFile(&ntHandle, SYNCHRONIZE |
FILE_READ_ATTRIBUTES,
&objectAttributes, &ioStatus,
FILE_SHARE_READ | FILE_SHARE_WRITE |
FILE_SHARE_DELETE,
FILE_SYNCHRONOUS_IO_NONALERT |
FILE_OPEN_FOR_BACKUP_INTENT);
if (!NT_SUCCESS(status) || ntHandle == INVALID_HANDLE_VALUE) {
return;
}
file.own(ntHandle);
}
break;
case eDosPath:
file.own(::CreateFileW(aPath, 0, FILE_SHARE_READ |
FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, nullptr));
if (file == INVALID_HANDLE_VALUE) {
return;
}
break;
}
GetId(file);
}
explicit FileUniqueId(const nsAutoHandle& aFile)
: mId()
{
GetId(aFile);
}
FileUniqueId(const FileUniqueId& aOther)
: mId(aOther.mId)
{
}
~FileUniqueId() = default;
explicit operator bool() const
{
FILE_ID_INFO zeros = {};
return memcmp(&mId, &zeros, sizeof(FILE_ID_INFO));
}
FileUniqueId& operator=(const FileUniqueId& aOther)
{
mId = aOther.mId;
return *this;
}
FileUniqueId(FileUniqueId&& aOther) = delete;
FileUniqueId& operator=(FileUniqueId&& aOther) = delete;
bool operator==(const FileUniqueId& aOther) const
{
return !memcmp(&mId, &aOther.mId, sizeof(FILE_ID_INFO));
}
bool operator!=(const FileUniqueId& aOther) const
{
return !((*this) == aOther);
}
private:
void GetId(const nsAutoHandle& aFile)
{
if (IsWin8OrLater()) {
if (::GetFileInformationByHandleEx(aFile.get(), FileIdInfo, &mId, sizeof(mId))) {
return;
}
// Only NTFS and ReFS support FileIdInfo. So we have to fallback if
// GetFileInformationByHandleEx failed.
}
BY_HANDLE_FILE_INFORMATION info = {};
if (!::GetFileInformationByHandle(aFile.get(), &info)) {
return;
}
mId.VolumeSerialNumber = info.dwVolumeSerialNumber;
memcpy(&mId.FileId.Identifier[0], &info.nFileIndexLow,
sizeof(DWORD));
memcpy(&mId.FileId.Identifier[sizeof(DWORD)], &info.nFileIndexHigh,
sizeof(DWORD));
}
private:
FILE_ID_INFO mId;
};
inline Maybe<bool>
DoPathsPointToIdenticalFile(const wchar_t* aPath1, const wchar_t* aPath2,
PathType aPathType1 = eDosPath,
PathType aPathType2 = eDosPath)
{
FileUniqueId id1(aPath1, aPathType1);
if (!id1) {
return Nothing();
}
FileUniqueId id2(aPath2, aPathType2);
if (!id2) {
return Nothing();
}
return Some(id1 == id2);
}
} // namespace mozilla
#endif // mozilla_WinHeaderOnlyUtils_h