Project 6: A Simple File Server
Important Info and Dates
You can have a partner if you'd like.
Questions about the project? Send them to
Due: Friday 5/7 by 9pm.
- 04/27: For directory entries that are not yet in use, the inode number
should be set to -1. This way, utilities can scan through the entries to check
if they are valid.
In this assignment, you will be developing a working file server.
We provide you with only the bare minimal UDP communication code - you have to
build the rest. Thus, this project has a bigger design component than the
previous projects - so think about your design! And feel free to consult with
me or the TA if you have questions.
A Basic File Server
Your file server is built as a stand-alone UDP-based server. It should wait
for a message and then process the message as need be, replying to the given
Your file server will store all of its data in an on-disk file which will
be referred to as the file system image . This image contains the on-disk
representation of your data structures; you should use these system calls
to access it: open(), read(), write(), lseek(), close(), fsync() .
To access the file server, you will be building a client library. The
interface that the library supports is defined in mfs.h .
The library should be called libmfs.so, and any programs that wish to access
your file server will link with it and call its various routines.
On-Disk File System
Your on-disk file system structures should be very simple. You should have
a simple inode table which consists of 4096 inodes. You should have a data
region which consists of 4096 data blocks. Each inode should contain a type
field (directory or regular file), a size field (number of bytes in the file),
a blocks field (number of blocks allocated to the file), and 10 (direct)
pointers to data blocks. The data block size is fixed at 4096 bytes, and hence
the max file size is 10 * 4096 or 40KB. To track what is allocated and
what isn't, you should also have a couple of bit maps. One bitmap for the
inodes, and one for the data blocks.
When your server is started, it is passed the name of the file system image
file. If this file does not exist, the file server should create it, and
initialize it properly, and force it to disk. Such initialization includes
creating a space big enough for the inodes and the data blocks, and
initializing a root directory with proper . and .. entries. The root
inode number should be 0.
The client library should export the following interfaces:
- int MFS_Init(char *hostname, int port): MFS_Init() takes a host name
and port number and uses those to find the server exporting the file system.
- int MFS_Lookup(int pinum, char *name): MFS_Lookup() takes the parent
inode number (which should be the inode number of a directory) and looks up
the entry name in it. The inode number of name is returned. Success:
return inode number of name; failure: return -1. Failure modes: invalid pinum,
name does not exist in pinum.
- int MFS_Stat(int inum, MFS_Stat_t *m): MFS_Stat() returns some
information about the file specified by inum. Upon success, return 0,
otherwise -1. The exact info returned is defined by MFS_Stat_t. Failure modes:
inum does not exist.
- int MFS_Write(int inum, char *buffer, int block): MFS_Write() writes a
block of size 4096 bytes at the block offset specified by block . Returns 0
on success, -1 on failure. Failure modes: invalid inum, invalid block, not a
regular file (you can't write to directories).
- int MFS_Read(int inum, char *buffer, int block): MFS_Read() reads
a block specified by block into the buffer from file specified by
inum . The routine should work for either a file or directory; directories
should return data in the format specified by MFS_DirEnt_t. Success: 0,
failure: -1. Failure modes: invalid inum, invalid block.
- int MFS_Creat(int pinum, int type, char *name): MFS_Creat() makes a
file ( type == MFS_REGULAR_FILE) or directory ( type == MFS_DIRECTORY)
in the parent directory specified by pinum of name name . Returns 0 on
success, -1 on failure. Failure modes: pinum does not exist. If name
already exists, return success (think about why).
- int MFS_Unlink(int pinum, char *name): MFS_Unlink() removes the file or
directory name from the directory specified by pinum . 0 on success, -1
on failure. Failure modes: pinum does not exist, directory is NOT empty. Note
that the name not existing is NOT a failure by our definition (think about why
this might be).
The key behavior implemented by the server is idempotency.
Specifically, on any change to the file system state (such as a Write, Creat,
or Unlink), all the dirtied buffers in the server are committed to the disk.
The server can achieved this end by calling fsync() on the file system
image. Thus, before returning a success code, the file system should always
fsync() the image.
Now you might be wondering: why do this? Simple: if the server crashes, the
client can simply timeout and retry the operation and know that it is OK to do
so. We'll be talking about this more when we talk about NFS, the Network File
System from the company formerly known as Sun.
Now you might be wondering: how do I implement a timeout? Simple, with the
select() interface. The select() calls allows you to wait for a reply
on a certain socket descriptor (or more than one, though that is not needed
here). You can even specify a timeout so that the client does not block
forever waiting for data to be returned from the server. By doing so, you can
wait for a reply for a certain amount of time, and if nothing is returned, try
the operation again until it is successful.
Your server program must be invoked exactly as follows:
prompt> server [portnum] [file-system-image]
The command line arguments to your file server are to be interpreted as follows.
- portnum: the port number that the file server should listen on.
- file-system-image: a file that contains the file system image.
If the file system image does not exist, you should create it and properly
initialize it to include an empty root directory.
Your client library should be called libmfs.so and be built as usual. It
should implement the interface as specified by mfs.h , and in particular
deal with the case where the server does not reply in a timely fashion; the
way it deals with that is simply by retrying the operation, after a timeout of
some kind (default: five second timeout).
Some Helper Code
To get you going, we have written some simple UDP code that can send a
message and then receive a reply from a client to a server. It can be found in
Hand in your source code in the p6 directory, as usual. Please put a copy of
everything (described below) in BOTH partners directories (if you have a
partner). Also, in a README, specify WHO your partner is.
You should copy all of your server source files (*.c and *.h) and a
Makefile to your p6 handin directory. Do not submit any .o files.