Description
Background and motivation
Add direct support for getting/setting Unix file permissions and special bits.
These APIs allow to restrict/extend access to files and directories from .NET.
There have been some requests for these APIs: #925, #928, #17540.
The tar implementation can make use of them (#65951) without resorting to internal APIs.
API Proposal
namespace System.IO;
[System.FlagsAttribute]
public enum UnixFileMode
{
// From https://github.com/dotnet/runtime/issues/65951.
None = 0,
OtherExecute = 1,
OtherWrite = 2,
OtherRead = 4,
GroupExecute = 8,
GroupWrite = 16,
GroupRead = 32,
UserExecute = 64,
UserWrite = 128,
UserRead = 256,
StickyBit = 512,
SetGroup = 1024, // or, more generic name: GroupSpecial
SetUser = 2048, // or, more generic name: UserSpecial
// Maybe:
AccessPermissionMask = UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute,
DefaultFileOpenPermissions = UserRead | UserWrite | GroupRead | GroupWrite | OtherRead | OtherWrite
}
static partial class Directory
{
// Set mode when creating file, returns SafeFileHandle.
public static SafeFileHandle OpenHandle (string path, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read, FileShare share = FileShare.Read, FileOptions options = FileOptions.None, long preallocationSize = 0, UnixFileMode unixCreateMode = UnixFileMode.DefaultFileOpenPermissions);
// Set mode when creating directory
public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode);
}
static partial class File
{
public static SafeFileHandle OpenHandle (string path, System.IO.FileMode mode = System.IO.FileMode.Open, System.IO.FileAccess access = System.IO.FileAccess.Read, System.IO.FileShare share = System.IO.FileShare.Read, System.IO.FileOptions options = System.IO.FileOptions.None, long preallocationSize = 0);
[EditorBrowsable(EditorBrowsableState.Never)]
public static SafeFileHandle OpenHandle (string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize = 0); // Existing API, marked as Never Browsable to add a new default parameter.
// Get/Set from SafeHandle, (.NET 7) https://github.com/dotnet/runtime/issues/20234
public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle);
public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode);
}
class FileSystemInfo
{
// Get/Set from string path
public UnixFileMode UnixFileMode { get; set; }
}
// Set mode when creating file, returns FileStream (by `File.Open`).
class FileStreamOptions
{
// Set mode when creating file
public UnixFileMode UnixCreateMode { get; set; } = UnixFileMode.DefaultFileOpenPermissions;
}
// `UnixFileMode` replaces `TarFileMode` (https://github.com/dotnet/runtime/issues/65951)
namespace System.Formats.Tar
{
- enum TarFileMode { ... }
}
Notes
On Windows/non-Unix:
-
The
unixCreateMode
arg onDirectory.CreateDirectory
/File.OpenHandle
andFileStreamOptions.UnixCreateMode
are ignored. These APIs can be used in in cross-platform code. -
FileSystemInfo.UnixFileMode get
returnsUnixFileMode.None
. -
FileSystemInfo.UnixFileMode set
throws PNSE.
APIs that create a file/directory (Directory.CreateDirectory
, FileStreamOptions.UnixCreateMode
, File.OpenHandle
) have 'POSIX behavior':
-
If the file/directory exists, the mode argument is ignored.
-
The OS kernel filters the provided mode by the process
umask
. The umask protects users from unintentionally opening up permissions. A regular Linux user has a umask of0002
which prevents it from giving write permissions to other. Use-cases that require the exact mode, must make an additional call toSetUnixFileMode
. See example 3. -
note: creating and setting permissions is atomic to ensure permissions are applied immediately
UnixFileMode
can replace TarFileMode
introduced in #65951.
AccessPermissionMask
, DefaultFileOpenPermissions
enum values.
- These are convenient masks. Example usage of these masks:
API Usage
Example 1: limit a file to be read/written by the owner only:
File.SetUnixFileMode("filename", UnixFileMode.UserRead | UnixFileMode.UserWrite);
Example 2: exclude 'Other' access to entries in a directory:
var di = new DirectoryInfo("somedir");
foreach (var fi in di.GetFileSystemInfos())
{
fi.UnixFleMode = fi.UnixFileMode & ~(UnixFileMode.OtherExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite);
}
Example 3: create new files/directories while extracting a tar file with the exact permissions of the tar file.
if (entry.EntryType == EntryType.Directory)
{
DirectoryInfo directoryInfo = Directory.CreateDirectory(path, entry.Mode); // filtered by umask
di.UnixFileMode = entry.Mode;
}
else if (entry.EntryType == EntryType.RegularFile)
{
using var file = File.OpenHandle(path, entry.Mode); // filtered by umask
File.SetUnixFileMode(file, entry.Mode);
}
Alternative Designs
No response
Risks
No response