]>
Commit | Line | Data |
---|---|---|
c221578e NS |
1 | /* |
2 | * pipesz(1) - Set or examine pipe buffer sizes. | |
3 | * | |
4 | * Copyright (c) 2022 Nathan Sharp | |
5 | * Written by Nathan Sharp <nwsharp@live.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License as | |
9 | * published by the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it would be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along | |
17 | * with this program; if not, write to the Free Software Foundation, Inc., | |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
19 | */ | |
20 | ||
21 | #include <getopt.h> | |
22 | #include <sys/ioctl.h> /* FIONREAD */ | |
23 | #include <fcntl.h> /* F_GETPIPE_SZ F_SETPIPE_SZ */ | |
24 | ||
25 | #include "c.h" | |
26 | #include "nls.h" | |
27 | ||
28 | #include "closestream.h" /* close_stdout_atexit */ | |
29 | #include "optutils.h" /* err_exclusive_options */ | |
30 | #include "path.h" /* ul_path_read_s32 */ | |
31 | #include "pathnames.h" /* _PATH_PROC_PIPE_MAX_SIZE */ | |
32 | #include "strutils.h" /* strtos32_or_err strtosize_or_err */ | |
33 | ||
34 | static char opt_check = 0; /* --check */ | |
410b261c | 35 | static char opt_get = 0; /* --get */ |
c221578e NS |
36 | static char opt_quiet = 0; /* --quiet */ |
37 | static int opt_size = -1; /* --set <size> */ | |
38 | static char opt_verbose = 0; /* --verbose */ | |
39 | ||
40 | /* fallback file for default size */ | |
41 | #ifndef PIPESZ_DEFAULT_SIZE_FILE | |
42 | #define PIPESZ_DEFAULT_SIZE_FILE _PATH_PROC_PIPE_MAX_SIZE | |
43 | #endif | |
44 | ||
45 | /* convenience macros, since pipesz is by default very lenient */ | |
46 | #define check(FMT...) do { \ | |
47 | if (opt_check) { \ | |
48 | err(EXIT_FAILURE, FMT); \ | |
49 | } else if (!opt_quiet) { \ | |
50 | warn(FMT); \ | |
51 | } \ | |
52 | } while (0) | |
53 | ||
54 | #define checkx(FMT...) do { \ | |
55 | if (opt_check) { \ | |
56 | errx(EXIT_FAILURE, FMT); \ | |
57 | } else if (!opt_quiet) { \ | |
58 | warnx(FMT); \ | |
59 | } \ | |
60 | } while (0) | |
61 | ||
62 | static void __attribute__((__noreturn__)) usage(void) | |
63 | { | |
64 | fputs(USAGE_HEADER, stdout); | |
bad4c729 MY |
65 | fprintf(stdout, _(" %s [options] [--set <size>] [--] [command]\n"), program_invocation_short_name); |
66 | fprintf(stdout, _(" %s [options] --get\n"), program_invocation_short_name); | |
c221578e NS |
67 | |
68 | fputs(USAGE_SEPARATOR, stdout); | |
69 | /* TRANSLATORS: 'command' refers to a program argument */ | |
f11785b5 | 70 | fputsln(_("Set or examine pipe buffer sizes and optionally execute command."), stdout); |
c221578e NS |
71 | |
72 | fputs(USAGE_OPTIONS, stdout); | |
f11785b5 | 73 | fputsln(_(" -g, --get examine pipe buffers"), stdout); |
c221578e | 74 | /* TRANSLATORS: '%s' refers to a system file */ |
bad4c729 | 75 | fprintf(stdout, |
410b261c KZ |
76 | _(" -s, --set <size> set pipe buffer sizes\n" |
77 | " size defaults to %s\n"), | |
78 | PIPESZ_DEFAULT_SIZE_FILE); | |
c221578e NS |
79 | |
80 | fputs(USAGE_SEPARATOR, stdout); | |
f11785b5 TW |
81 | fputsln(_(" -f, --file <path> act on a file"), stdout); |
82 | fputsln(_(" -n, --fd <num> act on a file descriptor"), stdout); | |
83 | fputsln(_(" -i, --stdin act on standard input"), stdout); | |
84 | fputsln(_(" -o, --stdout act on standard output"), stdout); | |
85 | fputsln(_(" -e, --stderr act on standard error"), stdout); | |
c221578e NS |
86 | |
87 | fputs(USAGE_SEPARATOR, stdout); | |
f11785b5 TW |
88 | fputsln(_(" -c, --check do not continue after an error"), stdout); |
89 | fputsln(_(" -q, --quiet do not warn of non-fatal errors"), stdout); | |
90 | fputsln(_(" -v, --verbose provide detailed output"), stdout); | |
c221578e NS |
91 | |
92 | fputs(USAGE_SEPARATOR, stdout); | |
bad4c729 | 93 | fprintf(stdout, USAGE_HELP_OPTIONS(20)); |
c221578e | 94 | |
bad4c729 | 95 | fprintf(stdout, USAGE_MAN_TAIL("pipesz(1)")); |
c221578e NS |
96 | |
97 | exit(EXIT_SUCCESS); | |
98 | } | |
99 | ||
100 | /* | |
101 | * performs F_GETPIPE_SZ and FIONREAD | |
102 | * outputs a table row | |
103 | */ | |
104 | static void do_get(int fd, const char *name) | |
105 | { | |
106 | int sz, used; | |
410b261c | 107 | |
c221578e NS |
108 | sz = fcntl(fd, F_GETPIPE_SZ); |
109 | if (sz < 0) { | |
110 | /* TRANSLATORS: '%s' refers to a file */ | |
111 | check(_("cannot get pipe buffer size of %s"), name); | |
112 | return; | |
113 | } | |
114 | ||
115 | if (ioctl(fd, FIONREAD, &used)) | |
116 | used = 0; | |
117 | ||
118 | printf("%s\t%d\t%d\n", name, sz, used); | |
119 | } | |
120 | ||
121 | /* | |
122 | * performs F_SETPIPE_SZ | |
123 | */ | |
124 | static void do_set(int fd, const char *name) | |
125 | { | |
126 | int sz; | |
410b261c | 127 | |
c221578e NS |
128 | sz = fcntl(fd, F_SETPIPE_SZ, opt_size); |
129 | if (sz < 0) | |
130 | /* TRANSLATORS: '%s' refers to a file */ | |
131 | check(_("cannot set pipe buffer size of %s"), name); | |
132 | else if (opt_verbose) | |
133 | /* TRANSLATORS: '%s' refers to a file, '%d' to a buffer size in bytes */ | |
134 | warnx(_("%s pipe buffer size set to %d"), name, sz); | |
135 | } | |
136 | ||
137 | /* | |
138 | * does the requested operation on an fd | |
139 | */ | |
140 | static void do_fd(int fd) | |
141 | { | |
142 | char name[sizeof(stringify(INT_MIN)) + 3]; | |
143 | ||
144 | sprintf(name, "fd %d", fd); | |
145 | ||
146 | if (opt_get) | |
147 | do_get(fd, name); | |
148 | else | |
149 | do_set(fd, name); | |
150 | } | |
151 | ||
152 | /* | |
153 | * does the requested operation on a file | |
154 | */ | |
155 | static void do_file(const char *path) | |
156 | { | |
157 | int fd; | |
158 | ||
159 | fd = open(path, O_RDONLY | O_CLOEXEC); | |
160 | if (fd < 0) { | |
161 | /* TRANSLATORS: '%s' refers to a file */ | |
162 | check(_("cannot open %s"), path); | |
163 | return; | |
164 | } | |
165 | ||
166 | if (opt_get) | |
167 | do_get(fd, path); | |
168 | else | |
169 | do_set(fd, path); | |
170 | ||
171 | close(fd); | |
172 | } | |
173 | ||
174 | /* | |
175 | * if necessary, determines a default buffer size and places it in opt_size | |
176 | * returns FALSE if this could not be done | |
177 | */ | |
178 | static char set_size_default(void) | |
179 | { | |
180 | if (opt_size >= 0) | |
181 | return TRUE; | |
182 | ||
183 | if (ul_path_read_s32(NULL, &opt_size, PIPESZ_DEFAULT_SIZE_FILE)) { | |
184 | /* TRANSLATORS: '%s' refers to a system file */ | |
185 | check(_("cannot parse %s"), PIPESZ_DEFAULT_SIZE_FILE); | |
186 | return FALSE; | |
187 | } | |
188 | ||
189 | if (opt_size < 0) { | |
190 | /* TRANSLATORS: '%s' refers to a system file */ | |
191 | checkx(_("cannot parse %s"), PIPESZ_DEFAULT_SIZE_FILE); | |
192 | return FALSE; | |
193 | } | |
194 | ||
195 | return TRUE; | |
196 | } | |
197 | ||
198 | int main(int argc, char **argv) | |
199 | { | |
200 | static const char shortopts[] = "+cef:ghin:oqs:vV"; | |
201 | static const struct option longopts[] = { | |
202 | { "check", no_argument, NULL, 'c' }, | |
203 | { "fd", required_argument, NULL, 'n' }, | |
204 | { "file", required_argument, NULL, 'f' }, | |
205 | { "get", no_argument, NULL, 'g' }, | |
206 | { "help", no_argument, NULL, 'h' }, | |
207 | { "quiet", no_argument, NULL, 'q' }, | |
208 | { "set", required_argument, NULL, 's' }, | |
209 | { "stdin", no_argument, NULL, 'i' }, | |
210 | { "stdout", no_argument, NULL, 'o' }, | |
211 | { "stderr", no_argument, NULL, 'e' }, | |
212 | { "verbose", no_argument, NULL, 'v' }, | |
213 | { "version", no_argument, NULL, 'V' }, | |
214 | { NULL, 0, NULL, 0 } | |
215 | }; | |
216 | static const ul_excl_t excl[] = { | |
217 | { 'g', 's' }, | |
218 | { 0 } | |
219 | }; | |
220 | ||
221 | int c, fd, n_opt_pipe = 0, n_opt_size = 0; | |
222 | uintmax_t sz; | |
223 | ||
224 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
225 | ||
226 | setlocale(LC_ALL, ""); | |
227 | bindtextdomain(PACKAGE, LOCALEDIR); | |
228 | textdomain(PACKAGE); | |
229 | close_stdout_atexit(); | |
230 | ||
231 | /* check for --help or --version */ | |
410b261c | 232 | while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { |
c221578e NS |
233 | switch (c) { |
234 | case 'h': | |
235 | usage(); | |
236 | case 'V': | |
237 | print_version(EXIT_SUCCESS); | |
238 | } | |
410b261c | 239 | } |
c221578e NS |
240 | |
241 | /* gather normal options */ | |
242 | optind = 1; | |
243 | while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { | |
244 | err_exclusive_options(c, longopts, excl, excl_st); | |
245 | ||
246 | switch (c) { | |
247 | case 'c': | |
248 | opt_check = TRUE; | |
249 | break; | |
250 | case 'e': | |
251 | ++n_opt_pipe; | |
252 | break; | |
253 | case 'f': | |
254 | ++n_opt_pipe; | |
255 | break; | |
256 | case 'g': | |
257 | opt_get = TRUE; | |
258 | break; | |
259 | case 'i': | |
260 | ++n_opt_pipe; | |
261 | break; | |
262 | case 'n': | |
132422bd | 263 | (void) strtos32_or_err(optarg, _("invalid fd argument")); |
c221578e NS |
264 | ++n_opt_pipe; |
265 | break; | |
266 | case 'o': | |
267 | ++n_opt_pipe; | |
268 | break; | |
269 | case 'q': | |
270 | opt_quiet = TRUE; | |
271 | break; | |
272 | case 's': | |
273 | sz = strtosize_or_err(optarg, _("invalid size argument")); | |
274 | opt_size = sz >= INT_MAX ? INT_MAX : (int)sz; | |
63444630 | 275 | ++n_opt_size; |
c221578e NS |
276 | break; |
277 | case 'v': | |
278 | opt_verbose = TRUE; | |
279 | break; | |
280 | default: | |
281 | errtryhelp(EXIT_FAILURE); | |
282 | } | |
283 | } | |
284 | ||
285 | /* check arguments */ | |
286 | if (opt_get) { | |
287 | if (argv[optind]) | |
288 | errx(EXIT_FAILURE, _("cannot specify a command with --get")); | |
289 | ||
290 | /* print column headers, if requested */ | |
291 | if (opt_verbose) | |
292 | printf("%s\t%s\t%s\n", | |
293 | /* TRANSLATORS: a column that contains the names of files that are unix pipes */ | |
294 | _("pipe"), | |
295 | /* TRANSLATORS: a column that contains buffer sizes in bytes */ | |
296 | _("size"), | |
297 | /* TRANSLATORS: a column that contains an amount of data which has not been used by a program */ | |
298 | _("unread") | |
299 | ); | |
300 | ||
301 | /* special behavior for --get */ | |
302 | if (!n_opt_pipe) { | |
303 | do_fd(STDIN_FILENO); | |
304 | return EXIT_SUCCESS; | |
305 | } | |
306 | } else { | |
307 | if (!set_size_default()) | |
308 | goto execute_command; | |
309 | ||
310 | if (!opt_quiet && n_opt_size > 1) | |
311 | warnx(_("using last specified size")); | |
312 | ||
313 | /* special behavior for --set */ | |
314 | if (!n_opt_pipe) { | |
315 | do_fd(STDOUT_FILENO); | |
316 | goto execute_command; | |
317 | } | |
318 | } | |
319 | ||
320 | /* go through the arguments again and do the requested operations */ | |
321 | optind = 1; | |
322 | while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) | |
323 | switch (c) { | |
324 | case 'e': | |
325 | do_fd(STDERR_FILENO); | |
326 | break; | |
327 | case 'f': | |
328 | do_file(optarg); | |
329 | break; | |
330 | case 'i': | |
331 | do_fd(STDIN_FILENO); | |
332 | break; | |
333 | case 'n': | |
334 | /* optarg was checked before, but it's best to be safe */ | |
335 | fd = strtos32_or_err(optarg, _("invalid fd argument")); | |
336 | do_fd(fd); | |
337 | break; | |
338 | case 'o': | |
339 | do_fd(STDOUT_FILENO); | |
340 | break; | |
341 | } | |
342 | ||
343 | execute_command: | |
344 | /* exec the command, if it's present */ | |
345 | if (!argv[optind]) | |
346 | return EXIT_SUCCESS; | |
347 | ||
348 | execvp(argv[optind], &argv[optind]); | |
349 | errexec(argv[optind]); | |
350 | } |