Project 1: Warm-up Project

Important Dates

Questions about the project? Send them to 537-help@cs.wisc.edu (and not just to remzi; this way, remzi OR the TAs can see it).

Due: Monday, 02/01, by 9pm.

Clarifications

01/23: See Assumptions and Errors below for a clarification on what to do when the input files are the same.

Notes

Before beginning: Read Lab 0: Tutorial. It has some useful tips for programming in the C environment.

This project must be done alone. You can talk to your colleagues about it, but every line of code must be written and understood by you. Of course, you can always ask the TAs and professors for help too.

Overview

The first project is simply a warm-up to get you used to how this whole project thing will go. It also serves to get you into the mindset of a C programmer, something you will become quite familiar with over the next few months. Good luck!

You will write a simple program called reverse . This program should be invoked in one of the following ways:

shell% ./reverse
shell% ./reverse input.txt
shell% ./reverse input.txt output.txt

The above line means the users typed in the name of the reversing program reverse (the ./ in front of it simply refers to the current directory (.) and the slash is a separator; thus, in this directory, look for a program named reverse ) and gave it either no command-line arguments, one command-line argument (an input file, input.txt ), or two command-line arguments (an input file and an output file output.txt ).

An input file might look like this:

hello
this
is
a file

The goal of the reversing program is to read in the data from the specified input file and reverse it; thus, the lines should be printed out in the reverse order of the input stream. Thus, for the aforementioned example, the output should be:

a file
is
this
hello

The different ways to invoke the file (as above) all correspond to slightly different ways of using this simple new Unix utility. For example, when invoked with two command-line arguments, the program should read from the input file the user supplies and write the reversed version of said file to the output file the user supplies.

When invoked with just one command-line argument, the user supplies the input file, but the file should be printed to the screen. In Unix, printing to the screen is the same as writing to a special file known as “standard output”, or stdout for short.

Finally, when invoked without any arguments, your reversing program should read from “standard input” ( stdin ), which is the input that a user types in, and write to standard output (i.e., the screen).

Sounds easy, right? It should. But there are a few details...

Details

Assumptions and Errors

Input is the same as output: If the input file and output file are the same file, you should print out an error message “Input and output file must differ” and exit with return code 1.
String length: You may not assume anything about how long a line should be. Thus, you may have to read in a very long input line...
File length: You may not assume anything about the length of the file, i.e., it may be VERY long.
Invalid files: If the user specifies an input file or output file, and for some reason, when you try to open said file (e.g., input.txt ) and fail, you should print out the following exact error message: Error: Cannot open file 'input.txt' and then exit with return code 1 (i.e., call exit(1); ).
Malloc fails: If you call malloc() to allocate some memory, and malloc fails, you should print the error message Malloc failed and exit with return code 1.
Too many arguments passed to program: If the user runs reverse with too many arguments, print Usage: reverse <input> <output> and exit with return code 1.
How to print error messages: On any error, you should print the error to the screen using fprintf() , and send the error message to stderr (standard error) and not stdout (standard output). This is accomplished in your C code as follows:
fprintf(stderr, “whatever the error message is\n”);

Useful Routines

To exit, call exit(1) . The number you pass to exit(), in this case 1, is then available to the user to see if the program returned an error (i.e., return a non-zero) or exited cleanly (i.e., returned 0).

For reading in the input file, the following routines will make your life easy: fopen(), fgets(), fprintf(), and fclose().

Note that fgets() can already read from standard input; all you have to do is pass stdin as the file stream (third parameter). Similarly, it is easy to write to standard output by passing stdout to fprintf() as its first parameter (or to fwrite() as its last parameter).

The routine malloc() is useful for memory allocation. Perhaps for adding elements to a list?

If you don't know how to use these functions, use the man pages. For example, typing man malloc at the command line will give you a lot of information on malloc.

Other Tips

Start small, and get things working incrementally. For example, first get a program that simply reads in the input file, one line at a time, and prints out what it reads in. Then, slowly add features and test them as you go.

For example, the way I wrote this code was first to write some code that used fopen() , fgets() , and fclose() to read an input file and print it out. Then, I wrote code to store each input line into a linked list and made sure that worked. Then, I printed out the list in reverse order. Then I made sure to handle error cases. And so forth...

Testing is critical. A great programmer I once knew said you have to write 5-10 lines of test code for every line of code you produce; testing your code to make sure it works is crucial. Write tests to see if your code handles all the cases you think it should. Be as comprehensive as you can be. Of course, when grading your projects, we will be. Thus, it is better if you find your bugs first, before we do.

Keep old versions around. Keep copies of older versions of your program around, as you may introduce bugs and not be able to easily undo them. A simple way to do this is to keep copies around, by explicitly making copies of the file at various points during development. For example, let's say you get a simple version of reverse.c working (say, that just reads in the file); type cp reverse.c reverse.v1.c to make a copy into the file reverse.v1.c . More sophisticated developers use version control systems like CVS or SVN, but we'll not get into that here (yet).

Keep your source code in a private directory. An easy way to do this is to log into your account and first change directories into private/ and then make a directory therein (say p1 , by typing mkdir p1 after you've typed cd private/ to change into the private directory). However, you can always check who can read the contents of your AFS directory by using the fs command. For example, by typing in fs listacl . you will see who can access files in your current directory. If you see that system:anyuser can read (r) files, your directory contents are readable by anybody. To fix this, you would type fs setacl . system:anyuser “” in the directory you wish to make private. The dot “.” referred to in both of these examples is just shorthand for the current working directory.

Handing It In

You should turn in FOUR files. The first, containing your code, should be called reverse.c . We will compile it in the following way:

shell% gcc -Wall -o reverse reverse.c -O
so make sure it compiles in such a manner.

You should also include a file called README which includes any notes on your program that you think are important.

Third, you should include a fact page about yourself, called about.html . Use this template here to fill in your name, major, standing in school (e.g., Junior, 1st year grad, etc.), and, most importantly, a link to a CLEAR picture of yourself.

Finally, you should include said picture of yourself. Thus, if someone points a browser to the file about.html , they should see your picture and the info about you on it.

You should copy these files into your handin directory. These will be located in ~cs537-1/handin/login/p1 where login is your login. For example, Remzi's login is remzi , and thus he would copy his beautiful code, README, etc., into ~cs537-1/handin/remzi/p1 . Copying of these files is accomplished with the cp program, as follows:

shell% cp reverse.c ~cs537-1/handin/remzi/p1/
shell% cp README ~cs537-1/handin/remzi/p1/
shell% cp about.html ~cs537-1/handin/remzi/p1/
shell% cp pic.jpg ~cs537-1/handin/remzi/p1/
or more succinctly:
shell% cp reverse.c README about.html pic.jpg ~cs537-1/handin/remzi/p1/
(the copy utility knows that if the last thing specified is a directory, it should just copy the files into that directory and give them the same names. Read the man page for cp for more details.)