Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/process_ops.c
Line
Count
Source
1
#include "process_ops.h"
2
#include <stdlib.h>
3
#include <string.h>
4
#include <stdio.h>
5
6
#ifndef __EMSCRIPTEN__
7
8
#include "ds/hashmap.h"
9
#include <unistd.h>
10
#include <sys/wait.h>
11
#include <errno.h>
12
13
/* Read all data from a file descriptor into a heap-allocated string.
14
 * Returns NULL on allocation failure. */
15
18
static char *read_all_fd(int fd) {
16
18
    size_t cap = 1024;
17
18
    size_t len = 0;
18
18
    char *buf = malloc(cap);
19
18
    if (!buf) return NULL;
20
21
24
    for (;;) {
22
24
        ssize_t n = read(fd, buf + len, cap - len);
23
24
        if (n < 0) {
24
0
            if (errno == EINTR) continue;
25
0
            break;
26
0
        }
27
24
        if (n == 0) break;
28
6
        len += (size_t)n;
29
6
        if (len == cap) {
30
0
            cap *= 2;
31
0
            char *tmp = realloc(buf, cap);
32
0
            if (!tmp) { free(buf); return NULL; }
33
0
            buf = tmp;
34
0
        }
35
6
    }
36
18
    buf[len] = '\0';
37
18
    return buf;
38
18
}
39
40
3
LatValue process_exec(const char *cmd, char **err) {
41
3
    FILE *fp = popen(cmd, "r");
42
3
    if (!fp) {
43
0
        char msg[256];
44
0
        snprintf(msg, sizeof(msg), "exec: failed to run command: %s", strerror(errno));
45
0
        *err = strdup(msg);
46
0
        return value_unit();
47
0
    }
48
49
3
    size_t cap = 1024;
50
3
    size_t len = 0;
51
3
    char *buf = malloc(cap);
52
3
    if (!buf) {
53
0
        pclose(fp);
54
0
        *err = strdup("exec: out of memory");
55
0
        return value_unit();
56
0
    }
57
58
6
    for (;;) {
59
6
        size_t n = fread(buf + len, 1, cap - len, fp);
60
6
        if (n == 0) break;
61
3
        len += n;
62
3
        if (len == cap) {
63
0
            cap *= 2;
64
0
            char *tmp = realloc(buf, cap);
65
0
            if (!tmp) { free(buf); pclose(fp); *err = strdup("exec: out of memory"); return value_unit(); }
66
0
            buf = tmp;
67
0
        }
68
3
    }
69
3
    buf[len] = '\0';
70
71
3
    int status = pclose(fp);
72
3
    int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
73
74
3
    if (exit_code != 0) {
75
0
        char msg[256];
76
0
        snprintf(msg, sizeof(msg), "exec: command failed with exit code %d", exit_code);
77
0
        free(buf);
78
0
        *err = strdup(msg);
79
0
        return value_unit();
80
0
    }
81
82
3
    return value_string_owned(buf);
83
3
}
84
85
9
LatValue process_shell(const char *cmd, char **err) {
86
9
    int stdout_pipe[2];
87
9
    int stderr_pipe[2];
88
89
9
    if (pipe(stdout_pipe) != 0) {
90
0
        *err = strdup("shell: failed to create stdout pipe");
91
0
        return value_unit();
92
0
    }
93
9
    if (pipe(stderr_pipe) != 0) {
94
0
        close(stdout_pipe[0]);
95
0
        close(stdout_pipe[1]);
96
0
        *err = strdup("shell: failed to create stderr pipe");
97
0
        return value_unit();
98
0
    }
99
100
9
    pid_t pid = fork();
101
9
    if (pid < 0) {
102
0
        close(stdout_pipe[0]); close(stdout_pipe[1]);
103
0
        close(stderr_pipe[0]); close(stderr_pipe[1]);
104
0
        *err = strdup("shell: fork failed");
105
0
        return value_unit();
106
0
    }
107
108
9
    if (pid == 0) {
109
        /* Child process */
110
0
        close(stdout_pipe[0]);
111
0
        close(stderr_pipe[0]);
112
0
        dup2(stdout_pipe[1], STDOUT_FILENO);
113
0
        dup2(stderr_pipe[1], STDERR_FILENO);
114
0
        close(stdout_pipe[1]);
115
0
        close(stderr_pipe[1]);
116
0
        execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
117
0
        _exit(127);
118
0
    }
119
120
    /* Parent process */
121
9
    close(stdout_pipe[1]);
122
9
    close(stderr_pipe[1]);
123
124
9
    char *stdout_buf = read_all_fd(stdout_pipe[0]);
125
9
    char *stderr_buf = read_all_fd(stderr_pipe[0]);
126
9
    close(stdout_pipe[0]);
127
9
    close(stderr_pipe[0]);
128
129
9
    int status = 0;
130
9
    waitpid(pid, &status, 0);
131
132
9
    if (!stdout_buf) stdout_buf = strdup("");
133
9
    if (!stderr_buf) stderr_buf = strdup("");
134
135
    /* Build result Map.
136
     * lat_map_set does a shallow memcpy, so the map takes ownership
137
     * of the inner data (strings). Do NOT value_free the locals. */
138
9
    LatValue map = value_map_new();
139
140
9
    LatValue exit_code_val = value_int(WIFEXITED(status) ? WEXITSTATUS(status) : -1);
141
9
    lat_map_set(map.as.map.map, "exit_code", &exit_code_val);
142
143
9
    LatValue stdout_val = value_string_owned(stdout_buf);
144
9
    lat_map_set(map.as.map.map, "stdout", &stdout_val);
145
146
9
    LatValue stderr_val = value_string_owned(stderr_buf);
147
9
    lat_map_set(map.as.map.map, "stderr", &stderr_val);
148
149
9
    return map;
150
9
}
151
152
3
char *process_cwd(char **err) {
153
3
    char *buf = getcwd(NULL, 0);
154
3
    if (!buf) {
155
0
        char msg[256];
156
0
        snprintf(msg, sizeof(msg), "cwd: %s", strerror(errno));
157
0
        *err = strdup(msg);
158
0
        return NULL;
159
0
    }
160
3
    return buf;
161
3
}
162
163
3
char *process_hostname(char **err) {
164
3
    char buf[256];
165
3
    if (gethostname(buf, sizeof(buf)) != 0) {
166
0
        *err = strdup("hostname: failed to get hostname");
167
0
        return NULL;
168
0
    }
169
3
    return strdup(buf);
170
3
}
171
172
3
int process_pid(void) {
173
3
    return (int)getpid();
174
3
}
175
176
#else /* __EMSCRIPTEN__ */
177
178
LatValue process_exec(const char *cmd, char **err) {
179
    (void)cmd;
180
    *err = strdup("exec: not available in browser");
181
    return value_unit();
182
}
183
184
LatValue process_shell(const char *cmd, char **err) {
185
    (void)cmd;
186
    *err = strdup("shell: not available in browser");
187
    return value_unit();
188
}
189
190
char *process_cwd(char **err) {
191
    *err = strdup("cwd: not available in browser");
192
    return NULL;
193
}
194
195
char *process_hostname(char **err) {
196
    *err = strdup("hostname: not available in browser");
197
    return NULL;
198
}
199
200
int process_pid(void) {
201
    return 0;
202
}
203
204
#endif /* __EMSCRIPTEN__ */
205
206
/* process_platform works on all targets via preprocessor */
207
6
const char *process_platform(void) {
208
#if defined(__EMSCRIPTEN__)
209
    return "wasm";
210
#elif defined(__APPLE__)
211
    return "macos";
212
#elif defined(__linux__)
213
    return "linux";
214
#elif defined(_WIN32)
215
    return "windows";
216
#else
217
    return "unknown";
218
#endif
219
6
}