Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 5.6k
Tar: restore directory permissions while extracting.#72078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
2440cc39a9f2514ea807bdedb8fa37348f09733a66a3915526193b8a67a602334d1c9dde73db6967082e956094cFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| // 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.IO; | ||
| using System.Diagnostics; | ||
| namespace System.Formats.Tar | ||
| { | ||
| internal static partial class TarHelpers | ||
| { | ||
| private static readonly Lazy<UnixFileMode> s_umask = new Lazy<UnixFileMode>(DetermineUMask); | ||
tmds marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| 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; | ||
| /* | ||
| Tar files are usually ordered: parent directories come before their child entries. | ||
| They may be unordered. In that case we need to create parent directories before | ||
| we know the proper permissions for these directories. | ||
| We create these directories with restrictive permissions. If we encounter an entry for | ||
| the directory later, we store the mode to apply it later. | ||
| If the archive doesn't have an entry for the parent directory, we use the default mask. | ||
| The pending modes to be applied are tracked through a reverse-sorted dictionary. | ||
| The reverse order is needed to apply permissions to children before their parent. | ||
| 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, bool overwriteMetadata, SortedDictionary<string, UnixFileMode>? pendingModes) | ||
carlossanlop marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| { | ||
| // Restrictive mask for creating the missing parent directories while extracting. | ||
| const UnixFileMode ExtractPermissions = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute; | ||
| Debug.Assert(pendingModes is not null); | ||
carlossanlop marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (Directory.Exists(fullPath)) | ||
| { | ||
| // Apply permissions to an existing directory when we're overwriting metadata | ||
| // or the directory was created as a missing parent (stored in pendingModes). | ||
| if (mode.HasValue) | ||
| { | ||
| bool hasExtractPermissions = (mode.Value & ExtractPermissions) == ExtractPermissions; | ||
| if (hasExtractPermissions) | ||
| { | ||
| bool removed = pendingModes.Remove(fullPath); | ||
| if (overwriteMetadata || removed) | ||
| { | ||
| UnixFileMode umask = UMask; | ||
| File.SetUnixFileMode(fullPath, mode.Value & ~umask); | ||
| } | ||
| } | ||
| else if (overwriteMetadata || pendingModes.ContainsKey(fullPath)) | ||
| { | ||
| pendingModes[fullPath] = mode.Value; | ||
| } | ||
| } | ||
| return; | ||
| } | ||
| if (mode.HasValue) | ||
| { | ||
| // Ensure we have sufficient permissions to extract in the directory. | ||
| if ((mode.Value & ExtractPermissions) != ExtractPermissions) | ||
| { | ||
| pendingModes[fullPath] = mode.Value; | ||
| mode = ExtractPermissions; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| pendingModes.Add(fullPath, DefaultDirectoryMode); | ||
| mode = ExtractPermissions; | ||
| } | ||
| string parentDir = Path.GetDirectoryName(fullPath)!; | ||
| string rootDir = Path.GetPathRoot(parentDir)!; | ||
| bool hasMissingParents = false; | ||
| for (string dir = parentDir; dir != rootDir && !Directory.Exists(dir); dir = Path.GetDirectoryName(dir)!) | ||
| { | ||
| pendingModes.Add(dir, DefaultDirectoryMode); | ||
| hasMissingParents = true; | ||
| } | ||
tmds marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (hasMissingParents) | ||
| { | ||
| Directory.CreateDirectory(parentDir, ExtractPermissions); | ||
| } | ||
| Directory.CreateDirectory(fullPath, mode.Value); | ||
| } | ||
| internal static void SetPendingModes(SortedDictionary<string, UnixFileMode>? pendingModes) | ||
eerhardt marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| { | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // 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.IO; | ||
| using System.Text; | ||
| using System.Diagnostics; | ||
| namespace System.Formats.Tar | ||
| { | ||
| internal static partial class TarHelpers | ||
| { | ||
| internal static SortedDictionary<string, UnixFileMode>? CreatePendingModesDictionary() | ||
| => null; | ||
| internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool overwriteMetadata, SortedDictionary<string, UnixFileMode>? pendingModes) | ||
| => Directory.CreateDirectory(fullPath); | ||
| internal static void SetPendingModes(SortedDictionary<string, UnixFileMode>? pendingModes) | ||
| => Debug.Assert(pendingModes is null); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.