Week 10 Laboratory Exercises

Objectives

  • learning how to retrieve environment variables
  • understand how fork, wait and execve work
  • understand how posix spawn works

Preparation

Before the lab you should re-read the relevant lecture slides and their accompanying examples.

Getting Started

Set up for the lab by creating a new directory called lab10 and changing to this directory.
mkdir lab10
cd lab10

There are some provided files for this lab which you can fetch with this command:

1092 fetch lab10

If you're not working at CSE, you can download the provided files as a zip file or a tar file.

Exercise — individual:
Append to a Diary File

We wish to maintain a simple diary in the file $HOME/.diary

Write a C program, diary.c, which appends 1 line to $HOME/.diary.

The line should be its command-line arguments separated by a space followed by a '\n'.

$HOME represents the HOME environment variable. This means you will need to retrieve the value of the environment variable, join it with the name of the file we wish to open and then use the resulting pathname in order to open it.

diary.c should print nothing on stdout. It should only append to $HOME/.diary.

dcc diary.c -o diary
./diary Lisa
cat $HOME/.diary
Lisa
./diary in this house
./diary we obey the laws of thermodynamics
cat $HOME/.diary
Lisa
in this house
we obey the laws of thermodynamics

When you think your program is working, you can use autotest to run some simple automated tests:

1092 autotest diary 

When you are finished working on this exercise, you must submit your work by running give:

give dp1092 lab10_diary diary.c

You must run give before Monday 01 January 00:00 (midnight) (Saturday 01 January 00:00) to obtain the marks for this lab exercise. Note that this is an individual exercise, the work you submit with give must be entirely your own.

Exercise — individual:
mysh

You should now find the following files in the directory:

mysh.ca very very simple shell program
Makefilecompiles the mysh program

Note that, as supplied, the mysh program will not compile. You will need to add some code before this happens.

There are no autotests for this exercise. You must test your programs and be able to demonstrate running your program and be able to explain it to your tutor in the lab this week or next week.

Grade Criteria
(100 marks)Complete, correct, clear, outstanding solution and demonstration
(80 marks)Competent solution to lab - any problems or bugs must be minor
(60 marks)Incomplete solution, or complete with significant defects or obvious bugs
(40 marks)Partial solution only, minimal achievement; or inability to explain solution
. or (0 marks)Not attempted or Trivial Attempt

Background

Every time you use the CSE workstations you interact with the Linux shell (bash). The shell is a program that reads command lines and executes them. Each "command line" contains the name of a program, along with command-line arguments that are passed to this program. Examples:

ls -l           # displays files in current directory
cat -n mysh.c   # show mysh.c with line numbers
file mysh.c     # show the file type of mysh.c
wc -l Makefile  # show how many lines in the Makefile
/bin/ls -l      # displays files in current directory

Note the difference between the first and last commands. They actually produce exactly the same result, but the last command uses the full name of the ls command while the first one doesn't. You don't need to type the full name of each command because the shell keeps, in the PATH environment variable, a list of directories where it should look for executable commands.

To make your task simpler, we have supplied a tokeniser that can be used to break a string into components using a token separator(s). The examples below show the function behaves. You might profit from reading how the function works internally. Note that it uses malloc() and strdup() to create its structures. These need to be freed at an appropriate time to avoid memory leaks.

Exercise

You have several tasks for this lab exercise:

  1. Implement the core part of the body of main()

    This requires the shell to:

    • fork a copy of itself
    • the parent shell process then waits for the child to complete
    • the child shell process tokenises the command line and then calls the execute() function
    • the child should then clean up the args tokens. Note this will only happen if the child does not end up doing a successful excve in the execute function.
    • the parent shell process then prints another prompt
    • At the end of the program before exiting, the parent should clean up the path tokens.

    The arguments to execute() are: the tokenised command line, the array of path directory strings, and the environment array.

  2. Implement the execute() function

    The execute function behaves as follows:

    args = tokenise the command line
    if (args[0] starts with '/' or '.') {
       check if the file called args[0] is executable
       if so, use args[0] as the command
    else {
       for each of the directories D in the path {
          see if an executable file called "D/args[0]" exists
          if it does, use that file name as the command
       }
    }
    if (no executable file found)
       print Command not found message
    else {
       print the full name of the command being executed
       use execve() to attempt to run the command with args and envp
       if doesn't run, perror("Exec failed")
    }
    exit the child process (since execute() is not supposed to return)
    

    You can use the isExecutable() function to check whether a given file name is executable.

    Note that this approach is not used in real shells because there is a race condition where the executable file may have its status changed (made non-executable) or even be removed between you checking its status and then attempting to execute it. There are other functions that you can use (see man 3 exec) to do this more reliably. However, the point of this lab is to let you explicitly see the kind of processes that the shell carries out when executing commands.

When your program is working correctly (and after you've switched DBUG off) it should look like:

./mysh
mysh$ ls -l
Executing /bin/ls
total 28
-rw-r--r-- 1 jas jas   140 Oct  8 11:49 Makefile
-rwxr-xr-x 1 jas jas 12288 Oct  8 11:49 mysh
-rw-r--r-- 1 jas jas  3832 Oct  8 11:48 mysh.c
mysh$ wc -l mysh.c
Executing /usr/bin/wc
148 mysh.c
mysh$ dud command with many args
dud: Command not found
mysh$ cp mysh.c xyz
Executing /bin/cp
mysh$ ls -l
Executing /bin/ls
total 32
-rw-r--r-- 1 jas jas   140 Oct  8 11:49 Makefile
-rwxr-xr-x 1 jas jas 12288 Oct  8 11:49 mysh
-rw-r--r-- 1 jas jas  3832 Oct  8 11:48 mysh.c
-rw-r--r-- 1 jas jas  3832 Oct  8 11:50 xyz
mysh$ ^D
... back to the real shell

Obviously the file sizes, dates and user and group will be different for you.

Note that some commands won't work the same in mysh as they do in the regular shell. Try to find some of these (hint: try cd), discuss with your classmates why the difference exists, and then explain it to your lab demonstrator.

When you think your program is working, you can use autotest to run some simple automated tests:

1092 autotest mysh 

When you are finished working on this exercise, you must submit your work by running give:

give dp1092 lab10_mysh mysh.c

You must run give before Monday 01 January 00:00 (midnight) (Saturday 01 January 00:00) to obtain the marks for this lab exercise. Note that this is an individual exercise, the work you submit with give must be entirely your own.

Exercise — individual:
Compile C Files

You have been given compile_c_files.c, a C program that given the pathname of C source code files compiles them with DCC. For example:
dcc compile_c_files.c -o compile_c_files
ls -1
compile_c_files
compile_c_files.c
file_modes.c
file_sizes.c
./compile_c_files file_sizes.c file_modes.c
running the command: "/usr/local/bin/dcc file_modes.c -o file_modes"
running the command: "/usr/local/bin/dcc file_sizes.c -o file_sizes"
ls -1
compile_c_files
compile_c_files.c
file_modes
file_modes.c
file_sizes
file_sizes.c
file file_modes
file_modes: ELF 64-bit LSB executable # ...

When you think your program is working, you can use autotest to run some simple automated tests:

1092 autotest compile_c_files 

When you are finished working on this exercise, you must submit your work by running give:

give dp1092 lab10_compile_c_files compile_c_files.c

You must run give before Monday 01 January 00:00 (midnight) (Saturday 01 January 00:00) to obtain the marks for this lab exercise. Note that this is an individual exercise, the work you submit with give must be entirely your own.

Challenge Exercise — individual:
Compile C Files If Needed

Copy your code from the last activity into compile_if_needed.c

cp compile_c_file.c compile_if_needed.c

Unfortunately the code you have written so far is inefficient.

It recompiles the C file even if this is not necessary.

Modify compile_if_needed.c so that it only compiles C files if necessary.

A compilation is necessary if the binary doesn't exist.

A compilation is also necessary if the C file has been changed since the last compilation. In other words, if the modification time of the C file is more recent than the modification time of the binary.

Follow the output format below.

./compile_if_needed file_sizes.c file_modes.c
running the command: "/usr/local/bin/dcc file_modes.c -o file_modes"
running the command: "/usr/local/bin/dcc file_sizes.c -o file_sizes"
./compile_if_needed file_sizes.c file_modes.c
file_modes.c does not need compiling
file_sizes.c does not need compiling
rm file_sizes
./compile_if_needed file_sizes.c file_modes.c
file_modes.c does not need compiling
running the command: "/usr/local/bin/dcc file_sizes.c -o file_sizes"
echo >> file_sizes.c # add a new-line to file_sizes.c
./compile_if_needed file_sizes.c file_modes.c
file_modes.c does not need compiling
running the command: "/usr/local/bin/dcc file_sizes.c -o file_sizes"
./compile_if_needed file_sizes.c file_modes.c
file_modes.c does not need compiling
file_sizes.c does not need compiling
./compile_if_needed file_sizes.c file_modes.c
file_modes.c does not need compiling
file_sizes.c does not need compiling

When you think your program is working, you can use autotest to run some simple automated tests:

1092 autotest compile_if_needed 

When you are finished working on this exercise, you must submit your work by running give:

give dp1092 lab10_compile_if_needed compile_if_needed.c

You must run give before Monday 01 January 00:00 (midnight) (Saturday 01 January 00:00) to obtain the marks for this lab exercise. Note that this is an individual exercise, the work you submit with give must be entirely your own.

Submission

When you are finished each exercises make sure you submit your work by running give.

You can run give multiple times. Only your last submission will be marked.

Don't submit any exercises you haven't attempted.

If you are working at home, you may find it more convenient to upload your work via give's web interface.

Remember you have until Saturday 01 January 00:00 to submit your work.

You cannot obtain marks by e-mailing your code to tutors or lecturers.

You check the files you have submitted here.

Automarking will be run by the lecturer several days after the submission deadline, using test cases different to those autotest runs for you. (Hint: do your own testing as well as running autotest.)

After automarking is run by the lecturer you can view your results here. The resulting mark will also be available via give's web interface.

Lab Marks

When all components of a lab are automarked you should be able to view the the marks via give's web interface or by running this command on a CSE machine:

1092 classrun -sturec