Methods
42 (V): pipex, Unix pipes
dup2, fork and file descriptors: recreating how pipes work
2 min read
Every day you use pipes in the terminal without thinking about it: grep hello file | wc -l and done. The 42 pipex project consists of recreating that mechanism from the inside: a program that runs two commands connected by a pipe (a channel that passes the output of one command directly to the input of the next), with input and output redirection to files. Equivalent to < infile cmd1 | cmd2 > outfile, but written from scratch.
The problem
The pipe is very easy to use and a lot less intuitive from the inside. Internally, it's a pair of file descriptors (numbers the system uses as handles for reading and writing): one for reading and one for writing that connect two processes. The real complication is orchestrating everything at once: create the pipe with pipe(), split the program into two processes with fork(), redirect the standard input and output of each child with dup2() so that the output of the first command flows directly into the second, close the descriptors that aren't needed —otherwise the second will never see the end of input— and execute each command with execve(), after finding it by walking the PATH in the environment. And you still have to keep track of errors: nonexistent input file, command that can't be found, missing permissions.
How I approached it
I reused the library from the first deliverable, libft, for the generic parts: splitting commands into arguments and resolving paths. The rest I organized into a struct holding all of the program's state: the input and output file descriptors, the pipe ends, and the resolved paths of both commands. With the state centralized, the process loop came out clean: one child per command, redirections via dup2, closing the descriptors in order, and a parent that waits for both children with waitpid and returns the exit code of the second. The details that cost me the most were closing the descriptors correctly —an unclosed pipe and the program hangs— and reproducing the shell's behavior on errors: code 127 if the command doesn't exist, 126 if it's not executable. The repository, 42_pipex, includes libft as a subfolder and the code split into small modules —initialization, execution, and memory freeing— each in its own file.
What I took away
I came out of the project with a new feeling: the terminal had stopped being a black box. Now I know what happens when I press Enter and chain two commands together: two processes, a pipe, and a few redirections. It also became clear to me that fork and dup2 are the foundation of half the internet: the shell, servers, containers. And a methodology lesson: when a program has many moving parts, centralizing its state in a well-defined struct is the difference between debugging it and getting lost in it. The path of the series followed its order: before this there had been first exam, rank 2; next up was push_swap, sorting with one hand tied.