A distributed Network File System (NFS) implemented from scratch in C, built for the CS3.301 course project. It follows a three-tier architecture — Clients, a central Naming Server, and multiple Storage Servers — communicating over raw TCP sockets.
- Naming Server (NM) — the central coordinator. Storage Servers register with it on startup; clients query it to resolve which Storage Server holds a given path. It never moves file data itself — it hands clients the IP/port of the right Storage Server and lets them talk directly.
- Storage Servers (SS) — hold the actual files/folders on disk and serve reads, writes, and metadata queries directly to clients. They also execute privileged commands (create, delete, copy) issued by the NM.
- Client — a CLI that sends commands to the NM, gets redirected to the appropriate SS, and then performs the operation directly against that SS.
Client <----> Naming Server <----> Storage Server(s)
\_____________________________________/
direct data transfer
naming_server/ Naming Server source (registration, path resolution, LRU cache, redundancy)
storage_server/ Storage Server source (read/write/create/delete/copy, streaming)
client/ Client CLI source
- Path resolution: clients issue commands with a file path, and the NM resolves it to the owning Storage Server's IP and port.
- Core file operations:
READ,WRITE,ADDITIONAL_INFO(size/permissions),CREATE_FILE,CREATE_DIR,DELETE_FILE,DELETE_DIR,COPY_FILE,COPY_DIR. - Audio streaming directly from a Storage Server to the client.
- Concurrent clients: multiple clients can read a file simultaneously; a client writing to a file blocks other clients from accessing it until the write completes.
- Dynamic scaling: Storage Servers can register with the NM at any time, not just at startup.
- Redundancy: the NM replicates each Storage Server's data onto two other Storage Servers. If a Storage Server goes down, reads fall back to a replica; writes require the primary. On reconnection, replicas are reconciled with the primary.
- Efficient lookups: trie-based path indexing plus an LRU cache for recently resolved paths.
- Bookkeeping: the NM logs requests, acknowledgments, and the IP/port involved in each operation.
- A C compiler (GCC)
- POSIX sockets (Linux/macOS)
Note: The Naming Server's IP and port are currently hardcoded in the Storage Server source (
storage_server/storage_server/strogeservers.c) — update theip/portvalues there to match your Naming Server's machine before building.
cd naming_server
make clean; make
./NMSCopy the storage_server/storage_server directory to the machine/directory you want to serve as a Storage Server, then:
make clean; make
./SS <port_for_client_connections> <port_for_naming_server_connection>
# Example
./SS 8001 9001Start as many Storage Servers as you need — each can run on a different machine, and each can register with a different accessible path.
cd client
make
./a.outNon-privileged operations (client talks to the resolved Storage Server directly):
| Command | Example |
|---|---|
READ <path> |
READ /path/to/file.txt |
WRITE <path> <content> |
WRITE /path/to/file.txt "New content" |
ADDITIONAL_INFO <path> |
ADDITIONAL_INFO /path/to/file.txt |
STREAM <path> |
STREAM /path/to/song.mp3 |
Privileged operations (routed through the Naming Server):
| Command | Example |
|---|---|
CREATE_FILE <path> |
CREATE_FILE /path/to/new_file.txt |
CREATE_DIR <path> |
CREATE_DIR /path/to/new_dir |
DELETE_FILE <path> |
DELETE_FILE /path/to/file_to_delete.txt |
DELETE_DIR <path> |
DELETE_DIR /path/to/directory_to_delete |
COPY_FILE <src> <dst> |
COPY_FILE /path/to/source.txt /path/to/dest_dir |
COPY_DIR <src> <dst> |
COPY_DIR /path/to/source_dir /path/to/dest_dir |
For non-privileged operations, the Naming Server resolves the path and returns the target Storage Server's IP/port so the client can connect directly; each Storage Server can then serve multiple clients concurrently.
- Multiple clients can read the same file at the same time.
- A file being written to is locked from all other client access until the write finishes.
- The Naming Server does not block on long-running operations — it acknowledges requests and lets Storage Servers report back once complete, so it stays responsive to other clients.
- The Naming Server replicates every file/folder from a Storage Server onto two other Storage Servers once at least three are registered.
- If a primary Storage Server goes down, reads are served from a replica; writes are rejected until it comes back.
- On reconnection, the Naming Server reconciles the primary against its replicas.