I have a multithreaded application that installs a handler for SIGCHLD that logs and reaps the child processes.
The problem I see starts when I'm doing a call to system(). system() needs to wait for the child process to end and reaps him itself since it needs the exit code. This is why it calls sigprocmask() to block SIGCHLD. But in my multithreaded application, the SIGCHLD is still called in a different thread and the child is reaped before system() has a chance to do so.
Is this a known problem in POSIX?
One way around this I thought of is to block SIGCHLD in all other threads but this is not really realistic in my case since not all threads are directly created by my code.
What other options do I have?
Yes, it's a known (or at least strongly intimated) problem.
Blocking SIGCHLD while waiting for the child to terminate prevents the application from catching the signal and obtaining status from system()'s child process before system() can get the status itself.
....
Note that if the application is catching SIGCHLD signals, it will receive such a signal before a successful system() call returns.
(From the documentation for system(), emphasis added.)
So, POSIXly you are out of luck, unless your implementation happens to queue SIGCHLD. If it does, you can of course keep a record of pids you forked, and then only reap the ones you were expecting.
Linuxly, too, you are out of luck, as signalfd appears also to collapse multiple SIGCHLDs.
UNIXly, however, you have lots of clever and too-clever techniques available to manage your own children and ignore those of third-party routines. I/O multiplexing of inherited pipes is one alternative to SIGCHLD catching, as is using a small, dedicated "spawn-helper" to do your forking and reaping in a separate process.
Since you have threads you cannot control, I recommend you write a preloaded library to interpose the system() call (and perhaps also popen() etc.) with your own implementation. I'd also include your SIGCHLD handler in the library, too.
If you don't want to run your program via env LD_PRELOAD=libwhatever.so yourprogram, you can add something like
const char *libs;
libs = getenv("LD_PRELOAD");
if (!libs || !*libs) {
setenv("LD_PRELOAD", "libwhatever.so", 1);
execv(argv[0], argv);
_exit(127);
}
at the start of your program, to have it re-execute itself with LD_PRELOAD appropriately set. (Note that there are quirks to consider if your program is setuid or setgid; see man ld.so for details. In particular, if libwhatever.so is not installed in a system library directory, you must specify a full path.)
One possible approach would be to use a lockless array (using atomic built-ins provided by the C compiler) of pending children. Instead of waitpid(), your system() implementation allocates one of the entries, sticks the child PID in there, and waits on a semaphore for the child to exit instead of calling waitpid().
Here is an example implementation:
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <semaphore.h>
#include <dlfcn.h>
#include <errno.h>
/* Maximum number of concurrent children waited for.
*/
#define MAX_CHILDS 256
/* Lockless array of child processes waited for.
*/
static pid_t child_pid[MAX_CHILDS] = { 0 }; /* 0 is not a valid PID */
static sem_t child_sem[MAX_CHILDS];
static int child_status[MAX_CHILDS];
/* Helper function: allocate a child process.
* Returns the index, or -1 if all in use.
*/
static inline int child_get(const pid_t pid)
{
int i = MAX_CHILDS;
while (i-->0)
if (__sync_bool_compare_and_swap(&child_pid[i], (pid_t)0, pid)) {
sem_init(&child_sem[i], 0, 0);
return i;
}
return -1;
}
/* Helper function: release a child descriptor.
*/
static inline void child_put(const int i)
{
sem_destroy(&child_sem[i]);
__sync_fetch_and_and(&child_pid[i], (pid_t)0);
}
/* SIGCHLD signal handler.
* Note: Both waitpid() and sem_post() are async-signal safe.
*/
static void sigchld_handler(int signum __attribute__((unused)),
siginfo_t *info __attribute__((unused)),
void *context __attribute__((unused)))
{
pid_t p;
int status, i;
while (1) {
p = waitpid((pid_t)-1, &status, WNOHANG);
if (p == (pid_t)0 || p == (pid_t)-1)
break;
i = MAX_CHILDS;
while (i-->0)
if (p == __sync_fetch_and_or(&child_pid[i], (pid_t)0)) {
child_status[i] = status;
sem_post(&child_sem[i]);
break;
}
/* Log p and status? */
}
}
/* Helper function: close descriptor, without affecting errno.
*/
static inline int closefd(const int fd)
{
int result, saved_errno;
if (fd == -1)
return EINVAL;
saved_errno = errno;
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
if (result == -1)
result = errno;
else
result = 0;
errno = saved_errno;
return result;
}
/* Helper function: Create a close-on-exec socket pair.
*/
static int commsocket(int fd[2])
{
int result;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) {
fd[0] = -1;
fd[1] = -1;
return errno;
}
do {
result = fcntl(fd[0], F_SETFD, FD_CLOEXEC);
} while (result == -1 && errno == EINTR);
if (result == -1) {
closefd(fd[0]);
closefd(fd[1]);
return errno;
}
do {
result = fcntl(fd[1], F_SETFD, FD_CLOEXEC);
} while (result == -1 && errno == EINTR);
if (result == -1) {
closefd(fd[0]);
closefd(fd[1]);
return errno;
}
return 0;
}
/* New system() implementation.
*/
int system(const char *command)
{
pid_t child;
int i, status, commfd[2];
ssize_t n;
/* Allocate the child process. */
i = child_get((pid_t)-1);
if (i < 0) {
/* "fork failed" */
errno = EAGAIN;
return -1;
}
/* Create a close-on-exec socket pair. */
if (commsocket(commfd)) {
child_put(i);
/* "fork failed" */
errno = EAGAIN;
return -1;
}
/* Create the child process. */
child = fork();
if (child == (pid_t)-1)
return -1;
/* Child process? */
if (!child) {
char *args[4] = { "sh", "-c", (char *)command, NULL };
/* If command is NULL, return 7 if sh is available. */
if (!command)
args[2] = "exit 7";
/* Close parent end of comms socket. */
closefd(commfd[0]);
/* Receive one char before continuing. */
do {
n = read(commfd[1], &status, 1);
} while (n == (ssize_t)-1 && errno == EINTR);
if (n != 1) {
closefd(commfd[1]);
_exit(127);
}
/* We won't receive anything else. */
shutdown(commfd[1], SHUT_RD);
/* Execute the command. If successful, this closes the comms socket. */
execv("/bin/sh", args);
/* Failed. Return the errno to the parent. */
status = errno;
{
const char *p = (const char *)&status;
const char *const q = (const char *)&status + sizeof status;
while (p < q) {
n = write(commfd[1], p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
break;
}
}
/* Explicitly close the socket pair. */
shutdown(commfd[1], SHUT_RDWR);
closefd(commfd[1]);
_exit(127);
}
/* Parent process. Close the child end of the comms socket. */
closefd(commfd[1]);
/* Update the child PID in the array. */
__sync_bool_compare_and_swap(&child_pid[i], (pid_t)-1, child);
/* Let the child proceed, by sending a char via the socket. */
status = 0;
do {
n = write(commfd[0], &status, 1);
} while (n == (ssize_t)-1 && errno == EINTR);
if (n != 1) {
/* Release the child entry. */
child_put(i);
closefd(commfd[0]);
/* Kill the child. */
kill(child, SIGKILL);
/* "fork failed". */
errno = EAGAIN;
return -1;
}
/* Won't send anything else over the comms socket. */
shutdown(commfd[0], SHUT_WR);
/* Try reading an int from the comms socket. */
{
char *p = (char *)&status;
char *const q = (char *)&status + sizeof status;
while (p < q) {
n = read(commfd[0], p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
break;
}
/* Socket closed with nothing read? */
if (n == (ssize_t)0 && p == (char *)&status)
status = 0;
else
if (p != q)
status = EAGAIN; /* Incomplete error code, use EAGAIN. */
/* Close the comms socket. */
shutdown(commfd[0], SHUT_RDWR);
closefd(commfd[0]);
}
/* Wait for the command to complete. */
sem_wait(&child_sem[i]);
/* Did the command execution fail? */
if (status) {
child_put(i);
errno = status;
return -1;
}
/* Command was executed. Return the exit status. */
status = child_status[i];
child_put(i);
/* If command is NULL, then the return value is nonzero
* iff the exit status was 7. */
if (!command) {
if (WIFEXITED(status) && WEXITSTATUS(status) == 7)
status = 1;
else
status = 0;
}
return status;
}
/* Library initialization.
* Sets the sigchld handler,
* makes sure pthread library is loaded, and
* unsets the LD_PRELOAD environment variable.
*/
static void init(void) __attribute__((constructor));
static void init(void)
{
struct sigaction act;
int saved_errno;
saved_errno = errno;
sigemptyset(&act.sa_mask);
act.sa_sigaction = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_SIGINFO;
sigaction(SIGCHLD, &act, NULL);
(void)dlopen("libpthread.so.0", RTLD_NOW | RTLD_GLOBAL);
unsetenv("LD_PRELOAD");
errno = saved_errno;
}
If you save the above as say child.c, you can compile it into libchild.so using
gcc -W -Wall -O3 -fpic -fPIC -c child.c -lpthread
gcc -W -Wall -O3 -shared -Wl,-soname,libchild.so child.o -ldl -lpthread -o libchild.so
If you have a test program that does system() calls in various threads, you can run it with system() interposed (and children automatically reaped) using
env LD_PRELOAD=/path/to/libchild.so test-program
Note that depending on exactly what those threads that are not under your control do, you may need to interpose further functions, including signal(), sigaction(), sigprocmask(), pthread_sigmask(), and so on, to make sure those threads do not change the disposition of your SIGCHLD handler (after installed by the libchild.so library).
If those out-of-control threads use popen(), you can interpose that (and pclose()) with very similar code to system() above, just split into two parts.
(If you are wondering why my system() code bothers to report the exec() failure to the parent process, it's because I normally use a variant of this code that takes the command as an array of strings; this way it correctly reports if the command was not found, or could not be executed due to insufficient privileges, etc. In this particular case the command is always /bin/sh. However, since the communications socket is needed anyway to avoid racing between child exit and having up-to-date PID in the *child_pid[]* array, I decided to leave the "extra" code in.)
For those who are still looking for the answer, there is an easier way to solve this problem:
Rewrite SIGCHLD handler to use waitid call with flags WNOHANG|WNOWAIT to check child's PID before reaping them. You can optionally check /proc/PID/stat (or similar OS interface) for command name.
Replace the system() by proc_system().
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.
The below manual on system() says it blocks SIGINT and SIGQUIT signal for any binary program run through system() call.
https://man7.org/linux/man-pages/man3/system.3.html#:~:text=The%20system()%20library%20function,the%20command%20has%20been%20completed.
Psedo Code:
thread_1()
{
...
system("binary application");
}
main() {
...
pid = pthread_create(thread_1);
pthread_cancel(pid);
}
pthread_cancel issues SIGINT to thread 1 which kill the thread 1, but not the binary application.
How to make the "binary application" receive the SIGINT signal?
The below manual on system() says it blocks SIGINT and SIGQUIT signal for any binary program run through system() call.
No, it doesn't. It says this:
During execution of the command, SIGCHLD will be blocked, and SIGINT
and SIGQUIT will be ignored, in the process that calls system().
(These signals will be handled according to their defaults inside the
child process that executes command.)
(Emphasis added.) It is the process that calls system() whose signal-handling is affected, not the (separate) process in which the command runs. Moreover, this is purposeful, and you should not lightly attempt to interfere with it.
pthread_cancel issues SIGINT to thread 1
Doubtful. POSIX does not document how thread cancellation is implemented, but sending SIGINT is an unlikely choice, as its default behavior is to terminate the process. The Linux manual for pthread_cancel() does say that it is implemented via signals, but it also says that the first realtime signal is used if realtime signals are available, and otherwise SIGUSR2 is used. Neither of those is documented as being blocked while system() is running.
which kill the thread 1, but not the binary application.
Yes, forcible termination of the thread that calls system() while that function is running would not be expected to kill the separate process in which the specified command is executing. That has nothing to do with signals being blocked. If you want to terminate the process in which the command is running before it completes then you're probably looking for the kill() function. For that purpose, you'll need to find the PID of the child you want to kill, which might turn out to require considerable effort and trouble.
Overall, if you don't like the semantics of system() then you are probably better off rolling your own version, based on fork() and exec*(), so that you can have the level of control you want.
The man page also says:
(These signals will be handled according to their defaults inside the
child process that executes command.)
So to answer your question "How to make the "binary application" receive the SIGINT signal?"; it's ok, it will anyway. The blocking happens in the thread that calls the command, not the command process.
EDIT:
To answer #Hanu's comment below, use the wait() set of system calls: you can get the pid of the command inside the system() call from there, and you can safely close your child thread or take action depending on the result of wait(). But I don't know what resources you would need to clean if the process has terminated: Linux will free all the resources associated with the process called by system: there is a distinction between how the OS cleans pthreads when they finish and process resources - see this SO answer .
Instead of using system(), you fork() a child process, and execl("/bin/sh", "-c", "system-command-goes-here", (char *)0); in that child process.
When you call fork(), it returns twice: once in the parent with a positive value – the process identifier, "pid", of the child process; and once in the child with a zero value.
To send the child process an INT signal, just use kill(pid, SIGINT);.
You can use pthread_cleanup_push(kill_int, (intptr_t)pid) in the thread to kill the child process if the thread exits (is canceled or killed), with
static void kill_int(void *pidptr)
{
const pid_t pid = (intptr_t)pidptr;
pid_t p;
if (pid > 1)
kill(pid, SIGINT);
}
Here are some Public Domain helper functions you might find useful. run.h:
/* SPDX-License-Identifier: CC0-1.0 */
#ifndef RUN_H
#define RUN_H
#include <unistd.h>
#include <sys/types.h>
/* Execute command. First parameter is the binary to execute (path or name),
and the second parameter is the argument array. First element in the
argument array is command name, and the last element must be (char *)0.
Returns the child process ID if successful, -1 with errno set if error.
*/
pid_t run(const char *, const char *[]);
/* Execute shell command. The parameter is the shell command,
otherwise this behaves like run().
*/
pid_t run_sh(const char *);
/* Check if child process has exited.
Returns the PID of the child if it has returned,
with the status (use WIFEXITED(), WEXITSTATUS(), WIFSIGNALED(), WTERMSIG())
stored at the location specified by the int pointer, if not NULL.
Returns 0 if the child hasn't exited yet, or
-1 if an error occurred (with errno set).
try_reap() tries to reap a specific child,
try_reap_any() checks if any child processes have exited, and
try_reap_group() checks if a child belonging to a process group has exited.
*/
pid_t try_reap(pid_t, int *);
pid_t try_reap_any(int *);
pid_t try_reap_group(pid_t, int *);
/* Wait until a specific child exits.
Returns the child PID with status set if not NULL,
or -1 if an error occurs.
*/
pid_t reap(pid_t, int *);
/* Wait until all child processes have exited.
If non-NULL, the callback is called for each reaped child.
If the callback returns nonzero, the function returns immediately
without waiting for other children.
Returns 0 if success, callback return value if it returns nonzero,
or -1 with errno set if an error occurs.
*/
pid_t reap_all(int (*report)(pid_t, int));
pid_t reap_group(pid_t, int (*report)(pid_t, int));
#endif /* RUN_H */
Implementation, run.c:
/* SPDX-License-Identifier: CC0-1.0 */
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#ifndef RUN_FAILURE_EXIT_STATUS
#define RUN_FAILURE_EXIT_STATUS 69
#endif
static inline int has_slash(const char *cmd)
{
while (*cmd)
if (*(cmd++) == '/')
return 1;
return 0;
}
pid_t run(const char *cmd, const char *args[])
{
int ctrl[2] = { -1, -1 };
int cause;
pid_t child, p;
/* Sanity checks. */
if (!cmd || !*cmd || !args) {
errno = EINVAL;
return -1;
}
/* Create a close-on-exec control pipe. */
if (pipe2(ctrl, O_CLOEXEC) == -1) {
/* Failed; errno already set. */
return -1;
}
/* Fork the child process. */
child = fork();
if (child == (pid_t)-1) {
/* Failed; errno set. */
cause = errno;
close(ctrl[0]);
close(ctrl[1]);
errno = cause;
return -1;
} else
if (!child) {
/* This is the child process. */
/* Close parent end of control pipe. */
close(ctrl[0]);
/* Try and execute the command. */
if (has_slash(cmd))
execv(cmd, (char *const *)args);
else
execvp(cmd, (char *const *)args);
/* Failed. Try and report cause to parent. */
cause = errno;
{
const char *ptr = (const char *)(&cause);
const char *const end = (const char *)(&cause) + sizeof cause;
ssize_t n;
while (ptr < end) {
n = write(ctrl[1], ptr, (size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (n != -1 || errno != EINTR)
break;
}
}
exit(RUN_FAILURE_EXIT_STATUS);
}
/* This is the parent process. */
/* Close child end of control pipe. */
close(ctrl[1]);
/* Try reading from the control pipe. */
{
char *ptr = (char *)(&cause) + sizeof cause;
char *const end = (char *)(&cause) + sizeof cause;
int err = 0;
ssize_t n;
while (ptr < end) {
n = read(ctrl[0], ptr, (size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (!n) {
break;
} else
if (n != -1) {
err = EIO;
break;
} else
if (errno != EINTR) {
err = errno;
break;
}
}
/* If we failed, and didn't get a full cause,
use the error from the read. */
if (err && ptr != end)
cause = err;
}
/* Close parent end of the control pipe. */
close(ctrl[0]);
/* If we failed, reap the child and exit. */
if (cause) {
do {
p = waitpid(child, NULL, 0);
} while (p == -1 && errno == EINTR);
errno = cause;
return -1;
}
/* Everything looks okay! */
return child;
}
pid_t run_shell(const char *command)
{
const char *args[4] = { "sh", "-c", command, (char *)0 };
return run("/bin/sh", args);
}
pid_t try_reap(const pid_t pid, int *status)
{
int temp_status;
pid_t p;
if (pid <= 1) {
errno = EINVAL;
return -1;
}
do {
p = waitpid(pid, &temp_status, WNOHANG);
} while (p == -1 && errno == EINTR);
if (status && p > 0)
*status = temp_status;
return p;
}
pid_t try_reap_any(int *status)
{
int temp_status;
pid_t p;
do {
p = waitpid(-1, &temp_status, WNOHANG);
} while (p == -1 && errno == EINTR);
if (status && p > 0)
*status = temp_status;
return p;
}
pid_t try_reap_group(pid_t pgid, int *status)
{
int temp_status;
pid_t p;
if (pgid <= 1) {
errno = EINVAL;
return -1;
}
do {
p = waitpid(-1, &temp_status, WNOHANG);
} while (p == -1 && errno == EINTR);
if (status && p > 0)
*status = temp_status;
return p;
}
pid_t reap(const pid_t pid, int *status)
{
int temp_status;
pid_t p;
if (pid <= 1) {
errno = EINVAL;
return -1;
}
do {
p = waitpid(pid, &temp_status, 0);
} while (p == -1 && errno == EINTR);
if (status && p > 0)
*status = temp_status;
return p;
}
int reap_all(int (*report)(pid_t pid, int status))
{
int status, retval;
pid_t p;
while (1) {
p = waitpid(-1, &status, 0);
if (p == -1) {
if (errno == ECHILD)
return 0;
else
if (errno != EINTR)
return -1;
} else
if (p > 0 && report) {
retval = report(p, status);
if (retval)
return retval;
}
}
}
int reap_group(pid_t pgid, int (*report)(pid_t pid, int status))
{
int status, retval;
pid_t p;
if (pgid <= 1) {
errno = EINVAL;
return -1;
}
while (1) {
p = waitpid(-pgid, &status, 0);
if (p == -1) {
if (errno == ECHILD)
return 0;
else
if (errno != EINTR)
return -1;
} else
if (p > 0 && report) {
retval = report(p, status);
if (retval)
return retval;
}
}
}
and here is an example of use, example.c, which runs the binary specified by command-line parameters:
/* SPDX-License-Identifier: CC0-1.0 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "run.h"
int main(int argc, char *argv[])
{
pid_t child, p;
int status;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *argv0 = (argc > 0 && argv && argv[0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
fprintf(stderr, " %s COMMAND [ ARGS ... ]\n", argv0);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
child = run(argv[1], (const char **)(argv + 1));
if (child == -1) {
fprintf(stderr, "%s: Cannot execute: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
fprintf(stderr, "%s: Started process %d.\n", argv[1], (int)child);
p = reap(child, &status);
if (p == -1) {
fprintf(stderr, "%s: Cannot reap child: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
} else
if (p != child) {
fprintf(stderr, "%s: Internal bug: reaped the wrong child process (%d, expected %d).\n", argv[1], (int)p, (int)child);
return EXIT_FAILURE;
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS) {
fprintf(stderr, "%s: Exited successfully.\n", argv[1]);
return EXIT_SUCCESS;
} else {
fprintf(stderr, "%s: Exited with status %d.\n", argv[1], WEXITSTATUS(status));
return WEXITSTATUS(status);
}
} else
if (WIFSIGNALED(status)) {
fprintf(stderr, "%s: Died from signal %d.\n", argv[1], WTERMSIG(status));
return EXIT_FAILURE;
} else {
fprintf(stderr, "%s: Child process vanished!\n", argv[1]);
return EXIT_FAILURE;
}
}
To tie all these together, Makefile:
CC := gcc
CFLAGS := -Wall -O2
LDFLAGS :=
PROGS := example
all: $(PROGS)
clean:
rm -f *.o $(PROGS)
%.o:%.c
$(CC) $(CFLAGS) -c $^
example: run.o example.o
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $#
Note that this forum eats Tabs, so you need to run sed -e 's|^ *|\t|' -i Makefile to fix the indentation. To compile, just run make.
To run, run e.g
./example date
The parent process examines how and why the child process exited, and will report both process identifier (pid) and the exit status.
I have 2 programs: 1) Father 2) Child.
When Father receives SIGINT (CTRL-C) signal his handler sends a SIGTERM to his child. The problem is that often (not always, don't know why) it shows this error in loop after SIGINT:
Invalid Argument
Goal of the father is to create a child and then just being alive to be ready to handle SIGINT.
Father
#include "library.h"
static void handler();
int main(int argc, char* argv[]){
int value, que_id;
char str_que_id[10], **child_arg;
pid_t child_pid;
sigaction int_sa;
//Create message queue
do{
que_id = msgget(IPC_PRIVATE, ALL_PERM | IPC_CREAT);
}while(que_id == -1);
snprintf(str_que_id, sizeof(str_que_id), "%d", que_id);
//Set arguments for child
child_arg = malloc(sizeof(char*) * 3);
child[0] = "child";
child[1] = str_que_id;
child[2] = NULL;
//Set handler for SIGINT
int_sa.sa_handler = &handler;
int_sa.sa_flags = SA_RESTART;
sigemptyset(&int_sa.sa_mask);
sigaddset(&int_sa.sa_mask, SIGALRM);
sigaction(SIGINT, &int_sa, NULL);
//Fork new child
if(value = fork() == 0){
child_pid = getpid();
do{
errno = 0;
execve("./child", child_arg, NULL);
}while(errno);
}
//Keep alive father
while(1);
return 0;
}
static void handler(){
if(kill(child_pid, SIGTERM) != -1)
waitpid(child_pid, NULL, WNOHANG);
while(msgctl(que_id, IPC_RMID, NULL) == -1);
free(child_arg);
exit(getpid());
}
Goal of the child (only for now in my project) is just to wait a new message incoming from the message queue. Since there won't be any message, it will always be blocked.
Child
#include "library.h"
typedef struct _Msgbuf {
long mtype;
char[10] message;
} Msgbuf;
int main(int argc, char * argv[]){
int que_id;
//Recovery of message queue id
que_id = atoi(argv[1]);
//Set handler for SIGTERM
signal(SIGTERM, handler);
//Dynamic allocation of message
received = calloc(1, sizeof(Msgbuf));
while(1){
do{
errno = 0;
//This will block child because there won't be any message incoming
msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0);
if(errno)
perror(NULL);
}while(errno && errno != EINTR);
}
}
static void handler(){
free(received);
exit(getpid());
}
I know from the man pages on msgrcv():
The calling process catches a signal. In this case the system call fails with errno set to EINTR. (msgrcv() is never automatically restarted after being interrupted by a signal handler, regardless of the setting of the SA_RESTART flag when establishing a signal handler.)
So why does it go to loop printing that error? It should exit in the handler instead it seems that after the handler comes back and (since the free(received) ) it doesn't find the buffer of the message setting errno to EINVAL .
(Almost) always errno only carries a sane value if and only if a function call failed.
This is the case for msgrcv().
From msgrcv()'s documentation:
RETURN VALUE
Upon successful completion, msgrcv() shall return a value equal to the number of bytes actually placed into the buffer mtext. Otherwise, no message shall be received, msgrcv() shall return -1, and errno shall be set to indicate the error.
So only use errno if msgrcv() returned -1, else errno's value is undefined and it might very well contain garbage or not ...
The code below does not make sense ...
msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0);
if(errno)
perror(NULL);
} while(errno && errno != EINTR);
... and should look like:
if (-1 == msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0))
{
/* Only here errno had a well defined value. */
perror("msgrcv() failed"); /* perror() translates errno into a human readable text prefixed by its argument and logs it to the stderr. */
}
else
{
errno = 0;
}
} while (errno && errno != EINTR);
This BTW
do{
errno = 0;
execve("./child", child_arg, NULL);
}while(errno);
only works as the members of the exec*() family of functions only return on error. So when the while's condition is tested then execve() had failed, though errno had been set. Here also the initial errnr = 0; setting is useless.
There are a number of problems with your program. It invokes undefined behaviour by calling exit, free, and msgctl from within the signal handlers. The table in the Signal Actions section of The Open Group Base Specifications lists the functions that are safe to call from within a signal handler. In most cases, you simply want to toggle a "running" flag from within the handler and have your main loop run until it is told to exit. Something like the following simple example:
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* this will be set when the signal is received */
static sig_atomic_t running = 1;
void
sig_handler(int signo, siginfo_t *si, void *context)
{
running = 0;
}
int
main(int argc, char *argv[])
{
int rc;
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = &sig_handler;
rc = sigaction(SIGINT, &sa, NULL);
if (rc < 0) {
perror("sigaction");
exit(EXIT_FAILURE);
}
printf("Waiting for SIGINT\n");
while (running) {
printf("... sleeping for 10 seconds\n");
sleep(10);
}
printf("Signal received\n");
return 0;
}
I put together a more complex session on repl.it as well.
The other problem is that you assume that errno retains a zero value across function calls. This is likely the case but the only thing that you should assume about errno is that it will be assigned a value when a library function returns a failure code -- e.g., read returns -1 and sets errno to something that indicates the error. The conventional way to call a C runtime library function is to check the return value and consult errno when appropriate:
int bytes_read;
unsigned char buf[128];
bytes_read = read(some_fd, &buf[0], sizeof(buf));
if (bytes_read < 0) {
printf("read failed: %s (%d)\n", strerror(errno), errno);
}
Your application is probably looping because the parent is misbehaving and not waiting on the child or something similar (see above about undefined behavior). If the message queue is removed before the child exits, then the msgrcv call is going to fail and set errno to EINVAL. You should check if msgrcv is failing before you check errno. The child should also be terminating the loop when it encounters a msgrcv failure with errno equal to EINVAL since that is a terminal condition -- the anonymous message queue can never be recreated after it ceases to exist.
Hy.
I'm trying to execute omxplayer (http://elinux.org/Omxplayer) on the Raspberry Pi after a C fork() via the execve or execl functions so that I can save the PID for the video playing process (so System will not do the work). If I execute the program on a X console/terminal it works but if its via a standard terminal (without starting X) it will run but not output the video it to the screen if execve is called on the child process. By the way, executing the player via "omxplayer ..." commnad in the console will play the video and output to the screen. I'm a bit new to this kind of things so this is a situation I haven't been able to solve or find an answer to. Anyone here has an ideia on how to solve this or a direction to give for me to find a possible solution?
Note: The code is just a execve call wich I know is right because in X it works perfectly.
The execve() call supplies a new environment to the executed program. For the program to be able to access the X display, you need to retain certain environment variables -- DISPLAY at minimum. Have you inadvertently omitted DISPLAY from the new environment?
For OMXPlayer to work without X, it has to have access to the video device itself (/dev/video, in this case; see OMXPlayer builds page for details). It's usually configured so that all members of the video group are allowed to access it.
You can use popen("id -Gn", "r") in your program, to run the id -Gn command which lists the current group memberships. (Read the list as a string from the file handle, then close it using pclose().) If the list does not contain video, then the problem is that the privileges of the user that is running the original program do not include access to the video device. The fix is simple: adding video to the groups that user is a member of.
Here is an example program, run.c, to illustrate basic use of execvp():
#include <unistd.h>
/* For the example main(): */
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Try executing a command in a child process.
* Returns the PID of the child process,
* but does not tell whether the execution was
* successful or not.
* Returns (pid_t)-1 with errno set if fork() fails.
*/
pid_t run(char *const command[])
{
pid_t child;
child = fork();
if (child == (pid_t)-1)
return (pid_t)-1;
if (!child) {
execvp(command[0], command);
_exit(127);
}
return child;
}
int main(int argc, char *argv[])
{
pid_t child, p;
int status;
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 1;
}
child = run(argv + 1);
if (child == (pid_t)-1) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return 1;
}
fprintf(stderr, "(%s: PID %d)\n", argv[1], (int)child);
fflush(stderr);
do {
p = waitpid(child, &status, 0);
if (p == (pid_t)-1 && errno == EINTR)
continue;
} while (p != child && p != (pid_t)-1);
if (p == (pid_t)-1) {
fprintf(stderr, "(%s: %s.)\n", argv[1], strerror(errno));
return 1;
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == 127)
fprintf(stderr, "(%s: Could not execute command.)\n", argv[1]);
else
if (WEXITSTATUS(status) == 0)
fprintf(stderr, "(%s: Exited successfully.)\n", argv[1]);
else
fprintf(stderr, "(%s: Exited with error %d.)\n", argv[1], WEXITSTATUS(status));
} else
if (WIFSIGNALED(status))
fprintf(stderr, "(%s: Killed by %s.)\n", argv[1], strsignal(WTERMSIG(status)));
else
fprintf(stderr, "(%s: Died from unknown causes.)\n", argv[1]);
return status;
}
You can compile and test it using e.g.
gcc -W -Wall -O3 run.c -o run
./run date --utc
Note that the run() function does not attempt to check whether the command was actually executed or not; it just returns the child process PID, or (pid_t)-1 if fork() fails.
Many implementations, including GNU C library popen(), use the 127 exit status as an indication that the execution failed. That is, it is not returned by the command that should have been executed, but by the child process, because the command execution failed. The above run() does so too.
You can use a close-on-exec pipe between the parent and child processes in the run() function, to let the parent process know whether the child process successfully started the desired command or not, and if not, why not. The parent process can then also immediately reap the defunct child process. This leaves very little extra effort to the caller in case of errors, so I personally highly recommend this approach. Here is an example implementation:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
/* Helper function: Close file descriptor, without modifying errno.
* Returns 0 if successful, otherwise the errno reported by close().
*/
static int closefd(const int fd)
{
int saved_errno, result;
/* Invalid descriptor? */
if (fd == -1)
return EINVAL;
/* Save errno. It's thread-local, so as long as we restore
* it before returning, no-one will notice any change in it. */
saved_errno = errno;
/* Close descriptor, and save errno (or 0) in result. */
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
if (result == -1)
result = errno;
else
result = 0;
/* Restore errno. Done. */
errno = saved_errno;
return result;
}
/* Helper function: Create a close-on-exec pipe.
* Return 0 if success, errno otherwise.
*/
int close_on_exec_pipe(int fds[2])
{
int result;
result = pipe(fds);
if (result == -1) {
fds[0] = -1;
fds[1] = -1;
return errno;
}
do {
do {
result = fcntl(fds[0], F_SETFD, FD_CLOEXEC);
} while (result == -1 && errno == EINTR);
if (result == -1)
break;
do {
result = fcntl(fds[1], F_SETFD, FD_CLOEXEC);
} while (result == -1 && errno == EINTR);
if (result == -1)
break;
/* Success. */
return 0;
} while (0);
/* Failed. */
closefd(fds[0]);
closefd(fds[1]);
fds[0] = -1;
fds[1] = -1;
return errno;
}
/* Run an external command in a child process.
* command[0] is the path or name of the command,
* and the array must be terminated with a NULL.
*
* If successful, this function will return the PID
* of the child process. Otherwise, it will return
* (pid_t)-1, with errno indicating the error.
*/
pid_t run(char *const command[])
{
pid_t child, p;
int commfd[2], errcode;
/* Create a close-on-exec pipe between the parent and child. */
if (close_on_exec_pipe(commfd))
return (pid_t)-1;
/* Fork the new child process. */
child = fork();
if (child == (pid_t)-1) {
closefd(commfd[0]);
closefd(commfd[1]);
return (pid_t)-1;
}
if (!child) {
/* Child process: */
/* Close the read/parent end of the pipe. */
closefd(commfd[0]);
/* In case of C library bugs, prepare errno. */
errno = EINVAL;
/* Execute the desired command. */
execvp(command[0], command);
/* Failed. errno describes why. */
errcode = errno;
/* Send it to the parent via the pipe. */
{
const char *p = (char *)&errcode;
const char *const q = (char *)&errcode + sizeof errcode;
ssize_t n;
while (p < q) {
n = write(commfd[1], p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
break;
}
}
/* Close write/child end of the pipe. */
closefd(commfd[1]);
/* Exit with a failure (127). */
_exit(127);
}
/* Parent: */
/* Close the write/child end of the pipe. */
closefd(commfd[1]);
/* Try to read the execution error. */
{
char *p = (char *)&errcode;
char *const q = (char *)&errcode + sizeof errcode;
ssize_t n;
errcode = 0;
while (p < q) {
n = read(commfd[0], p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
break; /* n == 0 is pipe closed */
else
if (errno != EINTR)
break;
}
/* Close the read/parent end of the pipe. */
closefd(commfd[0]);
/* Pipe closed (on exec), no data read? */
if (n == (ssize_t)0 && p == (char *)&errcode) {
/* Yes, success! */
errno = 0;
return child;
}
/* Execution failed.
* If we didn't get the reason, use EINVAL. */
if (!errcode || p != q)
errcode = EINVAL;
}
/* Reap the child process. */
do {
p = waitpid(child, NULL, 0);
if (p == (pid_t)-1) {
if (errno == EINTR)
continue;
else
break;
}
} while (p != child);
/* Return with failure. */
errno = errcode;
return (pid_t)-1;
}
The only downside to this approach, in my opinion, is the extra two descriptors used in the parent process, albeit only temporarily. In almost all cases this is irrelevant, but if you have a server-type application that uses a lot of file descriptors, this is something you should be aware of.
The Phidgets library uses threads. The thread that executes the callbacks is different than the one that say, waits for the keypress in the RFID Phidgets example. One option would be to use posix_spawn() to execute the player (from a non-main thread).
However, in general, it is better to have the main thread monitor both the player using waitpid(child, &status, WNOHANG) to check if the player has exited, and to handle any new RFID events, launching the player as needed (killing an existing instance if new RFID), and even killing the player if the RFID is moved outside the reader range.
This requires a simple threaded event queue:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
/* RFID tag event types: tag read, tag lost.
*/
typedef enum {
RFID_TAG_LOST = 0,
RFID_TAG_READ
} rfid_event_type_t;
/* Structure describing all possible RFID tag events.
*/
typedef struct rfid_event_st rfid_event_t;
struct rfid_event_st {
struct rfid_event_st *next;
rfid_event_type_t type;
CPhidgetRFIDHandle rfid;
CPhidgetRFID_Protocol protocol;
void *userptr;
char tag[];
};
static pthread_mutex_t event_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t event_wait = PTHREAD_COND_INITIALIZER;
static rfid_event_t *event_queue = NULL;
/* Add event to event queue.
*/
static int add_event(const CPhidgetRFIDHandle rfid,
const CPhidgetRFID_Protocol protocol,
const rfid_event_type_t type,
const char *const tag,
void *const userptr)
{
const size_t taglen = (tag) ? strlen(tag) : 0;
rfid_event_t *ev;
/* Allocate memory for a new event. */
ev = malloc(sizeof (rfid_event_t) + taglen + 1);
if (!ev)
return errno = ENOMEM;
/* Fill in the fields. */
ev->next = NULL;
ev->type = type;
ev->rfid = rfid;
ev->protocol = protocol;
ev->userptr = userptr;
if (taglen > 0)
memcpy(ev->tag, tag, taglen);
ev->tag[taglen] = '\0';
/* Lock event queue. */
pthread_mutex_lock(&event_lock);
/* Append to the event queue. */
if (event_queue) {
rfid_event_t *prev = event_queue;
while (prev->next)
prev = prev->next;
prev->next = ev;
} else
event_queue = ev;
/* Signal and unlock. */
pthread_cond_signal(&event_wait);
pthread_mutex_unlock(&event_lock);
return 0;
}
/* Get next event, waiting at most 'maxwait' seconds.
*/
static rfid_event_t *get_event(const long maxwait)
{
struct timespec until;
rfid_event_t *ev;
pthread_mutex_lock(&event_lock);
/* Event already in the queue? */
if (event_queue) {
ev = event_queue;
event_queue = ev->next;
ev->next = NULL;
pthread_mutex_unlock(&event_lock);
return ev;
}
/* No waiting requested? */
if (maxwait <= 0L) {
pthread_mutex_unlock(&event_lock);
return NULL;
}
/* Get current wall clock time, */
clock_gettime(CLOCK_REALTIME, &until);
/* and add maxwait seconds. */
until.tv_sec += maxwait;
/* Wait for a signal. */
pthread_cond_timedwait(&event_wait, &event_lock, &until);
/* Event arrived in the queue? */
if (event_queue) {
ev = event_queue;
event_queue = ev->next;
ev->next = NULL;
pthread_mutex_unlock(&event_lock);
return ev;
}
/* No event; timed out. */
pthread_mutex_unlock(&event_lock);
return NULL;
}
As per the Phidgets RFID example, the tag and tag lost handlers are
int CCONV TagHandler(CPhidgetRFIDHandle RFID, void *usrptr, char *TagVal, CPhidgetRFID_Protocol proto)
{
return add_event(RFID, proto, RFID_TAG_READ, TagVal, usrptr);
}
int CCONV TagLostHandler(CPhidgetRFIDHandle RFID, void *usrptr, char *TagVal, CPhidgetRFID_Protocol proto)
{
return add_event(RFID, proto, RFID_TAG_LOST, TagVal, usrptr);
}
Instead of waiting for a keypress after everything has been set up, you create a loop, something like
pid_t child = (pid_t)-1; /* Not running */
pid_t p;
rfid_event_t *event;
/* Infinite loop */
while (1) {
/* Do we have a player child process? */
if (child != (pid_t)-1) {
/* Yes. Has it exited yet? */
p = waitpid(child, NULL, WNOHANG);
if (p == child) {
/* Yes. No more player. */
child == (pid_t)-1;
}
}
/* Check for a new event.
* If we have a child, only wait one second only
* for the event; otherwise, wait up to 30 secs.
*/
if (child == (pid_t)-1)
event = get_event(30L);
else
event = get_event(1L);
/* If no event yet, start at the beginning of the loop. */
if (!event)
continue;
/*
* TODO: Handle the event.
* You can stop the existing player via e.g.
* if (child != (pid_t)-1)
* kill(child, SIGKILL);
* and then start a new one.
*/
/* Discard the event. It's dynamically allocated. */
free(event);
}
If you start the player, the above loop detects it is not playing within a second. If there is no player running, then it's okay for the loop to wait for an RFID signal for as long as it wants -- I used 30 seconds.