LCOV - code coverage report
Current view: top level - cmdline - main.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 51 62 82.3 %
Date: 2025-10-28 11:59:11 Functions: 2 2 100.0 %

          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

Generated by: LCOV version 1.0