Skip to content

Instantly share code, notes, and snippets.

@apage43
Created October 6, 2017 09:18
Show Gist options
  • Save apage43/15e2919299417ff7ee1178834f6d1bbc to your computer and use it in GitHub Desktop.
Save apage43/15e2919299417ff7ee1178834f6d1bbc to your computer and use it in GitHub Desktop.
// how to create a process pipeline in C, or:
// how does the shell execute something like this:
//// $ awk '{print $3}' < /proc/mounts | sort | uniq -c
#define _GNU_SOURCE // needed for pipe2
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
// i like the error check macro
#define CHECK(x) if ((x) < 0) { perror(#x); exit(1); }
// because i get them backwards all the time
#define READ_END(pipe) pipe[0]
#define WRITE_END(pipe) pipe[1]
int main(int argc, char **argv) {
int procmounts = open("/proc/mounts", O_RDONLY | O_CLOEXEC);
CHECK(procmounts);
int awktosort[2];
// we pass O_CLOEXEC to pipe so that when our child procs
// dup2 over their stdin/stdout and then exec() their new process
// that new process will not have the higher-numbered fds open,
// only the stdio fds.
CHECK(pipe2(awktosort, O_CLOEXEC));
int awk = fork();
CHECK(awk);
if (awk == 0) {
// replace our stdin with the procmounts fd
CHECK(dup2(procmounts, 0));
// send our stdout to sort
CHECK(dup2(WRITE_END(awktosort), 1));
// using execlp to search $PATH is -kiinda- cheating since that's a lot
// more work from libc than just a syscall wrapper
execlp("awk", "awk", "{print $3}", NULL);
exit(1); // should not get here
}
close(procmounts);
close(WRITE_END(awktosort));
int sorttouniq[2];
CHECK(pipe2(sorttouniq, O_CLOEXEC));
int sort = fork();
CHECK(sort);
if (sort == 0) {
// replace our stdin with the pipe connected to awk's stdout
CHECK(dup2(READ_END(awktosort), 0));
// replace our stdout with the pipe to uniq
CHECK(dup2(WRITE_END(sorttouniq), 1));
execlp("sort", "sort", NULL);
exit(1);
}
close(READ_END(awktosort));
close(WRITE_END(sorttouniq));
int uniq = fork();
CHECK(uniq);
if (uniq == 0) {
CHECK(dup2(READ_END(sorttouniq), 0));
execlp("uniq", "uniq", "-c", NULL);
exit(1);
}
close(READ_END(sorttouniq));
int wstat;
// just like an actual shell, we only preserve the exit code
// of the last process in the pipeline
waitpid(uniq, &wstat, 0);
if (WIFEXITED(wstat)) {
return WEXITSTATUS(wstat);
}
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment