#include string.h#include stdlib.h#include systypes.h.docx

AASTHA76 25 views 15 slides Dec 06, 2022
Slide 1
Slide 1 of 15
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15

About This Presentation

#include
#include
#include
#include
#include
#include
#include


// Function: void parse(char *line, char **argv)
// Purpose : This function takes in a null terminated string pointed to by
// . It also takes in an array of pointers to char .
// When the function returns,...


Slide Content

#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.
Tags