The Windows NFS client

Doing things the Microsoft® way™…

Table of contents:

This was supposed to be an appendix to my WebDAV article, but eventually I spent way too much time playing with this, and wrote too much for it to fit there. I didn’t wind up using this, as WebDAV was working well for me, but kept the instructions around in case they become useful later.

Anonymous access

This is what the export looks like on the NFS server side:

alpine:~# grep \.101 /etc/exports
/netstor   192.168.1.101(rw,anonuid=1000,anongid=1000,no_subtree_check,insecure,all_squash)

The first step on the Windows is enabling the necessary features. This will vary slightly depending on whether you’re using a workstation or server class operating system. On Windows 10 Pro, you could do this at an elevated PowerShell:

PS C:\WINDOWS\system32> Enable-WindowsOptionalFeature -Online -FeatureName ServicesForNFS-ClientOnly,ClientForNFS-Infrastructure


Path          :
Online        : True
RestartNeeded : False



PS C:\WINDOWS\system32> 

After that, it’s possible to mount as a normal user:

PS C:\Users\fission> mount.exe alpine:/netstor Z:
Z: is now successfully connected to alpine:/netstor

The command completed successfully.
PS C:\Users\fission> 

(Note: use mount.exe rather than just mount. In PowerShell, mount is an alias for New-PSDrive.)

Initially it may seem like all is well: reading files and writing new files works fine. But changing existing files, or deleting files (using PowerShell’s Remove-Item cmdlet) fails:

PS C:\Users\fission> Set-Content Z:\hello.txt "Hello, world!"
PS C:\Users\fission> Set-Content Z:\hello.txt "The grey wolf howls"
Set-Content : Access to the path 'Z:\hello.txt' is denied.
At line:1 char:1
+ Set-Content Z:\hello.txt "The grey wolf howls"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Set-Content], UnauthorizedAccessException
    + FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.
   SetContentCommand
 
PS C:\Users\fission> Remove-Item Z:\hello.txt
Remove-Item : Cannot remove item Z:\hello.txt: You do not have sufficient access rights to
perform this operation.
At line:1 char:1
+ Remove-Item Z:\hello.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (Z:\hello.txt:FileInfo) [Remove-Item], IOExcept
   ion
    + FullyQualifiedErrorId : RemoveFileSystemItemUnAuthorizedAccess,Microsoft.PowerShell.Comma
   nds.RemoveItemCommand
PS C:\Users\fission> 

This is a strictly client-side issue, as any other NFS client with the same export settings works fine. And in fact, just to show that, you can remove the file with System.IO.File:

PS C:\Users\fission> [System.IO.File]::Delete('Z:\hello.txt')
PS C:\Users\fission> 

The problem is identity mapping: Windows has to decide which uid/gid to use to access the NFS server. The server doesn’t care – it is clearly willing to let the client do whatever it wants to the files, as it has all_squash defined in the export line.

Windows does a strange thing when it doesn’t know that it has permission ; it marks the file as having the read-only attribute set (that’s the little ‘r’ in the Mode column):

PS C:\Users\fission> touch Z:\hello.txt
PS C:\Users\fission> dir Z:\hello.txt


    Directory: Z:\


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-ar---          5/5/2025  11:50 PM              0 hello.txt


PS C:\Users\fission> 

So this is why file modification is not (directly) permitted: programs see that file as read-only.

Identity mapping

When an NFS export is mounted, Windows will try to determine which uid/gid to use to access it. If an identity-to-uid mapping can’t be made – or you have specified -o anon, ie, anonymous access, the AnonymousUid and AnonymousGid will be used, which are both -2 (or 0xfffffffe, or 4294967294, if you like) by default. Thus initially it doesn’t matter whether you specify -o anon or not:

PS C:\Users\fission> mount.exe alpine:/netstor Z:
Z: is now successfully connected to alpine:/netstor

The command completed successfully.
PS C:\Users\fission> mount.exe

Local    Remote                                 Properties
-------------------------------------------------------------------------------
Z:       \\alpine\netstor                       UID=-2, GID=-2
                                                rsize=131072, wsize=131072
                                                mount=soft, timeout=0.8
                                                retry=1, locking=yes
                                                fileaccess=755, lang=ANSI
                                                casesensitive=no
                                                sec=sys
PS C:\Users\fission> umount Z:

Disconnecting           Z:      \\alpine\netstor
The command completed successfully.
PS C:\Users\fission> mount.exe -o anon alpine:/netstor Z:
Z: is now successfully connected to alpine:/netstor

The command completed successfully.
PS C:\Users\fission> mount.exe

Local    Remote                                 Properties
-------------------------------------------------------------------------------
Z:       \\alpine\netstor                       UID=-2, GID=-2
                                                rsize=131072, wsize=131072
                                                mount=soft, timeout=0.8
                                                retry=1, locking=yes
                                                fileaccess=755, lang=ANSI
                                                casesensitive=no
                                                sec=sys
PS C:\Users\fission> 

There is a good article about connecting to NFS shares from Windows that discusses various methods of identity-mapping between the Windows world and NFS.

There are two possible fixes that I could apply to my situation:

  1. Change AnonymousUid and AnonymousGid in the registry to match the NFS server. In my case, that would be to set them both to 1000. Unfortunately this changes it for all NFS servers ; so if I were using, say, 2000 on a different server, this wouldn’t work.
  2. Create an identity mapping between my Windows account and a Unix uid/gid. This has the same problem as method (1), but has the advantage of not being hardcoded in the registry. It would also be possible to use different Windows accounts on different NFS servers, which map accordingly.

Creating passwd and group files

The passwd and group files follow basically the same format as they would on any Unix system, except that the username should match your Windows side username (eg machine\user or domain\user). In my case, I did this at an elevated PowerShell:

PS C:\WINDOWS\system32> cd .\drivers\etc
PS C:\WINDOWS\system32\drivers\etc> Set-Content passwd 'quark\fission:x:1000:1000:fission:/home/fission:/bin/sh'
PS C:\WINDOWS\system32\drivers\etc> Set-Content group 'quark\Users:x:1000:'
PS C:\WINDOWS\system32\drivers\etc> 

You do not need to reboot or restart any services after this ; just unmount the drive (if mounted) and mount it again:

PS C:\Users\fission> umount Z:

Disconnecting           Z:      \\alpine\netstor
The command completed successfully.
PS C:\Users\fission> mount.exe alpine:/netstor Z:
Z: is now successfully connected to alpine:/netstor

The command completed successfully.
PS C:\Users\fission> mount.exe

Local    Remote                                 Properties
-------------------------------------------------------------------------------
Z:       \\alpine\netstor                       UID=1000, GID=1000
                                                rsize=131072, wsize=131072
                                                mount=soft, timeout=0.8
                                                retry=1, locking=yes
                                                fileaccess=755, lang=ANSI
                                                casesensitive=no
                                                sec=sys
PS C:\Users\fission> 

You can see UID=1000, GID=1000 in the output, signifying that the mapping worked. Let’s see if it improved the permissions situation:

PS C:\Users\fission> del Z:\hello.txt
PS C:\Users\fission> Set-Content Z:\hello.txt "Os justi meditabitur sapientiam"
PS C:\Users\fission> Set-Content Z:\hello.txt "et lingua ejus loquetur judicium"
PS C:\Users\fission> dir Z:\hello.txt


    Directory: Z:\


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          5/6/2025  12:53 AM             34 hello.txt


PS C:\Users\fission> 

Looks good! Or…is it?

Unix file permissions

In Windows, everything looks good now. But on the Unix side, things are a different story:

alpine:~# ls -l /netstor/hello.txt
-rwxr-xr-x    1 1000     1000            34 May  6 00:53 /netstor/hello.txt
alpine:~# 

Ew, +x on a plain text file. You might think you could just set the fileaccess=644 option when mounting:

PS C:\Users\fission> del Z:\hello.txt
PS C:\Users\fission> umount Z:

Disconnecting           Z:      \\alpine\netstor
The command completed successfully.
PS C:\Users\fission> mount.exe -o fileaccess=644 alpine:/netstor Z:
Z: is now successfully connected to alpine:/netstor

The command completed successfully.
PS C:\Users\fission> Set-Content Z:\hello.txt "Now we both have said moustache"
PS C:\Users\fission> mkdir Z:\foo


    Directory: Z:\


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          5/6/2025   1:05 AM                foo


PS C:\Users\fission> 

But alas, out of the frying pan into the fire: directories are now also created 644. So this is not practical.

alpine:~# ls -ldrt /netstor/hello.txt /netstor/foo
-rw-r--r--    1 1000     1000            33 May  6 01:03 /netstor/hello.txt
drw-r--r--    2 1000     1000          4096 May  6 01:05 /netstor/foo
alpine:~# 

One suggestion to deal with this is to use an ACL to prevent files from being created +x. But I don’t actually want this, as I would like Unix clients to create files with the execute bit set, if appropriate.

Another suggestion is to create a cron job on the Unix side to adjust the permissions. I guess that means either adding execute bits to directories, or pruning them from files. Or you could use WebDAV instead. :-)

But if one is willing to adopt a server-side method…is there perhaps a better way?

Using bindfs to set the mode of newly-created files

I successfully used bindfs in the WebDAV note to do UID/GID remapping ; could this sorcery be used to stop Windows clients from creating files – at least by default – with the execute bit set?

It took some time to find the right incantation, but this definitely works:

alpine:~# bindfs --create-with-perms=af-x /netstor /netstor.win
alpine:~# echo "/netstor.win   192.168.1.101(fsid=101,rw,anonuid=1000,anongid=1000,no_subtree_check,insecure,all_squash)" >>/etc/fstab
alpine:~# exportfs -ar
alpine:~# 

Some notes on that:

OptionWhat it does
--create-with-perms=af-xThe af-x looks a bit strange, but basically it’s an extension to the symbolic notation for chmod that sets all (ugo) users’ files non-executable. Directory permissions are already correct (755) so it’s only necessary to change the mode of new files.
fsid=101When exporting a bindfs location, there’s no uuid, so this just gives a unique identifier to the export. I don’t think it matters much what number you use here, except not 0. If you don’t set this, you’ll get: exportfs: /netstor.win requires fsid= for NFS export.

Let’s test from the Windows side now:

PS C:\Users\fission> umount Z:

Disconnecting           Z:      \\alpine\netstor
The command completed successfully.
PS C:\Users\fission> mount.exe alpine:/netstor.win Z:
Z: is now successfully connected to alpine:/netstor.win

The command completed successfully.
PS C:\Users\fission> mount.exe

Local    Remote                                 Properties
-------------------------------------------------------------------------------
Z:       \\alpine\netstor.win                   UID=1000, GID=1000
                                                rsize=131072, wsize=131072
                                                mount=soft, timeout=0.8
                                                retry=1, locking=yes
                                                fileaccess=755, lang=ANSI
                                                casesensitive=no
                                                sec=sys
PS C:\Users\fission> Set-Content Z:\new.txt "dans nos vieilles maisons"
PS C:\Users\fission> mkdir Z:\new.dir


    Directory: Z:\


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          5/7/2025  12:03 AM                new.dir


PS C:\Users\fission> 

And from the Linux side, finally everything looks nice:

alpine:~# ls -ld /netstor/new.*
drwxr-xr-x    2 1000     1000          4096 May  7 00:03 /netstor/new.dir
-rw-r--r--    1 1000     1000            27 May  7 00:03 /netstor/new.txt
alpine:~# 

Note that I used /netstor rather than /netstor.win to prove that the change was made on the filesystem rather than just something in the bindfs mount.

Now that this has been tested, it can be made permanent:

alpine:~# exportfs -u 192.168.1.101:/netstor.win
alpine:~# umount /netstor.win
alpine:~# echo /netstor /netstor.win fuse.bindfs create-with-perms=af-x 0 0 >>/etc/fstab
alpine:~# mount /netstor.win
alpine:~# exportfs -ar
alpine:~# 

One final sanity check: does the execute bit matter at all for Windows executables? It shouldn’t, but let’s find out.

PS C:\Users\fission> Z:
PS Z:\> Set-Content hello.cs -Value 'class Program { static int Main(string[] args) { System.Console.WriteLine("Hello, world!"); return 0; } }'
PS Z:\> C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /nologo hello.cs
PS Z:\> .\hello.exe
Hello, world!
PS Z:\> dir hello.*


    Directory: Z:\


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----          5/7/2025  12:07 AM           3584 hello.exe
-a----          5/7/2025  12:07 AM            107 hello.cs


PS Z:\> 

So there’s the answer: no, the execute bit isn’t required for Windows to actually execute a file. ( No surprise there!)

Tips and tricks

PowerShell mount vs mount.exe

Microsoft has this bad habit of creating PowerShell aliases that conflict with existing executables. Two that I know without even having to look them up:

The messages you get if you choose the ‘wrong’ one can range from strange questions to full-on red-text failures. Here are some examples relevant to the NFS mount operation:

PS C:\Users\fission> mount

cmdlet New-PSDrive at command pipeline position 1
Supply values for the following parameters:
Name:
PS C:\Users\fission> mount alpine:/netstor Z:

cmdlet New-PSDrive at command pipeline position 1
Supply values for the following parameters:
Root:
PS C:\Users\fission> mount -o anon alpine:/netstor Z:
New-PSDrive : Parameter cannot be processed because the parameter name 'o' is ambiguous.
Possible matches include: -OutVariable -OutBuffer.
At line:1 char:7
+ mount -o anon alpine:/netstor Z:
+       ~~
    + CategoryInfo          : InvalidArgument: (:) [New-PSDrive], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameter,Microsoft.PowerShell.Commands.NewPSDriveCommand
 
PS C:\Users\fission> 

The upshot: if you see anything mentioning New-PSDrive, you have the wrong mount. Append .exe to the end of it (ie, mount.exe) so that you get the right one. ( Yes, you could open a Command Prompt instead, but that’s exceedingly dull…)

Network Error - 85

This is one of those “read the instructions on the screen” moments:

PS C:\Users\fission> mount.exe alpine:/netstor.win Z:
Network Error - 85

Type 'NET HELPMSG 85' for more information.

PS C:\Users\fission> net helpmsg 85

The local device name is already in use.

PS C:\Users\fission> [System.ComponentModel.Win32Exception]::new(85)
The local device name is already in use
PS C:\Users\fission> 

That last command there is just showing a more fun way to see the message.

This just means that the local drive you specified (Z: in this example) is already mounted, or otherwise in use. Did you want to umount it first?

Multiple options to mount.exe

On the off chance that this might spare someone a lot of frustration, options to mount.exe are space separated, not comma-separated. Thus if you wanted to set both anon and fileaccess:

PS C:\Users\fission> mount.exe -o anon fileaccess=775 alpine:/netstor Z:
Z: is now successfully connected to alpine:/netstor

The command completed successfully.
PS C:\Users\fission> 

Why should Microsoft follow the prevailing standard for specifying mount options? It’s almost as if someone developed this utility without ever having actually used its original incarnation on a real Unix system…


Feel free to contact me with any questions, comments, or feedback.