CS 537
Programming Assignment 5
Frequently Asked Questions

Last modified Mon May 3 18:54:04 CDT 2004
Q1:
Just how careful do we have to be about race conditions in this project?

A:
Not very. We are deliberately de-emphasizing multi-user operation for this project. You had enough problems dealing with these issues on projects 2, 3, and 4, and if you haven't learned it by now, you probably never will. You may assume there is just one instance of FileTester running, doing one kernel operation at a time.

Q2:
How can I tell whether the disk has already been formatted? Is it an error to format a disk that has already been formatted?

A:
When you call FastDisk.flush, it writes the contents of the simulated disk to a Unix file named DISK. When FastDisk starts up, it looks for this file, and if it finds it, it reads it to initialize the contents of the simulated disk. Otherwise, it creates an "empty" disk in which the first block is filled with null bytes. Thus you can tell whether the disk has been formatted by looking at some field in the superblock that cannot be zero on a formatted disk.

It is not an error to format an already formatted disk, but of course any data previously written to the disk is lost.


Q3:
If DISK already exists, and next time we run our program and offer a different disk size from the previous disk size, what should we do ?

A:
If you want to change the size of the disk, type "rm DISK" before you run the program. The Disk constructor reports an error if the DISK file exists and its size does not match the argument to the constructor.

Q4:
Are we supposed to keep the entire directory in memory, or is this structure only to be stored on the disk?

A:
You can do it either way, but you will probably find it easiest to keep the directory in memory as a HashMap.

Q5:
I've seen most of the codes in the project 5 desription contains the '>>' operator. Can you explain what does this operator does and give me some examples on the correct usage of this operator?

A:
As you probably know, integers are stored internally as 32-bit binary values in "2's complement" notation. The expression x << y returns the value of x shifted left y places, with zeros shifed into the low-order bits. Because of the way 2's complement works, this has the effect of returning the value x * 2y. Similarly, >> shifts right y places, returning the value x / 2y. The bits shifted in from the left are duplicates of the original high-order bit. Due to the details of the way 2's complement arithmetic works, this gives x / 2y even if x is negative.

You will also need to use the operator &, which performs "bitwise and". Each bit of the result is the logical "and" of corresponding bits of the operands. Thus x & mask has the effect of "clearing" (setting to zero) result bits in positions where mask has zero bits, and returning the bits of x in positions where mask has one bits.

You will find examples of use of these operators in the code for pack and unpackShort.

See also Q6.


Q6:
In the sample unpackShort, why do a bitwise & of a byte with a hex ff? That doesn't seem to do anything at all.

A:
Recall that in Java, the type "byte" means 8-bit signed integer. In an expression, all the operands are first converted to int (or double, or long, as appropriate) before the arithmetic is done. A "negative" byte (one with the high bit set) gets converted to an integer by padding it on the left (high-order) end with 24 one bits. Try the following example and see if you can explain the results.

    byte b = (byte) 128;
    int n = (b<<8) + 1;
    System.out.println(n);

See also Q5.


Q7:
When I try to write disk.read(blockNumber, buffer) I get the compile-time error: No method named "read" was found in type "Disk". How can I call the method FastDisk.read?

A:
Use code like this
((FastDisk) disk).read(blockNumber, buffer)
or declare disk to be of type FastDisk and use a cast when initializing it.

Q8:
In case the name passed to create or link was more than 30 characters should the method return an error or truncate the name?

A:
Return an error.

Q9:
What happens if the end of the region of file modified by a write call extends beyond the end of the file?

A:
The file is expaned as necessary. This is the normal way files grow. Suppose the file is currently 1000 bytes long and you call Library.write with an offset of 1000 and a buffer of size 100. The contents of the buffer are appended to the end of the end of the file and the file is now 1100 bytes long. The same thing happens if the write overlaps the end of the file. Suppose the next write has an offset of 1050 and a buffer of size 100. The first 50 bytes in the buffer will overwrite the last 50 bytes in the file (which were put there by the previous write) and the other 50 bytes are appended to the end. The file how has size 1150.

See also Q10 and Q11.


Q10:
With regard to Q9, what if the offset in a write is beyond the end of a file?

A:
The gap is filled in with zero bytes. For example, if the file has length 1000 and you write 100 bytes starting at an offset of 2000, the effect should be as if you write 1100 bytes, the first 1000 of which were (byte) 0. However, the actual disk block should only be allocated when they are actually needed. Any complete blocks in the gap should be reprended by a zero block number in the inode's data array or in an indirect block. An attempt to read bytes from those blocks will behave as if the blocks were actually allocated and filled with zeros, but they should not take up any space on disk.

Q11:
What if a read call extends beyound the end of the file?

A:
The read should be truncated to return only the part of the file indicated by its current size. For example, if the file is currently 1000 bytes long and you ask for 100 bytes starting at an offset of 950, only the last 50 bytes of the file should be returned, overwriting the first 50 bytes of the buffer passed to the read call. The rest of the buffer should remain unchanged. The return value of the read all is the actual number of bytes read (50 in this example). As a special case, if the offset parameter is greater than or equal to the current size of the file, the return value is zero and the buffer is not modified at all.

Q12:
When we try to write data to a file and read it back, we don't get what we expect. How can we tell whether it is the write or read that is broken?

A:
One helpful tool is the Unix od command. Try this:
    od -Ad -td2 DISK
This says to dump the file in two-byte decimal integers (-td2) and show offsets in the file in decimal (-Ad). You might also try -td4 (4-byte decimal integers), or -tc (1-byte characters).

One word of warning however: This will only work on X86 machines (e.g. the tux lab), not SPARC machines (the nova lab) because the "little-endian" byte order specified for this project matches the native byte-order of the X86 but not the SPARC.


Q13:
The data in our superblock, inode, and directory structures should be written out to disk when sync() is called, but is that the only time? Should we be updating the inode and dir data every time we modify a file's contents on disk, for example? What should we do when so many files are created that we need more inodes than we have space allocated?

A:
These questions can be answered together. It's up to you what you cache in memeory. The project instructions suggest caching the superblock and the directory and not the inodes, but that's up to you. Do whatever makes the code simplest and easiest to get correct. Don't worry about efficiency. The only rule is all cached data must be flushed back to disk by sync(). If you are caching the directory in memory, you many not notice that it is too big to fit on disk until you do a sync(). You can indicate the error eagerly, by making create() or link() return -1, or lazily by making sync() return -1. Either is ok. Similar remarks apply to inodes.

Q14:
The kernel.read() is documented to return -1 on error or the number of bytes read. However, the FileTester prints out a error message "bad result nnn from system call". Is something wrong in FileTester?

A:
Yes. The original version of FileTester has a bug. It treats any non-zero return value from a system call as being an error. A fixed version has been installed in FileTester.java. If you have made modifications to your own copy and just want to patch it, replace these lines
    // Print out the result of the function call
    if (result != 0) {
        if (result == -1) {
            pl("*** System call failed");
        } else {
            pl("*** Bad result " + result + " from system call");
        }
    }
with these lines
    // Print out the result of the function call
    switch (result) {
    case 0:
        break;
    case -1:
        pl("*** System call failed");
        break;
    default:
        pl("*** Result " + result + " from system call");
    }