Assignment 3, due Feb. 3

Part of the homework for 22C:112, Spring 2012
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

  1. Background: The launch() routine in the mush.c Minimally Usable SHell simply gives up with the no such command error message if indicated file cannot be run by the exec command. In all of the more useful shells, there is a concept of "search path" whereby the shell appends the command name to a series of directory names, trying to open it from each directory in sequence, and only gives up if the command is found in none of those directories.

    The C standard library provides the strncpy() and strncat() routine to copy and concatenate strings. So, you can write: write:

            char filename[100];
            filename[100] = '\000';
            strncpy( filename, "/bin/", 99 );
            strncat( filename, argv[0], 99 );

    With this code, if argv[0] was "echo", filename will end up being "/bin/echo".

    a) Why set filename[100] to NUL and then why limit the copied and concatenated strings to 99 characters? This is purely a question about C. (0.5 points)

    First, there are some bugs here. In the call to strncat(). The call should have been:

            strncat( filename, argv[0], 99-strlen(filename));

    This is because the limit on strncat is not the buffer size, but the maximum amount by which the length of the string may be extended.

    The second bug is that the assignment to filename[100] should have been an assignment to filename[99]. These were my errors, so you are not required to have found them.

    Now, to the issue: In a 100 character buffer, one must be reserved to hold the trailing null. If strncpy() or strncat() are asked to copy too many characters, they simply truncate the string, and it is not clear from the documentation that they are guaranteed to put a null at the end.

    b) Write a new version of launch() that has a hard-coded search path, trying these directories in sequence: /bin/ and then /usr/bin/, and then ./ (the current directory). Note that this can be done by changing one line and adding well under 15 lines of new code to the original. (1.0 points)

    void launch(){
            if (fork() == 0) { /*child*/
                    char buf[100];
                    buf[99] = '\000';
    		strcpy(buf, "/bin/");
    		strncat(buf, argv[0], 99-strlen(buf));
                    execve(buf, argv, NULL);
    		strcpy(buf, "/usr/bin/");
    		strncat(buf, argv[0], 99-strlen(buf));
                    execve(buf, argv, NULL);
    		strcpy(buf, "./");
    		strncat(buf, argv[0], 99-strlen(buf));
                    execve(buf, argv, NULL);
                    printf("no such command\n");
            } else { /*parent*/
                    wait( NULL );
  2. Background: Single-user systems such as early MS-DOS, the operating system on the Apple ][, and early minicomputer systems such as OS-8 and RT-11 had a very simple memory model. Consider the OS-8 system as described by Wikipedia. This system could run on the PDP-8 computer with as little as 8K 12-bit words of main memory, so long as there was at least one mass storage device such as a disk or tape.

    a) What was the most interesting feature linking the OS-8 command-language interpreter to its I/O drivers (including support for a rudimentary file system)? (0.5 points)

    A rudimentary I/O redirection mechanism using the ASSIGN directive to the command line intrpreter.

    b) Did OS/8 impose any discipline on the user's use of memory? Contrast this with the rudimentary system described in the lecture notes for Jan. 30. (0.5 points)

    The system "owned" the top 128 words of fields 0 and 1 of memory. Aside from that, it imposed nothing on applications.

  3. Background: In all but the earliest versions of Unix and its descendants, a memory management unit is used to give each application its own memory address space. Thus, every application program can be written to start at the same address without any conflict. Despite this, Unix (and later) operating systems all support relocating linkers.

    A Question: Why is relocation still valuable? (0.5 points)

    Relocation may no longer be needed at load time, but it is still needed to allow linking of separately compiled routines.

Machine Problem II

Due Monday, Feb. 13.

Modify the mush.c Minimally Usable SHell so that it uses the search path found in the environment variable PATH. When running in any of the standard shells, you can examine this using the shell command echo $PATH.

The value of the variable PATH is a string consisting of a number of substrings delimited by colons. A typical but unusually short path might look like this:


Assuming that the user types in cmd as a shell command, the above path means that the shell should try to exec each of the following programs, in sequence, until one of them worked:


Only if none of the above can be exec'd should the shell give up and report an error message indicating that the command is undefined.

To implement this, your modified version of mush must call getenv("PATH") to get the value of the environment variable. Note that this returns a pointer to the string in the environment data structure, it does not make a working copy. You can look up the documentation for getenv() (and all other library routines) using the man command. For example, try the man getenv shell command

You will probably want to make a working copy of the path, using strcpy() or strncpy() into an appropriate sized buffer. A safe way to allocate a buffer large enough is to use malloc() to allocate a buffer the right size for the path, where a call to strlen() was used to find the length of the string holding the path. The following is a safe way to make a working copy of the path, assuming appropriate declarations:

    workpath = strcpy( malloc( strlen(path)+1, path );
Once you have a working copy, you can chop it into substrings using logic similar to that used by parseargv() in mush.

Finally, you should modify the launch() routine in mush so that it tries to exec each of the components in the path, in turn. To do this, it will have to concatenate each component with a / character, and the value of argv[0]. The strncat() or strcat() library routine can be used for this does this. Note that these concatenation routines do not allocate a new buffer, so if you want to concatenate, you must first make sure that you have a buffer big enough to hold the result.

Note that your solution will be graded on two different issues:

You are not responsible for improving the (awful) quality of the parts of mush you have been given, but you must make sure that all of the code you add is written very well. The programming guidelines in the Manual of style for C programmers should be followed in all code you add, and in modifying the program header to reflect changes you have made.

Submit your solution using the coursework submission tools documented at:


In short, you will type the shell command submit, with no arguments, and when it prompts for a file name, you will type mush.c, and then an extra carriage return to indicate that there is only one file in your submission. When it prompts for a course, type c_112 and finally, when it prompts for an assignment directory, enter mp2.