summaryrefslogtreecommitdiffstats
path: root/src/common/impl/processing_windows.c
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/common/impl/processing_windows.c
Add the files
Diffstat (limited to 'src/common/impl/processing_windows.c')
-rw-r--r--src/common/impl/processing_windows.c281
1 files changed, 281 insertions, 0 deletions
diff --git a/src/common/impl/processing_windows.c b/src/common/impl/processing_windows.c
new file mode 100644
index 0000000..846276b
--- /dev/null
+++ b/src/common/impl/processing_windows.c
@@ -0,0 +1,281 @@
+#include "fastfetch.h"
+#include "common/mallocHelper.h"
+#include "common/processing.h"
+#include "common/io.h"
+#include "common/windows/unicode.h"
+#include "common/windows/nt.h"
+
+#include <stdalign.h>
+#include <windows.h>
+#include <ntstatus.h>
+
+enum { FF_PIPE_BUFSIZ = 8192 };
+
+static void argvToCmdline(char* const argv[], FFstrbuf* result) {
+ // From https://gist.github.com/jin-x/cdd641d98887524b091fb1f82a68717d
+
+ FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate();
+ 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;
+ 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)
+ if (ffStrbufFirstIndexS(&temp, " \t") != temp.length) {
+ uint32_t pos = temp.length;
+ uint32_t cnt = 0;
+ while (pos > 0 && temp.chars[pos - 1] == '\\') { ++cnt, --pos; }
+ if (cnt > 0) {
+ ffStrbufAppendNC(&temp, cnt, '\\');
+ }
+ ffStrbufPrependC(&temp, '"');
+ ffStrbufAppendC(&temp, '"');
+ }
+
+ // Add space delimiter
+ if (i > 0) {
+ ffStrbufAppendC(result, ' ');
+ }
+ ffStrbufAppend(result, &temp);
+ ffStrbufClear(&temp);
+ }
+}
+
+const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle) {
+ const int32_t timeout = instance.config.general.processingTimeout;
+
+ wchar_t pipeName[32];
+ static unsigned pidCounter = 0;
+ 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,
+ NULL);
+ if (hChildPipeRead == INVALID_HANDLE_VALUE) {
+ return "CreateNamedPipeW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
+ }
+
+ HANDLE hChildPipeWrite = CreateFileW(
+ pipeName,
+ GENERIC_WRITE,
+ 0,
+ &(SECURITY_ATTRIBUTES) {
+ .nLength = sizeof(SECURITY_ATTRIBUTES),
+ .lpSecurityDescriptor = NULL,
+ .bInheritHandle = TRUE,
+ },
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if (hChildPipeWrite == INVALID_HANDLE_VALUE) {
+ return "CreateFileW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
+ }
+
+ PROCESS_INFORMATION piProcInfo = {};
+ STARTUPINFOW siStartInfo = {
+ .cb = sizeof(siStartInfo),
+ .dwFlags = STARTF_USESTDHANDLES,
+ };
+ if (useStdErr) {
+ siStartInfo.hStdOutput = ffGetNullFD();
+ siStartInfo.hStdError = hChildPipeWrite;
+ } else {
+ siStartInfo.hStdOutput = hChildPipeWrite;
+ siStartInfo.hStdError = ffGetNullFD();
+ }
+
+ 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);
+ if (!NT_SUCCESS(RtlUTF8ToUnicodeN(cmdline, cmdlineBytes, NULL, buf.chars, buf.length + 1))) {
+ return "RtlUTF8ToUnicodeN() failed";
+ }
+ }
+
+ BOOL success = CreateProcessW(
+ 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);
+ if (!success) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+ return "command not found";
+ }
+ return "CreateProcessW() failed";
+ }
+
+ NtClose(piProcInfo.hThread); // we don't need the thread handle
+ outHandle->pid = piProcInfo.hProcess;
+ outHandle->pipeRead = hChildPipeRead;
+ hChildPipeRead = INVALID_HANDLE_VALUE; // ownership transferred, don't close it
+
+ return NULL;
+}
+
+static void terminateChildProcess(HANDLE hProcess, HANDLE hChildPipeRead, HANDLE hReadEvent, IO_STATUS_BLOCK* piosb) {
+ IO_STATUS_BLOCK cancelIosb = {};
+ if (NT_SUCCESS(NtCancelIoFileEx(hChildPipeRead, piosb, &cancelIosb))) {
+ if (hReadEvent) {
+ NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = -100000 }); // wait for cancellation to complete
+ }
+ }
+ NtTerminateProcess(hProcess, 1);
+}
+
+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;
+ FF_AUTO_CLOSE_FD HANDLE hReadEvent = NULL;
+ handle->pid = INVALID_HANDLE_VALUE;
+ handle->pipeRead = INVALID_HANDLE_VALUE;
+
+ if (timeout >= 0 && !NT_SUCCESS(NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE))) {
+ return "NtCreateEvent() failed";
+ }
+
+ char str[FF_PIPE_BUFSIZ];
+ uint32_t nRead = 0;
+ IO_STATUS_BLOCK iosb = {};
+ do {
+ NTSTATUS status = NtReadFile(
+ hChildPipeRead,
+ hReadEvent,
+ NULL,
+ NULL,
+ &iosb,
+ str,
+ (ULONG) sizeof(str),
+ NULL,
+ NULL);
+ if (status == STATUS_PENDING) {
+ switch (NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 })) {
+ 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";
+ }
+ }
+
+ if (status == STATUS_PIPE_BROKEN || status == STATUS_END_OF_FILE) {
+ goto exit;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ terminateChildProcess(hProcess, hChildPipeRead, NULL, &iosb);
+ return "NtReadFile(hChildPipeRead) failed";
+ }
+
+ nRead = (uint32_t) iosb.Information;
+ ffStrbufAppendNS(buffer, nRead, str);
+ } while (nRead > 0);
+
+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";
+ }
+ } else {
+ return "NtQueryInformationProcess(ProcessBasicInformation) failed";
+ }
+}
+
+ return NULL;
+}
+
+bool ffProcessGetInfoWindows(uint32_t pid, uint32_t* ppid, FFstrbuf* pname, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath, bool* gui) {
+ FF_AUTO_CLOSE_FD HANDLE hProcess = NtCurrentProcess();
+ if (pid != 0) {
+ if (!NT_SUCCESS(NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &(OBJECT_ATTRIBUTES) {
+ .Length = sizeof(OBJECT_ATTRIBUTES),
+ },
+ &(CLIENT_ID) { .UniqueProcess = (HANDLE) (uintptr_t) pid }))) {
+ return false;
+ }
+ }
+
+ if (ppid) {
+ PROCESS_BASIC_INFORMATION info = {};
+ ULONG size;
+ if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size))) {
+ assert(size == sizeof(info));
+ *ppid = (uint32_t) info.InheritedFromUniqueProcessId;
+ } else {
+ return false;
+ }
+ }
+
+ if (exe) {
+ // TODO: It's possible to query the command line with `NtQueryInformationProcess(60/*ProcessCommandLineInformation*/)` since Windows 8.1
+
+ alignas(UNICODE_STRING) uint8_t buffer[4096];
+ ULONG size;
+ 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);
+
+ if (exePath) {
+ ffStrbufSet(exePath, exe);
+ }
+
+ if (pname && exeName) {
+ *exeName = exe->chars + ffStrbufLastIndexC(exe, '\\') + 1;
+ ffStrbufSetS(pname, *exeName);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ if (gui) {
+ SECTION_IMAGE_INFORMATION info = {};
+ ULONG size;
+ if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageInformation, &info, sizeof(info), &size))) {
+ assert(size == sizeof(info));
+ *gui = info.SubSystemType == IMAGE_SUBSYSTEM_WINDOWS_GUI;
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+}