User Report

#include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <unistd.h> #include <string.h> // Function: void parse(char *line, char **argv) // Purpose : This function takes in a null terminated string pointed to by // <line>. It also takes in an array of pointers to char <argv>. // When the function returns, the string pointed to by the // pointer <line> has ALL of its whitespace characters (space, // tab, and newline) turned into null characters ('\0'). The // array of pointers to chars will be modified so that the zeroth // slot will point to the first non-null character in the string // pointed to by <line>, the oneth slot will point to the second // non-null character in the string pointed to by <line>, and so // on. In other words, each subsequent pointer in argv will point // to each subsequent "token" (characters separated by white space) // IN the block of memory stored at the pointer <line>. Since all // the white space is replaced by '\0', every one of these "tokens" // pointed to by subsequent entires of argv will be a valid string // The "last" entry in the argv array will be set to NULL. This // will mark the end of the tokens in the string. // void parse(char *line, char **argv) { // We will assume that the input string is NULL terminated. If it // is not, this code WILL break. The rewriting of whitespace characters // and the updating of pointers in argv are interleaved. Basically // we do a while loop that will go until we run out of characters in // the string (the outer while loop that goes until '\0'). Inside // that loop, we interleave between rewriting white space (space, tab, // and newline) with nulls ('\0') AND just skipping over non-whitespace. // Note that whenever we encounter a non-whitespace character, we record // that address in the array of address at argv and increment it. When // we run out of tokens in the string, we make the last entry in the array // at argv NULL. This marks the end of pointers to tokens. Easy, right? while (*line != '\0') // outer loop. keep going until the whole string is read { // keep moving forward the pointer into the input string until // we encounter a non-whitespace character. While we're at it, // turn all those whitespace characters we're seeing into null chars. while (*line == ' ' || *line == '\t' || *line == '\n' || *line == '\r') { *line = '\0'; line++; } // If I got this far, I MUST be looking at a non-whitespace character, // or, the beginning of a token. So, let's record the address of this // beginning of token to the address I'm pointing at now. (Put it in *argv) // then we'll increment argv so that the next time I store an address, it // will be in the next slot of the array of integers. *argv++ = line; /* save the argument position */ // Ok... now let's just keep incrementing the input line pointer until // I'm looking at whitespace again. This "eats" the token I just found // and sets me up to look for the next. while (*line != '\0' && *line != ' ' && *line != '\t' && *line != '\n' && *line !='\r') line++; /* skip the argument until ... */ } // Heh, I ran out of characters in the input string. I guess I'm out of tokens. // So, whatever slot of the array at argv I'm pointing at? Yeah, put a NULL // there so we can mark the end of entries in the table. *argv = NULL; /* mark the end of argument list */ } void execute(char **argv) { if (argv[1] == NULL) printf("I would fork() a process to execute %s if I knew how\n", *argv); else { printf("I would fork() a process to execute %s with the parameters\n", *argv++); while (*argv != NULL) printf(" %s\n", *argv++); printf("if I knew how\n"); } } int main(void) { char line[1024]; // This is the string buffer that will hold // the string typed in by the user. This // string will be parsed. The shell will do // what it needs to do based on the tokens it // finds. Note that a user may NOT type in // an input line of greater than 1024 characters // because that's the size of the array. char *largv[64]; // This is a pointer to an array of 64 pointers to // char, or, an array of pointers to strings. // after parsing, this array will hold pointers // to memory INSIDE of the string pointed to by // the pointer line. argv[0] will be the string // version of the first token inside of line... // argv[1] will be the second... and so on... // See the routine parse() for details. char shell_prompt[15]; // This string will hold the shell prompt string // set the default prompt strcpy(shell_prompt, "SillyShell"); // The shell by default goes forever... so... while forever ;) while (1) { printf("%s> ",shell_prompt); // display the shell prompt fgets(line, 1024, stdin); // use the safe fgets() function to read // the user's command line. Why wouldn't // we use gets() here? line[strlen(line)-1]='\0'; // This is a dirty hack. Figure it out maybe? if (*line != '\0') // If something was actually typed, then do something... { // First, get all the addresses of all of the tokens inside the input line parse(line, largv); // parse the line to break it into token references // Check the first token to see if there are any built in commands // we want to handle directly. Do this with an "if/then/else" ladder. // if we hit the end of the ladder, we assume the command line was requesting // an external program be run as a child process and do that.... if (strcmp(largv[0], "exit") == 0) exit(0); else if (strcmp(largv[0], "done") == 0) exit(0); else if (strcmp(largv[0], "newprompt") == 0) { if (largv[1] != NULL) strncpy(shell_prompt, largv[1], 15); else strncpy(shell_prompt, "SillyShell", 15); } else execute(largv); /* otherwise, execute the command */ } } }

Homework One: Operating Systems Internals and Design Fall Semester 2018

As we discussed in class and in the book, a shell is a type of user interface. Generally speaking,

a CLI (Command-LIne) shell is a text-based interface in which a user types text commands at a prompt, and the shell program reads and executes user commands. Shells can also include a

simple programming language (a scripting language) that a user could also use to automate

tasks that would otherwise require a great deal of typing from the command line. You can read

more about shells in general at:

https://en.wikipedia.org/wiki/Shell_(computing)

A simple CLI shell might have a processing loop that looks something like this:

while (shell_is_not_finished) { read a line of input; tokenize the line of input; if (first token is a built-in command)

{ do what the command says }

else { fork a clone of the shell;

Have the clone load the program named in the first token and pass it the tokens it has them too

} }

In English, what the shell does is this:

Go into an infinite loop of reading and interpreting command line input. For each line read,

FIRST “tokenize” the input. By tokenizing, we mean that we rewrite the input string so that all

white space in the string (space, tab, newline, and carriage return characters) are replaced by

null characters (ASCII code zero) and that we create an array of pointers that point to the first

NON ZERO (null) character of each cluster of non-null characters. This may sound complex, but

consider the following:

A user types “ls -l” at the command line. In this case, the user typed the “ls” command name, three spaces, and then the command line flag “-l” (long list option). In memory, the shell would maintain a buffer of characters typed that would look like this:

l S l-input_line \0

Spring, 2019

Where input_line is a variable of type pointer to character (char *) that points to the memory location where the FIRST character of the input data is stored. Each subsequent

memory location will hold subsequent characters in the input string. The first step is to change

all the “whitespace” characters to null characters, like this:

This could be done with a simple loop. After this step, you’d create another array, this time of

POINTERS to chars. Each pointer in THAT array would point to the first character in each of the

“separated words” in the input line. That would look something like this:

Now we have a new array. The first element of that array is pointed to by l_argv. Each subsequent slot of l_argv is a pointer to subsequent words in the input string. The array of pointers is itself terminated by a memory location containing NULL. Notice the format of our “l_argv” array is IDENTICAL to that of the argv array you’re already familiar with.

Once the above structure is created, then l_argv[0] will be the string that is the first TOKEN inside of the input. l_argv[1] will be the string that is the second TOKEN in in the input…. and so on.

In short, the process of parsing creates something that looks and acts just like the

char **argv parameter you could pass into main(), except of course it creates a tokenized version of user input instead of system input.

Once things are tokenized, your shell could look at the FIRST token to see if it is a “built-in” or a

“program name”. If it’s a built-in, then it should just call local code to do it. If it is not, it should

fork a process, load the text segment of that process with the program (passing to it any

parameters it should get from the command line) and then wait until the child is done. When

the child is done, then the shell can continue reading, parsing, and doing what it’s told line by

line.

You may want to examine the heavily commented code “sillyshell_template.c” at this point.

The template code will take in commands, parse them, and process a very small collection of

built-ins. It will not actually fork processes and run other programs in them. It will just

l S \0 \0 \0 l- \0input_line

S \0 \0 \0 l- \0l

NULL

l_argv

input_line

complain that it wish it could and return you to regular processing. Before moving on, make

sure you understand the template code.

For your assignment, you will need to complete the following programming tasks. EVERYTHING

you need is either in the book, explicitly mentioned in lecture or one of the in-class examples

you were asked to work, or is explicitly in the template code itself. You will need at least a basic

understanding of everything in those sources to do this assignment. Note that you get to a 60%

JUST by repeating things we did in class.

Task One: Add Simple Program Calling (50 points) For task one, modify the program so that that when the token pointed to by largv[0] is NOT a built-in command, your sillyshell will do the following: a) fork a process b) have the child

process load the program in the file specified by largv[0] and be passed the appropriate command line arguments. c) have the parent wait on the completion of the child, then return

to normal processing of input lines. Note, ALL of modifications you need to make could be

done INSIDE the sillyshell routine called execute(). Also note that this task is nearly identical to a task we did during an in-class activity.

Task Two: Add a Built-In Command that Prints out All Environment Variables (10 points) For task two, add a new built-in command called “printenv” that prints to the screen ALL of the current shell’s environment variables. This will require you to include a slight variation on

code you would have developed during an in-class exercise.

Task Three: Properly Handle Control Codes (20 points) Generally speaking, shells should NOT react to signals in the way that other processes might.

For example, typing control-c USUALLY interrupts a running process. A shell should not shut

down just because someone types control-c. Also, a shell USUALLY “shuts down” when

someone types control-d. The template I gave you goes into an endless loop if you try that

(yes, this is an intentional bug). For this task, you should make silly shell PROPERLY handle both

control-c and control-d. When you are running silly shell, typing control-c should have no effect

when you are at a prompt. Typing control-d should make sillyshell quit. Adding each capability

is worth 10 points each. Note, you’ll want to handle the control-c problem with material you

can find here: https://www.usna.edu/Users/cs/aviv/classes/ic221/s16/lec/19/lec.html

You will want to handle the control-d problem by investigating the fgets() routine and finding out what it returns if anyone types control-d.

Task Four: Putting a Command in the Background (20 points) In many standard shells, typing the & character as the last token for something that is not a

built-in will put the child process “in the background”. This means that the child process

created will NOT block the shell. The child and the shell will run at the same time and the shell

will continue accepting and running command lines even before the child terminates. Actual

shells also have job control commands that enable you to manipulate background jobs using

additional built-in commands. You can see some details at

https://www.gnu.org/software/bash/manual/html_node/Job-Control-Basics.html

Note that for this assignment, I am not requiring job control commands be added to your

sillyshell. For purposes of this task, you can put jobs “in the background” in any number and

you are not required to have any mechanism by which silly shell can interact with them after

they are created. Note, though, that REAL shells would always have such capability.

Task Five: Adding Job Control Capability Do NOT attempt this until you have verified that EVERYTHING ELSE is perfect. In this task, you

should describe a job control mechanism of your design that mimics at least some of the

abilities of the BASH shell’s job control capability. Tell me about your JCL built-ins and what

they do. Provide me with screen dumps showing us their use with real processes. Points will

be assigned according to the completeness of the JCL and will be applied against the NEXT

assignment (I.E. if you don’t get all the points on assignment #2, you can “spend” your bonus

points to make up the difference).

You should turn in a si e i i e that contains your C language source code for your assignment. With TASK FIVE, there should also be a text file that explains what job control capability you attempted. Your code should be HIGHLY commented. Please make your

comments descriptive of your thinking processes so that we are in the best position to give

partial credit if for some reason there are bugs in your code. You should also include in the zip file screen dumps and/or descriptions of how you tested each capability. The more evidence of

functionality you can provide, the better. The instructor and TA will also be compiling your

code and running our own tests. I will link a brief video lecture that explains how I will test your

code. If you can pass all the tests, you’ll get full credit.

Submit Your Homework

Let us help you with your homework, we will match you with one of our professional tutors.

My Info

Project Info

Due Date

Describe your Homework Problem

Budget (optional)

By providing your budget we will work on finding the best tutors that can work within it.

Get help from top-rated tutors in any subject.

Efficiently complete your homework and academic assignments by getting help from the experts at homeworkarchive.com