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.
- Fd: Fd is bad -- either it is a non-open file
descriptor, or it was not opened for writing.
- Seg: ids+i or vals+i is not a legal memory address.
That can happen either because ids or vals is not a valid
address, or because i is big enough that ids+i or vals+i
runs into bad memory space.
- Fewer:
The write() call writes fewer than sizeof(int) or sizeof(double)
bytes --- for example, it is a network connection and the network goes away.
Grading
Part A is 4 points.
Part B is 8 points. Your program was given a base value, which was one of the following:
- Excellent, Great or Good: 8 points.
- Minor Mistakes: 7 points.
- No-ids-or-vals: 5 points -- everything looks good, but you're not buffering
from the correct place.
- Interleaved: 5 points -- You need to have the ints and vals interleaved in
the output. It's very easy to make two write calls and write all of ints and vals.
However, that's not the same output as the original.
- Half-Done: 4 points
- Decent-Start: 3 points. Looks very good, but not you didn't finish it.
- Flavor: 2 points. You've got the flavor, but you're rather far away.
- No-Buffering: 2 points. You aren't actually buffering -- you're still making
small writes.
- Uses-Stdio: 1.5 points. The question specified that you can't use stdio.
- Confused: 1.5 points. You've got some things right, but a lot is confused.
Then these could be modified by the following:
- Bad-Memcpy: -0.5 points -- your copying of memory is not really close.
- Ptrs: -0.5 points -- you have some egregious pointer mistakes.
- Half-Done: Half points. Self explanatory.
Part C 3 points. Each acceptable answer was worth 1.5 points. The following
are unacceptable answers:
- Exist: 0.3 points: "The file doesn't exist." There is no file -- just a file descriptor, which is an integer.
- Failed: 0.3 points: "The write call failed entirely." Why did it fail?
- Permissions: 0.3 points: "The permissions are set to where it is
unwritable." Again, permissions are a file
property, which is handled by the open() call.
- More: 0 points: "Write writes more than sizeof(int) bytes."
That's never happening. The semantics of the
write() system call are that it writes up to the number of bytes specified.
- Type: 0 points: "If you wrote a double to an int." Write() does not worry about types – it just writes the
specified number of bytes from the pointer.
- FDI: "If fd contained non-int's or non-double's."
FD is an int, not a pointer. I'm afraid I can't make
sense of this answer.
- Duplicate: 0 points. You gave two of the same answer.
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.
- Great: 5 points. Everything is there -- great answer!
- Definition-Good: 2.5 points. The definition of atomic action is good.
The rest of the answer has issues.
- Definition-Middling: 1.0 points. Your definition is worth a point, but not more.
- Definition-Interrupt-All: 2.0 points. "An atomic action interrputs all processes
currently running to perform an action." Not really -- it ensures that the actions
are performed without interruption. If there are other processors and they are not
performing conflicting actions, that it fine.
- Describing-O_SYNC: 1 point. You give a good description of O_SYNC rather
than O_EXCL.
- O_EXCL-Close: 1 point. Your O_EXCL definition hints around the right things,
but is not right.
- O_EXCL-Very-Close: 1.5 point.
You're O_EXCL definition is really close, but off in some minor way.
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:
- Fantastic: 10 points -- fantastic answer!
- Structure-Good: 10 points for correct structure.
- Structure-No-Inode-Check: 4 points -- see above.
- Worth-About-2: Self-Explanatory.
- Name-Const: -2 points: Don't construct file names, but just use d->de_name
- No-Dir-Calls: -2 points: Pseudo-code directory calls.
- No-Count: -1.5 points: Forgot to count files
- No-Tree: -1.5 points: Tried to do inodes without a tree
- Misuse-Tree: -1 point: Only works if red-black trees don't hold duplicates
- Bad-Link-Check: -1 point: You aren't doing the link check correctly
- Freeing: -.5 points: No freeing of name buffer or tree
- Returning: -.5 points: You don't return anything from the procedure
- Closing: -.5 points: You don't call closedir
- Opendir-Check: -.5 points: You don't error check the opendir call
- Returning-0: -.3 points: You returned 0 instead of the nubmer of 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:
- Excellent: 4 points -- excellent answer!
- Ok: 4 points Full credit, but not an excellent answer.
- No-With-Without-Directory: 1 points: You left out the "with" part of the answer.
On the "without" part, you said to change permissions on the directory, which is giving
way too much control to other users.
- Too-Much: 2.5 points: Your answer for the part
was too elaborate, usually including making calls to seteuid().
You just need to set the bit on the astring program.
- Without-Wrong: 2 points: You didn't get the "without" part right.
- Without-NE: 3 points: You were on the right track, but
didn't provide enough information on the "without" part.
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:
- 12-like3: Deduction: 1 -- Not recognizing the hard link.
- 1bad: Deduction: 1 -- First call incorrect.
- 2bad: Deduction: 1 -- Second call incorrect.
- 34like1: Deduction: 1 -- No difference between the first four calls.
- 34-perror: Deduction: 0.5 -- Calls 3 & 4 only: There is no perror call -- open and read simply return -1
- 3467-perror: Deduction: 1 -- Calls 3, 4, 6 & 7: There is no perror call -- open and read simply return -1
- 3like1: Deduction: 1 -- Not recognizing the hard link.
- 5good: Deduction: 0 -- Good job talking about the lack of a null character in call #5.
- 67-blank: Deduction: 2 -- Didn't answer 6 & 7.
- 67-perror: Deduction: .5 -- Calls 6 & 7 only: There is no perror call -- open and read simply return -1
- 7like1: Deduction: 1 -- Answer to 7 was the same as 1
- 7seg: Deduction: .5 -- Said that 7 would have a seg fault.
- 7compiler: Deduction: .5 -- Said that 7 would have a compiler error.
- 7wait: Deduction: .5 -- Said that 7 would stall waiting on standard input.
- ABCD-only: Deduction: 1 -- Had the read call return a null character.
- noextra: Deduction: .3 -- No extra characters in call #5.
- nonew: Deduction: .5 -- No newline in calls 1 & 2.
- no-1234: Deduction: 1 -- No 123456789 in the failed calls.
- rds/-1/0/: Deduction: .3 -- Had the read return 0 instead of -1
- s/1234/ABCD/: Deduction: .8 -- Had ABCD in the failed calls.
- s/3/0/: Deduction: .7 -- Had open return 0 instead of 3 on success.
- s/3/1/: Deduction: .7 -- Had open return 1 instead of 3 on success.
- s/3/4/: Deduction: .7 -- Had open return 4 instead of 3 on success.
- s/3/fd/: Deduction: .2 -- Said we didn't know what the return value of open would be.
- s/5/0/: Deduction: .8 -- Had read return 0 instead of 5.
- s/5/10/: Deduction: .8 -- Had read return 10 instead of 5.
- s/5/9/: Deduction: .8 -- Had read return 9 instead of 5.
- s/5/4/: Deduction: 0 -- Had read return 4 instead of 5.
- s/\n/\0/: Deduction: .3 -- Had read return the null character rather than the newline.
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.