view dep/animone/src/fd/win32.cc @ 337:a7d4e5107531

dep/animone: REFACTOR ALL THE THINGS 1: animone now has its own syntax divergent from anisthesia, making different platforms actually have their own sections 2: process names in animone are now called `comm' (this will probably break things). this is what its called in bsd/linux so I'm just going to use it everywhere 3: the X11 code now checks for the existence of a UTF-8 window title and passes it if available 4: ANYTHING THATS NOT LINUX IS 100% UNTESTED AND CAN AND WILL BREAK! I still actually need to test the bsd code. to be honest I'm probably going to move all of the bsds into separate files because they're all essentially different operating systems at this point
author Paper <paper@paper.us.eu.org>
date Wed, 19 Jun 2024 12:51:15 -0400
parents a4257370de16
children 74e2365326c6
line wrap: on
line source

/*
 * fd/win32.cc: support for windows
 *
 * this file is noticably more complex than *nix, and that's because
 * there is no "simple" way to get the paths of a file. In fact, this thing requires
 * you to use *internal functions* that can't even be linked to, hence why we have to
 * use GetProcAddress and such. what a mess.
 *
 * Speaking of which, because this file uses internal functions of the OS, it is not
 * even guaranteed to work far into the future. however, just like with macOS, these
 * things have stayed the same since Vista so if Microsoft *really* wants compatibility
 * then they're pretty much forced to keeping this the same anyway.
 */
#include "animone/fd/win32.h"
#include "animone.h"
#include "animone/util/win32.h"

#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>

#include <fileapi.h>
#include <handleapi.h>
#include <libloaderapi.h>
#include <ntdef.h>
#include <psapi.h>
#include <shlobj.h>
#include <stringapiset.h>
#include <tlhelp32.h>
#include <windows.h>
#include <winternl.h>

/* SystemExtendedHandleInformation is only available in NT 5.1+ (XP and higher) and provides information for
 * 32-bit PIDs, unlike SystemHandleInformation
 */
static constexpr SYSTEM_INFORMATION_CLASS SystemExtendedHandleInformation = static_cast<SYSTEM_INFORMATION_CLASS>(0x40);
static constexpr NTSTATUS STATUS_INFO_LENGTH_MISMATCH = 0xC0000004UL;

/* this is filled in at runtime because it's not guaranteed to be (and isn't)
 * constant between different versions of Windows */
static unsigned short file_type_index = 0;

struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX {
	PVOID Object;
	ULONG_PTR UniqueProcessId;
	HANDLE HandleValue;
	ACCESS_MASK GrantedAccess;
	USHORT CreatorBackTraceIndex;
	USHORT ObjectTypeIndex;
	ULONG HandleAttributes;
	ULONG Reserved;
};

struct SYSTEM_HANDLE_INFORMATION_EX {
	ULONG_PTR NumberOfHandles;
	ULONG_PTR Reserved;
	SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
};

namespace animone::internal::win32 {

class Ntdll {
public:
	Ntdll() {
		ntdll = ::GetModuleHandleW(L"ntdll.dll");
		nt_query_system_information = reinterpret_cast<decltype(::NtQuerySystemInformation)*>(
		    ::GetProcAddress(ntdll, "NtQuerySystemInformation"));
		nt_query_object = reinterpret_cast<decltype(::NtQueryObject)*>(::GetProcAddress(ntdll, "NtQueryObject"));
	}

	NTSTATUS QuerySystemInformation(SYSTEM_INFORMATION_CLASS cls, PVOID sysinfo, ULONG len,
	                                PULONG retlen){
		return nt_query_system_information(cls, sysinfo, len, retlen);
	}

	NTSTATUS QueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS cls, PVOID objinf, ULONG objinflen, PULONG retlen) {
		return nt_query_object(handle, cls, objinf, objinflen, retlen);
	}

private:
	HMODULE ntdll;
	decltype(::NtQuerySystemInformation)* nt_query_system_information;
	decltype(::NtQueryObject)* nt_query_object;
};

Ntdll ntdll;

static HANDLE DuplicateHandle(HANDLE process_handle, HANDLE handle) {
	HANDLE dup_handle = nullptr;
	const bool result =
	    ::DuplicateHandle(process_handle, handle, ::GetCurrentProcess(), &dup_handle, 0, false, DUPLICATE_SAME_ACCESS);
	return result ? dup_handle : nullptr;
}

static std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetSystemHandleInformation() {
	/* we should really put a cap on this */
	ULONG cb = 1 << 19;
	NTSTATUS status = STATUS_NO_MEMORY;
	std::unique_ptr<SYSTEM_HANDLE_INFORMATION_EX> info;

	do {
		info.reset(reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(malloc(cb *= 2)));
		if (!info)
			continue;

		status = ntdll.QuerySystemInformation(SystemExtendedHandleInformation, info.get(), cb, &cb);
	} while (status == STATUS_INFO_LENGTH_MISMATCH);

	if (!NT_SUCCESS(status))
		return {};

	std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> res;

	ULONG_PTR handles = info->NumberOfHandles;
	if (!handles)
		return {};

	res.reserve(handles);

	SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* entry = info->Handles;
	do {
		if (entry)
			res.push_back(*(entry++));
	} while (--handles);

	return res;
}

static std::wstring GetHandleType(HANDLE handle) {
	OBJECT_TYPE_INFORMATION info = {0};
	ntdll.QueryObject(handle, ObjectTypeInformation, &info, sizeof(info), NULL);
	return std::wstring(info.TypeName.Buffer, info.TypeName.Length);
}

static std::wstring GetFinalPathNameByHandle(HANDLE handle) {
	std::wstring buffer;

	DWORD size = ::GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
	buffer.resize(size);
	::GetFinalPathNameByHandleW(handle, &buffer.front(), buffer.size(), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);

	return buffer;
}

static bool IsFileHandle(HANDLE handle, unsigned short object_type_index) {
	if (file_type_index)
		return object_type_index == file_type_index;
	else if (!handle)
		return true;
	else if (GetHandleType(handle) == L"File") {
		file_type_index = object_type_index;
		return true;
	}
	return false;
}

static bool IsFileMaskOk(ACCESS_MASK access_mask) {
	if (!(access_mask & FILE_READ_DATA))
		return false;

	if ((access_mask & FILE_APPEND_DATA) || (access_mask & FILE_WRITE_EA) || (access_mask & FILE_WRITE_ATTRIBUTES))
		return false;

	return true;
}

static bool IsFilePathOk(const std::wstring& path) {
	if (path.empty())
		return false;

	if (IsSystemDirectory(path))
		return false;

	const auto file_attributes = GetFileAttributesW(path.c_str());
	if ((file_attributes == INVALID_FILE_ATTRIBUTES) || (file_attributes & FILE_ATTRIBUTE_DIRECTORY))
		return false;

	return true;
}

bool GetProcessName(pid_t pid, std::string& name) {
	std::string path = GetProcessPath(pid);
	if (path.empty() || !VerifyProcessPath(path))
		return false;

	name = GetFileNameFromPath(path);
	if (!VerifyProcessFileName(name))
		return false;

	return true;
}

bool EnumerateOpenProcesses(process_proc_t process_proc) {
	Handle process_snap(::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
	if (process_snap.get() == INVALID_HANDLE_VALUE)
		return false;

	PROCESSENTRY32 pe32;
	pe32.dwSize = sizeof(PROCESSENTRY32);

	if (!::Process32First(process_snap.get(), &pe32))
		return false;

	do {
		std::string name;
		if (!GetProcessName(pe32.th32ProcessID, name))
			continue;

		if (!process_proc({.platform = ExecutablePlatform::Win32, .pid = pe32.th32ProcessID, .comm = name}))
			return false;
	} while (::Process32Next(process_snap.get(), &pe32));

	return true;
}

/* this could be changed to being a callback, but... I'm too lazy right now :) */
bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
	if (!open_file_proc)
		return false;

	std::unordered_map<pid_t, Handle> proc_handles;

	for (const pid_t& pid : pids) {
		const HANDLE handle = ::OpenProcess(PROCESS_DUP_HANDLE, false, pid);
		if (handle != INVALID_HANDLE_VALUE)
			proc_handles[pid] = Handle(handle);
	}

	if (proc_handles.empty())
		return false;

	std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation();

	for (const auto& h : info) {
		const pid_t pid = h.UniqueProcessId;
		if (!pids.count(pid))
			continue;

		if (!IsFileHandle(nullptr, h.ObjectTypeIndex))
			continue;

		if (!IsFileMaskOk(h.GrantedAccess))
			continue;

		Handle handle(DuplicateHandle(proc_handles[pid].get(), h.HandleValue));
		if (handle.get() == INVALID_HANDLE_VALUE)
			continue;

		if (GetFileType(handle.get()) != FILE_TYPE_DISK)
			continue;

		const std::wstring path = GetFinalPathNameByHandle(handle.get());
		if (!IsFilePathOk(path))
			continue;

		if (!open_file_proc({pid, ToUtf8String(path)}))
			return false;
	}

	return true;
}

} // namespace animone::internal::win32