mirror of
git://git.sv.gnu.org/coreutils.git
synced 2026-06-02 07:46:51 +02:00
3cbe0491ba
* src/yes.c (splice_write): Always drain what we've written to an internal pipe, so there is no possibility of vmsplice() blocking. I.e., be defensive in the case that fcntl() fails, and our default buffer size (currently 16kiB) is larger than the pipe. https://github.com/coreutils/coreutils/issues/253
271 lines
7.8 KiB
C
271 lines
7.8 KiB
C
/* yes - output a string repeatedly until killed
|
|
Copyright (C) 1991-2026 Free Software Foundation, Inc.
|
|
|
|
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, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will 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, see <https://www.gnu.org/licenses/>. */
|
|
|
|
/* David MacKenzie <djm@gnu.ai.mit.edu> */
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include "system.h"
|
|
|
|
#include "alignalloc.h"
|
|
#include "full-write.h"
|
|
#include "isapipe.h"
|
|
#include "long-options.h"
|
|
#include "splice.h"
|
|
#include "unistd--.h"
|
|
|
|
/* The official name of this program (e.g., no 'g' prefix). */
|
|
#define PROGRAM_NAME "yes"
|
|
|
|
#define AUTHORS proper_name ("David MacKenzie")
|
|
|
|
void
|
|
usage (int status)
|
|
{
|
|
if (status != EXIT_SUCCESS)
|
|
emit_try_help ();
|
|
else
|
|
{
|
|
printf (_("\
|
|
Usage: %s [STRING]...\n\
|
|
or: %s OPTION\n\
|
|
"),
|
|
program_name, program_name);
|
|
|
|
fputs (_("\
|
|
Repeatedly output a line with all specified STRING(s), or 'y'.\n\
|
|
\n\
|
|
"), stdout);
|
|
oputs (HELP_OPTION_DESCRIPTION);
|
|
oputs (VERSION_OPTION_DESCRIPTION);
|
|
emit_ancillary_info (PROGRAM_NAME);
|
|
}
|
|
exit (status);
|
|
}
|
|
|
|
/* Fill DEST[0..BUFSIZE-1] with repeated copies of SRC[0..SRCSIZE-1],
|
|
doubling the copy size each iteration. DEST may equal SRC. */
|
|
|
|
static void
|
|
repeat_pattern (char *dest, char const *src, idx_t srcsize, idx_t bufsize)
|
|
{
|
|
if (dest != src)
|
|
memcpy (dest, src, srcsize);
|
|
for (idx_t filled = srcsize; filled < bufsize; )
|
|
{
|
|
idx_t chunk = MIN (filled, bufsize - filled);
|
|
memcpy (dest + filled, dest, chunk);
|
|
filled += chunk;
|
|
}
|
|
}
|
|
|
|
#if HAVE_SPLICE
|
|
|
|
/* Enlarge a pipe towards SPLICE_PIPE_SIZE and return the actual
|
|
capacity as a quarter of the pipe size (the empirical sweet spot
|
|
for vmsplice throughput), rounded down to a multiple of COPYSIZE.
|
|
Return 0 if the result would be smaller than COPYSIZE. */
|
|
|
|
static idx_t
|
|
pipe_splice_size (int fd, idx_t copysize)
|
|
{
|
|
size_t buf_cap = increase_pipe_size (fd) / 4;
|
|
return buf_cap / copysize * copysize;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Repeatedly write the COPYSIZE-byte pattern in BUF to standard output
|
|
using vmsplice/splice zero-copy I/O. Since the data never varies,
|
|
SPLICE_F_GIFT tells the kernel the pages will not be modified.
|
|
|
|
Return TRUE if splice I/O was used (caller should check errno and
|
|
report any error). Return FALSE if splice could not be used. */
|
|
|
|
static bool
|
|
splice_write (MAYBE_UNUSED char const *buf, MAYBE_UNUSED idx_t copysize)
|
|
{
|
|
bool output_started = false;
|
|
#if HAVE_SPLICE
|
|
idx_t page_size = getpagesize ();
|
|
|
|
bool stdout_is_pipe = isapipe (STDOUT_FILENO) > 0;
|
|
|
|
/* Determine buffer size: enlarge the target pipe,
|
|
then use 1/4 of actual capacity as the transfer size. */
|
|
int pipefd[2] = { -1, -1 };
|
|
idx_t splice_bufsize;
|
|
char *splice_buf = NULL;
|
|
|
|
if (stdout_is_pipe)
|
|
splice_bufsize = pipe_splice_size (STDOUT_FILENO, copysize);
|
|
else
|
|
{
|
|
if (pipe2 (pipefd, 0) < 0)
|
|
return false;
|
|
splice_bufsize = pipe_splice_size (pipefd[0], copysize);
|
|
}
|
|
|
|
if (splice_bufsize == 0)
|
|
goto done;
|
|
|
|
/* Allocate page-aligned buffer for vmsplice.
|
|
Needed with SPLICE_F_GIFT, but generally good for performance. */
|
|
if (! (splice_buf = alignalloc (page_size, splice_bufsize)))
|
|
goto done;
|
|
|
|
repeat_pattern (splice_buf, buf, copysize, splice_bufsize);
|
|
|
|
/* For the pipe case, vmsplice directly to stdout.
|
|
For the non-pipe case, vmsplice into the intermediate pipe
|
|
and then splice from it to stdout. */
|
|
int vmsplice_fd = stdout_is_pipe ? STDOUT_FILENO : pipefd[1];
|
|
|
|
for (;;)
|
|
{
|
|
struct iovec iov = { .iov_base = splice_buf,
|
|
.iov_len = splice_bufsize };
|
|
|
|
while (iov.iov_len > 0)
|
|
{
|
|
/* Use SPLICE_F_{GIFT,MOVE} to allow the kernel to take references
|
|
to the pages. I.e., we're indicating we won't make changes.
|
|
SPLICE_F_GIFT is only appropriate for full pages. */
|
|
unsigned int flags = iov.iov_len % page_size ? 0 : SPLICE_F_GIFT;
|
|
ssize_t n = vmsplice (vmsplice_fd, &iov, 1, flags);
|
|
if (n <= 0)
|
|
goto done;
|
|
|
|
if (stdout_is_pipe)
|
|
output_started = true;
|
|
else
|
|
{
|
|
idx_t remaining = n;
|
|
while (remaining > 0)
|
|
{
|
|
ssize_t s = splice (pipefd[0], NULL, STDOUT_FILENO, NULL,
|
|
remaining, SPLICE_F_MOVE);
|
|
if (s <= 0)
|
|
goto done;
|
|
output_started = true;
|
|
remaining -= s;
|
|
}
|
|
}
|
|
|
|
iov.iov_base = (char *) iov.iov_base + n;
|
|
iov.iov_len -= n;
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (pipefd[0] >= 0)
|
|
{
|
|
int saved_errno = errno;
|
|
close (pipefd[0]);
|
|
close (pipefd[1]);
|
|
errno = saved_errno;
|
|
}
|
|
alignfree (splice_buf);
|
|
#endif
|
|
return output_started;
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
initialize_main (&argc, &argv);
|
|
set_program_name (argv[0]);
|
|
setlocale (LC_ALL, "");
|
|
bindtextdomain (PACKAGE, LOCALEDIR);
|
|
textdomain (PACKAGE);
|
|
|
|
atexit (close_stdout);
|
|
|
|
parse_gnu_standard_options_only (argc, argv, PROGRAM_NAME, PACKAGE_NAME,
|
|
Version, true, usage, AUTHORS,
|
|
(char const *) NULL);
|
|
|
|
char **operands = argv + optind;
|
|
char **operand_lim = argv + argc;
|
|
if (optind == argc)
|
|
*operand_lim++ = bad_cast ("y");
|
|
|
|
/* Buffer data locally once, rather than having the
|
|
large overhead of stdio buffering each item. */
|
|
size_t bufalloc = 0;
|
|
bool reuse_operand_strings = true;
|
|
char **operandp = operands;
|
|
do
|
|
{
|
|
size_t operand_len = strlen (*operandp);
|
|
bufalloc += operand_len + 1;
|
|
if (operandp + 1 < operand_lim
|
|
&& *operandp + operand_len + 1 != operandp[1])
|
|
reuse_operand_strings = false;
|
|
}
|
|
while (++operandp < operand_lim);
|
|
|
|
/* Improve performance by using a buffer size greater than BUFSIZ / 2. */
|
|
if (bufalloc <= BUFSIZ / 2)
|
|
{
|
|
bufalloc = BUFSIZ;
|
|
reuse_operand_strings = false;
|
|
}
|
|
|
|
#if defined __CHERI__
|
|
/* Cheri capability bounds do not allow for this. */
|
|
reuse_operand_strings = false;
|
|
#endif
|
|
|
|
/* Fill the buffer with one copy of the output. If possible, reuse
|
|
the operands strings; this wins when the buffer would be large. */
|
|
char *buf = reuse_operand_strings ? *operands : xmalloc (bufalloc);
|
|
size_t bufused = 0;
|
|
operandp = operands;
|
|
do
|
|
{
|
|
size_t operand_len = strlen (*operandp);
|
|
if (! reuse_operand_strings)
|
|
memcpy (buf + bufused, *operandp, operand_len);
|
|
bufused += operand_len;
|
|
buf[bufused++] = ' ';
|
|
}
|
|
while (++operandp < operand_lim);
|
|
buf[bufused - 1] = '\n';
|
|
|
|
idx_t copysize = bufused;
|
|
|
|
/* Repeatedly output the buffer until there is a write error; then fail.
|
|
Do a minimal write first to check output with minimal set up cost.
|
|
If successful then set up for efficient repetition. */
|
|
if (full_write (STDOUT_FILENO, buf, copysize) == copysize
|
|
&& splice_write (buf, copysize) == 0)
|
|
{
|
|
/* If a larger buffer was allocated, fill it by repeated copies. */
|
|
bufused = bufalloc / copysize * copysize;
|
|
if (bufused > copysize)
|
|
repeat_pattern (buf, buf, copysize, bufused);
|
|
while (full_write (STDOUT_FILENO, buf, bufused) == bufused)
|
|
continue;
|
|
}
|
|
|
|
error (0, errno, _("standard output"));
|
|
main_exit (EXIT_FAILURE);
|
|
}
|