On an embedded system with Linux as OS I want to call a 3rd party binary to retrieve data from a database and to append this data to an existing file.
The command string which is handed over to the system() function looks like:
"export_from_db >> /tmp/myFile"
Unfortunately, this doesn't work. /tmp/myFile never gets created! If I omit the redirection, then the database export is printed to stdout.
I wonder if system() and the redirection via ">>" go together well? On the prompt I successfully tested this command "export_fom_db >> /tmp/myFile"! Does anybody know how to achieve it using system()? Is there some kind of quoting necessary?
hm.. Actually, it seems ok for me.. this is exactly what system() is for - to execute a line under current shell. Does that embedded linux's shell support >> operator? Have you tried it manually in the terminal?
Another thought is that your application may be run under some other user account and that account could have some weird configuration, like having some csh or ksh instead of bash (or viceversa, depending on what you like). Check what user actually owns the process and check the /etc/passwd for the shell setup.
Also, there is small possibility that the user account that the app runs under simply does not have rights to write to /tmp :) be sure to check that too
Also... there is small possibility that on yours 'embedded linux' simply has the system() implemented in a simplistic way, that just invokes the application with given parameters and skips all other shell-wise operators. This could have been done to save on resources, as system() might be though to be rarely used, or just though to be "too heavy" by your linux designers.. it depends on the distro.. If you tell us which one it is, then people with more knowledge will probably be able to say if this is the case.
On an embedded system, you are better off implementing the system() yourself. Consider the following code (untested!):
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
/* Helper function: Open the specified file at the desired descriptor.
*/
static int openfd(const int descriptor,
const char *const filename,
const int flags, const mode_t mode)
{
int fd, result;
if (!filename || descriptor == -1)
return errno = EINVAL;
/* Close existing descriptor. Ignore errors. Hopefully it is reused. */
do {
result = close(descriptor);
} while (result == -1 && errno == EINTR);
/* Open the desired file. */
do {
fd = open(filename, flags, mode);
} while (fd == -1 && errno == EINTR);
if (fd == -1)
return errno;
/* Did we get lucky, and get the correct descriptor already? */
if (fd == descriptor)
return 0;
/* Move the descriptor. */
do {
result = dup2(fd, descriptor);
} while (result == -1 && errno == EINTR);
if (result == -1) {
const int saved_errno = errno;
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
return errno = saved_errno;
}
/* Close the temporary descriptor. */
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
if (result == -1) {
const int saved_errno = errno;
do {
result = close(descriptor);
} while (result == -1 && errno == EINTR);
return errno = saved_errno;
}
return 0;
}
/* Start command on the background.
* Note: args[1] is the first argument, args[0] is the command name.
* NULL input/output/error redirects from/to /dev/null.
* Empty string for input/output/error does no redirections;
* the command the uses the same input/output/error.
* For non-empty output or error, specify the 'man 2 open' O_ flags too.
*
* Returns (pid_t)0 with errno set if an error occurs,
* otherwise the PID of the child process started.
*/
pid_t run(const char *file,
char *const args[],
const char *const input,
const char *const output, const int output_flags,
const char *const error, const int error_flags)
{
pid_t child;
int result, flags;
if (!cmd || !arg || !arg[0]) {
errno = EINVAL;
return (pid_t)0;
}
child = fork();
if (child == (pid_t)-1)
return (pid_t)0;
if (child)
return child;
/* This is the child process. */
if (input && *input)
result = openfd(STDIN_FILENO, input, O_RDONLY | O_NOCTTY, 0);
else
if (!input)
result = openfd(STDIN_FILENO, "/dev/null", O_RDONLY | O_NOCTTY, 0);
else
result = 0;
if (result)
exit(127);
if (output && *output)
result = openfd(STDOUT_FILENO, output, output_flags, 0666);
else
if (!output)
result = openfd(STDOUT_FILENO, "/dev/null", O_WRONLY | O_NOCTTY, 0);
else
result = 0;
if (result)
exit(127);
if (error && *error)
result = openfd(STDERR_FILENO, error, error_flags, 0666);
else
if (!error)
result = openfd(STDERR_FILENO, "/dev/null", O_WRONLY | O_NOCTTY, 0);
else
result = 0;
if (result)
exit(127);
execvp(file, args);
exit(127);
}
The run() only starts the command, you'll need to wait for it to complete. Note that your main program can do meaningful work at the same time, unless it needs the exit status (or the files the command is supposed to create) right away. Example use:
/* Command to run. NULL terminates the list. */
char *const cmd[] = { "ls", "-l", NULL };
pid_t child, p;
int status;
child = run(cmd[0], cmd,
NULL /* < /dev/null */,
"/tmp/some-log-file", O_WRONLY | O_CREAT | O_APPEND,
"", 0 /* No redirection for standard error */);
if (!child) {
fprintf(stderr, "Cannot run '%s': %s.\n", cmd[0], strerror(errno));
exit(1);
}
do {
status = 0;
p = waitpid(child, &status, 0);
} while (p == (pid_t)-1 && errno == EINTR);
if (p == (pid_t)-1) {
fprintf(stderr, "Lost '%s': %s.\n", cmd[0], strerror(errno));
exit(1);
}
if (WIFEXITED(status)) {
if (!WEXITSTATUS(status))
printf("Command executed successfully.\n");
else
printf("Command failed with exit status %d.\n", WEXITSTATUS(status));
} else
if (WSIGNALED(status))
printf("Command died from signal %s.\n", strsignal(WTERMSIG(status)));
else
printf("Command died unexpectedly.\n");
although the last part is often abbreviated to
if (WIFEXITED(status) && !WEXITSTATUS(status))
printf("'%s': Successful.\n", cmd[0]);
else
printf("'%s': Failed.\n", cmd[0]);
Note that if you process the output anyway, you probably should use a pipe (either popen() or an extended version of the above function) instead.
Hope you find this useful.
Related
I am trying to figure out how to do the following:
create a new pseudo-terminal
open a ncurses screen running inside the (slave) pseudo terminal
fork
A) forward I/O from the terminal the program is running in (bash) to the new (slave) terminal OR
B) exit leaving the ncurses program running in the new pty.
Can anyone provide pointers to what I might be doing wrong or that would make sense of some of this or even better an example program using newterm() with either posix_openpt(), openpty() or forkpty().
The code I have is roughly (details simplified or omitted):
openpty(master,slave,NULL,NULL,NULL);
pid_t res = fork();
if(res == -1)
std::exit(1);
if(res == 0) //child
{
FILE* scrIn = open(slave,O_RDWR|O_NONBLOCK);
FILE* scrOut = open(slave,O_RDWR|O_NONBLOCK);
SCREEN* scr = newterm(NULL,scrIn,scrOut);
}
else //parent
{
if (!optionA)
exit(0); // but leave the child running and using the slave
for(;;)
{
// forward IO to slave
fd_set read_fd;
fd_set write_fd;
fd_set except_fd;
FD_ZERO(&read_fd);
FD_ZERO(&write_fd);
FD_ZERO(&except_fd);
FD_SET(masterTty, &read_fd);
FD_SET(STDIN_FILENO, &read_fd);
select(masterTty+1, &read_fd, &write_fd, &except_fd, NULL);
char input[2];
char output[2];
input[1]=0;
output[1]=0;
if (FD_ISSET(masterTty, &read_fd))
{
if (read(masterTty, &output, 1) != -1)
{
write(STDOUT_FILENO, &output, 1);
}
}
if (FD_ISSET(STDIN_FILENO, &read_fd))
{
read(STDIN_FILENO, &input, 1);
write(masterTty, &input, 1);
}
}
}
}
I have various debug routines logging results from the parent and child to files.
There are several things relating to terminals that I do not understand.
I have seen several behaviours I don't understand depending on what variations I try.
Things I don't understand:
If I instruct the parent process exits the child terminates without anything interesting being logged by the child.
If I try closing stdin, stdout and using dup() or dup2() to make the pty the replace stdin
the curses window uses the original stdin and stdout and uses the original pty not the new one based on the output of ptsname().
(the parent process successful performs IO with the child but in the terminal it was lauched from not the new pty)
If I open the new pty using open() then I get a segfault inside the ncurses newterm() call as below:
Program terminated with signal 11, Segmentation fault.
#0 0x00007fbd0ff580a0 in fileno_unlocked () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-317.el7.x86_64 ncurses-libs-5.9-14.20130511.el7_4.x86_64
(gdb) where
#0 0x00007fbd0ff580a0 in fileno_unlocked () from /lib64/libc.so.6
#1 0x00007fbd106eced9 in newterm () from /lib64/libncurses.so.5
... now in my program...
I am trying to understand the pty system calls here. Using a program like screen or tmux does not help with this (also the source is not sufficiently annotated to fill in the gaps in my understanding).
Some other datums:
I am targeting GNU/Linux
I have also tried using forkpty
I looked at source for openpty, forkpty, login_tty, openpt, grantpt & posix_openpt
(e.g. https://github.com/coreutils/gnulib/blob/master/lib/posix_openpt.c)
I don't have access to a copy of APUE
though I have looked at the pty example.
Although the ncurses documentation for newterm() mentions talking to multiple terminals simultaneously I have not found an example program that does this.
I am still not clear on:
what login_tty / grantpt actually do.
If you opened the pty yourself why wouldn't you already have the correct capabilities?
why I might prefer openpty to posix_openpt or visa-versa.
Note: This is a different question to attach-a-terminal-to-a-process-running-as-a-daemon-to-run-an-ncurses-ui which describes a use case and looks for a solution where this question assumes a particular but incorrect/incomplete implementation for that use case.
Let's look at one possible implementation of pseudoterminal_run(), which creates a new pseudoterminal, forks a child process to run with that pseudoterminal as the controlling terminal with standard input, output, and error directed to that pseudoterminal, and executes a specified binary.
Here's the header file, pseudoterminal.h:
#ifndef PSEUDOTERMINAL_H
#define PSEUDOTERMINAL_H
int pseudoterminal_run(pid_t *const, /* Pointer to where child process ID (= session and process group ID also) is saved */
int *const, /* Pointer to where pseudoterminal master descriptor is saved */
const char *const, /* File name or path of binary to be executed */
char *const [], /* Command-line arguments to binary */
const struct termios *const, /* NULL or pointer to termios settings for the pseudoterminal */
const struct winsize *const); /* NULL or pointer to pseudoterminal size */
#endif /* PSEUDOTERMINAL_H */
Here is the corresponding implementation, pseudoterminal.c:
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 600
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
/* Helper function: Moves fd so that it does not overlap standard streams.
* If an error occurs, will close fd.
*/
static int not_stdin_stdout_stderr(int fd)
{
unsigned int close_mask = 0;
if (fd == -1) {
errno = EBADF;
return -1;
}
while (1) {
if (fd == STDIN_FILENO)
close_mask |= 1;
else
if (fd == STDOUT_FILENO)
close_mask |= 2;
else
if (fd == STDERR_FILENO)
close_mask |= 4;
else
break;
fd = dup(fd);
if (fd == -1) {
const int saved_errno = errno;
if (close_mask & 1) close(STDIN_FILENO);
if (close_mask & 2) close(STDOUT_FILENO);
if (close_mask & 4) close(STDERR_FILENO);
errno = saved_errno;
return -1;
}
}
if (close_mask & 1) close(STDIN_FILENO);
if (close_mask & 2) close(STDOUT_FILENO);
if (close_mask & 4) close(STDERR_FILENO);
return fd;
}
static int run_slave(int master,
const char * binary,
char *const args[],
const struct termios *termp,
const struct winsize *sizep)
{
int slave;
/* Close standard streams. */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
/* Fix ownership and permissions for the slave side. */
if (grantpt(master) == -1)
return errno;
/* Unlock the pseudoterminal pair */
if (unlockpt(master) == -1)
return errno;
/* Obtain a descriptor to the slave end of the pseudoterminal */
do {
#if defined(TIOCGPTPEER)
slave = ioctl(master, TIOCGPTPEER, O_RDWR);
if (slave == -1) {
if (errno != EINVAL &&
#if defined(ENOIOCTLCMD)
errno != ENOIOCTLCMD &&
#endif
errno != ENOSYS)
return errno;
} else
break;
#endif
const char *slave_pts = ptsname(master);
if (!slave_pts)
return errno;
slave = open(slave_pts, O_RDWR);
if (slave == -1)
return errno;
else
break;
} while (0);
#if defined(TIOCSCTTY)
/* Make sure slave is our controlling terminal. */
ioctl(slave, TIOCSCTTY, 0);
#endif
/* Master is no longer needed. */
close(master);
/* Duplicate slave to standard streams. */
if (slave != STDIN_FILENO)
if (dup2(slave, STDIN_FILENO) == -1)
return errno;
if (slave != STDOUT_FILENO)
if (dup2(slave, STDOUT_FILENO) == -1)
return errno;
if (slave != STDERR_FILENO)
if (dup2(slave, STDERR_FILENO) == -1)
return errno;
/* If provided, set the termios settings. */
if (termp)
if (tcsetattr(STDIN_FILENO, TCSANOW, termp) == -1)
return errno;
/* If provided, set the terminal window size. */
if (sizep)
if (ioctl(STDIN_FILENO, TIOCSWINSZ, sizep) == -1)
return errno;
/* Execute the specified binary. */
if (strchr(binary, '/'))
execv(binary, args); /* binary is a path */
else
execvp(binary, args); /* binary is a filename */
/* Failed! */
return errno;
}
/* Internal exit status used to verify child failure. */
#ifndef PSEUDOTERMINAL_EXIT_FAILURE
#define PSEUDOTERMINAL_EXIT_FAILURE 127
#endif
int pseudoterminal_run(pid_t *const childp,
int *const masterp,
const char *const binary,
char *const args[],
const struct termios *const termp,
const struct winsize *const sizep)
{
int control[2] = { -1, -1 };
int master;
pid_t child;
int cause;
char *const cause_end = (char *)(&cause) + sizeof cause;
char *cause_ptr = (char *)(&cause);
/* Verify required parameters exist. */
if (!childp || !masterp || !binary || !*binary || !args || !args[0]) {
errno = EINVAL;
return -1;
}
/* Acquire a new pseudoterminal */
master = posix_openpt(O_RDWR | O_NOCTTY);
if (master == -1)
return -1;
/* Make sure master does not shadow standard streams. */
master = not_stdin_stdout_stderr(master);
if (master == -1)
return -1;
/* Control pipe passes exec error back to this process. */
if (pipe(control) == -1) {
const int saved_errno = errno;
close(master);
errno = saved_errno;
return -1;
}
/* Write end of the control pipe must not shadow standard streams. */
control[1] = not_stdin_stdout_stderr(control[1]);
if (control[1] == -1) {
const int saved_errno = errno;
close(control[0]);
close(master);
errno = saved_errno;
return -1;
}
/* Write end of the control pipe must be close-on-exec. */
if (fcntl(control[1], F_SETFD, FD_CLOEXEC) == -1) {
const int saved_errno = errno;
close(control[0]);
close(control[1]);
close(master);
errno = saved_errno;
return -1;
}
/* Fork the child process. */
child = fork();
if (child == -1) {
const int saved_errno = errno;
close(control[0]);
close(control[1]);
close(master);
errno = saved_errno;
return -1;
} else
if (!child) {
/*
* Child process
*/
/* Close read end of control pipe. */
close(control[0]);
/* Note: This is the point where one would change real UID,
if one wanted to change identity for the child process. */
/* Child runs in a new session. */
if (setsid() == -1)
cause = errno;
else
cause = run_slave(master, binary, args, termp, sizep);
/* Pass the error back to parent process. */
while (cause_ptr < cause_end) {
ssize_t n = write(control[1], cause_ptr, (size_t)(cause_end - cause_ptr));
if (n > 0)
cause_ptr += n;
else
if (n != -1 || errno != EINTR)
break;
}
exit(PSEUDOTERMINAL_EXIT_FAILURE);
}
/*
* Parent process
*/
/* Close write end of control pipe. */
close(control[1]);
/* Read from the control pipe, to see if child exec failed. */
while (cause_ptr < cause_end) {
ssize_t n = read(control[0], cause_ptr, (size_t)(cause_end - cause_ptr));
if (n > 0) {
cause_ptr += n;
} else
if (n == 0) {
break;
} else
if (n != -1) {
cause = EIO;
cause_ptr = cause_end;
break;
} else
if (errno != EINTR) {
cause = errno;
cause_ptr = cause_end;
}
}
/* Close read end of control pipe as well. */
close(control[0]);
/* Any data received indicates an exec failure. */
if (cause_ptr != (const char *)(&cause)) {
int status;
pid_t p;
/* Partial error report is an I/O error. */
if (cause_ptr != cause_end)
cause = EIO;
/* Make sure the child process is dead, and reap it. */
kill(child, SIGKILL);
do {
p = waitpid(child, &status, 0);
} while (p == -1 && errno == EINTR);
/* If it did not exit with PSEUDOTERMINAL_EXIT_FAILURE, cause is I/O error. */
if (!WIFEXITED(status) || WEXITSTATUS(status) != PSEUDOTERMINAL_EXIT_FAILURE)
cause = EIO;
/* Close master pseudoterminal. */
close(master);
errno = cause;
return -1;
}
/* Success. Save master fd and child PID. */
*masterp = master;
*childp = child;
return 0;
}
To detect errors in the child process before the binary is executed (including errors in executing a binary), the above uses a close-on-exec pipe between the child and the parent to pass errors. In the success case, the pipe write end is closed by the kernel when execution of a new binary starts.
Otherwise the above is a straightforward implementation.
In particular:
posix_openpt(O_RDWR | O_NOCTTY) creates the pseudoterminal pair, and returns the descriptor for the master side. The O_NOCTTY flag is used because we do not want the current process to have that pseudoterminal as the controlling terminal.
in the child process, setsid() is used to start a new session, with both session ID and process group ID matching the child process ID. This way, the parent process can for example send a signal to each process in that group; and when the child opens the pseudoterminal slave side, it should become the controlling terminal for the child process. (The code does do an ioctl(slave_fd, TIOCSCTTY, 0) to ensure that, if TIOCSCTTY is defined.)
grantpt(masterfd) changes the owner user of the slave pseudoterminal to match current real user, so that only the current real user (and privileged users like root) can access the slave side of the pseudoterminal.
unlockpt(masterfd) allows access to the slave side of the pseudoterminal. It must be called before the slave side can be opened.
slavefd = ioctl(masterfd, TIOCGPTPEER, O_RDWR) is used to open the slave side pseudoterminal if available. If not available, or it fails, then slavefd = open(ptsname(masterfd), O_RDWR) is used instead.
The following example.c is an example using the above pseudoterminal.h, which runs a specified binary in a new pseudoterminal, proxying the data between the child process pseudoterminal and the parent process terminal. It logs all reads and writes to a log file you specify as the first command line parameter. The rest of the command line parameters form the command run in the child process.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <termios.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "pseudoterminal.h"
static struct termios master_oldterm, master_newterm, slave_newterm;
static struct winsize slave_size;
static int tty_fd = -1;
static int master_fd = -1;
static void handle_winch(int signum)
{
/* Silence warning about signum not being used. */
(void)signum;
if (tty_fd != -1 && master_fd != -1) {
const int saved_errno = errno;
struct winsize temp_size;
if (ioctl(tty_fd, TIOCGWINSZ, &temp_size) == 0)
if (ioctl(master_fd, TIOCSWINSZ, &temp_size) == 0)
slave_size = temp_size;
errno = saved_errno;
}
}
static int install_winch(void)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_winch;
act.sa_flags = SA_RESTART;
return sigaction(SIGWINCH, &act, NULL);
}
int main(int argc, char *argv[])
{
pid_t child_pid = 0;
int child_status = 0;
FILE *log = NULL;
if (argc < 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *argv0 = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
fprintf(stderr, " %s LOGFILE COMMAND [ ARGS ... ]\n", argv0);
fprintf(stderr, "\n");
fprintf(stderr, "This program runs COMMAND in a pseudoterminal, logging all I/O\n");
fprintf(stderr, "to LOGFILE, and proxying them to the current terminal.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
if (isatty(STDIN_FILENO))
tty_fd = STDIN_FILENO;
else
if (isatty(STDOUT_FILENO))
tty_fd = STDOUT_FILENO;
else
if (isatty(STDERR_FILENO))
tty_fd = STDERR_FILENO;
else {
fprintf(stderr, "This program only runs in a terminal or pseudoterminal.\n");
return EXIT_FAILURE;
}
if (tcgetattr(tty_fd, &master_oldterm) == -1) {
fprintf(stderr, "Cannot obtain termios settings: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (ioctl(tty_fd, TIOCGWINSZ, &slave_size) == -1) {
fprintf(stderr, "Cannot obtain terminal window size: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (install_winch() == -1) {
fprintf(stderr, "Cannot install SIGWINCH signal handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* For our own terminal, we want RAW (nonblocking) I/O. */
memcpy(&master_newterm, &master_oldterm, sizeof (struct termios));
master_newterm.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
master_newterm.c_oflag &= ~OPOST;
master_newterm.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
master_newterm.c_cflag &= ~(CSIZE | PARENB);
master_newterm.c_cflag |= CS8;
master_newterm.c_cc[VMIN] = 0;
master_newterm.c_cc[VTIME] = 0;
/* We'll use the same for the new terminal also. */
memcpy(&slave_newterm, &master_newterm, sizeof (struct termios));
/* Open log file */
log = fopen(argv[1], "w");
if (!log) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
/* Execute binary in pseudoterminal */
if (pseudoterminal_run(&child_pid, &master_fd, argv[2], argv + 2, &slave_newterm, &slave_size) == -1) {
fprintf(stderr, "%s: %s.\n", argv[2], strerror(errno));
return EXIT_FAILURE;
}
fprintf(log, "Pseudoterminal has %d rows, %d columns (%d x %d pixels)\n",
slave_size.ws_row, slave_size.ws_col, slave_size.ws_xpixel, slave_size.ws_ypixel);
fflush(log);
/* Ensure the master pseudoterminal descriptor is nonblocking. */
fcntl(tty_fd, F_SETFL, O_NONBLOCK);
fcntl(master_fd, F_SETFL, O_NONBLOCK);
/* Pseudoterminal proxy. */
{
struct pollfd fds[2];
const size_t slavein_size = 8192;
unsigned char slavein_data[slavein_size];
size_t slavein_head = 0;
size_t slavein_tail = 0;
const size_t slaveout_size = 8192;
unsigned char slaveout_data[slaveout_size];
size_t slaveout_head = 0;
size_t slaveout_tail = 0;
while (1) {
int io = 0;
if (slavein_head < slavein_tail) {
ssize_t n = write(master_fd, slavein_data + slavein_head, slavein_tail - slavein_head);
if (n > 0) {
slavein_head += n;
io++;
fprintf(log, "Wrote %zd bytes to child pseudoterminal.\n", n);
fflush(log);
} else
if (n != -1) {
fprintf(log, "Error writing to child pseudoterminal: write() returned %zd.\n", n);
fflush(log);
} else
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
fprintf(log, "Error writing to child pseudoterminal: %s.\n", strerror(errno));
fflush(log);
}
}
if (slavein_head > 0) {
if (slavein_tail > slavein_head) {
memmove(slavein_data, slavein_data + slavein_head, slavein_tail - slavein_head);
slavein_tail -= slavein_head;
slavein_head = 0;
} else {
slavein_tail = 0;
slavein_head = 0;
}
}
if (slaveout_head < slaveout_tail) {
ssize_t n = write(tty_fd, slaveout_data + slaveout_head, slaveout_tail - slaveout_head);
if (n > 0) {
slaveout_head += n;
io++;
fprintf(log, "Wrote %zd bytes to parent terminal.\n", n);
fflush(log);
} else
if (n != -1) {
fprintf(log, "Error writing to parent terminal: write() returned %zd.\n", n);
fflush(log);
} else
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
fprintf(log, "Error writing to parent terminal: %s.\n", strerror(errno));
fflush(log);
}
}
if (slaveout_head > 0) {
if (slaveout_tail > slaveout_head) {
memmove(slaveout_data, slaveout_data + slaveout_head, slaveout_tail - slaveout_head);
slaveout_tail -= slaveout_head;
slaveout_head = 0;
} else {
slaveout_tail = 0;
slaveout_head = 0;
}
}
if (slavein_tail < slavein_size) {
ssize_t n = read(tty_fd, slavein_data + slavein_tail, slavein_size - slavein_tail);
if (n > 0) {
slavein_tail += n;
io++;
fprintf(log, "Read %zd bytes from parent terminal.\n", n);
fflush(log);
} else
if (!n) {
/* Ignore */
} else
if (n != -1) {
fprintf(log, "Error reading from parent terminal: read() returned %zd.\n", n);
fflush(log);
} else
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
fprintf(log, "Error reading from parent terminal: %s.\n", strerror(errno));
fflush(log);
}
}
if (slaveout_tail < slaveout_size) {
ssize_t n = read(master_fd, slaveout_data + slaveout_tail, slaveout_size - slaveout_tail);
if (n > 0) {
slaveout_tail += n;
io++;
fprintf(log, "Read %zd bytes from child pseudoterminal.\n", n);
fflush(log);
} else
if (!n) {
/* Ignore */
} else
if (n != -1) {
fprintf(log, "Error reading from child pseudoterminal: read() returned %zd.\n", n);
fflush(log);
} else
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
fprintf(log, "Error reading from child pseudoterminal: %s.\n", strerror(errno));
fflush(log);
}
}
/* If we did any I/O, retry. */
if (io > 0)
continue;
/* If child process has exited and its output buffer is empty, we're done. */
if (child_pid <= 0 && slaveout_head >= slaveout_tail)
break;
/* Check if the child process has exited. */
if (child_pid > 0) {
pid_t p = waitpid(child_pid, &child_status, WNOHANG);
if (p == child_pid) {
child_pid = -child_pid;
continue;
}
}
/* If both buffers are empty, we proxy also the termios settings. */
if (slaveout_head >= slaveout_tail && slavein_head >= slavein_tail)
if (tcgetattr(master_fd, &slave_newterm) == 0)
if (tcsetattr(tty_fd, TCSANOW, &slave_newterm) == 0)
master_newterm = slave_newterm;
/* Wait for I/O to become possible. */
/* fds[0] is parent terminal */
fds[0].fd = tty_fd;
fds[0].events = POLLIN | (slaveout_head < slaveout_tail ? POLLOUT : 0);
fds[0].revents = 0;
/* fds[1] is child pseudoterminal */
fds[1].fd = master_fd;
fds[1].events = POLLIN | (slavein_head < slaveout_head ? POLLOUT : 0);
fds[1].revents = 0;
/* Wait up to a second */
poll(fds, 2, 1000);
}
}
/* Report child process exit status to log. */
if (WIFEXITED(child_status)) {
if (WEXITSTATUS(child_status) == EXIT_SUCCESS)
fprintf(log, "Child process exited successfully.\n");
else
fprintf(log, "Child process exited with exit status %d.\n", WEXITSTATUS(child_status));
} else
if (WIFSIGNALED(child_status))
fprintf(log, "Child process died from signal %d.\n", WTERMSIG(child_status));
else
fprintf(log, "Child process lost.\n");
fflush(log);
fclose(log);
/* Discard pseudoterminal. */
close(master_fd);
/* Return original parent terminal settings. */
tcflush(tty_fd, TCIOFLUSH);
tcsetattr(tty_fd, TCSANOW, &master_oldterm);
return EXIT_SUCCESS;
}
Whenever the parent process receives a WINCH (window size change) signal, the new terminal window size is obtained from the parent terminal, then set to the child pseudoterminal.
For simplicity (and not providing code that can be used as-is), the example attempts nonblocking reads and writes whenever possible, and only polls (waits until input becomes available, or buffered data can be written) if all four fail. Also, if the buffers are empty then, it copies the terminal settings from the child pseudoterminal to the parent terminal.
Compile using e.g.
gcc -Wall -Wextra -O2 -c pseudoterminal.c
gcc -Wall -Wextra -O2 -c example.c
gcc -Wall -Wextra -O2 example.o pseudoterminal.o -o example
and run e.g. ./example nano.log nano test-file. This runs nano in a sub-pseudoterminal, reflecting everything in it to the parent terminal, and essentially acts as if you had simply ran nano test-file. (Press Ctrl+X to exit.)
However, every read and write is logged to the nano.log file. For simplicity, only the length is currently logged, but you can surely write a dumper function to also log the contents. (Because these contain control characters, you'll want to either escape all control characters, or dump the data in hexadecimal format.)
It is interesting to note that when the child process (last process with the pseudoterminal as their controlling terminal) exits, trying to read from the pseudoterminal master returns -1 with errno == EIO. This means that before treating that as a fatal error, one should reap processes in the child process process group (waitpid(-child_pid, &status, WNOHANG)); and if that returns -1 with errno = ECHILD, it means the EIO was caused by no process having the pseudoterminal slave open.
If we compare this to tmux or screen, we have implemented only a crude version of the part when "attached" to a running session. When the user (parent process, running in the parent terminal) "detaches" from a session, tmux and screen both leave a process collecting the output of the running command. (They do not just buffer everything, they tend to record the effects of the running command to a virtual terminal buffer – rows × columns array of printable glyphs and their attributes –, so that a limited/fixed amount of memory is needed to recover the terminal contents when re-attaching to it later on.)
When re-attaching to a session, the screen/tmux command connects to the existing process (usually using an Unix domain socket, which allows verifying the peer user ID, and also passing the descriptor (to the pseudoterminal master) between processes, so the new process can take the place of the old process, and the old process can exit.
If we set the TERM environment variable to say xterm-256color before executing the child binary, we could interpret everything we read from the pseudoterminal master side in terms of how 256-color xterm does, and e.g. draw the screen using e.g. GTK+ – that is how we'd write our own terminal emulator.
I am trying to figure out how to do the following:
create a new pseudo-terminal
open a ncurses screen running inside the (slave) pseudo terminal
fork
A) forward I/O from the terminal the program is running in (bash) to the new (slave) terminal OR
B) exit leaving the ncurses program running in the new pty.
You seem to have a fundamental misconception of pseudoterminal pairs, and especially the importance of a process being the pseudoterminal master. Without the master, and a process managing the master side, there is literally no pseudoterminal pair: when the master is closed, the kernel forcibly removes the slave too, invalidating the file descriptors the slave has open to the slave side of the pseudoterminal pair.
Above, you completely ignore the role of the master, and wonder why what you want is not working.
My answer shows to accomplish 4.A), with any binary running as the slave, with the program itself being the master, proxying data between the slave pseudoterminal and the master terminal.
Reversing the role, with your "main program" telling some other binary to be the master terminal, is simple: write your own "main program" as a normal ncurses program, but run it using my example program to manage the master side of the pseudoterminal pair. This way signal propagation et cetera works correctly.
If you want to swap the roles, with the pseudoterminal slave being the parent process and the pseudoterminal master being the child process, you need to explain exactly why, when the entire interface has been designed for the opposite.
No, there is no "just a generic master pseudoterminal program or library you can use for this". The reason is that such makes no sense. Whenever you need a pseudoterminal pair, the master is the reason you want one. Any standard stream using human-readable text producing or consuming program is a valid client, using the slave end. They are not important, only the master is.
Can anyone provide pointers to what I might be doing wrong or that would make sense of some of this
I tried that, but you didn't appreciate the effort. I am sorry I tried.
or even better an example program using newterm() with either posix_openpt(), openpty() or forkpty().
No, because your newterm() makes absolutely no sense.
Glärbo's answers have helped me understand the problems enough that after some experimentation I believe I can answer my remaining questions directly.
The important points are:
The master side of the pty must remain opened
The file descriptor for the slave must be opened in the same mode as originally created.
without setsid() on the slave it remains connected to the original controlling terminal.
You need to be careful with ncurses calls when using newterm rather tha initscr
The master side of the pty must remain opened
Me: "If I instruct the parent process exits the child terminates without anything interesting being logged by the child."
Glärbo: "Without the master, and a process managing the master side, there is literally no pseudoterminal pair: when the master is closed, the kernel forcibly removes the slave too, invalidating the file descriptors the slave has open to the slave side of the pseudoterminal pair."
The file descriptor for the slave must be opened in the same mode as originally created.
My incorrect pseudo code (for the child side of the fork):
FILE* scrIn = open(slave,O_RDWR|O_NONBLOCK);
FILE* scrOut = open(slave,O_RDWR|O_NONBLOCK);
SCREEN* scr = newterm(NULL,scrIn,scrOut);
Works if replaced with (error checking omitted):
setsid();
close(STDIN_FILENO);
close(STDOUT_FILENO);
const char* slave_pts = pstname(master);
int slave = open(slave_pts, O_RDWR);
ioctl(slave(TIOCTTY,0);
close(master);
dup2(slave,STDIN_FILENO);
dup2(slave,STDOUT_FILENO);
FILE* slaveFile = fdopen(slavefd,"r+");
SCREEN* scr = newterm(NULL,slaveFile,slaveFile);
(void)set_term(scr);
printw("hello world\n"); // print to the in memory represenation of the curses window
refresh(); // copy the in mem rep to the actual terminal
I think a bad file or file descriptor must have crept through somewhere without being checked. This explains the segfault inside fileno_unlocked().
Also I had tried in some experiments opening the slave twice. Once for reading and once for writing. The mode would have conflicted with the mode of the original fd.
Without setsid() on the child side (with the slave pty) the child process still has the original controlling terminal.
setsid() makes the process a session leader. Only the session leader can change its controlling terminal.
ioctl(slave(TIOCTTY,0) - make slave the controlling terminal
You need to be careful with ncurses calls when using newterm() rather tha initscr()
Many ncurses functions have an implicit "intscr" argument which refers to a screen or window created for the controlling terminals STDIN and STDOUT. They doen't work unless replaced with the equivalent ncurses() functions for a specified WINDOW. You need to call newwin() to create a WINDOW, newterm() only gives you a screen.
In fact I am still wrestling with this kind of issue such as a call to subwin() which fails when the slave pty is used but not with the normal terminal.
It is also noteworthy that:
You need to handle SIGWINCH in the process connected to an actual terminal and pass that to the slave if it needs to knows the terminal size has changed.
You probably need a pipe to daemon to pass additional information.
I left stderr connect to the original terminal above for convenience of debugging. That would be closed in practice.
attach a terminal to a process running as a daemon (to run an ncurses UI) does a better job of describing the use case than the specific issues troubleshooted here.
I want to my program to execute md5sum command to generate a hash given a file, and then storing the hash into an array (char *) variable.
I have read about popen(), but it's involving FILE * variables and I'd like to use only file descriptors.
Is there any way to do this?
As I noted in my comment, it is perfectly possible to implement the functionality of popen() but have the function return a file descriptor instead of a file stream like popen() does. There isn't a standard library function for the task. You'll need to create a pipe and fork. The child will do plumbing so that the standard output of the command goes to the write end of the pipe (and the read end is closed) and then executes the command. The parent will close the write end of the pipe, read the response from the read end of the pipe, and close that. It's not really all that hard — it is mildly fiddly, that's all.
The code corresponding to pclose() is a bit trickier. Should the code wait for the child to die, or at least attempt to collect the zombie? If so, how will it know which PID is appropriate wait for? It is tempting to just say "call close() with the returned file descriptor", but that could leave zombies around. Should it wait for the child to die, or should it just collect the corpse if the child has died, leaving it to other code to deal with the zombies? The solution implemented in the code below:
Limits file descriptors to 128 (including standard I/O channels).
Records the PID associated with a file descriptor in the fixed-size array pids.
Waits on the child with either 0 (unconditional wait) or WNOHANG using waitpid() and the saved PID associated with the file descriptor.
Reports the child status if the child has exited.
Otherwise reports success — 0.
It would be feasible to alter the design so that the array of PID values for each file descriptor is dynamically allocated. You control whether the dpclose() function waits for the child to exit or does not wait if it has not already exited.
The code does no signal handling. That is another layer of complication.
/* SO 6557-1879 */
/* #include "dpopen.h" */
#ifndef DPOPEN_H_INCLUDED
#define DPOPEN_H_INCLUDED
#include <fcntl.h> /* O_RDONLY or O_WRONLY for mode in dpopen() */
#include <sys/wait.h> /* WNOHANG for options in dpclose() */
/* dpopen() - similar to popen(), but returning a file descriptor */
/* The value in mode must be O_RDONLY or O_WRONLY */
extern int dpopen(char *cmd, int mode);
/* dpclose() - similar to pclose(), but working with a file descriptor returned by dpopen() */
/* The value in options must be 0 or WNOHANG */
/* The return value is the exit status of the child if available, 0 if not, or -1 if there is a problem */
extern int dpclose(int fd, int options);
#endif /* DPOPEN_H_INCLUDED */
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
enum { MAX_PDOPEN_FD = 128 };
static pid_t pids[MAX_PDOPEN_FD];
int dpopen(char *cmd, int mode)
{
if (cmd == 0 || (mode != O_RDONLY && mode != O_WRONLY))
{
errno = EINVAL;
return -1;
}
int fd[2];
if (pipe(fd) != 0)
return -1;
/*
** Avoid embarrassment in debug builds if someone closed file
** descriptors too enthusiastically, and double check at run-time
** for non-debug builds. In some ways, it isn't very necessary as a
** runtime check - the circumstances are implausible. It is
** possible to code around fd[0] == STDIN_FILENO and fd[1] ==
** STDERR_FILENO, etc, but it is very messy to do so (having to
** avoid closing file descriptors, etc). It is simpler to close the
** two new file descriptors and return -1 with errno set to EINVAL
** if they overlap with the standard I/O descriptors. If this
** problem is detected, the program is already screwed up because at
** least one of standard input, standard output or standard error
** was closed.
*/
assert(fd[0] > STDERR_FILENO && fd[1] > STDERR_FILENO);
if (fd[0] <= STDERR_FILENO || fd[1] <= STDERR_FILENO)
{
close(fd[0]);
close(fd[1]);
errno = EINVAL;
return -1;
}
if (fd[0] >= MAX_PDOPEN_FD || fd[1] >= MAX_PDOPEN_FD)
{
close(fd[0]);
close(fd[1]);
errno = EMFILE;
return -1;
}
/*
** Prepare for forking - minimal step. See SO 5011-0992
** (https://stackoverflow.com/q/50110992 and
** https://stackoverflow.com/a/50112169/): "Why does forking my
** process cause the file to be read infinitely?"
** See also SO 0297-9209 (https://stackoverflow.com/q/2979209 and
** https://stackoverflow.com/a/34247021) "Using fflush(stdin)",
** noting that Standard C and POSIX diverge somewhat; POSIX mandates
** behaviour that the C standard does not. It would be possible to
** ensure standard input is 'clean' using code such as:
**
** if (lseek(fileno(stdin), 0L, SEEK_CURR) >= 0)
** fflush(stdin);
**
** Standard error is normally not a problem; by default, it is not
** fully buffered.
*/
fflush(stdout);
pid_t pid = fork();
if (pid < 0)
{
close(fd[0]);
close(fd[1]);
return -1;
}
if (pid == 0)
{
/* Child */
if (mode == O_RDONLY)
dup2(fd[1], STDOUT_FILENO);
else
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
close(fd[1]);
char *argv[] = { "/bin/sh", "-c", cmd, 0 };
execv(argv[0], argv);
exit(EXIT_FAILURE);
}
/* Parent */
if (mode == O_RDONLY)
{
close(fd[1]);
pids[fd[0]] = pid;
return fd[0];
}
else
{
close(fd[0]);
pids[fd[1]] = pid;
return fd[1];
}
}
int dpclose(int fd, int options)
{
if (fd <= STDERR_FILENO || fd >= MAX_PDOPEN_FD || pids[fd] == 0 ||
(options != 0 && options != WNOHANG))
{
errno = EINVAL;
return -1;
}
if (close(fd) != 0)
return -1;
pid_t corpse;
int status;
pid_t child = pids[fd];
pids[fd] = 0;
if ((corpse = waitpid(child, &status, options)) == child)
return status;
return 0;
}
int main(void)
{
int fd1 = dpopen("ls -ltr", O_RDONLY);
int fd2 = dpopen("cat > ls.out; sleep 10", O_WRONLY);
if (fd1 < 0 || fd2 < 0)
{
fprintf(stderr, "failed to create child processes\n");
exit(EXIT_FAILURE);
}
char buffer[256];
ssize_t rbytes;
while ((rbytes = read(fd1, buffer, sizeof(buffer))) > 0)
{
ssize_t wbytes = write(fd2, buffer, rbytes);
if (wbytes != rbytes)
{
fprintf(stderr, "Failed to write data\n");
close(fd1);
close(fd2);
exit(EXIT_FAILURE);
}
}
if (dpclose(fd1, WNOHANG) < 0 || dpclose(fd2, 0) < 0)
{
fprintf(stderr, "failed to close pipes correctly\n");
exit(EXIT_FAILURE);
}
return 0;
}
See also Why does forking my process cause the file to be read infinitely? and
my answer.
See also Using fflush(stdin) and
my answer, noting also the information in SO 5011-0992.
I am trying out a C shell implementation from an open course, yet there is something intriguing about the behavior of the output buffering.
The code goes like this (note the line where I use pid = waitpid(-1, &r, WNOHANG)):
int
main(void)
{
static char buf[100];
int fd, r;
pid_t pid = 0;
// Read and run input commands.
while(getcmd(buf, sizeof(buf)) >= 0){
if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
buf[strlen(buf)-1] = 0; // chop \n
if(chdir(buf+3) < 0)
fprintf(stderr, "cannot cd %s\n", buf+3);
continue;
}
if((pid = fork1()) == 0)
runcmd(parsecmd(buf));
while ((pid = waitpid(-1, &r, WNOHANG)) >= 0) {
if (errno == ECHILD) {
break;
}
}
}
exit(0);
}
The runcmd function is like this (note that in pipe handling I create 2 child processes and wait for them to terminate):
void
runcmd(struct cmd *cmd)
{
int p[2], r;
struct execcmd *ecmd;
struct pipecmd *pcmd;
struct redircmd *rcmd;
if(cmd == 0)
exit(0);
switch(cmd->type){
case ' ':
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0) {
exit(0);
}
// Your code here ...
// fprintf(stderr, "starting to run cmd: %s\n", ecmd->argv[0]);
execvp(ecmd->argv[0], ecmd->argv);
fprintf(stderr, "exec error !\n");
exit(-1);
break;
case '>':
case '<':
rcmd = (struct redircmd*)cmd;
// fprintf(stderr, "starting to run <> cmd: %s\n", rcmd->file);
// Your code here ...
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
if (rcmd->type == '<') {
// input
close(0);
if (open(rcmd->file, O_RDONLY, mode) != 0) {
fprintf(stderr, "Opening file error !\n");
exit(-1);
}
} else {
// output
close(1);
if (open(rcmd->file, O_WRONLY|O_CREAT|O_TRUNC, mode) != 1) {
fprintf(stderr, "Opening file error !\n");
exit(-1);
}
}
runcmd(rcmd->cmd);
break;
case '|':
pcmd = (struct pipecmd*)cmd;
// fprintf(stderr, "starting to run pcmd\n");
// Your code here ...
pipe(p);
if (fork1() == 0) {
// child for read, right side command
close(0);
if (dup(p[0]) != 0) {
fprintf(stderr, "error when dup !\n");
exit(-1);
}
close(p[0]);
close(p[1]);
runcmd(pcmd->right);
fprintf(stderr, "exec error !\n");
}
if (fork1() == 0) {
// left side command for writing
close(1);
if (dup(p[1]) != 1) {
fprintf(stderr, "dup error !\n");
exit(-1);
}
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
fprintf(stderr, "exec error !\n");
}
close(p[0]);
close(p[1]);
int stat;
wait(&stat);
wait(&stat);
break;
default:
fprintf(stderr, "unknown runcmd\n");
exit(-1);
}
exit(0);
}
The wierd thing is, when I execute "ls | sort" in the terminal, I constantly get the following output
6.828$ ls | sort
6.828$ a.out
sh.c
t.sh
This indicates that before the next command prompt "6828$" is printed, the output from the child process is still not flushed to terminal.
However, if I don't use pid = waitpid(-1, &r, WNOHANG)) and use pid = waitpid(-1, &r, 0)) (or wait()), the output would be normal like:
6.828$ ls | sort
a.out
sh.c
t.sh
I have been thinking about the cause of the problem for a long time but did not come up with a possible reason. Can anyone suggest some possible reason?
Thanks a lot!
This code does not have well-defined behaviour:
while ((pid = waitpid(-1, &r, WNOHANG)) >= 0) {
if (errno == ECHILD) {
break;
}
}
The while loop breaks immediately if waitpid returns -1, which is precisely what it returns in the case of an error. So if the body of the loop is entered, waitpid returned some non-negative value: either 0 -- indicating that the child is still executing -- or the pid of a child which had exited. Those are not error conditions, so the value of errno is not meaningful. It might be ECHILD, in which case the loop will incorrectly break.
You must only check the value of errno in cases where the value is meaningful. Or, to be more precise, quoting the Posix standard:
The value of errno shall be defined only after a call to a function for which it is explicitly stated to be set and until it is changed by the next function call or if the application assigns it a value. The value of errno should only be examined when it is indicated to be valid by a function's return value.
But I'm puzzled why you feel it necessary to busy loop using WNOHANG. That's a massive waste of resources, since your parent process will repeatedly execute the system call until the child actually terminates. Since you really intend to wait until the child terminates, it would make much more sense to just call wait or to specify 0 as a flag value to waitpid.
On the other hand, you might want to repeat the wait (or waitpid) if it returns -1 with errno set to EINTR. And if it returns -1 and errno is neither EINTR nor ECHILD, then some hard error has occurred which you might want to log. But that's not related to your problem, afaics.
When I execute this on my system:
FILE* pipe = popen("something_that_doesnt_exist", "r");
printf("pipe: %p\n", pipe);
I get a valid pointer for the pipe, despite that program not existing. I'd like to write a popen that can detect that and return a NULL pointer indicating launch failure. However, I'm not sure how to achieve that while keeping the /bin/sh call for interpretation. Does anyone know how I could check the return status of a call like:
execl("/bin/sh", "sh", "-c", "something_that_doesnt_exist");
To do this, you need to use the low level facilities. You need to create an extra pipe, close-on-exec, that the child uses to write the error code when exec fails.
Since the pipe is close-on-exec, it will be closed by the kernel at the start of the execution of the new binary. (We do not actually know if it is running at that point; we only know that the exec did not fail. So, do not assume that a closed pipe means the command is already running. It only means it did not fail yet.)
The parent process closes the unnecessary pipe ends, and reads from the control pipe. If the read succeeds, the child process failed to execute the command, and the data read describes the error. If the pipe is closed (read returns 0), the command execution will start (there was no error barring its execution).
After that, we can continue reading from the pipe as usual. When the child process closes the pipe, we should use waitpid() to reap it.
Consider the following example program. It executes the command specified on the command line -- use sh -c 'command' if you want the same behaviour as system() and popen(). (That is, pathname == "sh" and argv == { "sh", "-c", "command", NULL }.)
It reads the output from the command character by character, counting them, until the child ends (by closing the pipe). After that, we reap the child process, and report the status.
If the command could not be executed, the reason is also reported. (Since non-executables are reported as ENOENT ("No such file or directory"), the exec_subproc() modifies that case to EACCESS ("Permission denied").)
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int reap_subproc(pid_t pid)
{
int status;
pid_t p;
if (pid < 1) {
errno = EINVAL;
return -1;
}
do {
status = 0;
p = waitpid(pid, &status, 0);
} while (p == -1 && errno == EINTR);
if (p == -1)
return -1;
errno = 0;
return status;
}
FILE *exec_subproc(pid_t *pidptr, const char *pathname, char **argv)
{
char buffer[1];
int datafd[2], controlfd[2], result;
FILE *out;
ssize_t n;
pid_t pid, p;
if (pidptr)
*pidptr = (pid_t)0;
if (!pidptr || !pathname || !*pathname || !argv || !argv[0]) {
errno = EINVAL;
return NULL;
}
if (pipe(datafd) == -1)
return NULL;
if (pipe(controlfd) == -1) {
const int saved_errno = errno;
close(datafd[0]);
close(datafd[1]);
errno = saved_errno;
return NULL;
}
if (fcntl(datafd[0], F_SETFD, FD_CLOEXEC) == -1 ||
fcntl(controlfd[1], F_SETFD, FD_CLOEXEC) == -1) {
const int saved_errno = errno;
close(datafd[0]);
close(datafd[1]);
close(controlfd[0]);
close(controlfd[1]);
errno = saved_errno;
return NULL;
}
pid = fork();
if (pid == (pid_t)-1) {
const int saved_errno = errno;
close(datafd[0]);
close(datafd[1]);
close(controlfd[0]);
close(controlfd[1]);
errno = saved_errno;
return NULL;
}
if (!pid) {
/* Child process. */
close(datafd[0]);
close(controlfd[0]);
if (datafd[1] != STDOUT_FILENO) {
do {
result = dup2(datafd[1], STDOUT_FILENO);
} while (result == -1 && errno == EINTR);
if (result == -1) {
buffer[0] = errno;
close(datafd[1]);
do {
n = write(controlfd[1], buffer, 1);
} while (n == -1 && errno == EINTR);
exit(127);
}
close(datafd[1]);
}
if (pathname[0] == '/')
execv(pathname, argv);
else
execvp(pathname, argv);
buffer[0] = errno;
close(datafd[1]);
/* In case it exists, we return EACCES instead of ENOENT. */
if (buffer[0] == ENOENT)
if (access(pathname, R_OK) == 0)
buffer[0] = EACCES;
do {
n = write(controlfd[1], buffer, 1);
} while (n == -1 && errno == EINTR);
exit(127);
}
*pidptr = pid;
close(datafd[1]);
close(controlfd[1]);
do {
n = read(controlfd[0], buffer, 1);
} while (n == -1 && errno == EINTR);
if (n == -1) {
close(datafd[0]);
close(controlfd[0]);
kill(pid, SIGKILL);
do {
p = waitpid(pid, NULL, 0);
} while (p == (pid_t)-1 && errno == EINTR);
errno = EIO;
return NULL;
} else
if (n == 1) {
close(datafd[0]);
close(controlfd[0]);
do {
p = waitpid(pid, NULL, 0);
} while (p == (pid_t)-1 && errno == EINTR);
errno = (int)buffer[0];
return NULL;
}
close(controlfd[0]);
out = fdopen(datafd[0], "r");
if (!out) {
close(datafd[0]);
kill(pid, SIGKILL);
do {
p = waitpid(pid, NULL, 0);
} while (p == (pid_t)-1 && errno == EINTR);
errno = EIO;
return NULL;
}
errno = 0;
return out;
}
int main(int argc, char *argv[])
{
FILE *cmd;
pid_t pid;
int c;
unsigned long bytes = 0UL;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s command [ arguments ... ]\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
cmd = exec_subproc(&pid, argv[1], argv + 1);
if (!cmd) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
while ((c = getc(cmd)) != EOF) {
bytes++;
putchar(c);
}
fflush(stdout);
fclose(cmd);
c = reap_subproc(pid);
if (errno) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
if (WIFEXITED(c) && !WEXITSTATUS(c)) {
fprintf(stderr, "%s: %lu bytes; success.\n", argv[1], bytes);
return 0;
}
if (WIFEXITED(c)) {
fprintf(stderr, "%s: %lu bytes; failed (exit status %d)\n", argv[1], bytes, WEXITSTATUS(c));
return WEXITSTATUS(c);
}
if (WIFSIGNALED(c)) {
fprintf(stderr, "%s: %lu bytes; killed by signal %d (%s)\n", argv[1], bytes, WTERMSIG(c), strsignal(WTERMSIG(c)));
return 128 + WTERMSIG(c);
}
fprintf(stderr, "%s: %lu bytes; child lost.\n", argv[1], bytes);
return EXIT_FAILURE;
}
Compile using e.g.
gcc -Wall -Wextra -O2 example.c -o example
and run e.g.
./example date
./example ./example date -u
./example /bin/sh -c 'date | tr A-Za-z a-zA-Z'
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
int fd = open("/dev/null", O_WRONLY);
/* backup */
int f1 = dup(1);
int f2 = dup(2);
dup2(fd, 1);
dup2(fd, 2);
int res = system("something_that_doesnt_exist");
/* recovery */
dup2(f1, 1);
dup2(f2, 2);
FILE* pipe = popen("something_that_doesnt_exist", "r");
if (res != 0 )
{
pipe = NULL;
}
}
redirect stdout and stderr in order to avoid unexcepted output.
If popen() will successfully execute a command or not, you can not tell. This is because the process calls fork() first, so the pipe and child process will always be created. But if the execv() call following the fork() fails, the child will die and the parent will not be able to tell if this is caused by execv() failure or the command you wanted just completed without any output.
If your process has no other child process, maybe you can use waitpid
int stat;
File* fp = popen("something_that_doesnt_exist", "r");
waitpid(-1, &stat, 0);
then you can determine the value of stat, if popen succeed, stat = 0. Not very sure about this way, need someone to confirm
I am confused about how popen() redirects stdin, stdout and stderr of the child process in unix. The man page on popen() is not very clear in this regard. The call
FILE *p = popen("/usr/bin/foo", "w");
forks a child process and executes a shell with arguments "-c", "/usr/bin/foo", and redirects stdin of this shell (which is redirected stdin of foo), stdout to p. But what happens with stderr? What is the general principle behind it?
I noticed that, if I open a file in foo (using fopen, socket, accept etc.), and the parent process has no stdout, it gets assigned the next available file number, which is 1 and so on. This delivers unexpected results from calls like fprintf(stderr, ...).
It can be avoided by writing
FILE *p = popen("/usr/bin/foo 2>/dev/null", "w");
in the parent program, but are their better ways?
popen(3) is just a library function, which relies on fork(2) and pipe(2) to do the real work.
However pipe(2) can only create unidirectional pipes. To send the child process input, and also capture the output, you need to open two pipes.
If you want to capture the stderr too, that's possible, but then you'll need three pipes, and a select loop to arbitrate reads between the stdout and stderr streams.
There's an example here for the two-pipe version.
simple idea: why not add "2>&1" to the command string to force the bash to redirect stderr to stdout (OK, writing to stdin still is not possible but at least we get stderr and stdout into our C program).
The return value from popen() is a normal standard I/O stream in all
respects save that it must be closed with pclose() rather than
fclose(3). Writing to such a stream writes to the standard input of
the command; the command's standard output is the same as that of the
process that called popen(), unless this is altered by the command
itself. Conversely, reading from a "popened" stream reads the
command's standard output, and the command's standard input is the
same as that of the process that called popen().
From its manpage, so it allows you to read the commands standard output or write into its standard input. It doesn't say anything about stderr. Thus that is not redirected.
If you provide "w", you will send your stuff to the stdin of the shell that is executed. Thus, doing
FILE * file = popen("/bin/cat", "w");
fwrite("hello", 5, file);
pclose(file);
Will make the shell execute /bin/cat, and pass it the string "hello" as its standard input stream. If you want to redirect, for example stderr to the file "foo" do this first, before you execute the code above:
FILE * error_file = fopen("foo", "w+");
if(error_file) {
dup2(fileno(error_file), 2);
fclose(error_file);
}
It will open the file, and duplicate its file-descriptor to 2, closing the original file descriptor afterwards.
Now, if you have your stdout closed in your parent, then if the child calls open it will get 1, since that's (if stdin is already opened) the next free file-descriptor. Only solution i see is to just use dup2 and duplicate something into that in the parent, like the above code. Note that if the child opens stdout, it will not make stdout open in the parent too. It stays closed there.
Check out popenRWE by Bart Trojanowski. Clean way to do all 3 pipes.
if you just want to get STDERR, try this:
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <malloc.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
/*
* Pointer to array allocated at run-time.
*/
static pid_t *childpid = NULL;
/*
* From our open_max(), {Prog openmax}.
*/
static int maxfd;
FILE *
mypopen(const char *cmdstring, const char *type)
{
int i;
int pfd[2];
pid_t pid;
FILE *fp;
/* only allow "r" "e" or "w" */
if ((type[0] != 'r' && type[0] != 'w' && type[0] != 'e') || type[1] != 0) {
errno = EINVAL; /* required by POSIX */
return(NULL);
}
if (childpid == NULL) { /* first time through */
/* allocate zeroed out array for child pids */
maxfd = 256;
if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
return(NULL);
}
if (pipe(pfd) < 0)
return(NULL); /* errno set by pipe() */
if ((pid = fork()) < 0) {
return(NULL); /* errno set by fork() */
} else if (pid == 0) { /* child */
if (*type == 'e') {
close(pfd[0]);
if (pfd[1] != STDERR_FILENO) {
dup2(pfd[1], STDERR_FILENO);
close(pfd[1]);
}
} else if (*type == 'r') {
close(pfd[0]);
if (pfd[1] != STDOUT_FILENO) {
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
}
} else {
close(pfd[1]);
if (pfd[0] != STDIN_FILENO) {
dup2(pfd[0], STDIN_FILENO);
close(pfd[0]);
}
}
/* close all descriptors in childpid[] */
for (i = 0; i < maxfd; i++)
if (childpid[i] > 0)
close(i);
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
/* parent continues... */
if (*type == 'e') {
close(pfd[1]);
if ((fp = fdopen(pfd[0], "r")) == NULL)
return(NULL);
} else if (*type == 'r') {
close(pfd[1]);
if ((fp = fdopen(pfd[0], type)) == NULL)
return(NULL);
} else {
close(pfd[0]);
if ((fp = fdopen(pfd[1], type)) == NULL)
return(NULL);
}
childpid[fileno(fp)] = pid; /* remember child pid for this fd */
return(fp);
}
int
mypclose(FILE *fp)
{
int fd, stat;
pid_t pid;
if (childpid == NULL) {
errno = EINVAL;
return(-1); /* popen() has never been called */
}
fd = fileno(fp);
if ((pid = childpid[fd]) == 0) {
errno = EINVAL;
return(-1); /* fp wasn't opened by popen() */
}
childpid[fd] = 0;
if (fclose(fp) == EOF)
return(-1);
while (waitpid(pid, &stat, 0) < 0)
if (errno != EINTR)
return(-1); /* error other than EINTR from waitpid() */
return(stat); /* return child's termination status */
}
int shellcmd(char *cmd){
FILE *fp;
char buf[1024];
fp = mypopen(cmd,"e");
if (fp==NULL) return -1;
while(fgets(buf,1024,fp)!=NULL)
{
printf("shellcmd:%s", buf);
}
pclose(fp);
return 0;
}
int main()
{
shellcmd("ls kangear");
}
and you will get this:
shellcmd:ls: cannot access kangear: No such file or directory