
Hello everyone,

Here are the tutorial notes for this week. Remember, feel free to contact me
if you have any questions about the material. (You don't have to email me
after your tutorial this week, but if there are any problems please let me
know about them.)

Reminders
=========

 - Due to the Bahen power outage over the weekend, the deadline for Assignment
   2 has been extended to July 1 (this was announced to the class over a week
   ago).

 - The midterm is scheduled for July 6, from 6:00-7:00pm in SF1105 (the
   lecture room). Please announce this information to your students so that
   they go to the correct room. There will be no tutorial next week (July 6),
   so each of you will have the week off.

 - Also, please announce again these extra office hours to your students: Paul
   will be holding a lab/office hour on June 30 from 6:00-7:00pm in BA3234. I
   will also be extending my office hours on July 5 from 10:00-1:00pm.


Tutorial 6 Notes -- June 29
===========================

(1) Assignment 2

 - There may still be questions about A2, so take some time to answer any that
   arise.

(2) Midterm

 - I've told the class that any material covered during the tutorials is also
   fair game for the midterm. Please spend a few minutes reminding your
   students about some of the main topics you've covered in tutorial:

       - Basic Unix commands
       - Relative and absolute pathnames
       - File permissions
       - Shell scripts in sh (especially quoting, special variables,
         return values/exit status)
       - C programs (especially pointers, memory allocation, arrays and
         strings)

   (You can simply write this list onto the board.) Be prepared for any
   questions about these topics or anything else you've covered.

(3) Yet again, more C examples

 - Last week we completed the section on the C language and we have now
   started into systems programming. This week I'd still like you to present
   two more C examples to your students.

   (a) The first example emphasizes error checking, especially using the
   global error variable "errno" and the function call perror():

       int main(int argc, char *argv[])
       {
          DIR *dirp;

          if ((dirp = opendir(argv[1])) == NULL) {
             if (errno == EACCES) {
                fprintf(stderr, "Permission denied for directory \"%s\"!\n",
                        argv[1]);
                /* process case */
             }
             else if (errno == ENOENT) {
                fprintf(stderr, "Directory \"%s\" doesn't exist!\n",
                        argv[1]);
                /* process case */
             }
             else {
                perror("opendir() error");
                /* otherwise, process general case */
             }
          }
          /* continue with normal operation */
       }

   Here are some points you can make:

       - You should always check the return value of all system/function
         calls!
       - If there is an error, you should either check the value of "errno"
         immediately, or copy its value to another variable. If another
         function call is made before "errno" is checked, the value of "errno"
         may change and the old value will be lost.
       - In the above piece of code, we are checking for two specific types of
         errors that we can then process individually. The third error case
         catches all remaining error types.
       - The manpage for each system/function call lists the possible errors
         that can arise, along with the defined error code values (e.g.,
         EACCESS and ENOENT). The defined error codes should be used in
         conjunction with errno whenever possible, rather than directly
         testing errno for a specific numeric value.
       - perror() will display the English error string associated with the
         current value of errno. The string argument to perror() is displayed
         before the error string. (Note: in the above code we could have used
         perror() for each case, but we have instead used our own error
         messages for the first two types of errors.)

   (b) In the second program we demonstrate the use of fread() with different
   types of arguments, including structs:

      typedef struct st1_def
      {
         char c1;
         char c2;
         char c3;
         char c4;
      } st1;

      typedef struct st2_def
      {
         char d1;
         char d2;
      } st2;

      int main(int argc, char *argv[])
      {
         FILE *fp;
         st1 v1;
         st2 v2[2];
         char v3[4];
         int v4;

         fp = fopen(argv[1], "r");

         fread(&v1, sizeof(st1), 1, fp);
         fread(v2, sizeof(st2), 2, fp);
         fread(v3, sizeof(char), 4, fp);
         fread(&v4, sizeof(int), 1, fp);

         printf("%c %c %c %c\n", v1.c1, v1.c2, v1.c3, v1.c4);
         printf("%c %c %c %c\n", v2[0].d1, v2[0].d2, v2[1].d1, v2[1].d2);
         printf("%c %c %c %c\n", v3[0], v3[1], v3[2], v3[3]);
         printf("%d\n", v4);

         fclose(fp);

         return(0);
      }

    For this example we assume that we have an input file called "infile"
    with the contents:

        wxyzwxyzwxyzwxyz

    When we run the program on this file (e.g., $ prog2 infile) we get the
    output:

        w x y z
        w x y z
        w x y z
        2054781047

    or

        w x y z
        w x y z
        w x y z
        2004384122

    Some points to make:

        - We are defining two structs (struct st1_def and struct st2_def) that
          we have wrapped in new type definitions using "typedef".
        - fread() provides a more generic interface that fgets() and operates
          on bytes (e.g., binary input), rather than simply character input.
        - The first three fread()s achieve the same results when printed, but
          perform different operations: the first fread() reads enough data to
          fill 1 "st1" structure; the second fread() reads enough data to fill
          2 "st2" structures; the third fread() reads enough data to fill 4
          chars.
        - The fourth fread() reads enough data to fill 1 int (e.g., I'm
          assuming 4 bytes here). Note that the value of this variable depends
          on how integers are represented internally, and so we may either
          get the value 2054781047 or 2004384122 when we print out the
          integer v4. (On CDF (x86) we get 2054781047 while on dvp (sun)
          we get 2004384122.)

          The difference in internal representation is "big endian" versus
          "little endian". We haven't talked about these terms in lecture yet,
          but feel free to mention them if you like. The difference usually
          depends on the underlying architecture and how the order of bytes is
          interpreted in integer form (e.g., from high byte to low byte, or
          low byte to high byte).

          For instance, you might simply want to note that

              2054781047 (dec) = 0x7a797877 (hex)

          and

              2004384122 (dec) = 0x7778797a (hex)

          where

              0x77 (hex) = 119 (hex) = char code for 'w'
              0x78 (hex) = 120 (hex) = char code for 'x'
              0x79 (hex) = 121 (hex) = char code for 'y'
              0x7a (hex) = 122 (hex) = char code for 'z'

          (We'll be covering this concept in a future lecture so you can
          stress to your class that it's okay if they don't follow this right
          now.)
        - As a final point, you can mention that since fread() works on binary
          data, we may need to know beforehand how to interpret the data we
          read from a file.

