| File: System\Formats\Tar\TarHelpers.Unix.cs | Web Access |
| Project: System.Formats.Tar.csproj (System.Formats.Tar) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.IO; namespace System.Formats.Tar { internal struct FileId : IEquatable<FileId> { internal long Dev; internal long Ino; internal FileId(long dev, long ino) { Dev = dev; Ino = ino; } public bool Equals(FileId other) => Dev == other.Dev && Ino == other.Ino; public override bool Equals(object? obj) => obj is FileId other && Equals(other); public override int GetHashCode() => HashCode.Combine(Dev, Ino); } internal static partial class TarHelpers { private static readonly Lazy<UnixFileMode> s_umask = new Lazy<UnixFileMode>(DetermineUMask); private static UnixFileMode DetermineUMask() { // To determine the umask, we'll create a file with full permissions and see // what gets filtered out. // note: only the owner of a file, and root can change file permissions. const UnixFileMode OwnershipPermissions = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute; string filename = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); FileStreamOptions options = new() { Mode = FileMode.CreateNew, UnixCreateMode = OwnershipPermissions, Options = FileOptions.DeleteOnClose, Access = FileAccess.Write, BufferSize = 0 }; using var fs = new FileStream(filename, options); UnixFileMode actual = File.GetUnixFileMode(fs.SafeFileHandle); return OwnershipPermissions & ~actual; } private sealed class ReverseStringComparer : IComparer<string> { public int Compare(string? x, string? y) => StringComparer.Ordinal.Compare(y, x); } private static readonly ReverseStringComparer s_reverseStringComparer = new(); private static UnixFileMode UMask => s_umask.Value; // Use a reverse-sorted dictionary to apply permission to children before their parents. // Otherwise we may apply a restrictive mask to the parent, that prevents us from changing a child. internal static SortedDictionary<string, UnixFileMode>? CreatePendingModesDictionary() => new SortedDictionary<string, UnixFileMode>(s_reverseStringComparer); internal static void CreateDirectory(string fullPath, UnixFileMode? mode, SortedDictionary<string, UnixFileMode>? pendingModes) { // Minimal permissions required for extracting. const UnixFileMode ExtractPermissions = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute; Debug.Assert(pendingModes is not null); if (Directory.Exists(fullPath)) { // Apply permissions to an existing directory. if (mode.HasValue) { // Ensure we have sufficient permissions to extract in the directory. bool hasExtractPermissions = (mode.Value & ExtractPermissions) == ExtractPermissions; if (hasExtractPermissions) { pendingModes.Remove(fullPath); UnixFileMode umask = UMask; File.SetUnixFileMode(fullPath, mode.Value & ~umask); } else { pendingModes[fullPath] = mode.Value; } } return; } // If there are missing parents, Directory.CreateDirectory will create them using default permissions. if (mode.HasValue) { // Ensure we have sufficient permissions to extract in the directory. if ((mode.Value & ExtractPermissions) != ExtractPermissions) { pendingModes[fullPath] = mode.Value; mode = ExtractPermissions; } Directory.CreateDirectory(fullPath, mode.Value); } else { Directory.CreateDirectory(fullPath); } } internal static void SetPendingModes(SortedDictionary<string, UnixFileMode>? pendingModes) { Debug.Assert(pendingModes is not null); if (pendingModes.Count == 0) { return; } UnixFileMode umask = UMask; foreach (KeyValuePair<string, UnixFileMode> dir in pendingModes) { File.SetUnixFileMode(dir.Key, dir.Value & ~umask); } } internal static FileId GetFileId(string filePath) { Interop.CheckIo(Interop.Sys.Stat(filePath, out Interop.Sys.FileStatus status), filePath); return new FileId(status.Dev, status.Ino); } internal static bool TryGetPathInfo(string path, out FileId fileId, out bool isSymlink) { int rv = Interop.Sys.LStat(path, out Interop.Sys.FileStatus status); if (rv != 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); if (errorInfo.Error == Interop.Error.ENOENT || errorInfo.Error == Interop.Error.ENOTDIR) { fileId = default; isSymlink = false; return false; } throw Interop.GetExceptionForIoErrno(errorInfo, path); } fileId = new FileId(status.Dev, status.Ino); isSymlink = (status.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK; return true; } } }