Files
fastfetch/src/common/impl/processing_windows.c
T

282 lines
9.7 KiB
C
Raw Normal View History

2022-10-08 16:04:55 +08:00
#include "fastfetch.h"
#include "common/mallocHelper.h"
2026-01-06 09:48:38 +08:00
#include "common/processing.h"
#include "common/io.h"
#include "common/windows/unicode.h"
#include "common/windows/nt.h"
2022-10-08 16:04:55 +08:00
#include <stdalign.h>
#include <windows.h>
#include <ntstatus.h>
2022-10-08 16:04:55 +08:00
2023-12-04 15:10:58 +08:00
enum { FF_PIPE_BUFSIZ = 8192 };
2026-03-29 09:28:49 +08:00
static void argvToCmdline(char* const argv[], FFstrbuf* result) {
// From https://gist.github.com/jin-x/cdd641d98887524b091fb1f82a68717d
FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate();
2026-03-29 09:28:49 +08:00
for (int i = 0; argv[i] != NULL; i++) {
ffStrbufSetS(&temp, argv[i]);
// Add slash (\) before double quotes (") and duplicate slashes before it
for (
uint32_t pos = ffStrbufFirstIndexC(&temp, '"'), cnt;
pos != temp.length;
2026-03-29 09:28:49 +08:00
pos = ffStrbufNextIndexC(&temp, pos + cnt * 2, '"')) {
cnt = 1;
while (pos > 0 && temp.chars[pos - 1] == '\\') { ++cnt, --pos; }
ffStrbufInsertNC(&temp, pos, cnt, '\\');
}
// Add quotes around string if whitespace chars are present (with slash duplicating at the end of string)
2026-03-29 09:28:49 +08:00
if (ffStrbufFirstIndexS(&temp, " \t") != temp.length) {
uint32_t pos = temp.length;
uint32_t cnt = 0;
while (pos > 0 && temp.chars[pos - 1] == '\\') { ++cnt, --pos; }
2026-03-29 09:28:49 +08:00
if (cnt > 0) {
ffStrbufAppendNC(&temp, cnt, '\\');
}
ffStrbufPrependC(&temp, '"');
ffStrbufAppendC(&temp, '"');
}
// Add space delimiter
2026-03-29 09:28:49 +08:00
if (i > 0) {
ffStrbufAppendC(result, ' ');
}
ffStrbufAppend(result, &temp);
ffStrbufClear(&temp);
}
}
2026-03-29 09:28:49 +08:00
const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle) {
const int32_t timeout = instance.config.general.processingTimeout;
2022-10-08 16:04:55 +08:00
wchar_t pipeName[32];
static unsigned pidCounter = 0;
2026-02-12 13:56:55 +08:00
swprintf(pipeName, ARRAY_SIZE(pipeName), L"\\\\.\\pipe\\FASTFETCH-%u-%u", instance.state.platform.pid, ++pidCounter);
FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = CreateNamedPipeW(
pipeName,
PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | (timeout < 0 ? 0 : FILE_FLAG_OVERLAPPED),
0,
1,
FF_PIPE_BUFSIZ,
FF_PIPE_BUFSIZ,
0,
2026-03-29 09:28:49 +08:00
NULL);
if (hChildPipeRead == INVALID_HANDLE_VALUE) {
return "CreateNamedPipeW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
2026-03-29 09:28:49 +08:00
}
2022-10-08 16:04:55 +08:00
HANDLE hChildPipeWrite = CreateFileW(
pipeName,
GENERIC_WRITE,
0,
2026-03-29 09:28:49 +08:00
&(SECURITY_ATTRIBUTES) {
.nLength = sizeof(SECURITY_ATTRIBUTES),
.lpSecurityDescriptor = NULL,
.bInheritHandle = TRUE,
},
OPEN_EXISTING,
0,
2026-03-29 09:28:49 +08:00
NULL);
if (hChildPipeWrite == INVALID_HANDLE_VALUE) {
return "CreateFileW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
2026-03-29 09:28:49 +08:00
}
2022-10-08 16:04:55 +08:00
PROCESS_INFORMATION piProcInfo = {};
STARTUPINFOW siStartInfo = {
.cb = sizeof(siStartInfo),
.dwFlags = STARTF_USESTDHANDLES,
};
2026-03-29 09:28:49 +08:00
if (useStdErr) {
siStartInfo.hStdOutput = ffGetNullFD();
siStartInfo.hStdError = hChildPipeWrite;
2026-03-29 09:28:49 +08:00
} else {
siStartInfo.hStdOutput = hChildPipeWrite;
siStartInfo.hStdError = ffGetNullFD();
2022-11-13 00:04:34 +08:00
}
2022-10-13 20:06:54 +08:00
FF_AUTO_FREE wchar_t* cmdline = NULL;
{
FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
argvToCmdline(argv, &buf);
uint32_t cmdlineBytes = (buf.length + 1) * sizeof(wchar_t);
cmdline = malloc(cmdlineBytes);
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(cmdline, cmdlineBytes, NULL, buf.chars, buf.length + 1))) {
return "RtlUTF8ToUnicodeN() failed";
2026-03-29 09:28:49 +08:00
}
}
BOOL success = CreateProcessW(
2026-03-29 09:28:49 +08:00
NULL, // application name
cmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo // receives PROCESS_INFORMATION
);
NtClose(hChildPipeWrite);
2026-03-29 09:28:49 +08:00
if (!success) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return "command not found";
2026-03-29 09:28:49 +08:00
}
return "CreateProcessW() failed";
}
NtClose(piProcInfo.hThread); // we don't need the thread handle
2026-03-29 09:28:49 +08:00
outHandle->pid = piProcInfo.hProcess;
outHandle->pipeRead = hChildPipeRead;
hChildPipeRead = INVALID_HANDLE_VALUE; // ownership transferred, don't close it
return NULL;
}
2026-03-29 09:28:49 +08:00
static void terminateChildProcess(HANDLE hProcess, HANDLE hChildPipeRead, HANDLE hReadEvent, IO_STATUS_BLOCK* piosb) {
IO_STATUS_BLOCK cancelIosb = {};
2026-03-29 09:28:49 +08:00
if (NT_SUCCESS(NtCancelIoFileEx(hChildPipeRead, piosb, &cancelIosb))) {
if (hReadEvent) {
2026-03-30 10:27:23 +08:00
NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = -100000 }); // wait for cancellation to complete
2026-03-29 09:28:49 +08:00
}
}
NtTerminateProcess(hProcess, 1);
}
2026-03-29 09:28:49 +08:00
const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
assert(handle->pipeRead != INVALID_HANDLE_VALUE);
assert(handle->pid != INVALID_HANDLE_VALUE);
int32_t timeout = instance.config.general.processingTimeout;
FF_AUTO_CLOSE_FD HANDLE hProcess = handle->pid;
FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = handle->pipeRead;
2026-03-09 20:24:19 +08:00
FF_AUTO_CLOSE_FD HANDLE hReadEvent = NULL;
handle->pid = INVALID_HANDLE_VALUE;
handle->pipeRead = INVALID_HANDLE_VALUE;
2026-03-29 09:28:49 +08:00
if (timeout >= 0 && !NT_SUCCESS(NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE))) {
2026-03-09 20:24:19 +08:00
return "NtCreateEvent() failed";
2026-03-29 09:28:49 +08:00
}
2026-03-09 20:24:19 +08:00
char str[FF_PIPE_BUFSIZ];
2026-03-09 20:24:19 +08:00
uint32_t nRead = 0;
IO_STATUS_BLOCK iosb = {};
2026-03-29 09:28:49 +08:00
do {
2026-03-09 20:24:19 +08:00
NTSTATUS status = NtReadFile(
hChildPipeRead,
hReadEvent,
NULL,
NULL,
&iosb,
str,
(ULONG) sizeof(str),
NULL,
2026-03-29 09:28:49 +08:00
NULL);
if (status == STATUS_PENDING) {
2026-03-30 10:27:23 +08:00
switch (NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 })) {
2026-03-29 09:28:49 +08:00
case STATUS_WAIT_0:
status = iosb.Status;
break;
case STATUS_TIMEOUT: {
terminateChildProcess(hProcess, hChildPipeRead, hReadEvent, &iosb);
return "NtReadFile(hChildPipeRead) timed out";
}
default:
terminateChildProcess(hProcess, hChildPipeRead, hReadEvent, &iosb);
return "NtWaitForSingleObject(hReadEvent) failed";
}
}
2026-03-09 20:24:19 +08:00
2026-03-29 09:28:49 +08:00
if (status == STATUS_PIPE_BROKEN || status == STATUS_END_OF_FILE) {
2026-03-09 20:24:19 +08:00
goto exit;
2026-03-29 09:28:49 +08:00
}
2026-03-09 20:24:19 +08:00
2026-03-29 09:28:49 +08:00
if (!NT_SUCCESS(status)) {
terminateChildProcess(hProcess, hChildPipeRead, NULL, &iosb);
2026-03-09 20:24:19 +08:00
return "NtReadFile(hChildPipeRead) failed";
}
nRead = (uint32_t) iosb.Information;
ffStrbufAppendNS(buffer, nRead, str);
} while (nRead > 0);
2026-03-29 09:28:49 +08:00
exit: {
PROCESS_BASIC_INFORMATION info = {};
ULONG size;
if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size))) {
assert(size == sizeof(info));
if (info.ExitStatus != STILL_ACTIVE && info.ExitStatus != 0) {
return "Child process exited with an error";
}
2026-03-29 09:28:49 +08:00
} else {
return "NtQueryInformationProcess(ProcessBasicInformation) failed";
}
2026-03-29 09:28:49 +08:00
}
2022-10-08 16:04:55 +08:00
return NULL;
}
2026-03-29 09:28:49 +08:00
bool ffProcessGetInfoWindows(uint32_t pid, uint32_t* ppid, FFstrbuf* pname, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath, bool* gui) {
2026-03-16 14:41:52 +08:00
FF_AUTO_CLOSE_FD HANDLE hProcess = NtCurrentProcess();
2026-03-29 09:28:49 +08:00
if (pid != 0) {
2026-03-16 14:41:52 +08:00
if (!NT_SUCCESS(NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &(OBJECT_ATTRIBUTES) {
2026-03-29 09:28:49 +08:00
.Length = sizeof(OBJECT_ATTRIBUTES),
},
2026-03-30 10:27:23 +08:00
&(CLIENT_ID) { .UniqueProcess = (HANDLE) (uintptr_t) pid }))) {
2026-03-16 14:41:52 +08:00
return false;
2026-03-29 09:28:49 +08:00
}
2026-03-16 14:41:52 +08:00
}
2026-03-29 09:28:49 +08:00
if (ppid) {
PROCESS_BASIC_INFORMATION info = {};
ULONG size;
2026-03-29 09:28:49 +08:00
if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size))) {
assert(size == sizeof(info));
2026-03-29 09:28:49 +08:00
*ppid = (uint32_t) info.InheritedFromUniqueProcessId;
} else {
return false;
2026-03-29 09:28:49 +08:00
}
}
2026-03-29 09:28:49 +08:00
if (exe) {
2026-02-11 21:02:14 +08:00
// TODO: It's possible to query the command line with `NtQueryInformationProcess(60/*ProcessCommandLineInformation*/)` since Windows 8.1
2026-02-27 15:04:43 +08:00
alignas(UNICODE_STRING) uint8_t buffer[4096];
ULONG size;
2026-03-29 09:28:49 +08:00
if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageFileNameWin32, &buffer, sizeof(buffer), &size))) {
UNICODE_STRING* imagePath = (UNICODE_STRING*) buffer;
ffStrbufSetNWS(exe, imagePath->Length / sizeof(wchar_t), imagePath->Buffer);
2026-03-29 09:28:49 +08:00
if (exePath) {
ffStrbufSet(exePath, exe);
}
2026-03-29 09:28:49 +08:00
if (pname && exeName) {
*exeName = exe->chars + ffStrbufLastIndexC(exe, '\\') + 1;
ffStrbufSetS(pname, *exeName);
}
2026-03-29 09:28:49 +08:00
} else {
return false;
2026-03-29 09:28:49 +08:00
}
}
2026-03-29 09:28:49 +08:00
if (gui) {
SECTION_IMAGE_INFORMATION info = {};
ULONG size;
2026-03-29 09:28:49 +08:00
if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageInformation, &info, sizeof(info), &size))) {
assert(size == sizeof(info));
*gui = info.SubSystemType == IMAGE_SUBSYSTEM_WINDOWS_GUI;
2026-03-29 09:28:49 +08:00
} else {
return false;
2026-03-29 09:28:49 +08:00
}
}
return true;
}