The goal of the next and final project is to implement a file system, using FUSE, which uses the volatile and non-volatile network block system as storage devices. Your filesystem development environment will be within UML in order to have access to the FUSE kernel module and corresponding user space libraries. We will be publishing, throughout the remainder of the quarter, test codes which are to be executed from your mounted FUSE directory. The test codes will perform common filesystem operations and will be used to evaluate the quality of your filesystem implementation.
Your filesystem will allow a regular user to use the filesystem like other common Linux filesystems in that it will support a hierarchical directory structure which can contain files. Files and directories will have attributes denoting access times, size and type qualities, and standard UNIX permission information.
Features of your filesystem will support but are not limited to
the following abstract concepts, which will be detailed later.
There are a number of global issues that arise when developing a filesystem based on remote resources, some of which we will be able to ignore but others must be properly addressed.
Capacity. The capacity of your filesystem will be dynamic, depending on how many block servers and how many blocks per server you have available at any given point in time.
Persistence. This issue arises whenever the filesystem is in operation for some amount of time, then the controlling FUSE daemon or the client operating system is stopped and restarted. If one of these is restarted, the filesystem should remain in the same state or very close to the same state as it was when it was last operational. Of course, volatile blocks may be lost due to block timeouts between when the system was stopped and when it comes back online, but metadata information (file inodes, directory structures, etc) must be unchanged between restarts.
Fault Tolerance. Since the storage 'device' used for this project is not as reliable as more traditional file system storage devices, the possibility of faults occurring within the storage system are much higher than if we were only concerned with local disks. Your file system must be able to respond to faults without creating a catastrophic file system failure. Faults include block servers being unavailable (volatile and non-volatile) either because the servers have failed or the network is down, and server block data being lost. The fault tolerant mechanisms you design only need to report and work around these types of failures; they do not need to be able to necessarily REPAIR data which is lost do to system failure (this would be under the heading 'reliability', which we are not addressing with this project). Catastrophic failure for this project is defined as any failure which results in the death of your fuse daemon, the fuse daemon being put into a state which isn't correct even when the block servers become available, or any kernel crash or failure. The fault tolerance mechanisms must be implemented to respond to failures without the entire system (either the UML itself or the fuse daemon) being restarted.
Performance. Due to the fact that we are developing a filesystem under UML which uses a stateless network API and are sharing a small number of storage servers, there is not an expectation of implementing a high performance file system relative to more standard file systems. However, we have a reference solution and your filesystem's performance characteristics will be compared against other projects in the class. Please note that although performance is an issue, it is not as important an issue as completeness, persistence, or fault tolerance. Because of this we advise that you employ a very conservative caching policy, if any at all, while implementing your filesystem. Our test codes will be rigorously testing the primary issues, and not all of the codes we use to test your projects will be published. The safest route with regards to caching would be to not cache at all.
The basic operation of a FUSE filesystem is fairly straightforward. The filesystem developer creates a FUSE daemon process which implements a number of callbacks. When a userspace process makes any filesystem calls within a FUSE mounted directory, the calls are passed to the kernel, which then passes them to the FUSE kernel module. The kernel module, which knows about the running FUSE daemon you have executed, passes function calls to the FUSE API which then executes specific daemon callbacks. Each callback is passed a well defined set of inputs, and is required to return a well defined result either by value or by reference depending on the call.
As one can tell from the 'fusexmp.c' example daemon included in
the FUSE package, there are a number of filesystem related callbacks
that are passed from the kernel module to a FUSE daemon whenever
filesystem operations are performed within a FUSE mounted directory.
Below is a list of these callbacks, the some of which will not be
required for this project.
You will need to carefully read and understand the 'fusexmp.c' example to know exactly what is passed to each callback function and what is expected to be returned. Experimentation is highly advised!
Since fuse daemon processes are designed to be event driven (a number of event callbacks are defined, some initialization is performed, and then an infinite event loop is entered) and there does not appear to be an idle callback defined in the FUSE API, your daemon must implement a set of routines to handle the volatile nature of the network block servers which are outside the domain of FUSE callbacks. Several methods exist that may be employed, such as an alarm() timer system or the use of pthreads.