6.824 - Spring 2005

6.824 Lab 3 - File Server

Reading, Writing and Sharing Files

Last change: $Date: 2005/02/22 18:20:44 $

Due: Thursday Feb 24th, 1:00pm.


Introduction

In this lab, you'll continue implementing the Frangipani-like file server you started in Lab 2.

You'll start with the code you wrote for Lab 2, and add support for reading and writing file contents. You'll have to choose a representation with which to store file contents in the block server and implement the SETATTR, WRITE, and READ RPCs.

Continuing with the CCFS Server

Download the lab tester files from http://pdos.lcs.mit.edu.ezproxy.canberra.edu.au/6.824/labs/lab-3.tgz to your home directory on one of the class machines.
% wget -nc http://pdos.lcs.mit.edu.ezproxy.canberra.edu.au/6.824/labs/lab-3.tgz
% tar xzvf lab-3.tgz
Then, copy the source files from your Lab 2 to the Lab 3 directory.
% cp ~/lab-2/*.[Cchx] ~/lab-3/.
% cp ~/lab-2/Makefile ~/lab-3/.
% cd lab-3
% gmake
As a sanity check, you should check to see if your file server manages directory contents correctly. Start up the block server on one of the class machines:
suffering% cd ~/lab-3
suffering% ./blockdbd 3772 &
Then, start up the file server.
anguish% ./ccfs dir suffering 3772 &
root file handle: 2d1b68f779135270
You should be able to run commands like this in your Lab 2 file system:
anguish% cd /classfs/dir
anguish% ls
anguish% touch a
anguish% ls -lt
total 0
-rw-r--r--  0 root  wheel  0 Feb  9 11:10 a
anguish% touch b
anguish% ls -lt
total 0
-rw-r--r--  0 root  wheel  0 Feb  9 11:10 b
-rw-r--r--  0 root  wheel  0 Feb  9 11:10 a
anguish% 
When you're done with Lab 3, you should be able to read and write files in your file system:
anguish% ./ccfs dir suffering 3772 &
root file handle: 791057898997cb97
anguish% cd /classfs/dir
anguish% echo hello > a
anguish% ls -lt
total 0
-rw-r--r--  0 root  wheel  6 Feb 16 13:52 a
anguish% cat a
hello
anguish% 
If you try the above commands on your Lab 2 file server, the echo will hang forever.

Your Lab 3 file server must support sharing the file system on other hosts via the block server. So when you're done with Lab 3 you should be able to do this on suffering:

suffering% cd ~/lab-3
suffering% ./ccfs dir suffering 3772 82cda8d746e81085 &
[2] 6608
root file handle: 82cda8d746e81085
suffering% cd /classfs/dir
suffering% ls -lt
total 0
-rw-r--r--  0 root  wheel  6 Feb 16 13:52 a
suffering% cat a
hello
suffering% 
Replace the fourth argument to ccfs with the root file handle that ccfs printed out on anguish.

Your Job

Your job is to implement SETATTR, WRITE, and READ NFS RPCs in fs.C. You must store the file system's contents in the block server, so that in you can share one file system among multiple servers.

You'll have to set the size field in fattr3 as a side effect of most WRITE RPCs and when SETATTR explicitly asks your server to do so.

************** Added 2/21/05 ************

After a WRITE the file length should be MAX(old_length, offset+count). A WRITE should never make a file shorter. If an application wants to overwrite a file, which can cause the file to be shorter, the NFS client will first send a SETATTR and then a WRITE. The SETATTR has new_attributes.size.set=true, and new_attributes.size.val=0, which should cause your server to set the file length to zero. The WRITE then writes the number of bytes specified by the client. The resulting file can have a shorter or longer length than the original file.

******************************************

If your server passes the tester (see below), then you are done. If you have questions about whether you have to implement specific pieces of NFS server functionality, then you should be guided by the tester: if you can pass the tests without implementing something, then don't bother implementing it.

Please modify only fs.C and fs.h. You can make any changes you like to these files. Please don't modify the block server or its client interface (blockdbc.C and blockdbc.h), since we may test your file server against our own copy of the block server.

Testing

You can test your file server using the test-lab-3.pl script. The script tests reading, writing, and appending to files, as well as testing whether files written on one server can be read through a second server. To run the tester, first start a block server:
suffering% ~/lab-3/blockdbd 3883 &
Then start two instances of ccfs, with the same root file handle, on the same machine:
anguish% ./ccfs dir1 suffering 3883 &
[1] 72422
root file handle: 2a868a18dad0f84e
anguish% ./ccfs dir2 suffering 3883 2a868a18dad0f84e &
[3] 72595
root file handle: 2a868a18dad0f84e
anguish%
The directory argument for each instance is different, but you should pass the root file handle printed by the first ccfs as an argument to the second ccfs.

Now you can use test-lab-3.pl to test your file system by passing the two root directories that you just created:

anguish% ./test-lab-3.pl /classfs/dir1 /classfs/dir2
Write and read one file: OK
Write and read a second file: OK
Overwrite an existing file: OK
Append to an existing file: OK
Write into the middle of an existing file: OK
Check that one cannot open non-existant file: OK
Check directory listing: OK
Read files via second server: OK
Check directory listing on second server: OK
Passed all tests

If test-lab-3.pl exits without printing "Passed all tests!", then it thinks something is wrong with your file server.

************** Added 2/22/05 ************

You should re-start ccfs before you run the tester. If you run the tester more than once without re-starting ccfs, the tester is likely to see stale information cached by the NFS client for the second file system. You don't need to solve this problem for Lab 3, but if you're interested, the solution is to implement mtime and ctime for files and directories.

******************************************

Hints

You should consult the NFS v3 specification for You may also want to consult the book "NFS Illustrated" by Brent Callaghan.

You can learn more about NFS loopback servers and asynchronous programming in the loop-back NFS paper. You can find the sources for this software at www.fs.net or in /u/6.824/sfs-0.7.2 and /u/6.824/classfs-0.0. You can see the NFS RPC definitions in /u/6.824/sfs-0.7.2/svc/nfs3_prot.x. The output of the RPC compiler is in /u/6.824/sfs-0.7.2-build/svc/nfs3_prot.h.

You can learn more about the asynchronous programming library (wrap, callbacks, str, and strbuf) by reading the Libasync Tutorial.

Here's how to extract the argument for the SETATTR RPC:

setattr3args *a = nc->template getarg<setattr3args> ();
//nfs_fh3 a->object : the file handle that the client wants to set attributes
//sattr3 a->new_attributes : the new attrbutes
//sattrguard3 a->guard : ignore this argument 
Here's an example of how to set the size and mtime attributes (atime is similar to mtime):
fattr3 attr;
if (a->new_attributes.size.set)
   attr.size = *(a->new_attributes.size.val);
if (a->new_attributes.mtime.set == SET_TO_CLIENT_TIME) 
   attr.mtime = *(a->new_attributes.mtime.time);
Here's how to generate a reply for the SETATTR RPC:
wccstat3 *res = nc->template getres<wccstat3> ();
res->set_status (NFS3_OK);
*res->wcc = make_wcc (old_attr, new_attr);
nc->reply (nc->getvoidres ());
Here's how to extract the argument for the WRITE RPC:
write3args *a = nc->template getarg<write3args> ();
// nfs_fh3 a->file : the file handle where the client wants to write.
// uint64 a->offset : the offset to start the write
// uint32 a->count : the length of data to write
// stable_how a->stable : ignore
// opaque a->data : handle for the data
The last argument is essentially a pointer to the new data. Below is an example of how to copy its contents to a str variable.
str newdata (a->data.base (), a->count);
You should ignore the stable argument and always write the file to stable storage (the block server). You should wait for the put of the file's contents and attributes to complete before sending a reply to the client. This behavior is equivalent to performing writes with the a->stable argument set to FILE_SYNC.

Here's how to generate a reply for the WRITE RPC:

write3res *res = nc->template getres<write3res> ();
res->set_status(NFS3_OK);
res->resok->file_wcc = make_wcc (old_attr, new_attr);
res->resok->count = /* The number of bytes of data written */;
res->resok->committed = FILE_SYNC; //Since this is how we implemented the write.
nc->reply (nc->getvoidres ());
Here's how to extract the argument for the READ RPC:
read3args *a = nc->template getarg<read3args> ();
// nfs_fh3 a->file :  the file handle where the client wants to read.
// uint64 a->offset : the offset to start the read.
// uint32 a->count : the length of data to write.
Here's how to generate a reply for the READ RPC:
char value[bytes_read];

read3res *res = nc->template getres<read3res> ();
res->set_status (NFS3_OK);
res->resok->eof = /* true, if already end of file; otherwise, false. */;
res->resok->file_attributes.set_present (false);
res->resok->count = bytes_read;
res->resok->data.set (value, bytes_read);
nc->reply (nc->getvoidres ());

Collaboration policy

You must write all the code you hand in for the programming assignments, except for code that we give you as part of the assigment. You are not allowed to look at anyone else's solution (and you're not allowed to look at solutions from previous years). You may discuss the assignments with other students, but you may not look at or copy each others' code.

Handin procedure

You should hand in the gzipped tar file lab-3-handin.tgz produced by running these commands in your ~/lab-3 directory.
% tar czf lab-3-handin.tgz *.[Cchx] Makefile
% chmod og= lab-3-handin.tgz
Copy this file to ~/handin/lab-3-handin.tgz. We will use the first copy of the file that we find after the deadline.