Network File Systems

[!NOTE] This module explores the core principles of Network File Systems, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.

1. Files Over The Wire

A Network File System allows a client to access files on a remote server as if they were local. Ideally, the user (and the read() syscall) shouldn’t know the difference.

The Challenge: “State”

If you open a file on your laptop, the OS remembers your cursor position (Offset).

  • Local: The OS Kernel holds the struct file in memory.
  • Remote: Who holds the state? The Client or the Server?

NFSv3 (Stateless)

  • Design: The Server is dumb. It doesn’t know who has the file open.
  • Protocol: Every request must contain all context (File Handle, Offset, Credentials).
  • Pros: Server crash recovery is trivial (just reboot, no state to lose).
  • Cons: No easy way to implement file locking or cache coherency.

NFSv4 (Stateful)

  • Design: The Server remembers “Client A has File X open”.
  • Pros: Supports locking and delegations (caching).
  • Cons: Complex recovery (Lease expiration, Grace periods).

2. Distributed Locking

What happens if Client A and Client B write to the same file on Server S? Without locking, “Last Writer Wins” (Data Corruption).

Advisory vs Mandatory Locking:

  • Advisory (Unix/NFS): Processes must voluntarily check for locks. If vim checks but echo doesn’t, echo can overwrite vim.
  • Mandatory (Windows/SMB): The OS enforces the lock. Any write to a locked file fails.

3. Interactive: RPC Packet Visualizer

Trace the lifecycle of a remote read operation.

💻
Client
RPC
🖥️
Server
Idle

Network Idle.

4. Code Examples: File Locking

Even on a local disk, file locking is crucial for concurrency.

import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;

public class LockDemo {
    public static void main(String[] args) throws Exception {
        RandomAccessFile file = new RandomAccessFile("shared.db", "rw");
        FileChannel channel = file.getChannel();

        System.out.println("Acquiring lock...");

        // 1. Acquire Lock (Blocks if already locked)
        // Wraps fcntl() or flock() syscall
        try (FileLock lock = channel.lock()) {
            System.out.println("Lock held! Writing critical section...");
            Thread.sleep(2000); // Simulate work
        } catch (OverlappingFileLockException e) {
            System.out.println("Already locked by this thread!");
        }

        // 2. Lock is released automatically by try-with-resources
        System.out.println("Lock released.");
    }
}
package main

import (
	"fmt"
	"os"
	"syscall"
	"time"
)

func main() {
	file, _ := os.Create("shared.db")
	defer file.Close()

	fd := int(file.Fd())

	fmt.Println("Acquiring lock...")

	// 1. Acquire Exclusive Lock (LOCK_EX)
	// Uses 'flock' syscall directly
	err := syscall.Flock(fd, syscall.LOCK_EX)
	if err != nil {
		panic(err)
	}

	fmt.Println("Lock held! Writing critical section...")
	time.Sleep(2 * time.Second)

	// 2. Release Lock (LOCK_UN)
	syscall.Flock(fd, syscall.LOCK_UN)
	fmt.Println("Lock released.")
}