2022-10-08 16:04:55 +08:00
|
|
|
#include "fastfetch.h"
|
2026-01-06 09:48:38 +08:00
|
|
|
#include "common/processing.h"
|
|
|
|
|
#include "common/io.h"
|
2026-02-09 16:12:24 +08:00
|
|
|
#include "common/windows/unicode.h"
|
2026-02-13 16:18:24 +08:00
|
|
|
#include "common/windows/nt.h"
|
2022-10-08 16:04:55 +08:00
|
|
|
|
2026-02-09 16:12:24 +08:00
|
|
|
#include <stdalign.h>
|
2025-10-14 09:10:24 +08:00
|
|
|
#include <windows.h>
|
2024-05-28 22:34:41 +08:00
|
|
|
#include <ntstatus.h>
|
2022-10-08 16:04:55 +08:00
|
|
|
|
2023-12-04 15:10:58 +08:00
|
|
|
enum { FF_PIPE_BUFSIZ = 8192 };
|
2023-07-11 16:00:58 +08:00
|
|
|
|
2024-09-26 14:41:06 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-10 10:33:14 +08:00
|
|
|
const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle)
|
2022-10-08 16:04:55 +08:00
|
|
|
{
|
2025-11-10 10:33:14 +08:00
|
|
|
const int32_t timeout = instance.config.general.processingTimeout;
|
2022-10-08 16:04:55 +08:00
|
|
|
|
2024-10-18 14:08:55 +08:00
|
|
|
wchar_t pipeName[32];
|
2025-11-10 10:33:14 +08:00
|
|
|
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);
|
2024-10-18 14:08:55 +08:00
|
|
|
|
2023-07-11 16:00:58 +08:00
|
|
|
FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = CreateNamedPipeW(
|
2024-10-18 14:08:55 +08:00
|
|
|
pipeName,
|
2023-07-11 16:00:58 +08:00
|
|
|
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)
|
2024-10-18 14:08:55 +08:00
|
|
|
return "CreateNamedPipeW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
|
2022-10-08 16:04:55 +08:00
|
|
|
|
2023-07-11 16:00:58 +08:00
|
|
|
HANDLE hChildPipeWrite = CreateFileW(
|
2024-10-18 14:08:55 +08:00
|
|
|
pipeName,
|
2023-07-11 16:00:58 +08:00
|
|
|
GENERIC_WRITE,
|
|
|
|
|
0,
|
|
|
|
|
&(SECURITY_ATTRIBUTES){
|
|
|
|
|
.nLength = sizeof(SECURITY_ATTRIBUTES),
|
|
|
|
|
.lpSecurityDescriptor = NULL,
|
|
|
|
|
.bInheritHandle = TRUE,
|
|
|
|
|
},
|
|
|
|
|
OPEN_EXISTING,
|
|
|
|
|
0,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
if (hChildPipeWrite == INVALID_HANDLE_VALUE)
|
2024-10-18 14:08:55 +08:00
|
|
|
return "CreateFileW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
|
2022-10-08 16:04:55 +08:00
|
|
|
|
2025-07-18 10:39:11 +08:00
|
|
|
PROCESS_INFORMATION piProcInfo = {};
|
2025-11-10 10:33:14 +08:00
|
|
|
STARTUPINFOA siStartInfo = {
|
|
|
|
|
.cb = sizeof(siStartInfo),
|
|
|
|
|
.dwFlags = STARTF_USESTDHANDLES,
|
|
|
|
|
};
|
|
|
|
|
if (useStdErr)
|
2022-11-13 00:04:34 +08:00
|
|
|
{
|
2025-11-10 10:33:14 +08:00
|
|
|
siStartInfo.hStdOutput = ffGetNullFD();
|
|
|
|
|
siStartInfo.hStdError = hChildPipeWrite;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
siStartInfo.hStdOutput = hChildPipeWrite;
|
|
|
|
|
siStartInfo.hStdError = ffGetNullFD();
|
2022-11-13 00:04:34 +08:00
|
|
|
}
|
2022-10-13 20:06:54 +08:00
|
|
|
|
2025-11-10 10:33:14 +08:00
|
|
|
FF_STRBUF_AUTO_DESTROY cmdline = ffStrbufCreate();
|
|
|
|
|
argvToCmdline(argv, &cmdline);
|
|
|
|
|
|
|
|
|
|
BOOL success = CreateProcessA(
|
|
|
|
|
NULL, // application name
|
|
|
|
|
cmdline.chars, // 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
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-13 10:58:38 +08:00
|
|
|
NtClose(hChildPipeWrite);
|
2022-10-08 16:04:55 +08:00
|
|
|
if(!success)
|
2025-11-10 10:33:14 +08:00
|
|
|
{
|
|
|
|
|
if (GetLastError() == ERROR_FILE_NOT_FOUND)
|
|
|
|
|
return "command not found";
|
2022-10-08 16:04:55 +08:00
|
|
|
return "CreateProcessA() failed";
|
2025-11-10 10:33:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-13 10:58:38 +08:00
|
|
|
NtClose(piProcInfo.hThread); // we don't need the thread handle
|
2025-11-10 10:33:14 +08:00
|
|
|
outHandle->pid = piProcInfo.hProcess;
|
|
|
|
|
outHandle->pipeRead = hChildPipeRead;
|
|
|
|
|
hChildPipeRead = INVALID_HANDLE_VALUE; // ownership transferred, don't close it
|
2023-07-10 09:36:33 +08:00
|
|
|
|
2025-11-10 10:33:14 +08:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2025-11-10 10:33:14 +08:00
|
|
|
handle->pid = INVALID_HANDLE_VALUE;
|
|
|
|
|
handle->pipeRead = INVALID_HANDLE_VALUE;
|
2023-07-16 18:57:33 +08:00
|
|
|
|
2026-03-09 20:24:19 +08:00
|
|
|
if (timeout >= 0 && !NT_SUCCESS(NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE)))
|
|
|
|
|
return "NtCreateEvent() failed";
|
|
|
|
|
|
2023-07-11 16:00:58 +08:00
|
|
|
char str[FF_PIPE_BUFSIZ];
|
2026-03-09 20:24:19 +08:00
|
|
|
uint32_t nRead = 0;
|
|
|
|
|
IO_STATUS_BLOCK iosb = {};
|
2023-07-11 19:45:16 +08:00
|
|
|
do
|
2023-07-10 09:36:33 +08:00
|
|
|
{
|
2026-03-09 20:24:19 +08:00
|
|
|
NTSTATUS status = NtReadFile(
|
|
|
|
|
hChildPipeRead,
|
|
|
|
|
hReadEvent,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
&iosb,
|
|
|
|
|
str,
|
|
|
|
|
(ULONG) sizeof(str),
|
|
|
|
|
NULL,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
if (status == STATUS_PENDING)
|
2023-07-11 16:00:58 +08:00
|
|
|
{
|
2026-03-09 20:24:19 +08:00
|
|
|
switch (NtWaitForSingleObject(hReadEvent, TRUE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 }))
|
2023-07-11 16:00:58 +08:00
|
|
|
{
|
2026-03-09 20:24:19 +08:00
|
|
|
case STATUS_WAIT_0:
|
|
|
|
|
status = iosb.Status;
|
2023-07-11 19:45:16 +08:00
|
|
|
break;
|
|
|
|
|
|
2026-03-09 20:24:19 +08:00
|
|
|
case STATUS_TIMEOUT:
|
|
|
|
|
CancelIo(hChildPipeRead);
|
|
|
|
|
TerminateProcess(hProcess, 1);
|
|
|
|
|
return "NtReadFile(hChildPipeRead) timed out";
|
2023-07-11 19:45:16 +08:00
|
|
|
|
|
|
|
|
default:
|
2023-07-11 16:00:58 +08:00
|
|
|
CancelIo(hChildPipeRead);
|
2023-07-16 18:57:33 +08:00
|
|
|
TerminateProcess(hProcess, 1);
|
2026-03-14 23:08:25 +08:00
|
|
|
return "NtWaitForSingleObject(hReadEvent) failed";
|
2023-07-11 16:00:58 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 20:24:19 +08:00
|
|
|
|
|
|
|
|
if (status == STATUS_PIPE_BROKEN || status == STATUS_END_OF_FILE)
|
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(status))
|
|
|
|
|
{
|
|
|
|
|
CancelIo(hChildPipeRead);
|
|
|
|
|
TerminateProcess(hProcess, 1);
|
|
|
|
|
return "NtReadFile(hChildPipeRead) failed";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nRead = (uint32_t) iosb.Information;
|
2023-07-11 19:45:16 +08:00
|
|
|
ffStrbufAppendNS(buffer, nRead, str);
|
|
|
|
|
} while (nRead > 0);
|
2023-07-10 09:36:33 +08:00
|
|
|
|
2025-11-10 10:33:14 +08:00
|
|
|
exit:
|
|
|
|
|
{
|
2026-02-09 16:12:24 +08:00
|
|
|
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";
|
2025-11-10 10:33:14 +08:00
|
|
|
}
|
|
|
|
|
|
2022-10-08 16:04:55 +08:00
|
|
|
return NULL;
|
|
|
|
|
}
|
2024-05-28 22:34:41 +08:00
|
|
|
|
|
|
|
|
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 = pid == 0
|
2026-02-13 16:18:24 +08:00
|
|
|
? NtCurrentProcess()
|
2024-05-28 22:34:41 +08:00
|
|
|
: OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
|
|
|
|
|
2025-01-08 20:37:35 +08:00
|
|
|
if (hProcess == NULL)
|
2025-01-06 16:39:44 +08:00
|
|
|
return false;
|
|
|
|
|
|
2024-05-28 22:34:41 +08:00
|
|
|
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;
|
|
|
|
|
}
|
2026-02-28 10:06:13 +08:00
|
|
|
|
2024-05-28 22:34:41 +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];
|
2026-02-09 16:12:24 +08:00
|
|
|
ULONG size;
|
|
|
|
|
if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageFileNameWin32, &buffer, sizeof(buffer), &size)))
|
2024-05-28 22:34:41 +08:00
|
|
|
{
|
2026-02-28 10:06:13 +08:00
|
|
|
UNICODE_STRING* imagePath = (UNICODE_STRING*)buffer;
|
|
|
|
|
ffStrbufSetNWS(exe, imagePath->Length / sizeof(wchar_t), imagePath->Buffer);
|
2026-02-09 16:12:24 +08:00
|
|
|
|
2024-05-28 22:34:41 +08:00
|
|
|
if (exePath) ffStrbufSet(exePath, exe);
|
2026-02-28 10:06:13 +08:00
|
|
|
|
|
|
|
|
if (pname && exeName)
|
|
|
|
|
{
|
|
|
|
|
*exeName = exe->chars + ffStrbufLastIndexC(exe, '\\') + 1;
|
|
|
|
|
ffStrbufSetS(pname, *exeName);
|
|
|
|
|
}
|
2024-05-28 22:34:41 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-02-28 10:06:13 +08:00
|
|
|
|
|
|
|
|
if (gui)
|
2024-05-28 22:34:41 +08:00
|
|
|
{
|
2026-02-28 10:06:13 +08:00
|
|
|
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;
|
2024-05-28 22:34:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|