Related
I'm writing a very simple bash-like shell in C and am currently implementing pipes between commands (i.e. command1 | command2, which should run both commands at the same time with the stdout of the first one connected through a pipe with the stdin of the second one).
I've gotten to the point where something like
shell> echo test | cat | cat
correctly prints "test" to the string, but anything more complicated than that doesn't make it. For example:
shell> ls -1 / | sort | rev
It's (as far as I can tell) equivalent to the previous one in terms of piping, yet this one fails and the other one succeeds.
I'm at a complete loss as to why this is because I've debugged both the main process and the children exhaustively and verified that the processes get launched with the correct connections both in the working and in the not working command.
Here's a simplified version of the code:
// Uncomment to use hardcoded input
// #define USE_HARDCODED_INPUT
#include <stdlib.h>
#include <string.h>
#include <stddef.h> // NULL
#include <errno.h> // ENOENT
#include <stdio.h> // setbuf, printf
#include <unistd.h> // exec, fork
#include <fcntl.h> // open
#include <sys/types.h> // wait
#include <sys/wait.h>
void set_process_FDs(int input, int output, int error)
{
if (input)
{
dup2(input, STDIN_FILENO);
close(input);
}
if (output)
{
dup2(output, STDOUT_FILENO);
close(output);
}
if (error)
{
dup2(error, STDERR_FILENO);
close(error);
}
}
void child_setup(char **argv, int input, int output, int error)
{
if (input || output || error)
set_process_FDs(input, output, error);
execvp(argv[0], argv);
perror("exec()");
exit(1);
}
int launch_process(char **argv, int is_last,
int input, int output, int error)
{
int status;
pid_t pid = fork();
switch(pid)
{
case -1:
perror("fork()");
return 0;
case 0:
child_setup(argv, input, output, error);
return 0;
default:
break;
}
if (is_last)
wait(&status);
return 1;
}
int run_commands(char ***argvv)
{
int no_commands_ran = 0;
int argc;
char **argv = argvv[0];
int in_pipe[2];
int out_pipe[2];
for (int i=0; (argv = argvv[i]); ++i)
{
pipe(out_pipe);
if (i == 0)
in_pipe[0] = 0;
if (!argvv[i+1])
{
close(out_pipe[0]);
close(out_pipe[1]);
out_pipe[1] = 0;
}
for (argc=0; argv[argc]; ++argc);
if (!launch_process(argv, !argvv[i+1],
in_pipe[0], out_pipe[1], 0))
break;
if (i != 0)
{
close(in_pipe[0]);
close(in_pipe[1]);
}
in_pipe[0] = out_pipe[0];
in_pipe[1] = out_pipe[1];
no_commands_ran = i + 1;
}
return no_commands_ran;
}
extern int obtain_order(); // Obtains an order from stdin
int main(void)
{
char ***argvv = NULL;
int argvc;
char *filev[3] = {NULL, NULL, NULL};
int bg;
int ret;
setbuf(stdout, NULL); // Unbuffered
setbuf(stdin, NULL);
while (1)
{
#ifndef USE_HARDCODED_INPUT
printf("%s", "shell> "); // Prompt
ret = obtain_order(&argvv, filev, &bg);
if (ret == 0) // EOF
{
fprintf(stderr, "EOF\n");
break;
}
if (ret == -1)
continue; // Syntax error
argvc = ret - 1; // Line
if (argvc == 0)
continue; // Empty line
if (!run_commands(argvv))
continue; // Error executing command
#else
argvc = 3;
char ***argvv1 = calloc(4, sizeof(char*));
argvv1[0] = calloc(3, sizeof(char*));
argvv1[0][0] = strdup("echo");
argvv1[0][1] = strdup("test");
argvv1[1] = calloc(2, sizeof(char*));
argvv1[1][0] = strdup("cat");
argvv1[2] = calloc(2, sizeof(char*));
argvv1[2][0] = strdup("cat");
char ***argvv2 = calloc(4, sizeof(char*));
argvv2[0] = calloc(4, sizeof(char*));
argvv2[0][0] = strdup("ls");
argvv2[0][1] = strdup("-1");
argvv2[0][2] = strdup("/");
argvv2[1] = calloc(4, sizeof(char*));
argvv2[1][0] = strdup("sort");
argvv2[2] = calloc(4, sizeof(char*));
argvv2[2][0] = strdup("rev");
printf("%s", "shell> echo test | cat | cat\n");
if (!run_commands(argvv1))
continue; // Error executing command
usleep(500);
printf("%s", "shell> ls -1 / | sort | rev\n");
if (!run_commands(argvv2))
continue; // Error executing command
printf("%s", "\nNo more hardcoded commands to run\n");
break;
#endif
}
return 0;
}
obtain_order() is a function located in the parser, which is a simple Yacc parser. It just fills the vector of argvs called argvv with whatever was input in the shell. In case anyone wants to try the code and see the problem, simply uncomment the #define at the beginning to see the behaviour you'd get from typing the problematic commands manually.
To start, your parent process does not wait for all of its child processes to complete their execution.
This call to wait does occur after the last child process has been spawned
if (is_last)
wait(&status);
but it does not necessarily wait for the last child process. That is to say, it will return when any one child process has completed execution (or an error occurs).
Properly waiting for all child processes to complete, at the end of run_commands,
/* ... */
/* reap children */
pid_t pid;
int status;
while ((pid = wait(&status)) > 0)
if (WIFEXITED(status))
fprintf(stderr, "LOG: Child<%ld> process exited with status<%d>\n",
(long) pid,
WEXITSTATUS(status));
return no_commands_ran;
exposes the fact that children after the first are hanging, as wait blocks execution of the parent program.
(After placing a few fprintf statements. █ here indicates program is blocking.)
shell> echo test | cat | cat
LOG: Child<30607> (echo)
LOG: Child<30608> (cat)
LOG: Child<30609> (cat)
LOG: Child<30607> process exited with status <0>
█
Without waiting for all child processes, you are creating orphan processes.
As for why these processes fail to terminate, this is due to the fact that certain file descriptors are not being closed.
The call to launch_process
launch_process(argv, !argvv[i+1], in_pipe[0], out_pipe[1], 0)
ensures that in_pipe[0] and out_pipe[1] are closed in the child process, but leaks any valid file descriptors in_pipe[1] or out_pipe[0]. With those leaked file descriptors still open in the child processes, the associated pipes remain valid, and thus the processes will continue to block while they wait for more data to arrive.
The quickest fix is to change launch_process to accept both pipes
int launch_process(char **argv, int is_last,
int input[2], int output[2], int error);
pass both pipes
if (!launch_process(argv, !argvv[i+1], in_pipe, out_pipe, 0))
close the excess file descriptors
case 0:
close(input[1]);
close(output[0]);
child_setup(argv, input[0], output[1], error);
return 0;
remove
if (is_last)
wait(&status);
and add the previously shown wait loop to the end of run_commands.
Here is a complete example of a working version of your program, with minimal refactoring.
Compile with -DDEBUG for some additional sleep time, in order to discover file descriptor leaks (there should not be any). Please read the extended comment in main.
#define _POSIX_C_SOURCE 200809L
#define USE_HARDCODED_INPUT
#define DEBUG_SLEEP_TIME 20
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
void set_process_FDs(int input, int output, int error)
{
if (input) {
dup2(input, STDIN_FILENO);
close(input);
}
if (output) {
dup2(output, STDOUT_FILENO);
close(output);
}
if (error) {
dup2(error, STDERR_FILENO);
close(error);
}
}
void child_setup(char **argv, int input, int output, int error)
{
if (input || output || error)
set_process_FDs(input, output, error);
#ifdef DEBUG
/* a sleep here should allow time to inspect
* `/proc/$PID/fd` for FD leaks, see `main` for details
* if the child process hangs you will have ample time, regardless
*/
sleep(DEBUG_SLEEP_TIME);
#endif
execvp(argv[0], argv);
perror("exec()");
exit(EXIT_FAILURE);
}
int launch_process(char **argv, int is_last,
int input[2], int output[2], int error)
{
pid_t pid = fork();
(void) is_last;
switch(pid) {
case -1:
perror("fork()");
return 0;
case 0:
fprintf(stderr, "LOG: Child<%ld> (%s)\n", (long) getpid(), *argv);
close(input[1]);
close(output[0]);
child_setup(argv, input[0], output[1], error);
return 0;
default:
break;
}
return 1;
}
int run_commands(char ***argvv)
{
int no_commands_ran = 0;
int in_pipe[2];
int out_pipe[2];
char **argv;
for (int i = 0; (argv = argvv[i]); ++i) {
pipe(out_pipe);
if (i == 0)
in_pipe[0] = 0;
if (!argvv[i+1]) {
close(out_pipe[0]);
close(out_pipe[1]);
out_pipe[1] = 0;
}
if (!launch_process(argv, !argvv[i+1], in_pipe, out_pipe, 0))
break;
if (i != 0) {
close(in_pipe[0]);
close(in_pipe[1]);
}
in_pipe[0] = out_pipe[0];
in_pipe[1] = out_pipe[1];
no_commands_ran = i + 1;
}
/* reap children */
pid_t pid;
int status;
while ((pid = wait(&status)) > 0)
if (WIFEXITED(status))
fprintf(stderr, "LOG: Child<%ld> process exited with status<%d>\n",
(long) pid,
WEXITSTATUS(status));
return no_commands_ran;
}
int main(void)
{
fprintf(stderr, "LOG: Parent ID: <%ld>\n", (long) getpid());
#ifdef USE_HARDCODED_INPUT
char ***argvv1 = calloc(4, sizeof(char*));
argvv1[0] = calloc(3, sizeof(char*));
argvv1[0][0] = "echo";
argvv1[0][1] = "test";
argvv1[1] = calloc(2, sizeof(char*));
argvv1[1][0] = "cat";
argvv1[2] = calloc(2, sizeof(char*));
argvv1[2][0] = "cat";
char ***argvv2 = calloc(4, sizeof(char*));
argvv2[0] = calloc(4, sizeof(char*));
argvv2[0][0] = "ls";
argvv2[0][1] = "-1";
argvv2[0][2] = "/";
argvv2[1] = calloc(2, sizeof(char*));
argvv2[1][0] = "sort";
argvv2[2] = calloc(2, sizeof(char*));
argvv2[2][0] = "rev";
puts("shell> echo test | cat | cat");
if (!run_commands(argvv1))
return EXIT_FAILURE;
/* usleep is deprecated */
nanosleep(&(struct timespec) { .tv_nsec = 5e5 }, NULL);
puts("shell> ls -1 / | sort | rev");
if (!run_commands(argvv2))
return EXIT_FAILURE;
puts("No more hardcoded commands to run");
#endif
#ifdef DEBUG
/* compile with -DDEBUG
* placing a sleep here to provide time to discover
* any file descriptor leaks
* inspect `ls -l /proc/$PID/fd`
* only the standard stream fds should exist (0, 1, 2) at
* either debug sleep
* see child_setup as well
*/
sleep(DEBUG_SLEEP_TIME);
#endif
}
Here is a cursory, annotated example of establishing a series of pipes and processes. It works similarly to your example, and might help to further showcase the order in which file descriptors must be opened, duplicated, and closed.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
int valid(int fd)
{
return fd >= 0;
}
/* these safe_* functions are a non-operation when passed negative values */
void safe_close(int fd)
{
if (valid(fd) && !valid(close(fd)))
perror("close");
}
void safe_dup2(int old, int new)
{
if (valid(old) && valid(new) && !valid(dup2(old, new)))
perror("dup2");
}
void execute(char *args[][8], size_t length)
{
int channel[2] = { -1, -1 };
for (size_t i = 0; i < length; i++) {
/* get previous reader in parent */
int from = channel[0];
/* close previous writer in parent */
safe_close(channel[1]);
/* create current-writer-to-next-reader pipe */
if (!valid(pipe(channel)))
perror("pipe");
int to = (i < length - 1) ? channel[1] : -1;
if (0 == fork()) {
/* duplicate previous reader to stdin in child */
safe_dup2(from, fileno(stdin));
/* close previous reader in child */
safe_close(from);
/* close next reader in current child */
safe_close(channel[0]);
/* duplicate current writer to stdout in child */
safe_dup2(to, fileno(stdout));
/* close current writer in child */
safe_close(channel[1]);
execvp(args[i][0], args[i]);
perror("exec");
exit(EXIT_FAILURE);
}
/* close previous reader in parent */
safe_close(from);
}
/* close final pipe in parent */
safe_close(channel[0]);
safe_close(channel[1]);
/* reap children */
pid_t pid;
int status;
while ((pid = wait(&status)) > 0)
if (WIFEXITED(status))
fprintf(stderr, "LOG: Child<%ld> process exited with status<%d>\n",
(long) pid,
WEXITSTATUS(status));
}
int main(void)
{
char *argv[][8] = {
{ "echo", "test" },
{ "cat" },
{ "cat", "-n" }
};
execute(argv, 3);
char *argv2[][8] = {
{ "ls", "-1", "/" },
{ "sort" },
{ "rev" }
};
execute(argv2, 3);
}
Aside: As an edge case, 0 is a valid file descriptor. set_process_FDs is flawed in that if STDIN_FILENO is closed, and a new file descriptor is acquired, it may be zero. if (output) or if (error) may not behave as expected.
I am writing a C code that acts as a Linux shell. I'm trying to create a program that can run a command such as 'sleep 7 &' in the background while simultaneously running a command like 'ps -l' in the foreground. Currently, my code is triggering my "perror("wait")" error message. Am I not forking at the correct location? Any insight is appreciated.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h>
int execute(char *argv[])
/*
* purpose: run a program passing it arguments
* returns: status returned via wait, or -1 on error
* errors: -1 on fork() or wait() errors
*/
{
int i = 0; /* This will be our array iterator */
size_t size = sizeof *(argv) / sizeof *(argv[0]); /* This is the size of the argv array that we will iterate through */
int pid;
int child_info = -1;
/* Check if the first argument exists */
if (argv[0] == NULL)
{ /* nothing succeeds */
return 0;
}
/* Using a for loop to traverse the argvs */
for (i; i < size; i++)
{
/* if the argv exists */
if (argv[i] != NULL)
{
/* check to see if the argv is an ampersand */
if (strcmp(argv[i], "&") == 0)
{
/* Let us know there is infact an ampersand */
printf("We have an ampersand\n");
argv[i] = '\0';
}
}
}
if ((pid = fork()) == -1)
{
perror("fork");
}
else if (pid == 0)
{
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
execvp(argv[0], argv);
perror("cannot execute command");
exit(1);
}
else
{
printf("\nProcess %d has begun in the background.\n", pid);
fork(); !!! <---- Where I'm trying to fork() and allow a new process to run in the foreground.
if (waitpid(pid, &child_info, WNOHANG) == -1)
{
perror("wait");
}
else if (waitpid(pid, &child_info, WNOHANG == 0))
{
printf("Process %d done.\n", pid);
kill(pid, SIGKILL);
}
}
return child_info;
}
Currently, it runs the sleep command but says "Wait: No Child Process".
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 am working on a ncurses based file manager in C. The problem is that some child processes can take some time to complete and till that happens it remains stuck due to waitpid.
I can't use the WNOHANG flag because the next block of code is dependent on the output of the child process.
void getArchivePreview(char *filepath, int maxy, int maxx)
{
pid_t pid;
int fd;
int null_fd;
// Reallocate `temp_dir` and store path to preview file
char *preview_path = NULL;
allocSize = snprintf(NULL, 0, "%s/preview", cache_path);
preview_path = malloc(allocSize+1);
if(preview_path == NULL)
{
endwin();
printf("%s\n", "Couldn't allocate memory!");
exit(1);
}
snprintf(preview_path, allocSize+1, "%s/preview", cache_path);
// Create a child process to run "atool -lq filepath > ~/.cache/cfiles/preview"
pid = fork();
if( pid == 0 )
{
remove(preview_path);
fd = open(preview_path, O_CREAT | O_WRONLY, 0755);
null_fd = open("/dev/null", O_WRONLY);
// Redirect stdout
dup2(fd, 1);
// Redirect errors to /dev/null
dup2(null_fd, 2);
execlp("atool", "atool", "-lq", filepath, (char *)0);
exit(1);
}
else
{
int status;
waitpid(pid, &status, 0);
getTextPreview(preview_path, maxy, maxx);
free(preview_path);
}
}
In this case, I would like to carry forward with the rest of the program if the user decides to go to some other file. In what way can I change the architecture of the program?
If I have understood the question correctly then you want to unblock parent on either completion of child or any user input.
As suggested in this comment, you could handle SIGCHLD and one more signal say SIGUSR1. SIGUSR1 will be raised when you get user input. Following is the example where both SIGCHLD and 'SIGUSR1' is handled. If use inputs any number then it raises SIGUSR1 to parent and parent kill child. Else child will raise SIGCHLD on exit.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int raised_signal = -1;
static void cb_sig(int signal)
{
if (signal == SIGUSR1)
raised_signal = SIGUSR1;
else if (signal == SIGCHLD)
raised_signal = SIGCHLD;
}
int main()
{
int pid;
int i, a;
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = cb_sig;
if (sigaction(SIGUSR1, &act, NULL) == -1)
printf("unable to handle siguser1\n");
if (sigaction(SIGCHLD, &act, NULL) == -1)
printf("unable to handle sigchild\n");
pid = fork();
if (pid == 0) {
/* child */
for (i = 0; i < 10; i++) {
sleep(1);
printf("child is working\n");
}
exit(1);
} else {
/* parent */
if (-1 == scanf("%d", &a)) {
if (errno == EINTR)
printf("scanf interrupted by signal\n");
} else {
raise(SIGUSR1);
}
if (raised_signal == SIGUSR1) {
printf("user terminated\n");
kill(pid, SIGINT);
} else if (raised_signal == SIGCHLD) {
printf("child done working\n");
}
exit(1);
}
return 0;
}
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