A distributed network file system implemented in C with support for multiple clients, storage servers, and a centralized name server. This system provides file operations with access control, concurrent access handling, and persistence.
-
Name Server (NM)
- Central coordinator managing file locations and client connections
- Implements efficient file search using Trie data structure
- Maintains an LRU cache for frequently accessed metadata lookups, reducing repeated Trie traversals for hot paths.
- Handles access control and user authentication
- Routes client requests to appropriate storage servers
-
Storage Server (SS)
- Stores and manages file data
- Supports concurrent read/write operations
- Implements sentence-level locking for write operations
- Provides undo functionality (one level)
- Ensures data persistence
-
Client
- User interface for file operations
- Supports all required operations (VIEW, READ, WRITE, etc.)
- Direct connection to storage servers for data transfer
- Interactive command-line interface
- ✅ VIEW - List files with optional flags (-a, -l)
- ✅ READ - Read complete file contents
- ✅ CREATE - Create new empty files
- ✅ WRITE - Word-level editing with sentence locking
- ✅ UNDO - Revert last change to a file
- ✅ INFO - Display file metadata and access control
- ✅ DELETE - Remove files (owner only)
- ✅ STREAM - Word-by-word streaming with 0.1s delay
- ✅ LIST - List all registered users
- ✅ EXEC - Execute file contents as shell commands
- ✅ CHECKPOINT - Create snapshot of storage server state
- ✅ ADDACCESS - Grant read (-R) or write (-W) access
- ✅ REMACCESS - Remove user access
- Owner always has read/write access
- Write access implies read access
- ✅ Efficient file search using Trie (O(m) where m = filename length)
- ✅ LRU-based metadata caching for frequently accessed file lookups
- ✅ Sentence-level locking for concurrent writes
- ✅ Data persistence across server restarts
- ✅ Comprehensive logging to files and console
- ✅ Error handling with standardized error codes
- ✅ Dynamic storage server registration
- GCC compiler
- POSIX-compliant system (Linux/Unix)
- pthread library
makemake naming_server # Build name server
make storage_server # Build storage server
make client # Build clientmake clean # Remove binaries and object files
make distclean # Remove everything including storage./bin/naming_serverThe name server listens on port 8080 by default.
Important: Each storage server is uniquely identified by its IP address and client port combination. The system automatically detects the server's IP and creates a unique storage directory (e.g., ./storage_192_168_1_10_9001/).
./bin/storage_server <nm_ip> <nm_port> <client_port>Examples:
On localhost (same machine, different ports):
./bin/storage_server 127.0.0.1 8080 9001 # Creates ./storage_127_0_0_1_9001/
./bin/storage_server 127.0.0.1 8080 9002 # Creates ./storage_127_0_0_1_9002/Across multiple devices (different machines on network):
# On Device 1 (192.168.1.10):
./bin/storage_server 192.168.1.5 8080 9001 # Creates ./storage_192_168_1_10_9001/
# On Device 2 (192.168.1.11):
./bin/storage_server 192.168.1.5 8080 9001 # Creates ./storage_192_168_1_11_9001/The storage server will:
- Auto-detect its own IP address
- Create a unique storage directory based on IP:Port
- Register with the naming server at the specified IP
- List all existing files in its storage directory
- Listen for client connections on the specified port
./bin/client <nm_ip> <nm_port>Examples:
# Connect to local naming server:
./bin/client 127.0.0.1 8080
# Connect to remote naming server:
./bin/client 192.168.1.5 8080Example:
./client 127.0.0.1 8080You will be prompted for a username, then enter the interactive command shell.
VIEW # List files you have access to
VIEW -a # List all files in system
VIEW -l # List files with details
VIEW -al # List all files with detailsREAD <filename>CREATE <filename>WRITE <filename> <sentence_number>
# Then enter word updates:
<word_index> <content>
<word_index> <content>
...
ETIRW # Finish writingExample:
WRITE test.txt 0
Client: 1 Hello world
Client: 3 beautiful
Client: ETIRWUNDO <filename>INFO <filename>DELETE <filename> # Must be ownerSTREAM <filename> # Displays word-by-word with 0.1s delayLISTADDACCESS -R <filename> <username> # Read access
ADDACCESS -W <filename> <username> # Write accessREMACCESS <filename> <username>EXEC <filename> # Executes content as shell commandsCHECKPOINT # Creates a checkpoint of all storage serversCreates a timestamped snapshot of all registered storage servers' data and metadata. Checkpoint files are stored in ./checkpoints/ directory with naming format: checkpoint_<ip>_<port>_<YYYYmmddHHMMSS>.tar
On storage server restart, the most recent checkpoint is automatically restored if available.
The Name Server maintains a Trie-based namespace index that enables O(m) file lookup, where m is the pathname length.
To reduce repeated metadata traversals, the system additionally maintains an LRU (Least Recently Used) cache for recently resolved paths. Frequently accessed files are served directly from the cache, while infrequently accessed entries are automatically evicted using the LRU replacement policy.
This hybrid design combines deterministic Trie lookups with temporal locality, improving lookup performance under realistic workloads while maintaining bounded memory usage.
- Multiple clients can read the same file simultaneously
- Write operations lock individual sentences
- Other sentences in the same file remain accessible
- Locks are automatically released after write completion
- Sentences are delimited by
.,!, or? - Every delimiter creates a new sentence (including "e.g." → multiple sentences)
- Words are separated by spaces
- WRITE operations can insert new delimiters, creating new sentences
- Storage servers maintain files in
./storage/directory - Undo history stored as
.undofiles - File metadata tracked in name server
- Access control lists persist with name server
Checkpointing provides a way for storage servers to create a durable snapshot of their current on-disk state and lightweight metadata so that a server restart or recovery can restore to a known good point. Checkpoints are designed to be compact, consistent at the storage-server level, and quick to write so they can be performed during maintenance windows or on-demand.
-
What a checkpoint contains:
- A compact archive of the storage server's file data and directory layout (files under the server's storage directory).
- Metadata required to restore file-level information (undo files, small metadata files) and any server-local state needed by that storage server.
- A timestamp and the storage server identifier (IP:Port) used to locate/identify the checkpoint file.
-
Where checkpoints are stored:
- Checkpoints are written to a
checkpoints/directory inside the workspace (e.g../checkpoints/). - Files are named using the server identifier and timestamp:
checkpoint_<ip>_<port>_<YYYYmmddHHMMSS>.tar(dots in the IP are converted to underscores).
- Checkpoints are written to a
-
Triggering checkpoints:
- Admins can trigger a checkpoint from the naming server (if supported) or by sending an explicit checkpoint request to a storage server. The exact mechanism depends on your deployment; the common approaches are:
- A naming-server-level
CHECKPOINTcontrol command that instructs all registered storage servers to create a checkpoint. - An admin script that connects to a storage server's control port and requests a checkpoint.
- A naming-server-level
- Admins can trigger a checkpoint from the naming server (if supported) or by sending an explicit checkpoint request to a storage server. The exact mechanism depends on your deployment; the common approaches are:
-
Restore behavior:
- On startup, a storage server will look for the most recent checkpoint file for its identifier in
./checkpoints/and, if found, attempt to restore files and metadata from it before processing client requests. - If a restore fails or no checkpoint is found, the server will continue with files present on disk in its storage directory.
- On startup, a storage server will look for the most recent checkpoint file for its identifier in
-
Notes & limitations:
- Checkpointing is scoped to a single storage server. There is no global, coordinated snapshot across multiple storage servers; achieving a consistent global snapshot requires quiescing clients or using an external orchestration procedure.
- Creating a checkpoint can be I/O intensive on large datasets. Schedule checkpoints during low-traffic windows or when the system is quiesced.
- Checkpoint/restore logic is intended for operational convenience and faster recovery; it is not a substitute for external backups or replication.
- When using checkpoints in production, verify permissions and available disk space for the
checkpoints/directory.
Each component logs to:
- Console (stdout) - Important messages
- Log files - Detailed operation logs
NM.log- Name server logsSS.log- Storage server logs
Log format: [timestamp] [level] message
Comprehensive error codes:
ERR_FILE_NOT_FOUND- File doesn't existERR_ACCESS_DENIED- Insufficient permissionsERR_SENTENCE_LOCKED- Sentence being edited by another userERR_INDEX_OUT_OF_RANGE- Invalid sentence/word indexERR_SERVER_UNAVAILABLE- Storage server unreachable- And more...
- TCP sockets for reliable communication
- Custom message protocol with serialization
- Length-prefixed messages to handle variable-length data
- Name Server maintains a Trie-based namespace index.
- Maps pathname → metadata → storage server.
- Frequently accessed metadata is cached using an LRU cache.
- Cache misses fall back to Trie traversal.
- Trie lookup complexity: O(m), where m is the pathname length.
- The Name Server employs an LRU (Least Recently Used) cache to store recently resolved file metadata.
- The cache exploits temporal locality exhibited by filesystem workloads, where recently accessed files are more likely to be accessed again.
On every metadata lookup:
- Check the LRU cache.
- On a cache hit, return the cached metadata immediately.
- On a cache miss, perform Trie traversal.
- Insert the resolved metadata into the cache.
- Evict the least recently used entry when the cache reaches capacity.
This design improves average lookup latency while keeping memory usage bounded.
- Owner-based permissions (first user in ACL)
- Hierarchical access: WRITE implies READ
- Stored in memory at name server
- One-level undo per file
- Previous version saved before each write
- File-level undo (not user-specific)
# Terminal 1: Start name server
./naming_server
# Terminal 2: Start storage server
./storage_server 127.0.0.1 8080 9001
# Terminal 3: Client 1
./client 127.0.0.1 8080
# Username: user1
nfs> CREATE test.txt
nfs> WRITE test.txt 0
Client: 1 Hello world
Client: ETIRW
nfs> READ test.txt
# Terminal 4: Client 2
./client 127.0.0.1 8080
# Username: user2
nfs> READ test.txt # Should fail (no access)
nfs> LIST # Should show user1, user2# Client 1
nfs> WRITE test.txt 0 # Lock sentence 0
# Client 2 (while Client 1 is writing)
nfs> WRITE test.txt 1 # Should succeed (different sentence)
nfs> WRITE test.txt 0 # Should fail (sentence locked)# Client 1 (owner)
nfs> CREATE myfile.txt
nfs> ADDACCESS -R myfile.txt user2
nfs> INFO myfile.txt
# Client 2
nfs> READ myfile.txt # Should succeed
nfs> WRITE myfile.txt 0 # Should fail (read-only)- Single name server (no redundancy)
- One-level undo only
- No file versioning
- In-memory access control (lost on restart)
- No encryption for data transfer
- Limited to text files only
- LRU cache is maintained locally within the Name Server and is not replicated.
- Name server replication for fault tolerance
- Multi-level undo/redo
- File versioning and history
- Persistent access control lists
- SSL/TLS encryption
- Binary file support
- File compression
- Distributed metadata cache shared across replicated Name Servers
- Load balancing across storage servers
- Ensure name server is running
- Check IP and port are correct
- Verify no firewall blocking connections
- File may not exist - use
VIEW -ato list all files - Check spelling of filename
- Ensure storage server containing file is running
- You don't have permissions for this file
- Ask owner to grant access via ADDACCESS
- Use INFO to check current access list
- Another user is currently editing this sentence
- Wait for them to finish (ETIRW)
- Edit a different sentence
- Storage server crashed or disconnected
- Restart storage server and try again
- POSIX Socket Programming: https://pubs.opengroup.org/onlinepubs/9699919799/
- Network File Systems: RFC 1094, RFC 1813
- Trie Data Structure: Efficient string matching
This project is an academic implementation for educational purposes.
Developed as part of Operating Systems and Networks course project