Line data Source code
1 : // SPDX-License-Identifier: GPL-3.0-or-later
2 : // Copyright (C) 2025 Andrea Mazzoleni
3 :
4 : #include "portable.h"
5 :
6 : #include "snapraid.h"
7 :
8 : #define MODE_DEFAULT 0
9 : #define MODE_SPINDOWN 1
10 :
11 : #ifdef _WIN32
12 : #define COMMAND_LINE_MAX 32767
13 :
14 : static int needs_quote(const WCHAR* arg)
15 : {
16 : while (*arg) {
17 : if (*arg == L' ' || *arg == L'\t' || *arg == L'"')
18 : return 1;
19 : ++arg;
20 : }
21 :
22 : return 0;
23 : }
24 :
25 : #define charcat(c) \
26 : do { \
27 : if (pos + 1 >= size) { \
28 : return -1; \
29 : } \
30 : cmd[pos++] = (c); \
31 : } while (0)
32 :
33 : static int argcat(WCHAR* cmd, int size, int pos, const WCHAR* arg)
34 : {
35 : int has_quote;
36 :
37 : /* space separator */
38 : if (pos != 0)
39 : charcat(L' ');
40 :
41 : has_quote = needs_quote(arg);
42 :
43 : if (!has_quote) {
44 : while (*arg)
45 : charcat(*arg++);
46 : } else {
47 : /* starting quote */
48 : charcat(L'"');
49 :
50 : while (*arg) {
51 : int bl = 0;
52 : while (*arg == L'\\') {
53 : ++arg;
54 : ++bl;
55 : }
56 :
57 : if (*arg == 0) {
58 : /* double backslashes before closing quote */
59 : bl = bl * 2;
60 : while (bl--)
61 : charcat(L'\\');
62 : } else if (*arg == '"') {
63 : /* double backslashes + escape the quote */
64 : bl = bl * 2 + 1;
65 : while (bl--)
66 : charcat(L'\\');
67 : charcat(L'"');
68 : ++arg;
69 : } else {
70 : /* normal backslashes */
71 : while (bl--)
72 : charcat(L'\\');
73 : charcat(*arg);
74 : ++arg;
75 : }
76 : }
77 :
78 : /* ending quote */
79 : charcat(L'"');
80 : }
81 :
82 : return pos;
83 : }
84 :
85 : static char* argutf8(const WCHAR* arg)
86 : {
87 : size_t len = wcslen(arg);
88 : char* utf8_arg;
89 : DWORD utf8_len;
90 : DWORD res;
91 :
92 : utf8_len = WideCharToMultiByte(CP_UTF8, 0, arg, len + 1 /* include final null */, 0, 0, 0, 0);
93 :
94 : utf8_arg = malloc(utf8_len);
95 : if (!utf8_arg)
96 : return 0;
97 :
98 : res = WideCharToMultiByte(CP_UTF8, 0, arg, len + 1 /* include final null */, utf8_arg, utf8_len, 0, 0);
99 : if (res != utf8_len)
100 : return 0;
101 :
102 : return utf8_arg;
103 : }
104 :
105 : /* Global variable to store child process handle */
106 : static HANDLE child_process = NULL;
107 :
108 : /* Console control handler - forwards Ctrl+C, Ctrl+Break to child */
109 : static BOOL WINAPI console_handler(DWORD ctrl_type)
110 : {
111 : /* if no child, default behavior */
112 : if (child_process == NULL)
113 : return FALSE;
114 :
115 : switch (ctrl_type) {
116 : case CTRL_C_EVENT :
117 : case CTRL_BREAK_EVENT :
118 : /*
119 : * Return TRUE to prevent parent termination. The child process
120 : * will receive these events automatically because it's attached
121 : * to the same console, so we don't need to forward them.
122 : */
123 : return TRUE; /* signal handled, don't terminate parent */
124 : case CTRL_CLOSE_EVENT :
125 : case CTRL_LOGOFF_EVENT :
126 : case CTRL_SHUTDOWN_EVENT :
127 : /*
128 : * Return TRUE to prevent our termination while child handles shutdown.
129 : * The child receives these events automatically (same console).
130 : * Note: Windows will forcibly kill us after a timeout regardless
131 : * of returning TRUE: ~5 seconds for CLOSE_EVENT and LOGOFF_EVENT,
132 : * ~5-20 seconds for SHUTDOWN_EVENT (configurable in registry).
133 : */
134 : return TRUE; /* signal handled, but Windows will kill us after timeout */
135 : default :
136 : return FALSE;
137 : }
138 : }
139 :
140 : int main(int argc, char* argv[])
141 : {
142 : int wide_argc;
143 : WCHAR** wide_argv;
144 : int utf8_argc;
145 : char** utf8_argv;
146 : WCHAR app_buffer[COMMAND_LINE_MAX];
147 : WCHAR cmd_buffer[COMMAND_LINE_MAX];
148 : DWORD res;
149 : int i;
150 : int pos;
151 : int mode;
152 : int ret;
153 :
154 : (void)argc;
155 : (void)argv;
156 :
157 : res = GetModuleFileNameW(NULL, app_buffer, sizeof(app_buffer));
158 : if (res == 0 || res >= sizeof(app_buffer)) {
159 : exit(EXIT_FAILURE);
160 : }
161 :
162 : wide_argv = CommandLineToArgvW(GetCommandLineW(), &wide_argc);
163 : if (!wide_argv) {
164 : exit(EXIT_FAILURE);
165 : }
166 :
167 : utf8_argc = 0;
168 : utf8_argv = malloc((wide_argc + 1) * sizeof(void*));
169 : if (!utf8_argv) {
170 : exit(EXIT_FAILURE);
171 : }
172 :
173 : pos = argcat(cmd_buffer, sizeof(cmd_buffer), 0, app_buffer);
174 : if (pos < 0) {
175 : exit(EXIT_FAILURE);
176 : }
177 :
178 : utf8_argv[0] = argutf8(app_buffer);
179 : if (!utf8_argv[0]) {
180 : exit(EXIT_FAILURE);
181 : }
182 : utf8_argc = 1;
183 :
184 : mode = MODE_DEFAULT;
185 : for (i = 1; i < wide_argc; i++) {
186 : if (wcscmp(wide_argv[i], L"-s") == 0 || wcscmp(wide_argv[i], L"--spin-down-on-error") == 0) {
187 : mode = MODE_SPINDOWN;
188 : } else {
189 : pos = argcat(cmd_buffer, sizeof(cmd_buffer), pos, wide_argv[i]);
190 : if (pos < 0) {
191 : exit(EXIT_FAILURE);
192 : }
193 : utf8_argv[utf8_argc] = argutf8(wide_argv[i]);
194 : if (!utf8_argv[utf8_argc]) {
195 : exit(EXIT_FAILURE);
196 : }
197 : ++utf8_argc;
198 : }
199 : }
200 : utf8_argv[utf8_argc] = 0;
201 : cmd_buffer[pos] = 0;
202 :
203 : LocalFree(wide_argv);
204 :
205 : if (mode == MODE_DEFAULT) {
206 : ret = snapraid_main(utf8_argc, utf8_argv);
207 : } else {
208 : STARTUPINFOW si;
209 : PROCESS_INFORMATION pi;
210 : DWORD wait;
211 :
212 : /* install console control handler */
213 : if (!SetConsoleCtrlHandler(console_handler, TRUE)) {
214 : fprintf(stderr, "Failed to set console handler\n");
215 : exit(EXIT_FAILURE);
216 : }
217 :
218 : ZeroMemory(&si, sizeof(si));
219 : si.cb = sizeof(si);
220 : ZeroMemory(&pi, sizeof(pi));
221 :
222 : if (!CreateProcessW(app_buffer, cmd_buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
223 : fprintf(stderr, "Failed to exec SnapRAID\n");
224 : exit(EXIT_FAILURE);
225 : }
226 :
227 : /* store child process handle for signal handler */
228 : child_process = pi.hProcess;
229 :
230 : wait = WaitForSingleObject(pi.hProcess, INFINITE);
231 : if (wait != WAIT_OBJECT_0) {
232 : fprintf(stderr, "WaitForSingleObject failed: %lu\n", (unsigned long)GetLastError());
233 : TerminateProcess(pi.hProcess, 1);
234 : CloseHandle(pi.hThread);
235 : CloseHandle(pi.hProcess);
236 : return 1;
237 : }
238 :
239 : /* clear child process handle */
240 : child_process = NULL;
241 :
242 : if (!GetExitCodeProcess(pi.hProcess, &res)) {
243 : fprintf(stderr, "GetExitCodeProcess failed: %lu\n", (unsigned long)GetLastError());
244 : CloseHandle(pi.hThread);
245 : CloseHandle(pi.hProcess);
246 : return 1;
247 : }
248 :
249 : CloseHandle(pi.hThread);
250 : CloseHandle(pi.hProcess);
251 :
252 : ret = res;
253 :
254 : if (ret != 0) {
255 : char* spindown_argv[3];
256 : spindown_argv[0] = utf8_argv[0];
257 : spindown_argv[1] = "down";
258 : spindown_argv[2] = 0;
259 :
260 : /* sync all disks */
261 : printf("Flush...\n");
262 : if (sync() != 0)
263 : printf("WARNING! Failed to flush disks!\n");
264 :
265 : snapraid_main(2, spindown_argv);
266 : }
267 : }
268 :
269 : for (i = 0; i < utf8_argc; ++i)
270 : free(utf8_argv[i]);
271 : free(utf8_argv);
272 :
273 : return ret;
274 : }
275 : #else
276 :
277 : char full_argv0[PATH_MAX];
278 :
279 1 : const char* get_argv0(const char* argv0)
280 : {
281 1 : ssize_t len = readlink("/proc/self/exe", full_argv0, sizeof(full_argv0) - 1);
282 1 : if (len != -1) {
283 1 : full_argv0[len] = '\0';
284 1 : return full_argv0;
285 : } else {
286 : #ifdef __APPLE__
287 : uint32_t size = sizeof(full_argv0);
288 : if (_NSGetExecutablePath(full_argv0, &size) == 0)
289 : return full_argv0;
290 : #endif
291 : }
292 0 : return argv0;
293 : }
294 :
295 : /* global variable to store child PID for signal handler */
296 : static volatile pid_t child_pid = 0;
297 :
298 : /* signal handler that forwards signals to child */
299 : /* LCOV_EXCL_START */
300 : static void forward_signal(int sig)
301 : {
302 : if (child_pid > 0) {
303 : kill(child_pid, sig);
304 : }
305 : }
306 : /* LCOV_EXCL_STOP */
307 :
308 328 : int main(int argc, char* argv[])
309 : {
310 : int mode;
311 : int i, j;
312 : int ret;
313 :
314 328 : mode = MODE_DEFAULT;
315 328 : j = 1;
316 4752 : for (i = 1; i < argc; ++i) {
317 4424 : if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--spin-down-on-error") == 0) {
318 1 : mode = MODE_SPINDOWN;
319 : } else {
320 4423 : argv[j] = argv[i];
321 4423 : ++j;
322 : }
323 : }
324 328 : argc = j;
325 328 : argv[argc] = 0;
326 :
327 328 : if (mode == MODE_DEFAULT) {
328 327 : ret = snapraid_main(argc, argv);
329 : } else {
330 : struct sigaction sa;
331 :
332 : /* set up signal handler to ignore signals */
333 1 : sa.sa_handler = forward_signal;
334 1 : sigemptyset(&sa.sa_mask);
335 1 : sa.sa_flags = SA_RESTART; /* restart interrupted system calls */
336 :
337 : /* install forwarding handler for signals */
338 1 : sigaction(SIGINT, &sa, NULL); /* Ctrl+C */
339 1 : sigaction(SIGQUIT, &sa, NULL); /* Ctrl+\ */
340 1 : sigaction(SIGTERM, &sa, NULL); /* termination */
341 1 : sigaction(SIGHUP, &sa, NULL); /* hangup */
342 1 : sigaction(SIGUSR1, &sa, NULL); /* user-defined 1 */
343 1 : sigaction(SIGUSR2, &sa, NULL); /* user-defined 2 */
344 :
345 1 : pid_t pid = fork();
346 2 : if (pid == -1) {
347 : /* LCOV_EXCL_START */
348 : perror("Failed to fork SnapRAID");
349 : exit(EXIT_FAILURE);
350 : /* LCOV_EXCL_STOP */
351 : }
352 :
353 2 : if (pid == 0) {
354 : /* child process */
355 :
356 : /* restore default signal handlers */
357 1 : signal(SIGINT, SIG_DFL);
358 1 : signal(SIGQUIT, SIG_DFL);
359 1 : signal(SIGTERM, SIG_DFL);
360 1 : signal(SIGHUP, SIG_DFL);
361 1 : signal(SIGUSR1, SIG_DFL);
362 1 : signal(SIGUSR2, SIG_DFL);
363 :
364 1 : execvp(get_argv0(argv[0]), argv);
365 :
366 : /* here it's an error */
367 : /* LCOV_EXCL_START */
368 : perror("Failed to exec SnapRAID");
369 : exit(EXIT_FAILURE);
370 : /* LCOV_EXCL_STOP */
371 : } else {
372 : /* parent process */
373 : int status;
374 :
375 : /* store child PID so signal handler can forward signals */
376 1 : child_pid = pid;
377 :
378 : do {
379 1 : ret = waitpid(pid, &status, 0);
380 1 : } while (ret == -1 && errno == EINTR); /* retry if interrupted by signal */
381 :
382 1 : if (ret == -1) {
383 : /* LCOV_EXCL_START */
384 : perror("Failed to wait for SnapRAID");
385 : exit(EXIT_FAILURE);
386 : /* LCOV_EXCL_STOP */
387 : }
388 :
389 : /* clear child PID */
390 1 : child_pid = 0;
391 :
392 : /* restore default signal handlers */
393 1 : signal(SIGINT, SIG_DFL);
394 1 : signal(SIGQUIT, SIG_DFL);
395 1 : signal(SIGTERM, SIG_DFL);
396 1 : signal(SIGHUP, SIG_DFL);
397 1 : signal(SIGUSR1, SIG_DFL);
398 1 : signal(SIGUSR2, SIG_DFL);
399 :
400 1 : if (WIFEXITED(status)) {
401 1 : ret = WEXITSTATUS(status);
402 0 : } else if (WIFSIGNALED(status)) {
403 : /* terminated by signal */
404 0 : ret = 128 + WTERMSIG(status);
405 : } else {
406 0 : ret = -1;
407 : }
408 :
409 1 : if (ret != 0) {
410 : char* spindown_argv[3];
411 0 : spindown_argv[0] = argv[0];
412 0 : spindown_argv[1] = "down";
413 0 : spindown_argv[2] = 0;
414 :
415 : /* ignore sigpipe to allow spindown even if the terminal is closed */
416 0 : signal(SIGPIPE, SIG_IGN);
417 :
418 : /* sync all disks */
419 0 : printf("Flush...\n");
420 :
421 0 : snapraid_main(2, spindown_argv);
422 : }
423 : }
424 : }
425 :
426 280 : return ret;
427 : }
428 : #endif
429 :
|