CS 537 Spring 2008 Section 1: Programming Homework 0


Due: Tuesday, January 27th at 9 pm
You are to do this project BY YOURSELF
This project must be implemented in C (and not C++ or anything else)

Notes:

Monday, January 26

Hint 2: you can assume that the input is a well-formed text file, meaning that it contains no null characters.

Thursday, January 22

Hint 1: here is sample code from this web site showing how to allocate a two dimensional array:
int **array1 = (int **)malloc(nrows * sizeof(int *));
	for(i = 0; i < nrows; i++)
		array1[i] = (int *)malloc(ncolumns * sizeof(int));

array1[1][2] = 0;

Hint 2 (student generated): it may be easier to test your code for correctness by reducing the maximum length of the file and length of the line. Once that works, you can experiment on larger files.

Purpose

The purpose of this assignment is to get familiar with the C programming language, the gcc compiler, and the gdb debugger.

Part 0: a picture

We want to get to know you better, and what better way than to be able to associate a name with a face? You need to turn in a digital picture of yourself so I can learn who you are. Put this in your handin directory (a jpg or a gif is fine), in the form Firstname.Lastname.jpg (or whatever). If you don't turn in a picture, your project will not be graded!

Part 1: Writing C code

You will write a program to read in a text file and print the lines in reverse order (last line first, first line last) and also print out each line in reverse order (last non-newline character first, first character last).

Specification:

Program input: read a file specified on the command line:

  reverse-lines filename.txt 
Program output: every line of the file, in reverse order.

Note that the lines of the file may be very short (blank) or very long (up to hundreds of characters).

This input:

<!--#include file="base-head.html" -->
<title>CS 537: Operating Systems</title>
<!--#include file="base-top.html" -->

<h2>Overview</h2>

<p> Welcome to your first the Wisconsin Operating Systems course. This
course will describe a number of topicsincluding basic operating
system structure, process and thread synchronization and concurrency,
file systems and storage servers, memory management techniques,
process scheduling and resource management, system security, and a few
other "hot" topics.

should produce this output:

.scipot "toh" rehto
wef a dna ,ytiruces metsys ,tnemeganam ecruoser dna gniludehcs ssecorp
,seuqinhcet tnemeganam yromem ,srevres egarots dna smetsys elif
,ycnerrucnoc dna noitazinorhcnys daerht dna ssecorp ,erutcurts metsys
gnitarepo cisab gnidulcniscipot fo rebmun a ebircsed lliw esruoc
sihT .esruoc smetsyS gnitarepO nisnocsiW eht tsrif ruoy ot emocleW >p<

>2h/<weivrevO>2h<

>-- "lmth.pot-esab"=elif edulcni#--!<
>eltit/<smetsyS gnitarepO :735 SC>eltit<
>-- "lmth.daeh-esab"=elif edulcni#--!<

Assumptions

String length: You may assume no line in the input file is longer than 512 bytes. If you encounter a line that is too long, you should print error message LINE_TOO_LONG (as detailed below) and skip the rest of this line.

Let's say the max size of an input line is 8 characters (and not 512). Which of these lines should be in the final output?

abcdef
abcdefg
abcdefgh
The first seemingly has 6 characters (abcdef), the second has 7, and the third 8, and thus you might naively think all should be accepted. However, you are forgetting the newline character (\n) which is at the end of each input line. Thus, the first two should be accepted (as they have 7 and 8 characters including the newline). For the third line, you should only accept 'abcdefg' and put a \n, and skip the rest of the line, and don't forget to print the LINE_TOO_LONG message.

File length: You may assume the input file has 1024 valid lines (not too long) of input or fewer. If you encounter more lines, print the FILE_TOO_LONG error message once (as detailed below) and reverse whatever input you currently have read in.

Error Messages

All error messages encountered while reading the input file should be of this format:

Error in line XXX: specific error message
where the specific error messages are:
FILE_TOO_LONG: You should print the following message: File too long
LINE_TOO_LONG: You should print the following message: Line too long
INVALID_FILE: If the user specifies exactly one file (as desired) but it can't be opened (for whatever reason), you should print the following message:
Error: Cannot open file FILE
where FILE is what the user passed in.

If you encounter a line that has both of these errors, you should print both error messages.

There are some other possible errors too. For example, if the user doesn't properly specify the input file on the command line (by say, not giving one, or by giving too many files), you should print:

usage: reverse <file>
and then exit.

Important: On any error code, 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, “usage: ... ”);

Implementation tips:

To do this, use the C standard I/O "f" functions:
  • fgetc, fputc
  • fgets, fgets
  • fscanf, fprintf
  • fopen, fclose
Documentation for these functions are available through the Linux man pages:

emperor01(1)% man fgets 

GETS(3)                 BSD Library Functions Manual
FGETS(3) 

NAME
     fgets, gets -- get a line from a stream

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include <stdio.h>

     char *
     fgets(char *restrict s, int n, FILE *restrict stream);

     char *
     gets(char *s);

To invoke the compiler, run the command line:

emperor01(1) gcc -g -o programname filename.c

This will compile the file "filename.c" and produce the output program "filename" that you can then run with the command "./filename" (the "./" is to tell Linux in which directory to find the program. The "-g" flag indicates that the compiler should produce extra data to enable source-level debugging, and the "-o" options specifies to put the program in a specific file as compared to the standard name "a.out".

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.

Testing is critical. One 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 myreverse.c working (say, that just reads in the file); type cp myreverse.c myreverse.v1.c to make a copy into the file myreverse.v1.c . More sophisticated developers use version control systems like CVS , 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.

Part 2: Debugging C code

The gdb debugger allows you to stop your program at any point and investigate program variables.

Specification:

Start your program under the debugger. Put a breakpoint on your main loop reading a line from the file. Single step through input of a single line. Print out the values of at least two different variables. Submit a transcript of your debugging session.

Implementation tips:

You start gdb by invoking it with the program name:

emperor01(5)% gdb programname
GNU gdb Red Hat Linux (6.5-37.el5_2.2rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host
libthread_db library "/lib/libthread_db.so.1".
(gdb) 
Start running your program with the "run" command any input or output redirection:
(gdb) run < filename.c 
You can set breakpoints on a function with the "break" command and pass it either a function name or a file and line number:
(gdb) break main
Breakpoint 1 at 0x8048419: file filename.c, line 8.
(gdb) break filename.c:11
Breakpoint 3 at 0x804841e: file filename.c, line 11.
(gdb) 
You can print variables with the "print" command:
gdb) print stdin 
$3 = (struct _IO_FILE *) 0x2ae420 
(gdb)  
You can list code with the "list" command, which takes a function name or a filename:linenumber (like break):
(gdb) list main
1	#include <stdio.h> 
2	#include <string.h> 
3	
4	int main(int argc, char * argv[]) 
5	{
6	  char buffer[200]; 
7	  char * tmp; 
8	  while (!feof(stdin)) { 
9	    int len; 
10	    int i; 
(gdb)

After execution stops at a breakpoint, you can continue exection with the "cont" command:

(gdb) run < filename.c

Starting program: /afs/cs.wisc.edu/u/s/w/swift/tmp/programname <
filename.c 

Breakpoint 1, main () at filename.c:8 
8	  while (!feof(stdin)) {
(gdb) cont
Continuing.
One execution stops, you can step forward one source code line at a time with the "next" command (abbreviated "n"), which jumps over function calls, or the "step" command (abbreviated "s"), which goes into the function and stops:
Breakpoint 1, main () at filename.c:12 
12	  while (!feof(stdin)) { 
(gdb) next 
15	    tmp = fgets(buffer, 200, stdin);
(gdb) next 
16	    if (tmp == 0){ 
(gdb) next 
19	    len = strlen(tmp);
(gdb) next
20	    for (i = len-2; i >= 0; i--) {
(gdb) next
21	      printf("%c",myfunction(tmp[i]));
(gdb) step
myfunction (c=62 '>') at filename.c:5
5	  return((int)c);
(gdb)

You can quit gdb with the "quit" command.

What to turn in:

  1. A digital picture of yourself
  2. Please turn in your source code as a single file named "assignment0.c" (there is no need for multiple files or a makefile for this assignment)
  3. Please turn in a transcript from your debugging session as a text file name "debug-trace.txt"
Please put all files in the directory: ~cs537-1/handin/<your-login>/p0

What we will look for:

  • Does the code work on simple files?
  • Does the code work on more complex files (zero-length lines, very long lines)
  • Did you get the debugger to work?
  • Did you turn in a picture?