RFC 1094

NFS: Network File System Protocol specification

๐Ÿ“… published March 1989, from ietf

RFC 1094: NFS: Network File System Protocol Specification ๐ŸŽ€

Network Working Group ๐Ÿ’– Sun Microsystems, Inc. ๐Ÿ’… March 1989 ๐Ÿฆ‹

okay bestie, this is the RFC that defines NFS version two ๐Ÿงš. Sun Microsystems wrote it and dropped it in March 1989. The header doesn't name a category or list anything it obsoletes or updates, so there's nothing extra to track there โœจ.


Status of This Memo โœจ

okay so this RFC describes a protocol that Sun Microsystems, Inc., and others are already using ๐ŸŒธ. A newer version is in the works, but other people can still benefit from the descriptions of the current one and from the talk about some of the design issues ๐Ÿ’œ. Distribution of this memo is unlimited ๐Ÿซถ.

1. Introduction โœจ

so NFS, the Sun Network Filesystem protocol, gives you transparent remote access to shared files across networks ๐ŸŒ. It's built to be portable across different machines, operating systems, network architectures, and transport protocols, and that's possible because it uses Remote Procedure Call (RPC) primitives sitting on top of eXternal Data Representation (XDR) ๐Ÿฆ‹. Implementations already exist for a whole bunch of machines, from personal computers all the way up to supercomputers ๐Ÿ”ฅ. iconic range, honestly ๐Ÿ‘‘.

the supporting mount protocol lets the server hand out remote access privileges to a restricted set of clients ๐Ÿ”. It handles the operating system-specific stuff that lets you do things like attach remote directory trees to some local file system ๐ŸŒˆ.

1.1. Remote Procedure Call

okay so Sun's Remote Procedure Call specification gives you a procedure-oriented interface to remote services ๐Ÿ’…. Each server supplies a "program", which is just a set of procedures, and NFS is one of those programs ๐Ÿงš. The combination of host address, program number, and procedure number is what pins down one remote procedure ๐Ÿ“.

here's the goal that's giving main character energy: NFS was never supposed to need any specific level of reliability from its lower levels ๐Ÿ™ˆ. That means it could potentially run on lots of underlying transport protocols, or even on a different remote procedure call implementation entirely โœจ. For ease of discussion, the rest of this document assumes NFS is implemented on top of Sun RPC, which is described in RFC 1057, "RPC: Remote Procedure Call Protocol Specification" ๐Ÿ“š.

1.2. External Data Representation

the eXternal Data Representation (XDR) standard gives everyone a common way to represent a set of data types over a network ๐ŸŽ€. The NFS Protocol Specification is written in the RPC data description language ๐Ÿ’œ. If you want more info, check RFC 1014, "XDR: External Data Representation Standard" ๐Ÿค“. Automated RPC/XDR compilers exist to generate server and client "stubs", but NFS doesn't require you to use them ๐Ÿ™Œ. Any software that provides equivalent functionality can be used, and if the encoding is exactly the same, it can interoperate with other NFS implementations ๐Ÿซถ.

1.3. Stateless Servers

the NFS protocol was intended to be as stateless as possible, meaning a server shouldn't need to keep any protocol state information about any of its clients in order to work correctly ๐ŸงŠ. Stateless servers have a really distinct advantage over stateful ones when something fails ๐Ÿ’ฅ. With a stateless server, a client only needs to keep retrying a request until the server responds ๐Ÿ”. It doesn't even need to know the server crashed, or that the network briefly went down ๐Ÿ˜Œ. A client of a stateful server, on the other hand, has to either detect the failure and rebuild the server's state when it comes back up, or let client operations fail ๐Ÿ˜ญ.

this might not sound like a big deal, but it changes the protocol in some unexpected ways ๐Ÿ™ˆ. We feel it may be worth a bit of extra complexity in the protocol so you can write very simple servers that don't need fancy crash recovery ๐Ÿงš. Note that even if a so-called "reliable" transport protocol such as TCP is used, the client still has to handle interruptions of service by re-opening connections when they time out ๐Ÿ”Œ. So a stateless protocol may actually simplify the implementation ๐Ÿ’–.

but also, NFS deals with objects like files and directories that inherently have state ๐Ÿ“. what good would a file be if it didn't keep its contents intact? ๐Ÿฅบ The goal was to not introduce any extra state in the protocol itself ๐ŸŒท. Inherently stateful operations, like file or record locking and remote execution, were implemented as separate services and aren't described in this document ๐Ÿ”’.

the basic way to simplify recovery was to make operations as "idempotent" as possible, so they can potentially be repeated ๐Ÿ”„. Some operations in this version of the protocol didn't reach that goal, but luckily most of the operations, such as Read and Write, are idempotent ๐Ÿ™Œ. Also, most server failures happen between operations, not between receiving an operation and sending the response โฑ๏ธ. Finally, even though actual server failures may be rare, in complex networks, failures of any network, router, or bridge can look exactly like a server failure ๐ŸŒ.

2. NFS PROTOCOL DEFINITION ๐Ÿ‘‘

servers change over time, and so can the protocol they use ๐Ÿ”„. RPC gives every RPC request a version number ๐Ÿ”ข. This RFC describes version two of the NFS protocol ๐Ÿ’œ. Even in version two there are a few obsolete procedures and parameters, which will be removed in later versions ๐Ÿ—‘๏ธ. An RFC for version three of the NFS protocol is currently under preparation ๐Ÿ‘€.

2.1. File System Model

NFS assumes a hierarchical file system, where directories are all but the bottom level of files ๐Ÿ—‚๏ธ. Each entry in a directory (file, directory, device, etc.) has a string name ๐Ÿท๏ธ. Different operating systems may restrict the depth of the tree or the names used, and they may use different syntax to represent the "pathname", which is the concatenation of all the "components" (directory and file names) in the name ๐Ÿงต. A "file system" is a tree on a single server (usually a single disk or physical partition) with a specified "root" ๐ŸŒณ. Some operating systems provide a "mount" operation to make all file systems appear as a single tree, while others maintain a "forest" of file systems ๐ŸŒฒ. Files are unstructured streams of uninterpreted bytes ๐ŸŒŠ. Version 3 of NFS uses a slightly more general file system model โœจ.

NFS looks up one component of a pathname at a time ๐Ÿ‘ฃ. It might not be obvious why it doesn't just take the whole pathname, traipse down the directories, and return a file handle when it's done ๐Ÿค”. There are several good reasons for that ๐Ÿ’…. First, pathnames need separators between the directory components, and different operating systems use different separators ๐Ÿช“. We could define a Network Standard Pathname Representation, but then every pathname would have to be parsed and converted at each end ๐Ÿ˜ฉ. Other issues are discussed in section 3, NFS Implementation Issues ๐Ÿ“–.

even though files and directories are similar objects in many ways, different procedures are used to read them ๐Ÿ™ƒ. This gives a network standard format for representing directories ๐Ÿ“‹. The same argument could have justified a procedure that returns only one directory entry per call ๐Ÿคท. The problem is efficiency ๐Ÿข. Directories can contain many entries, and a remote call to return each one would be just too slow ๐Ÿ’€.

2.2. Server Procedures

the protocol definition is given as a set of procedures, with arguments and results defined using the RPC language (the XDR language extended with program, version, and procedure declarations) ๐Ÿ“œ. A brief description of what each procedure does should give you enough info to implement it โœ๏ธ. Section 2.3 goes into the basic data types in more detail ๐Ÿ’–.

all of the procedures in the NFS protocol are assumed to be synchronous ๐Ÿซถ. When a procedure returns to the client, the client can assume the operation has completed and any data tied to the request is now on stable storage ๐Ÿ’พ. For example, a client WRITE request may cause the server to update data blocks, filesystem information blocks (like indirect blocks), and file attribute information (size and modify times) ๐Ÿงฑ. When the WRITE returns, the client can assume the write is safe, even if the server crashes, and it can discard the data it wrote ๐Ÿ—‘๏ธ. This is a really important part of the server's statelessness ๐Ÿ‘‘. If the server waited to flush data from remote requests, the client would have to save those requests so it could resend them in case of a server crash ๐Ÿ˜ฑ.

        /*
         * Remote file service routines
         */
        program NFS_PROGRAM {
                version NFS_VERSION {
                        void
                        NFSPROC_NULL(void)              = 0;

                        attrstat
                        NFSPROC_GETATTR(fhandle)        = 1;

                        attrstat
                        NFSPROC_SETATTR(sattrargs)      = 2;

                        void
                        NFSPROC_ROOT(void)              = 3;

                        diropres
                        NFSPROC_LOOKUP(diropargs)       = 4;

                        readlinkres
                        NFSPROC_READLINK(fhandle)       = 5;

                        readres
                        NFSPROC_READ(readargs)          = 6;

                        void
                        NFSPROC_WRITECACHE(void)        = 7;
                        attrstat
                        NFSPROC_WRITE(writeargs)        = 8;

                        diropres
                        NFSPROC_CREATE(createargs)      = 9;

                        stat
                        NFSPROC_REMOVE(diropargs)       = 10;

                        stat
                        NFSPROC_RENAME(renameargs)      = 11;

                        stat
                        NFSPROC_LINK(linkargs)          = 12;

                        stat
                        NFSPROC_SYMLINK(symlinkargs)    = 13;

                        diropres
                        NFSPROC_MKDIR(createargs)       = 14;

                        stat
                        NFSPROC_RMDIR(diropargs)        = 15;

                        readdirres
                        NFSPROC_READDIR(readdirargs)    = 16;

                        statfsres
                        NFSPROC_STATFS(fhandle)         = 17;
                } = 2;
        } = 100003;

2.2.1. Do Nothing ๐Ÿ’ค

        void
        NFSPROC_NULL(void) = 0;

this procedure does no work ๐Ÿ˜ด. It's made available in all RPC services so you can test server response and timing โฒ๏ธ.

2.2.2. Get File Attributes ๐Ÿ”

        attrstat
        NFSPROC_GETATTR (fhandle) = 1;

if the reply status is NFS_OK, then the reply attributes contain the attributes for the file given by the input fhandle โœ….

2.2.3. Set File Attributes ๐Ÿ› ๏ธ

        struct sattrargs {
                fhandle file;
                sattr attributes;
        };

        attrstat
        NFSPROC_SETATTR (sattrargs) = 2;

the "attributes" argument contains fields that are either -1 or the new value for the attributes of "file" ๐ŸŽฏ. If the reply status is NFS_OK, then the reply attributes have the attributes of the file after the "SETATTR" operation has completed ๐Ÿงพ.

Notes: the use of -1 to indicate an unused field in "attributes" is changed in the next version of the protocol ๐Ÿšจ

2.2.4. Get Filesystem Root ๐Ÿงš

           void
           NFSPROC_ROOT(void) = 3;

Obsolete. okay so this one is retired ๐Ÿ’€ we don't use it anymore bc finding the root file handle of a filesystem means moving pathnames between client and server, and to do that right we'd need a whole network standard way of representing pathnames ๐Ÿ˜ญ that's a lot. so now the job of looking up the root file handle is handled by the MNTPROC_MNT procedure instead โœจ (see Appendix A, "Mount Protocol Definition", for the full tea ๐Ÿซถ)

2.2.5. Look Up File Name ๐Ÿ”

           diropres
           NFSPROC_LOOKUP(diropargs) = 4;

ok this one is giving detective energy ๐Ÿ•ต๏ธโ€โ™€๏ธ if the reply status is NFS_OK, then the reply file and reply attributes are the file handle and attributes for the file name in the directory given by dir in the argument. literally that's it, we found her ๐Ÿ’–

           union readlinkres switch (stat status) {
           case NFS_OK:
               path data;
           default:
               void;
           };
           readlinkres
           NFSPROC_READLINK(fhandle) = 5;

so if status is NFS_OK, the reply data is whatever's inside the symbolic link that the fhandle argument points at ๐Ÿฆ‹

Notes: since NFS always parses pathnames on the client, the pathname in a symbolic link might mean something totally different (or nothing at all) on a different client or on the server, if a different pathname syntax is used. the way pathnames can shapeshift across machines is kind of a lot ๐Ÿ’€

2.2.7. Read From File ๐Ÿ“–

           struct readargs {
                   fhandle file;
                   unsigned offset;
                   unsigned count;
                   unsigned totalcount;
           };

           union readres switch (stat status) {
           case NFS_OK:
                   fattr attributes;
                   nfsdata data;
           default:
                   void;
           };

           readres
           NFSPROC_READ(readargs) = 6;

this one returns up to count bytes of data from the file given by file, starting at offset bytes from the beginning of the file. the first byte of the file is at offset zero (very important, don't be a menace about it ๐Ÿ™ˆ). the file attributes after the read happens get returned in attributes, so you always see the latest version of the file ๐ŸŒธ

Notes: the argument totalcount is unused, and it's getting removed in the next protocol revision. bye bye ๐Ÿ‘‹

2.2.8. Write to Cache ๐Ÿง

           void
           NFSPROC_WRITECACHE(void) = 7;

this one is reserved for the next protocol revision. it's a future era thing ๐Ÿ”ฎ

2.2.9. Write to File โœ๏ธ

           struct writeargs {
                   fhandle file;
                   unsigned beginoffset;
                   unsigned offset;
                   unsigned totalcount;
                   nfsdata data;
           };

           attrstat
           NFSPROC_WRITE(writeargs) = 8;

this writes data beginning offset bytes from the beginning of file. again, the first byte of the file is at offset zero, we're not gonna say it again ๐Ÿ˜ค if the reply status is NFS_OK, then the reply attributes contains the attributes of the file after the write has completed. the write operation is atomic ๐Ÿ”ฅ and data from this "WRITE" will not get mixed with data from another client's "WRITE". no chaos, no blending, iconic behaviour ๐Ÿ’…

Notes: the arguments beginoffset and totalcount are ignored, and they're getting removed in the next protocol revision ๐Ÿซ 

2.2.10. Create File ๐ŸŒฑ

           struct createargs {
                   diropargs where;
                   sattr attributes;
           };

           diropres
           NFSPROC_CREATE(createargs) = 9;

the file name gets created in the directory given by dir, and its initial attributes come from attributes. a reply status of NFS_OK means the file was created, and the reply file and reply attributes are its file handle and attributes โœจ any other reply status means the operation failed and no file was created. so it's either a yes or a no, no in-between ๐Ÿ“

Notes: this routine should pass an exclusive create flag, meaning "create the file only if it is not already there". basically don't overwrite somebody's stuff ๐Ÿ™…โ€โ™€๏ธ

2.2.11. Remove File ๐Ÿ—‘๏ธ

           stat
           NFSPROC_REMOVE(diropargs) = 10;

the file name gets removed from the directory given by dir. a reply of NFS_OK means the directory entry was removed โœ…

Notes: possibly non-idempotent operation. (translation: doing this twice might not give you the same vibe the second time ๐Ÿ˜ญ)

2.2.12. Rename File โœ๏ธ

           struct renameargs {
                   diropargs from;
                   diropargs to;
           };

           stat
           NFSPROC_RENAME(renameargs) = 11;

the existing file from.name in the directory given by from.dir gets renamed to to.name in the directory given by to.dir. if the reply is NFS_OK, the file was renamed ๐ŸŽ€ the RENAME operation is atomic on the server, which means it cannot be interrupted in the middle. no half-renamed files, we don't do that here ๐Ÿ™…โ€โ™€๏ธ

Notes: possibly non-idempotent operation.

Procedure 12, Version 2.

           struct linkargs {
                   fhandle from;
                   diropargs to;
           };

           stat
           NFSPROC_LINK(linkargs) = 12;

this creates the file to.name in the directory given by to.dir, and it's a hard link to the existing file given by from. if the return value is NFS_OK, a link was created. any other return value means there was an error and the link was not created ๐Ÿ’”

ok the vibe for a hard link is that changes to either of the linked files should show up in both files ๐Ÿ‘ฏโ€โ™€๏ธ also, when a hard link is made to a file, the attributes for the file should have a value for nlink that is one greater than the value before the link. so the count goes up by one, very simple, very iconic โœจ

Notes: possibly non-idempotent operation.

           struct symlinkargs {
                   diropargs from;
                   path to;
                   sattr attributes;
           };

           stat
           NFSPROC_SYMLINK(symlinkargs) = 13;

this creates the file from.name with ftype NFLNK in the directory given by from.dir. the new file contains the pathname to and has initial attributes given by attributes. if the return value is NFS_OK, a link was created. any other return value means there was an error and the link was not created ๐Ÿ™ƒ

a symbolic link is basically a pointer to another file ๐Ÿ‘‰ the name given in to is not interpreted by the server, it's only stored in the newly created file. when the client references a file that's a symbolic link, the contents usually get transparently reinterpreted as a pathname to substitute. a READLINK operation hands the data back to the client so the client can do the interpreting ๐Ÿงš

Notes: on UNIX servers the attributes are never used, since symbolic links always have mode 0777. so the attributes are basically ignored there, it's giving "vibes only" ๐ŸŒˆ

2.2.15. Create Directory ๐Ÿ“

           diropres
           NFSPROC_MKDIR (createargs) = 14;

the new directory where.name is created in the directory given by where.dir. its initial attributes are given by attributes. a reply status of NFS_OK means the new directory was created, and the reply file and reply attributes are its file handle and attributes. any other reply status means the operation failed and no directory was created ๐ŸŒท

Notes: possibly non-idempotent operation.

2.2.16. Remove Directory ๐Ÿซง

           stat
           NFSPROC_RMDIR(diropargs) = 15;

the existing empty directory name in the directory given by dir gets removed. if the reply is NFS_OK, the directory was removed ๐Ÿ‘‹ (the "empty" part is doing a lot of work here, bestie ๐Ÿ˜ญ)

Notes: possibly non-idempotent operation.

2.2.17. Read From Directory ๐Ÿ“š

           struct readdirargs {
                   fhandle dir;
                   nfscookie cookie;
                   unsigned count;
           };

           struct entry {
                   unsigned fileid;
                   filename name;
                   nfscookie cookie;
                   entry *nextentry;
           };

           union readdirres switch (stat status) {
           case NFS_OK:
                   struct {
                           entry *entries;
                           bool eof;
                   } readdirok;
           default:
                   void;
           };

           readdirres
           NFSPROC_READDIR (readdirargs) = 16;

this returns a variable number of directory entries, with a total size of up to count bytes, from the directory given by dir. if the returned value of status is NFS_OK, it's followed by a variable number of entrys. each entry has a fileid, which is a unique number that identifies the file within a filesystem, the name of the file, and a cookie, which is an opaque pointer to the next entry in the directory ๐Ÿง

the cookie is used in the next READDIR call to get more entries starting at a given point in the directory. the special cookie zero (all bits zero) can be used to get the entries starting at the beginning of the directory. the fileid field should be the same number as the fileid in the the attributes of the file. (see section "2.3.5. fattr" under "Basic Data Types".) and the eof flag has a value of TRUE if there are no more entries in the directory. the end of the line, literally ๐ŸŽฌ

2.2.18. Get Filesystem Attributes ๐Ÿ“Š

           union statfsres (stat status) {
           case NFS_OK:
               struct {
                   unsigned tsize;
                   unsigned bsize;
                   unsigned blocks;
                   unsigned bfree;
                   unsigned bavail;
               } info;
           default:
                   void;
           };

           statfsres
           NFSPROC_STATFS(fhandle) = 17;

if the reply status is NFS_OK, then the reply info gives the attributes for the filesystem that contains the file referred to by the input fhandle. the attribute fields hold these values:

  • tsize โ€” the optimum transfer size of the server in bytes. this is the number of bytes the server would like to have in the data part of READ and WRITE requests ๐Ÿ’–
  • bsize โ€” the block size in bytes of the filesystem ๐Ÿงฑ
  • blocks โ€” the total number of bsize blocks on the filesystem ๐Ÿงฎ
  • bfree โ€” the number of free bsize blocks on the filesystem ๐Ÿซง
  • bavail โ€” the number of bsize blocks available to non-privileged users ๐Ÿ‘‘

Notes: this call does not work well if a filesystem has variable size blocks. so if your blocks are shapeshifting, this one's gonna get confused ๐Ÿ™ˆ


2.3. Basic Data Types ๐Ÿ’œ

The following XDR definitions are basic structures and types used in other structures described further on. these are the building blocks, so pay attention, the rest of the spec leans on them ๐Ÿงš

2.3.1. stat

Okay bestie, stat is the enum that comes back with literally every procedure's results ๐Ÿ’… NFS_OK (value 0) means the call worked and the results are good to use โœจ Everything else means the server hit some kind of error while handling the procedure. These error values are based on UNIX error numbers, so if you've seen those before, they'll feel familiar ๐Ÿฆ‹

enum stat {
    NFS_OK = 0,
    NFSERR_PERM=1,
    NFSERR_NOENT=2,
    NFSERR_IO=5,
    NFSERR_NXIO=6,
    NFSERR_ACCES=13,
    NFSERR_EXIST=17,
    NFSERR_NODEV=19,
    NFSERR_NOTDIR=20,
    NFSERR_ISDIR=21,
    NFSERR_FBIG=27,
    NFSERR_NOSPC=28,
    NFSERR_ROFS=30,
    NFSERR_NAMETOOLONG=63,
    NFSERR_NOTEMPTY=66,
    NFSERR_DQUOT=69,
    NFSERR_STALE=70,
    NFSERR_WFLUSH=99
};

Here's the breakdown of each error, no cap:

  • NFSERR_PERM ๐Ÿ™…โ€โ™€๏ธ: Not owner. The caller doesn't have the right ownership to do the operation they asked for.
  • NFSERR_NOENT ๐Ÿ”: No such file or directory. The file or directory they named doesn't exist.
  • NFSERR_IO ๐Ÿ’ฅ: Some kind of hard error happened while the operation was running. Think a disk error, for example.
  • NFSERR_NXIO ๐Ÿซฅ: No such device or address.
  • NFSERR_ACCES ๐Ÿšซ: Permission denied. The caller doesn't have the correct permission for the operation.
  • NFSERR_EXIST ๐Ÿ“: File exists. The file they specified is already there.
  • NFSERR_NODEV ๐Ÿ”Œ: No such device.
  • NFSERR_NOTDIR ๐Ÿ™ƒ: Not a directory. The caller gave a non-directory in a directory operation.
  • NFSERR_ISDIR ๐Ÿ“‚: Is a directory. The caller gave a directory in a non-directory operation.
  • NFSERR_FBIG ๐Ÿ˜: File too large. The operation made a file grow past the server's limit.
  • NFSERR_NOSPC ๐Ÿ˜ฉ: No space left on device. The operation filled up the server's filesystem.
  • NFSERR_ROFS ๐Ÿ”’: Read-only filesystem. Somebody tried to write to a read-only filesystem.
  • NFSERR_NAMETOOLONG ๐Ÿ“: File name too long. The file name in the operation was too long.
  • NFSERR_NOTEMPTY ๐Ÿ—‘๏ธ: Directory not empty. They tried to remove a directory that still has stuff in it.
  • NFSERR_DQUOT ๐Ÿ’ธ: Disk quota exceeded. The client's disk quota on the server has been used up.
  • NFSERR_STALE ๐Ÿ‘ป: The fhandle in the arguments was invalid. The file that handle pointed to doesn't exist anymore, or access to it got revoked.
  • NFSERR_WFLUSH ๐Ÿงน: The server's write cache, the one used in the WRITECACHE call, got flushed to disk.

2.3.2. ftype

This enum tells you what kind of file you're looking at ๐ŸŒธ NFNON is a non-file, NFREG is a regular file, NFDIR is a directory, NFBLK is a block-special device, NFCHR is a character-special device, and NFLNK is a symbolic link.

enum ftype {
    NFNON = 0,
    NFREG = 1,
    NFDIR = 2,
    NFBLK = 3,
    NFCHR = 4,
    NFLNK = 5
};

2.3.3. fhandle

This is the file handle that gets passed back and forth between the server and the client ๐ŸŽ€ Every file operation uses file handles to point at a file or directory. The handle can hold whatever info the server needs to tell one specific file apart from the rest.

typedef opaque fhandle[FHSIZE];

2.3.4. timeval

A timeval is the number of seconds and microseconds since midnight, January 1, 1970, Greenwich Mean Time โฐ It's how time and date info gets passed around.

struct timeval {
    unsigned int seconds;
    unsigned int useconds;
};

2.3.5. fattr

Okay, fattr is where all the file attributes live ๐Ÿ—‚๏ธ Here's what each field means:

  • type is the type of the file.
  • nlink is the number of hard links to the file, which is how many different names point at the same file.
  • uid is the user ID number of the file's owner.
  • gid is the group ID number of the file's group.
  • size is the file's size in bytes.
  • blocksize is the size in bytes of one block of the file.
  • rdev is the device number of the file, but only if it's type NFCHR or NFBLK.
  • blocks is how many blocks the file takes up on disk.
  • fsid is the file system identifier for the filesystem that holds the file.
  • fileid is a number that uniquely identifies the file inside its filesystem.
  • atime is the last time the file was accessed for a read or a write.
  • mtime is the last time the file data was modified (written).
  • ctime is the last time the file's status changed. Writing to the file also changes ctime if the file's size changes.
struct fattr {
    ftype        type;
    unsigned int mode;
    unsigned int nlink;
    unsigned int uid;
    unsigned int gid;
    unsigned int size;
    unsigned int blocksize;
    unsigned int rdev;
    unsigned int blocks;
    unsigned int fsid;
    unsigned int fileid;
    timeval      atime;
    timeval      mtime;
    timeval      ctime;
};

Now, mode is the access mode, stored as a set of bits ๐Ÿ” Heads up, the file type shows up both in the mode bits and in the type field. This is honestly a bug in the protocol, and it's going to be fixed in future versions ๐Ÿ™ˆ The bit positions below are written in octal.

The file type appears twice, once in the mode bits and once in type. That's a known protocol bug, and it's supposed to get fixed in future versions. Don't let it trip you up!
0040000 This is a directory; "type" field should be NFDIR.
0020000 This is a character special file; "type" field should
        be NFCHR.
0060000 This is a block special file; "type" field should be
        NFBLK.
0100000 This is a regular file; "type" field should be NFREG.
0120000 This is a symbolic link file;  "type" field should be
        NFLNK.
0140000 This is a named socket; "type" field should be NFNON.
0004000 Set user id on execution.
0002000 Set group id on execution.
0001000 Save swapped text even after use.
0000400 Read permission for owner.
0000200 Write permission for owner.
0000100 Execute and search permission for owner.
0000040 Read permission for group.
0000020 Write permission for group.
0000010 Execute and search permission for group.

0000004 Read permission for others.
0000002 Write permission for others.
0000001 Execute and search permission for others.

Notes, because they matter ๐Ÿ“ The bits match the mode bits that UNIX's stat(2) system call returns. The file type is in both the mode bits and the file type field, and that gets fixed in future versions. Also, the rdev field is an operating system specific device specifier, and it's going to be removed and generalized in the next revision of the protocol ๐Ÿฆ‹

2.3.6. sattr

sattr is the set of file attributes the client is allowed to set ๐ŸŒท The fields match fattr above. A size of zero means the file should be truncated โœ‚๏ธ And a value of -1 means that field should be ignored.

struct sattr {
    unsigned int mode;
    unsigned int uid;
    unsigned int gid;
    unsigned int size;
    timeval      atime;
    timeval      mtime;
};

2.3.7. filename

This type is used for passing file names or pathname components ๐Ÿ“›

typedef string filename<MAXNAMLEN>;

2.3.8. path

path is a pathname ๐Ÿ›ค๏ธ The server treats it as a plain string with no internal structure. The client, though, sees it as the name of a node in a filesystem tree.

typedef string path<MAXPATHLEN>;

2.3.9. attrstat

attrstat is a common procedure result ๐Ÿ’– It holds a status and, if the call succeeded, the attributes of the file the operation was done on.

union attrstat switch (stat status) {
case NFS_OK:

    fattr attributes;
default:
    void;
};

2.3.10. diropargs

diropargs is used for directory operations, meaning operations that change the directory itself ๐Ÿ“‚ The fhandle called dir is the directory where the file name gets looked up.

struct diropargs {
    fhandle  dir;
    filename name;
};

2.3.11. diropres

Directory operation results come back in a diropres structure ๐ŸŽ If the call worked, you get a new file handle called file, plus the attributes for that file, along with the status.

union diropres switch (stat status) {
case NFS_OK:
    struct {
        fhandle file;
        fattr   attributes;
    } diropok;
default:
    void;
};

3. NFS IMPLEMENTATION ISSUES โœจ

NFS was designed so that different operating systems could share files ๐ŸŒˆ But it was designed in a UNIX environment, so a lot of its operations act a lot like the UNIX file system. This section covers some of the implementation-specific details and semantic issues.

3.1. Server/Client Relationship

The NFS protocol is built to keep servers as simple and general as possible ๐Ÿงš Sometimes the server being that simple is a problem, especially if the client wants to implement complicated filesystem semantics.

Take removing open files, for example ๐Ÿ“„ Some operating systems let a process open a file and, while it's still open, remove it from the directory. The process can keep reading and writing the file even though it has no name in the filesystem anymore. A stateless server can't implement those semantics, ngl. The client can do some tricks, like renaming the file on remove and only actually removing it on close. The authors believe the server still gives enough functionality for the client to implement most filesystem semantics ๐Ÿซถ

Every NFS client can also potentially be a server, and remote and local mounted filesystems can be freely mixed together ๐Ÿฆ‹ This causes some interesting problems when a client travels down the directory tree of a remote filesystem and reaches the mount point on the server for another remote filesystem. Letting the server follow that second remote mount would require loop detection, server lookup, and user revalidation. So instead, they decided not to let clients cross a server's mount point ๐Ÿšง When a client does a LOOKUP on a directory where the server has mounted a filesystem, the client sees the underlying directory instead of the mounted one.

For example, say a server has a file system called /usr and mounts another file system on /usr/src. If a client mounts /usr, it NOT sees the mounted version of /usr/src. A client could do remote mounts that match the server's mount points to keep the server's view. In this example, the client would also have to mount /usr/src in addition to /usr, even if they both come from the same server ๐Ÿ’…

3.2. Pathname Interpretation โœจ

okay so the big rule is that pathnames are always parsed on the client ๐Ÿงš, BUT there are a few plot twists ๐Ÿ™ˆ. Like, symbolic links could mean totally different things on different clients ๐Ÿ’€. And for non-UNIX implementations, the special meaning of .. (the parent of a given directory) is a whole mood ๐Ÿ˜ญ. The next revision of the protocol uses an explicit flag to indicate the parent instead ๐Ÿ’…๐Ÿซถ.

3.3. Permission Issues ๐Ÿ”

okay so strictly speaking, the NFS protocol doesn't define the permission checking that servers use ๐Ÿ™ƒ. It's expected that a server will do normal operating system permission checking using AUTH_UNIX style authentication as the basis of its protection mechanism ๐Ÿ›ก๏ธ. The server grabs the client's effective uid, effective gid, and groups on each call and uses them to check permission ๐Ÿ‘€. There are various problems with this method that can be resolved in interesting ways โœจ.

Using uid and gid implies the client and server share the same uid list ๐Ÿ“. Every server and client pair must have the same mapping from user to uid and from group to gid ๐Ÿ“‹. Since every client can also be a server, this tends to imply the whole network shares the same uid/gid space ๐ŸŒ. AUTH_DES (and the next revision of the NFS protocol) uses string names instead of numbers, but there are still complex problems to be solved ๐Ÿงฉ.

Another problem comes from the usually stateful open operation ๐Ÿ˜ค. Most operating systems check permission at open time, and then check that the file is open on each read and write request. With stateless servers, the server has no idea that the file is open, so it must do permission checking on each read and write call. On a local filesystem, a user can open a file and then change the permissions so that no one is allowed to touch it, but they'll still be able to write to the file because it's open ๐Ÿ“‚. On a remote filesystem, by contrast, the write would fail ๐Ÿšซ. To get around this problem, the server's permission checking algorithm should allow the owner of a file to access it regardless of the permission setting ๐Ÿ‘‘.

A similar problem has to do with paging in from a file over the network ๐Ÿ“„. The operating system usually checks for execute permission before opening a file for demand paging, and then reads blocks from the open file. The file may not have read permission, but after it is opened it does not matter ๐Ÿคทโ€โ™€๏ธ. An NFS server cannot tell the difference between a normal file read and a demand page-in read ๐Ÿ™ˆ. To make this work, the server allows reading of files if the uid given in the call has either execute or read permission on the file โœ….

In most operating systems, a particular user (on UNIX, the user ID zero) has access to all files no matter what permission and ownership they have ๐Ÿ‘‘. This "super-user" permission may not be allowed on the server, since anyone who can become super-user on their workstation could gain access to all remote files ๐Ÿ˜ฑ. The UNIX server by default maps user id 0 to -2 before doing its access checking ๐Ÿ”. This works except for NFS root filesystems, where super-user access cannot be avoided ๐Ÿ’€.

3.4. RPC Information ๐Ÿ“ก

  • Authentication: The NFS service uses AUTH_UNIX, AUTH_DES, or AUTH_SHORT style authentication, except in the NULL procedure where AUTH_NONE is also allowed.
  • Transport Protocols: NFS is supported normally on UDP.
  • Port Number: The NFS protocol currently uses the UDP port number 2049. This is not an officially assigned port, so later versions of the protocol use the "Portmapping" facility of RPC.

3.5. Sizes of XDR Structures ๐Ÿ“

okay the numbers time ๐Ÿ”ข These are the sizes, given in decimal bytes, of various XDR structures used in the protocol:

/*
 * The maximum number of bytes of data in a READ or WRITE
 * request.
 */
const MAXDATA = 8192;

/* The maximum number of bytes in a pathname argument. */
const MAXPATHLEN = 1024;

/* The maximum number of bytes in a file name argument. */
const MAXNAMLEN = 255;

/* The size in bytes of the opaque "cookie" passed by READDIR. */
const COOKIESIZE  = 4;

/* The size in bytes of the opaque file handle. */
const FHSIZE = 32;

3.6. Setting RPC Parameters โš™๏ธ

Various file system parameters and options should be set at mount time ๐Ÿ› ๏ธ. The mount protocol is described in the appendix below. For example, "Soft" mounts as well as "Hard" mounts are usually both provided. Soft mounted file systems return errors when RPC operations fail (after a given number of optional retransmissions), while hard mounted file systems continue to retransmit forever โ™พ๏ธ. The maximum transfer sizes are implementation dependent ๐Ÿค“. For efficient operation over a local network, 8192 bytes of data are normally used ๐Ÿš€. This may result in lower-level fragmentation (such as at the IP level) ๐Ÿงฉ. Since some network interfaces may not allow such packets, for operation over slower-speed networks or hosts, or through gateways, transfer sizes of 512 or 1024 bytes often provide better results ๐Ÿข.

Clients and servers may need to keep caches of recent operations to help avoid problems with non-idempotent operations ๐Ÿ—‚๏ธ. For example, if the transport protocol drops the response for a Remove File operation, upon retransmission the server may return an error code of NFSERR_NOENT instead of NFS_OK ๐Ÿ˜ฌ. But if the server keeps around the last operation requested and its result, it could return the proper success code โœ…. Of course, the server could be crashed and rebooted between retransmissions, but a small cache (even a single entry) would solve most problems ๐Ÿ’–.


Appendix A. MOUNT PROTOCOL DEFINITION ๐ŸŽ€

A.1. Introduction ๐Ÿงธ

The mount protocol is separate from, but related to, the NFS protocol. It provides operating system specific services to get the NFS off the ground โ€” looking up server path names, validating user identity, and checking access permissions. Clients use the mount protocol to get the first file handle, which allows them entry into a remote filesystem ๐Ÿ—๏ธ.

The mount protocol is kept separate from the NFS protocol to make it easy to plug in new access checking and validation methods without changing the NFS server protocol ๐Ÿ”Œ.

Notice that the protocol definition implies stateful servers because the server maintains a list of client's mount requests. The mount list information is not critical for the correct functioning of either the client or the server. It is intended for advisory use only, for example, to warn possible clients when a server is going down ๐Ÿ“ข.

Version one of the mount protocol is used with version two of the NFS protocol. The only information communicated between these two protocols is the "fhandle" structure ๐Ÿ’œ.

A.2. RPC Information ๐Ÿ“ก

  • Authentication: The mount service uses AUTH_UNIX and AUTH_NONE style authentication only.
  • Transport Protocols: The mount service is supported on both UDP and TCP.
  • Port Number: Consult the server's portmapper, described in RFC 1057, "RPC: Remote Procedure Call Protocol Specification", to find the port number on which the mount service is registered.

A.3. Sizes of XDR Structures ๐Ÿ“

These are the sizes, given in decimal bytes, of various XDR structures used in the protocol:

/* The maximum number of bytes in a pathname argument. */
const MNTPATHLEN = 1024;
/* The maximum number of bytes in a name argument. */
const MNTNAMLEN = 255;

/* The size in bytes of the opaque file handle. */
const FHSIZE = 32;

A.4. Basic Data Types ๐Ÿงƒ

This section presents the data types used by the mount protocol. In many cases they are similar to the types used in NFS.

A.4.1. fhandle ๐Ÿ”‘

typedef opaque fhandle[FHSIZE];

The type "fhandle" is the file handle that the server passes to the client. All file operations are done using file handles to refer to a file or directory. The file handle can contain whatever information the server needs to distinguish an individual file ๐Ÿซง.

This is the same as the "fhandle" XDR definition in version 2 of the NFS protocol; see section "2.3.3. fhandle" under "Basic Data Types" ๐Ÿ”.

A.4.2. fhstatus ๐Ÿšฆ

union fhstatus switch (unsigned status) {
case 0:
    fhandle directory;
default:
    void;
}

The type "fhstatus" is a union. If a "status" of zero is returned, the call completed successfully, and a file handle for the "directory" follows โœ…. A non-zero status indicates some sort of error. In this case, the status is a UNIX error number ๐Ÿ’€.

A.4.3. dirpath ๐Ÿ“

typedef string dirpath<MNTPATHLEN>;

The type "dirpath" is a server pathname of a directory ๐Ÿ—บ๏ธ.

A.4.4. name ๐Ÿท๏ธ

typedef string name<MNTNAMLEN>;

The type "name" is an arbitrary string used for various names ๐Ÿ’….

A.5. Server Procedures ๐Ÿงšโ€โ™€๏ธ

The following sections define the RPC procedures supplied by a mount server.

/*
 * Protocol description for the mount program
 */
program MOUNTPROG {
        /*
         * Version 1 of the mount protocol used with
         * version 2 of the NFS protocol.
         */
        version MOUNTVERS {

                void
                MOUNTPROC_NULL(void) = 0;

                fhstatus
                MOUNTPROC_MNT(dirpath) = 1;

                mountlist
                MOUNTPROC_DUMP(void) = 2;

                void
                MOUNTPROC_UMNT(dirpath) = 3;

                void
                MOUNTPROC_UMNTALL(void) = 4;

                exportlist
                MOUNTPROC_EXPORT(void)  = 5;
        } = 1;
} = 100005;

A.5.1. Do Nothing ๐Ÿ˜ด

void
MNTPROC_NULL(void) = 0;

This procedure does no work. It is made available in all RPC services to allow server response testing and timing โฑ๏ธ.

A.5.2. Add Mount Entry โž•

fhstatus
MNTPROC_MNT(dirpath) = 1;

If the reply "status" is 0, then the reply "directory" contains the file handle for the directory "dirname". This file handle may be used in the NFS protocol. This procedure also adds a new entry to the mount list for this client mounting "dirname" ๐Ÿ“.

A.5.3. Return Mount Entries ๐Ÿ“œ

struct *mountlist {
        name      hostname;
        dirpath   directory;
        mountlist nextentry;
};

mountlist
MNTPROC_DUMP(void) = 2;

Returns the list of remote mounted filesystems. The "mountlist" contains one entry for each "hostname" and "directory" pair ๐ŸŒธ.

A.5.4. Remove Mount Entry โž–

void
MNTPROC_UMNT(dirpath) = 3;

Removes the mount list entry for the input "dirpath" ๐Ÿงน.

A.5.5. Remove All Mount Entries ๐Ÿงฝ

void
MNTPROC_UMNTALL(void) = 4;

Removes all of the mount list entries for this client โœจ.

A.5.6. Return Export List โœจ

okay bestie, this is the one that tells you what's available to mount ๐Ÿฆ‹ It's a procedure that gives back a whole bunch of export list entries, and each one has a filesystem name plus the groups that are allowed to import it ๐Ÿ’…

The grammar for it looks like this:

struct *groups {
        name grname;
        groups grnext;
};

struct *exportlist {
        dirpath filesys;
        groups groups;
        exportlist next;
};

exportlist
MNTPROC_EXPORT(void) = 5;

So the procedure takes void and returns an exportlist ๐ŸŒธ Each entry has filesys, which is the filesystem name, and groups, which is the list of group names. Every group in that list has a grname and a grnext pointing to the next one, and each export has a next pointing to the next export. The number is 5, so that's the procedure number for MNTPROC_EXPORT โœจ

The variable number part is the iconic bit ๐Ÿ‘‘ The server just returns however many entries it has, and you walk the list until you hit the end.

Notes: the exportlist should contain more info about the status of the filesystem, like a read-only flag ๐Ÿ”’ It's not in there yet, so that's a lowkey gap in the design ๐Ÿ™ˆ


Author's Address ๐ŸŽ€

Bill Nowicki

Sun Microsystems, Inc.

Mail Stop 1-40

2550 Garcia Avenue

Mountain View, CA 94043

Phone: (415) 336-7278

Email: nowicki@SUN.COM ๐Ÿ’œ

๐Ÿ“ฆ where this page comes from