I like to use System.IO.File.WriteAllBytes() to keep things simple. But it seems, that this method can not be used everywhere. To write on my local system it works fine.
But when I use System.IO.File.WriteAllBytes() to write on a Windows share it produces an empty file and fails with an Exception:
System.UnauthorizedAccessException: Access to the path '/var/windowsshare/file.bin' is denied.
---> System.IO.IOException: Permission denied
If I look at the source at https://github.com/dotnet/runtime/blob/c72b54243ade2e1118ab24476220a2eba6057466/src/libraries/System.IO.FileSystem/src/System/IO/File.cs#L421 I found the following code working under the hood:
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
fs.Write(bytes, 0, bytes.Length);
}
If I change the code and use FileShare.None instead of FileShare.Read it works. So I have a workaround and I have to keep in mind that System.IO.File.WriteAllBytes() is not waterproof (is it correct?).
Unfortunately, my analysis ended up with a few related questions:
So what is the best practice if the target path is configurable? Does the developer have to avoid System.IO.File.WriteAllBytes() or does the system administrator have to find another way to mount the share? What is wrong with FileShare.Read? Does the Windows share change permissions/locking while System.IO.File.WriteAllBytes() is writing? Are there some tips to mount the Windows share?
Update 1
WriteAllBytes():
// WriteAllBytes() Throws System.UnauthorizedAccessException
System.IO.File.WriteAllBytes("/var/windowsshare/file.bin", bytes);
Create and move with C#
// Create local and move + optional overwrite works!
var tmp = Path.GetTempFileName(); // local file
System.IO.File.WriteAllBytes(tmp, bytes); // write local
System.IO.File.Move(tmp, "/var/windowsshare/file.bin", true); // optional overwrite
ls:
# ls -l /var/windowsshare/file.bin
-rw-rw-rw-. 1 apache apache 20 Feb 9 11:43 /var/windowsshare/file.bin
# ls -Z /var/windowsshare/file.bin
system_u:object_r:cifs_t:s0 /var/windowsshare/file.bin
mount ...
# mount -l
//1.2.3.4/windowsshare on /var/windowsshare type cifs (rw,relatime,vers=3.1.1,cache=strict,username=luke,domain=dom,uid=48,forceuid,gid=48,forcegid,addr=1.2.3.4,file_mode=0666,dir_mode=0777,soft,nounix,nodfs,nouser_xattr,mapposix,noperm,rsize=4194304,wsize=4194304,bsize=1048576,echo_interval=60,actimeo=1,_netdev)
# stat -f -c %T /var/windowsshare/file.bin
smb2
The following thread (https://github.com/dotnet/runtime/issues/42790) on Github helped me out. In the end I remounted my CIFS shares with the
nobrloption.In the thread they also came to the conclusion that using
FileShare.Noneworks, but the root cause seems to be that the CIFS server we are using does not support byte range locks.I am not sure what all the implications of this is, but in my case there is no need to write the file more than once and there should be no two processes trying to write to the same file.