+
+ 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
+
+ |
+
+