-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
unixCreateModearg onDirectory.CreateDirectory/File.OpenHandleandFileStreamOptions.UnixCreateModeare ignored. These APIs can be used in in cross-platform code. -
FileSystemInfo.UnixFileMode getreturnsUnixFileMode.None. -
FileSystemInfo.UnixFileMode setthrows 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 of0002which 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: and
runtime/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs
Lines 45 to 51 in b42adad
// Only extract USR, GRP, and OTH file permissions, and ignore // S_ISUID, S_ISGID, and S_ISVTX bits. // It is off by default because it's possible that a file in an archive could have // one of these bits set and, unknown to the person extracting, could allow others to // execute the file as the user or group. const int ExtractPermissionMask = 0x1FF; int permissions = (int)Mode & ExtractPermissionMask; .runtime/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Lines 167 to 174 in b42adad
// If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the // actual permissions will typically be less than what we select here. private const Interop.Sys.Permissions DefaultOpenPermissions = Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR | Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP | Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
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