This is the web page for Operation Systems at the University of Oklahoma.
CS 3113, Fall 2018 Project 1, Due 9/27/2018
You task in Project 1 is to write your own shell.
This will be a small program that loops reading a line from standard input and checks the first word of the input line.
If the first word is one of the following internal commands (or aliases) perform the designated task.
Otherwise, use the standard ANSI C system
function to execute the line through the default system shell.
Please carefully read this project specification in full.
These are custom commands that your shell must implement.
esc
— quit from the program with a zero return value. Use the standard exit function. Note an EOF
signal should also quit the shell.
wipe
— clear the screen using the system function: system("clear")
.
filez [target]
— list the contents or the target directory (ls -1 [target]
). If [target]
is a file, you should only print that file. If no target is specified, the program should print out the files in the current working directory. You will need to provide some command line parsing capability to extract the target directory for listing. Once you have built the replacement command line, use the system function to execute it. The files should sorted by name in ASCII order.
environ
— list all the environment strings - the environment strings can be accessed from within a program by specifying the POSIX compliant environment list:
extern char **environ;
as a global variable. environ
is an array of pointers to the environment strings terminated with a NULL pointer. (see environ.c below for examples of use)
ditto [comment]
- Calling this will print the comment line on the display (without the word ditto
). Remember that [comment]
can be multiple words. This command always prints to the stdout.
help
- This command will print the contents of your README file. More information about the contents of the README file is below.
mimic [src] [dst]
- This command copies the file pointed to by [src]
and writes a new file pointed to by [dst]
. It overwrites any file that may previously existed. Both [src]
and [dst]
may be relative or full paths, but they must be files. Your code must copy the bytes from src
and write them to dst
to create this file. You must use syscalls or libc wrapper functions. Do not use system
.
erase [myfile]
- This command deletes the file pointed to by the parameter [myfile]
. Do not use the system
function to implement the erase function.
morph [src] [dst]
- Move the file or directory indicated by [src]
to [dst]
, changing the name to the name described in [dst]
. For file transfers, your code must move the bytes from src
and write them to dst
to create this file. You must use syscalls or libc wrapper functions. Do not use system
. Cases:
chdir [path]
- Changes the present working directory to the directory pointed to by [path]
. If no [path]
is supplied, the code should print the current working directory followed by a new line to stdout
. Note that only a chdir
will not work. You also need to update the PWD
environment variables using the putenv
function. You do do not need to update the OLDPWD
environment variable.
For all other command line inputs, relay the command line to the parent shell for execution using the system function.
Each command may return errors.
Your code should detect the errors and return an error message to stderr
.
If a command causes an error, do not write your error message to any place but stderr
, not other output is needed.
You should recover gracefully from any errors.
This means proper error checking for each function.
Make the shell capable of taking its command line input from a file whose name (and path) is provided as a command line argument. For example, your shell could be invoked with any of the following command line arguments:
./project1 batchfile
cat batchfile | ./project1
./project1 < batchfile
where batchfile
is assumed to contain a set of command lines for the shell to process.
Each line of the batch file contains a command to be executed.
A command will not be more than one line long.
When the end-of-file (EOF) is reached, the shell should exit.
When a batch file is provided, the shell should not accept input from STDIN.
Important: If a batchfile is given as a parameter, your output should print the line to stdout before processing.
This only corresonds to the first of the instantiations above (./project1 batchfile
).
This is way we can see both the commands coming from stdin
and the correct output.
If the shell is invoked without a command line argument it should solicit input from the user via a prompt on the display as before.
You will probably need to have a look at the difference between the gets
and fgets
functions and insert a test for end-of-file for when reading from a file (int feof(FILE*)
).
You may find it easier to use fgets
for both types of input using the default FILE stream pointer stdin
when no batch file is specified.
Note the use of fgets
in the strtokeg.c
example.
When parsing the command line you may have to explicitly or implicitly malloc (strdup) storage for a copy of the command line. Ensure that you free any malloced memory after it is no longer needed. You may find strtok useful for parsing.
The C Standard Library has a number of other string related functions that you may find useful (string.h). Use the glibc documentation for more information on string functions. The source of the basis for a simple shell using strtok and system is contained in strtokeg.c below. Note the number, type and style of comments in strtokeg.c - this is the level of commentry expected of the code you hand in for your projects.
Code should be in ‘straight’ C using the latest gcc compiler.
Always use nice
to execute your test programs at lower priority to ensure it doesnt lock up the system. e.g. nice ./project1
.
Below is an example run:
Below is an annimated image of how your shell should be executed. We will release test cases over time.
Your files are to be submitted to the Canvas Project 1 dropbox. Submit a single project1.tar.gz file that contains:
In addition, place these files in the /projects/1/
directory on your
virtual machine instance. Make sure that this directory and its
contents are readable by all users (don’t worry, this is all users on
your own instance, not the class and not the world).
For this project, use your f1-micro virtual machine on your google cloud instance.
Choose the us-central1-c
zone.
Be sure to select an external ip and to allow all project keys.
Use the latest version of Ubuntu.
It is okay to use the same instance from the previous project.
Add the ssh public key for the cs3113
user.
The key is available here: https://oudalab.github.io/cs3113fa18/instance/id_rsa_cs3113.pub.
You should have all the necesasry prerequsites installed. You may run the start up script available here to reinstall programs and create the proper directories startup.sh.
The code for this project should go in the /projects/1/
folder.
Create a makefile
that will compile all necessary files and create a project1
executable file.
Your make file should enable both make clean
and make all
.
The make clean
command removes an old executable and any other non source files.
The make all
command compiles the code and creates the executable.
Task | Percent |
---|---|
Instance is reachable and code compiles with make all and make clean |
10% |
Documentation: Proper functional-level and inline documentation. README is thorough and complete. | 40% |
Correctness: This will be assessed by giving your code a range of inputs and matching the expected output. | 50% |
Total | 100% |
Note: We expect a thorough discussion of your implementation as part of your README file.
int atexit(void (*fcm)(void));
Registers fcn to be called when program terminates normally (or when main returns). Returns non-zero on failure.
void exit(int status);
Terminates program normally. Functions installed using atexit are called (in reverse order to that in which installed), open files are flushed, open streams are closed and control is returned to environment. status is returned to environment in implementation-dependent manner. Zero or EXIT_SUCCESS indicates successful termination and EXIT_FAILURE indicates unsuccessful termination. Implementations may define other values.
void* malloc(size_t size);
Returns pointer to uninitialised newly-allocated space for an object of size size, or NULL on error.
char* strdup(const char *s);
Returns a pointer to a new string that is a duplicate of the string pointed to by s
. The space for the new string is obtained using malloc
. If the new string cannot be created, a null pointer is returned.
char* strtok(char* s, const char* ct);
Searches s
for next token delimited by any character from ct
. Non-NULL s
indicates the first call of a sequence. If a token is found, it is NULL-terminated and returned, otherwise NULL
is returned. ct
need not be identical for each call in a sequence. Beware! This call does not malloc
space for the returned string - strtok
returns a pointer to the string in the original buffer s
, and replaces the delimiting character with NULL.
View the Open Group website for more information on this function http://pubs.opengroup.org/onlinepubs/9699919799/functions/strtok.html.
int system(const char* s);
If s
is not NULL
, passes s
to the default command shell for execution, and returns status reported by the command processor when the command is complete; if s
is NULL, non-zero returned if environment has a command processor (UNIX systems will always have one - by default it will use sh
).
/*
environ - skeleton program displaying environment
usage:
environ
displays environment with each name, value pair on a separate line
*/
#include <stdio.h>
#include <stdlib.h>
extern char **environ; // environment array
int main(int argc, char **argv)
{
char ** env = environ;
while (*env) printf("%s\n",*env++); // step through environment
exit(0);
}
/*
strtokeg - skeleton shell using strtok to parse command line
usage:
./a.out
reads in a line of keyboard input at a time, parsing it into
tokens that are separated by white spaces (set by #define
SEPARATORS).
can use redirected input
if the first token is a recognized internal command, then that
command is executed. otherwise the tokens are printed on the
display.
internal commands:
wipe - clears the screen
esc - exits from the program
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_BUFFER 1024 // max line buffer
#define MAX_ARGS 64 // max # args
#define SEPARATORS " \t\n" // token sparators
int main (int argc, char ** argv)
{
char buf[MAX_BUFFER]; // line buffer
char * args[MAX_ARGS]; // pointers to arg strings
char ** arg; // working pointer thru args
char * prompt = "==>" ; // shell prompt
// keep reading input until "quit" command or eof of redirected input
while (!feof(stdin)) {
// get command line from input
fputs (prompt, stdout); // write prompt
if (fgets (buf, MAX_BUFFER, stdin )) { // read a line
// tokenize the input into args array
arg = args;
*arg++ = strtok(buf,SEPARATORS); // tokenize input
while ((*arg++ = strtok(NULL,SEPARATORS)));
// last entry will be NULL
if (args[0]) { // if there's anything there
// check for internal/external command
if (!strcmp(args[0],"wipe")) { // "clear" command
system("clear");
continue;
}
if (!strcmp(args[0],"esc")) // "quit" command
break; // break out of 'while' loop
// else pass command onto OS (or in this instance, print them out)
arg = args;
while (*arg) fprintf(stdout,"%s ",*arg++);
fputs ("\n", stdout);
}
}
}
return 0;
}