You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
8.5 KiB
350 lines
8.5 KiB
/* |
|
* pipesz(1) - Set or examine pipe buffer sizes. |
|
* |
|
* Copyright (c) 2022 Nathan Sharp |
|
* Written by Nathan Sharp <nwsharp@live.com> |
|
* |
|
* 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. |
|
* |
|
* This program is distributed in the hope that it would 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, write to the Free Software Foundation, Inc., |
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
*/ |
|
|
|
#include <getopt.h> |
|
#include <sys/ioctl.h> /* FIONREAD */ |
|
#include <fcntl.h> /* F_GETPIPE_SZ F_SETPIPE_SZ */ |
|
|
|
#include "c.h" |
|
#include "nls.h" |
|
|
|
#include "closestream.h" /* close_stdout_atexit */ |
|
#include "optutils.h" /* err_exclusive_options */ |
|
#include "path.h" /* ul_path_read_s32 */ |
|
#include "pathnames.h" /* _PATH_PROC_PIPE_MAX_SIZE */ |
|
#include "strutils.h" /* strtos32_or_err strtosize_or_err */ |
|
|
|
static char opt_check = 0; /* --check */ |
|
static char opt_get = 0; /* --get */ |
|
static char opt_quiet = 0; /* --quiet */ |
|
static int opt_size = -1; /* --set <size> */ |
|
static char opt_verbose = 0; /* --verbose */ |
|
|
|
/* fallback file for default size */ |
|
#ifndef PIPESZ_DEFAULT_SIZE_FILE |
|
#define PIPESZ_DEFAULT_SIZE_FILE _PATH_PROC_PIPE_MAX_SIZE |
|
#endif |
|
|
|
/* convenience macros, since pipesz is by default very lenient */ |
|
#define check(FMT...) do { \ |
|
if (opt_check) { \ |
|
err(EXIT_FAILURE, FMT); \ |
|
} else if (!opt_quiet) { \ |
|
warn(FMT); \ |
|
} \ |
|
} while (0) |
|
|
|
#define checkx(FMT...) do { \ |
|
if (opt_check) { \ |
|
errx(EXIT_FAILURE, FMT); \ |
|
} else if (!opt_quiet) { \ |
|
warnx(FMT); \ |
|
} \ |
|
} while (0) |
|
|
|
static void __attribute__((__noreturn__)) usage(void) |
|
{ |
|
fputs(USAGE_HEADER, stdout); |
|
printf(_(" %s [options] [--set <size>] [--] [command]\n"), program_invocation_short_name); |
|
printf(_(" %s [options] --get\n"), program_invocation_short_name); |
|
|
|
fputs(USAGE_SEPARATOR, stdout); |
|
/* TRANSLATORS: 'command' refers to a program argument */ |
|
puts(_("Set or examine pipe buffer sizes and optionally execute command.")); |
|
|
|
fputs(USAGE_OPTIONS, stdout); |
|
puts(_(" -g, --get examine pipe buffers")); |
|
/* TRANSLATORS: '%s' refers to a system file */ |
|
printf( |
|
_(" -s, --set <size> set pipe buffer sizes\n" |
|
" size defaults to %s\n"), |
|
PIPESZ_DEFAULT_SIZE_FILE); |
|
|
|
fputs(USAGE_SEPARATOR, stdout); |
|
puts(_(" -f, --file <path> act on a file")); |
|
puts(_(" -n, --fd <num> act on a file descriptor")); |
|
puts(_(" -i, --stdin act on standard input")); |
|
puts(_(" -o, --stdout act on standard output")); |
|
puts(_(" -e, --stderr act on standard error")); |
|
|
|
fputs(USAGE_SEPARATOR, stdout); |
|
puts(_(" -c, --check do not continue after an error")); |
|
puts(_(" -q, --quiet do not warn of non-fatal errors")); |
|
puts(_(" -v, --verbose provide detailed output")); |
|
|
|
fputs(USAGE_SEPARATOR, stdout); |
|
printf(USAGE_HELP_OPTIONS(20)); |
|
|
|
printf(USAGE_MAN_TAIL("pipesz(1)")); |
|
|
|
exit(EXIT_SUCCESS); |
|
} |
|
|
|
/* |
|
* performs F_GETPIPE_SZ and FIONREAD |
|
* outputs a table row |
|
*/ |
|
static void do_get(int fd, const char *name) |
|
{ |
|
int sz, used; |
|
|
|
sz = fcntl(fd, F_GETPIPE_SZ); |
|
if (sz < 0) { |
|
/* TRANSLATORS: '%s' refers to a file */ |
|
check(_("cannot get pipe buffer size of %s"), name); |
|
return; |
|
} |
|
|
|
if (ioctl(fd, FIONREAD, &used)) |
|
used = 0; |
|
|
|
printf("%s\t%d\t%d\n", name, sz, used); |
|
} |
|
|
|
/* |
|
* performs F_SETPIPE_SZ |
|
*/ |
|
static void do_set(int fd, const char *name) |
|
{ |
|
int sz; |
|
|
|
sz = fcntl(fd, F_SETPIPE_SZ, opt_size); |
|
if (sz < 0) |
|
/* TRANSLATORS: '%s' refers to a file */ |
|
check(_("cannot set pipe buffer size of %s"), name); |
|
else if (opt_verbose) |
|
/* TRANSLATORS: '%s' refers to a file, '%d' to a buffer size in bytes */ |
|
warnx(_("%s pipe buffer size set to %d"), name, sz); |
|
} |
|
|
|
/* |
|
* does the requested operation on an fd |
|
*/ |
|
static void do_fd(int fd) |
|
{ |
|
char name[sizeof(stringify(INT_MIN)) + 3]; |
|
|
|
sprintf(name, "fd %d", fd); |
|
|
|
if (opt_get) |
|
do_get(fd, name); |
|
else |
|
do_set(fd, name); |
|
} |
|
|
|
/* |
|
* does the requested operation on a file |
|
*/ |
|
static void do_file(const char *path) |
|
{ |
|
int fd; |
|
|
|
fd = open(path, O_RDONLY | O_CLOEXEC); |
|
if (fd < 0) { |
|
/* TRANSLATORS: '%s' refers to a file */ |
|
check(_("cannot open %s"), path); |
|
return; |
|
} |
|
|
|
if (opt_get) |
|
do_get(fd, path); |
|
else |
|
do_set(fd, path); |
|
|
|
close(fd); |
|
} |
|
|
|
/* |
|
* if necessary, determines a default buffer size and places it in opt_size |
|
* returns FALSE if this could not be done |
|
*/ |
|
static char set_size_default(void) |
|
{ |
|
if (opt_size >= 0) |
|
return TRUE; |
|
|
|
if (ul_path_read_s32(NULL, &opt_size, PIPESZ_DEFAULT_SIZE_FILE)) { |
|
/* TRANSLATORS: '%s' refers to a system file */ |
|
check(_("cannot parse %s"), PIPESZ_DEFAULT_SIZE_FILE); |
|
return FALSE; |
|
} |
|
|
|
if (opt_size < 0) { |
|
/* TRANSLATORS: '%s' refers to a system file */ |
|
checkx(_("cannot parse %s"), PIPESZ_DEFAULT_SIZE_FILE); |
|
return FALSE; |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
static const char shortopts[] = "+cef:ghin:oqs:vV"; |
|
static const struct option longopts[] = { |
|
{ "check", no_argument, NULL, 'c' }, |
|
{ "fd", required_argument, NULL, 'n' }, |
|
{ "file", required_argument, NULL, 'f' }, |
|
{ "get", no_argument, NULL, 'g' }, |
|
{ "help", no_argument, NULL, 'h' }, |
|
{ "quiet", no_argument, NULL, 'q' }, |
|
{ "set", required_argument, NULL, 's' }, |
|
{ "stdin", no_argument, NULL, 'i' }, |
|
{ "stdout", no_argument, NULL, 'o' }, |
|
{ "stderr", no_argument, NULL, 'e' }, |
|
{ "verbose", no_argument, NULL, 'v' }, |
|
{ "version", no_argument, NULL, 'V' }, |
|
{ NULL, 0, NULL, 0 } |
|
}; |
|
static const ul_excl_t excl[] = { |
|
{ 'g', 's' }, |
|
{ 0 } |
|
}; |
|
|
|
int c, fd, n_opt_pipe = 0, n_opt_size = 0; |
|
uintmax_t sz; |
|
|
|
int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; |
|
|
|
setlocale(LC_ALL, ""); |
|
bindtextdomain(PACKAGE, LOCALEDIR); |
|
textdomain(PACKAGE); |
|
close_stdout_atexit(); |
|
|
|
/* check for --help or --version */ |
|
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { |
|
switch (c) { |
|
case 'h': |
|
usage(); |
|
case 'V': |
|
print_version(EXIT_SUCCESS); |
|
} |
|
} |
|
|
|
/* gather normal options */ |
|
optind = 1; |
|
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { |
|
err_exclusive_options(c, longopts, excl, excl_st); |
|
|
|
switch (c) { |
|
case 'c': |
|
opt_check = TRUE; |
|
break; |
|
case 'e': |
|
++n_opt_pipe; |
|
break; |
|
case 'f': |
|
++n_opt_pipe; |
|
break; |
|
case 'g': |
|
opt_get = TRUE; |
|
break; |
|
case 'i': |
|
++n_opt_pipe; |
|
break; |
|
case 'n': |
|
fd = strtos32_or_err(optarg, _("invalid fd argument")); |
|
++n_opt_pipe; |
|
break; |
|
case 'o': |
|
++n_opt_pipe; |
|
break; |
|
case 'q': |
|
opt_quiet = TRUE; |
|
break; |
|
case 's': |
|
sz = strtosize_or_err(optarg, _("invalid size argument")); |
|
opt_size = sz >= INT_MAX ? INT_MAX : (int)sz; |
|
++n_opt_size; |
|
break; |
|
case 'v': |
|
opt_verbose = TRUE; |
|
break; |
|
default: |
|
errtryhelp(EXIT_FAILURE); |
|
} |
|
} |
|
|
|
/* check arguments */ |
|
if (opt_get) { |
|
if (argv[optind]) |
|
errx(EXIT_FAILURE, _("cannot specify a command with --get")); |
|
|
|
/* print column headers, if requested */ |
|
if (opt_verbose) |
|
printf("%s\t%s\t%s\n", |
|
/* TRANSLATORS: a column that contains the names of files that are unix pipes */ |
|
_("pipe"), |
|
/* TRANSLATORS: a column that contains buffer sizes in bytes */ |
|
_("size"), |
|
/* TRANSLATORS: a column that contains an amount of data which has not been used by a program */ |
|
_("unread") |
|
); |
|
|
|
/* special behavior for --get */ |
|
if (!n_opt_pipe) { |
|
do_fd(STDIN_FILENO); |
|
return EXIT_SUCCESS; |
|
} |
|
} else { |
|
if (!set_size_default()) |
|
goto execute_command; |
|
|
|
if (!opt_quiet && n_opt_size > 1) |
|
warnx(_("using last specified size")); |
|
|
|
/* special behavior for --set */ |
|
if (!n_opt_pipe) { |
|
do_fd(STDOUT_FILENO); |
|
goto execute_command; |
|
} |
|
} |
|
|
|
/* go through the arguments again and do the requested operations */ |
|
optind = 1; |
|
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) |
|
switch (c) { |
|
case 'e': |
|
do_fd(STDERR_FILENO); |
|
break; |
|
case 'f': |
|
do_file(optarg); |
|
break; |
|
case 'i': |
|
do_fd(STDIN_FILENO); |
|
break; |
|
case 'n': |
|
/* optarg was checked before, but it's best to be safe */ |
|
fd = strtos32_or_err(optarg, _("invalid fd argument")); |
|
do_fd(fd); |
|
break; |
|
case 'o': |
|
do_fd(STDOUT_FILENO); |
|
break; |
|
} |
|
|
|
execute_command: |
|
/* exec the command, if it's present */ |
|
if (!argv[optind]) |
|
return EXIT_SUCCESS; |
|
|
|
execvp(argv[optind], &argv[optind]); |
|
errexec(argv[optind]); |
|
}
|
|
|