-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathminiShell.c
466 lines (363 loc) · 13.8 KB
/
miniShell.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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<signal.h>
#define MAX_INPUT 2049
#define MAX_ARG 512
#define MAX_THREADS 100
//Global that controls if background processes are allowed
volatile sig_atomic_t suppressBck = 0;
pid_t forkFunc(char *arg[MAX_ARG], char *input, char *output, int background, int *childExitMethod);
void expandString(char *str, int strLen);
void parseCommand(char *userInput, char *arg[MAX_ARG], char **input, char **output, int *background);
void suppressBackground(int sig);
int main(int argc, char *argv[]) {
//Creating the favorite iterator
int i;
//Child tracker variables
pid_t childPid;
int childCount = 0;
int fgExitMethod = -10;
int childExitMethod = -10;
int exitStatus;
//Generate array to store child process id's, using -10 as a null indicator
pid_t *childs = (pid_t*)malloc(sizeof(pid_t) * MAX_THREADS);
for (i = 0; i < MAX_THREADS; i++) {
childs[i] = -10;
}
//Sets parent shell to ignore Ctrl+C commands
struct sigaction ign = {0};
ign.sa_handler = SIG_IGN;
sigaction(SIGINT, &ign, NULL);
//Sets parent shell to toggle suppression of background on Ctrl+Z
struct sigaction supBck = {0};
supBck.sa_handler = suppressBackground;
sigaction(SIGTSTP, &supBck, NULL);
//Get memory block for input
char *userInput;
userInput = (char*)malloc(sizeof(char) * MAX_INPUT);
//Storage for parsed input
char *input;
char *output;
char *arg[MAX_ARG];
int background;
arg[0] = "";
//Main shell loop which ends on exit command
while (strcmp(arg[0], "exit") != 0) {
//Take in user input
printf(": ");
fflush(stdout);
fgets(userInput, MAX_INPUT, stdin);
//Clears new line from input
if (userInput[strlen(userInput) - 1] == '\n') {
userInput[strlen(userInput) - 1] = '\0';
}
//Replaces $$ with shell process id
expandString(userInput, MAX_INPUT);
//Parse input
parseCommand(userInput, arg, &input, &output, &background);
//If the suppress background setting is active then treat as
//always not run on background
if (suppressBck != 0) {
background = 0;
}
//Check over built in commands
if (strcmp(arg[0], "cd") == 0) {
//If path is empty cd to HOME otherwise go to location
if (arg[1] == NULL) {
chdir(getenv("HOME"));
} else {
chdir(arg[1]);
}
} else if (strcmp(arg[0], "status") == 0) {
//Checks if a foreground process has been ran
if (fgExitMethod == -10) {
printf("exit status 0\n");
fflush(stdout);
} else {
//Prints the appropriate message dependent on if
//it was a exit status or signal
if (WIFEXITED(fgExitMethod)) {
exitStatus = WEXITSTATUS(fgExitMethod);
printf("exit status %d\n", exitStatus);
fflush(stdout);
}
if (WIFSIGNALED(fgExitMethod)) {
exitStatus = WTERMSIG(fgExitMethod);
printf("terminated by signal %d\n", exitStatus);
fflush(stdout);
}
}
} else if (strcmp(arg[0], "exit") != 0 && arg[0] != "" && userInput[0] != '#') {
//Run command so long as there are not too many background processes being run
if (childCount < MAX_THREADS) {
//Fork child to run command
childPid = forkFunc(arg, input, output, background, &fgExitMethod);
//If the child was successfully created and ran in the background
if (childPid > 0 && background != 0) {
//Store the child pid in the child array
i = 0;
while (childs[i] != -10) {
i++;
}
childs[i] = childPid;
childCount++;
}
} else {
//Error if too many things in background
printf("Too many children, wait for processes to end\n");
fflush(stdout);
}
} else {
printf("\n");
fflush(stdout);
}
//Check the background children for zombies
for (i = 0; i < MAX_THREADS; i++) {
//If the array is storing a process number then check on it
if (childs[i] != -10) {
//Check individual child status
childPid = waitpid(childs[i], &childExitMethod, WNOHANG);
//If the child has already terminated then display exit status/term signal, otherwise ignore
if (childPid != 0) {
//Prints the appropriate message dependent on if
//it was a exit status or signal
if (WIFEXITED(childExitMethod)) {
exitStatus = WEXITSTATUS(childExitMethod);
printf("Process %d has ended, exit status %d\n", childPid, exitStatus);
fflush(stdout);
}
if (WIFSIGNALED(childExitMethod)) {
exitStatus = WTERMSIG(childExitMethod);
printf("Process %d has ended, terminated by signal %d\n", childPid, exitStatus);
fflush(stdout);
}
//Remove the process from the child array
childs[i] = -10;
childCount--;
}
}
}
}
//Exit all background processes
for (i = 0; i < MAX_THREADS; i++) {
if (childs[i] != -10) {
kill(childs[i], SIGINT);
}
}
//Clear memory
free(childs);
free(userInput);
return 0;
}
pid_t forkFunc(char *arg[MAX_ARG], char *input, char *output, int background, int *childExitMethod) {
//This function forks off a process that runs the passed in execution along with any
//redirection wanted or if it should run in the background.
pid_t childPid = -10;
//Forks off a process that runs the execution
childPid = fork();
switch (childPid)
{
//Failed Fork
case -1:
//If the fork fails then a error sent to errout
perror("Fork Failure");
fflush(stderr);
exit(1);
break;
//Child Process
case 0:
//If a input is put in, then the input for the exectued process is redirected
//if the process is run in the background it will also make input from null if no input is provided
if (input != NULL || background != 0) {
//If a background process and no input given, defaults to null
if (input == NULL) {
input = "/dev/null";
}
//Opens the input and reports a error if it fails
int inputFile = open(input, O_RDONLY);
if (inputFile < 0) {
printf("Failure to open input file\n");
fflush(stdout);
exit(1);
}
//Redirects stdin to the input file and reports a error if it fails
if (dup2(inputFile, 0) < 0) {
printf("Failed to dup input\n");
fflush(stdout);
exit(1);
}
//Closes input file
close(inputFile);
}
//If a output is put in, then the output for the exectued process is redirected
//if the process is run in the background it will also make output from null if no output is provided
if (output != NULL || background != 0) {
//If a background process and no output given, defaults to null
if (output == NULL) {
output = "/dev/null";
}
//Opens the output and reports a error if it fails
int outputFile = open(output, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outputFile < 0) {
printf("Failure to open output file\n");
fflush(stdout);
exit(1);
}
//Redirects stdout to the output file and reports a error if it fails
if (dup2(outputFile, 1) < 0) {
printf("Failed to dup output\n");
fflush(stdout);
exit(1);
}
//Closes output file
close(outputFile);
}
//If this is not a background process then re-enable the ability to give a interrupt signal
if (background == 0) {
struct sigaction dft = {0};
dft.sa_handler = SIG_DFL;
sigaction(SIGINT, &dft, NULL);
}
//Set up the child to ignore the stop signal
struct sigaction stp = {0};
stp.sa_handler = SIG_IGN;
sigaction(SIGTSTP, &stp, NULL);
//Runs the execution and returns a error if it fails
execvp(arg[0], arg);
perror("Exec Failure!\n");
fflush(stderr);
exit(1);
break;
//Parent Process
default:
//If not a background process then wait for the process
if (background == 0) {
//wait for the process to finish
childPid = waitpid(childPid, childExitMethod, 0);
//Prints out the signal that killed the foreground process if it was
//singal terminated
if (WIFSIGNALED(*childExitMethod)) {
int exitStatus = WTERMSIG(*childExitMethod);
printf("terminated by signal %d\n", exitStatus);
fflush(stdout);
}
} else {
//Prints what background process id was started
printf("Background process %d started\n", childPid);
fflush(stdout);
}
break;
}
return childPid;
}
void expandString(char *str, int strLen) {
//This function takes in a string and expands every instance of $$ to the process pid.
//Gets the current process pid and determines its string length
int pid = getpid();
int pidLen = snprintf(NULL, 0, "%d", pid);
//Creates a temp string that stores the pid number as characters
char* pidStr = malloc(pidLen + 1);
snprintf(pidStr, pidLen + 1, "%d", pid);
//Temp string that can be be copied from
char* tempStr = (char*)malloc(sizeof(char) * MAX_INPUT);
strcpy(tempStr, str);
//Iterates over the string replacing $$ with the ID
int i = 0, j = 0, k = 0;
while (tempStr[i] != '\0') {
//If this is the start of a $$ then replace both with the ID
if (tempStr[i] == '$' && tempStr[i+1] == '$') {
while (pidStr[k] != '\0') {
str[j] = pidStr[k];
j++;
k++;
}
k = 0;
i += 2;
} else {
//Write the current character if not a $$
str[j] = tempStr[i];
i++;
j++;
}
}
str[j] = '\0';
//Free the temp string memory
free(pidStr);
free(tempStr);
}
void parseCommand(char *userInput, char *arg[MAX_ARG], char **input, char **output, int *background) {
//This function takes in a string and parses it for commands arguments, redirection and a background flag
char *str = NULL;
char *strNext = NULL;
//Reset the values from last input
*input = NULL;
*output = NULL;
*background = 0;
int i;
for (i = 0; i < MAX_ARG; i++) {
arg[i] = NULL;
}
//If the input was blank then return a blank command in the arg list
if (*userInput == '\0') {
arg[0] = "";
return;
}
//Takes in the first word (the command) and the following word
str = strtok(userInput, " ");
strNext = strtok(NULL, " ");
//Iterate over the words in the user input string
int argNum = 0;
while (str != NULL) {
//If a < then take the next word as the input location
if (strcmp(str, "<") == 0) {
*input = strNext;
str = strtok(NULL, " ");
//Sets the correct next word if one exists
if (str != NULL) {
strNext = strtok(NULL, " ");
} else {
strNext = NULL;
}
//If a > then take the next word as the output location
} else if (strcmp(str, ">") == 0) {
*output = strNext;
str = strtok(NULL, " ");
//Sets the correct next word if one exists
if (str != NULL) {
strNext = strtok(NULL, " ");
} else {
strNext = NULL;
}
//If the last word is a & set the background flag
} else if (strcmp(str, "&") == 0 && strNext == NULL) {
*background = 1;
str = strNext;
//All other words are stored as commands
} else {
arg[argNum] = str;
argNum++;
str = strNext;
strNext = strtok(NULL, " ");
}
}
}
void suppressBackground(int sig) {
//This function takes in a signal and flips a suppress flag that controls if
//the background process command works or not
//Flip the suppress flag and print the new status
if (suppressBck == 1) {
char *message = "Exiting foreground-only mode\n";
write(STDOUT_FILENO, message, 30);
fflush(stdout);
suppressBck = 0;
} else {
char *message = "Entering foreground-only mode (& is now ignored)\n";
write(STDOUT_FILENO, message, 50);
fflush(stdout);
suppressBck = 1;
}
}