Practical Case Study B
Operating Systems Programming – 300698
1 Introduction
A Shell or Command Language Interpreter (CLI) is a piece of software that provides a simple,
ut powerful, textual interface for users to an operating system. In this Case Study you will
investigate the operation of and implement a simple CLI.
2 Specification
A CLI accepts textual input from the user, and executes the commands issued. The main logic
of the CLI is given below:
main:
loop
get input line
if end of input exit
eak line into words
found := false
if command is builtin
then
do_builtin(line)
found:=true
else
found:=find_and_execute(line)
end if
if not found report e
o
end loop
Your first task will be to write a program that repeatedly reads a line of input from the user,
the fgets() function (see Sect. 5 for usage information of all system provided functions)
will help you here. Your program should end when either end of the input file is encountered,
or the exact word exit appears in the input as the only word on a line. Traditionally both
the File System and Command Language are case sensitive, you should also implement this.
Your next task will be to
eak the line up into words, which are separated by one or more
spaces. The provided function tokenize() does this, and it uses strtok() internally.
You may use this function if you wish, in which case you should provide documentation on its
operation, or you may write your own parser.
You should next implement the find_and_execute() section of the logic above,
y creating a new process using fork(), and then use one of the exec() family of func-
tions to run the program requested by the user in the text provided. If the requested program
cannot be run then an appropriate e
or message should be displayed, pe
or() will help
with this (as there are many reasons why this may fail), and the child process terminated.
1
The CLI process must pause until the created process is concluded, wait() will need
to be used here. Once the new process has finished you must decode and print out the exit
status of that process.
Once this works you should add a builtin function cd to change the working directory of
the CLI. This builtin should always use the next word supplied as the directory to change to,
failure to supply a destination should be treated as an e
or. The chdir() function will be
vital here.
Note This logic has an infinite loop in it. The appropriate place to determine when to exit is in
the middle of the loop. When testing the sample code (if used) you should use the Ctrl + C
key combination to
eak out of the program. Do not use Ctrl + Z , it does something
completely different.
2
3 Marking Scheme
Please see the ru
ic on the vUWS site for the marking Scheme.
3
4 Sample Code
Please note that in the sample code " " indicates a space character in a string.
#include
#include
#include
#include
#include
#include
#include
no.h
#define MAX_LINE 4096
#define MAX_WORDS MAX_LINE/2
* a line can have at most MAX_LINE/2 words, why? *
void tokenize(char *line, char **words, int *nwords);
*
eak line into words separated by whitespace, placing them in the
a
ay words, and setting the count to nwords *
int main()
{
char line[MAX_LINE], *words[MAX_WORDS], message[MAX_LINE];
int stop=0,nwords=0;
while(1)
{
printf("OSP CLI $ ");
* read a line of text here *
tokenize(line,words,&nwords);
* More to do here *
}
eturn 0;
}
* this function works, it is up to you to work out why! *
void tokenize(char *line, char **words, int *nwords)
{
*nwords=1;
for(words[0]=strtok(line," \t\n");
(*nwords
*nwords=*nwords+1
); /* empty body *
eturn;
}
4
5 Supplementary Materials
The material on the following pages is an extract of the linux system documentation and may
prove useful in implementing this Workshop. These manual pages are taken from the Linux
man-pages Project available at:
http:
www.kernel.org/doc/man-pages/.
5
http:
www.kernel.org/doc/man-pages
GETS(3) Linux Programmer’s Manual GETS(3)
NAME
fgetc, fgets, getc, getchar, gets, ungetc − input of characters and strings
SYNOPSIS
#include
int fgetc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int getc(FILE *stream);
int getchar(void);
char *gets(char *s);
int ungetc(int c, FILE *stream);
DESCRIPTION
fgetc() reads the next character from stream and returns it as an unsigned char cast to an int, or EOF on
end of file or e
or.
getc() is equivalent to fgetc() except that it may be implemented as a macro which evaluates stream more
than once.
getchar() is equivalent to getc(stdin).
gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF,
which it replaces with ’\0’. No check for buffer ove
un is performed (see BUGS below).
fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to
y s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A ’\0’ is
stored after the last character in the buffer.
ungetc() pushes c back to stream, cast to unsigned char, where it is available for subsequent read opera-
tions. Pushed-back characters will be returned in reverse order; only one pushback is guaranteed.
Calls to the functions described here can be mixed with each other and with calls to other input functions
from the stdio li
ary for the same input stream.
For non-locking counterparts, see unlocked_stdio(3).
RETURN VALUE
fgetc(), getc() and getchar() return the character read as an unsigned char cast to an int or EOF on end of
file or e
or.
gets() and fgets() return s on success, and NULL on e
or or when end of file occurs while no characters
have been read.
ungetc() returns c on success, or EOF on e
or.
CONFORMING TO
C89, C99. LSB deprecates gets().
BUGS
Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters
gets() will read, and because gets() will continue to store characters past the end of the buffer, it is
extremely dangerous to use. It has been used to
eak computer security. Use fgets() instead.
It is not advisable to mix calls to input functions from the stdio li
ary with low-level calls to read() for the
file descriptor associated with the input stream; the results will be undefined and very probably not what
you want.
SEE ALSO
ead(2), write(2), fe
or(3), fgetwc(3), fgetws(3), fopen(3), fread(3), fseek(3), getline(3), getwchar(3),
puts(3), scanf(3), ungetwc(3), unlocked_stdio(3)
GNU XXXXXXXXXX
STRTOK(3) Linux Programmer’s Manual STRTOK(3)
NAME
strtok, strtok_r − extract tokens from strings
SYNOPSIS
#include
char *strtok(char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr);
DESCRIPTION
The strtok() function parses a string into a sequence of tokens. On the first call to strtok() the string to be
parsed should be specified in str. In each subsequent call that should parse the same string, str should be
NULL.
The delim argument specifies a set of characters that delimit the tokens in the parsed string. The caller may
specify different strings in delim in successive calls that parse the same string.
Each call to strtok() returns a pointer to a null-terminated string containing the next token. This string does
not include the delimiting character. If no more tokens are found, strtok() returns NULL.
A sequence of two or more contiguous delimiter characters in the parsed string is considered to be a single
delimiter. Delimiter characters at the start or end of the string are ignored. Put another way: the tokens
eturned by strtok() are always non-empty strings.
The strtok_r() function is a reentrant version strtok(). The saveptr argument is a pointer to a char * vari-
able that is used internally by strtok_r() in order to maintain context between successive calls that parse the
same string.
On the first call to strtok_r(), str should point to the string to be parsed, and the value of saveptr is
ignored. In subsequent calls, str should be NULL, and saveptr should be unchanged since the previous
call.
Different strings may be parsed concu
ently using sequences of calls to strtok_r() that specify different
saveptr arguments.
EXAMPLE
The following program uses nested loops that employ strtok_r() to
eak a string into a two-level hierarchy
of tokens. The first command-line argument specifies the string to be parsed. The second argument speci-
fies the delimiter character(s) to be used to separate that string into "major" tokens. The third argument
specifies the delimiter character(s) to be used to separate the "major" tokens into subtokens.
#include
#include
#include
int
main(int argc, char *argv[])
{
char *str1, *str2, *token, *subtoken;
char *saveptr1, *saveptr2;
int j;
if (argc != 4) {
fprintf(stde
, "Usage: %s string delim subdelim\n",
argv[0]);
exit(EXIT_FAILURE);
GNU XXXXXXXXXX
STRTOK(3) Linux Programmer’s Manual STRTOK(3)
}
for (j = 1, str1 = argv[1]; ; j++, str1 = NULL) {
token = strtok_r(str1, argv[2], &saveptr1);
if (token == NULL)
eak;
printf("%d: %s0, j, token);
for (str2 = token; ; str2 = NULL) {
subtoken = strtok_r(str2, argv[3], &saveptr2);
if (subtoken == NULL)
eak;
printf(" --> %s0, subtoken);
}
}
exit(EXIT_SUCCESS);
} /* main *
An example of the output produced by this program is the following:
$ ./a.out ’a
cc;xxx:yyy:’ ’:;’ ’/’
1: a
cc
--> a
-->
--> cc
2: xxx
--> xxx
3: yyy
--> yyy
BUGS
Av oid using these functions. If you do use them, note that:
These functions modify their first argument.
These functions cannot be used on constant strings.
The identity of the delimiting character is lost.
The strtok() function uses a static buffer while parsing, so it’s not thread safe. Use strtok_r() if
this matters to you.
RETURN VALUE
The strtok() and strtok_r() functions return a pointer to the next token, or NULL if there are no more
tokens.
CONFORMING TO
strtok()
SVr4, POSIX.1-2001, 4.3BSD, C89.
strtok_r()
POSIX.1-2001
SEE ALSO
index(3), memchr(3), rindex(3), strchr(3), strp
k(3), strsep(3), strspn(3), strstr(3), wcstok(3)
GNU XXXXXXXXXX
FORK(2) Linux Programmer’s Manual FORK(2)
NAME
fork − create a child process
SYNOPSIS
#include
#include
pid_t fork(void);
DESCRIPTION
fork() creates a child process that differs from the parent process only in its PID and PPID, and in the fact
that resource utilizations are set to 0. File locks and pending signals are not inherited.
Under Linux, fork() is implemented using copy-on-write pages, so the only penalty that it incurs is the time
and memory required to duplicate the parent’s page tables, and to create a unique task structure for the
child.
RETURN VALUE
On success, the PID of the child process is returned in the parent’s thread of execution, and a 0 is returned
in the