// Reads attributes specific to the GNU format. // Throws if any conversion fails. private void ReadGnuAttributes(Span <byte> buffer) { // Convert byte arrays int aTime = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); _aTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(aTime); int cTime = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); _cTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(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 }
// Unix specific implementation of the method that reads an entry from disk and writes it into the archive stream. partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, string entryName) { 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 TarFormat.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 { TarFormat.V7 => new V7TarEntry(entryType, entryName), TarFormat.Ustar => new UstarTarEntry(entryType, entryName), TarFormat.Pax => new PaxTarEntry(entryType, entryName), TarFormat.Gnu => new GnuTarEntry(entryType, entryName), _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), }; if ((entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice) && status.Dev > 0) { uint major; uint minor; unsafe { Interop.CheckIo(Interop.Sys.GetDeviceIdentifiers((ulong)status.Dev, &major, &minor)); } entry._header._devMajor = (int)major; entry._header._devMinor = (int)minor; } entry._header._mTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(status.MTime); entry._header._aTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(status.ATime); entry._header._cTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(status.CTime); entry._header._mode = (status.Mode & 4095); // First 12 bits entry.Uid = (int)status.Uid; entry.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(); if (entry.EntryType == TarEntryType.SymbolicLink) { entry.LinkName = info.LinkTarget ?? string.Empty; } 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); } WriteEntry(entry); if (entry._header._dataStream != null) { entry._header._dataStream.Dispose(); } } }
// Unix specific implementation of the method that reads an entry from disk and writes it into the archive stream. partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, string entryName) { 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.GetDateTimeFromSecondsSinceEpoch(status.MTime); entry._header._aTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(status.ATime); entry._header._cTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(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 = File.OpenRead(fullPath); } WriteEntry(entry); if (entry._header._dataStream != null) { entry._header._dataStream.Dispose(); } }
// 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 bool TryReadCommonAttributes(Span <byte> buffer) { // 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(false); } _checksum = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(spanChecksum); // Zero checksum means the whole header is empty if (_checksum == 0) { return(false); } _size = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Size, FieldLengths.Size)); if (_size < 0) { throw new FormatException(string.Format(SR.TarSizeFieldNegative, _name)); } // Continue with the rest of the fields that require no special checks _name = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.Name, FieldLengths.Name)); _mode = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)); _uid = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)); _gid = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)); int mTime = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.MTime, FieldLengths.MTime)); _mTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(mTime); _typeFlag = (TarEntryType)buffer[FieldLocations.TypeFlag]; _linkName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)); if (_format == TarFormat.Unknown) { _format = _typeFlag switch { TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes => TarFormat.Pax, TarEntryType.DirectoryList or TarEntryType.LongLink or TarEntryType.LongPath or TarEntryType.MultiVolume or TarEntryType.RenamedOrSymlinked or TarEntryType.SparseFile or TarEntryType.TapeVolume => TarFormat.Gnu, // V7 is the only one that uses 'V7RegularFile'. TarEntryType.V7RegularFile => TarFormat.V7, // 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 _ => (_typeFlag == TarEntryType.RegularFile) ? TarFormat.Ustar : TarFormat.V7 }; } return(true); }