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