Skip to content

Instantly share code, notes, and snippets.

@iximiuz
Last active July 6, 2023 11:52
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save iximiuz/65c7d2d128c374ef83d885dfef74bed7 to your computer and use it in GitHub Desktop.
Save iximiuz/65c7d2d128c374ef83d885dfef74bed7 to your computer and use it in GitHub Desktop.
execve() file descriptors sharing example

Description

As everybody knows, execve() system call replaces a calling process image with a new process image. At the same time the file descriptors table remains the same.

The idea of this example is to show how launched via execve() processes can access file descriptors from their parent.

The parent process creates 4 pipes (unidirectional data flows) for stdin/stdout streams of its two children. Then the parent forks itself twice to spawn its children. Each child process binds corresponding pipes to its stdin & stdout and then runs child executable via execl(). To simplify the example, child processes also explicitly shares their stdin file descriptors between each other via command line argument. In the end parent is able to read from children stdouts and write to their stdins, while children are able to write to sibling's stdins (see the schema below).

                      +------------+
                      |            |
                      |            |
                      |   parent   |
                      |            |
                      |            |
                      +---+----+---+
                          |    |
                          |    |
   +------------+  stdin  |    |        +------------+
   |            |<--------+----0--------|            |
   |            |              |        |            |
   |  child  I  |              |        |  child II  |
   |            |              | stdin  |            |
   |            |--------------+------->|            |
   +------------+                       +------------+

Run

# compile parent 
> cc parent.c -o parent

# compile child
> cc child.c -o child

# launch the example
> ./parent `pwd`/child

# expected output
> Parent started
> Parent forked first child
> Parent forked second child
> Message from parent has been written to first child stdin
> Message from parent has been written to second child stdin
> Child 1 STDOUT: echo from [first]: [message from parent]
> Child 2 STDOUT: echo from [second]: [message from parent]
> Child 1 STDOUT: echo from [first]: [message from second child]
> Child 2 STDOUT: echo from [second]: [message from first child]
> Parent finished

Conclusions

Do not forget to close file descriptors in forked processes before executing ;-)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]) {
char *name = argv[1];
char *buf = NULL;
size_t linecap = 0;
ssize_t linelen = getline(&buf, &linecap, stdin);
if (0 > linelen) {
perror("getline() failed");
exit(1);
}
buf[linelen - 1] = 0;
printf("echo from [%s]: [%s]\n", name, buf);
fflush(stdout);
free(buf);
buf = NULL;
FILE *f = fdopen(strtol(argv[2], (char **)NULL, 10), "w");
char msg[64] = {};
snprintf(msg, 64, "message from %s child\n", name);
if (1 != fwrite(msg, strlen(msg), 1, f)) {
perror("fwrite() failed");
exit(1);
}
fflush(f);
linecap = 0;
linelen = getline(&buf, &linecap, stdin);
if (0 > linelen) {
perror("getline() failed");
exit(1);
}
buf[linelen - 1] = 0;
printf("echo from [%s]: [%s]\n", name, buf);
fflush(stdout);
free(buf);
buf = NULL;
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main(int argc, char *argv[]) {
printf("Parent started\n");
int child1_in_pipe[2];
int child1_out_pipe[2];
if (0 != pipe(child1_in_pipe) || 0 != pipe(child1_out_pipe)) {
perror("cannot create pipe");
exit(1);
}
int child2_in_pipe[2];
int child2_out_pipe[2];
if (0 != pipe(child2_in_pipe) || 0 != pipe(child2_out_pipe)) {
perror("cannot create pipe");
exit(1);
}
pid_t child1_pid = fork();
if (child1_pid < 0) {
perror("cannot fork first child process");
exit(1);
}
if (child1_pid == 0) { // first child
dup2(child1_in_pipe[0], 0);
close(child1_in_pipe[0]);
close(child1_in_pipe[1]);
dup2(child1_out_pipe[1], 1);
close(child1_out_pipe[0]);
close(child1_out_pipe[1]);
// Uncommnet lines below to prevent observed behaviour
// close(child2_in_pipe[0]);
// close(child2_in_pipe[1]);
// close(child2_out_pipe[0]);
// close(child2_out_pipe[1]);
char fildes[64];
snprintf(fildes, 64, "%d", child2_in_pipe[1]);
execl(argv[1], argv[1], "first", fildes, NULL);
perror("first child exec() failed");
exit(1);
}
printf("Parent forked first child\n");
pid_t child2_pid = fork();
if (child2_pid < 0) {
perror("cannot fork second child process");
exit(1);
}
if (child2_pid == 0) { // second child
dup2(child2_in_pipe[0], 0);
close(child2_in_pipe[0]);
close(child2_in_pipe[1]);
dup2(child2_out_pipe[1], 1);
close(child2_out_pipe[0]);
close(child2_out_pipe[1]);
// Uncommnet lines below to prevent observed behaviour
// close(child1_in_pipe[0]);
// close(child1_in_pipe[1]);
// close(child1_out_pipe[0]);
// close(child1_out_pipe[1]);
char fildes[64];
snprintf(fildes, 64, "%d", child1_in_pipe[1]);
execl(argv[1], argv[1], "second", fildes, NULL);
perror("second child exec() failed");
exit(1);
}
printf("Parent forked second child\n");
FILE *child1_stdin = fdopen(child1_in_pipe[1], "w");
FILE *child1_stdout = fdopen(child1_out_pipe[0], "r");
FILE *child2_stdin = fdopen(child2_in_pipe[1], "w");
FILE *child2_stdout = fdopen(child2_out_pipe[0], "r");
char msg[] = "message from parent\n";
if (1 != fwrite(msg, strlen(msg), 1, child1_stdin)) {
perror("fwrite() to first child stdin failed");
}
fflush(child1_stdin);
printf("Message from parent has been written to first child stdin\n");
if (1 != fwrite(msg, strlen(msg), 1, child2_stdin)) {
perror("fwrite() to second child stdin failed");
}
fflush(child2_stdin);
printf("Message from parent has been written to second child stdin\n");
for (int i = 0; i < 2; i++) {
char *buf = NULL;
size_t linecap = 0;
ssize_t linelen = getline(&buf, &linecap, child1_stdout);
if (0 > linelen) {
perror("getline() from first child stdout failed");
exit(1);
}
printf("Child 1 STDOUT: %s", buf);
free(buf);
buf = NULL;
linecap = 0;
linelen = getline(&buf, &linecap, child2_stdout);
if (0 > linelen) {
perror("getline() from second child stdout failed");
exit(1);
}
printf("Child 2 STDOUT: %s", buf);
free(buf);
}
printf("Parent finished\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment