/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 | } |