146 lines
4.5 KiB
C
146 lines
4.5 KiB
C
#ifndef __NOB_POPEN_H__
|
||
#define __NOB_POPEN_H__
|
||
|
||
#include "nob.h"
|
||
|
||
typedef struct {
|
||
Nob_String_Builder out; // stdout 内容
|
||
int exit_code; // 实际退出码
|
||
bool ok; // 是否成功启动
|
||
} Nob_Cmd_Result;
|
||
|
||
typedef struct {
|
||
const char **items;
|
||
int capacity;
|
||
int count;
|
||
} Nob_Files;
|
||
|
||
// 运行命令,捕获 stdout(Windows 下会合并 stderr)和真实退出码。
|
||
// 返回 true 仅表示命令成功启动并完成,不代表 exit_code == 0。
|
||
bool nob_cmd_capture_popen(const char *tname, Nob_Cmd_Result *res) {
|
||
memset(res, 0, sizeof(*res));
|
||
|
||
#ifdef _WIN32
|
||
// ---------- Windows: CreateProcessA + 匿名管道 ----------
|
||
SECURITY_ATTRIBUTES sa = {0};
|
||
sa.nLength = sizeof(sa);
|
||
sa.bInheritHandle = TRUE;
|
||
sa.lpSecurityDescriptor = NULL;
|
||
|
||
HANDLE hRead = NULL;
|
||
HANDLE hWrite = NULL;
|
||
if (!CreatePipe(&hRead, &hWrite, &sa, 0)) {
|
||
nob_log(NOB_ERROR, "CreatePipe failed: %s", nob_win32_error_message(GetLastError()));
|
||
return false;
|
||
}
|
||
|
||
// 读端不能被继承,否则父进程 ReadFile 永远等不到 EOF
|
||
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0)) {
|
||
nob_log(NOB_ERROR, "SetHandleInformation failed: %s",
|
||
nob_win32_error_message(GetLastError()));
|
||
CloseHandle(hRead);
|
||
CloseHandle(hWrite);
|
||
return false;
|
||
}
|
||
|
||
STARTUPINFOA si = {0};
|
||
si.cb = sizeof(si);
|
||
si.dwFlags = STARTF_USESTDHANDLES;
|
||
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||
si.hStdOutput = hWrite;
|
||
si.hStdError = hWrite; // 把 stderr 也并入 out,需要分离的话另开一条管道
|
||
|
||
PROCESS_INFORMATION pi = {0};
|
||
|
||
// CreateProcessA 会就地修改 lpCommandLine,必须传可写缓冲
|
||
char *cmdline = NULL;
|
||
{
|
||
size_t n = strlen(tname) + 1;
|
||
cmdline = (char *)malloc(n);
|
||
if (!cmdline) {
|
||
CloseHandle(hRead);
|
||
CloseHandle(hWrite);
|
||
nob_log(NOB_ERROR, "malloc failed");
|
||
return false;
|
||
}
|
||
memcpy(cmdline, tname, n);
|
||
}
|
||
|
||
BOOL ok = CreateProcessA(NULL, // lpApplicationName: 让系统从 lpCommandLine 里解析
|
||
cmdline, // lpCommandLine
|
||
NULL, // lpProcessAttributes
|
||
NULL, // lpThreadAttributes
|
||
TRUE, // bInheritHandles: 必须为 TRUE,才能继承管道写端
|
||
CREATE_NO_WINDOW, // 不弹黑窗;想弹就传 0
|
||
NULL, // lpEnvironment
|
||
NULL, // lpCurrentDirectory
|
||
&si, &pi);
|
||
|
||
free(cmdline);
|
||
|
||
// 无论成功与否,父进程都不再需要写端(子进程已持有自己的副本)
|
||
CloseHandle(hWrite);
|
||
|
||
if (!ok) {
|
||
nob_log(NOB_ERROR, "CreateProcessA failed for \"%s\": %s", tname,
|
||
nob_win32_error_message(GetLastError()));
|
||
CloseHandle(hRead);
|
||
return false;
|
||
}
|
||
|
||
// ---------- 读取 stdout ----------
|
||
{
|
||
char buffer[4096];
|
||
DWORD n = 0;
|
||
while (ReadFile(hRead, buffer, sizeof(buffer), &n, NULL) && n > 0) {
|
||
nob_sb_append_buf(&res->out, buffer, (size_t)n);
|
||
}
|
||
}
|
||
CloseHandle(hRead);
|
||
|
||
// ---------- 等待并取真实退出码 ----------
|
||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||
DWORD code = 0;
|
||
if (!GetExitCodeProcess(pi.hProcess, &code)) {
|
||
nob_log(NOB_ERROR, "GetExitCodeProcess failed: %s",
|
||
nob_win32_error_message(GetLastError()));
|
||
code = (DWORD)-1;
|
||
}
|
||
res->exit_code = (int)code;
|
||
|
||
CloseHandle(pi.hProcess);
|
||
CloseHandle(pi.hThread);
|
||
|
||
#else
|
||
// ---------- POSIX: popen ----------
|
||
FILE *fp = popen(tname, "r");
|
||
if (!fp) {
|
||
nob_log(NOB_ERROR, "popen failed: %s", strerror(errno));
|
||
return false;
|
||
}
|
||
|
||
char buffer[4096];
|
||
while (fgets(buffer, sizeof(buffer), fp)) {
|
||
nob_sb_append_cstr(&res->out, buffer);
|
||
}
|
||
|
||
int status = pclose(fp);
|
||
if (status == -1) {
|
||
nob_log(NOB_ERROR, "pclose failed: %s", strerror(errno));
|
||
return false;
|
||
}
|
||
if (WIFEXITED(status))
|
||
status = WEXITSTATUS(status);
|
||
else if (WIFSIGNALED(status))
|
||
status = 128 + WTERMSIG(status);
|
||
res->exit_code = status;
|
||
#endif
|
||
|
||
res->ok = true;
|
||
return true;
|
||
}
|
||
|
||
#define nob_cmd_result_free(res) nob_sb_free((res).out)
|
||
|
||
#endif /* __NOB_POPEN_H__ */
|