Add a new -s, --spin-down-on-error option

Also handle correctly chars outside the Windows codepage as arguments.
This commit is contained in:
Andrea Mazzoleni 2025-09-26 09:49:20 +02:00
parent 30474cfb80
commit bb2b84d21a
6 changed files with 316 additions and 6 deletions

View file

@ -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 \

277
cmdline/main.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -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;

View file

@ -23,6 +23,7 @@
void speed(int period);
void selftest(void);
int snapraid_main(int argc, char* argv[]);
#endif

View file

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

View file

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