Question 1

Part A: Write() is a system call, which is expensive because it involves invoking the operating system. It is much slower than a normal procedure call. For example, while a procedure call may take ten instructions, or a couple of nanoseconds on a fast machine, a system call may take on the order of 50 milliseconds. Thus, doing 2,000,000 write() calls, will take 100 seconds.

Part B: To fix this, you need to add buffering. Use memory to combine all of those little writes into a few big writes. For example, the following procedure will write 6000 bytes at a time, rather than 4 and 8:

int write_double_array(int fd, int *ids, double *vals, int n) 
{
  char *buffer;
  int sid, i, j;

  sid = sizeof(int)*sizeof(double);
  buffer = (char ) malloc(sid*500);

  for (i = 0; i < n; i += 500) {
    for (j = 0; j < 500 && j + i < n; j++) {
      memcpy(buffer+j*sid, &ids[500*i+j], sizeof(int));
      memcpy(buffer+j*sid+sizeof(int), &vals[500*i+j], sizeof(double));
    }
    write(fd, buffer, j*sid);
  }
}

Part C: There are three acceptable answers. I've given them names so that you can see which one you gave in my grading file.

Grading

Part A is 4 points.

Part B is 8 points. Your program was given a base value, which was one of the following:

Then these could be modified by the following:

Part C 3 points. Each acceptable answer was worth 1.5 points. The following are unacceptable answers:


Question 2

An atomic action is a sequence of actions that are executed without interruption. The example in this test question is creating a file only if it does not exist already. That is the purview of the O_EXCL flag. When you open a file with the flags O_WRONLY | O_CREAT | O_EXCL, that specifies that the open operation should only be successful if the file does not already exist. Without the O_EXCL flag, you'd have to do two operations: checking to see if a file exists, and then creating it, and if those operations are not executed atomically, then it is possible for someone to create the file between your two operations, compromising your activity. Thus, the O_EXCL allows the operating system to perform the check and the creation atomically.


Question 3

This is a straightforward directory traversal, where you maintain a red-black trey keyed on inode number:

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

int find_distinct_files(char *directory)
{
  char *name;
  DIR *d;
  struct dirent *de;
  JRB t, tmp;
  struct stat buf;
  int nf;

  name = (char *) malloc(sizeof(char)*(258 + strlen(directory)));
  t = make_jrb();

  nf = 0;
  d = opendir(directory);
  if (d == NULL) { perror(directory); exit(1); }
  for (de = readdir(d); de != NULL; de = readdir(d)) {
    sprintf(name, "%s/%s", directory, de->d_name);
    if (stat(name, &buf) != 0) { perror(name); exit(1); }
    tmp = jrb_find_int(t, buf.st_ino);
    if (tmp == NULL) {
      nf++;
      jrb_insert_int(t, buf.st_ino, new_jval_i(0));
    }
  }
  jrb_free_tree(t);
  free(name);
  closedir(d);
  return nf;
}

If your structure was right (directory traversal with red-black tree), you started with 10 points. If you didn't use the tree, and just counted directory entries, you started with four. If you didn't construct filenames for your stat call, you lost two points. Here are the keys to your grade files:


Question 4

Without the setuid bit, I need to give other users permission to write the file. Otherwise, if they run the astring program, the open call will fail because they don't have write permission. Giving other users write permission is dangerous, because then they can do anything to the file, for example running vi on it and deleting everything.

To use the setuid bit, I will set the program's permission to 04755, which means that when they run it, their effective user id becomes my user id, giving them my permissions for the duration of the program. This allows me to set the permissions on the file so that only I have write permission -- the only way that other users can modify the file is to call astring on it. This is a better alternative than giving others write permission because I don't give up control of the file.

Grading Keys:


Question 5

With the first file, whose contents are "ABCD\n", the read will return 5, and the buffer will contain "ABCD\n6789\0". The open call will be successful, so fd will be three. Thus:
UNIX> readfile f1.txt
3 5 ABCD
6789
UNIX>
F1.txt and f2.txt are hard links to each other, so the second call will be the same as the first:
UNIX> readfile f2.txt
3 5 ABCD
6789
UNIX>
F3.txt and f4.txt are hard links to each other, with unreadable permissions. Thus, the open call will return -1, and the read call will fail, returning -1 as well. The buffer is unchanged by the read call:
UNIX> readfile f3.txt
-1 -1 123456789
UNIX> readfile f4.txt
-1 -1 123456789
UNIX> 
F5.txt contains "ABCDEFGHIJKLMN\n", so the read call will read in the first ten bytes. That overwrites the null character which was at buffer[9], so the printf statement will print out "ABCDEFGHIJ", and then whatever characters follow that in memory. If we're lucky, the first one is a null byte, and the printf statement stops. So the answer is:
UNIX> readfile f5.txt
3 10 ABCDEFGHIJ      -- plus potentially other stuff.
UNIX>
F6.txt does not exist, so the open call will return -1. The output will be the same as with f3.txt and f4.txt. Similarly, argv[1] is undefined with the last readfile command, so the open call will fail and the output will be the same as with f6.txt.
UNIX> readfile f6.txt
-1 -1 123456789
UNIX> readfile < f1.txt
-1 -1 123456789
UNIX> 

Grading Keys. Basically, you started out with 7 points, and received the following deductions:


Question 6

The first open will create f1.txt with permissions 0666 & ~(022), which is 0644. It writes the three bytes:
-rw-r--r-- 3 f1.txt
The second open will be successful, but since the file exists already, it is not recreated and the permissions do not change. The file is cleared to zero bytes on the open call, so it will have three bytes once it is closed:
-rw------- 3 f2.txt
The third open call will fail, since the caller does not have write permissions:
-r-------- 5 f3.txt
I accepted two answers for the fourth open call. I called umask(276) and not umask(0276). However, I was ok with you answering as if either had been called. 0666 & ~(0276), is equal to 0400:
-r-------- 3 f4.txt
276 is equal to 256+16+4: 0100010100 = 0424. 0666&~(0424) = 242:
--w-r---w- 3 f4.txt
The fifth one does not create the file, and since O_TRUNC is not specified, the file is not truncated. Even though the three bytes are written, the file's size of 5 bytes is not changed:
-rw-r--r-- 5 f5.txt
Again, I accepted two answers: The sixth open call creates the file with the permissions 0777 & ~(0276) which is 0501, or 0777 & ~(0424) = 0353:
-r-x-----x 3 f6.txt         or
--wxr-x-wx 3 f6.txt
The seventh open call simply opens the file and writes the three bytes -- it will suceed and since the file is not truncated, its size will remain at 5 bytes.
-rw---x--x 5 f7.txt
The eighth open call will fail, so the file will be unchanged:
-r-xr-xr-x 5 f8.txt
The last open call fails since f9.txt does not exist and O_CREAT was not specified. There will be no f9.txt in the listing.

Grading is as follows:

Each size is worth 0.25 points. Each rwx set of each file is also worth 0.25 points, and has to match exactly. So, for example, if you give -r-xrwxr-w for f8.txt, you get .5 points since the first and last set of rwx's match, but the middle set does not.