// TarHeader.cs // // Copyright (C) 2001 Mike Krueger // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // Linking this library statically or dynamically with other modules is // making a combined work based on this library. Thus, the terms and // conditions of the GNU General Public License cover the whole // combination. // // As a special exception, the copyright holders of this library give you // permission to link this library with independent modules to produce an // executable, regardless of the license terms of these independent // modules, and to copy and distribute the resulting executable under // terms of your choice, provided that you also meet, for each linked // independent module, the terms and conditions of the license of that // module. An independent module is a module which is not derived from // or based on this library. If you modify this library, you may extend // this exception to your version of the library, but you are not // obligated to do so. If you do not wish to do so, delete this // exception statement from your version. /* The tar format and its POSIX successor PAX have a long history which makes for compatability issues when creating and reading files... This is the ustar (Posix 1003.1) header. struct header { char t_name[100]; // 0 Filename char t_mode[8]; // 100 Permissions char t_uid[8]; // 108 Numerical User ID char t_gid[8]; // 116 Numerical Group ID char t_size[12]; // 124 Filesize char t_mtime[12]; // 136 st_mtime char t_chksum[8]; // 148 Checksum char t_typeflag; // 156 Type of File char t_linkname[100]; // 157 Target of Links char t_magic[6]; // 257 "ustar" char t_version[2]; // 263 Version fixed to 00 char t_uname[32]; // 265 User Name char t_gname[32]; // 297 Group Name char t_devmajor[8]; // 329 Major for devices char t_devminor[8]; // 337 Minor for devices char t_prefix[155]; // 345 Prefix for t_name // 500 End char t_mfill[12]; // 500 Filler up to 512 }; */ using System; using System.Text; namespace ICSharpCode.SharpZipLib.Tar { /// /// This class encapsulates the Tar Entry Header used in Tar Archives. /// The class also holds a number of tar constants, used mostly in headers. /// public class TarHeader : ICloneable { /// /// The length of the name field in a header buffer. /// public readonly static int NAMELEN = 100; /// /// The length of the mode field in a header buffer. /// public readonly static int MODELEN = 8; /// /// The length of the user id field in a header buffer. /// public readonly static int UIDLEN = 8; /// /// The length of the group id field in a header buffer. /// public readonly static int GIDLEN = 8; /// /// The length of the checksum field in a header buffer. /// public readonly static int CHKSUMLEN = 8; /// /// The length of the size field in a header buffer. /// public readonly static int SIZELEN = 12; /// /// The length of the magic field in a header buffer. /// public readonly static int MAGICLEN = 6; /// /// The length of the version field in a header buffer. /// public readonly static int VERSIONLEN = 2; /// /// The length of the modification time field in a header buffer. /// public readonly static int MODTIMELEN = 12; /// /// The length of the user name field in a header buffer. /// public readonly static int UNAMELEN = 32; /// /// The length of the group name field in a header buffer. /// public readonly static int GNAMELEN = 32; /// /// The length of the devices field in a header buffer. /// public readonly static int DEVLEN = 8; /// /// LF_ constants represents the "type" of an entry /// /// /// /// This is the "old way" of indicating a normal file. /// public const byte LF_OLDNORM = 0; /// /// Normal file type. /// public const byte LF_NORMAL = (byte) '0'; /// /// Link file type. /// public const byte LF_LINK = (byte) '1'; /// /// Symbolic link file type. /// public const byte LF_SYMLINK = (byte) '2'; /// /// Character device file type. /// public const byte LF_CHR = (byte) '3'; /// /// Block device file type. /// public const byte LF_BLK = (byte) '4'; /// /// Directory file type. /// public const byte LF_DIR = (byte) '5'; /// /// FIFO (pipe) file type. /// public const byte LF_FIFO = (byte) '6'; /// /// Contiguous file type. /// public const byte LF_CONTIG = (byte) '7'; /// /// Posix.1 2001 global extended header /// /// public const byte LF_GHDR = (byte) 'g'; /// /// Posix.1 2001 extended header /// public readonly static byte LF_XHDR = (byte) 'x'; // POSIX allows for upper case ascii type as extensions // Solaris access control list public const byte LF_ACL = (byte) 'A'; // This is a dir entry that contains the names of files that were in the // dir at the time the dump was made public const byte LF_GNU_DUMPDIR = (byte) 'D'; // Solaris Extended Attribute File public const byte LF_EXTATTR = (byte) 'E' ; // Inode (metadata only) no file content public const byte LF_META = (byte) 'I'; // Identifies the next file on the tape as having a long link name public const byte LF_GNU_LONGLINK = (byte) 'K'; // Identifies the next file on the tape as having a long name public const byte LF_GNU_LONGNAME = (byte) 'L'; // Continuation of a file that began on another volume public const byte LF_GNU_MULTIVOL = (byte) 'M'; // For storing filenames that dont fit in the main header (old GNU) public const byte LF_GNU_NAMES = (byte) 'N'; // Sparse file public const byte LF_GNU_SPARSE = (byte) 'S'; // Tape/volume header ignore on extraction public const byte LF_GNU_VOLHDR = (byte) 'V'; /// /// The magic tag representing a POSIX tar archive. (includes trailing NULL) /// public readonly static string TMAGIC = "ustar "; /// /// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it /// public readonly static string GNU_TMAGIC = "ustar "; /// /// The entry's name. /// public StringBuilder name; /// /// The entry's permission mode. /// public int mode; /// /// The entry's user id. /// public int userId; /// /// The entry's group id. /// public int groupId; /// /// The entry's size. /// public long size; /// /// The entry's modification time. /// public DateTime modTime; /// /// The entry's checksum. /// public int checkSum; /// /// The entry's type flag. /// public byte typeFlag; /// /// The entry's link name. /// public StringBuilder linkName; /// /// The entry's magic tag. /// public StringBuilder magic; /// /// The entry's version. /// public StringBuilder version; /// /// The entry's user name. /// public StringBuilder userName; /// /// The entry's group name. /// public StringBuilder groupName; /// /// The entry's major device number. /// public int devMajor; /// /// The entry's minor device number. /// public int devMinor; public TarHeader() { this.magic = new StringBuilder(TarHeader.TMAGIC); this.version = new StringBuilder(" "); this.name = new StringBuilder(); this.linkName = new StringBuilder(); string user = Environment.UserName; // string user = "PocketPC"; // string user = "Everyone"; if (user.Length > 31) { user = user.Substring(0, 31); } this.userId = 1003; // -jr- was 0 this.groupId = 513; // -jr- was 0 this.userName = new StringBuilder(user); // -jr- // this.groupName = new StringBuilder(String.Empty); // this.groupName = new StringBuilder("Everyone"); Attempt2 this.groupName = new StringBuilder("None"); // Gnu compatible this.size = 0; } /// /// TarHeaders can be cloned. /// public object Clone() { TarHeader hdr = new TarHeader(); hdr.name = (this.name == null) ? null : new StringBuilder(this.name.ToString()); hdr.mode = this.mode; hdr.userId = this.userId; hdr.groupId = this.groupId; hdr.size = this.size; hdr.modTime = this.modTime; hdr.checkSum = this.checkSum; hdr.typeFlag = this.typeFlag; hdr.linkName = (this.linkName == null) ? null : new StringBuilder(this.linkName.ToString()); hdr.magic = (this.magic == null) ? null : new StringBuilder(this.magic.ToString()); hdr.version = (this.version == null) ? null : new StringBuilder(this.version.ToString()); hdr.userName = (this.userName == null) ? null : new StringBuilder(this.userName.ToString()); hdr.groupName = (this.groupName == null) ? null : new StringBuilder(this.groupName.ToString()); hdr.devMajor = this.devMajor; hdr.devMinor = this.devMinor; return hdr; } /// /// Get the name of this entry. /// /// /// The entry's name. /// public string GetName() { return this.name.ToString(); } /// /// Parse an octal string from a header buffer. This is used for the /// file permission mode value. /// /// /// The header buffer from which to parse. /// /// /// The offset into the buffer from which to parse. /// /// /// The number of header bytes to parse. /// /// /// The long value of the octal string. /// public static long ParseOctal(byte[] header, int offset, int length) { long result = 0; bool stillPadding = true; int end = offset + length; for (int i = offset; i < end ; ++i) { if (header[i] == 0) { break; } if (header[i] == (byte)' ' || header[i] == '0') { if (stillPadding) { continue; } if (header[i] == (byte)' ') { break; } } stillPadding = false; result = (result << 3) + (header[i] - '0'); } return result; } /// /// Parse an entry name from a header buffer. /// /// /// The header buffer from which to parse. /// /// /// The offset into the buffer from which to parse. /// /// /// The number of header bytes to parse. /// /// /// The header's entry name. /// public static StringBuilder ParseName(byte[] header, int offset, int length) { StringBuilder result = new StringBuilder(length); for (int i = offset; i < offset + length; ++i) { if (header[i] == 0) { break; } result.Append((char)header[i]); } return result; } public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buf, int bufferOffset, int length) { int i; for (i = 0 ; i < length && nameOffset + i < name.Length; ++i) { buf[bufferOffset + i] = (byte)name[nameOffset + i]; } for (; i < length ; ++i) { buf[bufferOffset + i] = 0; } return bufferOffset + length; } /// /// Determine the number of bytes in an entry name. /// /// /// /// /// The header buffer from which to parse. /// /// /// The offset into the buffer from which to parse. /// /// /// The number of header bytes to parse. /// /// /// The number of bytes in a header's entry name. /// public static int GetNameBytes(StringBuilder name, byte[] buf, int offset, int length) { return GetNameBytes(name, 0, buf, offset, length); } /// /// Parse an octal integer from a header buffer. /// /// /// /// /// The header buffer from which to parse. /// /// /// The offset into the buffer from which to parse. /// /// /// The number of header bytes to parse. /// /// /// The integer value of the octal bytes. /// public static int GetOctalBytes(long val, byte[] buf, int offset, int length) { // TODO check for values too large... int idx = length - 1; // Either a space or null is valid here. We use NULL as per GNUTar buf[offset + idx] = 0; --idx; if (val > 0) { for (long v = val; idx >= 0 && v > 0; --idx) { buf[offset + idx] = (byte)((byte)'0' + (byte)(v & 7)); v >>= 3; } } for (; idx >= 0; --idx) { buf[offset + idx] = (byte)'0'; } return offset + length; } /// /// Parse an octal long integer from a header buffer. /// /// /// /// /// The header buffer from which to parse. /// /// /// The offset into the buffer from which to parse. /// /// /// The number of header bytes to parse. /// /// /// The long value of the octal bytes. /// public static int GetLongOctalBytes(long val, byte[] buf, int offset, int length) { return GetOctalBytes(val, buf, offset, length); } /// /// Add the checksum octal integer to header buffer. /// /// /// /// /// The header buffer to set the checksum for /// /// /// The offset into the buffer for the checksum /// /// /// The number of header bytes to update. /// It's formatted differently from the other fields: it has 6 digits, a /// null, then a space -- rather than digits, a space, then a null. /// The final space is already there, from checksumming /// /// /// The modified buffer offset /// private static int GetCheckSumOctalBytes(long val, byte[] buf, int offset, int length) { TarHeader.GetOctalBytes(val, buf, offset, length - 1); // buf[offset + length - 1] = (byte)' '; -jr- 23-Jan-2004 this causes failure!!! // buf[offset + length - 2] = 0; return offset + length; } /// /// Compute the checksum for a tar entry header. /// The checksum field must be all spaces prior to this happening /// /// /// The tar entry's header buffer. /// /// /// The computed checksum. /// private static long ComputeCheckSum(byte[] buf) { long sum = 0; for (int i = 0; i < buf.Length; ++i) { sum += buf[i]; } return sum; } readonly static long timeConversionFactor = 10000000L; // -jr- 1 tick == 100 nanoseconds readonly static DateTime datetTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0); // readonly static DateTime datetTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToUniversalTime(); // -jr- Should be UTC? doesnt match Gnutar if this is so though, why? static int GetCTime(System.DateTime dateTime) { return (int)((dateTime.Ticks - datetTime1970.Ticks) / timeConversionFactor); } static DateTime GetDateTimeFromCTime(long ticks) { return new DateTime(datetTime1970.Ticks + ticks * timeConversionFactor); } /// /// Parse TarHeader information from a header buffer. /// /// /// The tar entry header buffer to get information from. /// public void ParseBuffer(byte[] header) { int offset = 0; name = TarHeader.ParseName(header, offset, TarHeader.NAMELEN); offset += TarHeader.NAMELEN; mode = (int)TarHeader.ParseOctal(header, offset, TarHeader.MODELEN); offset += TarHeader.MODELEN; userId = (int)TarHeader.ParseOctal(header, offset, TarHeader.UIDLEN); offset += TarHeader.UIDLEN; groupId = (int)TarHeader.ParseOctal(header, offset, TarHeader.GIDLEN); offset += TarHeader.GIDLEN; size = TarHeader.ParseOctal(header, offset, TarHeader.SIZELEN); offset += TarHeader.SIZELEN; modTime = GetDateTimeFromCTime(TarHeader.ParseOctal(header, offset, TarHeader.MODTIMELEN)); offset += TarHeader.MODTIMELEN; checkSum = (int)TarHeader.ParseOctal(header, offset, TarHeader.CHKSUMLEN); offset += TarHeader.CHKSUMLEN; typeFlag = header[ offset++ ]; linkName = TarHeader.ParseName(header, offset, TarHeader.NAMELEN); offset += TarHeader.NAMELEN; magic = TarHeader.ParseName(header, offset, TarHeader.MAGICLEN); offset += TarHeader.MAGICLEN; version = TarHeader.ParseName(header, offset, TarHeader.VERSIONLEN); offset += TarHeader.VERSIONLEN; userName = TarHeader.ParseName(header, offset, TarHeader.UNAMELEN); offset += TarHeader.UNAMELEN; groupName = TarHeader.ParseName(header, offset, TarHeader.GNAMELEN); offset += TarHeader.GNAMELEN; devMajor = (int)TarHeader.ParseOctal(header, offset, TarHeader.DEVLEN); offset += TarHeader.DEVLEN; devMinor = (int)TarHeader.ParseOctal(header, offset, TarHeader.DEVLEN); // Fields past this point not currently parsed or used... } /// /// 'Write' header information to buffer provided /// /// output buffer for header information public void WriteHeader(byte[] outbuf) { int offset = 0; offset = GetNameBytes(this.name, outbuf, offset, TarHeader.NAMELEN); offset = GetOctalBytes(this.mode, outbuf, offset, TarHeader.MODELEN); offset = GetOctalBytes(this.userId, outbuf, offset, TarHeader.UIDLEN); offset = GetOctalBytes(this.groupId, outbuf, offset, TarHeader.GIDLEN); long size = this.size; offset = GetLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN); offset = GetLongOctalBytes(GetCTime(this.modTime), outbuf, offset, TarHeader.MODTIMELEN); int csOffset = offset; for (int c = 0; c < TarHeader.CHKSUMLEN; ++c) { outbuf[offset++] = (byte)' '; } outbuf[offset++] = this.typeFlag; offset = GetNameBytes(this.linkName, outbuf, offset, NAMELEN); offset = GetNameBytes(this.magic, outbuf, offset, MAGICLEN); offset = GetNameBytes(this.version, outbuf, offset, VERSIONLEN); offset = GetNameBytes(this.userName, outbuf, offset, UNAMELEN); offset = GetNameBytes(this.groupName, outbuf, offset, GNAMELEN); if (this.typeFlag == LF_CHR || this.typeFlag == LF_BLK) { offset = GetOctalBytes(this.devMajor, outbuf, offset, DEVLEN); offset = GetOctalBytes(this.devMinor, outbuf, offset, DEVLEN); } for ( ; offset < outbuf.Length; ) { outbuf[offset++] = 0; } long checkSum = ComputeCheckSum(outbuf); GetCheckSumOctalBytes(checkSum, outbuf, csOffset, CHKSUMLEN); } } } /* The original Java file had this header: * ** Authored by Timothy Gerard Endres ** ** ** This work has been placed into the public domain. ** You may use this work in any way and for any purpose you wish. ** ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR ** REDISTRIBUTION OF THIS SOFTWARE. ** */