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.
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.
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:
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?
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?
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:
Option What it does --create-with-perms=af-x The 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=101 When 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!)
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…)
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?
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.