MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include "watch.h"
2#include "messages.h"
3
4#include <uv.h>
5#include <stdio.h>
6#include <signal.h>
7#include <stdint.h>
8#include <stdlib.h>
9#include <string.h>
10#include <stdbool.h>
11#include <crprintf.h>
12
13#ifdef _WIN32
14#include <direct.h>
15#endif
16
17#ifndef SIGKILL
18#define SIGKILL SIGTERM
19#endif
20
21typedef struct watch_state {
22 uv_loop_t loop;
23 uv_fs_event_t fs_event;
24 uv_signal_t sigint;
25 uv_signal_t sigterm;
26 uv_timer_t kill_timer;
27 uv_process_t *child;
28 char **child_argv;
29 char *watch_path;
30 bool no_clear_screen;
31 bool stop_requested;
32 bool restart_pending;
33 bool child_running;
34 bool fs_event_inited;
35 bool sigint_inited;
36 bool sigterm_inited;
37 bool kill_timer_inited;
38 bool kill_timer_running;
39 int final_exit_code;
40} watch_state_t;
41
42static void watch_clear_screen(void) {
43 fputs("\033[3J\033[2J\033[H", stdout);
44 fflush(stdout);
45}
46
47char *ant_watch_resolve_path(const char *path) {
48#ifdef _WIN32
49 char *resolved = _fullpath(NULL, path, 0);
50 if (resolved) return resolved;
51 return _strdup(path);
52#else
53 char abs_path[PATH_MAX];
54 const char *resolved = realpath(path, abs_path);
55 return strdup(resolved ? resolved : path);
56#endif
57}
58
59int ant_watch_start(
60 uv_loop_t *loop,
61 uv_fs_event_t *event,
62 const char *path,
63 uv_fs_event_cb callback,
64 void *data,
65 unsigned int flags,
66 char **resolved_path_out
67) {
68 char *watch_path = NULL;
69 int rc = 0;
70
71 if (!loop || !event || !path || !callback) return UV_EINVAL;
72
73 watch_path = ant_watch_resolve_path(path);
74 if (!watch_path) return UV_ENOMEM;
75
76 rc = uv_fs_event_init(loop, event);
77 if (rc != 0) goto cleanup;
78
79 event->data = data;
80 rc = uv_fs_event_start(event, callback, watch_path, flags);
81 if (rc != 0) goto cleanup;
82
83 if (resolved_path_out) *resolved_path_out = watch_path;
84 else free(watch_path);
85 return 0;
86
87cleanup:
88 free(watch_path);
89 return rc;
90}
91
92void ant_watch_stop(uv_fs_event_t *event) {
93 if (!event) return;
94 uv_fs_event_stop(event);
95}
96
97static char **watch_build_child_argv(int argc, char **argv) {
98 char **child_argv = calloc((size_t)argc + 1, sizeof(char *));
99 if (!child_argv) return NULL;
100
101 int out = 0;
102 for (int i = 0; i < argc; i++) {
103 if (strcmp(argv[i], "--watch") == 0) continue;
104 if (strcmp(argv[i], "--no-clear-screen") == 0) continue;
105 child_argv[out++] = argv[i];
106 }
107
108 child_argv[out] = NULL;
109 if (out == 0) {
110 free(child_argv);
111 return NULL;
112 }
113
114 return child_argv;
115}
116
117static void watch_stop_kill_timer(watch_state_t *state) {
118 if (!state->kill_timer_inited || !state->kill_timer_running) return;
119 uv_timer_stop(&state->kill_timer);
120 state->kill_timer_running = false;
121}
122
123static void watch_on_child_closed(uv_handle_t *handle) { free(handle); }
124static void watch_request_shutdown(watch_state_t *state, int exit_code);
125static void watch_try_spawn_child(watch_state_t *state);
126
127static void watch_on_kill_timer(uv_timer_t *timer) {
128 watch_state_t *state = (watch_state_t *)timer->data;
129 state->kill_timer_running = false;
130
131 if (state->child_running && state->child)
132 uv_process_kill(state->child, SIGKILL);
133 if (state->stop_requested && !uv_is_closing((uv_handle_t *)&state->kill_timer))
134 uv_close((uv_handle_t *)&state->kill_timer, NULL);
135}
136
137static void watch_on_child_exit(uv_process_t *proc, int64_t exit_status, int term_signal) {
138 watch_state_t *state = (watch_state_t *)proc->data;
139 state->child_running = false;
140 state->child = NULL;
141 watch_stop_kill_timer(state);
142
143 if (state->stop_requested) {
144 if (state->kill_timer_inited && !uv_is_closing((uv_handle_t *)&state->kill_timer))
145 uv_close((uv_handle_t *)&state->kill_timer, NULL);
146 } else if (state->restart_pending) {
147 watch_try_spawn_child(state);
148 } else if (term_signal > 0) {
149 state->final_exit_code = 128 + term_signal;
150 } else state->final_exit_code = (int)exit_status;
151
152 uv_close((uv_handle_t *)proc, watch_on_child_closed);
153}
154
155static int watch_spawn_child(watch_state_t *state) {
156 uv_process_t *proc = calloc(1, sizeof(*proc));
157 if (!proc) return UV_ENOMEM;
158
159 uv_stdio_container_t stdio[3];
160 stdio[0].flags = UV_INHERIT_FD; stdio[0].data.fd = 0;
161 stdio[1].flags = UV_INHERIT_FD; stdio[1].data.fd = 1;
162 stdio[2].flags = UV_INHERIT_FD; stdio[2].data.fd = 2;
163
164 uv_process_options_t options = {0};
165 options.file = state->child_argv[0];
166 options.args = state->child_argv;
167 options.exit_cb = watch_on_child_exit;
168 options.stdio_count = 3;
169 options.stdio = stdio;
170
171 int rc = uv_spawn(&state->loop, proc, &options);
172 if (rc != 0) {
173 free(proc);
174 return rc;
175 }
176
177 proc->data = state;
178 state->child = proc;
179 state->child_running = true;
180 state->restart_pending = false;
181 return 0;
182}
183
184static void watch_try_spawn_child(watch_state_t *state) {
185 if (state->stop_requested) return;
186 if (!state->no_clear_screen && state->restart_pending) watch_clear_screen();
187 int rc = watch_spawn_child(state);
188
189 if (rc != 0) {
190 crfprintf(stderr, msg.watch_spawn_failed, uv_strerror(rc));
191 watch_request_shutdown(state, EXIT_FAILURE);
192 }
193}
194
195static void watch_start_kill_timer(watch_state_t *state, uint64_t timeout_ms) {
196 if (!state->kill_timer_inited || state->kill_timer_running) return;
197 int rc = uv_timer_start(&state->kill_timer, watch_on_kill_timer, timeout_ms, 0);
198 if (rc == 0) state->kill_timer_running = true;
199}
200
201static void watch_close_signals_and_watcher(watch_state_t *state) {
202 if (state->fs_event_inited && !uv_is_closing((uv_handle_t *)&state->fs_event)) {
203 uv_fs_event_stop(&state->fs_event);
204 uv_close((uv_handle_t *)&state->fs_event, NULL);
205 }
206
207 if (state->sigint_inited && !uv_is_closing((uv_handle_t *)&state->sigint)) {
208 uv_signal_stop(&state->sigint);
209 uv_close((uv_handle_t *)&state->sigint, NULL);
210 }
211
212 if (state->sigterm_inited && !uv_is_closing((uv_handle_t *)&state->sigterm)) {
213 uv_signal_stop(&state->sigterm);
214 uv_close((uv_handle_t *)&state->sigterm, NULL);
215 }
216}
217
218static void watch_request_shutdown(watch_state_t *state, int exit_code) {
219 if (state->stop_requested) return;
220
221 state->stop_requested = true;
222 state->restart_pending = false;
223 state->final_exit_code = exit_code;
224
225 watch_close_signals_and_watcher(state);
226
227 if (state->child_running && state->child) {
228 int rc = uv_process_kill(state->child, SIGTERM);
229 if (rc != 0 && rc != UV_ESRCH) {
230 crfprintf(stderr, msg.watch_graceful_term, uv_strerror(rc));
231 }
232 watch_start_kill_timer(state, 1000);
233 } else if (state->kill_timer_inited && !uv_is_closing((uv_handle_t *)&state->kill_timer)) {
234 uv_close((uv_handle_t *)&state->kill_timer, NULL);
235 }
236}
237
238static void watch_request_restart(watch_state_t *state) {
239 if (state->stop_requested) return;
240 if (!state->restart_pending) {}
241 state->restart_pending = true;
242
243 if (state->child_running && state->child) {
244 int rc = uv_process_kill(state->child, SIGTERM);
245 if (rc != 0 && rc != UV_ESRCH) {
246 crfprintf(stderr, msg.watch_child_error, uv_strerror(rc));
247 watch_request_shutdown(state, EXIT_FAILURE);
248 return;
249 }
250 watch_start_kill_timer(state, 1000);
251 } else watch_try_spawn_child(state);
252}
253
254static void watch_on_fs_event(uv_fs_event_t *handle, const char *filename, int events, int status) {
255 watch_state_t *state = (watch_state_t *)handle->data;
256
257 if (status < 0) {
258 crfprintf(stderr, msg.watch_warn_normal, state->watch_path, uv_strerror(status));
259 return;
260 }
261
262 if ((events & (UV_CHANGE | UV_RENAME)) == 0) return;
263 watch_request_restart(state);
264}
265
266static void watch_on_signal(uv_signal_t *handle, int signum) {
267 watch_state_t *state = (watch_state_t *)handle->data;
268 watch_request_shutdown(state, 128 + signum);
269}
270
271int ant_watch_run(int argc, char **argv, const char *entry_file, bool no_clear_screen) {
272 if (!entry_file || !*entry_file) {
273 crfprintf(stderr, msg.watch_entrypoint_missing);
274 return EXIT_FAILURE;
275 }
276
277 watch_state_t state = {0};
278 state.child_argv = watch_build_child_argv(argc, argv);
279 state.watch_path = ant_watch_resolve_path(entry_file);
280 state.no_clear_screen = no_clear_screen;
281 state.final_exit_code = EXIT_SUCCESS;
282
283 if (!state.child_argv || !state.watch_path) {
284 free(state.child_argv);
285 free(state.watch_path);
286 crfprintf(stderr, msg.watch_start_fatal);
287 return EXIT_FAILURE;
288 }
289
290 int rc = uv_loop_init(&state.loop);
291 if (rc != 0) {
292 crfprintf(stderr, msg.watch_loop_fatal, uv_strerror(rc));
293 free(state.child_argv); free(state.watch_path);
294 return EXIT_FAILURE;
295 }
296
297 rc = ant_watch_start(
298 &state.loop,
299 &state.fs_event,
300 state.watch_path,
301 watch_on_fs_event,
302 &state, 0, NULL
303 );
304
305 if (rc == 0) state.fs_event_inited = true;
306
307 if (rc != 0) {
308 crfprintf(stderr, msg.watch_file_failed, state.watch_path, uv_strerror(rc));
309 watch_request_shutdown(&state, EXIT_FAILURE);
310 }
311
312 if (rc == 0) {
313 rc = uv_signal_init(&state.loop, &state.sigint);
314 if (rc == 0) state.sigint_inited = true;
315 if (rc == 0) {
316 state.sigint.data = &state;
317 rc = uv_signal_start(&state.sigint, watch_on_signal, SIGINT);
318 }
319 }
320
321 if (rc == 0) {
322 rc = uv_signal_init(&state.loop, &state.sigterm);
323 if (rc == 0) state.sigterm_inited = true;
324 if (rc == 0) {
325 state.sigterm.data = &state;
326 int sigterm_rc = uv_signal_start(&state.sigterm, watch_on_signal, SIGTERM);
327 if (sigterm_rc != 0 && sigterm_rc != UV_ENOSYS && sigterm_rc != UV_EINVAL) {
328 rc = sigterm_rc;
329 } else if (sigterm_rc != 0) {
330 uv_signal_stop(&state.sigterm);
331 uv_close((uv_handle_t *)&state.sigterm, NULL);
332 state.sigterm_inited = false;
333 }
334 }
335 }
336
337 if (rc == 0) {
338 rc = uv_timer_init(&state.loop, &state.kill_timer);
339 if (rc == 0) {
340 state.kill_timer_inited = true;
341 state.kill_timer.data = &state;
342 }
343 }
344
345 if (rc != 0) {
346 crfprintf(stderr, msg.watch_loop_handles_fatal, uv_strerror(rc));
347 watch_request_shutdown(&state, EXIT_FAILURE);
348 }
349
350 if (!state.stop_requested) watch_try_spawn_child(&state);
351 while (uv_run(&state.loop, UV_RUN_DEFAULT) != 0) {}
352
353 if (state.fs_event_inited && !uv_is_closing((uv_handle_t *)&state.fs_event)) {
354 uv_close((uv_handle_t *)&state.fs_event, NULL);
355 }
356 if (state.sigint_inited && !uv_is_closing((uv_handle_t *)&state.sigint)) {
357 uv_close((uv_handle_t *)&state.sigint, NULL);
358 }
359 if (state.sigterm_inited && !uv_is_closing((uv_handle_t *)&state.sigterm)) {
360 uv_close((uv_handle_t *)&state.sigterm, NULL);
361 }
362 if (state.kill_timer_inited && !uv_is_closing((uv_handle_t *)&state.kill_timer)) {
363 uv_close((uv_handle_t *)&state.kill_timer, NULL);
364 }
365 while (uv_run(&state.loop, UV_RUN_DEFAULT) != 0) {}
366
367 rc = uv_loop_close(&state.loop);
368 if (rc != 0) crfprintf(stderr, msg.watch_loop_cleanup, uv_strerror(rc));
369
370 free(state.child_argv);
371 free(state.watch_path);
372 return state.final_exit_code;
373}