Mounting NFS shares on OS X
Under OS X it is really simple to access these shares using dynamic automounting. You can simply cd to the share without any setup at all using something like:
$ cd /net/server.name/export/shareWhere server.name is the NFS server hostname, and /export/share is the exported directory. I create a soft link in my home directory so that I can cd directly to the shared directory after running Terminal.app.
This usually works well, NFS has been around for a long time and is well tested. However, I was getting problems sometimes accessing the NFS mount from OS X, sometimes I could access the drive and sometimes not. From the command line I would get the error:
-bash: cd: dev: Operation timed outAll the other machines on the network had no problems so I knew it was a problem on my MacBook Pro.
Well, after a bit of Googling it turns out that for security NFS requires that all requests originate on an internet port less than 1024. OS X does not always abide by this rule. It is possible on the server in the /etc/hosts file to override this by adding the insecure option to the export line. This is initially what I tried. But this didn’t seem to be 100% effective, it was still slow when accessing the NFS share for the first time and what’s more I wasn’t happy in circumventing an obvious security measure.
So after restoring /etc/exports and restarting the NFS daemon I tried the other approach, fix it on the OS X client end. There are two ways to do this, the first is to edit /etc/autofs.conf and look for the AUTOMOUNTD_MNTOPTS option. Add rescport to the end of the line so it looks something like:
AUTOMOUNTD_MNTOPTS=nosuid,nodev,resvportThis will force the Mac to use reserved ports for all NFS requests. The second way (which is the approach I took) is to edit /etc/auto_master. Find the line that starts /net, and add the resvport option to the end so it becomes:
/net -hosts
-nobrowse,hidefromfinder,nosuid,resvportThis causes the Mac to use reserved ports only for those mounts under /net which are the dynamic mounts.
Since doing this NFS seems a lot faster and connects every time.
Listing local users
$ dscl localhost list /Search/Users
Unfortunately, this also lists all the system accounts such as those used by the www and mysql daemons. So if we only want users then we have to do a bit of work to filter out these accounts. Now, when creating users on OS X (and most Unix based systems that I am aware of) they are assigned a User ID starting from 500. So we can simply ignore any accounts with a UID less than 500.
To be able to do this from Objective C within my Cocoa application I had to execute the dscl command from within an NSTask. The code listing below shows how I done this.
The first half of the function sets up the NSTask with the path to the dscl application and the arguments to be passed. The task is then run and the output piped to a file. This is then read into an NSString which we can now process to remove all system accounts. Using an NSScanner we iterate through the string calling the standard C library function getpwnam() to discover the UID of each account. If it is less than 500 then the user name is added to the array initialised at the start of the function.
Finally return the list of users as an NSArray.
I was doing it this way for a while and it is fine for local users. Problems arise when you need to list network users or need more flexibility so I have recently switched to using the CSIdentity framework.
-(NSArray*)getLocalUsers
{
NSMutableArray *users = [[NSMutableArray alloc] init];
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: @"/usr/bin/dscl"];
NSArray *arguments;
arguments =
[NSArray arrayWithObjects: @"localhost", @"list", @"/Search/Users", nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding];
NSScanner *theScanner = [NSScanner scannerWithString:string];
while (NO == [theScanner isAtEnd])
{
NSString *str;
struct passwd *userInfo;
if ([theScanner scanUpToCharactersFromSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString:&str])
{
userInfo = getpwnam([str UTF8String]);
if (userInfo->pw_uid > 500)
{
[users addObject: str];
}
}
}
return [users autorelease];
}
Debugging Preference Panes
But if you have a problem and need to resort to stepping through your code in the debugger it is not immediately obvious how to do so. Of course, Google knows, but it took much longer than it should have done for me to drill down to find the answer. So to save you one or two mouse clicks here is how to do it, create a new custom executable (Project->New Custom Executable) and select the System Preferences app as the executable path.
That’s it. Simple eh?