RFC 1094
NFS: Network File System Protocol specification
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 ๐
2.2.6. Read From Symbolic Link ๐
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.
2.2.13. Create Link to File ๐
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.
2.2.14. Create Symbolic Link ๐ช
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 ofbsizeblocks on the filesystem ๐งฎbfreeโ the number of freebsizeblocks on the filesystem ๐ซงbavailโ the number ofbsizeblocks 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๐ป: Thefhandlein 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 theWRITECACHEcall, 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:
typeis the type of the file.nlinkis the number of hard links to the file, which is how many different names point at the same file.uidis the user ID number of the file's owner.gidis the group ID number of the file's group.sizeis the file's size in bytes.blocksizeis the size in bytes of one block of the file.rdevis the device number of the file, but only if it's typeNFCHRorNFBLK.blocksis how many blocks the file takes up on disk.fsidis the file system identifier for the filesystem that holds the file.fileidis a number that uniquely identifies the file inside its filesystem.atimeis the last time the file was accessed for a read or a write.mtimeis the last time the file data was modified (written).ctimeis the last time the file's status changed. Writing to the file also changesctimeif 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, orAUTH_SHORTstyle authentication, except in the NULL procedure whereAUTH_NONEis 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_UNIXandAUTH_NONEstyle 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
- ietf: rfc1094 1989-03
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms