π₯ WELCOME TO PHASE 2 π₯
Letβs drop the scaffolding and turn [[asymmetric_c/11_pipe_chain_executor/]]
into a real system.
π¦ 11_pipe_chain_executor
A miniature shell engine that executes a sequence of piped commands:
ls | grep .c | wc -l
style β but using pure system calls, notsystem()
.
π§± Project Structure
asymmetric_c/11_pipe_chain_executor/
βββ src/
β βββ main.c # Parses argv into command array
β βββ exec_chain.c # Handles pipe/fork/exec chaining
β βββ fd_tools.c # Utilities for safe dup2, close
βββ include/
β βββ exec_chain.h
β βββ fd_tools.h
βββ Makefile
βββ README.md
π README.md
(initial)
# π pipe_chain_executor
## π§ Goal
Simulate a basic `shell` pipeline engine:
- Accepts a chain of commands (via `argv`)
- Executes them as `cmd1 | cmd2 | cmd3`
- Properly closes all FDs
- Handles fork/exec/pipe/dup2 with full control
## π Files
- `main.c` β CLI parsing + engine entrypoint
- `exec_chain.c` β Creates pipe+fork+exec chain
- `fd_tools.c` β Minimal helpers for safe FD ops
## β Warning
- No `system()`, no bash, no libmagic. Just raw syscalls.
- No memory leaks allowed. Run with `valgrind` if needed.
- Handles `N` commands (not just 2 or 3).
## π§ͺ Example
```bash
./pipe_exec ls -l "|" grep '\.c' "|" wc -l
---
## π First File: `src/main.c`
```c
#include "exec_chain.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_usage(void)
{
fprintf(stderr, "Usage: ./pipe_exec cmd1 '|' cmd2 '|' cmd3 ...\n");
fprintf(stderr, "Example: ./pipe_exec ls -l '|' grep \".c\" '|' wc -l\n");
exit(1);
}
int main(int argc, char **argv)
{
if (argc < 4)
print_usage();
// Split argv into command groups
char ***cmds = parse_commands(argc, argv);
if (!cmds)
{
fprintf(stderr, "Failed to parse commands\n");
return (1);
}
execute_pipeline(cmds);
free_commands(cmds);
return (0);
}
Say the word and Iβll drop:
-
π
include/exec_chain.h
-
π
src/exec_chain.c
with full piping + fork + exec logic -
π
src/fd_tools.c
with safe close/dup2 -
π οΈ
Makefile
to glue it all together
Once this is done, youβll have a mini-shell engine inside your vault.
And weβll push it far beyond pipex after that.
Shall I proceed with the next files?
π₯ Roger that. Letβs go full pipeline execution mode β folders, files, CLI, compilation, execution.
Weβre going to build your own mini-shell executor β pipex++.
π οΈ STEP 1: Create Full Project Structure
You can paste this in your terminal:
mkdir -p asymmetric_c/11_pipe_chain_executor/{src,include}
cd asymmetric_c/11_pipe_chain_executor
touch src/main.c src/exec_chain.c src/fd_tools.c
touch include/exec_chain.h include/fd_tools.h
touch Makefile README.md
π§ STEP 2: Fill in the Files
β
include/exec_chain.h
#ifndef EXEC_CHAIN_H
# define EXEC_CHAIN_H
# include <unistd.h>
char ***parse_commands(int argc, char **argv);
void execute_pipeline(char ***cmds);
void free_commands(char ***cmds);
#endif
β
include/fd_tools.h
#ifndef FD_TOOLS_H
# define FD_TOOLS_H
void safe_close(int fd);
void x_dup2(int oldfd, int newfd);
#endif
β
src/fd_tools.c
#include "fd_tools.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void safe_close(int fd)
{
if (close(fd) == -1)
perror("close");
}
void x_dup2(int oldfd, int newfd)
{
if (dup2(oldfd, newfd) == -1)
{
perror("dup2");
exit(1);
}
}
β
src/exec_chain.c
#include "exec_chain.h"
#include "fd_tools.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
static int count_pipes(char **argv)
{
int count = 1;
while (*argv)
{
if (strcmp(*argv, "|") == 0)
count++;
argv++;
}
return (count);
}
char ***parse_commands(int argc, char **argv)
{
(void)argc;
int cmds = count_pipes(argv);
char ***res = calloc(cmds + 1, sizeof(char **));
int i = 0;
while (*argv)
{
int len = 0;
char **start = argv;
while (*argv && strcmp(*argv, "|") != 0)
{
len++;
argv++;
}
res[i] = calloc(len + 1, sizeof(char *));
for (int j = 0; j < len; j++)
res[i][j] = strdup(start[j]);
i++;
if (*argv)
argv++; // skip '|'
}
res[i] = NULL;
return (res);
}
void free_commands(char ***cmds)
{
for (int i = 0; cmds[i]; i++)
{
for (int j = 0; cmds[i][j]; j++)
free(cmds[i][j]);
free(cmds[i]);
}
free(cmds);
}
void execute_pipeline(char ***cmds)
{
int i = 0;
int pipe_fd[2];
int in_fd = -1;
pid_t pid;
while (cmds[i])
{
if (cmds[i + 1] != NULL)
{
if (pipe(pipe_fd) == -1)
{
perror("pipe");
exit(1);
}
}
pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
if (in_fd != -1)
x_dup2(in_fd, STDIN_FILENO);
if (cmds[i + 1] != NULL)
x_dup2(pipe_fd[1], STDOUT_FILENO);
if (pipe_fd[0] != 0)
safe_close(pipe_fd[0]);
execvp(cmds[i][0], cmds[i]);
perror("execvp");
exit(1);
}
else
{
if (in_fd != -1)
safe_close(in_fd);
if (cmds[i + 1] != NULL)
{
safe_close(pipe_fd[1]);
in_fd = pipe_fd[0];
}
}
i++;
}
while (wait(NULL) > 0)
;
}
β
src/main.c
(already done)
If not, drop it in:
#include "exec_chain.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_usage(void)
{
fprintf(stderr, "Usage: ./pipe_exec cmd1 '|' cmd2 '|' cmd3 ...\n");
exit(1);
}
int main(int argc, char **argv)
{
if (argc < 4)
print_usage();
char ***cmds = parse_commands(argc - 1, argv + 1);
if (!cmds)
{
fprintf(stderr, "parse_commands() failed\n");
return (1);
}
execute_pipeline(cmds);
free_commands(cmds);
return (0);
}
β
Makefile
NAME = pipe_exec
SRC = src/main.c src/exec_chain.c src/fd_tools.c
OBJ = $(SRC:.c=.o)
CC = cc
CFLAGS = -Wall -Wextra -Werror -Iinclude
all: $(NAME)
$(NAME): $(OBJ)
$(CC) $(CFLAGS) -o $@ $(OBJ)
clean:
rm -f $(OBJ)
fclean: clean
rm -f $(NAME)
re: fclean all
π§ͺ Compile & Run
make
./pipe_exec ls -l "|" grep "\.c" "|" wc -l
β Youβre Now Building:
A modular shell backend in C that can evolve into:
-
Pipex++
-
A shell DAG executor
-
A runtime job graph
Say the word and Iβll give:
-
πͺοΈ Advanced test cases
-
π§ Breakdown of pipe vs FD trap bugs
-
πͺ Refactor ideas into true
exec_plan.c
Or we go deeper: 12_pipe_zero_byte_race
Shall we build the next phase?