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.
% wget -nc http://pdos.lcs.mit.edu/6.824/labs/lab-3.tgz % tar xzvf lab-3.tgzThen, 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 % gmakeAs 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: 2d1b68f779135270You 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 &  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.
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.
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 &  72422 root file handle: 2a868a18dad0f84e anguish% ./ccfs dir2 suffering 3883 2a868a18dad0f84e &  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.
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 argumentHere'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 dataThe 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 ());
% tar czf lab-3-handin.tgz *.[Cchx] Makefile % chmod og= lab-3-handin.tgzCopy this file to ~/handin/lab-3-handin.tgz. We will use the first copy of the file that we find after the deadline.