Issue implementing command pipes in a simple shell program - c

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.

Related

Will prefixing the script path with `/usr/bin/env -i` make usage of system() and popen() secure?

I am doing a code review right now and I was blown away by the amount of code that the guy wrote just to execute one script (with hard-coded path and no input arguments) and read the output out of it. (BTW he had a lot of bugs in it.)
I encountered a similar issue before and it was suggested to me that doing pipe/fork/exec manually is "more secure". I am aware of two potential problems:
As system() and popen() execute shell commands, it is possible to slip potentially harmful environment variable values to the program executed this way
Another is that when the command is constructed from user input. I can imagine subshells doing all kinds of harmful things and so on.
I was wondering if suggesting to use popen() instead would be OK in this case. It would greatly simplify the code. The second point is not an issue as there is no user input. By using env -i to clean the environment before executing the script should make the first issue go away:
FILE *fp = popen("/usr/bin/env -i /path/to/some/fancy/script.sh", "r");
/* ... */
Are there any other potential issues I am missing, or is doing the script execution "manually" still worth the effort?
This is technically not your answer to the question on how to call popen() safely, but the answer to the question you should have asked: "How to make a better popen()"
The function child_spawn(argv, env, flags) will set up pipes for communicating with a child process, and spawn the child. It will return a
struct child that holds the child pid and file descriptors for communication.
argv is a NULL terminated string array of command and arguments, while env is a NULL terminated string array of environment variables. If env is NULL, the child will inherit the environment from the parent.
So argv should have the form
const char* argv[] = {"/bin/ls", "-l", NULL};
And env should have the form
const char **env = NULL;
or
const char *env[] =
{
"PATH=/bin:/usr/bin",
"HOME=/tmp",
"SHELL=/bin/sh",
NULL
};
When you are finished with the child process, child_wait() will close the file descriptors associated with the child and wait for it to exit.
To use child_spawn() as a substitute of popen() you call it like this:
struct child c = child_spawn(argv, NULL, CHILD_PIPE_STDOUT);
You may now read from c->fd_out to get the content of the childs stdout.
The constants CHILD_PIPE_STDIN, CHILD_PIPE_STDOUT and CHILD_PIPE_STDERR may be "or"-ed together to have a valid file descriptors in c->fd_in, c->fd_out, c->fd_err
Be aware that if you spawn a child with CHILD_PIPE_STDIN|CHILD_PIPE_STDOUT, there is a risk of deadlock when reading and writing, unless you do non-blocking io.
The function my_system() is an example on how to implement a safer system() using child_spawn()
/*
We have to #define _GNU_SOURCE to get access to `char **environ`
*/
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/sendfile.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>
#include <string.h>
struct child
{
pid_t pid;
int fd_in;
int fd_out;
int fd_err;
};
static void
close_if_valid(int fd)
{
if (fd != -1) close(fd);
}
/*
Closes all file-descriptors for child communication
and waits for child to exit
returns status value from waitpid().
see `man waitpid` on how to interpret that value
*/
int child_wait(struct child *c)
{
close_if_valid(c->fd_in);
close_if_valid(c->fd_out);
close_if_valid(c->fd_err);
int status;
pid_t p = waitpid(c->pid, &status, 0);
if (p == 0)
error(1, errno, "waitpid() failed");
return status;
}
int
dup_if_valid(int fd1, int fd2)
{
if (fd1 != -1 && fd1 != fd2)
return dup2(fd1, fd2);
return fd2;
}
pid_t
child_spawn_fd(const char *const argv[], const char *const env[],
int in, int out, int err)
{
fflush(stdout);
pid_t p = fork();
if (p)
return p;
/***********************
We are now in child
***********************/
/*
Set file descriptors to expected values,
-1 means inherit from parent
*/
if (dup_if_valid(in, 0) == -1)
goto CHILD_ERR;
if (dup_if_valid(out, 1) == -1)
goto CHILD_ERR;
if (dup_if_valid(err, 2) == -1)
goto CHILD_ERR;
/*
close all unneeded file descriptors
This will free resources and keep files and sockets belonging to
the parent from beeing open longer than needed
On *BSD we may call `closefrom(3);`, but this may not exits
on Linux. So we loop over all possible file descriptor numbers.
A better solution, is to look in `/proc/self/fs`
*/
int max_fd = sysconf(_SC_OPEN_MAX);
for (int fd = 3; fd <= max_fd; fd++)
close(fd);
if (env)
environ = (char **)env;
/* Change to execvp if command should be looked up in $PATH */
execv(argv[0], (char * const *)argv);
CHILD_ERR:
_exit(1);
}
#define CHILD_PIPE_STDIN (1 << 0)
#define CHILD_PIPE_STDOUT (1 << 1)
#define CHILD_PIPE_STDERR (1 << 2)
#define READ_END 0
#define WRITE_END 1
struct child
child_spawn(const char * const argv[], const char * const env[], int flags)
{
int in_pipe[2] = {-1, -1};
int out_pipe[2] = {-1, -1};
int err_pipe[2] = {-1, -1};
if (flags & CHILD_PIPE_STDIN)
if (pipe(in_pipe))
error(EXIT_FAILURE, errno, "pipe(in_pipe) failed");
if (flags & CHILD_PIPE_STDOUT)
if (pipe(out_pipe))
error(EXIT_FAILURE, errno, "pipe(out_pipe) failed");
if (flags & CHILD_PIPE_STDERR)
if (pipe(err_pipe))
error(EXIT_FAILURE, errno, "pipe(err_pipe) failed");
pid_t p = child_spawn_fd(argv, env,
in_pipe[READ_END],
out_pipe[WRITE_END],
err_pipe[WRITE_END]);
if (p == -1)
error(EXIT_FAILURE, errno, "fork() failed");
close_if_valid(in_pipe[READ_END]);
close_if_valid(out_pipe[WRITE_END]);
close_if_valid(err_pipe[WRITE_END]);
struct child c =
{
.pid = p,
.fd_in = in_pipe[WRITE_END],
.fd_out = out_pipe[READ_END],
.fd_err = err_pipe[READ_END],
};
return c;
}
/*
Safer implementation of `system()`. It does not invoke shell, and takes
command as NULL terminated list of execuatable and parameters
*/
int
my_system(const char * const argv[])
{
struct child c = child_spawn(argv, NULL, 0);
int status = child_wait(&c);
if (WIFEXITED(status))
return WEXITSTATUS(status);
else
return -1;
}
int
main (int argc, char **argv)
{
printf("Running 'ls -l' using my_system()\n");
printf("---------------------------------\n");
fflush(stdout);
const char * ls_argv[] =
{
"/bin/ls",
"-l",
NULL
};
int e = my_system(ls_argv);
printf("---------\n");
printf("\exit code ---> %d\n", e);
printf("\nRunning 'ls -l' using child_spawn() and reading from stdout\n");
printf("-----------------------------------------------------------\n");
fflush(stdout);
struct child c = child_spawn(ls_argv, NULL, CHILD_PIPE_STDOUT);
/*
Read from the childs stdout and write to current stdout
*/
size_t copied = 0;
while (1)
{
char buff[4096];
ssize_t rlen = read(c.fd_out, buff, 4096);
if (rlen == -1)
error(EXIT_FAILURE, errno, "read() failed");
if (rlen == 0)
break;
size_t written = 0;
while (written < rlen)
{
ssize_t wlen = write(1, buff + written, rlen - written);
if (wlen == -1)
error(EXIT_FAILURE, errno, "write() failed");
written += wlen;
}
copied += written;
}
/* Wait for child to end */
int status = child_wait(&c);
printf("---------\n");
if (WIFEXITED(status))
{
printf(" ---> child exited normally with exit code %d and with %ld bytes copied\n",
WEXITSTATUS(status),
copied);
}
else
printf(" ---> child exited by som other reason than _exit()");
printf("\nWriting to Elmer Fudd filter\n");
const char *quote = "Be very very quiet, I'm hunting rabbits!\n";
printf("Original text: %s", quote);
printf("-----------------------------------------------------------\n");
fflush(stdout);
const char *fudd_filter[] =
{"/bin/sed", "-e" "s/r/w/g", NULL};
struct child c2 = child_spawn(fudd_filter, NULL, CHILD_PIPE_STDIN);
size_t qlen = strlen(quote);
const char *q = quote;
while (qlen)
{
ssize_t wlen = write(c2.fd_in, q, qlen);
if (wlen == -1)
error(EXIT_FAILURE, errno, "write() failed");
q += wlen;
qlen -= wlen;
}
child_wait(&c2);
}

Killing a process once time limit exceeded

I've been working on the following code for quite some time, but can't really figure it out.
The task is to read a terminal command and to run it every x seconds; if the command hasn't finished within the waiting time, we want to kill the process and afterwards run the command again.
Any help would be really appreciated.
I'm pretty sure I'm not using waitpid() correctly; how would I go about using waitpid to achieve the goal?
Additionally, how would I go about detecting an error within a child process? The plan is to kill the parent process if an error occurred in a child process.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
/*
*
*/
int main(int argc, char** argv) {
int waitingTime;
if (argc < 3) {
printf("Invalid number of arguments provided. Please specify a command and exactly one parameter.");
return (EXIT_FAILURE);
}
// -n parameter specified? If so, set the waiting time.
if (argc == 5 && strcmp(argv[3], "-n") == 0) {
waitingTime = atoi(argv[4]);
} else {
waitingTime = 5; // Default waiting time.
}
char* cmd = (char*)malloc(sizeof(argv[1]));
cmd = argv[1];
char* param = (char*)malloc(sizeof(argv[2]));
param = argv[2];
// Print the read command and its param
printf("Command: %s, Parameter: %s, Interval: %d\n\n", cmd, param, waitingTime);
pid_t pid;
for (;;) {
// Declared here for scope
int secsWaited;
secsWaited = 0;
pid = fork();
if (pid == 0) {
pid = getpid();
printf("==============\n");
execlp(cmd, cmd, param, "/", (char *)NULL);
printf("Excec failed; killing the proccess.");
kill(pid, SIGKILL);
} else if (pid > 0) {
int status, code;
for (;;) {
code = waitpid(pid, &status, WNOHANG);
if (code == 0 && secsWaited >= waitingTime) {
kill(pid, SIGKILL);
printf("Child stopped");
break;
} else if (code == 0 && secsWaited < waitingTime) {
secsWaited++;
sleep(1);
} else {
break;
}
}
/*if (!WIFEXITED(status)) {
printf("Time exceeding, stopping child.");
// Get parent process id and kill it.
kill(getpid(), SIGKILL);
}*/
// Sleep for the specified time
sleep(waitingTime - secsWaited);
} else {
return (EXIT_FAILURE);
}
}
free(cmd);
free(param);
return (EXIT_SUCCESS);
}
Your logic was a bit too complicated (e.g. too many different sleep calls and if/else ladder logic).
Also, no need to malloc the argv strings--they can be used directly.
I've simplified it and restructured a bit to get it to work [please pardon the gratuitous style cleanup]:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
/*
*
*/
int
main(int argc, char **argv)
{
int waitingTime;
if (argc < 3) {
printf("Invalid number of arguments provided. Please specify a command and exactly one parameter.");
return (EXIT_FAILURE);
}
// -n parameter specified? If so, set the waiting time.
if (argc == 5 && strcmp(argv[3], "-n") == 0) {
waitingTime = atoi(argv[4]);
}
else {
waitingTime = 5; // Default waiting time.
}
char *cmd = argv[1];
char *param = argv[2];
// Print the read command and its param
printf("Command: %s, Parameter: %s, Interval: %d\n\n",
cmd, param, waitingTime);
pid_t pid;
int code = -1;
int status;
int killflg = 1;
for (;;) {
// Declared here for scope
int secsWaited;
secsWaited = 0;
pid = fork();
// stop on fork failure
if (pid < 0) {
killflg = 1;
break;
}
// child process
if (pid == 0) {
pid = getpid();
printf("==============\n");
#if 0
execlp(cmd, cmd, param, "/", (char *) NULL);
#else
execlp(cmd, cmd, param, (char *) NULL);
#endif
printf("Excec failed; killing the proccess.");
// NOTE/BUG: this is the child so pid is zero, so killing it is wrong
#if 0
kill(pid, SIGKILL);
#else
exit(1);
#endif
}
killflg = 0;
for (;;) {
code = waitpid(pid, &status, WNOHANG);
if (code > 0)
break;
if (killflg)
continue;
secsWaited++;
sleep(1);
if (secsWaited >= waitingTime) {
printf("timeout\n");
kill(pid, SIGKILL);
killflg = 1;
}
}
if (! killflg)
break;
}
#if 0
free(cmd);
free(param);
#endif
if (killflg)
code = EXIT_FAILURE;
else
code = EXIT_SUCCESS;
return code;
}
UPDATE:
Right now, the program will stop after one iteration; if I remove the breakpoint at if (! killflg), it will work as expected. Am I missing something or is this just a misunderstanding?
You are correct--my bad. I had missed the following in your question:
The task is to read a terminal command and to run it every x seconds;
Change the break into sleep(waitingTime - secsWaited).
But, a more robust way to keep track of elapsed time may be via two calls to time(2):
After the killflg = 0, do: time_t todbeg = time(NULL); time_t todelap;. Then, you can get elapsed time [anywhere] with: todelap = time(NULL) - todbeg; [here, todelap is similar to secsWaited]. This may be better than incrementing secsWaited.
time only has seconds resolution. For more precision control, consider using clock_gettime [has nanosecond resolution].
Here's a function that I use a lot for elapsed time [in fractional seconds]:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <time.h>
double
tvgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_REALTIME,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
return sec;
}
/*
*
*/
int
main(int argc, char **argv)
{
int waitingTime;
if (argc < 3) {
printf("Invalid number of arguments provided. Please specify a command and exactly one parameter.");
return (EXIT_FAILURE);
}
// -n parameter specified? If so, set the waiting time.
if (argc == 5 && strcmp(argv[3], "-n") == 0) {
waitingTime = atoi(argv[4]);
}
else {
waitingTime = 5; // Default waiting time.
}
char *cmd = argv[1];
char *param = argv[2];
// Print the read command and its param
printf("Command: %s, Parameter: %s, Interval: %d\n\n", cmd, param, waitingTime);
pid_t pid;
int code = -1;
int status;
int killflg = 1;
double todzero = tvgetf();
for (;;) {
// Declared here for scope
double todbeg = tvgetf();
double todelap;
pid = fork();
// stop on fork failure
if (pid < 0) {
killflg = 1;
break;
}
// child process
if (pid == 0) {
pid = getpid();
printf("============== (%.9f)\n",tvgetf() - todzero);
execlp(cmd, cmd, param, (char *) NULL);
printf("Excec failed; killing the proccess.");
exit(1);
}
killflg = 0;
for (;;) {
code = waitpid(pid, &status, WNOHANG);
if (code > 0)
break;
if (killflg)
continue;
usleep(1000);
todelap = tvgetf() - todbeg;
if (todelap >= waitingTime) {
printf("timeout\n");
kill(pid, SIGKILL);
killflg = 1;
}
}
// do _not_ wait -- we already timed out
if (killflg)
continue;
// get final elapsed time for this round and the amount of time
// remaining until the next interval
todelap = tvgetf() - todbeg;
useconds_t time_to_wait = ((double) waitingTime - todelap) * 1e6;
// wait until the next time period
if (time_to_wait > 0)
usleep(time_to_wait);
}
if (killflg)
code = EXIT_FAILURE;
else
code = EXIT_SUCCESS;
return code;
}
Side note: I used usleep here, but, although slightly more complex, it's considered better to use nanosleep

C fork loop to exec infinite commands (urandom/tail on an active file)

I'm trying to reproduce the behaviour of a pipe in a unix (OSX) shell (like bash) with C.
I have no problem to handle a command which is not infinite (for example: ls | wc -c).
But as you can imagine, I have an issue, I can't handle an infinite command, for instance: base64 /dev/urandom" | head -c 1000.
The output of this command is immediately the first 1000 characters of urandom.
My function just wait the end of the urandom which is infite... So, I need to kill with "CTRL + C" the process (and handle the signal to just the child and not my function) to print the first 1000 character.
My function to execute all the pipes:
#define READ_END 0
#define WRITE_END 1
void exec_pipes(char ***argv)
{
int p[2];
int fd_save = 0;
while (*argv)
{
pipe(p);
if (fork() == 0)
{
dup2(fd_save, STDIN_FILENO);
if (*(argv + 1))
dup2(p[WRITE_END], STDOUT_FILENO);
close(p[READ_END]);
execvp(**argv, *argv);
exit(EXIT_FAILURE);
}
else
{
wait(NULL);
fd_save = p[READ_END];
close(p[WRITE_END]);
(*argv)++;
}
}
}
Here, you can check my entire code with the main :
https://www.onlinegdb.com/Hkbjd3WOz
Thanks in advance.
Not, you don't. When a reading process just finishes, it closes the reading side of the pipe, and so the writer is not allowed to write anymore to the pipe, and gets an error EPIPE (cannot write to pipe) from the system.
For this to ocurr, no process has to have it open for reading, because the kernel counts the number of reading and writing processes, and will not give this error while at least one process left it open for reading (this means the first process in the chain has to close the read file descriptor of the pipe, if it is not going to read from it)
Then, you have to close the descriptor not used (it depends if you want the parent to be the writer or the reader) IN THE PARENT AND CHILD, and dup2(2) the other descriptor (again, IN THE PARENT AND CHILD) to file descriptor 0 (in) or 1 (out). (first to say, I've changed your /dev/urandom example by a call to the yes command, because it's standard in all unix flavours, and produces also infinite output)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char *writer_program = "yes";
char *argv_writer[] = {
"yes", "yes", NULL,
};
char *reader_program = "head";
char *argv_reader[] = {
"head", "-c", "10", NULL,
};
int main()
{
int pipe_fds[2];
int res;
pipe(pipe_fds);
if ((res = fork()) < 0) { /* PLEASE, CHECK FOR ERRORS. */
perror("fork");
} else if (res == 0) { /* child process, ACTING AS WRITER */
close(pipe_fds[0]); /* close writing part of pipe */
dup2(pipe_fds[1], 1); /* dup2 reading part as stdin */
execvp(writer_program, argv_writer);
perror("execvp"); /* PLEASE, CHECK FOR ERRORS. */
} else { /* parent process, ACTING AS A READER */
close(pipe_fds[1]); /* just the opposite */
dup2(pipe_fds[0], 0);
execvp(reader_program, argv_reader);
perror("execvp"); /* PLEASE, CHECK FOR ERRORS. */
}
/* BOTH, THIS IS REACHED IN CASE OF FAILURE (fork fails, or any
* of the exec(2) calls fail. */
exit(EXIT_FAILURE);
}
EDIT
Below is a complete example with a pipeline chain equivalent to the next:
dd if=/dev/urandom ibs=1024 | base64 | head -n 150 | sort -r | pr
in this case, the first program to die is head, as you wanted, and every program dies behind it. The parent program is waiting for all his children to die, and every system call has been wrapped, so you get a trace of execution through stderr, to see what happens.
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define F(fmt) "[pid=%d]:"__FILE__":%d:%s: " fmt, getpid(), __LINE__, __func__
#define SIZE(a) (sizeof a/sizeof *a)
#define ERR(fmt, ...) do{\
fprintf(stderr, \
F(fmt": %s (errno = %d)\n"),\
##__VA_ARGS__,\
strerror(errno),\
errno);\
exit(EXIT_FAILURE);\
}while(0)
#define CLOSE(expr) do{\
close(expr);\
fprintf(stderr, \
F("close("#expr" === %d);\n"), \
(expr));\
}while(0)
#define PIPE(var) do{\
if(pipe(var)<0) ERR("pipe"); \
fprintf(stderr,F("PIPE ==> %d, %d\n"), var[0], var[1]);\
}while(0)
#define FORK() do{\
if((res = fork()) < 0)ERR("fork");\
fprintf(stderr,F("FORK ==> %d\n"), res);\
}while(0)
#define DUP(expr1, expr2) do{\
if((res = dup2(expr1, expr2)) < 0) ERR("dup");\
fprintf(stderr,\
F("DUP("#expr1" === %d, "#expr2" === %d);\n"),\
(expr1), (expr2));\
}while(0)
char * argv_DISK_DUMP[] = { "dd", "if=/dev/urandom", "ibs=1024", NULL };
char * argv_BASE64[] = { "base64", NULL };
char * argv_HEAD[] = { "head", "-n", "150", NULL };
char * argv_SORT[] = { "sort", "-r", NULL };
char * argv_PR[] = { "pr", NULL };
struct pipe_program {
pid_t pid;
pid_t ppid;
char *pname;
char **argv;
} pipe_programs[] = {
0, 0, "dd", argv_DISK_DUMP,
0, 0, "base64", argv_BASE64,
0, 0, "head", argv_HEAD,
0, 0, "sort", argv_SORT,
0, 0, "pr", argv_PR,
};
/* size of last array */
size_t pipe_programs_n = SIZE(pipe_programs);
static size_t printus(int ix, struct pipe_program *p);
static pid_t WAIT(int *status);
int main()
{
int res, i;
struct pipe_program *p = pipe_programs;
int input_fd = 0; /* first process is connected to standard input */
static int pipe_fds[2] = { -1, -1 };
for(i = 0; i < pipe_programs_n - 1; i++, p++) {
PIPE(pipe_fds);
FORK();
if (res == 0) { /* child process, we have redirected nothing yet. */
p->pid = getpid();
p->ppid = getppid();
/* print who we are */
printus(i, p);
/* redirect input, if needed */
if (input_fd != 0) {
DUP(input_fd, 0);
CLOSE(input_fd); /* not used after redirection */
}
CLOSE(pipe_fds[0]); /* we don't use this */
/* and output */
DUP(pipe_fds[1], 1);
CLOSE(pipe_fds[1]);
execvp(p->pname, p->argv);
ERR("execvp: %s", p->pname);
/* NOTREACHED */
}
/* parent process */
/* save pid to be used later */
p->pid = res; /* we'll use it later */
p->ppid = getpid();
/* close unused pipe descriptor */
CLOSE(pipe_fds[1]);
/* if we have an old input_fd, then close it */
if (input_fd) CLOSE(input_fd);
/* ... and save pipe read descriptor */
input_fd = pipe_fds[0];
} /* for */
/* now we have our input connected to the output of the last process */
FORK();
if (res == 0) { /* child, last process in the pipe */
p->pid = getpid();
p->ppid = getppid();
/* print who we are */
printus(i, p);
/* redirect input */
if (input_fd != 0) {
DUP(input_fd, 0);
CLOSE(input_fd); /* not used after_redirection */
}
/* this time no output redirection */
execvp(p->pname, p->argv);
ERR("execvp");
/* NOTREACHED */
}
CLOSE(pipe_fds[1]);
if (input_fd) CLOSE(input_fd);
/* parent code... we did pipe_programs_n fork()'s so we
* have to do pipe_programs_n wait()'s */
int status;
pid_t cld_pid;
/* while we can wait for a child */
while ((cld_pid = WAIT(&status)) > 0) {
for (i = 0, p = pipe_programs; i < pipe_programs_n; i++, p++) {
if (cld_pid == p->pid) {
fprintf(stderr,
F("Child finished: pid = %d\n"),
cld_pid);
printus(i, p);
break;
}
}
} /* while */
exit(EXIT_SUCCESS);
}
static size_t printus(int ix, struct pipe_program *p)
{
size_t res = 0;
int j;
static char buffer[1024];
char *s = buffer;
size_t bfsz = sizeof buffer;
size_t n;
#define ACT() do{s+=n; bfsz-=n;}while(0)
n = snprintf(s, bfsz,
F("%d: pid = %d, ppid = %d, program = \"%s\": args = "),
ix, p->pid, p->ppid, p->pname);
ACT();
for (j = 0; p->argv[j]; j++) {
n = snprintf(s, bfsz,
"%s\"%s\"",
j ? ", " : "{",
p->argv[j]);
ACT();
}
n = snprintf(s, bfsz, "};\n");
ACT();
fputs(buffer, stderr);
return res;
}
static pid_t WAIT(int *status)
{
pid_t res = wait(status);
fprintf(stderr, F("WAIT() ==> %d\n"), res);
return res;
}
you can get the whole program with the following command:
git clone git#github.com:mojadita/pipe.git
beware that the program there is almost exactly equal, but some work has been done to facilitate the writing of different pipelines without having to touch several places in the main source file. Edit the file pipe.i if you download the program from the git server and you want to modify the pipeline. Then make and voilá!!! :) The program has been tested on linux, freebsd and mac osx, so I think you'll have no problem to adapt it to your needs. If you get it from the github, you'll also have a Makefile.

named pipe won't open in C program

I have user read/write permissions on a pipe. Group has read. Other has read. But program gets "stuck" when I run it. Program 1 is the "parent". Program 2 is the "child".
Program 1:
int main(int argc, char * argv[])
{
FILE *fptr; //for opening and closing input file
int fdw;// write to pipe;
int fdr; //read to pipe;
pid_t pid;
int inputarray[500];
int arraylength = 0; int j =0;
char *mypipe = "mypipe";
if (argc < 2)
{
printf("Need to provide the file's name. \n");
return EXIT_FAILURE;
}
//open input file
fptr = fopen(argv[1], "r");
if (fptr==NULL)
{
printf("fopen fail.\n");
return EXIT_FAILURE;
}
//read input file and fill array with integers
while (!feof(fptr))
{
fscanf(fptr,"%d",&inputarray[arraylength]);
arraylength = arraylength + 1;
}
fclose(fptr); //close input file
pid = fork();
mkfifo(mypipe, 0666);
fdw = open("mypipe",O_WRONLY);
if (fdw < 0)
{
perror("File can't open to write.");
return;
}
int b;
b=3;
write(fdw,&b,sizeof(b));
close(fdw);
if ( pid ==-1)
{
perror("fork");
exit(1);
}
int status; //exit status of child
if(pid==0)//if child process
{
execl("program2", (char*) NULL);
}
else //if parent process
{
wait(&status);}
if((WIFEXITED(status)))
{
printf("Child's exit code %d", WEXITSTATUS(status));
}
else{
printf("Child did not terminate with exit");}
}
Program 2:
int fdl;
int data;
fdl = open("mypipe",O_RDONLY);
if ( fdl < 0)
{
perror("File can't open to read.");
return;
}
read(fdl,&data,sizeof(data));
close(fdl);
The program will block on writing to the fifo until what it's writing is being read. The reading in the child process won't happen since the execl() doesn't happen until after the writing.
Also, it looks like both processes will actually attempt to write to the fifo since you fork() and then immediately start writing.
You should fork(), then test on the returned PID. The parent should then write to the fifo while the child should call execl(). The fifo should be created by the parent before the fork() call.
You should also consider using indent or clang-format to properly format your code, which eases reading it and may expose bugs (forgotten curly braces etc.).
A simple complete example program. The parent writes a string to the child and the child reads it character by character and outputs it to standard output:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
void parent(void);
void child(void);
int main(void) {
pid_t pid;
mkfifo("myfifo", 0666); /* fails if exists, but we don't care here */
if ((pid = fork()) < 0)
abort();
if (pid == 0)
child(); /* will not return */
else
parent();
return EXIT_SUCCESS;
}
void parent(void) {
int fd;
int len;
int ret;
int stat;
char *ptr;
char *msg = "Hello World!";
if ((fd = open("myfifo", O_WRONLY)) < 0)
abort();
len = strlen(msg) + 1;
ptr = msg;
puts("Parent: About to write to child");
while ((ret = write(fd, ptr, len)) != 0) {
if (ret > 0) {
len -= ret;
ptr += ret;
} else
abort();
}
close(fd);
puts("Parent: Waiting for child to exit");
wait(&stat);
printf("Parent: Child exited with status %d\n", stat);
}
void child(void) {
int fd;
int ret;
char ch;
if ((fd = open("myfifo", O_RDONLY)) < 0)
abort();
puts("Child: About to read from parent");
while ((ret = read(fd, &ch, 1)) != 0) {
if (ret > 0)
putchar(ch);
else
abort();
}
putchar('\n');
close(fd);
puts("Child: I'm done here");
exit(EXIT_SUCCESS);
}
In this case, since both child and parent processes are in the same context, I could have used an anonymous pipe pair created with pipe(), but this illustrates the flow, including the creation of the named pipe.

C - Redirecting stdout after forking of child from parent

I'm writing a program to execute another program as a forked process and redirect it's output to a file or /dev/null on demand.
Currently I have forked and executed the external program using execvp().
Then redirected the stdout from a thread created before forking as the forked process will inherit parents file descriptor table allowing me to redirect after foking.
But, I can initially redirect stdout to a desired file and both parents and child's stdouts are being redirected. However if I try to redirect it again to another file, only parents stdout is redirected, child's stdout stays the same.
Here's the code without all the error checking bits.
struct params {
const char *p;
int fd;
int wait;
};
#define EXIT_NOEXEC 126
#define EXIT_NOTFOUND 127
#define EXIT_MISC 127
static void dofile(struct params* st);
void dupit(const char *p, struct params* st);
void* reload_config(void* para);
int
main(int argc, char *argv[]) {
int exit_status, prog_status;
struct params init;
pid_t prog_pid;
dofile(&init);
prog_pid = fork();
if (prog_pid == 0) {
execvp(*argv, argv);
exit_status = (errno == ENOENT) ? EXIT_NOTFOUND : EXIT_NOEXEC;
err(exit_status, "%s", argv[0]);
exit(EXIT_FAILURE);
} else {
while (wait(&prog_status) != prog_pid);
return prog_status;
}
}
static void dofile(struct params* st) {
const char *p
p = out.txt;
dupit(p, st);
}
void dupit(const char *p, struct params* st) {
pthread_t tid;
st->wait = 0;
int err = pthread_create(&(tid), NULL, &reload_config, st);
if (err != 0) {
printf("\ncan't create thread :[%s]", strerror(err));
exit(1);
} else {
while (st->wait == 0) {
sleep(1)
}
}
}
void* reload_config(void* para) {
struct params *passed = (struct params *) para;
int pre_config = 3;
int cur_config = 1;
int saved_stdout = dup(STDOUT_FILENO);
char infile[5];
int devNull = open("/dev/null", O_WRONLY);
int file = open("out.txt", O_WRONLY);
FILE *config;
config = fopen("config.txt", "r");
if (access("config.txt", F_OK) != -1) {
while (1) {
fgets(infile, 5, config);
fclose(config);
cur_config = infile[0] - '0';
printf("output from thread, current config = %d\n", cur_config);
if (pre_config != cur_config) {
if (cur_config == 1) {
if (dup2(file, STDOUT_FILENO) == -1) {
err(EXIT_MISC, NULL);
}
} else {
dup2(devNull, STDOUT_FILENO);
}
pre_config = cur_config;
}
if (passed->wait==0) {
passed->wait = 1;
}
sleep(1);
}
} else {
if (dup2(passed->fd, STDOUT_FILENO) == -1) {
err(EXIT_MISC, NULL);
}
}
}
Well, I changed the code a bit so you guys will understand, so some parts will make no sense. But you get the basic idea.
How can I redirect child's stdout as I wish after forking.
Since you asked, here is a simple example. Some shortcuts have been taken for brevity but hopefully it gives you some idea. The program opens file1 and redirects stdout to that file. It then does a fork. The child process writes a counter to stdout (via printf) every 1 second. After a few seconds the parent process uses IPC, a pipe in this example, to tell the child to switch redirect file.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char **argv)
{
pid_t pid;
const char *file1 = "file1.txt";
const char *file2 = "file2.txt";
int pipefd[2];
int fd;
int rval;
fd = open(file1, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);
if (fd == -1) {
perror("file1 open");
exit(-1);
}
/*
* This pipe will be used by parent process to tell child which file
* to redirect to.
*/
rval = pipe2(pipefd, O_NONBLOCK);
if (fd == -1) {
perror("pipe");
exit(-1);
}
/* Redirect stdout to the file opened before the fork. */
dup2(fd, STDOUT_FILENO);
pid = fork();
if (pid == -1) {
perror("fork");
exit(-1);
} else if (pid == 0) {
/* Child process. */
int ix;
char redirect_file[100];
close(pipefd[1]);
for (ix = 0; ix < 10; ix++) {
printf("%d\n", ix);
sleep(1);
rval = read(pipefd[0], redirect_file, sizeof(redirect_file));
if (rval > 0) {
/*
* Parent process has written a filename to the pipe.
*/
fd = open(redirect_file, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);
if (fd == -1) {
perror("file2 open");
exit(-1);
}
/* Ensure previous output has been written to current file. */
fflush(stdout);
/* Change redirect now. */
dup2(fd, STDOUT_FILENO);
}
}
} else {
/* Parent process. */
close(pipefd[0]);
/* Wait a little and then tell child to change redirect file. */
sleep(5);
write(pipefd[1], file2, strlen(file2) + 1);
wait();
}
}
If this program is run you will find that half the child output went to file1 (first redirect) and other half of the output goes to file2 (second redirect).
$ cat file1.txt
0
1
2
3
4
$ cat file2.txt
5
6
7
8
9
One final note. The example program does the first dup before the fork. I did it like that because that's how your code was shown and also to emphasise the before and after fork aspect of the issue. But in real code the conventional way of doing that is to do fork first, then dup and finally exec. The dup is done after the fork so that only the child process gets affected and not the parent (unless that is really what you want).

Resources