[PATCH RFC 1/4] misc-utils: add the pipesz utility

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxx>
---
 .gitignore               |   1 +
 configure.ac             |   8 +
 include/pathnames.h      |   4 +
 meson.build              |  12 ++
 meson_options.txt        |   2 +
 misc-utils/Makemodule.am |   7 +
 misc-utils/meson.build   |   4 +
 misc-utils/pipesz.c      | 347 +++++++++++++++++++++++++++++++++++++++
 8 files changed, 385 insertions(+)
 create mode 100644 misc-utils/pipesz.c

diff --git a/.gitignore b/.gitignore
index 840f646..a31e7e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -143,6 +143,7 @@ ylwrap
 /nsenter
 /partx
 /pg
+/pipesz
 /pivot_root
 /prlimit
 /raw
diff --git a/configure.ac b/configure.ac
index 2c3b432..3ac79a5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
 
diff --git a/include/pathnames.h b/include/pathnames.h
index d86d9d5..0887bd7 100644
--- a/include/pathnames.h
+++ b/include/pathnames.h
@@ -198,6 +198,10 @@
 #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"
diff --git a/meson.build b/meson.build
index 0642ab5..358572e 100644
--- a/meson.build
+++ b/meson.build
@@ -2678,6 +2678,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,
diff --git a/meson_options.txt b/meson_options.txt
index 64c9924..5272cb3 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -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',
diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am
index cc18acb..60c7fe1 100644
--- a/misc-utils/Makemodule.am
+++ b/misc-utils/Makemodule.am
@@ -266,3 +266,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
diff --git a/misc-utils/meson.build b/misc-utils/meson.build
index d435207..16310a9 100644
--- a/misc-utils/meson.build
+++ b/misc-utils/meson.build
@@ -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
index 0000000..1283d86
--- /dev/null
+++ b/misc-utils/pipesz.c
@@ -0,0 +1,347 @@
+/*
+ * pipesz(1) - Set or examine pipe buffer sizes.
+ *
+ * Copyright (c) 2022 Nathan Sharp
+ * Written by Nathan Sharp <nwsharp@xxxxxxxx>
+ *
+ * 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]);
+}
-- 
2.35.1




[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux