blob: 8c9f367439c082b186eac2964de1ed3922dcadc0 [file]
/* file-util.c: running program and getting the output
Copyright 2025-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/>. */
#include "system.h"
#include <unistd.h>
#include <stdio.h>
#if defined (HAVE_SYS_TIME_H)
#include <sys/time.h>
#endif
#if defined (HAVE_SYS_WAIT_H)
#include <sys/wait.h>
#endif
#include "run-external.h"
/* From gnulib */
#include "xalloc.h"
static char *read_from_fd (int fd);
/* Run program FILENAME with arguments FORMATTER_ARGS. Save what it
prints to stdout in *PROGRAM_OUTPUT if PROGRAM_OUTPUT is non-null.
If DISCARD_STDERR, try to hide output printed to stderr. Return exit
status of program or 127 if there was a problem with running it. */
int
get_output_from_program (char *filename, char *formatter_args[],
char **program_output, int discard_stderr)
{
int pipes[2];
int exit_status = 0;
char *output = NULL;
/* Open a pipe to this program, read the output, and save it away
in FORMATTED_PAGE. The reader end of the pipe is pipes[0]; the
writer end is pipes[1]. */
#if PIPE_USE_FORK
if (pipe (pipes) == -1)
return 127; /* Creating pipe failed. */
pid_t child = fork ();
if (child == -1)
return 127;
if (child != 0)
{
/* In the parent, close the writing end of the pipe, and read from
the exec'd child. */
close (pipes[1]);
output = read_from_fd (pipes[0]);
close (pipes[0]);
wait (&exit_status); /* Wait for child process to exit. */
}
else
{ /* In the child, close the read end of the pipe, make the write end
of the pipe be stdout, and execute the man page formatter. */
close (pipes[0]);
(void)! freopen (NULL_DEVICE, "r", stdin);
if (discard_stderr)
(void)! freopen (NULL_DEVICE, "w", stderr);
dup2 (pipes[1], fileno (stdout));
execv (filename, formatter_args);
/* If we get here, we couldn't exec, so close out the pipe and
exit. */
close (pipes[1]);
exit (EXIT_FAILURE);
}
#else /* !PIPE_USE_FORK */
/* Cannot fork/exec, but can popen/pclose. */
{
FILE *fpipe;
char *cmdline;
int save_stderr = dup (fileno (stderr));
int fd_err = open (NULL_DEVICE, O_WRONLY, 0666);
size_t cmdlen = strlen (filename);
int i;
#ifdef __MINGW32__
/* Mirror all forward slashes in FILENAME to backslashes, since
otherwise the Windows shell might fail to run the script. */
filename = xstrdup (filename);
for (i = 0; i < cmdlen; i++)
if (filename[i] == '/')
filename[i] = '\\';
#endif
for (i = 1; formatter_args[i]; i++)
cmdlen += strlen (formatter_args[i]);
/* Add-ons: 2 blanks, 2 quotes for the formatter program, 1
terminating null character. */
cmdlen += 2 + 2 + 1;
cmdline = xmalloc (cmdlen);
if (fd_err > 2)
dup2 (fd_err, fileno (stderr)); /* Don't print errors. */
sprintf (cmdline, "\"%s\" %s %s", filename,
formatter_args[1], formatter_args[2] ? formatter_args[2] : "");
#ifdef __MINGW32__
free (filename);
#endif
fpipe = popen (cmdline, "r");
free (cmdline);
if (fd_err > 2)
close (fd_err);
dup2 (save_stderr, fileno (stderr));
if (fpipe == 0)
return 127;
output = read_from_fd (fileno (fpipe));
int pclose_status = pclose (fpipe);
if (pclose_status != -1)
exit_status = pclose_status;
else
{
/* unknown error */
free (output);
return 127;
}
}
#endif /* !PIPE_USE_FORK */
if (program_output)
*program_output = output;
else
free (output);
#if defined (HAVE_SYS_WAIT_H)
return WIFEXITED(exit_status) ? WEXITSTATUS(exit_status) : 127;
#else
/* This block is most likely used if we are coming from popen above,
as is the case on MinGW. */
return exit_status;
#endif
}
/* Return pointer to bytes read from file descriptor FD. Return value to be
freed by caller. */
static char *
read_from_fd (int fd)
{
struct timeval timeout;
char *buffer = NULL;
int bsize = 0;
int bindex = 0;
int select_result;
#if defined (FD_SET)
fd_set read_fds;
timeout.tv_sec = 15;
timeout.tv_usec = 0;
FD_ZERO (&read_fds);
FD_SET (fd, &read_fds);
select_result = select (fd + 1, &read_fds, 0, 0, &timeout);
#else /* !FD_SET */
select_result = 1;
#endif /* !FD_SET */
switch (select_result)
{
case 0:
case -1:
break;
default:
{
int amount_read;
int done = 0;
while (!done)
{
while ((bindex + 1024) > (bsize))
buffer = xrealloc (buffer, (bsize += 1024));
buffer[bindex] = '\0';
amount_read = read (fd, buffer + bindex, 1023);
if (amount_read < 0)
{
done = 1;
}
else
{
bindex += amount_read;
buffer[bindex] = '\0';
if (amount_read == 0)
done = 1;
}
}
}
}
if ((buffer != NULL) && (*buffer == '\0'))
{
free (buffer);
buffer = NULL;
}
return buffer;
}