// 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 { /// <summary> /// This class encapsulates the Tar Entry Header used in Tar Archives. /// The class also holds a number of tar constants, used mostly in headers. /// </summary> public class TarHeader : ICloneable { /// <summary> /// The length of the name field in a header buffer. /// </summary> public readonly static int NAMELEN = 100; /// <summary> /// The length of the mode field in a header buffer. /// </summary> public readonly static int MODELEN = 8; /// <summary> /// The length of the user id field in a header buffer. /// </summary> public readonly static int UIDLEN = 8; /// <summary> /// The length of the group id field in a header buffer. /// </summary> public readonly static int GIDLEN = 8; /// <summary> /// The length of the checksum field in a header buffer. /// </summary> public readonly static int CHKSUMLEN = 8; /// <summary> /// The length of the size field in a header buffer. /// </summary> public readonly static int SIZELEN = 12; /// <summary> /// The length of the magic field in a header buffer. /// </summary> public readonly static int MAGICLEN = 6; /// <summary> /// The length of the version field in a header buffer. /// </summary> public readonly static int VERSIONLEN = 2; /// <summary> /// The length of the modification time field in a header buffer. /// </summary> public readonly static int MODTIMELEN = 12; /// <summary> /// The length of the user name field in a header buffer. /// </summary> public readonly static int UNAMELEN = 32; /// <summary> /// The length of the group name field in a header buffer. /// </summary> public readonly static int GNAMELEN = 32; /// <summary> /// The length of the devices field in a header buffer. /// </summary> public readonly static int DEVLEN = 8; /// <summary> /// LF_ constants represents the "type" of an entry /// </summary> /// /// <summary> /// This is the "old way" of indicating a normal file. /// </summary> public const byte LF_OLDNORM = 0; /// <summary> /// Normal file type. /// </summary> public const byte LF_NORMAL = (byte) '0'; /// <summary> /// Link file type. /// </summary> public const byte LF_LINK = (byte) '1'; /// <summary> /// Symbolic link file type. /// </summary> public const byte LF_SYMLINK = (byte) '2'; /// <summary> /// Character device file type. /// </summary> public const byte LF_CHR = (byte) '3'; /// <summary> /// Block device file type. /// </summary> public const byte LF_BLK = (byte) '4'; /// <summary> /// Directory file type. /// </summary> public const byte LF_DIR = (byte) '5'; /// <summary> /// FIFO (pipe) file type. /// </summary> public const byte LF_FIFO = (byte) '6'; /// <summary> /// Contiguous file type. /// </summary> public const byte LF_CONTIG = (byte) '7'; /// <summary> /// Posix.1 2001 global extended header /// </summary> /// public const byte LF_GHDR = (byte) 'g'; /// <summary> /// Posix.1 2001 extended header /// </summary> 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'; /// <summary> /// The magic tag representing a POSIX tar archive. (includes trailing NULL) /// </summary> public readonly static string TMAGIC = "ustar "; /// <summary> /// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it /// </summary> public readonly static string GNU_TMAGIC = "ustar "; /// <summary> /// The entry's name. /// </summary> public StringBuilder name; /// <summary> /// The entry's permission mode. /// </summary> public int mode; /// <summary> /// The entry's user id. /// </summary> public int userId; /// <summary> /// The entry's group id. /// </summary> public int groupId; /// <summary> /// The entry's size. /// </summary> public long size; /// <summary> /// The entry's modification time. /// </summary> public DateTime modTime; /// <summary> /// The entry's checksum. /// </summary> public int checkSum; /// <summary> /// The entry's type flag. /// </summary> public byte typeFlag; /// <summary> /// The entry's link name. /// </summary> public StringBuilder linkName; /// <summary> /// The entry's magic tag. /// </summary> public StringBuilder magic; /// <summary> /// The entry's version. /// </summary> public StringBuilder version; /// <summary> /// The entry's user name. /// </summary> public StringBuilder userName; /// <summary> /// The entry's group name. /// </summary> public StringBuilder groupName; /// <summary> /// The entry's major device number. /// </summary> public int devMajor; /// <summary> /// The entry's minor device number. /// </summary> 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; } /// <summary> /// TarHeaders can be cloned. /// </summary> 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; } /// <summary> /// Get the name of this entry. /// </summary> /// <returns> /// The entry's name. /// </returns> public string GetName() { return this.name.ToString(); } /// <summary> /// Parse an octal string from a header buffer. This is used for the /// file permission mode value. /// </summary> /// <param name = "header"> /// The header buffer from which to parse. /// </param> /// <param name = "offset"> /// The offset into the buffer from which to parse. /// </param> /// <param name = "length"> /// The number of header bytes to parse. /// </param> /// <returns> /// The long value of the octal string. /// </returns> 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; } /// <summary> /// Parse an entry name from a header buffer. /// </summary> /// <param name="header"> /// The header buffer from which to parse. /// </param> /// <param name="offset"> /// The offset into the buffer from which to parse. /// </param> /// <param name="length"> /// The number of header bytes to parse. /// </param> /// <returns> /// The header's entry name. /// </returns> 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; } /// <summary> /// Determine the number of bytes in an entry name. /// </summary> /// <param name="name"> /// </param> /// <param name="buf"> /// The header buffer from which to parse. /// </param> /// <param name="offset"> /// The offset into the buffer from which to parse. /// </param> /// <param name="length"> /// The number of header bytes to parse. /// </param> /// <returns> /// The number of bytes in a header's entry name. /// </returns> public static int GetNameBytes(StringBuilder name, byte[] buf, int offset, int length) { return GetNameBytes(name, 0, buf, offset, length); } /// <summary> /// Parse an octal integer from a header buffer. /// </summary> /// <param name = "val"> /// </param> /// <param name = "buf"> /// The header buffer from which to parse. /// </param> /// <param name = "offset"> /// The offset into the buffer from which to parse. /// </param> /// <param name = "length"> /// The number of header bytes to parse. /// </param> /// <returns> /// The integer value of the octal bytes. /// </returns> 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; } /// <summary> /// Parse an octal long integer from a header buffer. /// </summary> /// <param name = "val"> /// </param> /// <param name = "buf"> /// The header buffer from which to parse. /// </param> /// <param name = "offset"> /// The offset into the buffer from which to parse. /// </param> /// <param name = "length"> /// The number of header bytes to parse. /// </param> /// <returns> /// The long value of the octal bytes. /// </returns> public static int GetLongOctalBytes(long val, byte[] buf, int offset, int length) { return GetOctalBytes(val, buf, offset, length); } /// <summary> /// Add the checksum octal integer to header buffer. /// </summary> /// <param name = "val"> /// </param> /// <param name = "buf"> /// The header buffer to set the checksum for /// </param> /// <param name = "offset"> /// The offset into the buffer for the checksum /// </param> /// <param name = "length"> /// 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 /// </param> /// <returns> /// The modified buffer offset /// </returns> 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; } /// <summary> /// Compute the checksum for a tar entry header. /// The checksum field must be all spaces prior to this happening /// </summary> /// <param name = "buf"> /// The tar entry's header buffer. /// </param> /// <returns> /// The computed checksum. /// </returns> 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); } /// <summary> /// Parse TarHeader information from a header buffer. /// </summary> /// <param name = "header"> /// The tar entry header buffer to get information from. /// </param> 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... } /// <summary> /// 'Write' header information to buffer provided /// </summary> /// <param name="outbuf">output buffer for header information</param> 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 ** <mailto:time@gjt.org> <http://www.trustice.com> ** ** 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. ** */