-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsmallsh.c
336 lines (290 loc) · 9.86 KB
/
smallsh.c
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/*
** smallsh.c
** Morgan Brenner
** CS 344 Program 3
** 03/05/2017
*/
#include <sys/types.h> //pid_t, etc.
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MAX_CHAR 2048
#define MAX_ARGS 512
#define PID_BUFFER_SIZE 50
int foregroundOnly = 0; //for SIGTSTP
void catchSIGINT(int signo);
void catchSIGTSTP(int signo);
int main() {
int shellStatus = 0;
char commandLine[MAX_CHAR];
char *ptr, *inputFile, *outputFile;
int i, j;
int numArgs, inputIndex, outputIndex, pidIndex; //track special characters
int pid, pidLen, lenWithPid; //for replacing '$$' with pid
char *args[MAX_ARGS];
char *filteredArgs[MAX_ARGS]; //holds arguments minus '&'
char pidBuffer[PID_BUFFER_SIZE];
char* argWithPid;
int sourceFD, targetFD, inputResult, outputResult;
char *ioArg[1];
pid_t childPid;
bool isBackgroundProcess;
int numBgProcesses = 0;
int bgProcesses[MAX_ARGS]; //store PIDs of non-completed processes
//initialize sigaction structs, and block actions
struct sigaction SIGINT_action = { 0 }, SIGTSTP_action = { 0 };
//initialize signal handlers
SIGINT_action.sa_handler = catchSIGINT;
sigfillset(&SIGINT_action.sa_mask);
SIGINT_action.sa_flags = 0;
SIGTSTP_action.sa_handler = catchSIGTSTP;
sigfillset(&SIGTSTP_action.sa_mask);
SIGTSTP_action.sa_flags = 0;
//assign behaviors to override default actions
sigaction(SIGINT, &SIGINT_action, NULL);
sigaction(SIGTSTP, &SIGTSTP_action, NULL);
while (1) {
numArgs = 0;
inputIndex = outputIndex = pidIndex = -1;
isBackgroundProcess = false; //foreground process by default
printf(": "); //use ': ' as prompt for each command line
fflush(stdout); fflush(stdin); //flush input & output buffers immediately after each output
fgets(commandLine, MAX_CHAR, stdin);
if (commandLine != NULL)
ptr = strtok(commandLine, " \n");
if (ptr != NULL && ptr[0] != '#') { //if line is not comment
while (ptr != NULL) {
// store index of input/output redirection symbols
if (strcmp(ptr, "<") == 0) {
inputIndex = numArgs;
}
else if (strcmp(ptr, ">") == 0) {
outputIndex = numArgs;
}
// detect '$$' within string and expand $$ to pid of
// shell (should work when adjacent to quotes like in 'echo "PID: $$"')
if (strlen(ptr) > 1) { //string length must be > 1 to contain "$$"
for (i = 1; i < strlen(ptr); i++) {
if ((ptr[i - 1] == '$') && (ptr[i] == '$')) {
pidIndex = i - 1;
break;
}
}
}
if (pidIndex >= 0) {
pid = getpid();
snprintf(pidBuffer, PID_BUFFER_SIZE, "%d", pid);
pidLen = strlen(pidBuffer); //get length of pid as string
lenWithPid = strlen(pidBuffer) + strlen(ptr) - 2; //new length with pid replacing $$
argWithPid = malloc(lenWithPid * sizeof(char));
memset(argWithPid, '\0', lenWithPid);
for (i = 0; i < pidIndex; i++)
argWithPid[i] = ptr[i]; //copy part of string preceding $$
j = 0;
for (i = pidIndex; i <= pidLen; i++)
argWithPid[i] = pidBuffer[j++]; //copy pid as string into arg string
if (pidIndex + pidLen < lenWithPid) { //not end of string
j = pidIndex + 2;
for (i = pidIndex + pidLen; i < lenWithPid; i++)
argWithPid[i] = ptr[j++]; //copy part of string following $$ if applicable
}
ptr = argWithPid; //reassign ptr to updated string
}
args[numArgs] = ptr; //store command/argument in array
ptr = strtok(NULL, " \n");
numArgs++;
} //while ptr != null
args[numArgs] = ptr; //add last argument to args
//Check for built-in commands
if (strcmp(args[0], "exit") == 0) {
//kill remaining processes
if (numBgProcesses > 0) {
for (i = 0; i < numBgProcesses; i++) {
kill(bgProcesses[i], SIGKILL);
printf("Bg process terminated\n");
fflush(stdout);
}
}
exit(0);
}
else if (strcmp(args[0], "cd") == 0) {
if (numArgs == 1 || numArgs == 2)
//change to directory in HOME environment variable
if (numArgs == 1) {
if (chdir(getenv("HOME"))) {
perror("Cannot find HOME environment variable.\n");
}
}
//change to directory specified in argument
else {
if (chdir(args[1])) {
perror("Cannot find directory.\n");
}
}
}
else if (strcmp(args[0], "status") == 0) {
//print exit status or...
if (WIFEXITED(shellStatus)) {
printf("exit value %d\n", WEXITSTATUS(shellStatus));
fflush(stdout);
}
//print terminating signal of last foreground process
else {
printf("terminated by signal %d\n", WTERMSIG(shellStatus));
fflush(stdout);
}
}
//Run other commands with fork(), exec(), and waitpid()
else {
//check for background process - & at end of args
if (strcmp(args[numArgs - 1], "&") == 0) {
isBackgroundProcess = true;
for (i = 0; i < numArgs - 1; i++) {
filteredArgs[i] = args[i];
}
numArgs--;
}
//start child process
pid_t childPid = -5;
childPid = fork();
if (childPid == -1) {
perror("Hull breach!\n");
exit(1);
}
else if (childPid == 0) {
//use dup2 to set up redirection
//Check for input and output files and store names
if (inputIndex >= 0 && inputIndex < numArgs - 1) { //there is a '<' within bounds
inputFile = args[inputIndex + 1];
sourceFD = open(inputFile, O_RDONLY);
if (sourceFD == -1) {
printf("cannot open %s for input\n", inputFile);
fflush(stdout);
shellStatus = 1;
exit(1);
}
else {
inputResult = dup2(sourceFD, 0);
if (inputResult == -1) { perror("dup2()"); shellStatus = 1; }
close(sourceFD);
}
}
if (outputIndex >= 0 && outputIndex < numArgs - 1) { //there is a '>' within bounds
outputFile = args[outputIndex + 1];
targetFD = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (targetFD == -1) { perror("target open()"); shellStatus = 1; }
outputResult = dup2(targetFD, 1);
if (outputResult == -1) { perror("dup2()"); shellStatus = 1; }
close(targetFD);
}
//for bg processes: if no input specified, redirect from dev/null
if (foregroundOnly == 0) {
if (isBackgroundProcess == true && inputFile != NULL) {
inputFile = "/dev/null";
sourceFD = open(inputFile, O_RDONLY);
if (sourceFD == -1) { perror("source open()"); shellStatus = 1; } //error here
inputResult = dup2(sourceFD, 0);
if (inputResult == -1) { perror("dup2()"); shellStatus = 1; } //error here
close(sourceFD);
}
if (isBackgroundProcess == true && outputFile != NULL) {
outputFile = "/dev/null";
targetFD = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (targetFD == -1) { perror("target open()"); shellStatus = 1; }
outputResult = dup2(targetFD, 1);
if (outputResult == -1) { perror("dup2()"); shellStatus = 1; }
close(targetFD);
}
}
//new process to execute command
//execute bg process
if (isBackgroundProcess == true) {
execvp(filteredArgs[0], filteredArgs);
printf("Could not find command\n");
fflush(stdout);
exit(1);
}
//execute command with i/o arguments
if ((inputIndex >= 0) || (outputIndex >= 0)) {
ioArg[0] = args[0]; //only take 1st argument (ignore args after > and <)
execvp(args[0], ioArg);
printf("%s: no such file or directory\n", args[0]);
fflush(stdout);
exit(1);
}
//execute normal command
else {
execvp(args[0], args);
printf("%s: no such file or directory\n", args[0]);
fflush(stdout);
exit(1);
}
} //else if (childPid == 0)
//immediately wait for foreground processes
if (isBackgroundProcess == false || foregroundOnly != 0) {
waitpid(childPid, &shellStatus, 0);
}
//add background processes to queue
else {
printf("background pid is %d\n", childPid);
fflush(stdout);
bgProcesses[numBgProcesses] = childPid;
numBgProcesses++;
//printf("added bg process #%d: %d\n", numBgProcesses, childPid);
fflush(stdout);
}
if (WIFEXITED(shellStatus)) {
WEXITSTATUS(shellStatus); //killed by exit
}
else {
WTERMSIG(shellStatus); //killed by signal
}
} //if other command
} //if not comment
//check for terminating bg processe before next prompt
childPid = waitpid(-1, &shellStatus, WNOHANG); //look for a child
while (childPid > 0) { //a child still exists
//if exit
if (WIFEXITED(shellStatus)) {
printf("background pid %d is done: exit value %d\n", childPid, WEXITSTATUS(shellStatus));
fflush(stdout);
numBgProcesses--;
}
//if signal
else if (WIFSIGNALED(shellStatus)) {
printf("background pid %d is done: terminated by signal %d\n", childPid, WTERMSIG(shellStatus));
fflush(stdout);
numBgProcesses--;
}
childPid = waitpid(-1, &shellStatus, WNOHANG); //look for next child
}
} //main while loop
if (argWithPid != NULL)
free(argWithPid);
return 0;
}
//^C will kill foreground process
void catchSIGINT(int signo) {
//foreground signal terminates self
puts("\nForeground signal terminating.\n"); //cannot use printf in signal handlers
fflush(stdout);
//kill child process
waitpid(signo);
}
//^Z will toggle foreground-only mode
void catchSIGTSTP(int signo) {
if (foregroundOnly == 0) {
puts("\nEntering foreground-only mode (& is now ignored)\n"); //cannot use printf in signal handlers
fflush(stdout);
foregroundOnly = 1;
}
else {
puts("\nExiting foreground-only mode\n"); //cannot use printf in signal handlers
fflush(stdout);
foregroundOnly = 0;
}
}