Files
pv/src/main/options.c
T

1377 lines
35 KiB
C

/*
* Parse command-line options.
*
* Copyright 2002-2008, 2010, 2012-2015, 2017, 2021, 2023-2026 Andrew Wood
*
* License GPLv3+: GNU GPL version 3 or later; see `docs/COPYING'.
*/
#include "config.h"
#include "options.h"
#include "pv.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#ifdef major
#ifdef minor
#ifdef HAVE_STRUCT_STAT_ST_RDEV
#define CAN_BUILD_SYSFS_FILENAME 1
#endif
#endif
#endif
#endif
#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
#include <sys/wait.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef HAVE_FTW_H
#include <ftw.h>
#endif
#if HAVE_MATH_H
#include <math.h>
#endif
void display_help(void);
void display_version(void);
static bool opts_watchfd_parse(opts_t, const char *, /*@null@ */ const char *, unsigned int);
/*
* splint note about mustfreefresh: the gettext calls made by _() cause
* memory leak warnings, but in these cases it's unavoidable, and mitigated
* by the fact that each string is only translated once.
*/
/*
* Free an opts_t object.
*/
void opts_free( /*@only@ */ opts_t opts)
{
if (!opts)
return;
/*@-keeptrans@ */
/*
* splint note: the "opts" object was explicitly passed to be freed,
* so the previously "kept" internally allocated buffers are no
* longer "kept".
*/
if (NULL != opts->name)
free(opts->name);
if (NULL != opts->name1)
free(opts->name1);
if (NULL != opts->format)
free(opts->format);
if (NULL != opts->format1)
free(opts->format1);
if (NULL != opts->pidfile)
free(opts->pidfile);
if (NULL != opts->output)
free(opts->output);
if (NULL != opts->default_bar_style)
free(opts->default_bar_style);
if (NULL != opts->store_and_forward_file)
free(opts->store_and_forward_file);
if (NULL != opts->extra_display)
free(opts->extra_display);
if (NULL != opts->watchfd_pid)
free(opts->watchfd_pid);
if (NULL != opts->watchfd_fd)
free(opts->watchfd_fd);
if (NULL != opts->argv)
free(opts->argv);
/*@+keeptrans@ */
free(opts);
}
/*
* Add a filename to the list of non-option arguments, returning false on
* error. The filename is not copied - the pointer is stored. The list is
* guaranteed to have a NULL after the last item.
*/
bool opts_add_file(opts_t opts, const char *filename)
{
/*
* If there's no array yet or there's insufficient room for both
* this filename and a NULL entry after it, reallocate the array to
* make room for the number of entries in it plus 10 (10 rather than
* 2 to avoid reallocating too often without wasting too much
* space).
*/
/*@-branchstate@ */
if (((1 + opts->argc) >= opts->argv_length) || (NULL == opts->argv)) {
opts->argv_length = opts->argc + 10;
/*@-keeptrans@ */
opts->argv = realloc(opts->argv, opts->argv_length * sizeof(char *));
/*@+keeptrans@ */
if (NULL == opts->argv) {
pv_perror("%s", "realloc");
opts->argv_length = 0;
opts->argc = 0;
return false;
}
}
/*@+branchstate@ */
/*
* splint notes: "branchstate" and "keeptrans" are turned off
* because depending on whether the array is to be extended, argv
* can change from "keep" to "only". There doesn't seem to be a
* clean way to tell splint that this function, and opts_parse(),
* are allowed to reallocate argv, but nothing else should alter it.
*/
opts->argv[opts->argc++] = filename;
opts->argv[opts->argc] = NULL;
return true;
}
/*
* Add a process ID and file descriptor to the list of items to watch with
* --watchfd, returning false on error.
*/
static bool opts_watchfd_add_item(opts_t opts, pid_t pid, int fd)
{
/*
* If there's no array yet or there's insufficient room for one more
* entry, reallocate the array to make room for the number of
* entries in it plus 10 (as above, to avoid reallocating too often
* without wasting too much space).
*/
/*@-branchstate@ */
if ((opts->watchfd_count >= opts->watchfd_length) || (NULL == opts->watchfd_pid)) {
opts->watchfd_length = opts->watchfd_count + 10;
/*@-keeptrans@ */
opts->watchfd_pid = realloc(opts->watchfd_pid, opts->watchfd_length * sizeof(pid_t));
opts->watchfd_fd = realloc(opts->watchfd_fd, opts->watchfd_length * sizeof(int));
/*@+keeptrans@ */
if ((NULL == opts->watchfd_pid) || (NULL == opts->watchfd_fd)) {
pv_perror("%s", "realloc");
opts->watchfd_length = 0;
opts->watchfd_count = 0;
return false;
}
}
/*@+branchstate@ */
/*
* splint notes: "branchstate" and "keeptrans" were turned off
* because of the same reason as for argv in opts_add_file().
*/
opts->watchfd_pid[opts->watchfd_count] = pid;
opts->watchfd_fd[opts->watchfd_count] = fd;
opts->watchfd_count++;
return true;
}
/*
* Look for processes with the given name, and add all of them to the list
* of watched PIDs. Returns false on error.
*/
static bool opts_watchfd_processname(opts_t opts, const char *process_name)
{
int fds[2];
pid_t pid;
FILE *fptr;
char *linebuf_ptr = NULL;
size_t linebuf_size = 0;
pid_t waited_pid;
int pid_status;
bool ok;
/* Pipe for communicating with pgrep. */
if (pipe(fds) < 0) {
pv_perror("%s", "pipe");
return false;
}
/* Subprocess in which to run pgrep. */
pid = (pid_t) fork();
if (pid < 0) {
/* Fork failure - error return. */
pv_perror("%s", "fork");
(void) close(fds[0]);
(void) close(fds[1]);
return false;
} else if (0 == pid) {
int nullfd;
/* Child process - close stdin, set up stdout, run pgrep. */
/* Replace stdin with /dev/null. */
nullfd = open("/dev/null", O_RDONLY); /* flawfinder: ignore */
/* flawfinder: /dev/null is trusted. */
if (nullfd < 0) {
pv_perror("%s", "/dev/null");
exit(EXIT_FAILURE);
}
if (!pv_fd_is_dev_null(nullfd, true)) {
exit(EXIT_FAILURE);
}
if (dup2(nullfd, STDIN_FILENO) < 0) {
pv_perror("%s", "dup2");
exit(EXIT_FAILURE);
}
(void) close(nullfd);
/* Replace stdout with the write end of the pipe. */
if (dup2(fds[1], STDOUT_FILENO) < 0) {
pv_perror("%s", "dup2");
exit(EXIT_FAILURE);
}
(void) close(fds[1]);
/* Close the read end of the pipe. */
(void) close(fds[0]);
/* Run pgrep. */
if (execlp("pgrep", "pgrep", process_name, NULL) < 0) { /* flawfinder: ignore */
pv_perror("%s", "pgrep");
}
/*
* flawfinder: deliberately calling pgrep as there isn't a
* portable library to use instead.
*/
exit(EXIT_FAILURE);
}
/* Close the write end of the pipe. */
(void) close(fds[1]);
/* Open a file stream on the read end of the pipe. */
fptr = fdopen(fds[0], "r");
if (NULL == fptr) {
pv_perror("%s", "fdopen");
(void) close(fds[0]);
return false;
}
/* Read the lines of output from the child process. */
ok = true;
while (0 == feof(fptr)) {
unsigned int watch_pid;
ssize_t line_length = 0;
/* Set errno to 0 to distinguish between EOF and error. */
errno = 0;
/*@-unrecog@ *//* splint doesn't know of getline(). */
line_length = getline(&linebuf_ptr, &linebuf_size, fptr);
/*@+unrecog@ */
if ((line_length < 0) || (NULL == linebuf_ptr)) {
if (0 != errno)
pv_perror("%s", "getline");
break;
}
if (line_length < 1)
continue;
/* Skip all lines if any errors were found. */
if (!ok)
continue;
/* Strip the newline. */
if ('\n' == linebuf_ptr[line_length - 1])
linebuf_ptr[--line_length] = '\0';
/* Skip lines without a valid PID. */
watch_pid = 0;
if (sscanf(linebuf_ptr, "%u", &watch_pid) < 1)
continue;
if (watch_pid < 1)
continue;
if (!opts_watchfd_parse(opts, linebuf_ptr, NULL, 0))
ok = false;
}
if (NULL != linebuf_ptr)
free(linebuf_ptr);
/* Close the stream from the read end of the pipe. */
(void) fclose(fptr);
/* Wait for the child process to exit. */
do {
/*@-type@ */
/* splint disagreement about __pid_t vs pid_t. */
pid_status = 0;
waited_pid = waitpid(pid, &pid_status, 0);
/*@+type@ */
} while (-1 == waited_pid && errno == EINTR);
return ok;
}
/*
* Read additional "-d" values from the given file, returning false on
* failure.
*/
static bool opts_watchfd_listfile(opts_t opts, const char *filename)
{
FILE *fptr;
char *linebuf_ptr = NULL;
size_t linebuf_size = 0;
unsigned int linenumber;
fptr = fopen(filename, "r"); /* flawfinder: ignore */
/*
* flawfinder note: the caller directly controls the filename, which
* here is being opened read-only, so there is no further
* mitigation.
*/
if (NULL == fptr) {
pv_perror("%s", filename);
return false;
}
linenumber = 0;
while (0 == feof(fptr)) {
const char *argument;
char *nlptr;
ssize_t line_length = 0;
/* Set errno to 0 to distinguish between EOF and error. */
errno = 0;
/*@-unrecog@ *//* splint doesn't know of getline(). */
line_length = getline(&linebuf_ptr, &linebuf_size, fptr);
/*@+unrecog@ */
if ((line_length < 0) || (NULL == linebuf_ptr)) {
if (0 != errno)
pv_perror("%s", filename);
break;
}
linenumber++;
/* Remove trailing carriage returns or newlines. */
nlptr = strchr(linebuf_ptr, '\r');
if (NULL != nlptr)
nlptr[0] = '\0';
nlptr = strchr(linebuf_ptr, '\n');
if (NULL != nlptr)
nlptr[0] = '\0';
/* Ignore leading spaces. */
argument = linebuf_ptr;
while (argument[0] != '\0' && (argument[0] == ' ' || argument[0] == '\t'))
argument++;
/* Ignore blank lines or comment lines. */
if ((argument[0] == '\0') || (argument[0] == '#')) {
continue;
}
/* Reject "@" lines. */
if (argument[0] == '@') {
free(linebuf_ptr);
(void) fclose(fptr);
return false;
}
if (!opts_watchfd_parse(opts, argument, filename, linenumber)) {
free(linebuf_ptr);
(void) fclose(fptr);
return false;
}
}
if (NULL != linebuf_ptr)
free(linebuf_ptr);
(void) fclose(fptr);
return true;
}
/*
* Parse a "-d" option value and make the appropriate additions to the list
* of watchfd items, returning false on failure.
*/
static bool opts_watchfd_parse(opts_t opts, const char *argument, /*@null@ */ const char *filename, unsigned int line)
{
unsigned int parse_pid;
int parse_fd;
if ('@' == argument[0]) {
/* "-d @FILE" syntax - read more values from FILE. */
/* Don't allow this syntax in a list file. */
if (NULL != filename) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s:%u: %s", filename, line, _("list files may not contain @ lines"));
return false;
/*@+mustfreefresh@ */
}
return opts_watchfd_listfile(opts, argument + 1);
} else if ('=' == argument[0]) {
/* "-d =NAME" syntax - find processes named NAME. */
return opts_watchfd_processname(opts, argument + 1);
}
/* "-d PID[:FD]" syntax. */
parse_pid = 0;
parse_fd = -1;
if (sscanf(argument, "%u:%d", &parse_pid, &parse_fd) < 1) {
/*@-mustfreefresh@ *//* see above */
if (NULL != filename) {
pv_error("%s:%u: %s: %s", filename, line, argument, _("process ID or pid:fd pair expected"));
} else {
pv_error("%s: %s", argument, _("process ID or pid:fd pair expected"));
}
return false;
/*@+mustfreefresh@ */
}
if (parse_pid < 1) {
/*@-mustfreefresh@ *//* see above */
if (NULL != filename) {
pv_error("%s:%u: %s: %s", filename, line, argument, _("invalid process ID"));
} else {
pv_error("%s: %s", argument, _("invalid process ID"));
}
return false;
/*@+mustfreefresh@ */
}
return opts_watchfd_add_item(opts, (pid_t) parse_pid, parse_fd);
}
/*
* Return true if the first string is at least as long as the second, and
* its start matches the entirety of the second. Both strings must be
* null-terminated.
*/
static bool string_starts_with(const char *string, const char *match)
{
size_t string_length, match_length;
string_length = strlen(string); /* flawfinder: ignore */
match_length = strlen(match); /* flawfinder: ignore */
/* flawfinder - these are null-terminated strings. */
if (string_length < match_length)
return false;
if (0 == strncmp(string, match, match_length))
return true;
return false;
}
#ifdef HAVE_NFTW
/*
* Callback function for nftw() to add the size of the given file to the
* total size so far.
*/
static off_t dir_tree_file_size_so_far = 0;
static int dir_tree_total_file_size( /*@unused@ */ __attribute__((unused))
const char *fpath, const struct stat *sb, int typeflag, /*@unused@ */
__attribute__((unused))
struct FTW *ftwbuf)
{
if (typeflag != FTW_F)
return 0;
if (NULL == sb)
return 0;
dir_tree_file_size_so_far += (off_t) (sb->st_size);
return 0;
}
#endif /* HAVE_NFTW */
/*
* Set opts->size from the size of the file whose name is size_file,
* returning false (and reporting the error) if there is a problem.
*
* If size_file points to a block device, the size of the block device is
* used.
*
* If size_file points to a directory, opts->size is set to the sum of the
* sizes of all regular files under that directory, if nftw() is available.
*/
static bool opts_use_size_of_file(opts_t opts, const char *size_file)
{
struct stat sb;
int stat_rc;
#ifdef CAN_BUILD_SYSFS_FILENAME
char sysfs_filename[512]; /* flawfinder: ignore */
FILE *sysfs_fptr;
long long sysfs_size;
#endif /* CAN_BUILD_SYSFS_FILENAME */
int device_fd;
off_t device_size;
/*
* flawfinder rationale: sysfs_filename is only used with
* pv_snprintf() which guarantees it will be bounded and zero
* terminated.
*/
stat_rc = 0;
memset(&sb, 0, sizeof(sb));
stat_rc = stat(size_file, &sb);
if (0 != stat_rc) {
pv_perror("%s", size_file);
return false;
}
/* This was a regular file - use its size and return. */
if (S_ISREG((mode_t) (sb.st_mode))) {
opts->size = (off_t) (sb.st_size);
return true;
}
#ifdef HAVE_NFTW
/* This was a directory - use the size of all files under it. */
if (S_ISDIR((mode_t) (sb.st_mode))) {
int maxfds, rc_nftw;
maxfds = -1;
#ifdef _POSIX_OPEN_MAX
maxfds = _POSIX_OPEN_MAX;
#endif
if (maxfds < 1) {
maxfds = (int) sysconf(_SC_OPEN_MAX);
}
dir_tree_file_size_so_far = 0;
rc_nftw = nftw(size_file, dir_tree_total_file_size, maxfds, 0
#ifdef FTW_PHYS
| FTW_PHYS
#endif
);
if (rc_nftw < 0) {
pv_perror("%s", size_file);
return false;
}
opts->size = dir_tree_file_size_so_far;
return true;
}
#else /* HAVE_NFTW */
/* This was a directory - report an error. */
if (S_ISDIR((mode_t) (sb.st_mode))) {
errno = EISDIR;
pv_perror("%s", size_file);
return false;
}
#endif /* !HAVE_NFTW */
/* This was not a block device - just use the size and return. */
if (!S_ISBLK((mode_t) (sb.st_mode))) {
opts->size = (off_t) (sb.st_size);
return true;
}
/*
* Block device - determine its size by looking for
* /sys/dev/block/MAJOR:MINOR/size, and if that fails, try opening
* the file and seeking to the end.
*/
#ifdef CAN_BUILD_SYSFS_FILENAME
/*
* Try the sysfs method - lightest touch and requires no read access
* to the actual block device.
*/
memset(sysfs_filename, 0, sizeof(sysfs_filename));
if (pv_snprintf
(sysfs_filename, sizeof(sysfs_filename), "/sys/dev/block/%u:%u/size", major(sb.st_rdev),
minor(sb.st_rdev)) < 0) {
pv_perror("%s", size_file);
return false;
}
sysfs_fptr = fopen(sysfs_filename, "r"); /* flawfinder: ignore */
/*
* flawfinder rationale: sysfs is trusted here, the filename is
* predictable, and only one number is being read.
*/
if (NULL != sysfs_fptr) {
sysfs_size = -1;
if (1 == fscanf(sysfs_fptr, "%lld", &sysfs_size)) {
/* Read successful - use the value (* 512) and return. */
(void) fclose(sysfs_fptr);
opts->size = (off_t) sysfs_size *512;
return true;
}
/* Read not successful - report the error and return. */
/* Report the error first, as fclose() could change errno. */
pv_perror("%s: %s", size_file, sysfs_filename);
(void) fclose(sysfs_fptr);
return false;
}
#endif /* CAN_BUILD_SYSFS_FILENAME */
/*
* Try opening the block device and seeking to the end.
*/
device_fd = open(size_file, O_RDONLY); /* flawfinder: ignore */
/*
* flawfinder rationale: the filename is under the direct control of
* the operator by its nature, so no further mitigation is possible.
* For example, refusing to open symlinks would be counterintuitive.
*/
if (device_fd < 0) {
pv_perror("%s", size_file);
return false;
}
device_size = (off_t) lseek(device_fd, 0, SEEK_END);
if (device_size < 0) {
pv_perror("%s", size_file);
/* NB close() after reporting error, to preserve errno. */
(void) close(device_fd);
return false;
}
(void) close(device_fd);
/* Set the size to the detected value. */
opts->size = device_size;
return true;
}
/*
* Parse the given command-line arguments into an opts_t object, handling
* "help" and "version" options internally.
*
* Returns an opts_t, or NULL on error.
*
* Note that the contents of *argv[] (i.e. the command line parameters)
* aren't copied anywhere, just the pointers are copied, so make sure the
* command line data isn't overwritten or argv[1] free()d or whatever.
*
* Calls pv_set_error_prefix() as a side effect.
*/
/*@null@ */
/*@only@ */
opts_t opts_parse(unsigned int argc, char **argv)
{
#ifdef HAVE_GETOPT_LONG
/*@-nullassign@ */
/* splint rationale: NULL is allowed for "flags" in long options. */
struct option long_options[] = {
{ "help", 0, NULL, (int) 'h' },
{ "version", 0, NULL, (int) 'V' },
{ "progress", 0, NULL, (int) 'p' },
{ "timer", 0, NULL, (int) 't' },
{ "eta", 0, NULL, (int) 'e' },
{ "fineta", 0, NULL, (int) 'I' },
{ "rate", 0, NULL, (int) 'r' },
{ "average-rate", 0, NULL, (int) 'a' },
{ "bytes", 0, NULL, (int) 'b' },
{ "bits", 0, NULL, (int) '8' },
{ "si", 0, NULL, (int) 'k' },
{ "buffer-percent", 0, NULL, (int) 'T' },
{ "last-written", 1, NULL, (int) 'A' },
{ "force", 0, NULL, (int) 'f' },
{ "numeric", 0, NULL, (int) 'n' },
{ "quiet", 0, NULL, (int) 'q' },
{ "cursor", 0, NULL, (int) 'c' },
{ "wait", 0, NULL, (int) 'W' },
{ "delay-start", 1, NULL, (int) 'D' },
{ "size", 1, NULL, (int) 's' },
{ "gauge", 0, NULL, (int) 'g' },
{ "line-mode", 0, NULL, (int) 'l' },
{ "null", 0, NULL, (int) '0' },
{ "interval", 1, NULL, (int) 'i' },
{ "width", 1, NULL, (int) 'w' },
{ "height", 1, NULL, (int) 'H' },
{ "name", 1, NULL, (int) 'N' },
{ "bar-style", 1, NULL, (int) 'u' },
{ "format", 1, NULL, (int) 'F' },
{ "conemu", 0, NULL, (int) '9' },
{ "extra-display", 1, NULL, (int) 'x' },
{ "stats", 0, NULL, (int) 'v' },
{ "rate-limit", 1, NULL, (int) 'L' },
{ "buffer-size", 1, NULL, (int) 'B' },
{ "no-splice", 0, NULL, (int) 'C' },
{ "skip-errors", 0, NULL, (int) 'E' },
{ "error-skip-block", 1, NULL, (int) 'Z' },
{ "pipe-buffer-size", 1, NULL, (int) 'J' },
{ "stop-at-size", 0, NULL, (int) 'S' },
{ "sync", 0, NULL, (int) 'Y' },
{ "direct-io", 0, NULL, (int) 'K' },
{ "sparse", 0, NULL, (int) 'O' },
{ "sparse-output", 0, NULL, (int) 'O' },
{ "discard", 0, NULL, (int) 'X' },
{ "store-and-forward", 1, NULL, (int) 'U' },
{ "remote", 1, NULL, (int) 'R' },
{ "query", 1, NULL, (int) 'Q' },
{ "pidfile", 1, NULL, (int) 'P' },
{ "watchfd", 1, NULL, (int) 'd' },
{ "output", 1, NULL, (int) 'o' },
{ "average-rate-window", 1, NULL, (int) 'm' },
{ "monitor", 1, NULL, (int) 'M' },
#ifdef ENABLE_DEBUGGING
{ "debug", 1, NULL, (int) '!' },
#endif /* ENABLE_DEBUGGING */
{ NULL, 0, NULL, 0 }
};
/*@+nullassign@ */
int option_index = 0;
#endif /* HAVE_GETOPT_LONG */
char *short_options = "hVpteIrab8kTA:fvnqcWD:s:gl0i:w:H:N:u:F:9x:L:B:CEZ:J:SYKOXU:R:Q:P:d:m:o:M:"
#ifdef ENABLE_DEBUGGING
"!:"
#endif
;
int c, numopts;
unsigned int check_pid;
int check_fd;
opts_t opts;
char *leafptr;
opts = calloc(1, sizeof(*opts));
if (NULL == opts) {
/*@-mustfreefresh@ *//* see above */
pv_perror("%s", _("memory allocation failure"));
return NULL;
/*@+mustfreefresh@ */
}
leafptr = strrchr(argv[0], '/');
if (NULL != leafptr) {
opts->program_name = 1 + leafptr;
} else {
leafptr = argv[0]; /* avoid splint "keep" warnings */
opts->program_name = leafptr;
}
/* Set the error message prefix to the parsed program name. */
pv_set_error_prefix(opts->program_name);
opts->argc = 0;
opts->argv = calloc((size_t) (argc + 1), sizeof(char *));
if (NULL == opts->argv) {
/*@-mustfreefresh@ *//* see above */
pv_perror("%s", _("memory allocation failure"));
free(opts); /* can't call opts_free as argv is not set */
return NULL;
/*@+mustfreefresh@ */
}
opts->argv_length = 1 + argc;
numopts = 0;
opts->action = PV_ACTION_TRANSFER;
opts->side = PV_SIDE_NONE;
opts->interval = 1;
opts->delay_start = 0;
opts->average_rate_window = 30;
opts->width_set_manually = false;
opts->height_set_manually = false;
opts->rate_limit_specified = false;
do {
#ifdef HAVE_GETOPT_LONG
c = getopt_long((int) argc, argv, short_options, long_options, &option_index); /* flawfinder: ignore */
#else
c = getopt((int) argc, argv, short_options); /* flawfinder: ignore */
#endif
/*
* flawfinder rationale: argv has to be passed to getopt,
* and limiting the argument sizes would be impractical and
* cumbersome (and likely lead to more bugs); so the system
* getopt has to be trusted not to have internal buffer
* overflows.
*/
if (c < 0)
continue;
/*
* Check that any numeric arguments are of the right type.
*/
switch (c) {
case 's':
/* "-s @" is valid, so allow it. */
if ('@' == *optarg)
break;
/* falls through */
/*@fallthrough@ */
case 'L':
case 'B':
case 'Z':
case 'J':
if (!pv_getnum_check(optarg, PV_NUMTYPE_ANY_WITH_SUFFIX)) {
/*@-mustfreefresh@ *//* see above */
pv_error("-%c: %s: %s", c, optarg, _("numeric value not understood"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
break;
case 'A':
case 'w':
case 'H':
case 'R':
case 'Q':
case 'm':
if (!pv_getnum_check(optarg, PV_NUMTYPE_BARE_INTEGER)) {
/*@-mustfreefresh@ *//* see above */
pv_error("-%c: %s: %s", c, optarg, _("integer argument expected"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
break;
case 'i':
case 'D':
if (!pv_getnum_check(optarg, PV_NUMTYPE_BARE_DOUBLE)) {
/*@-mustfreefresh@ *//* see above */
pv_error("-%c: %s: %s", c, optarg, _("numeric argument expected"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
break;
case 'd':
if ('@' == optarg[0]) {
/* "-d @FILE" syntax - check FILE exists. */
if (optarg[1] == '\0') {
/*@-mustfreefresh@ *//* see above */
pv_error("-%c @: %s", c, _("missing filename"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
} else if (0 != access(optarg + 1, R_OK)) { /* flawfinder: ignore */
/*
* flawfinder rationale: there is no
* TOCTOU issue because access() is
* only being used to inform the
* error message, not to check
* permissions.
*/
/*@-mustfreefresh@ *//* see above */
pv_perror("-%c @: %s", c, optarg + 1);
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
break;
} else if ('=' == optarg[0]) {
/*
* "-d =NAME" syntax - check NAME is not
* blank.
*/
if (optarg[1] == '\0') {
/*@-mustfreefresh@ *//* see above */
pv_error("-%c =: %s", c, _("missing process name"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
break;
}
if (sscanf(optarg, "%u:%d", &check_pid, &check_fd)
< 1) {
/* "-d PID[:FD]" syntax - check numbers. */
/*@-mustfreefresh@ *//* see above */
pv_error("-%c: %s", c, _("process ID or pid:fd pair expected"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
if (check_pid < 1) {
/*@-mustfreefresh@ *//* see above */
pv_error("-%c: %s", c, _("invalid process ID"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
break;
default:
break;
}
/*
* Parse each command line option.
*/
switch (c) {
case 'h':
display_help();
opts->action = PV_ACTION_NOTHING;
return opts; /* Early return. */
case 'V':
display_version();
opts->action = PV_ACTION_NOTHING;
return opts; /* Early return. */
case 'p':
opts->progress = true;
numopts++;
break;
case 't':
opts->timer = true;
numopts++;
break;
case 'I':
opts->fineta = true;
numopts++;
break;
case 'e':
opts->eta = true;
numopts++;
break;
case 'r':
opts->rate = true;
numopts++;
break;
case 'a':
opts->average_rate = true;
numopts++;
break;
case 'b':
opts->bytes = true;
numopts++;
break;
case '8':
opts->bytes = true;
opts->bits = true;
numopts++;
break;
case 'k':
opts->decimal_units = true;
break;
case 'T':
opts->bufpercent = true;
numopts++;
opts->no_splice = true;
break;
case 'A':
opts->lastwritten = (size_t) pv_getnum_count(optarg, opts->decimal_units);
numopts++;
opts->no_splice = true;
break;
case 'f':
opts->force = true;
break;
case '9':
opts->use_osc94 = true;
break;
case 'v':
opts->show_stats = true;
break;
case 'n':
opts->numeric = true;
numopts++;
break;
case 'q':
opts->no_display = true;
numopts++;
break;
case 'c':
opts->cursor = true;
break;
case 'W':
opts->wait = true;
break;
case 'D':
opts->delay_start = pv_getnum_interval(optarg);
break;
case 's':
if ('@' != *optarg) {
/* A number was passed, not "@<filename>". */
opts->size = pv_getnum_size(optarg, opts->decimal_units, NULL);
} else {
/* Permit "@<filename>". */
const char *size_file = 1 + optarg;
if (!opts_use_size_of_file(opts, size_file)) {
opts_free(opts);
return NULL;
}
}
break;
case 'g':
opts->rate_gauge = true;
break;
case 'l':
opts->linemode = true;
break;
case '0':
opts->null_terminated_lines = true;
opts->linemode = true;
break;
case 'i':
opts->interval = pv_getnum_interval(optarg);
break;
case 'w':
opts->width = pv_getnum_count(optarg, opts->decimal_units);
opts->width_set_manually = opts->width == 0 ? false : true;
break;
case 'H':
opts->height = pv_getnum_count(optarg, opts->decimal_units);
opts->height_set_manually = opts->height == 0 ? false : true;
break;
case 'N':
opts->name1 = opts->name;
opts->name = pv_strdup(optarg);
if (NULL == opts->name) {
pv_perror("-%c", c);
opts_free(opts);
return NULL;
}
break;
case 'u':
opts->default_bar_style = pv_strdup(optarg);
if (NULL == opts->default_bar_style) {
pv_perror("-%c", c);
opts_free(opts);
return NULL;
}
break;
case 'L':
(void) pv_getnum_size(optarg, opts->decimal_units, &(opts->rate_limit));
opts->rate_limit_specified = true;
#if HAVE_MATH_H
/*@-unrecog@ *//* splint doesn't know nextafterl(). */
if (opts->rate_limit < nextafterl(0, INFINITY)) {
opts->rate_limit_active = false;
} else {
opts->rate_limit_active = true;
}
/*@+unrecog@ */
#else
opts->rate_limit_active = false;
if (opts->rate_limit > 0.00001)
opts->rate_limit_active = true;
#endif
break;
case 'B':
opts->buffer_size = (size_t) pv_getnum_size(optarg, opts->decimal_units, NULL);
opts->no_splice = true;
break;
case 'C':
opts->no_splice = true;
break;
case 'E':
opts->skip_errors++;
break;
case 'Z':
opts->error_skip_block = pv_getnum_size(optarg, opts->decimal_units, NULL);
break;
case 'J':
opts->pipe_buffer_size = (size_t) pv_getnum_size(optarg, opts->decimal_units, NULL);
break;
case 'S':
opts->stop_at_size = true;
break;
case 'Y':
opts->sync_after_write = true;
break;
case 'K':
opts->direct_io = true;
break;
case 'O':
opts->sparse_output = true;
opts->no_splice = true;
break;
case 'X':
opts->discard_input = true;
break;
case 'U':
opts->store_and_forward_file = pv_strdup(optarg);
if (NULL == opts->store_and_forward_file) {
pv_perror("-%c", c);
opts_free(opts);
return NULL;
}
opts->action = PV_ACTION_STORE_AND_FORWARD;
break;
case 'R':
opts->remote = (pid_t) pv_getnum_count(optarg, false);
opts->action = PV_ACTION_REMOTE_CONTROL;
break;
case 'Q':
opts->query = (pid_t) pv_getnum_count(optarg, false);
opts->action = PV_ACTION_QUERY;
break;
case 'P':
opts->pidfile = pv_strdup(optarg);
if (NULL == opts->pidfile) {
pv_perror("-%c", c);
opts_free(opts);
return NULL;
}
break;
case 'F':
opts->format1 = opts->format;
opts->format = pv_strdup(optarg);
if (NULL == opts->format) {
pv_perror("-%c", c);
opts_free(opts);
return NULL;
}
break;
case 'x':
opts->extra_display = pv_strdup(optarg);
if (NULL == opts->extra_display) {
pv_perror("-%c", c);
opts_free(opts);
return NULL;
}
break;
case 'd':
if (!opts_watchfd_parse(opts, optarg, NULL, 0)) {
opts_free(opts);
return NULL;
}
opts->action = PV_ACTION_WATCHFD;
break;
case 'o':
opts->output = pv_strdup(optarg);
if (NULL == opts->output) {
pv_perror("-%c", c);
opts_free(opts);
return NULL;
}
break;
case 'm':
opts->average_rate_window = pv_getnum_count(optarg, opts->decimal_units);
break;
case 'M':
opts->action = PV_ACTION_MONITOR;
if (string_starts_with(optarg, "0")) {
opts->side = PV_SIDE_IN;
} else if (string_starts_with(optarg, "in")) {
opts->side = PV_SIDE_IN;
} else if (string_starts_with(optarg, "stdin")) {
opts->side = PV_SIDE_IN;
} else if (string_starts_with(optarg, "1")) {
opts->side = PV_SIDE_OUT;
} else if (string_starts_with(optarg, "out")) {
opts->side = PV_SIDE_OUT;
} else if (string_starts_with(optarg, "stdout")) {
opts->side = PV_SIDE_OUT;
} else if (string_starts_with(optarg, "2")) {
opts->side = PV_SIDE_BOTH;
} else if (string_starts_with(optarg, "both")) {
opts->side = PV_SIDE_BOTH;
} else {
/*@-mustfreefresh@ *//* see above */
pv_error("-M: %s: %s", optarg, _("invalid side specification"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
break;
#ifdef ENABLE_DEBUGGING
case '!':
debugging_output_destination(optarg);
break;
#endif /* ENABLE_DEBUGGING */
default:
/*@-mustfreefresh@ *//* see above */
/*@-formatconst@ */
#ifdef HAVE_GETOPT_LONG
fprintf(stderr, _("Try `%s --help' for more information."), opts->program_name);
#else
fprintf(stderr, _("Try `%s -h' for more information."), opts->program_name);
#endif
/*@+formatconst@ */
/*
* splint note: formatconst is warning about the use
* of a non constant (translatable) format string;
* this is unavoidable here and the only attack
* vector is through the message catalogue.
*/
fprintf(stderr, "\n");
opts_free(opts);
opts = NULL;
return NULL; /* early return */
/*@+mustfreefresh@ */
}
} while (c != -1);
/*
* splint thinks this point is reachable after opts_free() and
* opts=NULL above, so explicitly return here if opts was set to
* NULL.
*/
if (NULL == opts)
return NULL;
if (PV_ACTION_WATCHFD == opts->action) {
if (opts->linemode || opts->null_terminated_lines || opts->stop_at_size
|| (opts->skip_errors > 0) || (opts->buffer_size > 0)
|| (opts->rate_limit_active)) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s",
_("cannot use line mode or transfer modifier options when watching file descriptors"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
if (opts->cursor) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s", _("cannot use cursor positioning when watching file descriptors"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
if (0 != opts->remote) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s", _("cannot use remote control when watching file descriptors"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
if (0 != opts->query) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s", _("cannot use remote query when watching file descriptors"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
if (NULL != opts->output) {
/*@-mustfreefresh@ *//* see above */
pv_error("-o: %s", _("cannot transfer files when watching file descriptors"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
/* Accept additional watchfd arguments. */
while (optind < (int) argc) {
if (!opts_watchfd_parse(opts, argv[optind], NULL, 0)) {
opts_free(opts);
return NULL;
}
optind++;
}
#ifndef __APPLE__
if (0 != access("/proc/self/fdinfo", X_OK)) { /* flawfinder: ignore */
/*
* flawfinder rationale: access() is used here as a
* low cost stat() to see whether the path exists at
* all, under a path only modifiable by root, so is
* unlikely to be exploitable.
*/
/*@-mustfreefresh@ *//* see above */
pv_error("-d: %s", _("not available on systems without /proc/self/fdinfo"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
#endif
}
/* Don't allow -R and -Q together. */
if ((0 != opts->remote) && (0 != opts->query)) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s", _("cannot use remote control and remote query together"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
/*
* Default options: -pterb
*/
if (0 == numopts) {
opts->progress = true;
opts->timer = true;
opts->eta = true;
opts->rate = true;
opts->bytes = true;
}
/* If -Z was given but not -E, behave as if one -E was given too. */
if (opts->error_skip_block > 0 && 0 == opts->skip_errors)
opts->skip_errors = 1;
/*
* Don't allow -R or -Q with -M.
*/
if ((PV_ACTION_MONITOR == opts->action) && ((0 != opts->remote) || (0 != opts->query))) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s: %s", 0 != opts->remote ? "-R" : "-Q",
_("monitor mode cannot be specified with this option"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
/*
* Don't allow -U with -M.
*/
if ((PV_ACTION_STORE_AND_FORWARD == opts->action && PV_SIDE_NONE != opts->side)
|| (PV_ACTION_MONITOR == opts->action && NULL != opts->store_and_forward_file)) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s", _("monitor mode cannot be used with store-and-forward"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
/*
* Don't allow any non-option arguments with -R or -Q.
*/
if ((optind < (int) argc) && ((0 != opts->remote) || (0 != opts->query))) {
/*@-mustfreefresh@ *//* see above */
pv_error("%s: %s", 0 != opts->remote ? "-R" : "-Q", _("files cannot be specified with this option"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
/*
* At least one non-option argument is required with -M.
*/
if ((PV_ACTION_MONITOR == opts->action) && (optind >= (int) argc)) {
/*@-mustfreefresh@ *//* see above */
pv_error("-M: %s", _("a command to run must be specified"));
opts_free(opts);
return NULL;
/*@+mustfreefresh@ */
}
/*
* Store remaining command-line arguments.
*/
while (optind < (int) argc) {
if (!opts_add_file(opts, argv[optind++])) {
opts_free(opts);
return NULL;
}
}
return opts;
}