From 0f462e08d03cadb7f7a16bf15795b874ce47ab05 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Tue, 13 May 2025 21:22:08 +0200 Subject: [PATCH 01/13] Work on porting to Windows --- .gitignore | 8 ++ CMakeLists.txt | 29 ++++ build.c | 154 ++++++-------------- build.h | 5 +- graph.h | 1 + log.c | 1 + os-posix.c | 157 +++++++++++++++++++- os-win32.c | 383 +++++++++++++++++++++++++++++++++++++++++++++++++ os.h | 55 ++++++- samu.manifest | 9 ++ util.c | 1 + util.h | 6 + 12 files changed, 693 insertions(+), 116 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 os-win32.c create mode 100644 samu.manifest diff --git a/.gitignore b/.gitignore index d5091fb..2dadc86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ *.o .*.sw* /samu +out/ +.vs/ +.vscode/ +.cache/ +build/ +CMakeUserPresets.json +CMakePresets.json +*.user \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..864ca1f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.16) +project(samurai C) + +set(SOURCE + build.c + deps.c + env.c + graph.c + htab.c + log.c + parse.c + samu.c + scan.c + tool.c + tree.c + util.c +) + +if (WIN32) + list(APPEND SOURCE os-win32.c) +elseif(UNIX) + list(APPEND SOURCE os-posix.c) +endif() + +add_executable(samu ${SOURCE}) + +if (UNIX) + target_link_libraries(samu PRIVATE rt) +endif() diff --git a/build.c b/build.c index 53dbc60..efae07c 100644 --- a/build.c +++ b/build.c @@ -1,16 +1,11 @@ -#define _POSIX_C_SOURCE 200809L #include #include #include -#include #include -#include #include #include #include -#include -#include -#include +#include #include "build.h" #include "deps.h" #include "env.h" @@ -23,9 +18,7 @@ struct job { struct string *cmd; struct edge *edge; struct buffer buf; - size_t next; - pid_t pid; - int fd; + size_t next; bool failed; }; @@ -33,7 +26,7 @@ struct buildoptions buildopts = {.maxfail = 1}; static struct edge *work; static size_t nstarted, nfinished, ntotal; static bool consoleused; -static struct timespec starttime; +static struct ostimespec starttime; void buildreset(void) @@ -198,7 +191,7 @@ formatstatus(char *buf, size_t len) const char *fmt; size_t ret = 0; int n; - struct timespec endtime; + struct ostimespec endtime; for (fmt = buildopts.statusfmt; *fmt; ++fmt) { if (*fmt != '%' || *++fmt == '%') { @@ -230,14 +223,14 @@ formatstatus(char *buf, size_t len) n = snprintf(buf, len, "%3zu%%", 100 * nfinished / ntotal); break; case 'o': - if (clock_gettime(CLOCK_MONOTONIC, &endtime) != 0) { + if (osclock_gettime_monotonic(&endtime) != 0) { warn("clock_gettime:"); break; } n = snprintf(buf, len, "%.1f", nfinished / ((endtime.tv_sec - starttime.tv_sec) + 0.000000001 * (endtime.tv_nsec - starttime.tv_nsec))); break; case 'e': - if (clock_gettime(CLOCK_MONOTONIC, &endtime) != 0) { + if (osclock_gettime_monotonic(&endtime) != 0) { warn("clock_gettime:"); break; } @@ -275,15 +268,11 @@ printstatus(struct edge *e, struct string *cmd) } static int -jobstart(struct job *j, struct edge *e) +jobstart(struct osjob* oj, struct job *j, struct edge *e) { - extern char **environ; size_t i; struct node *n; struct string *rspfile, *content; - int fd[2]; - posix_spawn_file_actions_t actions; - char *argv[] = {"/bin/sh", "-c", NULL, NULL}; ++nstarted; for (i = 0; i < e->nout; ++i) { @@ -299,67 +288,26 @@ jobstart(struct job *j, struct edge *e) if (writefile(rspfile->s, content) < 0) goto err0; } - - if (pipe(fd) < 0) { - warn("pipe:"); - goto err1; - } j->edge = e; j->cmd = edgevar(e, "command", true); - j->fd = fd[0]; - argv[2] = j->cmd->s; if (!consoleused) printstatus(e, j->cmd); - if ((errno = posix_spawn_file_actions_init(&actions))) { - warn("posix_spawn_file_actions_init:"); - goto err2; - } - if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0]))) { - warn("posix_spawn_file_actions_addclose:"); - goto err3; - } - if (e->pool != &consolepool) { - if ((errno = posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDONLY, 0))) { - warn("posix_spawn_file_actions_addopen:"); - goto err3; - } - if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1))) { - warn("posix_spawn_file_actions_adddup2:"); - goto err3; - } - if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 2))) { - warn("posix_spawn_file_actions_adddup2:"); - goto err3; - } - if ((errno = posix_spawn_file_actions_addclose(&actions, fd[1]))) { - warn("posix_spawn_file_actions_addclose:"); - goto err3; - } - } - if ((errno = posix_spawn(&j->pid, argv[0], &actions, NULL, argv, environ))) { - warn("posix_spawn %s:", j->cmd->s); - goto err3; + if (osjob_create(oj, j->cmd, e->pool == &consolepool) < 0) { + goto err1; } - posix_spawn_file_actions_destroy(&actions); - close(fd[1]); + j->failed = false; if (e->pool == &consolepool) consoleused = true; - - return j->fd; - -err3: - posix_spawn_file_actions_destroy(&actions); -err2: - close(fd[0]); - close(fd[1]); + oj->valid = true; + return 0; err1: if (rspfile && !buildopts.keeprsp) remove(rspfile->s); err0: - return -1; + return -1; } static void @@ -438,30 +386,15 @@ edgedone(struct edge *e) } static void -jobdone(struct job *j) +jobdone(struct job *j, struct osjob* oj) { - int status; struct edge *e, *new; struct pool *p; ++nfinished; - if (waitpid(j->pid, &status, 0) < 0) { - warn("waitpid %d:", j->pid); - j->failed = true; - } else if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - warn("job failed with status %d: %s", WEXITSTATUS(status), j->cmd->s); - j->failed = true; - } - } else if (WIFSIGNALED(status)) { - warn("job terminated due to signal %d: %s", WTERMSIG(status), j->cmd->s); - j->failed = true; - } else { - /* cannot happen according to POSIX */ - warn("job status unknown: %s", j->cmd->s); + if (osjob_done(oj, j->cmd) < 0) { j->failed = true; } - close(j->fd); if (j->buf.len && (!consoleused || j->failed)) fwrite(j->buf.data, 1, j->buf.len, stdout); j->buf.len = 0; @@ -487,7 +420,7 @@ jobdone(struct job *j) /* returns whether a job still has work to do. if not, sets j->failed */ static bool -jobwork(struct job *j) +jobwork(struct job *j, struct osjob* ojob) { char *newdata; size_t newcap; @@ -503,20 +436,20 @@ jobwork(struct job *j) j->buf.cap = newcap; j->buf.data = newdata; } - n = read(j->fd, j->buf.data + j->buf.len, j->buf.cap - j->buf.len); - if (n > 0) { - j->buf.len += n; + ssize_t result = osjob_work(ojob, j->buf.data + j->buf.len, j->buf.cap - j->buf.len); + if (result > 0) { + j->buf.len += result; return true; - } - if (n == 0) + } else if (result == 0) { goto done; - warn("read:"); - + } else { + warn("read:"); kill: - kill(j->pid, SIGTERM); - j->failed = true; + osjob_close(ojob); + j->failed = true; + } done: - jobdone(j); + jobdone(j, ojob); return false; } @@ -543,7 +476,8 @@ void build(void) { struct job *jobs = NULL; - struct pollfd *fds = NULL; + struct osjob* osjobs = NULL; + struct osjob_ctx osctx = {0}; size_t i, next = 0, jobslen = 0, maxjobs = buildopts.maxjobs, numjobs = 0, numfail = 0; struct edge *e; @@ -552,7 +486,7 @@ build(void) return; } - clock_gettime(CLOCK_MONOTONIC, &starttime); + osclock_gettime_monotonic(&starttime); formatstatus(NULL, 0); nstarted = 0; @@ -579,18 +513,14 @@ build(void) if (jobslen > buildopts.maxjobs) jobslen = buildopts.maxjobs; jobs = xreallocarray(jobs, jobslen, sizeof(jobs[0])); - fds = xreallocarray(fds, jobslen, sizeof(fds[0])); - for (i = next; i < jobslen; ++i) { - jobs[i].buf.data = NULL; - jobs[i].buf.len = 0; - jobs[i].buf.cap = 0; - jobs[i].next = i + 1; - fds[i].fd = -1; - fds[i].events = POLLIN; + osjobs = xreallocarray(osjobs, jobslen, sizeof(osjobs[0])); + for (i = next; i < jobslen; ++i) { + jobs[i] = (struct job){0}; + jobs[i].next = i + 1; + osjobs[i] = (struct osjob){0}; } - } - fds[next].fd = jobstart(&jobs[next], e); - if (fds[next].fd < 0) { + } + if (jobstart(&osjobs[next], &jobs[next], e) < 0) { warn("job failed to start"); ++numfail; } else { @@ -600,23 +530,23 @@ build(void) } if (numjobs == 0) break; - if (poll(fds, jobslen, 5000) < 0) - fatal("poll:"); + if (osjob_wait(&osctx, osjobs, jobslen, 5000) < 0) + fatal("osjob_wait:"); for (i = 0; i < jobslen; ++i) { - if (!fds[i].revents || jobwork(&jobs[i])) + if (!osjobs[i].valid || !osjobs[i].has_data || jobwork(&jobs[i], &osjobs[i])) continue; --numjobs; jobs[i].next = next; - fds[i].fd = -1; + osjobs[i].valid = false; next = i; if (jobs[i].failed) ++numfail; } } - for (i = 0; i < jobslen; ++i) + for (i = 0; i < jobslen; ++i) { free(jobs[i].buf.data); + } free(jobs); - free(fds); if (numfail > 0) { if (numfail < buildopts.maxfail) fatal("cannot make progress due to previous errors"); diff --git a/build.h b/build.h index 47d6ea3..05e951a 100644 --- a/build.h +++ b/build.h @@ -1,8 +1,11 @@ +#include +#include + struct node; struct buildoptions { size_t maxjobs, maxfail; - _Bool verbose, explain, keepdepfile, keeprsp, dryrun; + bool verbose, explain, keepdepfile, keeprsp, dryrun; const char *statusfmt; double maxload; }; diff --git a/graph.h b/graph.h index 5ea808a..85a0d43 100644 --- a/graph.h +++ b/graph.h @@ -1,3 +1,4 @@ +#include #include /* for uint64_t */ /* set in the tv_nsec field of a node's mtime */ diff --git a/log.c b/log.c index 47144c2..b81d97f 100644 --- a/log.c +++ b/log.c @@ -6,6 +6,7 @@ #include "graph.h" #include "log.h" #include "util.h" +#include "os.h" static FILE *logfile; static const char *logname = ".ninja_log"; diff --git a/os-posix.c b/os-posix.c index 4c2a7f0..917c4ae 100644 --- a/os-posix.c +++ b/os-posix.c @@ -1,13 +1,23 @@ #define _POSIX_C_SOURCE 200809L + #include #include #include -#include -#include +#include +#include + #include "graph.h" #include "os.h" #include "util.h" +#include +#include +#include +#include +#include +#include +#include + void osgetcwd(char *buf, size_t len) { @@ -83,3 +93,146 @@ since it has not been updated to support POSIX.1-2008: #endif } } + +int +osclock_gettime_monotonic(struct ostimespec* time) +{ + struct timespec t; + if (clock_gettime(CLOCK_MONOTONIC, &t) < 0) { + return -1; + } + time->tv_sec = t.tv_sec; + time->tv_nsec = t.tv_nsec; + return 0; +} + +pid_t +oswaitpid(pid_t pid, int *status, int options) +{ + return waitpid(pid, status, options); +} + +int osjob_close(struct osjob* ojob) +{ + + close(ojob->fd); + kill(ojob->pid, SIGTERM); + memset(ojob, 0, sizeof(*ojob)); + return 0; +} + +int +osjob_done(struct osjob* ojob, struct string* cmd) +{ + int status; + if (waitpid(ojob->pid, &status, 0) < 0) { + warn("waitpid %d:", ojob->pid); + goto err; + } else if (WIFSIGNALED(status)) { + warn("job terminated due to signal %d: %s", WTERMSIG(status), cmd->s); + goto err; + } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + warn("job failed with status %d: %s", WEXITSTATUS(status), cmd->s); + goto err; + } + return osjob_close(ojob); +err: + osjob_close(ojob); + return -1; +} + +int +osjob_wait(struct osjob_ctx *ctx, struct osjob* ojobs, size_t jobslen, int timeout) +{ + if (ctx->pfds_len < jobslen) { + ctx->pfds = xreallocarray(ctx->pfds, jobslen, sizeof(ctx->pfds[0])); + ctx->pfds_len = jobslen; + } + nfds_t count = 0; + for (size_t i = 0; i < jobslen; ++i) { + if (ojobs[i].valid) { + ctx->pfds[i].events = POLLIN; + ctx->pfds[i].fd = ojobs[i].fd; + ctx->pfds[i].revents = 0; + count++; + } + } + if (poll(ctx->pfds, count, timeout) < 0) { + fatal("poll:"); + } + struct osjob* curr = ojobs; + for(nfds_t i = 0; i < count; ++i) { + while(!curr->valid) + curr++; + curr->has_data = ctx->pfds[i].revents; + curr++; + } + return 0; +} + +ssize_t +osjob_work(struct osjob *ojob, void *buf, size_t buflen) +{ + assert(ojob->has_data); + return read(ojob->fd, buf, buflen); +} + +int +osjob_create(struct osjob *created, struct string *cmd, bool console) +{ + extern char **environ; + int fd[2]; + posix_spawn_file_actions_t actions; + char *argv[] = {"/bin/sh", "-c", cmd->s, NULL}; + + if (pipe(fd) < 0) { + warn("pipe:"); + return -1; + } + + created->has_data = false; + created->fd = fd[0]; + + if ((errno = posix_spawn_file_actions_init(&actions))) { + warn("posix_spawn_file_actions_init:"); + goto err2; + } + if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0]))) { + warn("posix_spawn_file_actions_addclose:"); + goto err3; + } + if (!console) { + if ((errno = posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDONLY, 0))) { + warn("posix_spawn_file_actions_addopen:"); + goto err3; + } + if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1))) { + warn("posix_spawn_file_actions_adddup2:"); + goto err3; + } + if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 2))) { + warn("posix_spawn_file_actions_adddup2:"); + goto err3; + } + if ((errno = posix_spawn_file_actions_addclose(&actions, fd[1]))) { + warn("posix_spawn_file_actions_addclose:"); + goto err3; + } + } + if ((errno = posix_spawn(&created->pid, argv[0], &actions, NULL, argv, environ))) { + warn("posix_spawn %s:", cmd->s); + goto err3; + } + posix_spawn_file_actions_destroy(&actions); + close(fd[1]); + + return 0; + +err3: + posix_spawn_file_actions_destroy(&actions); +err2: + close(fd[0]); + close(fd[1]); + return -1; +} + diff --git a/os-win32.c b/os-win32.c new file mode 100644 index 0000000..45cc16e --- /dev/null +++ b/os-win32.c @@ -0,0 +1,383 @@ +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include + +#include "os.h" +#include "graph.h" +#include "util.h" + +void +osgetcwd(char *buf, size_t len) +{ + if (!GetCurrentDirectoryA(len, buf)) { + fatal("GetCurrentDirectory:"); + } +} + +void +oschdir(const char *dir) +{ + if (!SetCurrentDirectoryA(dir)) { + fatal("SetCurrentDirectory %s:", dir); + } +} + +int +osmkdirs(struct string *_path, bool parent) +{ + char *path = _path->s; + if (!parent) { + if (!CreateDirectoryA(path, NULL)) { + DWORD err = GetLastError(); + if (err != ERROR_ALREADY_EXISTS) { + warn("mkdirs %s:", path); + return -1; + } + } + return 0; + } + char folder[MAX_PATH]; + char *end; + ZeroMemory(folder, sizeof(folder)); + + end = strchr(path, L'\\'); + + while (end != NULL) { + strncpy(folder, path, end - path + 1); + if (!CreateDirectoryA(folder, NULL)) { + DWORD err = GetLastError(); + if (err != ERROR_ALREADY_EXISTS) { + warn("mkdirs %s:", folder); + return -1; + } + } + end = strchr(++end, L'\\'); + } + return 0; +} + +// taken fron ninja-build +static int64_t TimeStampFromFileTime(const FILETIME* filetime) +{ + // FILETIME is in 100-nanosecond increments since the Windows epoch. + // We don't much care about epoch correctness but we do want the + // resulting value to fit in a 64-bit integer. + int64_t mtime = ((int64_t)filetime->dwHighDateTime << 32) | + ((int64_t)filetime->dwLowDateTime); + // 1600 epoch -> 2000 epoch (subtract 400 years). + return (int64_t)mtime - 12622770400LL * (1000000000LL / 100LL); +} + +// taken fron ninja-build +int64_t +osmtime(const char *name) +{ + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!GetFileAttributesExA(name, GetFileExInfoStandard, &attrs)) { + DWORD win_err = GetLastError(); + if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) { + // ok + } else { + warn("GetFileTime:"); + } + return MTIME_MISSING; + } + return TimeStampFromFileTime(&attrs.ftLastWriteTime); +} + +int +osclock_gettime_monotonic(struct ostimespec* ts) +{ + static LARGE_INTEGER frequency = {0}; + static BOOL initialized = FALSE; + static BOOL qpf_available = FALSE; + if (!initialized) { + qpf_available = QueryPerformanceFrequency(&frequency); + initialized = TRUE; + } + if (!qpf_available) { + SetLastError(ERROR_NOT_CAPABLE); + warn("QueryPerformanceFrequency:"); + return -1; + } + LARGE_INTEGER counter; + if (!QueryPerformanceCounter(&counter)) { + warn("QueryPerformanceCounter:"); + return -1; + } + uint64_t ticks = counter.QuadPart; + uint64_t ticks_per_sec = frequency.QuadPart; + + ts->tv_sec = ticks / ticks_per_sec; + uint64_t remaining_ticks = ticks % ticks_per_sec; + ts->tv_nsec = (remaining_ticks * 1000000000ULL) / ticks_per_sec; + + return 0; +} + +pid_t +oswaitpid(pid_t hProcess, int *status, int options) +{ + + DWORD dwExitCode = 0; + + if (hProcess == (HANDLE)-1) { + return (pid_t)-1; + } + + // Check if the process is still running + if (!GetExitCodeProcess(hProcess, &dwExitCode)) { + warn("GetExitCodeProcess:"); + return (pid_t)-1; + } + if (dwExitCode == STILL_ACTIVE) { + if (options & WNOHANG) { + // Process is still running and we don't want to wait + return 0; + } else { + // Wait for the process to exit + WaitForSingleObject(hProcess, INFINITE); + GetExitCodeProcess(hProcess, &dwExitCode); + } + } + if (status) { + *status = (int)dwExitCode; + } + CloseHandle(hProcess); + return hProcess; +} + +ssize_t +osread(fd_t fd, void* buf, size_t buflen) +{ + if (buflen == 0) { + // POSIX allows read with size 0 (may return 0 or error) + return 0; + } + + char *buffer = (char *)buf; + ssize_t total_read = 0; + const DWORD max_chunk = 0xFFFFFFFF; // MAXDWORD + + while (buflen > 0) { + DWORD chunk = buflen > max_chunk ? max_chunk : (DWORD)buflen; + DWORD bytes_read = 0; + + if (!ReadFile(fd, buffer, chunk, &bytes_read, NULL)) { + const DWORD err = GetLastError(); + + if (total_read > 0) { + // Return partial read (POSIX allows this) + return total_read; + } + + switch (err) { + case ERROR_BROKEN_PIPE: + return 0; // Treat as EOF for closed pipe + + case ERROR_OPERATION_ABORTED: + errno = EINTR; + break; + + case ERROR_NO_DATA: + case ERROR_IO_PENDING: + errno = EAGAIN; + break; + + case ERROR_INVALID_HANDLE: + case ERROR_ACCESS_DENIED: + errno = EBADF; + break; + + case ERROR_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + break; + + case ERROR_HANDLE_EOF: + return 0; // Explicit EOF indication + + default: + errno = EIO; + break; + } + + warn("ReadFile"); + return -1; + } + + if (bytes_read == 0) { + // Regular EOF condition + break; + } + + total_read += bytes_read; + buffer += bytes_read; + buflen -= bytes_read; + } + + return total_read ? total_read : (ssize_t)0; +} + +void +oskill(pid_t pid, int signal) +{ + if (!TerminateProcess(pid, signal)) { + fatal("TerminateProcess:"); + } +} + +static int +pipe_check_ready(HANDLE pipe) +{ + DWORD avail = 0; + return PeekNamedPipe(pipe, NULL, 0, NULL, &avail, NULL) && avail > 0; +} + +int +ospoll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + while (nfds > MAXIMUM_WAIT_OBJECTS) { + int subcnt = ospoll(fds, MAXIMUM_WAIT_OBJECTS, timeout); //increases timeout + if (subcnt) { + return subcnt; + } + fds += MAXIMUM_WAIT_OBJECTS; + nfds -= MAXIMUM_WAIT_OBJECTS; + } + + int cnt = 0, wait_cnt = 0; + HANDLE h[MAXIMUM_WAIT_OBJECTS]; + + for (int i = 0; i < nfds; i++) { + assert(fds[i].fd); + assert(GetFileType(fds[i].fd) == FILE_TYPE_PIPE); + fds[i].revents = 0; + if (pipe_check_ready(fds[i].fd)) { + fds[i].revents = POLLIN; + cnt++; + } else if (wait_cnt < MAXIMUM_WAIT_OBJECTS) { + h[wait_cnt++] = fds[i].fd; + } + } + + if (cnt) { + return cnt; + } + + if (!wait_cnt) { + return 0; + } + + DWORD res = WaitForMultipleObjects(wait_cnt, h, 0, timeout < 0 ? INFINITE : timeout); + if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + wait_cnt) { + for (int i = 0; i < nfds; i++) { + struct pollfd *pfd = fds + i; + if (pfd->fd == h[res - WAIT_OBJECT_0] && (pfd->revents = pipe_check_ready(pfd->fd) ? POLLIN : 0)) { + cnt++; + } + } + } else if (res == WAIT_FAILED) { + errno = EINVAL; + return -1; + } + return res == WAIT_TIMEOUT ? 0 : cnt; +} + +// taken and modified from ninja-build +int +oscreate_job(struct osjob *created, struct string* cmd, bool console) +{ + char pipe_name[100] = {""}; + snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\samu_pid%lu_sp%p", GetCurrentProcessId(), cmd->s); + + created->output = CreateNamedPipeA(pipe_name, + PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE, + PIPE_UNLIMITED_INSTANCES, + 0, 0, INFINITE, NULL); + + if (created->output == INVALID_HANDLE_VALUE) { + fatal("CreateNamedPipe: %s", pipe_name); + } + + // here CreateIoCompletionPort() can be used to make subprocesses cancellable using centralized ioport + + HANDLE child_pipe; + { + HANDLE output_write_handle = + CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + HANDLE curr = GetCurrentProcess(); + if (!DuplicateHandle(curr, output_write_handle, + curr, &child_pipe, + 0, TRUE, DUPLICATE_SAME_ACCESS)) { + fatal("DuplicateHandle"); + } + CloseHandle(output_write_handle); + } + + HANDLE nul; + { + + SECURITY_ATTRIBUTES security_attributes; + memset(&security_attributes, 0, sizeof(SECURITY_ATTRIBUTES)); + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = TRUE; + + nul = + CreateFileA("NUL", GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + &security_attributes, OPEN_EXISTING, 0, NULL); + } + + + STARTUPINFOA startup_info; + memset(&startup_info, 0, sizeof(startup_info)); + startup_info.cb = sizeof(STARTUPINFO); + if (!console) { + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = nul; + startup_info.hStdOutput = child_pipe; + startup_info.hStdError = child_pipe; + } + + + PROCESS_INFORMATION process_info; + memset(&process_info, 0, sizeof(process_info)); + { + WORD process_flags = 0; + if (!CreateProcessA(NULL, cmd->s, NULL, NULL, + /* inherit handles */ TRUE, process_flags, + NULL, NULL, &startup_info, &process_info)) + { + DWORD error = GetLastError(); + + if (error == ERROR_FILE_NOT_FOUND) { + if (child_pipe) { + CloseHandle(child_pipe); + } + if (created->output) { + CloseHandle(created->output); + created->output = 0; + } + CloseHandle(nul); + return -1; + } else { + fatal("CreateProcess: %s", cmd->s); + } + } + } + if (child_pipe) + CloseHandle(child_pipe); + CloseHandle(nul); + created->pid = process_info.hProcess; + CloseHandle(process_info.hThread); + return 0; +} + +void +osclose_job(struct osjob *ojob) +{ + CloseHandle(ojob->output); +} \ No newline at end of file diff --git a/os.h b/os.h index 91cae05..c9f24e5 100644 --- a/os.h +++ b/os.h @@ -1,9 +1,62 @@ +#include +#include +#include +#include + +struct ostimespec { + int64_t tv_sec; + int64_t tv_nsec; +}; + +#ifdef _WIN32 + typedef void* HANDLE; + typedef HANDLE fd_t; + typedef HANDLE pid_t; + typedef long long ssize_t; + + + #define WNOHANG 1 + + #define WEXITSTATUS(status) status + #define WTERMSIG(status) 0 + #define WSTOPSIG(status) WEXITSTATUS(status) + #define WIFEXITED(status) (WTERMSIG(status) == 0) + #define WIFSIGNALED(status) 0 + + #define setvbuf(stream, buff, mode, len) (void)0 +#else + #include "poll.h" + struct osjob { + bool has_data; + bool valid; + int pid; + int fd; + }; + struct osjob_ctx { + struct pollfd* pfds; + size_t pfds_len; + }; +#endif + +struct buffer; struct string; void osgetcwd(char *, size_t); /* changes the working directory to the given path */ void oschdir(const char *); /* creates all the parent directories of the given path */ -int osmkdirs(struct string *, _Bool); +int osmkdirs(struct string *, bool); /* queries the mtime of a file in nanoseconds since the UNIX epoch */ int64_t osmtime(const char *); +/* get current monotonic time */ +int osclock_gettime_monotonic(struct ostimespec*); + +struct osjob_ctx; + +int osjob_create(struct osjob *created, struct string *cmd, bool console); +/*ojobs is array of osjob*, entries may be NULL (invalid osjob).*/ +int osjob_wait(struct osjob_ctx *ctx, struct osjob *ojobs, size_t jobs_count, int timeout); +/*read out into buffer*/ +ssize_t osjob_work(struct osjob *ojob, void* buf, size_t buflen); +int osjob_close(struct osjob* ojob); +int osjob_done(struct osjob* ojob, struct string* cmd); diff --git a/samu.manifest b/samu.manifest new file mode 100644 index 0000000..be79bf0 --- /dev/null +++ b/samu.manifest @@ -0,0 +1,9 @@ + + + + + UTF-8 + true + + + \ No newline at end of file diff --git a/util.c b/util.c index 8ee3ddd..c42629f 100644 --- a/util.c +++ b/util.c @@ -152,6 +152,7 @@ delevalstr(void *ptr) void canonpath(struct string *path) { + // TODO: canon for win32 char *component[60]; int n; char *s, *d, *end; diff --git a/util.h b/util.h index 5da348c..341ab04 100644 --- a/util.h +++ b/util.h @@ -18,6 +18,12 @@ struct evalstring { #define LEN(a) (sizeof(a) / sizeof((a)[0])) void warn(const char *, ...); + +#ifdef _MSC_VER +__declspec(noreturn) +#elif defined(__GNUC__) +__attribute__((noreturn)) +#endif void fatal(const char *, ...); void *xmalloc(size_t); From 06d75cf11b16a82af3bb1af9b34d007d207f3c03 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Tue, 13 May 2025 20:51:31 +0200 Subject: [PATCH 02/13] Rework API for IOCP --- build.c | 40 +++++---- os-posix.c | 5 ++ os-win32.c | 254 +++++++++++------------------------------------------ os.h | 38 ++++---- 4 files changed, 97 insertions(+), 240 deletions(-) diff --git a/build.c b/build.c index efae07c..218238e 100644 --- a/build.c +++ b/build.c @@ -18,7 +18,7 @@ struct job { struct string *cmd; struct edge *edge; struct buffer buf; - size_t next; + size_t next; bool failed; }; @@ -268,7 +268,7 @@ printstatus(struct edge *e, struct string *cmd) } static int -jobstart(struct osjob* oj, struct job *j, struct edge *e) +jobstart(struct osjob_ctx *osctx, struct osjob *oj, struct job *j, struct edge *e) { size_t i; struct node *n; @@ -294,7 +294,8 @@ jobstart(struct osjob* oj, struct job *j, struct edge *e) if (!consoleused) printstatus(e, j->cmd); - if (osjob_create(oj, j->cmd, e->pool == &consolepool) < 0) { + bool use_console = e->pool == &consolepool; + if (osjob_create(osctx, oj, j->cmd, use_console) < 0) { goto err1; } @@ -386,13 +387,13 @@ edgedone(struct edge *e) } static void -jobdone(struct job *j, struct osjob* oj) +jobdone(struct osjob_ctx *osctx, struct job *j, struct osjob *oj) { struct edge *e, *new; struct pool *p; ++nfinished; - if (osjob_done(oj, j->cmd) < 0) { + if (osjob_done(osctx, oj, j->cmd) < 0) { j->failed = true; } if (j->buf.len && (!consoleused || j->failed)) @@ -420,7 +421,7 @@ jobdone(struct job *j, struct osjob* oj) /* returns whether a job still has work to do. if not, sets j->failed */ static bool -jobwork(struct job *j, struct osjob* ojob) +jobwork(struct osjob_ctx* osctx, struct job *j, struct osjob *ojob) { char *newdata; size_t newcap; @@ -436,7 +437,7 @@ jobwork(struct job *j, struct osjob* ojob) j->buf.cap = newcap; j->buf.data = newdata; } - ssize_t result = osjob_work(ojob, j->buf.data + j->buf.len, j->buf.cap - j->buf.len); + ssize_t result = osjob_work(osctx, ojob, j->buf.data + j->buf.len, j->buf.cap - j->buf.len); if (result > 0) { j->buf.len += result; return true; @@ -445,11 +446,11 @@ jobwork(struct job *j, struct osjob* ojob) } else { warn("read:"); kill: - osjob_close(ojob); + osjob_close(osctx, ojob); j->failed = true; } done: - jobdone(j, ojob); + jobdone(osctx, j, ojob); return false; } @@ -477,7 +478,7 @@ build(void) { struct job *jobs = NULL; struct osjob* osjobs = NULL; - struct osjob_ctx osctx = {0}; + struct osjob_ctx* osctx = osjob_ctx_create(); size_t i, next = 0, jobslen = 0, maxjobs = buildopts.maxjobs, numjobs = 0, numfail = 0; struct edge *e; @@ -514,13 +515,13 @@ build(void) jobslen = buildopts.maxjobs; jobs = xreallocarray(jobs, jobslen, sizeof(jobs[0])); osjobs = xreallocarray(osjobs, jobslen, sizeof(osjobs[0])); - for (i = next; i < jobslen; ++i) { - jobs[i] = (struct job){0}; - jobs[i].next = i + 1; - osjobs[i] = (struct osjob){0}; + for (i = next; i < jobslen; ++i) { + jobs[i] = (struct job){0}; + jobs[i].next = i + 1; + osjobs[i] = (struct osjob){0}; } - } - if (jobstart(&osjobs[next], &jobs[next], e) < 0) { + } + if (jobstart(osctx, &osjobs[next], &jobs[next], e) < 0) { warn("job failed to start"); ++numfail; } else { @@ -530,14 +531,14 @@ build(void) } if (numjobs == 0) break; - if (osjob_wait(&osctx, osjobs, jobslen, 5000) < 0) + if (osjob_wait(osctx, osjobs, jobslen, 5000) < 0) fatal("osjob_wait:"); for (i = 0; i < jobslen; ++i) { - if (!osjobs[i].valid || !osjobs[i].has_data || jobwork(&jobs[i], &osjobs[i])) + if (!osjobs[i].valid || !osjobs[i].has_data || jobwork(osctx, &jobs[i], &osjobs[i])) continue; --numjobs; jobs[i].next = next; - osjobs[i].valid = false; + osjobs[i].valid = false; next = i; if (jobs[i].failed) ++numfail; @@ -547,6 +548,7 @@ build(void) free(jobs[i].buf.data); } free(jobs); + osjob_ctx_close(osctx); if (numfail > 0) { if (numfail < buildopts.maxfail) fatal("cannot make progress due to previous errors"); diff --git a/os-posix.c b/os-posix.c index 917c4ae..4f55479 100644 --- a/os-posix.c +++ b/os-posix.c @@ -18,6 +18,11 @@ #include #include +struct osjob_ctx { + struct pollfd* pfds; + size_t pfds_len; +}; + void osgetcwd(char *buf, size_t len) { diff --git a/os-win32.c b/os-win32.c index 45cc16e..10c34ee 100644 --- a/os-win32.c +++ b/os-win32.c @@ -117,115 +117,20 @@ osclock_gettime_monotonic(struct ostimespec* ts) return 0; } -pid_t -oswaitpid(pid_t hProcess, int *status, int options) -{ - - DWORD dwExitCode = 0; - - if (hProcess == (HANDLE)-1) { - return (pid_t)-1; - } - - // Check if the process is still running - if (!GetExitCodeProcess(hProcess, &dwExitCode)) { - warn("GetExitCodeProcess:"); - return (pid_t)-1; - } - if (dwExitCode == STILL_ACTIVE) { - if (options & WNOHANG) { - // Process is still running and we don't want to wait - return 0; - } else { - // Wait for the process to exit - WaitForSingleObject(hProcess, INFINITE); - GetExitCodeProcess(hProcess, &dwExitCode); - } - } - if (status) { - *status = (int)dwExitCode; - } - CloseHandle(hProcess); - return hProcess; -} +///////////////////////////// JOBS -ssize_t -osread(fd_t fd, void* buf, size_t buflen) +static HANDLE +win_create_nul() { - if (buflen == 0) { - // POSIX allows read with size 0 (may return 0 or error) - return 0; - } - - char *buffer = (char *)buf; - ssize_t total_read = 0; - const DWORD max_chunk = 0xFFFFFFFF; // MAXDWORD - - while (buflen > 0) { - DWORD chunk = buflen > max_chunk ? max_chunk : (DWORD)buflen; - DWORD bytes_read = 0; - - if (!ReadFile(fd, buffer, chunk, &bytes_read, NULL)) { - const DWORD err = GetLastError(); - - if (total_read > 0) { - // Return partial read (POSIX allows this) - return total_read; - } - - switch (err) { - case ERROR_BROKEN_PIPE: - return 0; // Treat as EOF for closed pipe - - case ERROR_OPERATION_ABORTED: - errno = EINTR; - break; - - case ERROR_NO_DATA: - case ERROR_IO_PENDING: - errno = EAGAIN; - break; - - case ERROR_INVALID_HANDLE: - case ERROR_ACCESS_DENIED: - errno = EBADF; - break; - - case ERROR_NOT_ENOUGH_MEMORY: - errno = ENOMEM; - break; - - case ERROR_HANDLE_EOF: - return 0; // Explicit EOF indication - - default: - errno = EIO; - break; - } - - warn("ReadFile"); - return -1; - } - - if (bytes_read == 0) { - // Regular EOF condition - break; - } - - total_read += bytes_read; - buffer += bytes_read; - buflen -= bytes_read; - } - - return total_read ? total_read : (ssize_t)0; -} - -void -oskill(pid_t pid, int signal) -{ - if (!TerminateProcess(pid, signal)) { - fatal("TerminateProcess:"); - } + SECURITY_ATTRIBUTES sa = {sizeof(sa)}; + sa.bInheritHandle = TRUE; + // Must be inheritable so subprocesses can dup to children. + HANDLE nul = CreateFileA("NUL", GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + &sa, OPEN_EXISTING, 0, NULL); + if (nul == INVALID_HANDLE_VALUE) + fatal("couldn't open nul:"); + return nul; } static int @@ -235,70 +140,21 @@ pipe_check_ready(HANDLE pipe) return PeekNamedPipe(pipe, NULL, 0, NULL, &avail, NULL) && avail > 0; } -int -ospoll(struct pollfd *fds, nfds_t nfds, int timeout) -{ - while (nfds > MAXIMUM_WAIT_OBJECTS) { - int subcnt = ospoll(fds, MAXIMUM_WAIT_OBJECTS, timeout); //increases timeout - if (subcnt) { - return subcnt; - } - fds += MAXIMUM_WAIT_OBJECTS; - nfds -= MAXIMUM_WAIT_OBJECTS; - } - - int cnt = 0, wait_cnt = 0; - HANDLE h[MAXIMUM_WAIT_OBJECTS]; - - for (int i = 0; i < nfds; i++) { - assert(fds[i].fd); - assert(GetFileType(fds[i].fd) == FILE_TYPE_PIPE); - fds[i].revents = 0; - if (pipe_check_ready(fds[i].fd)) { - fds[i].revents = POLLIN; - cnt++; - } else if (wait_cnt < MAXIMUM_WAIT_OBJECTS) { - h[wait_cnt++] = fds[i].fd; - } - } - - if (cnt) { - return cnt; - } - - if (!wait_cnt) { - return 0; - } - DWORD res = WaitForMultipleObjects(wait_cnt, h, 0, timeout < 0 ? INFINITE : timeout); - if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + wait_cnt) { - for (int i = 0; i < nfds; i++) { - struct pollfd *pfd = fds + i; - if (pfd->fd == h[res - WAIT_OBJECT_0] && (pfd->revents = pipe_check_ready(pfd->fd) ? POLLIN : 0)) { - cnt++; - } - } - } else if (res == WAIT_FAILED) { - errno = EINVAL; - return -1; - } - return res == WAIT_TIMEOUT ? 0 : cnt; -} - -// taken and modified from ninja-build int -oscreate_job(struct osjob *created, struct string* cmd, bool console) +osjob_create(struct osjob_ctx *ctx, struct osjob *created, struct string *cmd, bool console) { - char pipe_name[100] = {""}; - snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\samu_pid%lu_sp%p", GetCurrentProcessId(), cmd->s); + // taken and modified from ninja-build + char pipe_name[100] = {0}; + snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\samu_pid%lu_sp%p", GetCurrentProcessId(), (void *)created); - created->output = CreateNamedPipeA(pipe_name, - PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE, - PIPE_UNLIMITED_INSTANCES, - 0, 0, INFINITE, NULL); + created->pipe = CreateNamedPipeA(pipe_name, + PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE, + PIPE_UNLIMITED_INSTANCES, + 0, 0, INFINITE, NULL); - if (created->output == INVALID_HANDLE_VALUE) { + if (created->pipe == INVALID_HANDLE_VALUE) { fatal("CreateNamedPipe: %s", pipe_name); } @@ -307,59 +163,42 @@ oscreate_job(struct osjob *created, struct string* cmd, bool console) HANDLE child_pipe; { HANDLE output_write_handle = - CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); HANDLE curr = GetCurrentProcess(); if (!DuplicateHandle(curr, output_write_handle, curr, &child_pipe, - 0, TRUE, DUPLICATE_SAME_ACCESS)) { + 0, TRUE, DUPLICATE_SAME_ACCESS)) { fatal("DuplicateHandle"); } CloseHandle(output_write_handle); } - HANDLE nul; - { + HANDLE nul = win_create_nul(); - SECURITY_ATTRIBUTES security_attributes; - memset(&security_attributes, 0, sizeof(SECURITY_ATTRIBUTES)); - security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); - security_attributes.bInheritHandle = TRUE; - - nul = - CreateFileA("NUL", GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - &security_attributes, OPEN_EXISTING, 0, NULL); - } - - - STARTUPINFOA startup_info; - memset(&startup_info, 0, sizeof(startup_info)); - startup_info.cb = sizeof(STARTUPINFO); + STARTUPINFOA sa = {sizeof(sa)}; if (!console) { - startup_info.dwFlags = STARTF_USESTDHANDLES; - startup_info.hStdInput = nul; - startup_info.hStdOutput = child_pipe; - startup_info.hStdError = child_pipe; + sa.dwFlags = STARTF_USESTDHANDLES; + sa.hStdInput = nul; + sa.hStdOutput = child_pipe; + sa.hStdError = child_pipe; } - PROCESS_INFORMATION process_info; memset(&process_info, 0, sizeof(process_info)); { WORD process_flags = 0; if (!CreateProcessA(NULL, cmd->s, NULL, NULL, - /* inherit handles */ TRUE, process_flags, - NULL, NULL, &startup_info, &process_info)) - { + /* inherit handles */ TRUE, process_flags, + NULL, NULL, &sa, &process_info)) { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) { if (child_pipe) { CloseHandle(child_pipe); } - if (created->output) { - CloseHandle(created->output); - created->output = 0; + if (created->pipe) { + CloseHandle(created->pipe); + created->pipe = 0; } CloseHandle(nul); return -1; @@ -371,13 +210,26 @@ oscreate_job(struct osjob *created, struct string* cmd, bool console) if (child_pipe) CloseHandle(child_pipe); CloseHandle(nul); - created->pid = process_info.hProcess; + created->proc = process_info.hProcess; CloseHandle(process_info.hThread); return 0; } -void -osclose_job(struct osjob *ojob) +/*ojobs is array of osjob*, entries may be NULL (invalid osjob).*/ +int osjob_wait(struct osjob_ctx *ctx, struct osjob *ojobs, size_t jobs_count, int timeout); +/*read out into buffer*/ +ssize_t osjob_work(struct osjob_ctx *ctx, struct osjob *ojob, void *buf, size_t buflen); +int osjob_close(struct osjob_ctx *ctx, struct osjob *ojob); +int osjob_done(struct osjob_ctx *ctx, struct osjob *ojob, struct string *cmd); +void osjob_ctx_init(struct osjob_ctx* ctx) { - CloseHandle(ojob->output); -} \ No newline at end of file + +} + +void osjob_ctx_close(struct osjob_ctx* ctx) +{ + +} + + + diff --git a/os.h b/os.h index c9f24e5..472fe5d 100644 --- a/os.h +++ b/os.h @@ -10,32 +10,28 @@ struct ostimespec { #ifdef _WIN32 typedef void* HANDLE; - typedef HANDLE fd_t; - typedef HANDLE pid_t; typedef long long ssize_t; + #define setvbuf(stream, buff, mode, len) (void)0 - #define WNOHANG 1 - #define WEXITSTATUS(status) status - #define WTERMSIG(status) 0 - #define WSTOPSIG(status) WEXITSTATUS(status) - #define WIFEXITED(status) (WTERMSIG(status) == 0) - #define WIFSIGNALED(status) 0 + struct osjob { + bool has_data; + bool valid; + + HANDLE pipe; + HANDLE proc; + }; - #define setvbuf(stream, buff, mode, len) (void)0 #else #include "poll.h" struct osjob { bool has_data; bool valid; + int pid; int fd; }; - struct osjob_ctx { - struct pollfd* pfds; - size_t pfds_len; - }; #endif struct buffer; @@ -51,12 +47,14 @@ int64_t osmtime(const char *); /* get current monotonic time */ int osclock_gettime_monotonic(struct ostimespec*); +// os-specific job functions + struct osjob_ctx; -int osjob_create(struct osjob *created, struct string *cmd, bool console); -/*ojobs is array of osjob*, entries may be NULL (invalid osjob).*/ -int osjob_wait(struct osjob_ctx *ctx, struct osjob *ojobs, size_t jobs_count, int timeout); -/*read out into buffer*/ -ssize_t osjob_work(struct osjob *ojob, void* buf, size_t buflen); -int osjob_close(struct osjob* ojob); -int osjob_done(struct osjob* ojob, struct string* cmd); +struct osjob_ctx* osjob_ctx_create(); +void osjob_ctx_close(struct osjob_ctx* ctx); +int osjob_create(struct osjob_ctx *ctx, struct osjob *created, struct string *cmd, bool console); +int osjob_wait(struct osjob_ctx *ctx, struct osjob ojobs[], size_t jobs_count, int timeout); +ssize_t osjob_work(struct osjob_ctx *ctx, struct osjob *ojob, void *buf, size_t buflen); +int osjob_done(struct osjob_ctx *ctx, struct osjob *ojob, struct string *cmd); +int osjob_close(struct osjob_ctx *ctx, struct osjob *ojob); From 82833eec9242318b35752c497781f12c9a835315 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Tue, 13 May 2025 23:21:57 +0200 Subject: [PATCH 03/13] Fix posix side for new API. Fix memory leaks --- .clang-format | 4 ++-- build.c | 32 ++++++++++++++++++++++---------- deps.c | 35 ++++++++++++++++++++++++++++++----- env.c | 1 + graph.c | 1 + os-posix.c | 44 +++++++++++++++++++++++++++++--------------- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/.clang-format b/.clang-format index d95787c..14832e4 100644 --- a/.clang-format +++ b/.clang-format @@ -5,7 +5,7 @@ AlignEscapedNewlines: DontAlign AlwaysBreakAfterDefinitionReturnType: All BreakBeforeBraces: Linux ColumnLimit: 0 -IndentWidth: 8 +IndentWidth: 4 SortIncludes: false -TabWidth: 8 +TabWidth: 4 UseTab: ForIndentation diff --git a/build.c b/build.c index 218238e..fd94503 100644 --- a/build.c +++ b/build.c @@ -114,8 +114,7 @@ buildadd(struct node *n) { struct edge *e; struct node *newest; - size_t i; - bool generator, restat; + size_t i; e = n->gen; if (!e) { @@ -153,15 +152,19 @@ buildadd(struct node *n) ++e->nblock; } /* all outputs are dirty if any are older than the newest input */ - generator = edgevar(e, "generator", true); - restat = edgevar(e, "restat", true); + struct string* generator = edgevar(e, "generator", true); + struct string* restat = edgevar(e, "restat", true); for (i = 0; i < e->nout && !(e->flags & FLAG_DIRTY_OUT); ++i) { n = e->out[i]; - if (isdirty(n, newest, generator, restat)) { + if (isdirty(n, newest, (bool)generator, (bool)restat)) { n->dirty = true; e->flags |= FLAG_DIRTY_OUT; } } + if (generator) + free(generator); + if (restat) + free(restat); if (e->flags & FLAG_DIRTY) { for (i = 0; i < e->nout; ++i) { n = e->out[i]; @@ -265,6 +268,7 @@ printstatus(struct edge *e, struct string *cmd) formatstatus(status, sizeof(status)); fputs(status, stdout); puts(description->s); + free(description); } static int @@ -289,6 +293,8 @@ jobstart(struct osjob_ctx *osctx, struct osjob *oj, struct job *j, struct edge * goto err0; } j->edge = e; + if (j->cmd) + free(j->cmd); j->cmd = edgevar(e, "command", true); if (!consoleused) @@ -362,18 +368,19 @@ edgedone(struct edge *e) { struct node *n; size_t i; - struct string *rspfile; - bool restat; + struct string *rspfile; int64_t old; - restat = edgevar(e, "restat", true); + struct string * restat = edgevar(e, "restat", true); for (i = 0; i < e->nout; ++i) { n = e->out[i]; old = n->mtime; nodestat(n); n->logmtime = n->mtime == MTIME_MISSING ? 0 : n->mtime; - nodedone(n, restat && shouldprune(e, n, old)); + nodedone(n, (bool)restat && shouldprune(e, n, old)); } + if (restat) + free(restat); rspfile = edgevar(e, "rspfile", false); if (rspfile && !buildopts.keeprsp) remove(rspfile->s); @@ -484,6 +491,7 @@ build(void) if (ntotal == 0) { warn("nothing to do"); + osjob_ctx_close(osctx); return; } @@ -546,8 +554,12 @@ build(void) } for (i = 0; i < jobslen; ++i) { free(jobs[i].buf.data); + free(jobs[i].cmd); } - free(jobs); + if (jobs) + free(jobs); + if (osjobs) + free(osjobs); osjob_ctx_close(osctx); if (numfail > 0) { if (numfail < buildopts.maxfail) diff --git a/deps.c b/deps.c index 94c3f7b..8109a8f 100644 --- a/deps.c +++ b/deps.c @@ -189,7 +189,14 @@ depsinit(const char *builddir) entry = &entries[id]; entry->mtime = (int64_t)buf[2] << 32 | buf[1]; e = entry->node->gen; - if (!e || !edgevar(e, "deps", true)) + bool has_deps; + { + struct string* deps = edgevar(e, "deps", true); + has_deps = (bool)deps; + if (deps) + free(deps); + } + if (!e || !has_deps) continue; sz /= 4; free(entry->deps.node); @@ -407,11 +414,15 @@ depsparse(const char *name, bool allowmissing) if (ferror(f)) { warn("depfile read '%s':", name); goto err; - } + } + if (out) + free(out); fclose(f); return &deps; err: + if (out) + free(out); fclose(f); return NULL; } @@ -429,6 +440,7 @@ depsload(struct edge *e) n = e->out[0]; deptype = edgevar(e, "deps", true); if (deptype) { + free(deptype); if (n->id != -1 && n->mtime <= entries[n->id].mtime) deps = &entries[n->id].deps; else if (buildopts.explain) @@ -460,21 +472,34 @@ depsrecord(struct edge *e) bool update; deptype = edgevar(e, "deps", true); - if (!deptype || deptype->n == 0) - return; + if (!deptype) { + return; + } + if (deptype->n == 0) { + free(deptype); + return; + } if (strcmp(deptype->s, "gcc") != 0) { warn("unsuported deps type: %s", deptype->s); + free(deptype); return; } + free(deptype); depfile = edgevar(e, "depfile", false); - if (!depfile || depfile->n == 0) { + if (!depfile) { warn("deps but no depfile"); return; } + if (depfile->n == 0) { + warn("deps but no depfile"); + free(depfile); + return; + } out = e->out[0]; deps = depsparse(depfile->s, true); if (!buildopts.keepdepfile) remove(depfile->s); + free(depfile); if (!deps) return; update = false; diff --git a/env.c b/env.c index baffaf9..3b496f5 100644 --- a/env.c +++ b/env.c @@ -234,6 +234,7 @@ edgevar(struct edge *e, char *var, bool escape) n->value = cycle; len = 0; for (p = str; p; p = p->next) { + // leaks memory here. p->str no cleaned up in all cases if (p->var) p->str = edgevar(e, p->var, escape); if (p->str) diff --git a/graph.c b/graph.c index 16573c3..575049f 100644 --- a/graph.c +++ b/graph.c @@ -175,6 +175,7 @@ edgehash(struct edge *e) } else { e->hash = murmurhash64a(cmd->s, cmd->n); } + free(cmd); } static struct edge * diff --git a/os-posix.c b/os-posix.c index 4f55479..257609d 100644 --- a/os-posix.c +++ b/os-posix.c @@ -1,3 +1,4 @@ +#include #define _POSIX_C_SOURCE 200809L #include @@ -18,11 +19,6 @@ #include #include -struct osjob_ctx { - struct pollfd* pfds; - size_t pfds_len; -}; - void osgetcwd(char *buf, size_t len) { @@ -111,23 +107,41 @@ osclock_gettime_monotonic(struct ostimespec* time) return 0; } -pid_t -oswaitpid(pid_t pid, int *status, int options) +////////////// Jobs + +struct osjob_ctx { + struct pollfd* pfds; + size_t pfds_len; +}; + + +struct osjob_ctx* +osjob_ctx_create() +{ + struct osjob_ctx* result = xmalloc(sizeof(struct osjob_ctx)); + memset(result, 0, sizeof(*result)); + return result; +} + +void +osjob_ctx_close(struct osjob_ctx* ctx) { - return waitpid(pid, status, options); + if (ctx->pfds) + free(ctx->pfds); + free(ctx); } -int osjob_close(struct osjob* ojob) +int osjob_close(struct osjob_ctx* ctx, struct osjob* ojob) { - close(ojob->fd); + close(ojob->fd); kill(ojob->pid, SIGTERM); memset(ojob, 0, sizeof(*ojob)); return 0; } int -osjob_done(struct osjob* ojob, struct string* cmd) +osjob_done(struct osjob_ctx* ctx, struct osjob* ojob, struct string* cmd) { int status; if (waitpid(ojob->pid, &status, 0) < 0) { @@ -140,9 +154,9 @@ osjob_done(struct osjob* ojob, struct string* cmd) warn("job failed with status %d: %s", WEXITSTATUS(status), cmd->s); goto err; } - return osjob_close(ojob); + return osjob_close(ctx, ojob); err: - osjob_close(ojob); + osjob_close(ctx, ojob); return -1; } @@ -176,14 +190,14 @@ osjob_wait(struct osjob_ctx *ctx, struct osjob* ojobs, size_t jobslen, int timeo } ssize_t -osjob_work(struct osjob *ojob, void *buf, size_t buflen) +osjob_work(struct osjob_ctx *ctx, struct osjob *ojob, void *buf, size_t buflen) { assert(ojob->has_data); return read(ojob->fd, buf, buflen); } int -osjob_create(struct osjob *created, struct string *cmd, bool console) +osjob_create(struct osjob_ctx *ctx, struct osjob *created, struct string *cmd, bool console) { extern char **environ; int fd[2]; From 0e81a7c50f58ee0074632d270338f9b55722c817 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Tue, 13 May 2025 23:23:53 +0200 Subject: [PATCH 04/13] Revert to old clang-format --- .clang-format | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.clang-format b/.clang-format index 14832e4..d95787c 100644 --- a/.clang-format +++ b/.clang-format @@ -5,7 +5,7 @@ AlignEscapedNewlines: DontAlign AlwaysBreakAfterDefinitionReturnType: All BreakBeforeBraces: Linux ColumnLimit: 0 -IndentWidth: 4 +IndentWidth: 8 SortIncludes: false -TabWidth: 4 +TabWidth: 8 UseTab: ForIndentation From 7a293107a9532e964e57d235e1526e693827c460 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Tue, 13 May 2025 22:03:31 +0200 Subject: [PATCH 05/13] It segfaults for some reason on depsparse --- CMakeLists.txt | 2 +- os-win32.c | 185 ++++++++++++++++++++++++++++--------------------- os.h | 11 ++- 3 files changed, 117 insertions(+), 81 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 864ca1f..dff7dc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ set(SOURCE ) if (WIN32) - list(APPEND SOURCE os-win32.c) + list(APPEND SOURCE os-win32.c samu.manifest) elseif(UNIX) list(APPEND SOURCE os-posix.c) endif() diff --git a/os-win32.c b/os-win32.c index 10c34ee..aca0101 100644 --- a/os-win32.c +++ b/os-win32.c @@ -1,13 +1,12 @@ -#define WIN32_LEAN_AND_MEAN - #include #include -#include #include "os.h" #include "graph.h" #include "util.h" +#define BUFFER_SIZE 4096 + void osgetcwd(char *buf, size_t len) { @@ -133,103 +132,133 @@ win_create_nul() return nul; } -static int -pipe_check_ready(HANDLE pipe) +struct osjob_ctx { + HANDLE iocp; +}; + +struct osjob_ctx* +osjob_ctx_create() { - DWORD avail = 0; - return PeekNamedPipe(pipe, NULL, 0, NULL, &avail, NULL) && avail > 0; + struct osjob_ctx *result = xmalloc(sizeof(*result)); + result->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (result->iocp == INVALID_HANDLE_VALUE) { + fatal("CreateIoCompletionPort:"); + } + return result; } +void +osjob_ctx_close(struct osjob_ctx *osctx) +{ + CloseHandle(osctx->iocp); + free(osctx); +} int -osjob_create(struct osjob_ctx *ctx, struct osjob *created, struct string *cmd, bool console) +osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, bool console) { - // taken and modified from ninja-build - char pipe_name[100] = {0}; - snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\samu_pid%lu_sp%p", GetCurrentProcessId(), (void *)created); - - created->pipe = CreateNamedPipeA(pipe_name, - PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE, - PIPE_UNLIMITED_INSTANCES, - 0, 0, INFINITE, NULL); - - if (created->pipe == INVALID_HANDLE_VALUE) { - fatal("CreateNamedPipe: %s", pipe_name); + HANDLE stdoutRead, stdoutWrite; + SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE}; + if (!CreatePipe(&stdoutRead, &stdoutWrite, &sa, 0)) + return -1; + SetHandleInformation(stdoutRead, HANDLE_FLAG_INHERIT, 0); + + HANDLE nul = win_create_nul(); + + STARTUPINFOA si = {sizeof(si)}; + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdOutput = stdoutWrite; + si.hStdError = stdoutWrite; + si.hStdInput = nul; + + PROCESS_INFORMATION pi; + BOOL inheritHandles = TRUE; + DWORD flags = 0; + + if (!CreateProcessA(NULL, cmd->s, NULL, NULL, inheritHandles, flags, NULL, NULL, &si, &pi)) { + CloseHandle(stdoutRead); + CloseHandle(stdoutWrite); + CloseHandle(nul); + return -1; } - // here CreateIoCompletionPort() can be used to make subprocesses cancellable using centralized ioport - - HANDLE child_pipe; - { - HANDLE output_write_handle = - CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); - HANDLE curr = GetCurrentProcess(); - if (!DuplicateHandle(curr, output_write_handle, - curr, &child_pipe, - 0, TRUE, DUPLICATE_SAME_ACCESS)) { - fatal("DuplicateHandle"); - } - CloseHandle(output_write_handle); - } + CloseHandle(stdoutWrite); + CloseHandle(nul); + CloseHandle(pi.hThread); - HANDLE nul = win_create_nul(); + created->output = stdoutRead; + created->hProcess = pi.hProcess; + created->valid = true; + created->has_data = false; + created->overlapped = (OVERLAPPED){0}; - STARTUPINFOA sa = {sizeof(sa)}; - if (!console) { - sa.dwFlags = STARTF_USESTDHANDLES; - sa.hStdInput = nul; - sa.hStdOutput = child_pipe; - sa.hStdError = child_pipe; - } + CreateIoCompletionPort(created->output, osctx->iocp, (ULONG_PTR)created, 0); - PROCESS_INFORMATION process_info; - memset(&process_info, 0, sizeof(process_info)); - { - WORD process_flags = 0; - if (!CreateProcessA(NULL, cmd->s, NULL, NULL, - /* inherit handles */ TRUE, process_flags, - NULL, NULL, &sa, &process_info)) { - DWORD error = GetLastError(); - - if (error == ERROR_FILE_NOT_FOUND) { - if (child_pipe) { - CloseHandle(child_pipe); - } - if (created->pipe) { - CloseHandle(created->pipe); - created->pipe = 0; - } - CloseHandle(nul); - return -1; - } else { - fatal("CreateProcess: %s", cmd->s); - } - } + if (!ReadFile(created->output, NULL, BUFFER_SIZE, + NULL, &created->overlapped) && + GetLastError() != ERROR_IO_PENDING) { + osjob_close(osctx, created); + return -1; } - if (child_pipe) - CloseHandle(child_pipe); - CloseHandle(nul); - created->proc = process_info.hProcess; - CloseHandle(process_info.hThread); + return 0; } -/*ojobs is array of osjob*, entries may be NULL (invalid osjob).*/ -int osjob_wait(struct osjob_ctx *ctx, struct osjob *ojobs, size_t jobs_count, int timeout); -/*read out into buffer*/ -ssize_t osjob_work(struct osjob_ctx *ctx, struct osjob *ojob, void *buf, size_t buflen); -int osjob_close(struct osjob_ctx *ctx, struct osjob *ojob); -int osjob_done(struct osjob_ctx *ctx, struct osjob *ojob, struct string *cmd); -void osjob_ctx_init(struct osjob_ctx* ctx) +int osjob_wait(struct osjob_ctx* osctx, struct osjob ojobs[], size_t jobs_count, int timeout) { + OVERLAPPED_ENTRY entries[64]; + ULONG num_entries = 0; + const DWORD timeout_ms = timeout == -1 ? INFINITE : timeout; + + if (!GetQueuedCompletionStatusEx(osctx->iocp, entries, 64, &num_entries, + timeout_ms, FALSE)) { + return GetLastError() == WAIT_TIMEOUT ? 0 : -1; + } + for (ULONG i = 0; i < num_entries; i++) { + struct osjob *job = (struct osjob *)entries[i].lpCompletionKey; + const DWORD bytes = entries[i].dwNumberOfBytesTransferred; + job->to_read = bytes; + job->has_data = true; + } + return 0; } -void osjob_ctx_close(struct osjob_ctx* ctx) +ssize_t osjob_work(struct osjob_ctx* osctx, struct osjob* ojob, void* buf, size_t buflen) { + assert(ojob->has_data); + if (ojob->to_read == 0) { // EOF/process exit + return 0; + } else { // Data available + DWORD read; + if (!ReadFile(ojob->output, buf, buflen, &read, &ojob->overlapped) && + GetLastError() != ERROR_IO_PENDING) { + ojob->valid = false; + return -1; + } + return read; + } +} +int osjob_done(struct osjob_ctx* osctx, struct osjob* ojob, struct string* cmd) +{ + WaitForSingleObject(ojob->hProcess, INFINITE); + int exit_code; + GetExitCodeProcess(ojob->hProcess, &exit_code); + if (exit_code != 0) { + warn("job failed with status %d: %s", exit_code, cmd->s); + osjob_close(osctx, ojob); + return -1; + } + return osjob_close(osctx, ojob); } +int osjob_close(struct osjob_ctx* osctx, struct osjob* ojob) +{ + CloseHandle(ojob->hProcess); + CloseHandle(ojob->output); + memset(ojob, 0, sizeof(*ojob)); + return 0; +} diff --git a/os.h b/os.h index 472fe5d..7b67ec1 100644 --- a/os.h +++ b/os.h @@ -9,6 +9,10 @@ struct ostimespec { }; #ifdef _WIN32 + + #define WIN32_LEAN_AND_MEAN + #include + typedef void* HANDLE; typedef long long ssize_t; @@ -19,8 +23,11 @@ struct ostimespec { bool has_data; bool valid; - HANDLE pipe; - HANDLE proc; + OVERLAPPED overlapped; + HANDLE output; + HANDLE hProcess; + + DWORD to_read; }; #else From a1e5387bbf2e5fd6d624d205d4f828818f80560c Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Wed, 14 May 2025 20:21:27 +0200 Subject: [PATCH 06/13] Minor improvements --- .gitignore | 1 + env.c | 2 ++ graph.h | 3 ++- parse.c | 4 +++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2dadc86..1568433 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ out/ build/ CMakeUserPresets.json CMakePresets.json +CMakeSettings.json *.user \ No newline at end of file diff --git a/env.c b/env.c index 3b496f5..a9e7692 100644 --- a/env.c +++ b/env.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "env.h" #include "graph.h" @@ -211,6 +212,7 @@ ruleaddvar(struct rule *r, char *var, struct evalstring *val) struct string * edgevar(struct edge *e, char *var, bool escape) { + assert(e && "Attempt to read variable of null edge"); static void *const cycle = (void *)&cycle; struct evalstring *str, *p; struct treenode *n; diff --git a/graph.h b/graph.h index 85a0d43..0a278c6 100644 --- a/graph.h +++ b/graph.h @@ -1,4 +1,5 @@ #include +#include #include /* for uint64_t */ /* set in the tv_nsec field of a node's mtime */ @@ -27,7 +28,7 @@ struct node { int32_t id; /* does the node need to be rebuilt */ - _Bool dirty; + bool dirty; }; /* build rule, i.e., edge between inputs and outputs */ diff --git a/parse.c b/parse.c index c4cf0de..990d569 100644 --- a/parse.c +++ b/parse.c @@ -132,8 +132,10 @@ parseedge(struct scanner *s, struct environment *env) npaths = 0; val = edgevar(e, "pool", true); - if (val) + if (val) { e->pool = poolget(val->s); + free(val); + } } static void From 9dc4f7e383a9d49933f6ded34b947045cc524b88 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Wed, 14 May 2025 21:23:19 +0200 Subject: [PATCH 07/13] Fix deps/log file renaming. Now subprocesses work --- deps.c | 4 ++ log.c | 4 ++ os-win32.c | 118 ++++++++++++++++++++++++++++++++++++++++++----------- os.h | 2 + 4 files changed, 104 insertions(+), 24 deletions(-) diff --git a/deps.c b/deps.c index 8109a8f..53b492a 100644 --- a/deps.c +++ b/deps.c @@ -283,10 +283,14 @@ depsinit(const char *builddir) } free(oldentries); fflush(depsfile); + fclose(depsfile); if (ferror(depsfile)) fatal("deps log write failed"); if (rename(depstmppath, depspath) < 0) fatal("deps log rename:"); + depsfile = fopen(depspath, "a"); + if (!depsfile) + fatal("deps log reopen:"); if (builddir) { free(depstmppath); free(depspath); diff --git a/log.c b/log.c index b81d97f..d2db5a6 100644 --- a/log.c +++ b/log.c @@ -137,10 +137,14 @@ loginit(const char *builddir) } } fflush(logfile); + fclose(logfile); if (ferror(logfile)) fatal("build log write failed"); if (rename(logtmppath, logpath) < 0) fatal("build log rename:"); + logfile = fopen(logpath, "a"); + if (!logfile) + fatal("build log reopen:"); if (builddir) { free(logpath); free(logtmppath); diff --git a/os-win32.c b/os-win32.c index aca0101..aedcd79 100644 --- a/os-win32.c +++ b/os-win32.c @@ -5,8 +5,6 @@ #include "graph.h" #include "util.h" -#define BUFFER_SIZE 4096 - void osgetcwd(char *buf, size_t len) { @@ -154,17 +152,66 @@ osjob_ctx_close(struct osjob_ctx *osctx) free(osctx); } +static int +win_start_read(struct osjob_ctx *osctx, struct osjob *job) +{ + DWORD readAmount; + BOOL read = ReadFile(job->output, job->buff, sizeof(job->buff), &readAmount, &job->overlapped); + if (read && !readAmount) { + job->to_read = 0; + job->has_data = true; + } else if (!read && GetLastError() != ERROR_IO_PENDING) { + warn("StartPipeRead:"); + osjob_close(osctx, job); + return -1; + } + return 0; +} + int osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, bool console) { - HANDLE stdoutRead, stdoutWrite; + // Generate a unique pipe name + static volatile long counter = 0; + char pipeName[MAX_PATH]; + snprintf(pipeName, sizeof(pipeName), "\\\\.\\pipe\\ninjabuild-%ld-%lu", + InterlockedIncrement(&counter), GetCurrentProcessId()); + SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE}; - if (!CreatePipe(&stdoutRead, &stdoutWrite, &sa, 0)) + + // Create overlapped read handle (server side) + HANDLE stdoutRead = CreateNamedPipeA( + pipeName, + PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, // Number of instances + 4096, // Out buffer size + 4096, // In buffer size + 0, // Timeout + &sa); + if (stdoutRead == INVALID_HANDLE_VALUE) { + warn("CreateNamedPipe:"); return -1; - SetHandleInformation(stdoutRead, HANDLE_FLAG_INHERIT, 0); - + } + + // Create overlapped write handle (client side) + HANDLE stdoutWrite = CreateFileA( + pipeName, + GENERIC_WRITE, + 0, // No sharing + &sa, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + if (stdoutWrite == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + CloseHandle(stdoutRead); + warn("CreateFile(pipe): %lu", err); + return -1; + } + HANDLE nul = win_create_nul(); - + STARTUPINFOA si = {sizeof(si)}; si.dwFlags = STARTF_USESTDHANDLES; si.hStdOutput = stdoutWrite; @@ -179,6 +226,7 @@ osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, CloseHandle(stdoutRead); CloseHandle(stdoutWrite); CloseHandle(nul); + warn("CreateProcess:"); return -1; } @@ -190,17 +238,19 @@ osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, created->hProcess = pi.hProcess; created->valid = true; created->has_data = false; - created->overlapped = (OVERLAPPED){0}; + memset(&created->overlapped, 0, sizeof(OVERLAPPED)); - CreateIoCompletionPort(created->output, osctx->iocp, (ULONG_PTR)created, 0); - - if (!ReadFile(created->output, NULL, BUFFER_SIZE, - NULL, &created->overlapped) && - GetLastError() != ERROR_IO_PENDING) { + // Associate with IOCP + if (!CreateIoCompletionPort(created->output, osctx->iocp, (ULONG_PTR)created, 0)) { + warn("CreateIoCompletionPort:"); osjob_close(osctx, created); return -1; } + if (win_start_read(osctx, created) < 0) { + return -1; + } + return 0; } @@ -210,8 +260,14 @@ int osjob_wait(struct osjob_ctx* osctx, struct osjob ojobs[], size_t jobs_count, ULONG num_entries = 0; const DWORD timeout_ms = timeout == -1 ? INFINITE : timeout; - if (!GetQueuedCompletionStatusEx(osctx->iocp, entries, 64, &num_entries, - timeout_ms, FALSE)) { + for (size_t i = 0; i < jobs_count; ++i) { + struct osjob *job = ojobs + i; + if (job->valid && job->has_data) { + return 0; //some jobs are already buffered + } + } + + if (!GetQueuedCompletionStatusEx(osctx->iocp, entries, 64, &num_entries, timeout_ms, FALSE)) { return GetLastError() == WAIT_TIMEOUT ? 0 : -1; } @@ -230,11 +286,17 @@ ssize_t osjob_work(struct osjob_ctx* osctx, struct osjob* ojob, void* buf, size_ if (ojob->to_read == 0) { // EOF/process exit return 0; } else { // Data available - DWORD read; - if (!ReadFile(ojob->output, buf, buflen, &read, &ojob->overlapped) && - GetLastError() != ERROR_IO_PENDING) { - ojob->valid = false; - return -1; + size_t read = buflen < ojob->to_read ? buflen : ojob->to_read; + memcpy(buf, ojob->buff, read); + ojob->to_read -= read; + if (!ojob->to_read) { + if (win_start_read(osctx, ojob) < 0) { + return -1; + } + ojob->has_data = false; + } else { + memmove(ojob->buff, ojob->buff + read, ojob->to_read); + // move buffer -> will get polled again as if wait() } return read; } @@ -242,15 +304,23 @@ ssize_t osjob_work(struct osjob_ctx* osctx, struct osjob* ojob, void* buf, size_ int osjob_done(struct osjob_ctx* osctx, struct osjob* ojob, struct string* cmd) { - WaitForSingleObject(ojob->hProcess, INFINITE); + if (WaitForSingleObject(ojob->hProcess, INFINITE) == WAIT_FAILED) { + warn("wait process:"); + goto err; + } int exit_code; - GetExitCodeProcess(ojob->hProcess, &exit_code); + if (!GetExitCodeProcess(ojob->hProcess, &exit_code)) { + warn("WaitForSingleObject:"); + goto err; + } if (exit_code != 0) { warn("job failed with status %d: %s", exit_code, cmd->s); - osjob_close(osctx, ojob); - return -1; + goto err; } return osjob_close(osctx, ojob); +err: + osjob_close(osctx, ojob); + return -1; } int osjob_close(struct osjob_ctx* osctx, struct osjob* ojob) diff --git a/os.h b/os.h index 7b67ec1..5db38f1 100644 --- a/os.h +++ b/os.h @@ -28,6 +28,8 @@ struct ostimespec { HANDLE hProcess; DWORD to_read; + + char buff[4096]; }; #else From 6b320ca2a72d4f379f8a93af857edfbe6135d158 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Wed, 14 May 2025 21:53:31 +0200 Subject: [PATCH 08/13] Fix warning message on Pipe write end opening --- os-win32.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/os-win32.c b/os-win32.c index aedcd79..b58cde0 100644 --- a/os-win32.c +++ b/os-win32.c @@ -204,9 +204,8 @@ osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, FILE_FLAG_OVERLAPPED, NULL); if (stdoutWrite == INVALID_HANDLE_VALUE) { - DWORD err = GetLastError(); CloseHandle(stdoutRead); - warn("CreateFile(pipe): %lu", err); + warn("CreateFile(pipe):"); return -1; } From 169b2351eae7eb6bf010ff44dad2823c207ac7cb Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Fri, 16 May 2025 13:04:13 +0200 Subject: [PATCH 09/13] Run clang-format --- arg.h | 2 +- build.c | 74 +++++++++++++++++++++++++-------------------------- deps.c | 77 +++++++++++++++++++++++++++++------------------------- env.c | 2 +- graph.c | 2 +- graph.h | 16 ++++++------ htab.h | 2 +- log.c | 10 +++---- os-posix.c | 51 ++++++++++++++++++------------------ os-win32.c | 29 +++++++++++--------- os.h | 55 +++++++++++++++++++------------------- samu.c | 16 ++++++++---- scan.c | 15 ++++++----- tool.c | 24 ++++++++++------- util.c | 3 ++- util.h | 3 ++- 16 files changed, 201 insertions(+), 180 deletions(-) diff --git a/arg.h b/arg.h index e1d0903..b1cf783 100644 --- a/arg.h +++ b/arg.h @@ -14,7 +14,7 @@ extern const char *argv0; switch (*opt_) #define ARGEND \ - } \ + } \ } #define EARGF(x) \ diff --git a/build.c b/build.c index fd94503..e4ed233 100644 --- a/build.c +++ b/build.c @@ -18,7 +18,7 @@ struct job { struct string *cmd; struct edge *edge; struct buffer buf; - size_t next; + size_t next; bool failed; }; @@ -114,7 +114,7 @@ buildadd(struct node *n) { struct edge *e; struct node *newest; - size_t i; + size_t i; e = n->gen; if (!e) { @@ -152,19 +152,19 @@ buildadd(struct node *n) ++e->nblock; } /* all outputs are dirty if any are older than the newest input */ - struct string* generator = edgevar(e, "generator", true); - struct string* restat = edgevar(e, "restat", true); + struct string *generator = edgevar(e, "generator", true); + struct string *restat = edgevar(e, "restat", true); for (i = 0; i < e->nout && !(e->flags & FLAG_DIRTY_OUT); ++i) { n = e->out[i]; - if (isdirty(n, newest, (bool)generator, (bool)restat)) { + if (isdirty(n, newest, (bool)generator, (bool)restat)) { n->dirty = true; e->flags |= FLAG_DIRTY_OUT; } } - if (generator) - free(generator); - if (restat) - free(restat); + if (generator) + free(generator); + if (restat) + free(restat); if (e->flags & FLAG_DIRTY) { for (i = 0; i < e->nout; ++i) { n = e->out[i]; @@ -241,7 +241,7 @@ formatstatus(char *buf, size_t len) break; default: fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *fmt); - continue; /* unreachable, but avoids warning */ + continue; /* unreachable, but avoids warning */ } if (n < 0) fatal("snprintf:"); @@ -268,7 +268,7 @@ printstatus(struct edge *e, struct string *cmd) formatstatus(status, sizeof(status)); fputs(status, stdout); puts(description->s); - free(description); + free(description); } static int @@ -293,8 +293,8 @@ jobstart(struct osjob_ctx *osctx, struct osjob *oj, struct job *j, struct edge * goto err0; } j->edge = e; - if (j->cmd) - free(j->cmd); + if (j->cmd) + free(j->cmd); j->cmd = edgevar(e, "command", true); if (!consoleused) @@ -308,13 +308,13 @@ jobstart(struct osjob_ctx *osctx, struct osjob *oj, struct job *j, struct edge * j->failed = false; if (e->pool == &consolepool) consoleused = true; - oj->valid = true; - return 0; + oj->valid = true; + return 0; err1: if (rspfile && !buildopts.keeprsp) remove(rspfile->s); err0: - return -1; + return -1; } static void @@ -368,19 +368,19 @@ edgedone(struct edge *e) { struct node *n; size_t i; - struct string *rspfile; + struct string *rspfile; int64_t old; - struct string * restat = edgevar(e, "restat", true); + struct string *restat = edgevar(e, "restat", true); for (i = 0; i < e->nout; ++i) { n = e->out[i]; old = n->mtime; nodestat(n); n->logmtime = n->mtime == MTIME_MISSING ? 0 : n->mtime; - nodedone(n, (bool)restat && shouldprune(e, n, old)); + nodedone(n, (bool)restat && shouldprune(e, n, old)); } - if (restat) - free(restat); + if (restat) + free(restat); rspfile = edgevar(e, "rspfile", false); if (rspfile && !buildopts.keeprsp) remove(rspfile->s); @@ -428,7 +428,7 @@ jobdone(struct osjob_ctx *osctx, struct job *j, struct osjob *oj) /* returns whether a job still has work to do. if not, sets j->failed */ static bool -jobwork(struct osjob_ctx* osctx, struct job *j, struct osjob *ojob) +jobwork(struct osjob_ctx *osctx, struct job *j, struct osjob *ojob) { char *newdata; size_t newcap; @@ -452,7 +452,7 @@ jobwork(struct osjob_ctx* osctx, struct job *j, struct osjob *ojob) goto done; } else { warn("read:"); -kill: + kill: osjob_close(osctx, ojob); j->failed = true; } @@ -484,14 +484,14 @@ void build(void) { struct job *jobs = NULL; - struct osjob* osjobs = NULL; - struct osjob_ctx* osctx = osjob_ctx_create(); + struct osjob *osjobs = NULL; + struct osjob_ctx *osctx = osjob_ctx_create(); size_t i, next = 0, jobslen = 0, maxjobs = buildopts.maxjobs, numjobs = 0, numfail = 0; struct edge *e; if (ntotal == 0) { warn("nothing to do"); - osjob_ctx_close(osctx); + osjob_ctx_close(osctx); return; } @@ -524,11 +524,11 @@ build(void) jobs = xreallocarray(jobs, jobslen, sizeof(jobs[0])); osjobs = xreallocarray(osjobs, jobslen, sizeof(osjobs[0])); for (i = next; i < jobslen; ++i) { - jobs[i] = (struct job){0}; - jobs[i].next = i + 1; - osjobs[i] = (struct osjob){0}; + jobs[i] = (struct job){0}; + jobs[i].next = i + 1; + osjobs[i] = (struct osjob){0}; } - } + } if (jobstart(osctx, &osjobs[next], &jobs[next], e) < 0) { warn("job failed to start"); ++numfail; @@ -546,7 +546,7 @@ build(void) continue; --numjobs; jobs[i].next = next; - osjobs[i].valid = false; + osjobs[i].valid = false; next = i; if (jobs[i].failed) ++numfail; @@ -554,12 +554,12 @@ build(void) } for (i = 0; i < jobslen; ++i) { free(jobs[i].buf.data); - free(jobs[i].cmd); + free(jobs[i].cmd); } - if (jobs) - free(jobs); - if (osjobs) - free(osjobs); + if (jobs) + free(jobs); + if (osjobs) + free(osjobs); osjob_ctx_close(osctx); if (numfail > 0) { if (numfail < buildopts.maxfail) @@ -569,5 +569,5 @@ build(void) else fatal("subcommand failed"); } - ntotal = 0; /* reset in case we just rebuilt the manifest */ + ntotal = 0; /* reset in case we just rebuilt the manifest */ } diff --git a/deps.c b/deps.c index 53b492a..b6f2d09 100644 --- a/deps.c +++ b/deps.c @@ -162,7 +162,8 @@ depsinit(const char *builddir) goto rewrite; } if (sz > cap) { - do cap *= 2; + do + cap *= 2; while (sz > cap); free(buf); buf = xmalloc(cap); @@ -189,14 +190,14 @@ depsinit(const char *builddir) entry = &entries[id]; entry->mtime = (int64_t)buf[2] << 32 | buf[1]; e = entry->node->gen; - bool has_deps; - { - struct string* deps = edgevar(e, "deps", true); - has_deps = (bool)deps; - if (deps) - free(deps); - } - if (!e || !has_deps) + bool has_deps; + { + struct string *deps = edgevar(e, "deps", true); + has_deps = (bool)deps; + if (deps) + free(deps); + } + if (!e || !has_deps) continue; sz /= 4; free(entry->deps.node); @@ -344,9 +345,14 @@ depsparse(const char *name, bool allowmissing) for (; n > 2; n -= 2) bufadd(&buf, '\\'); switch (c) { - case '#': break; - case '\n': c = ' '; continue; - default: bufadd(&buf, '\\'); continue; + case '#': + break; + case '\n': + c = ' '; + continue; + default: + bufadd(&buf, '\\'); + continue; } break; case '$': @@ -377,7 +383,8 @@ depsparse(const char *name, bool allowmissing) } if (c == '\n') { sawcolon = false; - do c = getc(f); + do + c = getc(f); while (c == '\n'); } if (c == EOF) @@ -418,15 +425,15 @@ depsparse(const char *name, bool allowmissing) if (ferror(f)) { warn("depfile read '%s':", name); goto err; - } - if (out) - free(out); + } + if (out) + free(out); fclose(f); return &deps; err: - if (out) - free(out); + if (out) + free(out); fclose(f); return NULL; } @@ -444,7 +451,7 @@ depsload(struct edge *e) n = e->out[0]; deptype = edgevar(e, "deps", true); if (deptype) { - free(deptype); + free(deptype); if (n->id != -1 && n->mtime <= entries[n->id].mtime) deps = &entries[n->id].deps; else if (buildopts.explain) @@ -476,34 +483,34 @@ depsrecord(struct edge *e) bool update; deptype = edgevar(e, "deps", true); - if (!deptype) { - return; - } - if (deptype->n == 0) { - free(deptype); - return; - } + if (!deptype) { + return; + } + if (deptype->n == 0) { + free(deptype); + return; + } if (strcmp(deptype->s, "gcc") != 0) { warn("unsuported deps type: %s", deptype->s); - free(deptype); + free(deptype); return; } - free(deptype); + free(deptype); depfile = edgevar(e, "depfile", false); - if (!depfile) { + if (!depfile) { + warn("deps but no depfile"); + return; + } + if (depfile->n == 0) { warn("deps but no depfile"); + free(depfile); return; } - if (depfile->n == 0) { - warn("deps but no depfile"); - free(depfile); - return; - } out = e->out[0]; deps = depsparse(depfile->s, true); if (!buildopts.keepdepfile) remove(depfile->s); - free(depfile); + free(depfile); if (!deps) return; update = false; diff --git a/env.c b/env.c index a9e7692..09f4e11 100644 --- a/env.c +++ b/env.c @@ -236,7 +236,7 @@ edgevar(struct edge *e, char *var, bool escape) n->value = cycle; len = 0; for (p = str; p; p = p->next) { - // leaks memory here. p->str no cleaned up in all cases + // leaks memory here. p->str not cleaned up in all cases if (p->var) p->str = edgevar(e, p->var, escape); if (p->str) diff --git a/graph.c b/graph.c index 575049f..4b283ae 100644 --- a/graph.c +++ b/graph.c @@ -175,7 +175,7 @@ edgehash(struct edge *e) } else { e->hash = murmurhash64a(cmd->s, cmd->n); } - free(cmd); + free(cmd); } static struct edge * diff --git a/graph.h b/graph.h index 0a278c6..7e9538c 100644 --- a/graph.h +++ b/graph.h @@ -1,6 +1,6 @@ #include #include -#include /* for uint64_t */ +#include /* for uint64_t */ /* set in the tv_nsec field of a node's mtime */ enum { @@ -55,13 +55,13 @@ struct edge { size_t nprune; enum { - FLAG_WORK = 1 << 0, /* scheduled for build */ - FLAG_HASH = 1 << 1, /* calculated the command hash */ - FLAG_DIRTY_IN = 1 << 3, /* dirty input */ - FLAG_DIRTY_OUT = 1 << 4, /* missing or outdated output */ - FLAG_DIRTY = FLAG_DIRTY_IN | FLAG_DIRTY_OUT, - FLAG_CYCLE = 1 << 5, /* used for cycle detection */ - FLAG_DEPS = 1 << 6, /* dependencies loaded */ + FLAG_WORK = 1 << 0, /* scheduled for build */ + FLAG_HASH = 1 << 1, /* calculated the command hash */ + FLAG_DIRTY_IN = 1 << 3, /* dirty input */ + FLAG_DIRTY_OUT = 1 << 4, /* missing or outdated output */ + FLAG_DIRTY = FLAG_DIRTY_IN | FLAG_DIRTY_OUT, + FLAG_CYCLE = 1 << 5, /* used for cycle detection */ + FLAG_DEPS = 1 << 6, /* dependencies loaded */ } flags; /* used to coordinate ready work in build() */ diff --git a/htab.h b/htab.h index 20e1aeb..b6b0b25 100644 --- a/htab.h +++ b/htab.h @@ -1,4 +1,4 @@ -#include /* for uint64_t */ +#include /* for uint64_t */ struct hashtablekey { uint64_t hash; diff --git a/log.c b/log.c index d2db5a6..c082861 100644 --- a/log.c +++ b/log.c @@ -75,11 +75,11 @@ loginit(const char *builddir) ++nline; p = buf.data; buf.len = 0; - if (!nextfield(&p)) /* start time */ + if (!nextfield(&p)) /* start time */ continue; - if (!nextfield(&p)) /* end time */ + if (!nextfield(&p)) /* end time */ continue; - s = nextfield(&p); /* mtime (used for restat) */ + s = nextfield(&p); /* mtime (used for restat) */ if (!s) continue; mtime = strtoll(s, &s, 10); @@ -87,7 +87,7 @@ loginit(const char *builddir) warn("corrupt build log: invalid mtime"); continue; } - s = nextfield(&p); /* output path */ + s = nextfield(&p); /* output path */ if (!s) continue; n = nodeget(s, 0); @@ -96,7 +96,7 @@ loginit(const char *builddir) if (n->logmtime == MTIME_MISSING) ++nentry; n->logmtime = mtime; - s = nextfield(&p); /* command hash */ + s = nextfield(&p); /* command hash */ if (!s) continue; n->hash = strtoull(s, &s, 16); diff --git a/os-posix.c b/os-posix.c index 257609d..88bc968 100644 --- a/os-posix.c +++ b/os-posix.c @@ -96,7 +96,7 @@ since it has not been updated to support POSIX.1-2008: } int -osclock_gettime_monotonic(struct ostimespec* time) +osclock_gettime_monotonic(struct ostimespec *time) { struct timespec t; if (clock_gettime(CLOCK_MONOTONIC, &t) < 0) { @@ -110,38 +110,38 @@ osclock_gettime_monotonic(struct ostimespec* time) ////////////// Jobs struct osjob_ctx { - struct pollfd* pfds; - size_t pfds_len; + struct pollfd *pfds; + size_t pfds_len; }; - -struct osjob_ctx* +struct osjob_ctx * osjob_ctx_create() { - struct osjob_ctx* result = xmalloc(sizeof(struct osjob_ctx)); - memset(result, 0, sizeof(*result)); - return result; + struct osjob_ctx *result = xmalloc(sizeof(struct osjob_ctx)); + memset(result, 0, sizeof(*result)); + return result; } void -osjob_ctx_close(struct osjob_ctx* ctx) +osjob_ctx_close(struct osjob_ctx *ctx) { - if (ctx->pfds) - free(ctx->pfds); - free(ctx); + if (ctx->pfds) + free(ctx->pfds); + free(ctx); } -int osjob_close(struct osjob_ctx* ctx, struct osjob* ojob) +int +osjob_close(struct osjob_ctx *ctx, struct osjob *ojob) { - close(ojob->fd); + close(ojob->fd); kill(ojob->pid, SIGTERM); memset(ojob, 0, sizeof(*ojob)); return 0; } int -osjob_done(struct osjob_ctx* ctx, struct osjob* ojob, struct string* cmd) +osjob_done(struct osjob_ctx *ctx, struct osjob *ojob, struct string *cmd) { int status; if (waitpid(ojob->pid, &status, 0) < 0) { @@ -161,7 +161,7 @@ osjob_done(struct osjob_ctx* ctx, struct osjob* ojob, struct string* cmd) } int -osjob_wait(struct osjob_ctx *ctx, struct osjob* ojobs, size_t jobslen, int timeout) +osjob_wait(struct osjob_ctx *ctx, struct osjob *ojobs, size_t jobslen, int timeout) { if (ctx->pfds_len < jobslen) { ctx->pfds = xreallocarray(ctx->pfds, jobslen, sizeof(ctx->pfds[0])); @@ -169,21 +169,21 @@ osjob_wait(struct osjob_ctx *ctx, struct osjob* ojobs, size_t jobslen, int timeo } nfds_t count = 0; for (size_t i = 0; i < jobslen; ++i) { - if (ojobs[i].valid) { - ctx->pfds[i].events = POLLIN; - ctx->pfds[i].fd = ojobs[i].fd; - ctx->pfds[i].revents = 0; + if (ojobs[i].valid) { + ctx->pfds[i].events = POLLIN; + ctx->pfds[i].fd = ojobs[i].fd; + ctx->pfds[i].revents = 0; count++; } } if (poll(ctx->pfds, count, timeout) < 0) { fatal("poll:"); } - struct osjob* curr = ojobs; - for(nfds_t i = 0; i < count; ++i) { - while(!curr->valid) - curr++; - curr->has_data = ctx->pfds[i].revents; + struct osjob *curr = ojobs; + for (nfds_t i = 0; i < count; ++i) { + while (!curr->valid) + curr++; + curr->has_data = ctx->pfds[i].revents; curr++; } return 0; @@ -254,4 +254,3 @@ osjob_create(struct osjob_ctx *ctx, struct osjob *created, struct string *cmd, b close(fd[1]); return -1; } - diff --git a/os-win32.c b/os-win32.c index b58cde0..3c58d7e 100644 --- a/os-win32.c +++ b/os-win32.c @@ -56,7 +56,8 @@ osmkdirs(struct string *_path, bool parent) } // taken fron ninja-build -static int64_t TimeStampFromFileTime(const FILETIME* filetime) +static int64_t +TimeStampFromFileTime(const FILETIME *filetime) { // FILETIME is in 100-nanosecond increments since the Windows epoch. // We don't much care about epoch correctness but we do want the @@ -85,7 +86,7 @@ osmtime(const char *name) } int -osclock_gettime_monotonic(struct ostimespec* ts) +osclock_gettime_monotonic(struct ostimespec *ts) { static LARGE_INTEGER frequency = {0}; static BOOL initialized = FALSE; @@ -116,15 +117,15 @@ osclock_gettime_monotonic(struct ostimespec* ts) ///////////////////////////// JOBS -static HANDLE +static HANDLE win_create_nul() { SECURITY_ATTRIBUTES sa = {sizeof(sa)}; sa.bInheritHandle = TRUE; // Must be inheritable so subprocesses can dup to children. HANDLE nul = CreateFileA("NUL", GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - &sa, OPEN_EXISTING, 0, NULL); + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + &sa, OPEN_EXISTING, 0, NULL); if (nul == INVALID_HANDLE_VALUE) fatal("couldn't open nul:"); return nul; @@ -134,7 +135,7 @@ struct osjob_ctx { HANDLE iocp; }; -struct osjob_ctx* +struct osjob_ctx * osjob_ctx_create() { struct osjob_ctx *result = xmalloc(sizeof(*result)); @@ -253,7 +254,8 @@ osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, return 0; } -int osjob_wait(struct osjob_ctx* osctx, struct osjob ojobs[], size_t jobs_count, int timeout) +int +osjob_wait(struct osjob_ctx *osctx, struct osjob ojobs[], size_t jobs_count, int timeout) { OVERLAPPED_ENTRY entries[64]; ULONG num_entries = 0; @@ -262,7 +264,7 @@ int osjob_wait(struct osjob_ctx* osctx, struct osjob ojobs[], size_t jobs_count, for (size_t i = 0; i < jobs_count; ++i) { struct osjob *job = ojobs + i; if (job->valid && job->has_data) { - return 0; //some jobs are already buffered + return 0; // some jobs are already buffered } } @@ -279,7 +281,8 @@ int osjob_wait(struct osjob_ctx* osctx, struct osjob ojobs[], size_t jobs_count, return 0; } -ssize_t osjob_work(struct osjob_ctx* osctx, struct osjob* ojob, void* buf, size_t buflen) +ssize_t +osjob_work(struct osjob_ctx *osctx, struct osjob *ojob, void *buf, size_t buflen) { assert(ojob->has_data); if (ojob->to_read == 0) { // EOF/process exit @@ -301,7 +304,8 @@ ssize_t osjob_work(struct osjob_ctx* osctx, struct osjob* ojob, void* buf, size_ } } -int osjob_done(struct osjob_ctx* osctx, struct osjob* ojob, struct string* cmd) +int +osjob_done(struct osjob_ctx *osctx, struct osjob *ojob, struct string *cmd) { if (WaitForSingleObject(ojob->hProcess, INFINITE) == WAIT_FAILED) { warn("wait process:"); @@ -322,12 +326,11 @@ int osjob_done(struct osjob_ctx* osctx, struct osjob* ojob, struct string* cmd) return -1; } -int osjob_close(struct osjob_ctx* osctx, struct osjob* ojob) +int +osjob_close(struct osjob_ctx *osctx, struct osjob *ojob) { CloseHandle(ojob->hProcess); CloseHandle(ojob->output); memset(ojob, 0, sizeof(*ojob)); return 0; } - - diff --git a/os.h b/os.h index 5db38f1..0255838 100644 --- a/os.h +++ b/os.h @@ -4,43 +4,42 @@ #include struct ostimespec { - int64_t tv_sec; - int64_t tv_nsec; + int64_t tv_sec; + int64_t tv_nsec; }; #ifdef _WIN32 - #define WIN32_LEAN_AND_MEAN - #include +#define WIN32_LEAN_AND_MEAN +#include - typedef void* HANDLE; - typedef long long ssize_t; +typedef void *HANDLE; +typedef long long ssize_t; - #define setvbuf(stream, buff, mode, len) (void)0 +#define setvbuf(stream, buff, mode, len) (void)0 +struct osjob { + bool has_data; + bool valid; - struct osjob { - bool has_data; - bool valid; + OVERLAPPED overlapped; + HANDLE output; + HANDLE hProcess; - OVERLAPPED overlapped; - HANDLE output; - HANDLE hProcess; + DWORD to_read; - DWORD to_read; - - char buff[4096]; - }; + char buff[4096]; +}; #else - #include "poll.h" - struct osjob { - bool has_data; - bool valid; - - int pid; - int fd; - }; +#include "poll.h" +struct osjob { + bool has_data; + bool valid; + + int pid; + int fd; +}; #endif struct buffer; @@ -54,14 +53,14 @@ int osmkdirs(struct string *, bool); /* queries the mtime of a file in nanoseconds since the UNIX epoch */ int64_t osmtime(const char *); /* get current monotonic time */ -int osclock_gettime_monotonic(struct ostimespec*); +int osclock_gettime_monotonic(struct ostimespec *); // os-specific job functions struct osjob_ctx; -struct osjob_ctx* osjob_ctx_create(); -void osjob_ctx_close(struct osjob_ctx* ctx); +struct osjob_ctx *osjob_ctx_create(); +void osjob_ctx_close(struct osjob_ctx *ctx); int osjob_create(struct osjob_ctx *ctx, struct osjob *created, struct string *cmd, bool console); int osjob_wait(struct osjob_ctx *ctx, struct osjob ojobs[], size_t jobs_count, int timeout); ssize_t osjob_work(struct osjob_ctx *ctx, struct osjob *ojob, void *buf, size_t buflen); diff --git a/samu.c b/samu.c index 05f5597..bead33f 100644 --- a/samu.c +++ b/samu.c @@ -109,7 +109,8 @@ parseenvargs(char *env) } argv[argc] = NULL; - ARGBEGIN { + ARGBEGIN + { case 'j': jobsflag(EARGF(usage())); break; @@ -121,7 +122,8 @@ parseenvargs(char *env) break; default: fatal("invalid option in SAMUFLAGS"); - } ARGEND + } + ARGEND free(env); } @@ -148,7 +150,8 @@ main(int argc, char *argv[]) argv0 = progname(argv[0], "samu"); parseenvargs(getenv("SAMUFLAGS")); - ARGBEGIN { + ARGBEGIN + { case '-': arg = EARGF(usage()); if (strcmp(arg, "version") == 0) { @@ -197,13 +200,16 @@ main(int argc, char *argv[]) break; default: usage(); - } ARGEND + } + ARGEND argdone: if (!buildopts.maxjobs) { #ifdef _SC_NPROCESSORS_ONLN long nproc = sysconf(_SC_NPROCESSORS_ONLN); switch (nproc) { - case -1: case 0: case 1: + case -1: + case 0: + case 1: buildopts.maxjobs = 2; break; case 2: diff --git a/scan.c b/scan.c index 92cabd4..2167dd0 100644 --- a/scan.c +++ b/scan.c @@ -117,7 +117,8 @@ comment(struct scanner *s) { if (s->chr != '#') return false; - do next(s); + do + next(s); while (!newline(s)); return true; } @@ -144,12 +145,12 @@ scankeyword(struct scanner *s, char **var) const char *name; int value; } keywords[] = { - {"build", BUILD}, - {"default", DEFAULT}, - {"include", INCLUDE}, - {"pool", POOL}, - {"rule", RULE}, - {"subninja", SUBNINJA}, + {"build", BUILD}, + {"default", DEFAULT}, + {"include", INCLUDE}, + {"pool", POOL}, + {"rule", RULE}, + {"subninja", SUBNINJA}, }; int low = 0, high = LEN(keywords) - 1, mid, cmp; diff --git a/tool.c b/tool.c index 092bf46..d51bb50 100644 --- a/tool.c +++ b/tool.c @@ -72,7 +72,8 @@ clean(int argc, char *argv[]) struct node *n; struct rule *r; - ARGBEGIN { + ARGBEGIN + { case 'g': cleangen = true; break; @@ -82,7 +83,8 @@ clean(int argc, char *argv[]) default: fprintf(stderr, "usage: %s ... -t clean [-gr] [targets...]\n", argv0); return 2; - } ARGEND + } + ARGEND if (cleanrule) { if (!argc) @@ -200,14 +202,16 @@ compdb(int argc, char *argv[]) int i; size_t off; - ARGBEGIN { + ARGBEGIN + { case 'x': expandrsp = true; break; default: fprintf(stderr, "usage: %s ... -t compdb [-x] [rules...]\n", argv0); return 2; - } ARGEND + } + ARGEND osgetcwd(dir, sizeof(dir)); @@ -436,12 +440,12 @@ targets(int argc, char *argv[]) } static const struct tool tools[] = { - {"clean", clean}, - {"commands", commands}, - {"compdb", compdb}, - {"graph", graph}, - {"query", query}, - {"targets", targets}, + {"clean", clean}, + {"commands", commands}, + {"compdb", compdb}, + {"graph", graph}, + {"query", query}, + {"targets", targets}, }; const struct tool * diff --git a/util.c b/util.c index c42629f..5479aa8 100644 --- a/util.c +++ b/util.c @@ -173,7 +173,8 @@ canonpath(struct string *path) continue; case '.': switch (s[1]) { - case '\0': case '/': + case '\0': + case '/': s += 2; continue; case '.': diff --git a/util.h b/util.h index 341ab04..285445e 100644 --- a/util.h +++ b/util.h @@ -24,7 +24,8 @@ __declspec(noreturn) #elif defined(__GNUC__) __attribute__((noreturn)) #endif -void fatal(const char *, ...); +void +fatal(const char *, ...); void *xmalloc(size_t); void *xreallocarray(void *, size_t, size_t); From 564bc312197dc4c46d2a5723dad53e3b932dcc99 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Fri, 16 May 2025 13:08:44 +0200 Subject: [PATCH 10/13] Resolve unused var errors + fix types for some ints --- os-win32.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/os-win32.c b/os-win32.c index 3c58d7e..b043fc9 100644 --- a/os-win32.c +++ b/os-win32.c @@ -257,9 +257,10 @@ osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, int osjob_wait(struct osjob_ctx *osctx, struct osjob ojobs[], size_t jobs_count, int timeout) { + (void)osctx; OVERLAPPED_ENTRY entries[64]; ULONG num_entries = 0; - const DWORD timeout_ms = timeout == -1 ? INFINITE : timeout; + const DWORD timeout_ms = timeout == -1 ? INFINITE : (DWORD)timeout; for (size_t i = 0; i < jobs_count; ++i) { struct osjob *job = ojobs + i; @@ -311,13 +312,13 @@ osjob_done(struct osjob_ctx *osctx, struct osjob *ojob, struct string *cmd) warn("wait process:"); goto err; } - int exit_code; + DWORD exit_code; if (!GetExitCodeProcess(ojob->hProcess, &exit_code)) { warn("WaitForSingleObject:"); goto err; } if (exit_code != 0) { - warn("job failed with status %d: %s", exit_code, cmd->s); + warn("job failed with status %llu: %s", exit_code, cmd->s); goto err; } return osjob_close(osctx, ojob); @@ -329,6 +330,7 @@ osjob_done(struct osjob_ctx *osctx, struct osjob *ojob, struct string *cmd) int osjob_close(struct osjob_ctx *osctx, struct osjob *ojob) { + (void)osctx; CloseHandle(ojob->hProcess); CloseHandle(ojob->output); memset(ojob, 0, sizeof(*ojob)); From 9c2fc1852f57fff236f828d86b33f7a9d9147b3c Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Fri, 16 May 2025 14:35:09 +0200 Subject: [PATCH 11/13] Change NamedPipe pattern --- os-win32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os-win32.c b/os-win32.c index b043fc9..ca4b23a 100644 --- a/os-win32.c +++ b/os-win32.c @@ -175,7 +175,7 @@ osjob_create(struct osjob_ctx *osctx, struct osjob *created, struct string *cmd, // Generate a unique pipe name static volatile long counter = 0; char pipeName[MAX_PATH]; - snprintf(pipeName, sizeof(pipeName), "\\\\.\\pipe\\ninjabuild-%ld-%lu", + snprintf(pipeName, sizeof(pipeName), "\\\\.\\pipe\\samu-%ld-%lu", InterlockedIncrement(&counter), GetCurrentProcessId()); SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE}; From ce3fa9f52a09adb7501d8ad76bda88b4899f02ce Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Tue, 27 May 2025 23:22:11 +0200 Subject: [PATCH 12/13] Fix variables escaping --- graph.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/graph.c b/graph.c index 4b283ae..b16d44a 100644 --- a/graph.c +++ b/graph.c @@ -86,6 +86,12 @@ nodestat(struct node *n) n->mtime = osmtime(n->path->s); } +#ifdef _WIN32 +const char _esc = '"'; +#else +const char _esc = '\''; +#endif + struct string * nodepath(struct node *n, bool escape) { @@ -101,22 +107,22 @@ nodepath(struct node *n, bool escape) for (s = n->path->s; *s; ++s) { if (!isalnum(*(unsigned char *)s) && !strchr("_+-./", *s)) escape = true; - if (*s == '\'') + if (*s == _esc) ++nquote; } if (escape) { n->shellpath = mkstr(n->path->n + 2 + 3 * nquote); d = n->shellpath->s; - *d++ = '\''; + *d++ = _esc; for (s = n->path->s; *s; ++s) { *d++ = *s; - if (*s == '\'') { + if (*s == _esc) { *d++ = '\\'; - *d++ = '\''; - *d++ = '\''; + *d++ = _esc; + *d++ = _esc; } } - *d++ = '\''; + *d++ = _esc; } else { n->shellpath = n->path; } From 9c91b0907d90ceefe6160e89b98cbab772a6c2b4 Mon Sep 17 00:00:00 2001 From: Alexej Doronin Date: Tue, 27 May 2025 23:35:50 +0200 Subject: [PATCH 13/13] Canonpath: handle windows path --- util.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/util.c b/util.c index 5479aa8..6ff2d34 100644 --- a/util.c +++ b/util.c @@ -152,7 +152,13 @@ delevalstr(void *ptr) void canonpath(struct string *path) { - // TODO: canon for win32 + +#ifdef _WIN32 +#define _samu_path_sep '\\' +#else +#define _samu_path_sep '/' +#endif + char *component[60]; int n; char *s, *d, *end; @@ -162,23 +168,23 @@ canonpath(struct string *path) s = d = path->s; end = path->s + path->n; n = 0; - if (*s == '/') { + if (*s == _samu_path_sep) { ++s; ++d; } while (s < end) { switch (s[0]) { - case '/': + case _samu_path_sep: ++s; continue; case '.': switch (s[1]) { case '\0': - case '/': + case _samu_path_sep: s += 2; continue; case '.': - if (s[2] != '/' && s[2] != '\0') + if (s[2] != _samu_path_sep && s[2] != '\0') break; if (n > 0) { d = component[--n]; @@ -194,7 +200,7 @@ canonpath(struct string *path) if (n == LEN(component)) fatal("path has too many components: %s", path->s); component[n++] = d; - while (*s != '/' && *s != '\0') + while (*s != _samu_path_sep && *s != '\0') *d++ = *s++; *d++ = *s++; }