2024 CS360 Midterm Exam - Answers and Grading

February 29, 2024


Question 3 - 6 Points

pebbles, luigi, scooby, harvey, and dontonio: The answer is 24. In order to make everything align on multiples of their sizes, you need the following layout: benton, pluto, peach, bambam, and fred: The answer is 8. In order to make everything align on multiples of their sizes, you need the following layout: bruno, bluey, babydaisy, mario, and wilma: The answer is 8. In order to make everything align on multiples of their sizes, you need the following layout:

Grading

Each part was 2 points. On the questions where the answer was 8, you got 0.5 points for 5. On the questions where the answer was 24, you got 0.5 points for 20 and 28.


Question 4 - 14 Points

This is the answer to the example exam question. Here is a grid of correct answers, keyed on the first five characters of s:

DLTGB
1-ABCDLFG
2-HIJWMPK
3-
4-IHX
5-C
6-MPK
7-LTGBOPQR
JBASG
1-ABCJBFG
2-HIJNTYD
3-
4-IQC
5-W
6-TYD
7-BASGOPQR
KYFGU
1-ABCKYFG
2-HIJALXM
3-
4-IWZ
5-O
6-LXM
7-YFGUOPQR
SPMBI
1-ABCSPFG
2-HIJCNFT
3-
4-LDO
5-U
6-NFT
7-PMBIOPQR
TPGHC
1-ABCTPFG
2-HIJIUER
3-
4-XLV
5-S
6-UER
7-PGHCOPQR

Grading

Two points per part.


Question 6 - 20 points

Here are the 16 bytes, in both big and little endian, after the first for loop.

               Little          Big
r,   s       0x374a9a7c    7c 9a 4a 39
r+1, s+4     0x485bab8d    8d ab 5b 4a
r+2, s+8     0x596cbc9e    9e bc 6c 5b
r+3, s+12    0x6a7dcdaf    af cd 7d 6c

The strcpy() changes five bytes to 0x42, 0x45, 0x41, 0x44 and 0x00. The easiest thing to do is to change the big-endian, and then copy those changes to the little endian. I've colored that in red:

               Little          Big
r,   s       0x374a9a7c    7c 9a 4a 39
r+1, s+4     0x44414542    42 45 41 44
r+2, s+8     0x596cbc00    00 bc 6c 5b
r+3, s+12    0x6a7dcdaf    af cd 7d 6c

In lines 1-4, you print the little-endian, and in lines 5-9, you use the big endian to find the correct byte to print. These answers:

Line 1: 0x374a9a7c
Line 2: 0x44414542
Line 3: 0x596cbc00
Line 4: 0x6a7dcdaf
Line 5: 0x7c
Line 6: 0xab
Line 7: 0x6c
Line 8: 0c6c

Now, for line 9, you copy the four bytes starting at s+10 to r. The easiest way to do that is to use the big-endian representation, and then copy the changed bytes to the little endian. That changes the bytes to the following. I've colored the bytes that I copied in blue, and the bytes that have changed in red:

               Little          Big
r,   s       0xcdaf5b6c    6c 5b af cd
r+1, s+4     0x485bab8d    8d ab 5b 4a
r+2, s+8     0x596cbc9e    9e bc 6c 5b
r+3, s+12    0x6a7dcdaf    af cd 7d 6c

The output is the little endian: 0xcdaf5b6c.

In the last line, I set x to be 20 bytes after r. Since that is five integers, the answer is 5. Below is a grid of correct answers keyed on the value of num:

374a9a7c
0x374a9a7c
0x44414542
0x596cbc00
0x6a7dcdaf
0x7c
0x45
0x6c
0x6a
0xcdaf596c
5
3b49583a
0x3b49583a
0x44414542
0x5d6b7a00
0x6e7c8b6d
0x3a
0x45
0x6b
0x6e
0x8b6d5d6b
5
589737b7
0x589737b7
0x44414542
0x7ab95900
0x8bca6aea
0xb7
0x45
0xb9
0x8b
0x6aea7ab9
5
6c3829a8
0x6c3829a8
0x44414542
0x8e5a4b00
0x9f6b5cdb
0xa8
0x45
0x5a
0x9f
0x5cdb8e5a
5
9a7a87a9
0x9a7a87a9
0x44414542
0xbc9ca900
0xcdadbadc
0xa9
0x45
0x9c
0xcd
0xbadcbc9c
5

Grading

2 points per question.


Question 8

Although the pointers were different from question to question, the last bytes were all the same, so the answers were the same for each question.

Grading

2 points per line. I gave you half credit if you gave me the last byte for the address rather than the value. (For example, you gave me 0xe8 in Line 1, instead of 0x0c).


Question 10

The ln command adds a name/inode binding to dir1: It has added the name f3.txt and bound it to the same inode as ./f1.txt.

This also changes the modification time of the inode for dir1. It may change the size too, but sometimes not.

The echo command has added the line "fred" to f1.txt. It also changes the size and modification time of f1.txt's inode. Although the new line will be reflected in dir/f3.txt, we didn't add lines to two files -- just to one file that is linked to two names.


Question 11

I thought this would be a straightforward question. You need to: You have choices when it comes to how to organize "combination of size and filename" in the red-black tree:
  1. Create a data structure that has the size and name for each file, and insert it into the tree with jrb_insert_gen().

  2. Make a two-level red-black tree, where the first level is sorted by the file's size, and the second level is sorted by the file's name. The nice thing about this solution is that you can traverse the first tree in reverse, and then traverse the second tree normally.

  3. Create a single string from each file, where sorting the string allows you to print the files in the correct order. This is more of a pain than you might think, because you have to sort the sizes in reverse. Something like:

    key = (char *) malloc(15+strlen(de->d_name));
    sprintf(key, " %010d %s", -((int) buf.st_size), de->d_name);
    

In my solution, I chose the two-level tree:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include "jrb.h"

int main()
{
  DIR *d;
  struct dirent *de;
  JRB t1, tmp1;
  JRB t2, tmp2;
  struct stat buf;

  /* Open the current directory and make the main red-black tree. */

  d = opendir(".");
  if (d == NULL) { perror("."); exit(1); }
  t1 = make_jrb();

  /* Traverse the directory and get the size of each file in the stat buf. */

  for (de = readdir(d); de != NULL; de = readdir(d)) {
    if (stat(de->d_name, &buf) != 0) { perror(de->d_name); exit(1); }

    /* Look up the file size in the main tree.  If it's not there, make an
       entry for it with an empty red-black tree as the val. */

    tmp1 = jrb_find_int(t1, buf.st_size);
    if (tmp1 == NULL) {
      tmp1 = jrb_insert_int(t1, buf.st_size, new_jval_v((void *) make_jrb()));
    }

    /* Set t2 to the red-black tree in the val and insert the filename there.
       You have to strdup() this because opendir() might overwrite the dirent
       pointer. */

    t2 = (JRB) tmp1->val.v;
    jrb_insert_str(t2, strdup(de->d_name), new_jval_i(0));
  }
  /* It's good form to close the directory here, since you're done with it. */

  closedir(d);

  /* Perform a nested traversal and print out the file names in the second-level trees. */

  jrb_rtraverse(tmp1, t1) {
    t2 = (JRB) tmp1->val.v;
    jrb_traverse(tmp2, t2) printf("%s\n", tmp2->key.s);
  }

  /* If we weren't returning and killing the process, we'd have to free memory.  However,
     since the process is exiting, we can simply let the operating system clean up
     the memory. */

  return 0;

}

Grading

I split the grading into 6 parts:
  1. Getting the opendir() and readdir() calls right: 3 points
  2. Getting the stat() call right: 2 points
  3. Organizing your solution correctly (one of the three above): 3 points
  4. Getting details correct while creating your data structure: 3 points
  5. Getting details correct while printing your data structure: 3 points
  6. Memory management: 2 points.
Some random comments:

Please Read This for Future Exams

First piece of advice: Please read the question and look at the example. In particular: Second, if you have to sort things, you cannot use an n-squared data structure or algorithm. Similarly, if you have to do storage and lookup, you also cannot use an n-squared data structure or algorithm. This is regardless of your language. So, if you're uncomfortable with maps and unordered_maps in C++, dictionaries in Python, red-black trees in CS360, or whatever the proper data structures are in your other languages of choice, then you need to put some time in so that you are comfortable with them. Using them properly should be like breathing. On this question, it was exceptionally disheartening for me to see dllists in your implementation and O(n2) loops.


Question 12

The problem with this implementation is the write() call, which writes 16 bytes at a time. Since write() is a system call, it is slow, and you speed this program up drastically by adding a buffer to Logfile, and then only performing write() when the bufer is full, and when you close the logfile.

Of course, if you had access to the C stdio library, you'd just call fopen(), fclose() and fwrite(), and call it a day. But you don't have that luxury.

I fully intended for you to copy/paste the "bad" implementation into your answer and then update it. That's what I did below -- there is a comment by each of my changes:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "logfile.h"

typedef struct {
  int fd;
  char buffer[16*256];            /* I add a 4K buffer. */
  int nfull;                      /* This says how many entries there are: [0-256] */
} Logfile;

void *open_logfile(const char *filename)
{
  int fd;
  Logfile *l;

  fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
  if (fd < 0) return NULL;

  l = (Logfile *) malloc(sizeof(Logfile));   /* Allocate the buffer. */
  l->fd = fd;
  l->nfull = 0;                              /* Set the entries to zero. */
  return (void *) l;
}

void write_logentry(void *logfile, void *logentry)
{
  Logfile *l;

  l = (Logfile *) logfile;
  if (l->nfull == 256) {                    /* Flush the buffer and reset nfull if the */
    write(l->fd, l->buffer, 16*256);        /* buffer is full. */
    l->nfull = 0;
  }
  memcpy(l->buffer+(l->nfull * 16), logentry, 16);   /* Copy the entry to the buffer. */
  l->nfull++;                                        /* Increment the number of entries. */
}

void close_logfile(void *logfile)
{
  Logfile *l;

  l = (Logfile *) logfile;
  if (l->nfull != 0) write(l->fd, l->buffer, l->nfull*16);  /* Do a final flush if necessary. */
  close(l->fd);
  free(l);                                  /* Remember to free the buffer. */
}

I used the following rubric as a guide, but often simply assigned a grade after seeing how you approached the problem, since your approach often didn't fit the rubric exactly.