// Reads attributes specific to the GNU format. // Throws if any conversion fails. private void ReadGnuAttributes(Span <byte> buffer) { // Convert byte arrays long aTime = (long)TarHelpers.ParseOctal <ulong>(buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); _aTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(aTime); long cTime = (long)TarHelpers.ParseOctal <ulong>(buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); _cTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(cTime); // TODO: Read the bytes of the currently unsupported GNU fields, in case user wants to write this entry into another GNU archive, they need to be preserved. https://github.com/dotnet/runtime/issues/68230 }
// Creates an entry for writing using the specified path and entryName. If this is being called from an async method, FileOptions should contain Asynchronous. private TarEntry ConstructEntryForWriting(string fullPath, string entryName, FileOptions fileOptions) { Debug.Assert(!string.IsNullOrEmpty(fullPath)); Interop.Sys.FileStatus status = default; status.Mode = default; status.Dev = default; Interop.CheckIo(Interop.Sys.LStat(fullPath, out status)); TarEntryType entryType = (status.Mode & (uint)Interop.Sys.FileTypes.S_IFMT) switch { // Hard links are treated as regular files. // Unix socket files do not get added to tar files. Interop.Sys.FileTypes.S_IFBLK => TarEntryType.BlockDevice, Interop.Sys.FileTypes.S_IFCHR => TarEntryType.CharacterDevice, Interop.Sys.FileTypes.S_IFIFO => TarEntryType.Fifo, Interop.Sys.FileTypes.S_IFLNK => TarEntryType.SymbolicLink, Interop.Sys.FileTypes.S_IFREG => Format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, Interop.Sys.FileTypes.S_IFDIR => TarEntryType.Directory, _ => throw new IOException(string.Format(SR.TarUnsupportedFile, fullPath)), }; FileSystemInfo info = entryType is TarEntryType.Directory ? new DirectoryInfo(fullPath) : new FileInfo(fullPath); TarEntry entry = Format switch { TarEntryFormat.V7 => new V7TarEntry(entryType, entryName), TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), }; if (entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice) { uint major; uint minor; unsafe { Interop.Sys.GetDeviceIdentifiers((ulong)status.RDev, &major, &minor); } entry._header._devMajor = (int)major; entry._header._devMinor = (int)minor; } entry._header._mTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(status.MTime); entry._header._aTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(status.ATime); entry._header._cTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(status.CTime); entry._header._mode = status.Mode & 4095; // First 12 bits // Uid and UName entry._header._uid = (int)status.Uid; if (!_userIdentifiers.TryGetValue(status.Uid, out string?uName)) { uName = Interop.Sys.GetUserNameFromPasswd(status.Uid); _userIdentifiers.Add(status.Uid, uName); } entry._header._uName = uName; // Gid and GName entry._header._gid = (int)status.Gid; if (!_groupIdentifiers.TryGetValue(status.Gid, out string?gName)) { gName = Interop.Sys.GetGroupName(status.Gid); _groupIdentifiers.Add(status.Gid, gName); } entry._header._gName = gName; if (entry.EntryType == TarEntryType.SymbolicLink) { entry.LinkName = info.LinkTarget ?? string.Empty; } if (entry.EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) { Debug.Assert(entry._header._dataStream == null); entry._header._dataStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, fileOptions); } return(entry); }
// Attempts to read the fields shared by all formats and stores them in their expected data type. // Throws if any data type conversion fails. // Returns true on success, false if checksum is zero. private static TarHeader?TryReadCommonAttributes(Span <byte> buffer, TarEntryFormat initialFormat) { // Start by collecting fields that need special checks that return early when data is wrong // Empty checksum means this is an invalid (all blank) entry, finish early Span <byte> spanChecksum = buffer.Slice(FieldLocations.Checksum, FieldLengths.Checksum); if (TarHelpers.IsAllNullBytes(spanChecksum)) { return(null); } int checksum = (int)TarHelpers.ParseOctal <uint>(spanChecksum); // Zero checksum means the whole header is empty if (checksum == 0) { return(null); } long size = (int)TarHelpers.ParseOctal <uint>(buffer.Slice(FieldLocations.Size, FieldLengths.Size)); if (size < 0) { throw new FormatException(string.Format(SR.TarSizeFieldNegative)); } // Continue with the rest of the fields that require no special checks TarHeader header = new(initialFormat, name : TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.Name, FieldLengths.Name)), mode : (int)TarHelpers.ParseOctal <uint>(buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)), mTime : TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch((long)TarHelpers.ParseOctal <ulong>(buffer.Slice(FieldLocations.MTime, FieldLengths.MTime))), typeFlag : (TarEntryType)buffer[FieldLocations.TypeFlag]) { _checksum = checksum, _size = size, _uid = (int)TarHelpers.ParseOctal <uint>(buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)), _gid = (int)TarHelpers.ParseOctal <uint>(buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)), _linkName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)) }; if (header._format == TarEntryFormat.Unknown) { header._format = header._typeFlag switch { TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes => TarEntryFormat.Pax, TarEntryType.DirectoryList or TarEntryType.LongLink or TarEntryType.LongPath or TarEntryType.MultiVolume or TarEntryType.RenamedOrSymlinked or TarEntryType.TapeVolume => TarEntryFormat.Gnu, // V7 is the only one that uses 'V7RegularFile'. TarEntryType.V7RegularFile => TarEntryFormat.V7, TarEntryType.SparseFile => throw new NotSupportedException(string.Format(SR.TarEntryTypeNotSupported, header._typeFlag)), // We can quickly determine the *minimum* possible format if the entry type // is the POSIX 'RegularFile', although later we could upgrade it to PAX or GNU _ => (header._typeFlag == TarEntryType.RegularFile) ? TarEntryFormat.Ustar : TarEntryFormat.V7 }; } return(header); }