From bb2b84d21ae14598264b5136af4b052e4174501b Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 26 Sep 2025 09:49:20 +0200 Subject: [PATCH] Add a new -s, --spin-down-on-error option Also handle correctly chars outside the Windows codepage as arguments. --- Makefile.am | 1 + cmdline/main.c | 277 +++++++++++++++++++++++++++++++++++++++++++++ cmdline/snapraid.c | 9 +- cmdline/snapraid.h | 1 + snapraid.1 | 17 ++- snapraid.d | 17 ++- 6 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 cmdline/main.c diff --git a/Makefile.am b/Makefile.am index b073a855..a3fb237b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,6 +23,7 @@ snapraid_SOURCES = \ raid/test.c \ raid/tag.c \ tommyds/tommy.c \ + cmdline/main.c \ cmdline/snapraid.c \ cmdline/io.c \ cmdline/bw.c \ diff --git a/cmdline/main.c b/cmdline/main.c new file mode 100644 index 00000000..b3232487 --- /dev/null +++ b/cmdline/main.c @@ -0,0 +1,277 @@ + +/* + * Copyright (C) 2025 Andrea Mazzoleni + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "portable.h" + +#include "snapraid.h" + +#define MODE_DEFAULT 0 +#define MODE_SPINDOWN 1 + +#ifdef _WIN32 +#define COMMAND_LINE_MAX 32767 + +static int needs_quote(const WCHAR* arg) +{ + while (*arg) { + if (*arg == L' ' || *arg == L'\t' || *arg == L'"') + return 1; + ++arg; + } + + return 0; +} + +#define charcat(c) \ + do { \ + if (pos + 1 >= size) \ + return -1; \ + cmd[pos++] = (c); \ + } while (0) + +static int argcat(WCHAR* cmd, int size, int pos, const WCHAR* arg) +{ + /* space separator */ + if (pos != 0) + charcat(L' '); + + if (!needs_quote(arg)) { + while (*arg) + charcat(*arg++); + return pos; + } + + charcat(L'"'); + + while (*arg) { + int bl = 0; + while (*arg == L'\\') { + ++arg; + ++bl; + } + + if (*arg == 0) { + bl = bl * 2; + while (bl--) + charcat(L'\\'); + } else if (*arg == '"') { + bl = bl * 2 + 1; + while (bl--) + charcat(L'\\'); + charcat(L'"'); + ++arg; + } else { + while (bl--) + charcat(L'\\'); + charcat(*arg); + ++arg; + } + } + + /* ending quote */ + charcat(L'"'); + + return pos; +} + +static char* argutf8(const WCHAR* arg) +{ + size_t len = wcslen(arg); + char* utf8_arg; + DWORD utf8_len; + DWORD res; + + utf8_len = WideCharToMultiByte(CP_UTF8, 0, arg, len + 1 /* include final null */, 0, 0, 0, 0); + + utf8_arg = malloc(utf8_len); + if (!utf8_arg) + return 0; + + res = WideCharToMultiByte(CP_UTF8, 0, arg, len + 1 /* include final null */, utf8_arg, utf8_len, 0, 0); + if (res != utf8_len) + return 0; + + return utf8_arg; +} + +int main(int argc, char* argv[]) +{ + int wide_argc; + WCHAR** wide_argv; + int utf8_argc; + char** utf8_argv; + WCHAR app_buffer[COMMAND_LINE_MAX]; + WCHAR cmd_buffer[COMMAND_LINE_MAX]; + DWORD res; + int i; + int pos; + int mode; + int ret; + + (void)argc; + (void)argv; + + res = GetModuleFileNameW(NULL, app_buffer, sizeof(app_buffer)); + if (res == 0 || res >= sizeof(app_buffer)) { + exit(EXIT_FAILURE); + } + + wide_argv = CommandLineToArgvW(GetCommandLineW(), &wide_argc); + if (!wide_argv) { + exit(EXIT_FAILURE); + } + + utf8_argc = 0; + utf8_argv = malloc((wide_argc + 1) * sizeof(void*)); + if (!utf8_argv) { + exit(EXIT_FAILURE); + } + + pos = argcat(cmd_buffer, sizeof(cmd_buffer), 0, app_buffer); + if (pos < 0) { + exit(EXIT_FAILURE); + } + + utf8_argv[0] = argutf8(app_buffer); + if (!utf8_argv[0]) { + exit(EXIT_FAILURE); + } + utf8_argc = 1; + + mode = MODE_DEFAULT; + for (i = 1; i < wide_argc; i++) { + if (wcscmp(wide_argv[i], L"-s") == 0 || wcscmp(wide_argv[i], L"--spin-down-on-error") == 0) { + mode = MODE_SPINDOWN; + } else { + pos = argcat(cmd_buffer, sizeof(cmd_buffer), 0, wide_argv[i]); + if (pos < 0) { + exit(EXIT_FAILURE); + } + utf8_argv[utf8_argc] = argutf8(wide_argv[i]); + if (!utf8_argv[utf8_argc]) { + exit(EXIT_FAILURE); + } + ++utf8_argc; + } + } + utf8_argv[utf8_argc] = 0; + cmd_buffer[pos] = 0; + + LocalFree(wide_argv); + + if (mode == MODE_DEFAULT) { + ret = snapraid_main(utf8_argc, utf8_argv); + } else { + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + if (!CreateProcessW(app_buffer, cmd_buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { + exit(EXIT_FAILURE); + } + + WaitForSingleObject(pi.hProcess, INFINITE); + + GetExitCodeProcess(pi.hProcess, &res); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + ret = res; + + if (ret != 0) { + char* spindown_argv[3]; + spindown_argv[0] = utf8_argv[0]; + spindown_argv[1] = "down"; + spindown_argv[2] = 0; + snapraid_main(2, spindown_argv); + } + } + + for(i = 0;i < utf8_argc; ++i) + free(utf8_argv[i]); + free(utf8_argv); + + return ret; +} +#else +int main(int argc, char* argv[]) +{ + int mode; + int i, j; + int ret; + + mode = MODE_DEFAULT; + j = 1; + for (i = 1; i < argc; ++i) { + if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--spin-down-on-error") == 0 ) { + mode = MODE_SPINDOWN; + } else { + argv[j] = argv[i]; + ++j; + } + } + argc = j; + argv[argc] = 0; + + if (mode == MODE_DEFAULT) { + ret = snapraid_main(argc, argv); + } else { + pid_t pid = fork(); + if (pid == -1) { + exit(EXIT_FAILURE); + } + + if (pid == 0) { + execv(argv[0], argv); + + /* here it's an error */ + exit(EXIT_FAILURE); + } else { + int status; + + /* parent process - wait for child */ + if (waitpid(pid, &status, 0) == -1) { + exit(EXIT_FAILURE); + } + + if (WIFEXITED(status)) { + ret = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + /* terminated by signal */ + ret = 128 + WTERMSIG(status); + } else { + ret = -1; + } + + if (ret != 0) { + char* spindown_argv[3]; + spindown_argv[0] = argv[0]; + spindown_argv[1] = "down"; + spindown_argv[2] = 0; + snapraid_main(2, spindown_argv); + } + } + } + + return ret; +} +#endif diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index 54790b8c..4ddd0268 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -340,7 +340,7 @@ struct option long_options[] = { { "speed-test", 0, 0, 'T' }, /* undocumented speed test command */ { "gen-conf", 1, 0, 'C' }, { "verbose", 0, 0, 'v' }, - { "quiet", 0, 0, 'q' }, /* undocumented quiet option */ + { "quiet", 0, 0, 'q' }, { "gui", 0, 0, 'G' }, /* undocumented GUI interface option */ { "help", 0, 0, 'H' }, { "version", 0, 0, 'V' }, @@ -486,6 +486,11 @@ struct option long_options[] = { }; #endif +/* + * Free letters: AgIjJkKMnPQrtuxXWz + * + * The 's' letter is used in main.c + */ #define OPTIONS "c:f:d:mebp:o:S:B:L:i:l:ZEUDNFRahTC:vqHVGw:" volatile int global_interrupt = 0; @@ -540,7 +545,7 @@ void signal_init(void) #define OPERATION_SMART 17 #define OPERATION_PROBE 18 -int main(int argc, char* argv[]) +int snapraid_main(int argc, char* argv[]) { int c; struct snapraid_option opt; diff --git a/cmdline/snapraid.h b/cmdline/snapraid.h index e12cbcb1..0a91b202 100644 --- a/cmdline/snapraid.h +++ b/cmdline/snapraid.h @@ -23,6 +23,7 @@ void speed(int period); void selftest(void); +int snapraid_main(int argc, char* argv[]); #endif diff --git a/snapraid.1 b/snapraid.1 index c5595227..84201ed4 100644 --- a/snapraid.1 +++ b/snapraid.1 @@ -26,7 +26,7 @@ snapraid [\-c, \-\-conf CONFIG] .PD 0 .PP .PD - [\-w, \-\-bw\-limit RATE] + [\-s, \-\-spin\-down\-on\-error] [\-w, \-\-bw\-limit RATE] .PD 0 .PP .PD @@ -740,7 +740,12 @@ Spins down all the disks of the array. This command uses the \[dq]smartctl\[dq] tool, and it\'s equivalent to run \[dq]smartctl \-s standby,now\[dq] on all the devices. .PP -You can spin\-down only some specific disks using the \-d, \-\-filter\-disk option. +You can spin\-down only some specific disks using the \-d, \-\-filter\-disk +option. +.PP +To automatically spin down on error, you can use the option +\-s, \-\-spin\-down\-on\-error with any other command, which is equivalent +to running \[dq]down\[dq] manually when an error occurs. .PP Nothing is modified. .SS diff @@ -1079,6 +1084,14 @@ The files are read also in sub\-directories and they are identified regardless of their name. This option can be used only with \[dq]check\[dq] and \[dq]fix\[dq]. .TP +.B \-s, \-\-spin\-down\-on\-error +On any error, spin down all managed disks before exiting with +a non\-zero status code. This prevents the drives from +remaining active and spinning after an aborted operation, +helping to avoid unnecessary heat build\-up and power +consumption. Use this option to ensure disks are safely +stopped even when a command fails. +.TP .B \-w, \-\-bw\-limit RATE Applies a global bandwidth limit for all disks. The RATE is the number of bytes per second. You can specify a multiplier diff --git a/snapraid.d b/snapraid.d index 9dfaef39..11b3f290 100644 --- a/snapraid.d +++ b/snapraid.d @@ -8,7 +8,7 @@ Synopsis : [-a, --audit-only] [-h, --pre-hash] [-i, --import DIR] : [-p, --plan PERC|bad|new|full] : [-o, --older-than DAYS] [-l, --log FILE] - : [-w, --bw-limit RATE] + : [-s, --spin-down-on-error] [-w, --bw-limit RATE] : [-Z, --force-zero] [-E, --force-empty] : [-U, --force-uuid] [-D, --force-device] : [-N, --force-nocopy] [-F, --force-full] @@ -471,7 +471,12 @@ Commands This command uses the "smartctl" tool, and it's equivalent to run "smartctl -s standby,now" on all the devices. - You can spin-down only some specific disks using the -d, --filter-disk option. + You can spin-down only some specific disks using the -d, --filter-disk + option. + + To automatically spin down on error, you can use the option + -s, --spin-down-on-error with any other command, which is equivalent + to running "down" manually when an error occurs. Nothing is modified. @@ -804,6 +809,14 @@ Options identified regardless of their name. This option can be used only with "check" and "fix". + -s, --spin-down-on-error + On any error, spin down all managed disks before exiting with + a non-zero status code. This prevents the drives from + remaining active and spinning after an aborted operation, + helping to avoid unnecessary heat build-up and power + consumption. Use this option to ensure disks are safely + stopped even when a command fails. + -w, --bw-limit RATE Applies a global bandwidth limit for all disks. The RATE is the number of bytes per second. You can specify a multiplier