This project implements a Network File System (NFS) consisting of a Naming Server (NS), Storage Servers (SS), and Clients. The system allows multiple clients to access and perform operations on files and directories stored across distributed storage servers. The NS maintains the metadata and routing information, while SS stores and manages the actual data.
- To build Naming Server
./build.sh namingServer
- To build Storage Server 1 (in
StorageServers/storageServer1)
./build.sh storageServer1
- To build Client 1 (in
Clients/client1)
./build.sh client1
-
The naming server is initialized in
initializeNamingServer()where a new socket is created and binded to a fixed port. -
The function
acceptHost()runs on a thread allowing clients and storage servers to join. On joining, the host sends a message indicating if its a client or a storage server and their detials are stored inclientDetialsandstorageServersrespectively. -
After a storage server joins the naming server, then another socket is initialsed for listening to accessible paths dynamically on a separate thread running the
addToRecord()function. -
After a client joins the naming server, it continously listens for requests from the client on separate threads
acceptClientRequests(). The details of the storage server responsible for the given path is determined bygetRecord()function. -
The data structure for storing the records are
TriesandLRUcache. Both of these, store the details of each record by storing thepointerto each record in their ownstruct node. -
Each
TrieNodehas the pointer to a record and anarrayof pointers. Using the index of each letter in the path provided, it searches for a record inside theTrie. If at any point, the pointer at any index isNULL, then that record does not exist. Given a pointer a record, it creates a -
Another data structure for storing a record is an
LRUcache. It uses an array for storing the least recently used records. The cache has a fixed size of 10, which can be changed accordingly by the user. Search for a record first happens in theLRU. If the record is found, then it is acache hit. If the record is not found, then it is acache miss. Then it proceeds to search in theTrieif it was a cache miss. If the record is found, then the record is added to the cache. At the same time, the least recently used record is discarded which is at the end of the array in whichLRUis operating. -
Overall
LRUdoes search in O(1) time, since there are only a constant number of elements in the cache at any given time.Triestake a slighlty longer time, O(n) in the worst case, where n is the string length of the path provided. -
The NM checks if the client request is a privileged operation or not. Accordingly it redirects the client. If it is a privileged operation (
RMDIR,RMFILE,MKDIR,MKFILE,COPY), the NM itself handles this operation by extracting the corresponding record and from it the SS details. -
If it is a non-privileged operation (
READ,WRITE,FILEINFO,HELP), the NM sends the details of the SS where the file is stored by extracting the SS details from the record. -
If at any point in time, no record is found, then it sends the relevent error message to the client.
-
After each operation, the client sends
ACKaccordingly to the NM if the operation was completed successfully or not. if not, then the client sends or receives the corresponding error message.
-
The storage server is initialized by binding to two ports one for communication with all clients -
initialzeClientsConnection()and another for communication with the naming server -initializeNMConnection(). -
A thread running
takeInputsDynamically()is created which continously listens on the command line for the list of accessible paths which is then sent to the naming server. -
A thread for accepting clients is created which first accepts connection requests from clients and then responds to them using the
serveClient_Request()thread. -
The SS always receives the original request and accordingly performs the operation. If it is a privileged operation, then it performs the operation through the thread
serveNM_Requests. After the operation is done, it sends theACKto the NM, and in case an error happens, it sends the error message to the NM. -
If it was a non-privileged request, then the SS connects with the client and performs the necessary operation using the thread
serveClient_Requested. It also returns the necessaryACKstrings and handles error if the operation was unsuccessfull.
-
The client is initialized using the
joinNamingServerAsClient()and is accepted in theacceptHost()function in the naming server. -
A continuous thread running the
isNMConnected()function runs to check for the socket status of the naming server. Whenever the naming server goes down, the client is notified. -
In a continous loop, first an input is taken from the user and sent to the
sendRequest()function which processes the input and sends it to the naming server. -
Each request is redirected to the NM and these requests are performed in the function
sendRequest. There, if the request was privileged, the NM handles that request by connecting with the correct SS and returns theACKto the client. If there were any errors with the operation, they are handled accordingly. -
If the operation was not privileged, the NM on receiving the request returns the correct details of the SS. Upon receiving these details, the client connects with the SS using the function
joinSSand makes the request again. The SS performs this operation and returns theACK. If there were any errors, they are handled accordingly. -
Depending on the return value of the fucntion
sendRequest, the client accepts the next request.
-
MKDIRandMKFILE: enables the creation of directory with a given set of permissions. Implemented by themakeDirectorycall which checks whether a given path exists or not, and using thereversePermissionscall to get the permissions set by the user. Default permissions for a directory aredrwxr-xr-xand for a file are-rw-r--r--. After the succesful completion of the call, the newly created path is added to the records. -
RMDIRandRMFILE: deletes a file or a directory and the paths are removed from the records. For deletion of a directory,rmdirsystem call is used and for that of a file,rmfilecommand is used. Note that the expected behaviour of a directory that is not empty cannot be deleted is followed. -
COPYis used to copy a file or a directory. We are recursively traversing the accessible pathn-ary tree(nextSiblingfor all except the source itself and thefirstChildfor every intermediate node). If we encounter a directory, we use the already definedMKDIRcall otherwise we useMKFILE. We then send over the contents of the file via the network and send it to the destination storage server. The temporarily created file at the naming server location is subsequently deleted. -
READandWRITE: are used to view and modify the files respectively. They are performed between the storage servers and the clients directly. Before sending any file, the storage server first sends the permsissions of the file requested to the client. Then the client checks if the file has enough file permissions and continues. The storage server sends the file in fixed size chunks usingsendandrecvalong with intermediateACKpackets. The client receives the file until the client receives theSTOPpacket. The contents of the file requested are stored in atempfile for reading and writing. For the case ofWRITE, the modified file is sent back to the storage serber to update the changes. -
FILEINFO: returns the information related to the file requested (no directories) by the client like permission, last modified time, path, etc. The information about the file is stored in thestruct fileDetailsand is sent by the storage server to the client.
Custom error codes have been defined for almost all possible errors in the errors.c file.
| Error Code | Type Of Error |
|---|---|
| 5xx | File Operation Errors |
| 9xx | Network Errors |
| 6xx | System Call Errors |
-
To determine which storage servers would serve as the backup servers for a particular server, the policy used here is to assign the highest two SS ids among the currently active servers whenever a new storage server joins.
-
Reconnection of a storage server is tracked by the IP and Port of the storage server. It is used to match back the accessible paths of the disconnected storage server after reconnection.
-
Function
backupCopy()andbackupRemove()are used to update the backup files stored.backupCopy()is called whenever a new file/folder is added or modified byMKDIR,MKFILE,WRITEor when a new path is added to the list of accessible paths by the SS .backupRemove()is called whenever an existing file is removed byRMDIRorRMFILE -
When the originalSS for a requested file is down, and the operation is a read operation like
READorFILEPERMS, then the backup SS detials are sent back to the client. In other cases, an error is raised stating that the requested path is in read-only mode and cannot be modified.
-
The naming server can access requests from multiple clients and multiple storage servers at the same time. To prevent race condition on the critical sections of the code, locks have been implemented for global variables which are shared by multiple threads.
-
Each accessible file is associated with a lock which is aquired when an operation needs to take place on that file, and then released when the operation has completed.
-
If requested files have been blocked due to some reason, a timeout message is sent to the user to indicate the blockage. The basic case is when two clients request the same file to be written to, then the first client is given access first and the second has to wait until the first client has completed the write request.
A function logMessage() has been implemented which displays detials about all network operations related to the naming server. It displays the timestamp, sender/receiver IP and port along with the log message of what is sent or received in the format -
[DD-MM-YYYY HH:MM:SS] [<ip>:<port>] -> <message>
To initialize the storage servers in specific directories, one can modify the build.sh shell-script to do that.