- 
                Notifications
    You must be signed in to change notification settings 
- Fork 5.2k
Address some System.Formats.Tar TODOs (infra and syscalls) #69107
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
Changes from 8 commits
c178925
              9f020bd
              c4d444b
              354e40a
              d04ec6b
              9cb26f1
              d94536a
              cdbbddf
              e343a3b
              1af51df
              d845d56
              94d2cf0
              90cc58c
              fe9a927
              a8f5cf6
              498c084
              7b4e7b5
              a9090b4
              7c01996
              8de50ad
              31ab76b
              67fe812
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Runtime.InteropServices; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Buffers; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Text; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| internal static partial class Interop | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| internal static partial class Sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Gets the group name associated to the specified group ID. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="gid">The group ID.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>On success, return a string with the group name. On failure, returns a null string.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| internal static string? GetGName(uint gid) => GetGNameOrUName(gid, isGName: true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Gets the user name associated to the specified user ID. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="uid">The user ID.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>On success, return a string with the user name. On failure, returns a null string.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| internal static string? GetUName(uint uid) => GetGNameOrUName(uid, isGName: false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static string? GetGNameOrUName(uint id, bool isGName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Common max name length, like /etc/passwd, useradd, groupadd | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int outputBufferSize = 32; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] buffer = ArrayPool<byte>.Shared.Rent(outputBufferSize); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int resultLength = isGName ? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Interop.Sys.GetGName(id, buffer, buffer.Length) : | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Interop.Sys.GetUName(id, buffer, buffer.Length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (resultLength < 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else if (resultLength < buffer.Length) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|         
                  carlossanlop marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // success | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Encoding.UTF8.GetString(buffer, 0, resultLength); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finally | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ArrayPool<byte>.Shared.Return(buffer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Output buffer was too small, loop around again and try with a larger buffer. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outputBufferSize = buffer.Length * 2; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (outputBufferSize > 256) // Upper limit allowed for login name in kernel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|          | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte* stackBuf = stackalloc byte[DefaultPathBufferSize]; | |
| string? result = GetCwdHelper(stackBuf, DefaultPathBufferSize); | |
| if (result != null) | |
| { | |
| return result; | |
| } | |
| // If that was too small, try increasing large buffer sizes | |
| int bufferSize = DefaultPathBufferSize; | |
| while (true) | |
| { | |
| checked { bufferSize *= 2; } | |
| byte[] buf = ArrayPool<byte>.Shared.Rent(bufferSize); | |
| try | |
| { | |
| fixed (byte* ptr = &buf[0]) | |
| { | |
| result = GetCwdHelper(ptr, buf.Length); | |
| if (result != null) | |
| { | |
| return result; | |
| } | |
| } | |
| } | |
| finally | |
| { | |
| ArrayPool<byte>.Shared.Return(buf); | |
| } | 
I did find at least one case in the pal where it mallocs the buffer and resizes it for you, but it seems our pattern is to do all this in managed code.
Ideally it returns the actual length it needs, or you only loop on ERANGE. I'm not sure what is the pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use a similar loop in ReadLink:
runtime/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ReadLink.cs
Lines 31 to 68 in c2ec86b
| internal static string? ReadLink(ReadOnlySpan<char> path) | |
| { | |
| int outputBufferSize = 1024; | |
| // Use an initial buffer size that prevents disposing and renting | |
| // a second time when calling ConvertAndTerminateString. | |
| using var converter = new ValueUtf8Converter(stackalloc byte[1024]); | |
| while (true) | |
| { | |
| byte[] buffer = ArrayPool<byte>.Shared.Rent(outputBufferSize); | |
| try | |
| { | |
| int resultLength = Interop.Sys.ReadLink( | |
| ref MemoryMarshal.GetReference(converter.ConvertAndTerminateString(path)), | |
| buffer, | |
| buffer.Length); | |
| if (resultLength < 0) | |
| { | |
| // error | |
| return null; | |
| } | |
| else if (resultLength < buffer.Length) | |
| { | |
| // success | |
| return Encoding.UTF8.GetString(buffer, 0, resultLength); | |
| } | |
| } | |
| finally | |
| { | |
| ArrayPool<byte>.Shared.Return(buffer); | |
| } | |
| // Output buffer was too small, loop around again and try with a larger buffer. | |
| outputBufferSize = buffer.Length * 2; | |
| } | |
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlike the GetCwd and the ReadLink examples, which do not have an upper bound on the length of the buffer, the uname and gname case do have a max length. So I am now more inclined to just use the 256 byte limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is that length documented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above page says that:
- man useraddindicates a max length of 32. Confirmed in the- useraddmanual https://linux.die.net/man/8/useradd and the- groupaddmanual: https://linux.die.net/man/8/groupadd
- The centos kernel max login length is 256, indicated in the local_lim.hfile asLOGIN_NAME_MAX. I found this version of that file that confirms this: https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6/+/02075080d51c371ae87b9898bf84a085e436ee27/sysroot/usr/include/bits/local_lim.h#83
- The /etc/passwdfile takes usernames with a max length of 32. This illumOS man page confirms this: https://illumos.org/man/5/passwd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The limit is implementation specific, so there should be a resize loop.
256 is a good value for the stackalloc to start with because that is the Linux value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the Linux kernel only cares about the numeric ids. libc handles the names.
https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/bits/local_lim.h#L89-L90 defines this (implementation specific) limit for Linux:
/* Maximum login name length.  This is arbitrary.  */
#define LOGIN_NAME_MAX		256There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's another upper limit. It's in the code example in the manual: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrgid.html
long int initlen = sysconf(_SC_GETGR_R_SIZE_MAX);
size_t len;
if (initlen == -1)
    /* Default initial length. */
    len = 1024;
else
    len = (size_t) initlen;I can use 1024 as the absolute upper limit that would stop the loop, I think that's large enough (that's the value I got when printing _SC_GETGR_R_SIZE_MAX in Ubuntu).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can use 1024 as the absolute upper limit that would stop the loop, I think that's large enough (that's the value I got when printing _SC_GETGR_R_SIZE_MAX in Ubuntu).
I don't think that's necessarily the upper limit, eg., this man page for getgrnam_r says
The call
sysconf(_SC_GETGR_R_SIZE_MAX)
returns either -1, without changing errno, or an initial
suggested size for buf. (If this size is too small, the call
fails with ERANGE, in which case the caller can retry with a
larger buffer.)
In the man page for getgrgid it says it in different words, again referring to getgrnam_r
A call to sysconf(_SC_GETGR_R_SIZE_MAX) returns either -1
without changing errno or an initial value suggested for the size
of this buffer.
Although I see Mono just used it (or 1024) and did not resize without provision for resize, here is a bug report where it was not large enough. https://bugs.ruby-lang.org/issues/9600
        
          
              
                  carlossanlop marked this conversation as resolved.
              
              
                Outdated
          
            Show resolved
            Hide resolved
        
      | Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -41,13 +41,13 @@ partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, str | |
| _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), | ||
| }; | ||
|  | ||
| if ((entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice) && status.Dev > 0) | ||
| if ((entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice) && status.RDev > 0) | ||
|         
                  carlossanlop marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| { | ||
| uint major; | ||
| uint minor; | ||
| unsafe | ||
| { | ||
| Interop.CheckIo(Interop.Sys.GetDeviceIdentifiers((ulong)status.Dev, &major, &minor)); | ||
| Interop.Sys.GetDeviceIdentifiers((ulong)status.RDev, &major, &minor); | ||
| } | ||
|  | ||
| entry._header._devMajor = (int)major; | ||
|  | @@ -60,12 +60,11 @@ partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, str | |
|  | ||
| entry._header._mode = (status.Mode & 4095); // First 12 bits | ||
|  | ||
| entry.Uid = (int)status.Uid; | ||
| entry.Gid = (int)status.Gid; | ||
| entry._header._uid = (int)status.Uid; | ||
| entry._header._gid = (int)status.Gid; | ||
|  | ||
| // TODO: Add these p/invokes https://github.com/dotnet/runtime/issues/68230 | ||
| entry._header._uName = "";// Interop.Sys.GetUName(); | ||
| entry._header._gName = "";// Interop.Sys.GetGName(); | ||
| entry._header._uName = Interop.Sys.GetUName(status.Uid) ?? string.Empty; | ||
| entry._header._gName = Interop.Sys.GetGName(status.Gid) ?? string.Empty; | ||
|          | ||
|  | ||
| if (entry.EntryType == TarEntryType.SymbolicLink) | ||
| { | ||
|  | @@ -74,16 +73,9 @@ partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, str | |
|  | ||
| if (entry.EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) | ||
| { | ||
| FileStreamOptions options = new() | ||
| { | ||
| Mode = FileMode.Open, | ||
| Access = FileAccess.Read, | ||
| Share = FileShare.Read, | ||
| Options = FileOptions.None | ||
| }; | ||
|  | ||
| Debug.Assert(entry._header._dataStream == null); | ||
| entry._header._dataStream = File.Open(fullPath, options); | ||
| // FileMode.Open, FileAccess.Read and FileShare.Read are default FileStreamOptions | ||
| entry._header._dataStream = new FileStream(fullPath, new FileStreamOptions()); | ||
|         
                  carlossanlop marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| } | ||
|  | ||
| WriteEntry(entry); | ||
|  | ||
Uh oh!
There was an error while loading. Please reload this page.