diff --git a/cmdline/mingw.c b/cmdline/mingw.c index 96c3ff89..23e49f53 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -32,6 +32,11 @@ void os_signal_init(void) /****************************************************************************/ /* os */ +char* os_fgets(char* s, int size, OS_FILE* stream) +{ + return fgets(s, size, stream); +} + /** * Exit codes. */ diff --git a/cmdline/portable.h b/cmdline/portable.h index 13e941a4..9862448b 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -240,6 +240,17 @@ #define HAVE_LOCKFILE 1 #endif +/* + * Flags for open() + */ +#ifndef O_PATH +#ifdef O_SEARCH +#define O_PATH O_SEARCH /* macOS */ +#else +#define O_PATH O_RDONLY /* POSIX */ +#endif +#endif + /** * Includes specific support for Windows or Linux. */ @@ -306,6 +317,71 @@ void os_abort(void) __noreturn; */ void os_clear(void); +/** + * Opaque handle tracking a process execution stream and its process ID. + * + * This structure encapsulates a standard buffered I/O stream tied to the + * redirected standard output of a child process spawned via os_popen(). + */ +#ifdef __MINGW32__ +#define OS_FILE FILE +#else +typedef struct OS_FILE { + FILE* fp; /**< Standard I/O buffered stream wrapper. */ + pid_t pid; /**< Process ID of the child process. */ +} OS_FILE; +#endif + +/** + * Spawns a child process and opens a buffered stream to read its stdout. + * + * This function acts as a safer alternative to standard popen(), accepting an + * explicit argument vector rather than evaluating a shell command string. + * + * The child process is completely isolated in its own process group (via setpgid) + * with its stdin and stderr redirected to /dev/null. The underlying pipe is + * created with O_CLOEXEC set to prevent leakage across concurrent forks. + * + * @param argv A NULL-terminated array of strings representing the argument vector. + * argv[0] must contain the absolute path to the verified executable. + * @param extra_args A mutable string containing additional arguments separated by + * spaces, or NULL if no extra arguments are needed. + * @return A pointer to an initialized OS_FILE structure on success, or NULL on failure. + * On failure, errno is set appropriately. + */ +OS_FILE* os_popen(const char** argv, char* extra_args); + +/** + * Reads a line from an OS execution stream. + * + * Reads characters from the specified stream into the buffer string until + * (size - 1) characters are read, a newline character is read and transferred, + * or an end-of-file (EOF) condition is encountered. A terminating null + * character is appended. + * + * @param s Pointer to an array of chars where the string read is stored. + * @param size Maximum number of characters to be read (including the null character). + * @param stream Pointer to the OS_FILE tracking context. + * + * @return On success, returns the buffer pointer @p s. If EOF is reached or a read + * error occurs before any characters are read, returns NULL. + */ +char* os_fgets(char* s, int size, OS_FILE* stream); + +/** + * Closes an execution stream and reaps the associated child process. + * + * Closes the underlying buffered standard I/O stream, releases the allocated + * tracking context container, and blocks until the associated child process + * terminates to prevent the creation of zombie processes. + * + * @param stream Pointer to the active OS_FILE context to close and free. + * + * @return The termination status of the child process on success, or -1 on failure. + * On failure, errno is set appropriately. + */ +int os_pclose(OS_FILE* stream); + /****************************************************************************/ /* app */ diff --git a/cmdline/support.c b/cmdline/support.c index 72fb94e9..734fc243 100644 --- a/cmdline/support.c +++ b/cmdline/support.c @@ -963,6 +963,107 @@ unsigned strsplit(char** split_map, unsigned split_max, char* str, const char* d return mac; } +unsigned argsplit(const char** split_map, unsigned split_max, char* str) +{ + unsigned mac = 0; + + /* skip leading whitespace */ + while (*str != 0 && isspace((unsigned char)*str)) + ++str; + + while (*str != 0 && mac < split_max) { + char* dst = str; + split_map[mac++] = dst; + +#ifdef _WIN32 + /* + * cmd.exe rules: + * Unquoted: '^' escapes next char; '"' opens a quoted block. + * Quoted: '"' closes the block; '^' is an ordinary character. + */ + int quoted = 0; + + while (*str != 0 && (quoted || !isspace((unsigned char)*str))) { + if (quoted) { + if (*str == '"') { + quoted = 0; + ++str; + } else { + *dst++ = *str++; + } + } else { + if (*str == '^' && *(str + 1) != 0) { + ++str; + *dst++ = *str++; + } else if (*str == '"') { + quoted = 1; + ++str; + } else { + *dst++ = *str++; + } + } + } + +#else + /* + * bash rules: + * Single-quoted block: everything literal, no exceptions. + * Double-quoted block: '\' only escapes $, `, ", '\', newline; + * other backslashes are copied verbatim. + * Unquoted '\': always escapes the immediately following char. + */ + + /* set of characters that '\' can escape inside double quotes */ + static const char dq_escapable[] = "$`\"\\\n"; + + while (*str != 0 && !isspace((unsigned char)*str)) { + + if (*str == '\'') { + /* single-quoted block: truly everything is literal */ + ++str; + while (*str != 0 && *str != '\'') + *dst++ = *str++; + if (*str == '\'') + ++str; + + } else if (*str == '"') { + /* double-quoted block */ + ++str; + while (*str != 0 && *str != '"') { + if (*str == '\\' && strchr(dq_escapable, *(str + 1))) { + ++str; + } + *dst++ = *str++; + } + if (*str == '"') + ++str; + + } else if (*str == '\\') { + /* unquoted backslash: next character always literal */ + ++str; + if (*str != 0) + *dst++ = *str++; + + } else { + *dst++ = *str++; + } + } +#endif + + if (*str != 0) + ++str; /* step past the delimiter */ + + /* skip whitespace before the next token */ + while (*str != 0 && isspace((unsigned char)*str)) + ++str; + + /* null-terminate after str has already moved on */ + *dst = 0; + } + + return mac; +} + char* strtrim(char* str) { char* begin; @@ -1009,7 +1110,7 @@ char* worddigitstr(const char* haystack, const char* needle) /* left boundary */ if (s == haystack || isspace((unsigned char)s[-1]) || isdigit((unsigned char)s[-1])) { /* right boundary */ - if (s[len] == '\0' || isspace((unsigned char)s[len]) || isdigit((unsigned char)s[len])) { + if (s[len] == 0 || isspace((unsigned char)s[len]) || isdigit((unsigned char)s[len])) { return (char*)s; } } @@ -1236,7 +1337,7 @@ int pathncmp(const char* a, const char* b, size_t n) if (la != lb) return la - lb; - if (la == '\0') + if (la == 0) return 0; --n; @@ -2087,7 +2188,7 @@ int snumber(const char* str, const char* prefix, uint64_t* value) return 1; } -int smartctl_attribute(FILE* f, const char* file, const char* name, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* inter) +int smartctl_attribute(OS_FILE* f, const char* file, const char* name, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* inter) { unsigned i; int inside; @@ -2135,7 +2236,7 @@ int smartctl_attribute(FILE* f, const char* file, const char* name, struct smart uint64_t raw; char* s; - s = fgets(buf, sizeof(buf), f); + s = os_fgets(buf, sizeof(buf), f); if (s == 0) break; @@ -2455,14 +2556,14 @@ int smartctl_attribute(FILE* f, const char* file, const char* name, struct smart return 0; } -int smartctl_flush(FILE* f, const char* file, const char* name) +int smartctl_flush(OS_FILE* f, const char* file, const char* name) { /* read the file */ while (1) { char buf[256]; char* s; - s = fgets(buf, sizeof(buf), f); + s = os_fgets(buf, sizeof(buf), f); if (s == 0) break; diff --git a/cmdline/support.h b/cmdline/support.h index 1a0156f8..7d002fab 100644 --- a/cmdline/support.h +++ b/cmdline/support.h @@ -276,11 +276,69 @@ static inline const char* esc_shell(const char* str, char* buffer) char* strpolish(char* s); /** - * Split a string in multiple tokens separated by delimiters. + * Split a string into tokens in-place. * - * Multiple delimiters are grouped together. + * Scans @str for tokens separated by any character in @delimiters, + * null-terminates each token by overwriting its trailing delimiter, + * and stores a pointer to each token's start in @split_map. + * + * Leading and consecutive delimiters are silently skipped, so the + * output never contains empty tokens. + * + * The input string is modified in-place; @split_map entries point + * directly into @str and are only valid for its lifetime. + * + * \param split_map Output array receiving pointers to each token. + * \param split_max Capacity of @split_map; at most this many tokens are stored. + * \param str Input string to tokenise (modified in-place). + * \param delimiters Null-terminated set of delimiter characters. + * \return Number of tokens stored in @split_map. + * + * Example: + * char buf[] = " one,two,,three "; + * char* map[8]; + * unsigned n = strsplit(map, 8, buf, " ,"); + * // n == 3, map[] == { "one", "two", "three" } */ -unsigned strsplit(char** split_map, unsigned split_max, char* line, const char* delimiters); +unsigned strsplit(char** split_map, unsigned split_max, char* str, const char* delimiters); + +/** + * Split a command-line string into an argument vector, + * applying the quoting rules of the platform's native shell. + * + * The input string is modified in-place: quotes and escape characters + * are stripped and each token is null-terminated. Whitespace is the + * implicit token delimiter on both platforms. + * + * bash quoting (Linux / macOS): + * 'single quotes' - Everything literal; no escapes processed inside. + * "double quotes" - '\' only escapes $, `, ", '\', and newline; + * all other backslashes are kept as-is. + * \x (unquoted) - Next character taken literally (even whitespace). + * + * cmd.exe quoting (Windows): + * "double quotes" - Toggle quoted mode; inside, '^' is NOT special. + * ^x (unquoted) - Next character taken literally. + * Single quotes and '\' carry no special meaning in either mode. + * + * \param split_map Output array receiving pointers to each argument. + * \param split_max Capacity of @split_map; at most this many tokens are stored. + * \param str Input command-line string (modified in-place). + * \return Number of arguments stored in @split_map. + * + * Example (bash): + * char buf[] = "one 'two three' \"say \\\"hi\\\"\" back\\slash"; + * char* map[8]; + * unsigned n = argsplit(map, 8, buf); + * // n==4, map[] == { "one", "two three", "say \"hi\"", "backslash" } + * + * Example (cmd.exe): + * char buf[] = "one \"two three\" ^&safe ^\"quoted^\""; + * char* map[8]; + * unsigned n = argsplit(map, 8, buf); + * // n==4, map[] == { "one", "two three", "&safe", "\"quoted\"" } + */ +unsigned argsplit(const char** split_map, unsigned split_max, char* str); /** * Trim spaces from the start and the end @@ -476,12 +534,12 @@ void malloc_fail(size_t size); * Read smartctl attributes from a stream. * Return 0 on success. */ -int smartctl_attribute(FILE* f, const char* file, const char* name, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interf); +int smartctl_attribute(OS_FILE* f, const char* file, const char* name, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interf); /** * Flush smartctl output from a stream. */ -int smartctl_flush(FILE* f, const char* file, const char* name); +int smartctl_flush(OS_FILE* f, const char* file, const char* name); /** * Extract the temperature from the SMART info. diff --git a/cmdline/unix.c b/cmdline/unix.c index 0bd2c34d..bd5db81f 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -82,7 +82,6 @@ void os_signal_init(void) /****************************************************************************/ /* exec */ -#if HAVE_LINUX_DEVICE /* * Securely verify and open an executable file. * @@ -273,34 +272,11 @@ static int verify_executable(const char* exec_path, char* resolved_path) return fd; } -/** - * Validates a string for shell metacharacters. - * Returns -1 if dangerous characters are detected, 0 otherwise. - */ -static int validate_shell_input(const char* str) -{ - /* - * Define characters that can be used to inject commands or alter logic - * Includes: ; | & $ > < ` ' " ( ) [ * ? \ ~ and whitespace - */ - const char* danger_chars = ";|&$><`'\"()[]*?\\~\r\n "; - - /* check for any of the forbidden characters */ - if (strpbrk(str, danger_chars) != 0) - return -1; - - /* check if the string starts with a hyphen to prevent flag injection */ - if (str[0] == '-') - return -1; - - return 0; -} - /** * Validates a string for exec. * Returns -1 if dangerous characters are detected, 0 otherwise. */ -static int validate_exec_input(const char* str) +int validate_exec_input(const char* str) { /* reject paths trying to go up levels */ if (strstr(str, "..") != 0) @@ -356,7 +332,7 @@ bail: } /* - * and execute a verified executable, capturing stdout. + * Fork and execute a verified executable, capturing stdout. * * Spawns @argv[0] in a new process with stdout connected to a pipe whose * read end is returned in @stdout_fd. stdin and stderr are redirected to @@ -378,7 +354,7 @@ bail: * * Returns the child PID on success, or -1 on failure. */ -pid_t os_spawn_stdout(const char** argv, int* stdout_fd) +static pid_t os_spawn_stdout(const char** argv, int* stdout_fd) { char resolved_path[PATH_MAX]; int out_pipe[2]; @@ -466,11 +442,13 @@ pid_t os_spawn_stdout(const char** argv, int* stdout_fd) /* parent */ close(fd); +#ifdef F_SETPIPE_SZ /* not available on macOS */ /* set the pipe buffer to the minimum to improve responsiveness */ if (fcntl(out_pipe[0], F_SETPIPE_SZ, 4096) == -1) { log_error(errno, "Failed to set pipe size. %s.", strerror(errno)); /* log non-fatal error or ignore */ } +#endif close(out_pipe[1]); @@ -478,7 +456,7 @@ pid_t os_spawn_stdout(const char** argv, int* stdout_fd) return pid; } -int os_wait(pid_t pid, int* status) +static int os_wait(pid_t pid, int* status) { int ret; @@ -599,7 +577,77 @@ int os_spawn_and_wait(const char** argv) return -1; } -#endif + +#define ARGV_MAX 64 + +OS_FILE* os_popen(const char** argv, char* extra_args) +{ + const char* combined_argv[ARGV_MAX]; + + unsigned argc = 0; + while (argv[argc] && argc < ARGV_MAX) { + combined_argv[argc] = argv[argc]; + ++argc; + } + + if (extra_args) + argc += argsplit(combined_argv + argc, ARGV_MAX - argc, extra_args); + + if (argc >= ARGV_MAX) { + errno = E2BIG; + return NULL; + } + + combined_argv[argc] = 0; + + int stdout_fd = -1; + pid_t pid = os_spawn_stdout(combined_argv, &stdout_fd); + if (pid < 0) { + return 0; + } + + FILE* fp = fdopen(stdout_fd, "r"); + if (!fp) { + /* LCOV_EXCL_START */ + int saved_errno = errno; + close(stdout_fd); + int status; + os_wait(pid, &status); + errno = saved_errno; + return 0; + /* LCOV_EXCL_STOP */ + } + + OS_FILE* os_file = malloc_nofail(sizeof(OS_FILE)); + + os_file->fp = fp; + os_file->pid = pid; + + return os_file; +} + +char* os_fgets(char* s, int size, OS_FILE* stream) +{ + return fgets(s, size, stream->fp); +} + +int os_pclose(OS_FILE* stream) +{ + /* leep handles locally to free container block immediately */ + FILE* fp = stream->fp; + pid_t pid = stream->pid; + free(stream); + + /* close buffered stream, releasing the underlying fd */ + fclose(fp); + + int status = 0; + if (os_wait(pid, &status) < 0) { + return -1; + } + + return status; +} /** * Exit codes. @@ -1284,7 +1332,6 @@ static int devdereference_bcachefs(uint64_t device, const char* dir, tommy_list* static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char* uuid, size_t uuid_size, char* mount_point, size_t mount_point_size) { char resolved[PATH_MAX]; - char cmd[PATH_MAX * 2 + 64]; const char* zfs = find_zfs(); if (!zfs) { @@ -1300,9 +1347,18 @@ static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char } /* list all ZFS filesystems in one shot */ - snprintf(cmd, sizeof(cmd), "%s list -H -o name,guid,mountpoint -t filesystem 2>/dev/null", zfs); + const char* argv[] = { + zfs, + "list", + "-H", + "-o", + "name,guid,mountpoint", + "-t", + "filesystem", + 0 + }; - FILE* fp = popen(cmd, "r"); + OS_FILE* fp = os_popen(argv, 0); if (!fp) { /* LCOV_EXCL_START */ return -1; @@ -1315,7 +1371,7 @@ static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char char* map[3]; unsigned mac; - char* s = fgets(buf, sizeof(buf), fp); + char* s = os_fgets(buf, sizeof(buf), fp); if (s == 0) break; @@ -1344,7 +1400,7 @@ static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char } } - pclose(fp); + os_pclose(fp); if (best_len == 0) { /* LCOV_EXCL_START */ @@ -1358,7 +1414,6 @@ static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char static int devdereference_zfs(uint64_t device, const char* dir, tommy_list* devlist) { char pool[PATH_MAX]; - char cmd[PATH_MAX * 2 + 64]; const char* zpool = find_zpool(); if (!zpool) { @@ -1378,15 +1433,15 @@ static int devdereference_zfs(uint64_t device, const char* dir, tommy_list* devl if (slash) *slash = 0; - if (validate_shell_input(pool) != 0) { - /* LCOV_EXCL_START */ - return -1; - /* LCOV_EXCL_STOP */ - } + const char* argv[] = { + zpool, + "status", + "-P", + pool, + 0 + }; - snprintf(cmd, sizeof(cmd), "%s status -P %s 2>/dev/null", zpool, pool); - - FILE* fp = popen(cmd, "r"); + OS_FILE* fp = os_popen(argv, 0); if (!fp) { /* LCOV_EXCL_START */ return -1; @@ -1397,7 +1452,7 @@ static int devdereference_zfs(uint64_t device, const char* dir, tommy_list* devl while (1) { char buf[PATH_MAX * 2 + 64]; - char* s = fgets(buf, sizeof(buf), fp); + char* s = os_fgets(buf, sizeof(buf), fp); if (s == 0) break; @@ -1425,7 +1480,7 @@ static int devdereference_zfs(uint64_t device, const char* dir, tommy_list* devl ++count; } - pclose(fp); + os_pclose(fp); if (count == 0) { /* LCOV_EXCL_START */ @@ -3396,9 +3451,9 @@ static int devstat(dev_t device, uint64_t* count) #if HAVE_LINUX_DEVICE static int devsmart(dev_t device, const char* name, const char* smartctl, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interface) { - char cmd[PATH_MAX + 64]; + char extra_args[PATH_MAX]; char file[PATH_MAX]; - FILE* f; + OS_FILE* f; int ret; const char* x; @@ -3418,48 +3473,51 @@ static int devsmart(dev_t device, const char* name, const char* smartctl, struct } /* if there is a custom smartctl command */ + const char* args = "-a"; if (smartctl[0]) { - char option[PATH_MAX]; - snprintf(option, sizeof(option), smartctl, file); - snprintf(cmd, sizeof(cmd), "%s -a %s", x, option); + pathprint(extra_args, sizeof(extra_args), smartctl, file); + const char* argv[] = { + x, + "-a", + 0 + }; + log_tag("smartctl:%s:%s:run: %s %s %s\n", file, name, x, args, extra_args); + f = os_popen(argv, extra_args); } else { - snprintf(cmd, sizeof(cmd), "%s -a %s", x, file); + pathprint(extra_args, sizeof(extra_args), "%s", file); + const char* argv[] = { + x, + "-a", + file, + 0 + }; + log_tag("smartctl:%s:%s:run: %s %s %s\n", file, name, x, args, extra_args); + f = os_popen(argv, 0); } - - log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd); - - f = popen(cmd, "r"); if (!f) { /* LCOV_EXCL_START */ - log_tag("device:%s:%s:shell\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd); + log_tag("device:%s:%s:spawn\n", file, name); + log_fatal(EEXTERNAL, "Failed to run '%s %s %s'.\n", x, args, extra_args); return -1; /* LCOV_EXCL_STOP */ } if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) { /* LCOV_EXCL_START */ - pclose(f); - log_tag("device:%s:%s:shell\n", file, name); + os_pclose(f); + log_tag("device:%s:%s:spawn\n", file, name); return -1; /* LCOV_EXCL_STOP */ } - ret = pclose(f); + ret = os_pclose(f); log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret); if (!WIFEXITED(ret)) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:abort\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd); - return -1; - /* LCOV_EXCL_STOP */ - } - if (WEXITSTATUS(ret) == 127) { - /* LCOV_EXCL_START */ - log_tag("device:%s:%s:shell\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd); + log_fatal(EEXTERNAL, "Failed to run '%s %s %s' (not exited).\n", x, args, extra_args); return -1; /* LCOV_EXCL_STOP */ } @@ -3546,9 +3604,9 @@ static void devattr(dev_t device, uint64_t* info, char* serial, char* family, ch #if HAVE_LINUX_DEVICE static int devprobe(dev_t device, const char* name, const char* smartctl, int* power, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interface) { - char cmd[PATH_MAX + 64]; + char extra_args[PATH_MAX]; char file[PATH_MAX]; - FILE* f; + OS_FILE* f; int ret; const char* x; @@ -3568,48 +3626,55 @@ static int devprobe(dev_t device, const char* name, const char* smartctl, int* p } /* if there is a custom smartctl command */ + const char* args = "-n standby,3 -a"; if (smartctl[0]) { - char option[PATH_MAX]; - snprintf(option, sizeof(option), smartctl, file); - snprintf(cmd, sizeof(cmd), "%s -n standby,3 -a %s", x, option); + pathprint(extra_args, sizeof(extra_args), smartctl, file); + const char* argv[] = { + x, + "-n", + "standby,3", + "-a", + 0 + }; + log_tag("smartctl:%s:%s:run: %s %s %s\n", file, name, x, args, extra_args); + f = os_popen(argv, extra_args); } else { - snprintf(cmd, sizeof(cmd), "%s -n standby,3 -a %s", x, file); + pathprint(extra_args, sizeof(extra_args), "%s", file); + const char* argv[] = { + x, + "-n", + "standby,3", + "-a", + file, + 0 + }; + log_tag("smartctl:%s:%s:run: %s %s %s\n", file, name, x, args, extra_args); + f = os_popen(argv, 0); } - - log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd); - - f = popen(cmd, "r"); if (!f) { /* LCOV_EXCL_START */ - log_tag("device:%s:%s:shell\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd); + log_tag("device:%s:%s:spawn\n", file, name); + log_fatal(EEXTERNAL, "Failed to run '%s %s %s'.\n", x, args, extra_args); return -1; /* LCOV_EXCL_STOP */ } if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) { /* LCOV_EXCL_START */ - pclose(f); - log_tag("device:%s:%s:shell\n", file, name); + os_pclose(f); + log_tag("device:%s:%s:spawn\n", file, name); return -1; /* LCOV_EXCL_STOP */ } - ret = pclose(f); + ret = os_pclose(f); log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret); if (!WIFEXITED(ret)) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:abort\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd); - return -1; - /* LCOV_EXCL_STOP */ - } - if (WEXITSTATUS(ret) == 127) { - /* LCOV_EXCL_START */ - log_tag("device:%s:%s:shell\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd); + log_fatal(EEXTERNAL, "Failed to run '%s %s %s' (not exited).\n", x, args, extra_args); return -1; /* LCOV_EXCL_STOP */ } @@ -3636,9 +3701,9 @@ static int devprobe(dev_t device, const char* name, const char* smartctl, int* p #if HAVE_LINUX_DEVICE static int devdown(dev_t device, const char* name, const char* smartctl) { - char cmd[PATH_MAX + 64]; + char extra_args[PATH_MAX]; char file[PATH_MAX]; - FILE* f; + OS_FILE* f; int ret; const char* x; @@ -3658,55 +3723,61 @@ static int devdown(dev_t device, const char* name, const char* smartctl) } /* if there is a custom smartctl command */ + const char* args = "-s standby,now"; if (smartctl[0]) { - char option[PATH_MAX]; - snprintf(option, sizeof(option), smartctl, file); - snprintf(cmd, sizeof(cmd), "%s -s standby,now %s", x, option); + pathprint(extra_args, sizeof(extra_args), smartctl, file); + const char* argv[] = { + x, + "-s", + "standby,now", + 0 + }; + log_tag("smartctl:%s:%s:run: %s %s %s\n", file, name, x, args, extra_args); + f = os_popen(argv, extra_args); } else { - snprintf(cmd, sizeof(cmd), "%s -s standby,now %s", x, file); + pathprint(extra_args, sizeof(extra_args), "%s", file); + const char* argv[] = { + x, + "-s", + "standby,now", + file, + 0 + }; + log_tag("smartctl:%s:%s:run: %s %s %s\n", file, name, x, args, extra_args); + f = os_popen(argv, 0); } - - log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd); - - f = popen(cmd, "r"); if (!f) { /* LCOV_EXCL_START */ - log_tag("device:%s:%s:shell\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd); + log_tag("device:%s:%s:spawn\n", file, name); + log_fatal(EEXTERNAL, "Failed to run '%s %s %s'.\n", x, args, extra_args); return -1; /* LCOV_EXCL_STOP */ } if (smartctl_flush(f, file, name) != 0) { /* LCOV_EXCL_START */ - pclose(f); - log_tag("device:%s:%s:shell\n", file, name); + os_pclose(f); + log_tag("device:%s:%s:spawn\n", file, name); return -1; /* LCOV_EXCL_STOP */ } - ret = pclose(f); + ret = os_pclose(f); log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret); if (!WIFEXITED(ret)) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:abort\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd); - return -1; - /* LCOV_EXCL_STOP */ - } - if (WEXITSTATUS(ret) == 127) { - /* LCOV_EXCL_START */ - log_tag("device:%s:%s:shell\n", file, name); - log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd); + log_fatal(EEXTERNAL, "Failed to run '%s %s %s' (not exited).\n", x, args, extra_args); return -1; /* LCOV_EXCL_STOP */ } + if (WEXITSTATUS(ret) != 0) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:exit:%d\n", file, name, WEXITSTATUS(ret)); - log_fatal(EEXTERNAL, "Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret)); + log_fatal(EEXTERNAL, "Failed to run '%s %s %s' with return code %xh.\n", x, args, extra_args, WEXITSTATUS(ret)); return -1; /* LCOV_EXCL_STOP */ }