-
Notifications
You must be signed in to change notification settings - Fork 0
/
shell.c
464 lines (381 loc) · 11.4 KB
/
shell.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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define TRUE 1
#define FALSE 0
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#define MAX_INPUT_LENGTH 512 //Max input 512 bytes
#define INTERACTIVE_MODE 0
#define BATCH_MODE 1
/* Functions */
/* function displayError
* Writes the generic error message to stderr
*
*/
void displayError() {
// Display Error
char error_message[30] = "An error has occurred\n";
write(STDERR_FILENO, error_message, strlen(error_message));
}
/* function dumpline
* This function reads and dumps any remaining characters on the current input
* line of a file. fp is a pointer to the FILE to read characters from.
*/
void dumpline(FILE * fp) {
int c;
while ((c = fgetc(fp)) != '\n' && c != EOF)
;
}
/* function tokenize
*
* Converts an inputString into an array of tokens separated by whitespace
* Returns the number of tokens in the inputString
*/
int tokenize(char inputString[MAX_INPUT_LENGTH + 2],
char* tokens[MAX_INPUT_LENGTH / 2]) {
int ntokens = 0;
// tokens = NULL;
char* tok = NULL;
tok = strtok(inputString, " ");
do {
tokens[ntokens] = strdup(tok); // Copy each token into array
ntokens++; // increment number of tokens
// Tokenize inputBuffer and store into arg array
tok = strtok(NULL, " "); // Get next token
} while (tok != NULL );
// add null terminating argument to argArray
tokens[ntokens] = tok;
// return number of arguments found
return (ntokens);
}
char* prepRedirInput(char* redirStr) {
// Pointer to second half of string
char* redir_output = NULL;
redir_output = redirStr + 1;
// size_t maxOutputSize = strlen(redir_output) + 1;
// Truncate the command line section to r/m redir section
redirStr[0] = '\0';
// redirStr = &redir_output;
// redirStr++;
return (strdup(redir_output));
}
/* function checkRedir
*
* Detects if the command in the input buffer needs to be redirected.
* Sets redir_mode accordingly
*
*returns 0 for when redir is Not found
*returns 1 when redir is found, and correctly formatted
*returns -1 when redir is found, but incorrectly formatted
*/
int checkRedir(char inputBuffer[MAX_INPUT_LENGTH + 2], int* redir_mode,
char* outFile) {
outFile = NULL;
// check '>' exists in inputStream
char* redirStr = NULL;
redirStr = strchr(inputBuffer, '>');
if (redirStr) {
char* outputFileName;
// Pointer to second half of string
outputFileName = prepRedirInput(redirStr);
//outputFileName = strdup(redirStr);
// Token array to parse redir output into
char* oTokens[strlen(outputFileName)];
// validate that the output section is valid
// by tokenizing output and counting # of tokens
size_t numTokens = tokenize(outputFileName, oTokens);
if (numTokens != 1) { // check if redir commmand is formatted correctly
displayError();
return (-1);
} else {
*redir_mode = TRUE; // set redirect mode to true
outFile = strdup(*oTokens);
return (1);
}
}
return (0); // no redirect found
}
/* function checkBgJob
*
* Detects if the command in the input bufferr is a background job.
* Sets bg_mode accordingly
*
*/
void checkBgJob(char inputBuffer[MAX_INPUT_LENGTH + 2], int* bg_mode) {
// detect background jobs
if (inputBuffer[strlen(inputBuffer) - 1] == '&') {
*bg_mode = TRUE; // set background mode to true
// replace the '&' char with '\0', truncating it by 1
inputBuffer[strlen(inputBuffer) - 1] = '\0';
}
}
/* function parseCmdLn
*
* Parses a string buffer ending in a line break. Removes the '\n' char
* and tokenizes buffer into a string array
*
* Returns the number of tokens or items in the array on success
* Returns -1 if there was an error during parsing
*
*/
int parseCmdLn(char inputBuffer[MAX_INPUT_LENGTH + 2],
char* argTokens[MAX_INPUT_LENGTH / 2], int* bg_mode, int* redir_mode,
char* redirFile) {
// int bufferLength = strlen(inputBuffer);
// replace the '\n' char with '\0', truncating it by 1
inputBuffer[strlen(inputBuffer) - 1] = '\0';
// detect background jobs
checkBgJob(inputBuffer, bg_mode);
// check '>' exists in inputStream
int redir_rc = checkRedir(inputBuffer, redir_mode, redirFile);
if (redir_rc < 0) { // error in redir format
return (-1);
}
// Tokenize inputBuffer and store into arg array
int argCount = tokenize(inputBuffer, argTokens);
// Return # of tokens in cmd input
return (argCount);
}
/* function redirectOutput
*
* Closes whatever stream is at STDOUT_FILENO and attempts to open
* the given file
* Returns 0 if file was successfully opened
* Returns -1 if there was an error opening the redirect file
* Returns -2 if there was an error closing stdout file descriptor
*
*/
int redirectOutput(char* filename) {
// Close the file descriptor associated with stdout
FILE* fnFile = freopen(filename, "w", stdout);
int freopen_rc = close(STDOUT_FILENO);
if (freopen_rc < 0) {
displayError();
return (-2);
}
// // Open a new file with the same fd as STDOUT_FILENO
// int fd = open(filename, O_RDWR | O_TRUNC, S_IRWXU);
// if (fd < 0) {
// displayError();
// perror("file descriptor won't open..");
// return (-1);
// }
return (0);
}
/* function runCmd
*
* runs the char** array in a child process.
* bg_mode controls whether parent continues executing or waits.
* Sets bg_mode accordingly
* redir_mode redirects the output to the properly formatted file
*
* Will not complete if there is an error opening the file
*/
void runCmd(char** argTokens[256], int bg_mode, int redir_mode, int inputMode,
char* redirFile) {
// Creates a new process and executes the command
int rc = fork();
if (rc < 0) { // Failure
displayError();
exit(1);
}
// Child uses execvp to run a different program
else if (rc == 0) {
// If redir_mode is set:
// close the fd for stdout and open the redirected file
if (redir_mode)
redirectOutput(redirFile);
execvp(argTokens[0], argTokens);
// Error Case: The command does not exist
displayError();
} // end child process
// Parent waits for child to finish if bg_mode is not sett
else {
if (bg_mode == FALSE) {
int wc = wait(NULL );
}
if (inputMode == INTERACTIVE_MODE) {
printf("mysh> ");
}
}
}
/*function checkBuiltinCmds
*
* checks for built-in commands
*
* returns 1 if found and run, break the loop and reprompt
* returns -1 if in an incorrect format, break the loop and reprompt
* returns 0 if not builtin or python, continue loop
*/
int checkBuiltinCmds(char* argTokens[256]) {
// built-in commands
if (strcmp("exit", argTokens[0]) == 0) { // exit program
if (argTokens[1] == NULL ) {
exit(0);
return (-2);
}
else { //bad exit
displayError();
return (-1);
}
} else if (strcmp("pwd", argTokens[0]) == 0) { // print working directory
if (argTokens[1] == NULL ) { // no other args
char* currDir = NULL;
currDir = getcwd(currDir, 0);
if (currDir) {
write(STDOUT_FILENO,currDir, strlen(currDir));
write(STDOUT_FILENO,"\n", 1);
return (1);
}
displayError();
return (-1);
} else { //bad pwd
displayError();
return (-1);
}
} else if (strcmp("cd", argTokens[0]) == 0) { // cd
if (argTokens[1] == NULL ) { // no args
chdir(getenv("HOME"));
return(1);
}
// make sure there is only one arg
else if (argTokens[2] == NULL ) { // one arg
if (chdir(argTokens[1]) == -1) { // check if directory is valid
displayError();
return(-1);
} else { // bad cd: bad arg
chdir(argTokens[1]);
return (1);
}
} else { // bad cd2: extra args
displayError();
return (-1);
}
} else if (strcmp("wait", argTokens[0]) == 0) { // wait for all children to exit
if (argTokens[1] == NULL ) { // check for extra args
int status, pid;
while ((pid = wait(&status)) != -1) {} // wait for stuff
return(0);
} else { // bad wait
displayError();
return (-1);
}
} else if (isPythonFile(argTokens[0])) {
// replace arg0 after shifting the argTokens to the right
runPython(argTokens);
}
// not a built-in command run regularly
return (0);
}
int isPythonFile(const char* cmdString) {
const char *period = strrchr(cmdString, '.');
if (!period || period == cmdString)
return (0);
char* extension;
extension = period + 1;
if (strchr(extension, "py")) {
return (1);
} else {
return (0);
}
}
void runPython(char* cmdArgs[256]) {
// run python interpreter /usr/bin/python
// using the cmd args as parameters for it's parameters
int size = 256;
char* python = "/usr/bin/python";
int c;
// shift each argument over to make space
// Note: loses the last argument
for (c = size - 1; c >= 0 - 1; c--)
cmdArgs[c + 1] = cmdArgs[c];
// insert command to run python interpereter
cmdArgs[0] = python;
}
void printInteractivePrompt(int inputMode) {
if (inputMode == INTERACTIVE_MODE) {
printf("mysh> ");
}
}
/* function execShell
*
* Executes commands in a inputStream
* inputMode determines whether interactive or batch mode runs
*
*/
void execShell(FILE* inputStream, int inputMode) {
char inputBuf[MAX_INPUT_LENGTH + 2];
// read file line by line until EOF or error reading inputstream
while (fgets(inputBuf, (MAX_INPUT_LENGTH + 2), inputStream)) {
// check to make sure fgets returns valid input
if (inputBuf != NULL ) {
size_t buflen = strlen(inputBuf); // Number of characters read into buffer (sans '\0')
// Prints the command back in batch mode
if (inputMode == BATCH_MODE)
write(STDOUT_FILENO, inputBuf, buflen);
// newline char index, if it exists
if (inputBuf[buflen - 1] == '\n') { // input ends with ('\n')Acceptable length
char* argTokens[MAX_INPUT_LENGTH / 2] = { NULL }; // stores cmd line args
// Flags to keep track of special modes
int bg_mode = 0;
int redir_mode = 0;
char* redirFile = NULL;
// Parse command line and store into argTokens[]
// Also sets the various special flags and files
int parse_rc = parseCmdLn(inputBuf, argTokens, &bg_mode,
&redir_mode, redirFile);
// If bad return value, restart loop to get new input
if (parse_rc <= 0) // redirection error or no input
continue;
// Built-in commands
int builtin_rc = checkBuiltinCmds(argTokens);
if (builtin_rc == -2) {
exit(0);
}
if (builtin_rc == 0) // if builtin command not found or python
// Create a new process to run the command
runCmd(&argTokens, bg_mode, redir_mode, inputMode, redirFile);
} else { //Line does not terminate with '\n'
if (buflen + 1 == sizeof inputBuf) { // input line Too long
displayError();
// Flush input stream to get rid of trailing new line character
dumpline(inputStream);
// write a newline character to stdout start on a new line
write(STDOUT_FILENO,"\n",1);
// prints "mysh> "
printInteractivePrompt(inputMode);
continue;
} else { // EOF reached before line break
break;
}
}
}
}
}
int main(int argc, char *argv[]) {
if (argc == 1) { // if no arguments are specified run in Interactive mode
// prints initial prompt
printf("mysh> ");
// Interactive loop to keep asking user for input
execShell(stdin, 0);
} else if (argc == 2) { // batch mode in case of arguments
// replace stdin stream with batchFile stream
FILE *batchFile = freopen(argv[1], "r", stdin);
if (batchFile == NULL ) { // unable to open filename
displayError();
exit(1);
}
execShell(batchFile, BATCH_MODE);
exit(0);
} else { // invalid number of args (n>2)
displayError();
//exit gracefully
exit(EXIT_FAILURE);
}
return (0);
}