Reprinted from https://github.com/kamalmarhubi/shell-workshop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// shell.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#include "./utils.c"

ssize_t prompt_and_get_input(const char* prompt,
char **line,
size_t *len) {
fputs(prompt, stderr);
return getline(line, len, stdin);
}

void close_ALL_the_pipes(size_t n_pipes, int (*pipes)[2]) {
for (size_t i = 0; i < n_pipes; ++i) {
close(pipes[i][0]);
close(pipes[i][1]);
}
}

int exec_with_redir(cmd_struct* command, size_t n_pipes, int (*pipes)[2]) {
int fd = -1;
if ((fd = command->redirect[0]) != -1) {
dup2(fd, STDIN_FILENO);
}
if ((fd = command->redirect[1]) != -1) {
dup2(fd, STDOUT_FILENO);
}
close_ALL_the_pipes(n_pipes, pipes);
return execvp(command->progname, command->args);
}

pid_t run_with_redir(cmd_struct* command, size_t n_pipes, int (*pipes)[2]) {
pid_t child_pid = fork();

if (child_pid) { /* We are the parent. */
switch(child_pid) {
case -1:
fprintf(stderr, "Oh dear.\n");
return -1;
default:
return child_pid;
}
} else { // We are the child. */
exec_with_redir(command, n_pipes, pipes);
perror("OH DEAR");
return 0;
}
}

int main() {
char *line = NULL;
size_t len = 0;

while(prompt_and_get_input("r2> ", &line, &len) > 0) {
pipeline_struct* pipeline = parse_pipeline(line);
size_t n_pipes = pipeline->n_cmds - 1;

/* pipes[i] redirects from pipeline->cmds[i] to pipeline->cmds[i+1]. */
int (*pipes)[2] = calloc(sizeof(int[2]), n_pipes);

for (size_t i = 1; i < pipeline->n_cmds; ++i) {
pipe(pipes[i-1]);
pipeline->cmds[i]->redirect[STDIN_FILENO] = pipes[i-1][0];
pipeline->cmds[i-1]->redirect[STDOUT_FILENO] = pipes[i-1][1];
}

for (size_t i = 0; i < pipeline->n_cmds; ++i) {
run_with_redir(pipeline->cmds[i], n_pipes, pipes);
}

close_ALL_the_pipes(n_pipes, pipes);

/* Wait for all the children to terminate. Rule 0: not checking status. */
for (size_t i = 0; i < pipeline->n_cmds; ++i) {
wait(NULL);
}

}
fputs("\n", stderr);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// util.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LEN 1024
#define TOKEN_SEP " \t\n\r"

typedef unsigned int uint;
/**
* Struct to represent a command and its arguments.
*/
typedef struct {
/** The name of the executable. */
char* progname;

/**
* IO redirections; redirect[i] should be used as fd i in the child.
* A value of -1 indicates no redirect.
*/
int redirect[2];

/** The arguments; must be NULL-terminated. */
char* args[];
} cmd_struct;


/**
* Struct to represent a pipeline of commands. The intention is that cmd[i]'s
* output goes to cmd[i+1]'s input.
*/
typedef struct {
/** The total number of commands. */
uint n_cmds;
/** The commands themselves. */
cmd_struct* cmds[];
} pipeline_struct;


/**
* Parses str into a freshly allocated cmd_struct and returns a pointer to it.
* The redirects in the returned cmd_struct will be set to -1, ie no redirect.
*/
cmd_struct* parse_command(char* str);


/**
* Parses str into a freshly allocated pipeline_struct and returns a pointer to
* it. All cmd_structs in cmds will also be freshy allocated, and have their
* redirects set to -1, ie no redirect.
*/
pipeline_struct* parse_pipeline(char* str);


/* For debugging purposes. */
void print_command(cmd_struct* command);
void print_pipeline(pipeline_struct* pipeline);

/*
* For internal use. Returns the next non-empty token according in *line,
* splitting on characters in TOKEN_SEP. Returns NULL if no token remains.
* Updates line to point to remainder after removal of the token. Modifies the
* string *line.
*/
char* next_non_empty(char **line) {
char *tok;

/* Consume empty tokens. */
while ((tok = strsep(line, TOKEN_SEP)) && !*tok);

return tok;
}


cmd_struct* parse_command(char* str) {
/* Copy the input line in case the caller wants it later. */
char* copy = strndup(str, MAX_LEN);
char* token;
int i = 0;

/*
* Being lazy (Rule 0) and allocating way too much memory for the args array.
* Using calloc to ensure it's zero-initialised, which is important because
* execvp expects a NULL-terminated array of arguments.
*/
cmd_struct* ret = calloc(sizeof(cmd_struct) + MAX_LEN * sizeof(char*), 1);

while ((token = next_non_empty(&copy))) {
ret->args[i++] = token;
}
ret->progname = ret->args[0];
ret->redirect[0] = ret->redirect[1] = -1;
return ret;
}


pipeline_struct* parse_pipeline(char *str) {
char* copy = strndup(str, MAX_LEN);
char* cmd_str;
uint n_cmds = 0;
int i = 0;
pipeline_struct* ret;

/*
* Count pipe characters that appear in pipeline to know how much space to
* allocate for the cmds array.
*/
for (char* cur = copy; *cur; cur++) {
if (*cur == '|') ++n_cmds;
}

++n_cmds; /* There is one more command than there are pipe characters. */

ret = calloc(sizeof(pipeline_struct) + n_cmds * sizeof(cmd_struct*), 1);
ret->n_cmds = n_cmds;

while((cmd_str = strsep(&copy, "|"))) {
ret->cmds[i++] = parse_command(cmd_str);
}

return ret;
}


void print_command(cmd_struct* command) {
char** arg = command->args;
int i = 0;

fprintf(stderr, "progname: %s\n", command->progname);

for (i = 0, arg = command->args; *arg; ++arg, ++i) {
fprintf(stderr, " args[%d]: %s\n", i, *arg);
}
}


void print_pipeline(pipeline_struct* pipeline) {
cmd_struct** cmd = pipeline->cmds;

fprintf(stderr, "n_cmds: %d\n", pipeline->n_cmds);

for (uint i = 0; i < pipeline->n_cmds; ++i) {
fprintf(stderr, "cmds[%d]:\n", i);
print_command(cmd[i]);
}
}