Secure the popen use

This commit is contained in:
Andrea Mazzoleni 2026-05-16 17:14:59 +02:00
parent 39522d06d9
commit af840808bc
5 changed files with 437 additions and 126 deletions

View file

@ -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.
*/

View file

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

View file

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

View file

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

View file

@ -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 */
}