{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww16800\viewh10700\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural \f0\fs24 \cf0
\ Your very own shell\
\ In this project you will implement a shell program which prompts the user for commands, parses the command, and executes it with a child process. In this exercise, you will use execv( ). This means that you will have to read the PATH environment variable and then search each directory in the PATH for the command file name that appears in the command line. \
\ An OS shell exports a character-oriented human-computer interface, and uses the system call interface to invoke OS functions. A text-based shell assumes a screen display with a fixed number of lines (say 25) and a fixed number of characters (say 80) per line. The user interacts with the OS by typing a string of characters to the shell and the OS prints the characters back to the shell display. After a user logs in, the shell first prints a prompt (e.g. mimi$). The user responds by typing in a command (e.g. ls ), terminated by (for example) a return in UNIX. The shell then makes the appropriate system calls to execute the command.\
\
Shells have their own syntax and semantics. In UNIX a command line has the form\
\
command argument_1 argument_2.........\
\
where the command is what it sounds like it is, the command to be executed, and the arguments are utilized by the command. For example, in ls -al, ls is the command while a and l are arguments which ls utilizes. Each command specifies its own syntax for interpreting an argument. This means that the command determines which arguments are to be grouped (e.g. a and l are grouped above), which arguments must be preceded by a "-" symbol, and whether the position of the argument in the list of arguments is important. A more complicated example is \
\
mimi$ cc -g potrezebie -S main.c inout.c -lmath\
\
in which all of the arguments following cc are passed to the C compiler c. \
\ The command is usually the name of a file that contains an executable program (e.g. ls and cc are the names of files which are stored in /bin on most UNIX machines). When the command is not the name of a file, it is the name of a command that is implemented in the shell, e.g. cd (change directory) is implemented in the shell rather then being the name of a file most of the time. The vast majority of commands are the names of files which reside in directories on the machine in question. Hence the file's job is to find the file, to prepare the list of parameters (following the syntax of the command) for input to the command, and finally to cause the command to be executed. There are many shell for UNIX, including the Bourne shell (sh), the C shell (csh) and the Linux shell (bash). All of them have a similar syntax for the commands. \
\ The basic approach to designing a shell is to create a new process (or thread) which will execute a new computation. When a user compiles a program, the shell first creates a child process whose job it is to execute the compile command provided by the user. There is a reason behind this design philosophy-if the shell did not put a child process in charge of executing a command such as the compile command, then an error in the command might cause the shell itself to crash, bringing the entire machine to a halt. \
\
The file in the command line may be written by the user. It will be executed just as if it was a command in UNIX. For example, if the user writes a C program contained in a file named main.C, it can be compiled and executed by \
\
mimi$ cc main.c
\
mimi$ a.out
\
\ Here is what happens-the shell finds the cc command in the /bin directory. It creates a child process to execute the cc program and passes it the string main.c. \ A description how to create child processes is included at the end of problem statement, under the heading "how to create children".\ The C compiler then compiles main.c and writes the executable into a.out in the current directory. When the shell then executes the second command, it finds a.out in the current directory, loads it and executes it. \
\
The steps that a shell must take are as follows:
\
\
1. Print a prompt-when the shell starts up, it looks for the name of the of the machine on which it is executing and prepends it to its own very special character , e.g.\
mimi$. The shell can also print the name of the current directory in which it is operating. The name of directory can change as a result of executing the cd command. Once determined, the prompt string prints it to stdout. For example,\
\
void printPrompt( ) \{
\
promtString=frammis;
\
printf("%s",promptString);
\
\}
\
\
2. Get the command line-the shell does a blocking read to read in the command line. This means that the process executing the shell is blocked until the user types in the command. The user terminates the command with a NEWLINE character. \
The stdio library can be made use of in implementing this command. \
For example,\
\
void read Command(*char *buffer)\{
\
gets buffer(buffer):
\
\}
\
\
3. Parsing the command.\
\
The main program in the shell can be declared as
\
\
int main(in argc, char *argv [ ] )
\
\
This means that a command of the form a.out moxie 100\
results in argc being set to 3 (there are three words on the command line) and the argv[ ] array being initialized as follows:
\
\
arg[0]= "a.out"
\
arg[1]= "moxie"
\
arg[3]="100"
\
\
Use the struct data structure to store the command line arguments:
\
\
struct command_t\{
\
char *name;
\
int argc;
\
char *argv[MAX_ARGS];
\
\}
\
\
char *name contains the name of the file which contains the name of the binary program for the command.
\
\
Here is a function which parses a command line and returns the result in struct command_t *cmd argument: \
4. Finding the file. The shell provides environment variables for the user. They are defined in the .login file, and can be modified by the set command. The PATH environment variable is a list of pathnames which specify where the shell should look for command files. It the .login file has the line\
set path=(.:/bin:/usr/bin), then the shell first looks in the current directory ( as indicated by the "." , then in bin and finally in /usr/bin. \
\
Before reading command lines, the shell needs to parse the the PATH variable, accomplished by \
If the command name begins with a "/" then it is an absolute pathname that can be used to launch execution. Otherwise, it is necessary to search each directory in the list provided by the PATH environment variable to find the relative pathname. Each time that a command is read it is necessary to see if there is an executable file corresponding to the command in one of the directories specified by the Path variable. The following lookup( ) function serves this purpose. \
Here is a header file, minishell.h, for the shell program.\
\
When a (parent) process calls fork ( ), a new child process is created with its own copies of the parent's program text, data and stack segments, as well as access to all open file descriptors. The fork function returns a value of zero to the child and the PID of the child to the parent. The child and the parent then execute concurrently, each in their own address spaces. No part of their address spaces are shared; this means that the parent and child cannot communicate via shared variables, among other things. In UNIX, two processes can only reference open files. In the following code, the child starts executing the same code as the parent,\
right after the fork, i.e. it starts at the print statement. \
\
If the parent wants the child to execute code which is different from the parent,\
this can be done via:\
\
In this code, both parent and child test the PID variable. The child sees zero, and executes its code, while the parent sees the value of the PID variable, and executes his code. \
Use execve( ), which dynamically calls the loader to redefine a process's text, stack and data.\
\
This causes the binary program stored in the file at path to replace the text, data, and stack currently being exectued by the process. After execve( ) is called, the program which called it is no longer being executed by the process. The OS does not return from the execve( ) call. After the new program is loaded, the stack is cleared, variables are initialized, and the process begins execution at the the program's entry point. The new process is passed the argument list, argv and the process uses a new set of environment variables, envp. \
\
So the way to create a new child to execute the next command is \
\
\
#include
\
\
int parseCommand(char *cLine, struct command_t *cmd) \{
\
int argc;
\
char **char **c1Ptr;
\
/*Initialization*/
\
c1Ptr = &cLine; /*the command line*/
\
argc=0;
\
cmd->argv[argc] = (char *) malloc(MAX_ARG_LEN);
\
/*fill argv[ ]*/
\
while((cmd->argv[argc] = strsep((c1Ptr, WHITESPACE)) ! NULL \{
\
cmd->argv[++argc] = (char *) malloc(MAX_ARG_LEN);
\
\}
\
\
/*Set the command name and argc*/
\
cmd->argc=argc-1;
\
cmd->name + (char *) malloc(sizeof(cmd->argv[0]));
\
strcopy(cmd->name, cmd->argv[0]);
\
return 1;
\
\}
\
\
\
int parsePath( char *dirs[ ]) \{
\
/* This function reads the PATH variable for this environment, and builds an array, dirs[ ], of the directories in PATH*/
\
\
char *pathEnvVar;
\
char *thePath;
\
\
for(i-0; i < MAX_ARGS; i ++)
\
dirs[ ]=NULL;
\
pathEnvVar = (char*)getenv("PATH");
\
thePath = (char *) malloc(strlen(pathEnvVar) + 1;
\
strcpy(thePath, pathEnvVar);
\
\
/*Loop to parse thePath. Look for a ":" delimiter between each path name.*/\
\}\
\
\
char *lookupPath(char **argv, char **dir) \{
\
/*This function searches the directories identified by dir to see if argv[0] (the file name) is there. It allocates a new string, places the full path name in it and then returns the string*/
\
\
char *result;
\
char pName[MAX_PATH_LEN];
\
\
//Check to see if file name is already an absolute path name \
if(*argv[0]==== '/') \{
\
....
\
\}
\
\
//Look in PATH directories
\
//Use access ( ) to see if the file is in a dir.
\
for(i=0; i < MAX_PATHS; i+ +) \{
\
...
\
\}
\
\
\
Skeleton for the solution\
\
\
\
#define LINE_LEN 80
\
#define MAX_ARGS 64
\
#define MAX_ARG_LEN 16
\
#define MAX_PATHS 64
\
#define MAX_PATH-LEN 96
\
#define WHITESPACE " .,\\t\\n"
\
\
#ifndef NULL
\
#define NULL ......
\
#endif
\
\
struct command_t \{
\
char *name;
\
int argc
\
char *argv[MAX_ARGS];
\
\};
\
\
\
Here is a skeleton for the solution:\
\
\
/*The shell first finds an executable in the PATH, and then loads and executes it *using execv. Because it uses "." as a separator, it cannot handle file names like *"minshell.h" */
\
\
#include ...
\
#include "minishell.h"
\
\
char *lookupPath(char **, char **);
\
int parseCommand(char *, struct command_t*);
\
int parsePath(char **);
\
void printPrompt( );
\
void readCommand(char *);
\
...
\
void readCommand(char *);
\
...
\
int main( ) \{
\
...
\
\
/* Shell initialization* /
\
...
\
parsePath(pathv);/*Get directory paths from PATH*/
\
\
while(TRUE) \{
\
printPrompt( );
\
\
/*read the command line and parse it*/
\
readCommand(commandLine);
\
...
\
parseCommand(commandLine, &command);
\
...
\
/*Get the full pathname for the file*/
\
command.name = lookupPath(command.argv, pathv);
\
if(command.name == NULL \{
\
/* report error*/
\
continue;
\
/* create child and execute the command*/
\
...
\
/*wait for the child to terminate*/
\
...
\
\}
\
/*shell termination*/
\
...
\
\}
\
\
How to create children, according to UNIX\
\
theChild = fork( );
\
printf(" I am being treated like an orphan, my PID is %"d\\n" , theChild");
\
...
\
\
childPID = fork( );
\
if(theChild == 0) \{
\
code for the child;
\
exit(0);
\
\}
\
/*the parent executes here*/
\
\
\
But how do we get the command actually executed?\
\
int execve (char *pth, char *argv[ ] , char * envp[ ]);
\
\
if((pid = fork( ) = = 0) \{
\
execvp(comand.name, command.argv);
\
\
\
\
\
\
\
}