diff options
Diffstat (limited to 'misc-utils/pipesz.c')
-rw-r--r-- | misc-utils/pipesz.c | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/misc-utils/pipesz.c b/misc-utils/pipesz.c new file mode 100644 index 0000000..f586acb --- /dev/null +++ b/misc-utils/pipesz.c @@ -0,0 +1,350 @@ +/* + * 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]); +} |