LCOV - code coverage report
Current view: top level - cmdline - main.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 51 61 83.6 %
Date: 2026-04-29 15:04:44 Functions: 2 2 100.0 %

          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             : 

Generated by: LCOV version 1.0