How to Share Directories over NFS with Mac OS X

by Chris BeHanna, chris@behanna.org


NOTE: NONE OF THAT WHICH FOLLOWS APPLIES TO LEOPARD—NONE of it. Using NFS in Leopard is almost fall-over easy:
  1. Edit /etc/exports: sudo vi /etc/exports

    Add a line like the following (presuming your home network is using the 192.168 private network. You could also use 172.17 or 10.):

    /Users -network 192.168.0.0 -mask 255.255.0.0


  2. Enable nfsd: sudo nfsd enable

  3. Test your work: showmount -e

That last command should show that nfsd is sharing the directory you told it to export. Any NFS client on your network should now be able to mount /Users from your Mac, via mount -t nfs, or via autofs or amd. Other Macs on your network that are running Leopard will automatically be able to mount your new NFS share via either shift-cmd-G in the Finder and typing "/net/MyOtherMac/Users" in the text box, or via cd /net/MyOtherMac/Users in Terminal.

Using NFS between Macs is really just an exercise now, as afp sharing is a lot better in Leopard (though I don't know if the password exchange is encrypted—it wasn't in Tiger). If that matters to you, use NFS, though the security caveats mentioned in the following article still apply: all data is sent in the clear unless you tunnel through ssh.

Interestingly, using Howl on your non-Mac NFS servers should enable you to browse to them in the Finder, though I have not personally tried this.

IF YOU'RE STILL USING TIGER, READ ON.

As Mac OS X has evolved, it has become more UNIX-like in many ways. In some respects, that convergence is incomplete, and in other respects, the divergence is deliberate. The ability of a Mac OS X notebook or workstation to share directories over NFS is an example of the incomplete convergence. Storage of configuration information in the NetInfo database is an example of deliberate divergence. This article shows you how to address the NFS side of the world, providing you with a way to flexibly share directories on your hard disk with other machines in your network, while still allowing a powerful and flexible means of access control. In my experience, using NFS works a lot better than using Apple File Sharing when you are not using OS X Server.

But first, a word of caution:


THE INSTRUCTIONS IN THIS ARTICLE REQUIRE YOU TO MAKE CHANGES IN YOUR /System FOLDER, WHICH YOU ARE ORDINARILY NEVER, EVER, SUPPOSED TO TOUCH. I STRONGLY RECOMMENDED THAT YOU SAVE A COPY OF EACH ORIGINAL SYSTEM FILE BEFORE YOU EDIT IT.

THESE INSTRUCTIONS WERE WRITTEN FOR MAC OS X TIGER. THEY ARE NOT GUARANTEED IN ANY WAY, AND THEY MOST LIKELY WILL NOT WORK AT ALL WITH ANY OTHER VERSION OF OS X. PROCEED AT YOUR OWN RISK—REMEMBER, YOU ARE MODIFYING A SYSTEM STARTUP FILE. IF YOU TURN YOUR COMPUTER INTO A BRICK, IT IS ALL YOUR FAULT. I WILL HELP YOU AS I CAN, BUT I OFFER YOU NO WARRANTY OF ANY KIND.

FINALLY, NFS IS NOT BY NATURE A SECURE PROTOCOL. DATA PASSES IN THE CLEAR BETWEEN CLIENT AND SERVER. IT IS INTENDED TO BE USED ONLY WITHIN A PRIVATE NETWORK, BEHIND A FIREWALL. YOU ARE ADVISED NOT TO ACCESS SENSITIVE DATA OVER NFS, UNLESS YOU CAN TUNNEL IT THROUGH SSH, OR UNLESS YOU CAN USE A SECURE, KERBERIZED VERSION OF NFS (OS X DOES NOT SHIP WITH THIS).

CONSIDER YOURSELF WARNED.


With that out of the way ...

Contents

Why Do This?

In my home, I have two Mac OS X computers (a Powerbook and an iMac) as well as a FreeBSD server on which I have for years kept my email, personal files, source code, etc. My goal is to be able to access files on any of them from any other of them (e.g., access files on the iMac from the PowerBook or from the FreeBSD server). At work, I experience a mixed-UNIX environment, with OS X, FreeBSD, and many Linux machines, most of which speak NFS, but very few of which speak Apple protocol. I’d like my PowerBook to be able to access their files seamlessly, and I’d like to be able to access my PowerBook’s files from them.

Even if your needs are not as complex, if you have more than one UNIX-like computer in your home or office (Mac OS X counts!), you may find, as I have, that using NFS is much more flexible and convenient than trying to share home directories around using Apple File Protocol (afp://). If you have a machine running OS X Server, then afp:// may offer you advantages that NFS does not, and some discipline (keep files that you want to protect or share on the server box, not on the client boxes) will pay off; however, most home users and many small businesses are not in that position.

Let’s begin.

Step One: Patch or Replace the NFS Startup Script

As of this writing, OS X Tiger is up to version 10.4.8, with Leopard (10.5) due out soon. In Tiger, the NFS system startup script needs some changes to allow it to decide how to allow clients to connect, to decide which clients it will allow to connect, and to allow configuration changes to become visible without a reboot. Those of you who are more advanced can look at my changes directly here. I’ll assume that you know what to do with the patch command.

Those of you who are not so much with the geek stuff can simply download my version of the NFS startup script here, and skip to "Applying the Patch."


IMPORTANT! SAVE A COPY OF YOUR ORIGINAL, AS-INSTALLED VERSION OF THE NFS STARTUP SCRIPT BEFORE YOU APPLY THE PATCH!


Explanation of the Patch

Wherever possible, I’ve tried to be “Apple-licious” and store and retrieve configuration data from NetInfo instead of the traditional UNIX way of storing it in a gazillion different flat files. Some of Apple’s inherited BSD utilities do not yet know to look in NetInfo; however, so I reach into NetInfo and generate the utility’s configuration file on the fly. In some cases, I make use of NetInfo directories and properties that do not exist in an as-installed copy of OS X Tiger. I will show you how to add them on the following pages.

For those of you who are new to the UNIX world, the NFS startup script is just a Bourne shell script that gets executed by the SystemStarter utility when OS X boots. In addition to adding functionality, my changes allow you to use the script in the more traditional UNIX /etc/init.d sense, in that you can make changes and restart NFS to make those changes visible without having to reboot. I’ll tell you more about that farther down the page.

The first section just gathers together some common filenames that we are going to use throughout the script:

--- /System/Library/StartupItems/NFS/NFS.orig	2006-12-25 15:16:59.000000000 -0500
+++ /System/Library/StartupItems/NFS/NFS	2007-01-10 23:58:09.000000000 -0500
@@ -6,23 +6,26 @@
 
 . /etc/rc.common
 
-AUTOMOUNTDIR=/private/var/automount
+AUTOMOUNTDIR="/private/var/automount"
+AUTOMOUNT_HINTFILE="/Network/.localized"
+AUTOMOUNT_MARKER="/var/run/automount.initialized"
+LOCKFILE="/var/run/NFS.StartupItem"
  

This next section just fixes broken tabbing:

 StartService ()
 {
     CheckForNetwork 
     if [ "${NETWORKUP}" = "-NO-" ]; then exit; fi
-    lockfile -r 0 /var/run/NFS.StartupItem || exit 0
+    lockfile -r 0 $LOCKFILE || exit 0
 
     ##
     # Set up NFS client.
     ##
     echo "Starting network file system"
 
-	if [ -d ${AUTOMOUNTDIR} ]; then
-		chflags -R nouchg ${AUTOMOUNTDIR}
-		rm -rf ${AUTOMOUNTDIR}
-	fi
+    if [ -d ${AUTOMOUNTDIR} ]; then
+        chflags -R nouchg ${AUTOMOUNTDIR}
+        rm -rf ${AUTOMOUNTDIR}
+    fi
 
  

This next section is the first of the real meat of the change. Apple only starts the NFS server (nfsd) and the mount daemon (mountd) if there are actually file systems set up for export, so the first thing that we do is pull them out of NetInfo and use them to generate /etc/exports, then count the number of non-blank, non-comment lines to see if there actually is anything to export. This lets us keep all of our configuration information in one place (NetInfo), yet still support legacy utilities (such as mountd) that do not yet know how to access NetInfo (this is one of the cases in which I have invented my own scheme for storing exports information, as it does not exist by default, nor is there any documentation for what the properties should be or how they are set). We store the count of exports entries in the exports variable:

     # nsfiod is the NFS asynchronous block I/O daemon, which implements
     # NFS read-ahead and write-behind caching on NFS clients.
@@ -38,33 +41,45 @@
     ##
 
     ##
-    # gather list of NFS exports
+    # gather list of NFS exports from NetInfo and generate /etc/exports from it.
+    # This is a stopgap measure until mountd itself learns to consult NetInfo.
     ##
-    exports_ni=$(niutil -list . /exports 2> /dev/null | wc -w)
+
+    echo "# GENERATED FILE - DO NOT EDIT!" > /etc/exports
+    echo "# Make changes under /exports in NetInfo Manager" >> /etc/exports
+
+    for i in `niutil -list . /exports | awk '{print $2}' | sort`
+    do
+        path=`niutil -readprop . /exports/$i path`
+        export_opts=`niutil -readprop . /exports/$i export_opts`
+
+        echo "$path    $export_opts" >> /etc/exports
+    done
+
     # Look for exports in /etc/exports, ignoring comments and blank lines.
     exports_etc=$(grep -v '^[[:space:]]*\(#\|$\)' /etc/exports 2> /dev/null | wc -l)
-    exports=$(($exports_ni + $exports_etc))
+    exports=$exports_etc
  
In the next section, I just fixed broken tabbing. This section illustrates, however, the decision to serve files over NFS or not based upon the presence or absence of entries in /etc/exports:
     # if we are an NFS server, turn on NFS locking by default:
     if [ "${exports}" -gt 0 ]; then
-	if [ "${NFSLOCKS:=-AUTOMATIC-}" = "-AUTOMATIC-" ]; then
-	    NFSLOCKS=-YES-;
-	fi
+        if [ "${NFSLOCKS:=-AUTOMATIC-}" = "-AUTOMATIC-" ]; then
+            NFSLOCKS=-YES-;
+        fi
     fi
 
     if [ "${NFSLOCKS:=-AUTOMATIC-}" = "-YES-" ]; then
-	# we definitely want locks on, so turn them on now
-	rpc.statd
-	rpc.lockd
+        # we definitely want locks on, so turn them on now
+        rpc.statd
+        rpc.lockd
     fi
     if [ "${NFSLOCKS:=-AUTOMATIC-}" = "-AUTOMATIC-" ]; then
-	# delay starting daemons until we know we need them
+        # delay starting daemons until we know we need them
 
-	# invoke rpc.statd to send any SM_NOTIFY messages and quit.
-	rpc.statd -n
+        # invoke rpc.statd to send any SM_NOTIFY messages and quit.
+        rpc.statd -n
 
-	# -w says to wait for signal from kernel, then start daemons
-	rpc.lockd -w
+        # -w says to wait for signal from kernel, then start daemons
+        rpc.lockd -w
     fi
 
     ##
@@ -79,47 +94,79 @@
     rm -f /var/db/mountdtab
 
     if [ "${exports}" -gt 0 ]; then
-	echo "Starting Network File System server"
-	mountd
+        echo "Starting Network File System server"
+        mountd
 
-	# If the NetInfo config/nfsd directory contains startup args for nfsd, use those.
-	arguments=`niutil -readprop . /config/nfsd arguments`
-	if [ "${arguments}" = "" ]; then
-		arguments="-t -u -n 6"
-	fi
-	nfsd ${arguments}
+        # If the NetInfo config/nfsd directory contains startup args for nfsd,
+        # use those.
+        arguments=`niutil -readprop . /config/nfsd arguments`
+        if [ "${arguments}" = "" ]; then
+            arguments="-t -u -n 6"
+        fi
+        nfsd ${arguments}
     fi
 
  

This section starts NFS client services. In OS X, most file systems are intended to be mounted dynamically, as they are needed, as opposed to having some mounted statically and some mounted dynamically. Even your system disk gets automounted at boot time. That mount does not time out, however. In what follows, I've parameterized the automount commands using the variables I defined in the beginning, and I have also provided a way to change the default NFS protocol that the automounter will use (by default, it uses UDP. No one in their right mind, who values their data, uses UDP. I’ve added a property to the NetInfo /mounts directory to allow you to change that default to TCP, but if that property is missing, I respect Apple’s default of UDP).

     ##
     # Start the automounter
     ##
 
+    NETWORK_PROTOCOL=`niutil -readprop . /mounts default_protocol 2>/dev/null`
+    NETWORK_PROTOCOL=${NETWORK_PROTOCOL:-udp}
+
     if [ "${AUTOMOUNT:=-YES-}" = "-YES-" ]; then
-	automount -m /Network -nsl -mnt ${AUTOMOUNTDIR}
-	ln -s /automount/Library /Network/Library
-	automount -m /automount/Servers -fstab -mnt /private/Network/Servers \
-		-m /automount/static -static -mnt ${AUTOMOUNTDIR}
-	ln -s /automount/Servers /Network/Servers
-	
-	#
-	# Hint that the name /Network should be localized:
-	#
-	ln -s . /Network/.localized
-    fi
-	
-	#
-	# Leave a mark upon completion of the automounter startup:
-	#
-	touch /var/run/automount.initialized
+        automount -$NETWORK_PROTOCOL -m /Network -nsl -mnt ${AUTOMOUNTDIR}
+        ln -s /automount/Library /Network/Library
+
+        automount -$NETWORK_PROTOCOL \
+            -m /automount/Servers -fstab -mnt /private/Network/Servers \
+            -m /automount/static -static -mnt ${AUTOMOUNTDIR}
+        ln -s /automount/Servers /Network/Servers
+        
+        #
+        # Hint that the name /Network should be localized:
+        #
+        ln -s . $AUTOMOUNT_HINTFILE
+    fi
+
+    #
+    # Leave a mark upon completion of the automounter startup:
+    #
+    touch $AUTOMOUNT_MARKER
 }
  
Finally, using the parameterized filenames from the beginning, I add the missing piece that allows us to stop and restart NFS any time we want, without having to reboot the computer:
 
 StopService ()
 {
+    echo "Stopping NFS"
+
+    #
+    # Forcibly umount all automounted file systems.
+    #
+    mount | grep automounted | grep '\:' | \
+    while read DYNMOUNT
+    do
+        MOUNTPOINT=`echo $DYNMOUNT | awk '{ print $3 }'`
+        echo $DYNMOUNT
+        umount -f $MOUNTPOINT
+    done
+
+    killall automount
+    killall -9 mountd
+    killall -9 nfsiod
+    killall -9 rpc.statd
+    killall -9 rpc.lockd
+    killall -9 nfsd-server
+    killall -9 nfsd-master
+
+    rm -f $LOCKFILE
+    rm -f $AUTOMOUNT_HINTFILE
+    rm -f $AUTOMOUNT_MARKER
     return 0
 }
 
 RestartService ()
 {
+    StopService
+    StartService
     return 0
 }
 
  

Applying the Patch


IMPORTANT! SAVE A COPY OF YOUR ORIGINAL, AS-INSTALLED VERSION OF THE NFS STARTUP SCRIPT BEFORE YOU APPLY THE PATCH!


You have two choices for getting these changes onto your computer:

  1. Download my complete version of the NFS startup script and copy it in place of Apple’s version. Use this option if you have not personally made any changes to the existing NFS startup script that you care to preserve.
  2. Download the patch and use the patch utility to merge my changes with your own. Carefully verify that the changes make sense before putting them into production.

Whichever option you choose, you must use an account that has administrative privileges on your computer. If you are prompted for a password, type that account’s password to authorize the action.

1. Copying My Version

Download my NFS startup script here, then open a Terminal window and execute the following commands, exactly as shown:

sudo cp /System/Library/StartupItems/NFS/NFS /System/Library/StartupItems/NFS/NFS.orig
sudo cp NFS /System/Library/StartupItems/NFS/NFS
cd /System/Library/StartupItems/NFS
diff -u NFS.orig NFS | more

You should see the same differences I discussed above, in my explanation of the patch.

2. Using My Patch

Download my NFS.patch file. Open a Terminal window and execute the following commands exactly as shown:

sudo cp /System/Library/StartupItems/NFS/NFS /System/Library/StartupItems/NFS/NFS.orig
sudo patch <NFS.patch
cd /System/Library/StartupItems/NFS
diff -u NFS.orig NFS | more

As with the copy method, you should see the same differences I discussed, although the line numbers in the patch might not match up exactly.

[I have received a report of using the above patch command reporting the following error:

patch: **** File NFS.orig seems to be locked by somebody else under Perforce
I suspect that something in the user’s environment is responsible for this, but I do not know for sure. That user was able to use the “copy” method of getting my changes without a problem, though.]

You are not yet ready to start serving files. You need to do some frobbing in NetInfo Manager to add the properties that the patched NFS script expects to be able to read. Continue on to the next page.

1 2 3 4 5 Next

$Date: 2007/10/28 04:05:13 $