]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
pipesz: add the pipesz utility
authorNathan Sharp <nwsharp@live.com>
Tue, 12 Apr 2022 04:59:27 +0000 (22:59 -0600)
committerKarel Zak <kzak@redhat.com>
Wed, 18 May 2022 08:35:26 +0000 (10:35 +0200)
pipesz is a utility to examine and adjust the size of pipe buffers.

It uses fctnl F_GETPIPE_SZ and F_SETPIPE_SZ to examine and resize
these buffers. This functionality is unique to Linux and was added in
version 2.6.35. Minor bugfixes were made in 4.9, but these do not
obviate the use of pipesz prior to that release.

Signed-off-by: Nathan Sharp <nwsharp@live.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
.gitignore
configure.ac
include/pathnames.h
meson.build
meson_options.txt
misc-utils/Makemodule.am
misc-utils/meson.build
misc-utils/pipesz.c [new file with mode: 0644]

index 05ec48c9b87bdbfc8668675f984cc24f3bdb865b..66b2d2602a73e85fe27d3b8b286e9039e57ff791 100644 (file)
@@ -148,6 +148,7 @@ GSYMS
 /nsenter
 /partx
 /pg
+/pipesz
 /pivot_root
 /prlimit
 /raw
index 2c3b432fd4338bedc624b4166fd94c3d5c6126a9..3ac79a503588978ecf2c9e958f85bc2bd01d3389 100644 (file)
@@ -1785,6 +1785,14 @@ AC_ARG_ENABLE([whereis],
 UL_BUILD_INIT([whereis])
 AM_CONDITIONAL([BUILD_WHEREIS], [test "x$build_whereis" = xyes])
 
+AC_ARG_ENABLE([pipesz],
+  AS_HELP_STRING([--disable-pipesz], [do not build pipesz]),
+  [], [UL_DEFAULT_ENABLE([pipesz])]
+)
+UL_BUILD_INIT([pipesz])
+UL_REQUIRES_LINUX([pipesz])
+AM_CONDITIONAL([BUILD_PIPESZ], [test "x$build_pipesz" = xyes])
+
 UL_BUILD_INIT([getopt], [yes])
 AM_CONDITIONAL([BUILD_GETOPT], [test "x$build_getopt" = xyes])
 
index d86d9d5feb94d98a6a21aab7de3ac95b5e27c3e2..0887bd76eba7a8666edf594692d1ffd38fc836c1 100644 (file)
 #define _PATH_PROC_UCLAMP_MIN  _PATH_PROC_KERNEL "/sched_util_clamp_min"
 #define _PATH_PROC_UCLAMP_MAX  _PATH_PROC_KERNEL "/sched_util_clamp_max"
 
+/* sysctl fs paths */
+#define _PATH_PROC_SYS_FS      "/proc/sys/fs"
+#define _PATH_PROC_PIPE_MAX_SIZE       _PATH_PROC_SYS_FS "/pipe-max-size"
+
 /* irqtop paths */
 #define _PATH_PROC_INTERRUPTS  "/proc/interrupts"
 #define _PATH_PROC_SOFTIRQS    "/proc/softirqs"
index b085c4202bcb6cfbb730ecc4053a5dd4581665e2..204e4ac676ddf152de30665eb9112448527f56af 100644 (file)
@@ -2683,6 +2683,18 @@ if not is_disabler(exe)
   bashcompletions += ['hardlink']
 endif
 
+opt = not get_option('build-pipesz').disabled()
+exe = executable(
+  'pipesz',
+  pipesz_sources,
+  include_directories : includes,
+  link_with : [lib_common],
+  install_dir : usrbin_exec_dir,
+  install : true)
+if opt and not is_disabler(exe)
+  exes += exe
+endif
+
 exe = executable(
   'test_cal',
   cal_sources,
index 64c9924a213534bd2379dff5a1642258759bbc53..5272cb317c97d6f8a54ccaaaf772c306a411ffda 100644 (file)
@@ -150,6 +150,8 @@ option('build-more', type : 'feature',
        description : 'build more')
 option('build-pg', type : 'feature',
        description : 'build pg')
+option('build-pipesz', type : 'feature',
+       description : 'build pipesz')
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
index 52e457d23cb4c4bd3ba7d64f92962ad53ffa2610..9d8196524f475c55f9770652c498f8d7d1627484 100644 (file)
@@ -267,3 +267,10 @@ lsfd_SOURCES = \
 lsfd_LDADD = $(LDADD) libsmartcols.la libcommon.la
 lsfd_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
 endif
+
+if BUILD_PIPESZ
+bin_PROGRAMS += pipesz
+pipesz_SOURCES = misc-utils/pipesz.c
+pipesz_LDADD = $(LDADD) libcommon.la
+pipesz_CFLAGS = $(AM_CFLAGS)
+endif
index 6c0952a06a602ec4fb2af9eb453b9922ef89d60d..405f1ccf00bbc8bda42367a0e7d97e191e820e1c 100644 (file)
@@ -140,3 +140,7 @@ hardlink_sources = files(
 cal_sources = files(
   'cal.c',
 )
+
+pipesz_sources = files(
+  'pipesz.c',
+)
diff --git a/misc-utils/pipesz.c b/misc-utils/pipesz.c
new file mode 100644 (file)
index 0000000..76c1b15
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * 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;
+                       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]);
+}