CS371 Operating Systems Programming Assignment 1: Shell Implementation Purpose T
ID: 3880266 • Letter: C
Question
CS371 Operating Systems Programming Assignment 1: Shell Implementation Purpose There are several goals for this assignment. First, you will get a chance to familiarize yourself with the C/C++ programming language. Second, you will learn how to use some of the 1O facilities and library functions provided by UNIX and C/C++. Third, you develop some experience with the process creation and program starting facilities in UNIX. Fourth, you'll get some experience making your program robust, i.e., resistant to crashing Your assignment is to write a shell: a program that starts up other programs. Your shell, called Cs371sh will read input lines from standard input, parse them into a command name and arguments, and then start a new process running that command. When you start your shell, it will provide a prompt and then wait for a command line of the form: foobar -1 this and that this , "and", and This starts a program stored in a file called-foobar" with four arguments: "that" -l", UNIX/Linux Manual Pages You should become familiar with the UNIX manuals and the online "man" facility. This will be a great help in working on these assignments. For example, if you wanted to know how the fork (create process) system call works, you would type: man 2 fork The UNIX manual is organized into many sections. You will be mainly interested in the first three sections. Section 1 is for commands, like 1s, gec, or cat. Section 2 is for UNIX system calls (calls directly to the UNIX kernel), such as fork, open, or read. You will typically not use Section 2. The UNIX library routines are in Section 3. These are calls such as atof, or strcpy. More details about the online manual will be given in Discussion Sections. Program Details Your shell will loop (until end-of-file) waiting for a line from standard input. Each line has a command and zero or more arguments, and is of the form: and argi .argk Each of these items is separated by one or more blank characters (spaces or tabs). There is one command per input line. Your shell will fork a child process and then overlay itself (exec) the command in the file named by "cmd". The shell parent process will default to waiting for the child process. Optionally, at the end of any command input, can be a "&" character, which means that the parent process does not wait for the child process to complete before prompting for the next command The command line is processed by the shell into a list of character strings, one for each argument (including the command name). These arguments are passed as parameters to the exec commandExplanation / Answer
#include <stdio.h>
#include <stdlib.h>
#include <stdio_ext.h> // for the locking macros
#include <string.h>
#include <fcntl.h> // for the open function
#include <unistd.h> // for STDIN/STDOUT_FILENO
/* "An Interactive shell in C"
To Compile: gcc code.c
To Run : ./a.out
The program is divided into 3 modules: 1) READLINE 2) PARSER 3) EXECUTION
Module wise capabilites:
1) a handy prompt [READLINE]
2) eliminating trailing and in between whitespaces [PARSER]
3) ability to retain whitespaces in special names [PARSER]
4) redirecting I/O [EXECUTION]
5) PATH search [EXECUTION]
6) shell buitlins such as "cd" and "exit" [EXECUTION]
Note: The shell implemented here does not escape spaces using backward slashes. So if an argument contains whitespaces surround it with double quotes.
*/
char* ps1; // string used as prompt
int token_count;
/* readline returns a pointer to the line read or NULL if the user simply presses enter */
char* readline(char* prompt){
printf("%s:> ", prompt);
char* str = NULL;
size_t n = 0;
ssize_t x = getline(&str, &n, stdin); // reads a line including ' ' and returns no of characters read
if(x==-1)
perror("getline"); // perror: prints the error message as per the current value of errno
else if (x==1){ // i.e. only ' ' is read
free(str);
return NULL;
}
return str;
}
/* parseTokens returns an array of strings, therefore the double "**" */
char** parseTokens(char* str){
int i, j, len = strlen(str);
len=len-2; // -2 to exclude ' ' and ''
while(str[len]==' ') len--; // removing trailing whitespaces
str[++len] = ' '; // adding an extra space at the end to delilmit the last word with a space as well
token_count=0;
char** arr = (char**)malloc(0); // this is not same as making it point to NULL
int index = 0; // to index into arr
int lastIndex = 0; // to remember the last occurence of a whitespace
for(i=0;i<=len;i++){ // note: <=len and not just <len since we have already excluded the unnecessary
if(str[i]=='"'){ // remember to use the single quotes and not the double quotes
int j = i+1;
while(++i<len && str[i]!='"'); // finding tokens enclosed withing double quotes
if(i==len){
puts("err: could not find the corresponding closing quote.");
free(str);
int k; // releasing back the resources
for(k=0;k<index;k++)
free(arr[k]);
free(arr);
exit(EXIT_FAILURE);
}
token_count++;
arr = realloc(arr, sizeof(char*)*token_count); // allocating more size as and when needed
int sz = i-j; // no of character between the double quotes
char* tk = (char*)malloc(sz);
strncpy(tk, str+j, sz);
arr[index++] = tk;
}
else if(str[i] == ' '){
if(i>0 && str[i-1]==' '){
lastIndex = i+1;
continue;
}
token_count++;
arr = realloc(arr, sizeof(char*)*token_count); // remember to catch the value back in arr
int sz = i - lastIndex;
char* tk = (char*)malloc(sz + 1);
strncpy(tk, str+lastIndex, sz);
tk[sz] = ''; // null terminating each token (may not be essential but a good practise)
lastIndex = i + 1;
arr[index++] = tk;
}
}
return arr;
}
void changeDirectory(char* newDir){
if(chdir(newDir)==-1)
perror("chdir");
ps1 = getcwd(NULL, 0); // our prompt will be the current working directory (cwd)
}
int main(int argc, char const *argv[])
{
__fsetlocking(stdin, FSETLOCKING_BYCALLER); // removing implicit locking improves I/O speed
__fsetlocking(stdout, FSETLOCKING_BYCALLER); // safe since we are not multi-threading it
ps1 = getcwd(NULL, 0); // getcwd: "get current working directory"
while(1){
char* line = readline(ps1); // module1: readline
if(line==NULL)
continue;
int pipesPresent = 0;
char** tokens = parseTokens(line); // module2: parser
free(line);
/* Checking for shell butilins (functions that change the state of the shell itself) */
if(strcmp(tokens[0],"cd")==0){
char* dir = (token_count==1) ? "/home/a-star/" : tokens[1];
changeDirectory(dir); // defined above
int i;
for(i=0;i<token_count;i++)
free(tokens[i]);
free(tokens);
continue;
}
if(strcmp(tokens[0], "exit")==0){
int i;
for(i=0;i<token_count;i++)
free(tokens[i]);
free(tokens);
free(ps1);
exit(EXIT_SUCCESS);
}
/* Resuming with module3: execute */
pid_t rt = fork();
if(rt == -1) perror("fork"); // error
if(rt == 0) { // inside the child process
int new_out, new_in; // for new file descriptors [needed for redirecting the I/O]
int saved_in, saved_out; // for saved file descriptors
char* args[token_count+1]; // changing from char** to char*[] since that is the specified format for execvp
int i=0;
for(i=0;i<token_count;i++)
args[i] = tokens[i];
args[i] = NULL; // very important to null terminate the array otherwise u will get a BAD address error
int j, lindex = i; // lindex - last index of the redirection token
if(i>2) {
for(j=i-2; j>0; j--){
if(pipesPresent >= 2)
break;
char* ct = args[j]; // ct- current token
if(strlen(ct)==1){
char pipe = ct[0];
if(pipe=='>'){
lindex = j;
++pipesPresent;
new_out = open(args[j+1], O_CREAT | O_RDWR, 0666); // writing at times require reading privileges as well
if(new_out == -1) // the 0 above is very important
perror("write_open");
saved_out = dup(STDOUT_FILENO);
dup2(new_out, STDOUT_FILENO);
close(new_out); // note: closing them right away
}
else if(pipe=='<'){
lindex = j;
++pipesPresent;
new_in = open(args[j+1], O_RDONLY); // writing at times require reading privileges as well
if(new_in == -1)
perror("read_open");
saved_in = dup(STDIN_FILENO);
dup2(new_in, STDIN_FILENO);
close(new_in);
}
}
}
}
for(j=lindex;j<i;j++)
args[j] = NULL; // removing the unnecessary arguments
if(execvp(tokens[0], args)==-1){
perror("execvp");
dup2(saved_in, STDIN_FILENO); // restoring the original file descriptors
dup2(saved_out, STDOUT_FILENO); // in case execvp is not successful
close(saved_in);
close(saved_out);
}
}
else { // Inside the parent
if(wait(NULL)==-1) // waiting for the child to exit
perror("wait");
int i;
for(i=0;i<token_count;i++)
free(tokens[i]);
free(tokens);
}
}
return 0;
}