-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.c
634 lines (526 loc) · 22.2 KB
/
main.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
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
/*******************************************************************
* Title: smallsh *
* Author: Matthew DeMichele *
* Description: smallsh is an implementation of a basic shell *
* written in C. *
* Date: 1 November 2021 *
*******************************************************************/
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
/********************************************************************
* Global Variables
*********************************************************************/
int allowBackground = 1;
/*******************************************************************
* Command Structure: Struct definition
********************************************************************/
struct command {
char *commandName;
char *arguments[512];
int numArgs;
char *inputFile;
char *outputFile;
int backgroundFlag;
};
/******************************************************************
* Running Processes: Struct definition to keep track of currently
* running processes
******************************************************************/
struct processes {
pid_t currentProcesses[512];
int numProcesses;
};
/******************************************************************
* Prototypes
*******************************************************************/
void handle_parent_SIGTSTP(int signo);
int check_for_skips(char *input);
void change_directories(struct command enteredCommand);
void display_status(int exitStatus);
struct command convert_input(char *input);
int execute_command(struct command enteredCommand, struct sigaction foreground_child_SIGINT, struct sigaction child_SIGTSTP, struct processes*);
void prompt_loop();
/******************************************************************
* Main Function
*******************************************************************/
int main(int argc, char* argv[]) {
// This is the main loop that runs the shell
prompt_loop();
return 0;
}
/******************************************************************
* Prompt Loop: Gets input from user
*******************************************************************/
void prompt_loop() {
// Initialize runningProcesses structure to keep track of currently running background processes
struct processes *runningProcesses = malloc(sizeof(struct processes));
// Set initial values of running processes structure
memset(runningProcesses->currentProcesses, '\0', 512);
runningProcesses->numProcesses = 0;
// Declare buffer variable to hold user input
size_t bufferSize = 2050; // Maximum command line length is 2048 characters
char* buffer;
buffer = (char *)malloc(bufferSize * sizeof(char)); // Allocate 2048 characters worth of memory space
/* Handle memory allocation error */
if (buffer == NULL) {
perror("Unable to allocate line buffer\n");
exit(0);
}
/***************************************************************************************************
* SIGINT Signal 1 (^C): Used by Foreground Child Processes
* 1. The shell, i.e. the parent process, must ignore SIGINT
* 2. Any children running as background processes must ignore SIGINT
* 3. A child running as a foreground process must terminate itself when it receives SIGINT
* 3a. Parent must not attempt to terminate the foreground child process; instead,
* the foreground child (if any) must terminate itself on receipt of this signal
* 3b. If a child foreground process is killed by a signal, the parent must immediately
* print out the number of the signal that killed its foreground child process before
* prompting the user for the next command.
***************************************************************************************************/
struct sigaction foreground_child_SIGINT = {{0}}; // Initialize foreground_child_SIGINT struct to be empty
foreground_child_SIGINT.sa_handler = SIG_DFL; // Register SIG_DFL as the signal handler. SIG_DFL allows default behavior for SIGINT signals
sigfillset(&foreground_child_SIGINT.sa_mask); // Block all catchable signals while sa_handler is running
foreground_child_SIGINT.sa_flags = SA_RESTART; // Set SA_RESTART flag
/**************************************************************************************************
* SIGTINT Signal 2 (^C): Used by Parent and Background Processes
**************************************************************************************************/
struct sigaction parent_SIGINT = {{0}}; // Initialize parent_SIGINT structure to be empty
parent_SIGINT.sa_handler = SIG_IGN; // Register SIG_IGN as signal handler. SIG_IGN ignores SIGINT signals
sigaction(SIGINT, &parent_SIGINT, NULL); // Will be installed for parent and background child processes. Ignores SIGINT signals
/****************************************************************************************************
* SIGTSTP Signal Handler (^Z) 1: Used By Parent Processes
* 1. A child, if any, running as either a foreground or background process must ignore SIGSTSTP
* 2. When the parent process running the shell received SIGTSTP, program does the following:
* 2a. Shell must display an informative message immediately if it's sitting at the prompt, or
* immeditately after any currently running foreground process has terminated.
* 2b. The shell then enters a state where subsequent commands can no longer be run in the background
* 2c. In this state, the & operater should simply be ignored, i.e. all such commands are run as if
* they were foreground processes
*****************************************************************************************************/
struct sigaction parent_SIGTSTP = {{0}}; // Declare empty sigaction struct
parent_SIGTSTP.sa_handler = handle_parent_SIGTSTP; // Register handle_parent_SIGTSTP as the signal handler for parent processes
sigfillset(&parent_SIGTSTP.sa_mask); // Set sa_mask: block certain signals
parent_SIGTSTP.sa_flags = SA_RESTART; // Set SA_RESTART flag
sigaction(SIGTSTP, &parent_SIGTSTP, NULL); // Set the sigaction function to use the handle_parent_SIGTSTP structure we just initialized
/**************************************************************
* SIGTSTP Signal Handler (^Z) 2: Used by Child Processes
* Description: This handler just ignores ^Z signals
***************************************************************/
struct sigaction child_SIGTSTP = {{0}}; // Initialize empty sigaction struct
child_SIGTSTP.sa_handler = SIG_IGN; // Ignores ^Z signals
/**************************************************************
* Useful Variables:
* 1. continueLoop: used to run the main loop.
* 2. exitStatus: used to print out the status
***************************************************************/
int continueLoop = 1;
int terminationStatus = -5; // -5 means that no foreground process has run yet
/**************************************************************
* Main Loop: Continues as long as continueLoop variable
* is set to 1
***************************************************************/
while (continueLoop) {
// Display prompt to user
printf(": ");
fflush(stdout);
// Accept input from user
getline(&buffer, &bufferSize, stdin);
// Handle blank lines and comments
if (check_for_skips(buffer) == 1) {
// Do Nothing
} else {
// Break line into a command struct
// Will reset the newCommand structure every time through
struct command newCommand = convert_input(buffer);
// Check if command is "exit"
if (strcmp(newCommand.commandName, "exit") == 0) {
// Loop through the runningProcesses numProcesses array and kill any running processes
for (int i = 0; i < runningProcesses->numProcesses; i++) {
kill(runningProcesses->currentProcesses[runningProcesses->numProcesses], SIGTERM);
}
// Exit Loop
continueLoop = 0;
}
// Check if command is "cd"
else if (strcmp(newCommand.commandName, "cd") == 0) {
change_directories(newCommand);
}
// Check if command is "status"
else if (strcmp(newCommand.commandName, "status") == 0) {
display_status(terminationStatus);
}
// If not built-in, execute with command struct with "execute_command"
else {
terminationStatus = execute_command(newCommand, foreground_child_SIGINT, child_SIGTSTP, runningProcesses);
}
}
}
}
/******************************************************************
* Handle SIGTSTP signal
*******************************************************************/
void handle_parent_SIGTSTP(int signo) {
// If background processes are currently allowed...
if (allowBackground == 1) {
// Display message about foreground-only mode
char* display = "Entering foreground-only mode (& is now ignored)\n";
write(1, display, 49);
fflush(stdout);
// reset allowBackground flag
allowBackground = 0;
}
// If background processes are NOT currently allowed...
else {
// Display message about leaving foreground-only mode
char* display = "Exiting foreground-only mode\n";
write(1, display, 30);
fflush(stdout);
// set allowBackground flag to 1
allowBackground = 1;
}
}
/******************************************************************
* Check For Skips: check for situations that should be skipped.
* 1. Blank lines
* 2. comment lines
*******************************************************************/
int check_for_skips(char *input) {
// Use these two variables to check for blank lines and comments
char blankMarker = 10;
char commentMarker = '#';
// Get the first character of the input string
char firstCharacter = input[0];
// This line check for 1.) Whole line blank 2.) first character equals #
if ((*input == blankMarker) || (firstCharacter == commentMarker)) {
return 1;
} else {
return 0;
}
}
/*******************************************************************************
* Change Directories: Executes the cd command
* Rule 1. If no arguments, change to the directory specified in HOME
* environment variable.
*
* INPUT:
struct command enteredCommand The path of the directory to change to
*
********************************************************************************/
void change_directories(struct command enteredCommand) {
// If no arguments specified, change to home directory
if (enteredCommand.arguments[1] == NULL) {
chdir(getenv("HOME"));
}
// If path specified, change to path
else {
int cdResult = chdir(enteredCommand.arguments[1]);
// Send error message if cd doesn't work
if (cdResult == -1) {
printf("No such directory exists.\n");
fflush(stdout);
}
}
}
/******************************************************************************
* Display Status: Executes the status command
*******************************************************************************/
void display_status(int terminationStatus) {
// Initialize buffer for status output
int messageLength = 0;
char statusMessage[100];
memset(statusMessage, '\0', 100);
// If exitStatus == -5, then no foreground process has run yet
if (terminationStatus == -5) {
write(1, "exit value 0\n", 13);
fflush(stdout);
}
// If last process was terminated normally, then print exit status
else if (WIFEXITED(terminationStatus)) {
sprintf(statusMessage, "exit status %d\n", WEXITSTATUS(terminationStatus));
messageLength = strlen(statusMessage);
write(1, statusMessage, messageLength);
fflush(stdout);
}
// If last process was terminated because of a signal, print signal
else if (WIFSIGNALED(terminationStatus)) {
sprintf(statusMessage, "terminated by signal %d\n", WTERMSIG(terminationStatus));
messageLength = strlen(statusMessage);
write(1, statusMessage, messageLength);
fflush(stdout);
}
else {
}
}
/********************************************************************************
* Convert Input: Converts the input entered by the user into a command structure
*********************************************************************************/
struct command convert_input(char *input) {
// Create a new struct variable
struct command currCommand;
// Fill arguments array with Null values. Will erase any arguments preivously used if any
for (int i = 0; i < 512; i++) {
currCommand.arguments[i] = NULL;
}
// Reset inputFile and outputFile
currCommand.inputFile = (char*)NULL;
currCommand.outputFile = (char*)NULL;
currCommand.backgroundFlag = 0;
currCommand.numArgs = 0;
// Will use numArgs to keep track of current index of arguments array
int numArgs = 0;
// Get the pid and convert to string for use in expanding variable $$
pid_t currpid = getpid();
char *pidstring = malloc(13);
snprintf(pidstring, 13, "%d", currpid);
// Use convertedString in variable $$ expansion
char *convertedString;
int converted = 0;
// Declare variables to use with strtok_r and parsing the input string
char *saveptr = input;
char *readString;
char *token;
int iterator;
// Use these flags for determining how to parse the string
int setCommand = 0;
int setArgs = 0;
int setInput = 0;
int setOutput = 0;
int setBackground = 0;
// Loop through input and parse the input string
for (iterator = 1, readString = input; ; iterator++, readString = NULL) {
// Get token from readString, using space as the delimiter
token = strtok_r(readString, " \n\t\r\a", &saveptr);
// CHECK 01. Break out of for loop if you reach the end of the line
if (token == NULL) {
break;
}
// CHECK 02. Set background flag if token is &
if (*token == 38) {
setBackground = 1;
setOutput = 1;
setInput = 1;
setArgs = 1;
}
// CHECK 03. Set output flag if token is >
if (*token == 62) {
setOutput = 1;
setInput = 1;
setArgs = 1;
}
// CHECK 04. Set input flag if token is <
if (*token == 60) {
setInput = 1;
setArgs = 1;
}
// SET 01. Set currCommand background flag if setBackground flag is set
if ( (setBackground == 1) && (setOutput == 1) && (setInput == 1)) {
currCommand.backgroundFlag = 1;
}
// SET 02. Set currCommand output file if setOutput flag is set
if ( (setOutput == 1) && (setInput == 1) && (setBackground == 0) && (*token != 62) ) {
currCommand.outputFile = calloc(strlen(token) + 1, sizeof(char));
strcpy(currCommand.outputFile, token);
}
// SET 03. Set currCommand input file if setInput flag is set
if ( (setInput == 1) && (setOutput == 0) && (setBackground == 0) && (*token != 60) ) {
currCommand.inputFile = calloc(strlen(token) + 1, sizeof(char));
strcpy(currCommand.inputFile, token);
}
// SET 04. Set args of currCommand if setBackground, setOutput, and setArgs flag NOT set
if ( (setBackground != 1) && (setOutput != 1) && (setArgs != 1)) {
// Check the token string for any $$ variables
for (int i = 0; i < strlen(token) - 1; i++) {
if ( token[i] == '$' && token[i + 1] == '$' ) {
// Declare a variable to hold what comes before the $$ variable
convertedString = malloc(strlen(token) + 14);
// copy the first part of token into firstHalf
strncpy(convertedString, token, i);
// concat the pid and firstHalf string together
strcat(convertedString, pidstring);
// Set converted flag
converted = 1;
}
}
// Use convertedString if a variable was found, use regular token if not
if (converted == 1) {
currCommand.arguments[numArgs] = strdup(convertedString);
} else {
currCommand.arguments[numArgs] = strdup(token);
}
numArgs += 1;
}
// SET 05. Set commandName of currCommand if no flags set yet
if (setCommand == 0) {
currCommand.commandName = calloc(strlen(token) + 1, sizeof(char));
// Check the token string for any $$ variables
for (int i = 0; i < strlen(token) - 1; i++) {
if ( token[i] == '$' && token[i + 1] == '$' ) {
// Declare a variable to hold what comes before the $$ variable
convertedString = malloc(strlen(token) + 14);
// copy the first part of token into firstHalf
strncpy(convertedString, token, i);
// concat the pid and firstHalf string together
strcat(convertedString, pidstring);
// Set converted flag
converted = 1;
}
}
// Use convertedString if a variable was found, use regular token if not
if (converted == 1) {
strcpy(currCommand.commandName, convertedString);
} else {
strcpy(currCommand.commandName, token);
}
setCommand = 1;
}
}
// Set the total number of arguments
currCommand.numArgs = numArgs;
// Return newly created struct pointer
return currCommand;
}
/**********************************************************************************
* Execute Command: Executes any command not built-in to the shell
***********************************************************************************/
int execute_command(struct command enteredCommand, struct sigaction foreground_child_SIGINT, struct sigaction child_SIGTSTP, struct processes* runningProcesses) {
/*********** Initialize necessary variables ********************/
pid_t childPid = -5; // Used to keep track of current childPid
int input = 0; // Used for redirecting input
int inputResult = 0; // Also used for redirecting input
int output = 0; // Used for redirecting output
int outputResult; // Also used for redirecting output
int childExitStatus = 0; // Used for printing the child exit status
int lastForegroundStatus = 0; // Used for storing the last foregound child exit status
char *signalMessage = "terminated by signal "; // Used to display termination signal message
char statusMessage[100]; // Used to display exit status message
int statusLength = 0;
// fork the running process
childPid = fork();
// CHILD PROCESS
if (childPid == 0) {
// Install child_SIGTSTP signal handler, so that all child processes ignore ^Z signals
sigaction(SIGTSTP, &child_SIGTSTP, NULL);
// Child processes running in foreground terminate themselves when they receive SIGINT
// Child processes running as a background command continue to ignore SIGINT
if (enteredCommand.backgroundFlag == 0) {
sigaction(SIGINT, &foreground_child_SIGINT, NULL);
}
// Handle input file redirection if any
if (enteredCommand.inputFile != NULL) {
// Open input file for read only
input = open(enteredCommand.inputFile, O_RDONLY);
// If shell can't open file for reading, print error message and set exit status to 1
if (input == -1) {
perror("Smallsh can't open file\n");
exit(1);
}
// Redirect stdin to input file
inputResult = dup2(input, 0);
// If shell can't assign input, print error message and set exit status to 1
if (inputResult == -1) {
perror("Smallsh can't redirect input to input file\n");
exit(1);
}
}
// Handle output file redirection if any
if (enteredCommand.outputFile != NULL) {
// Open output file for writing only. Truncate if it already exists. Create if it doesn't exist.
output = open(enteredCommand.outputFile, O_WRONLY | O_TRUNC | O_CREAT, 0666);
// If shell can't open file for writing, print error message and set exit status to 1
if (output == -1) {
perror("Smallsh can't open file.\n");
exit(1);
}
// Rediect stdout to output file
outputResult = dup2(output, 1);
// If shell can't assign output, print error message and set exit status to 1
if (outputResult == -1) {
perror("Smallsh can't redirect output to output file.\n");
exit(1);
}
}
// If no input specified for a background command, handle input redirect
if ( (enteredCommand.backgroundFlag == 1) && (allowBackground == 1) && (enteredCommand.inputFile == NULL) ) {
// Open /dev/null for writing only
input = open("/dev/null", O_RDONLY);
// If shell can't open file for reading, print error message and set exit status to 1
if (input == -1) {
perror("Smallsh can't open file\n");
exit(1);
}
// Redirect stdin to input file
inputResult = dup2(input, 0);
// Close input file?
}
// If not output specified for a background command, handle output redirect
if ( (enteredCommand.backgroundFlag == 1) && (allowBackground == 1) && (enteredCommand.outputFile == NULL) ) {
// Open dev/null for writing only. Truncate if it exists. Create if it doesn't exist.
output = open("/dev/null", O_WRONLY | O_TRUNC | O_CREAT, 0666);
// If shell can't open file for writing, print error
if (output == -1) {
perror("Smallsh can't open file.\n");
exit(1);
}
// Redirect stdout
outputResult = dup2(output, 1);
// If shell can't assign output, print error message and set exit status to1
if (outputResult == -1) {
perror("Smallsh can't redirect output to output file.\n");
exit(1);
}
}
// Execute the Child process
if (execvp(enteredCommand.arguments[0], (char* const*)enteredCommand.arguments) == -1) {
perror("There was an error");
exit(1);
}
exit(0);
}
// ERROR HANDLING
else if (childPid < 0) {
// Error forking
perror("Error forking");
exit(1);
}
// PARENT PROCESS
else {
// Execute process in the background if backgroundFlag is set and background mode is enabled
if ( (enteredCommand.backgroundFlag == 1) && (allowBackground == 1) ) {
waitpid(childPid, &childExitStatus, WNOHANG);
printf("background pid is: %d\n", childPid);
fflush(stdout);
// add the running background process to the running processes structure
runningProcesses->currentProcesses[runningProcesses->numProcesses] = childPid;
runningProcesses->numProcesses += 1;
}
// Execute process in the foreground if backgroundFlag not set
else {
childPid = waitpid(childPid, &childExitStatus, 0);
// Check for any foreground child process that terminate due to a SIGINT signal
if (WIFSIGNALED(childExitStatus)) {
write(1, signalMessage, 24);
fflush(stdout);
}
// Store the exit status of most current child process
lastForegroundStatus = childExitStatus;
}
// Check for any background child process that finish
while ( (childPid = waitpid(-1, &childExitStatus, WNOHANG)) > 0) {
// Write output into a string, and then write to stdout
sprintf(statusMessage, "background pid %d is done: ", childPid);
statusLength = strlen(statusMessage);
write(1, statusMessage, statusLength);
fflush(stdout);
// Display the exit status
display_status(childExitStatus);
}
}
return lastForegroundStatus;
}