CMPSC 311, Introduction to Systems Programming

Introduction to Unix
Solutions to the Exercises at the end of the Introduction to Unix page



4.  Larry Wall, author of the Perl programming language, once said "It's easier to port a shell than a shell script."  Explain why this is true; you will need to add some information about the programming languages associated with command interpreters in general.

Solution:

Shell scripts are usually written in a language specific to the shell.  For example, here's a for loop in the C Shell
foreach file ($dir1/*)
if (-d $file) then
echo "Skipping $file (is a directory)"
else
echo "Copying $file"
cp $file $dir2
endif
end
and the same for loop in the Bourne Shell
for file in $dir1/*
do
if [ -d $file ]
then
echo "Skipping $file (is a directory)"
else
echo "Copying $file"
cp $file $dir2
fi
done
Scripts written for the C shell are not syntactically acceptable to the Bourne shell.  And, for various reasons, while the C shell is perfectly ok for interactive use, it's a bad choice for scripts, which tend to use far more complicated commands than people normally type.

The shell programs themselves, though, are usually written in C, which is easily ported to a new machine.

Note.  This early in the course, you probably would not have been able to give a detailed answer like this.  Later in the course, you should see how to fix the bug that is due to "software rot", the problem caused by correct programs becoming incorrect when the environment in which they are used has changed.



5.  The relevant code for this exercise is from APUE Appendix B, Fig. B.3.
Solution:

C uses call-by-value, so the value of errno is copied when it is passed as a argument.



6.  On APUE p. 7 (Sec. 1.4), concerning the sample program Fig 1.3, it is claimed "We don't care what's in the DIR structure.".  But, from the functions opendir() and readdir() it is possible to deduce at least one element of the DIR structure.  Explain why.

Solution:

An object of type DIR must contain position information that is initialized by opendir() and updated by readdir().

Note.  In C, "an object" means "some bytes with a type", and sometimes we don't even care about the type.  In C++, "an object" means "an instance of a class", which gives you more information about the type and lots of additional features.



7.  Use the program in APUE Fig. 1.3 (or the  ls -i  command) and the touch command to demonstrate that the Mac OS X file system is inconsistent about whether filenames are case-sensitive or not.

Solution:

We're going to use the suggested version of the ls command instead of the Fig. 1.3 program, because it's easy to produce less output, and it shows some more information about files.

The touch command can be used to create a file that does not already exist, or to update the modification time of a file that does already exist.  The ls command lists files in a directory, and with the -i option it shows you the "inode number", which is assigned by the file system as the "real name" of the file.  Unix allows you to have more than one name for a file, but only one inode number per file.  In a directory which does not have files named A or a, try this:  (recall that ? is expanded by the shell and matches any file with a one-character name)

ls -i ?
touch a
ls -i ?
touch A
ls -i ?
ls -i a A


On Solaris, you would get  (% is the command prompt, our input is in bold)

% ls -i ?
ls: No match.
% touch a
% ls -i ?
   4748352 a
% touch A
% ls -i ?
   4748464 A      4748352 a
% ls -i a A
   4748464 A      4748352 a

This shows that a and A are different files (because the inode numbers are different) - the file system is case-sensitive.

On Mac OS X, you would get

% ls -i ?
ls: No match.
% touch a
% ls -i ?
948516 a
% touch A
% ls -i ?
948516 a
% ls -i a A
948516 A    948516 a


This shows that a and A are the same file - the file system is case-insensitive.  But, the file system is indeed case-sensitive at some level, since this happens:

% rm a
% ls -i ?
ls: No match.
% touch A
% ls -i ?
948522 A
% touch a
% ls -i ?
948522 A
% ls -i a A
948522 A    948522 a

A shorter example uses the cp command, which copies one file to another.

Solaris
% echo foo > a
% cp a A
% ls -i ?
   3585479 A      4749276 a

Mac OS X
% echo foo > a
% cp a A
cp: A and a are identical (not copied).

In these cases, Linux has the same behavior as Solaris.  Of course, your inode numbers will be different.



8.  strerror() and perror() have no error conditions - they apparently never fail.  Do they ever modify errno?  If so, how could that affect their use?

Solution:

Consider the case where the error number given as an argument (to strerror) or as errno (to perror) is invalid.  perror() will not modify errno, but strerror() will.  Now suppose you are logging error reports, as well as printing them on the console.  Your code might look like

fprintf(stderr, "error: %s\n", strerror(errno));
fprintf(logfil, "error: %s\n", strerror(errno));

If errno was invalid before the first call to strerror(), it will be set to EINVAL (which is a valid error number, indicating an error due to an invalid argument) after the first call.  The logfile is then confused about which error had originally occured.  The right way to do this is

int err = errno;
fprintf(stderr, "error: %s\n", strerror(err));
fprintf(logfil, "error: %s\n", strerror(err));

Now, you could argue that the program is wrong somewhere else, since errno was originally invalid, but that's beside the point - we wound up with an inaccurate error message in the logfile.



Last revised, 11 Jan. 2013