diff --git a/include/wx/tarstrm.h b/include/wx/tarstrm.h new file mode 100644 index 0000000000..8a5baf6675 --- /dev/null +++ b/include/wx/tarstrm.h @@ -0,0 +1,349 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: tarstrm.h +// Purpose: Streams for Tar files +// Author: Mike Wetherell +// RCS-ID: $Id$ +// Copyright: (c) 2004 Mike Wetherell +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_WXTARSTREAM_H__ +#define _WX_WXTARSTREAM_H__ + +#include "wx/defs.h" + +#if wxUSE_TARSTREAM + +#include "wx/archive.h" + + +///////////////////////////////////////////////////////////////////////////// +// Constants + +// TypeFlag values +enum { + wxTAR_REGTYPE = '0', // regular file + wxTAR_LNKTYPE = '1', // hard link + wxTAR_SYMTYPE = '2', // symbolic link + wxTAR_CHRTYPE = '3', // character special + wxTAR_BLKTYPE = '4', // block special + wxTAR_DIRTYPE = '5', // directory + wxTAR_FIFOTYPE = '6', // named pipe + wxTAR_CONTTYPE = '7' // contiguous file +}; + +// Archive Formats (use wxTAR_PAX, it's backward compatible) +enum wxTarFormat +{ + wxTAR_USTAR, // POSIX.1-1990 tar format + wxTAR_PAX // POSIX.1-2001 tar format +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxTarNotifier + +class wxTarNotifier +{ +public: + virtual ~wxTarNotifier() { } + + virtual void OnEntryUpdated(class wxTarEntry& entry) = 0; +}; + + +///////////////////////////////////////////////////////////////////////////// +// Tar Entry - hold the meta data for a file in the tar + +class wxTarEntry : public wxArchiveEntry +{ +public: + wxTarEntry(const wxString& name = wxEmptyString, + const wxDateTime& dt = wxDateTime::Now(), + wxFileOffset size = wxInvalidOffset); + virtual ~wxTarEntry(); + + wxTarEntry(const wxTarEntry& entry); + wxTarEntry& operator=(const wxTarEntry& entry); + + // Get accessors + wxString GetName(wxPathFormat format = wxPATH_NATIVE) const; + wxString GetInternalName() const { return m_Name; } + wxPathFormat GetInternalFormat() const { return wxPATH_UNIX; } + int GetMode() const; + int GetUserId() const { return m_UserId; } + int GetGroupId() const { return m_GroupId; } + wxFileOffset GetSize() const { return m_Size; } + wxFileOffset GetOffset() const { return m_Offset; } + wxDateTime GetDateTime() const { return m_ModifyTime; } + wxDateTime GetAccessTime() const { return m_AccessTime; } + wxDateTime GetCreateTime() const { return m_CreateTime; } + int GetTypeFlag() const { return m_TypeFlag; } + wxString GetLinkName() const { return m_LinkName; } + wxString GetUserName() const { return m_UserName; } + wxString GetGroupName() const { return m_GroupName; } + int GetDevMajor() const { return m_DevMajor; } + int GetDevMinor() const { return m_DevMinor; } + + // is accessors + bool IsDir() const; + bool IsReadOnly() const { return !(m_Mode & 0222); } + + // set accessors + void SetName(const wxString& name, wxPathFormat format = wxPATH_NATIVE); + void SetUserId(int id) { m_UserId = id; } + void SetGroupId(int id) { m_GroupId = id; } + void SetMode(int mode); + void SetSize(wxFileOffset size) { m_Size = size; } + void SetDateTime(const wxDateTime& dt) { m_ModifyTime = dt; } + void SetAccessTime(const wxDateTime& dt) { m_AccessTime = dt; } + void SetCreateTime(const wxDateTime& dt) { m_CreateTime = dt; } + void SetTypeFlag(int type) { m_TypeFlag = type; } + void SetLinkName(const wxString& link) { m_LinkName = link; } + void SetUserName(const wxString& user) { m_UserName = user; } + void SetGroupName(const wxString& group) { m_GroupName = group; } + void SetDevMajor(int dev) { m_DevMajor = dev; } + void SetDevMinor(int dev) { m_DevMinor = dev; } + + // set is accessors + void SetIsDir(bool isDir = true); + void SetIsReadOnly(bool isReadOnly = true); + + static wxString GetInternalName(const wxString& name, + wxPathFormat format = wxPATH_NATIVE, + bool *pIsDir = NULL); + + wxTarEntry *Clone() const { return new wxTarEntry(*this); } + + void SetNotifier(wxTarNotifier& WXUNUSED(notifier)) { } + +private: + void SetOffset(wxFileOffset offset) { m_Offset = offset; } + + virtual wxArchiveEntry* DoClone() const { return Clone(); } + + wxString m_Name; + int m_Mode; + bool m_IsModeSet; + int m_UserId; + int m_GroupId; + wxFileOffset m_Size; + wxFileOffset m_Offset; + wxDateTime m_ModifyTime; + wxDateTime m_AccessTime; + wxDateTime m_CreateTime; + wxChar m_TypeFlag; + wxString m_LinkName; + wxString m_UserName; + wxString m_GroupName; + int m_DevMajor; + int m_DevMinor; + + friend class wxTarInputStream; + + DECLARE_DYNAMIC_CLASS(wxTarEntry) +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxTarInputStream + +class wxTarInputStream : public wxArchiveInputStream +{ +public: + typedef wxTarEntry entry_type; + + wxTarInputStream(wxInputStream& stream, wxMBConv& conv = wxConvLocal); + wxTarInputStream(wxInputStream *stream, wxMBConv& conv = wxConvLocal); + virtual ~wxTarInputStream(); + + bool OpenEntry(wxTarEntry& entry); + bool CloseEntry(); + + wxTarEntry *GetNextEntry(); + + wxFileOffset GetLength() const { return m_size; } + bool IsSeekable() const { return m_parent_i_stream->IsSeekable(); } + +protected: + size_t OnSysRead(void *buffer, size_t size); + wxFileOffset OnSysTell() const { return m_pos; } + wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode); + +private: + void Init(); + + wxArchiveEntry *DoGetNextEntry() { return GetNextEntry(); } + bool OpenEntry(wxArchiveEntry& entry); + bool IsOpened() const { return m_pos != wxInvalidOffset; } + + wxStreamError ReadHeaders(); + bool ReadExtendedHeader(class wxTarHeaderRecords*& recs); + + wxString GetExtendedHeader(const wxString& key) const; + wxString GetHeaderPath() const; + wxFileOffset GetHeaderNumber(int id) const; + wxString GetHeaderString(int id) const; + wxDateTime GetHeaderDate(const wxString& key) const; + + wxFileOffset m_pos; // position within the current entry + wxFileOffset m_offset; // offset to the start of the entry's data + wxFileOffset m_size; // size of the current entry's data + + int m_sumType; + int m_tarType; + class wxTarHeaderBlock *m_hdr; + class wxTarHeaderRecords *m_HeaderRecs; + class wxTarHeaderRecords *m_GlobalHeaderRecs; + + DECLARE_NO_COPY_CLASS(wxTarInputStream) +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxTarOutputStream + +class wxTarOutputStream : public wxArchiveOutputStream +{ +public: + wxTarOutputStream(wxOutputStream& stream, + wxTarFormat format = wxTAR_PAX, + wxMBConv& conv = wxConvLocal); + wxTarOutputStream(wxOutputStream *stream, + wxTarFormat format = wxTAR_PAX, + wxMBConv& conv = wxConvLocal); + virtual ~wxTarOutputStream(); + + bool PutNextEntry(wxTarEntry *entry); + + bool PutNextEntry(const wxString& name, + const wxDateTime& dt = wxDateTime::Now(), + wxFileOffset size = wxInvalidOffset); + + bool PutNextDirEntry(const wxString& name, + const wxDateTime& dt = wxDateTime::Now()); + + bool CopyEntry(wxTarEntry *entry, wxTarInputStream& inputStream); + bool CopyArchiveMetaData(wxTarInputStream& WXUNUSED(s)) { return true; } + + void Sync(); + bool CloseEntry(); + bool Close(); + + bool IsSeekable() const { return m_parent_o_stream->IsSeekable(); } + + void SetBlockingFactor(int factor) { m_BlockingFactor = factor; } + int GetBlockingFactor() const { return m_BlockingFactor; } + +protected: + size_t OnSysWrite(const void *buffer, size_t size); + wxFileOffset OnSysTell() const { return m_pos; } + wxFileOffset OnSysSeek(wxFileOffset pos, wxSeekMode mode); + +private: + void Init(wxTarFormat format); + + bool PutNextEntry(wxArchiveEntry *entry); + bool CopyEntry(wxArchiveEntry *entry, wxArchiveInputStream& stream); + bool CopyArchiveMetaData(wxArchiveInputStream& WXUNUSED(s)) { return true; } + bool IsOpened() const { return m_pos != wxInvalidOffset; } + + bool WriteHeaders(wxTarEntry& entry); + bool ModifyHeader(); + wxString PaxHeaderPath(const wxString& format, const wxString& path); + + void SetExtendedHeader(const wxString& key, const wxString& value); + void SetHeaderPath(const wxString& name); + bool SetHeaderNumber(int id, wxFileOffset n); + void SetHeaderString(int id, const wxString& str); + void SetHeaderDate(const wxString& key, const wxDateTime& datetime); + + wxFileOffset m_pos; // position within the current entry + wxFileOffset m_maxpos; // max pos written + wxFileOffset m_size; // expected entry size + + wxFileOffset m_headpos; // offset within the file to the entry's header + wxFileOffset m_datapos; // offset within the file to the entry's data + + wxFileOffset m_tarstart;// offset within the file to the tar + wxFileOffset m_tarsize; // size of tar so far + + bool m_pax; + int m_BlockingFactor; + wxUint32 m_chksum; + bool m_large; + class wxTarHeaderBlock *m_hdr; + class wxTarHeaderBlock *m_hdr2; + char *m_extendedHdr; + size_t m_extendedSize; + wxString m_badfit; + + DECLARE_NO_COPY_CLASS(wxTarOutputStream) +}; + + +///////////////////////////////////////////////////////////////////////////// +// Iterators + +#if wxUSE_STL || defined WX_TEST_ARCHIVE_ITERATOR +typedef wxArchiveIterator wxTarIter; +typedef wxArchiveIterator > wxTarPairIter; +#endif + + +///////////////////////////////////////////////////////////////////////////// +// wxTarClassFactory + +class wxTarClassFactory : public wxArchiveClassFactory +{ +public: + typedef wxTarEntry entry_type; + typedef wxTarInputStream instream_type; + typedef wxTarOutputStream outstream_type; + typedef wxTarNotifier notifier_type; +#if wxUSE_STL || defined WX_TEST_ARCHIVE_ITERATOR + typedef wxTarIter iter_type; + typedef wxTarPairIter pairiter_type; +#endif + + wxTarClassFactory(); + + wxTarEntry *NewEntry() const + { return new wxTarEntry; } + wxTarInputStream *NewStream(wxInputStream& stream) const + { return new wxTarInputStream(stream, GetConv()); } + wxTarOutputStream *NewStream(wxOutputStream& stream) const + { return new wxTarOutputStream(stream, wxTAR_PAX, GetConv()); } + wxTarInputStream *NewStream(wxInputStream *stream) const + { return new wxTarInputStream(stream, GetConv()); } + wxTarOutputStream *NewStream(wxOutputStream *stream) const + { return new wxTarOutputStream(stream, wxTAR_PAX, GetConv()); } + + wxString GetInternalName(const wxString& name, + wxPathFormat format = wxPATH_NATIVE) const + { return wxTarEntry::GetInternalName(name, format); } + + const wxChar * const *GetProtocols(wxStreamProtocolType type + = wxSTREAM_PROTOCOL) const; + +protected: + wxArchiveEntry *DoNewEntry() const + { return NewEntry(); } + wxArchiveInputStream *DoNewStream(wxInputStream& stream) const + { return NewStream(stream); } + wxArchiveOutputStream *DoNewStream(wxOutputStream& stream) const + { return NewStream(stream); } + wxArchiveInputStream *DoNewStream(wxInputStream *stream) const + { return NewStream(stream); } + wxArchiveOutputStream *DoNewStream(wxOutputStream *stream) const + { return NewStream(stream); } + +private: + DECLARE_DYNAMIC_CLASS(wxTarClassFactory) +}; + + +#endif // wxUSE_TARSTREAM + +#endif // _WX_WXTARSTREAM_H__ diff --git a/src/common/tarstrm.cpp b/src/common/tarstrm.cpp new file mode 100644 index 0000000000..f3a1cec17a --- /dev/null +++ b/src/common/tarstrm.cpp @@ -0,0 +1,1457 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: tarstrm.cpp +// Purpose: Streams for Tar files +// Author: Mike Wetherell +// RCS-ID: $Id$ +// Copyright: (c) 2004 Mike Wetherell +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +// For compilers that support precompilation, includes "wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#if wxUSE_TARSTREAM + +#include "wx/tarstrm.h" + +#ifndef WX_PRECOMP + #include "wx/hashmap.h" + #include "wx/intl.h" + #include "wx/log.h" + #include "wx/utils.h" +#endif + +#include "wx/buffer.h" +#include "wx/datetime.h" +#include "wx/ptr_scpd.h" +#include "wx/filename.h" + +#include + +#ifdef __UNIX__ +#include +#endif + + +///////////////////////////////////////////////////////////////////////////// +// constants + +enum { + TAR_NAME, + TAR_MODE, + TAR_UID, + TAR_GID, + TAR_SIZE, + TAR_MTIME, + TAR_CHKSUM, + TAR_TYPEFLAG, + TAR_LINKNAME, + TAR_MAGIC, + TAR_VERSION, + TAR_UNAME, + TAR_GNAME, + TAR_DEVMAJOR, + TAR_DEVMINOR, + TAR_PREFIX, + TAR_UNUSED, + TAR_NUMFIELDS +}; + +enum { + TAR_BLOCKSIZE = 512, +}; + +// checksum type +enum { + SUM_UNKNOWN, + SUM_UNSIGNED, + SUM_SIGNED +}; + +// type of input tar +enum { + TYPE_OLDTAR, // fields after TAR_LINKNAME are invalid + TYPE_GNUTAR, // all fields except TAR_PREFIX are valid + TYPE_USTAR // all fields are valid +}; + +// signatures +static const char *USTAR_MAGIC = "ustar"; +static const char *USTAR_VERSION = "00"; +static const char *GNU_MAGIC = "ustar "; +static const char *GNU_VERION = " "; + +IMPLEMENT_DYNAMIC_CLASS(wxTarEntry, wxArchiveEntry) +IMPLEMENT_DYNAMIC_CLASS(wxTarClassFactory, wxArchiveClassFactory) + + +///////////////////////////////////////////////////////////////////////////// +// Class factory + +static wxTarClassFactory g_wxTarClassFactory; + +wxTarClassFactory::wxTarClassFactory() +{ + if (this == &g_wxTarClassFactory) + PushFront(); +} + +const wxChar * const * +wxTarClassFactory::GetProtocols(wxStreamProtocolType type) const +{ + static const wxChar *protocols[] = { _T("tar"), NULL }; + static const wxChar *mimetypes[] = { _T("application/x-tar"), NULL }; + static const wxChar *fileexts[] = { _T(".tar"), NULL }; + static const wxChar *empty[] = { NULL }; + + switch (type) { + case wxSTREAM_PROTOCOL: return protocols; + case wxSTREAM_MIMETYPE: return mimetypes; + case wxSTREAM_FILEEXTENSION: return fileexts; + default: return empty; + } +} + + +///////////////////////////////////////////////////////////////////////////// +// tar header block + +typedef wxFileOffset wxTarNumber; + +struct wxTarField { const wxChar *name; int pos; }; + +class wxTarHeaderBlock +{ +public: + wxTarHeaderBlock() + { memset(data, 0, sizeof(data)); } + wxTarHeaderBlock(const wxTarHeaderBlock& hb) + { memcpy(data, hb.data, sizeof(data)); } + + bool Read(wxInputStream& in); + bool Write(wxOutputStream& out); + inline bool WriteField(wxOutputStream& out, int id); + + bool IsAllZeros() const; + wxUint32 Sum(bool SignedSum = false); + wxUint32 SumField(int id); + + char *Get(int id) { return data + fields[id].pos + id; } + static size_t Len(int id) { return fields[id + 1].pos - fields[id].pos; } + static const wxChar *Name(int id) { return fields[id].name; } + static size_t Offset(int id) { return fields[id].pos; } + + bool SetOctal(int id, wxTarNumber n); + wxTarNumber GetOctal(int id); + bool SetPath(const wxString& name, wxMBConv& conv); + +private: + char data[TAR_BLOCKSIZE + TAR_NUMFIELDS]; + static const wxTarField fields[]; + static void check(); +}; + +wxDEFINE_SCOPED_PTR_TYPE(wxTarHeaderBlock); + +// A table giving the field names and offsets in a tar header block +const wxTarField wxTarHeaderBlock::fields[] = +{ + { _T("name"), 0 }, // 100 + { _T("mode"), 100 }, // 8 + { _T("uid"), 108 }, // 8 + { _T("gid"), 116 }, // 8 + { _T("size"), 124 }, // 12 + { _T("mtime"), 136 }, // 12 + { _T("chksum"), 148 }, // 8 + { _T("typeflag"), 156 }, // 1 + { _T("linkname"), 157 }, // 100 + { _T("magic"), 257 }, // 6 + { _T("version"), 263 }, // 2 + { _T("uname"), 265 }, // 32 + { _T("gname"), 297 }, // 32 + { _T("devmajor"), 329 }, // 8 + { _T("devminor"), 337 }, // 8 + { _T("prefix"), 345 }, // 155 + { _T("unused"), 500 }, // 12 + { NULL, TAR_BLOCKSIZE } +}; + +void wxTarHeaderBlock::check() +{ + wxCOMPILE_TIME_ASSERT( + WXSIZEOF(fields) == TAR_NUMFIELDS + 1, + Wrong_number_of_elements_in_fields_table + ); +} + +bool wxTarHeaderBlock::IsAllZeros() const +{ + const char *p = data; + for (size_t i = 0; i < sizeof(data); i++) + if (p[i]) + return false; + return true; +} + +wxUint32 wxTarHeaderBlock::Sum(bool SignedSum /*=false*/) +{ + // the chksum field itself should be blanks during the calculation + memset(Get(TAR_CHKSUM), ' ', Len(TAR_CHKSUM)); + const char *p = data; + wxUint32 n = 0; + + if (SignedSum) + for (size_t i = 0; i < sizeof(data); i++) + n += (signed char)p[i]; + else + for (size_t i = 0; i < sizeof(data); i++) + n += (unsigned char)p[i]; + + return n; +} + +wxUint32 wxTarHeaderBlock::SumField(int id) +{ + unsigned char *p = (unsigned char*)Get(id); + unsigned char *q = p + Len(id); + wxUint32 n = 0; + + while (p < q) + n += *p++; + + return n; +} + +bool wxTarHeaderBlock::Read(wxInputStream& in) +{ + bool ok = true; + + for (int id = 0; id < TAR_NUMFIELDS && ok; id++) + ok = in.Read(Get(id), Len(id)).LastRead() == Len(id); + + return ok; +} + +bool wxTarHeaderBlock::Write(wxOutputStream& out) +{ + bool ok = true; + + for (int id = 0; id < TAR_NUMFIELDS && ok; id++) + ok = WriteField(out, id); + + return ok; +} + +bool wxTarHeaderBlock::WriteField(wxOutputStream& out, int id) +{ + return out.Write(Get(id), Len(id)).LastWrite() == Len(id); +} + +wxTarNumber wxTarHeaderBlock::GetOctal(int id) +{ + wxTarNumber n = 0; + const char *p = Get(id); + while (*p == ' ') + p++; + while (*p >= '0' && *p < '8') + n = (n << 3) | (*p++ - '0'); + return n; +} + +bool wxTarHeaderBlock::SetOctal(int id, wxTarNumber n) +{ + // set an octal field, return true if the number fits + char *field = Get(id); + char *p = field + Len(id); + *--p = 0; + while (p > field) { + *--p = '0' + (n & 7); + n >>= 3; + } + return n == 0; +} + +bool wxTarHeaderBlock::SetPath(const wxString& name, wxMBConv& conv) +{ + bool badconv = false; + +#if wxUSE_UNICODE + wxCharBuffer nameBuf = name.mb_str(conv); + + // if the conversion fails make an approximation + if (!nameBuf) { + badconv = true; + size_t len = name.length(); + wxCharBuffer approx(len); + for (size_t i = 0; i < len; i++) + approx.data()[i] = name[i] & ~0x7F ? '_' : name[i]; + nameBuf = approx; + } + + const char *mbName = nameBuf; +#else + const char *mbName = name.c_str(); + (void)conv; +#endif + + bool fits; + bool notGoingToFit = false; + size_t len = strlen(mbName); + size_t maxname = Len(TAR_NAME); + size_t maxprefix = Len(TAR_PREFIX); + size_t i = 0; + size_t nexti = 0; + + for (;;) { + fits = i < maxprefix && len - i <= maxname; + + if (!fits) { + const char *p = strchr(mbName + i, '/'); + if (p) + nexti = p - mbName + 1; + if (!p || nexti - 1 > maxprefix) + notGoingToFit = true; + } + + if (fits || notGoingToFit) { + strncpy(Get(TAR_NAME), mbName + i, maxname); + if (i > 0) + strncpy(Get(TAR_PREFIX), mbName, i - 1); + break; + } + + i = nexti; + } + + return fits && !badconv; +} + + +///////////////////////////////////////////////////////////////////////////// +// Some helpers + +static wxFileOffset RoundUpSize(wxFileOffset size, int factor = 1) +{ + wxFileOffset chunk = TAR_BLOCKSIZE * factor; + return ((size + chunk - 1) / chunk) * chunk; +} + +static wxString GroupName() +{ +#ifdef __UNIX__ + group *gr; + if ((gr = getgrgid(getgid())) != NULL) + return wxString(gr->gr_name, wxConvLibc); +#endif + return _("unknown"); +} + +static inline int UserId() +{ +#ifdef __UNIX__ + return getuid(); +#else + return 0; +#endif +} + +static inline int GroupId() +{ +#ifdef __UNIX__ + return getgid(); +#else + return 0; +#endif +} + +// ignore the size field for entry types 3, 4, 5 and 6 +// +static inline wxFileOffset GetDataSize(const wxTarEntry& entry) +{ + switch (entry.GetTypeFlag()) { + case wxTAR_CHRTYPE: + case wxTAR_BLKTYPE: + case wxTAR_DIRTYPE: + case wxTAR_FIFOTYPE: + return 0; + default: + return entry.GetSize(); + }; +} + + +///////////////////////////////////////////////////////////////////////////// +// Tar Entry +// Holds all the meta-data for a file in the tar + +wxTarEntry::wxTarEntry(const wxString& name /*=wxEmptyString*/, + const wxDateTime& dt /*=wxDateTime::Now()*/, + wxFileOffset size /*=0*/) + : m_Mode(0644), + m_IsModeSet(false), + m_UserId(UserId()), + m_GroupId(GroupId()), + m_Size(size), + m_Offset(wxInvalidOffset), + m_ModifyTime(dt), + m_TypeFlag(wxTAR_REGTYPE), + m_UserName(wxGetUserId()), + m_GroupName(GroupName()), + m_DevMajor(~0), + m_DevMinor(~0) +{ + if (!name.empty()) + SetName(name); +} + +wxTarEntry::~wxTarEntry() +{ +} + +wxTarEntry::wxTarEntry(const wxTarEntry& e) + : m_Name(e.m_Name), + m_Mode(e.m_Mode), + m_IsModeSet(e.m_IsModeSet), + m_UserId(e.m_UserId), + m_GroupId(e.m_GroupId), + m_Size(e.m_Size), + m_Offset(e.m_Offset), + m_ModifyTime(e.m_ModifyTime), + m_AccessTime(e.m_AccessTime), + m_CreateTime(e.m_CreateTime), + m_TypeFlag(e.m_TypeFlag), + m_LinkName(e.m_LinkName), + m_UserName(e.m_UserName), + m_GroupName(e.m_GroupName), + m_DevMajor(e.m_DevMajor), + m_DevMinor(e.m_DevMinor) +{ +} + +wxTarEntry& wxTarEntry::operator=(const wxTarEntry& e) +{ + if (&e != this) { + m_Name = e.m_Name; + m_Mode = e.m_Mode; + m_IsModeSet = e.m_IsModeSet; + m_UserId = e.m_UserId; + m_GroupId = e.m_GroupId; + m_Size = e.m_Size; + m_Offset = e.m_Offset; + m_ModifyTime = e.m_ModifyTime; + m_AccessTime = e.m_AccessTime; + m_CreateTime = e.m_CreateTime; + m_TypeFlag = e.m_TypeFlag; + m_LinkName = e.m_LinkName; + m_UserName = e.m_UserName; + m_GroupName = e.m_GroupName; + m_DevMajor = e.m_DevMajor; + m_DevMinor = e.m_DevMinor; + } + return *this; +} + +wxString wxTarEntry::GetName(wxPathFormat format /*=wxPATH_NATIVE*/) const +{ + bool isDir = IsDir() && !m_Name.empty(); + + // optimisations for common (and easy) cases + switch (wxFileName::GetFormat(format)) { + case wxPATH_DOS: + { + wxString name(isDir ? m_Name + _T("\\") : m_Name); + for (size_t i = 0; i < name.length(); i++) + if (name[i] == _T('/')) + name[i] = _T('\\'); + return name; + } + + case wxPATH_UNIX: + return isDir ? m_Name + _T("/") : m_Name; + + default: + ; + } + + wxFileName fn; + + if (isDir) + fn.AssignDir(m_Name, wxPATH_UNIX); + else + fn.Assign(m_Name, wxPATH_UNIX); + + return fn.GetFullPath(format); +} + +void wxTarEntry::SetName(const wxString& name, wxPathFormat format) +{ + bool isDir; + m_Name = GetInternalName(name, format, &isDir); + SetIsDir(isDir); +} + +// Static - Internally tars and zips use forward slashes for the path +// separator, absolute paths aren't allowed, and directory names have a +// trailing slash. This function converts a path into this internal format, +// but without a trailing slash for a directory. +// +wxString wxTarEntry::GetInternalName(const wxString& name, + wxPathFormat format /*=wxPATH_NATIVE*/, + bool *pIsDir /*=NULL*/) +{ + wxString internal; + + if (wxFileName::GetFormat(format) != wxPATH_UNIX) + internal = wxFileName(name, format).GetFullPath(wxPATH_UNIX); + else + internal = name; + + bool isDir = !internal.empty() && internal.Last() == '/'; + if (pIsDir) + *pIsDir = isDir; + if (isDir) + internal.erase(internal.length() - 1); + + while (!internal.empty() && *internal.begin() == '/') + internal.erase(0, 1); + while (!internal.empty() && internal.compare(0, 2, _T("./")) == 0) + internal.erase(0, 2); + if (internal == _T(".") || internal == _T("..")) + internal = wxEmptyString; + + return internal; +} + +bool wxTarEntry::IsDir() const +{ + return m_TypeFlag - wxTAR_DIRTYPE == 0; +} + +void wxTarEntry::SetIsDir(bool isDir) +{ + if (isDir) + m_TypeFlag = wxTAR_DIRTYPE; + else if (m_TypeFlag - wxTAR_DIRTYPE == 0) + m_TypeFlag = wxTAR_REGTYPE; +} + +void wxTarEntry::SetIsReadOnly(bool isReadOnly) +{ + if (isReadOnly) + m_Mode &= ~0222; + else + m_Mode |= 0200; +} + +int wxTarEntry::GetMode() const +{ + if (m_IsModeSet || !IsDir()) + return m_Mode; + else + return m_Mode | 0111; + +} + +void wxTarEntry::SetMode(int mode) +{ + m_Mode = mode & 07777; + m_IsModeSet = true; +} + + +///////////////////////////////////////////////////////////////////////////// +// Input stream + +wxDECLARE_SCOPED_PTR(wxTarEntry, wxTarEntryPtr_) +wxDEFINE_SCOPED_PTR (wxTarEntry, wxTarEntryPtr_) + +WX_DECLARE_STRING_HASH_MAP(wxString, wxTarHeaderRecords); + +wxTarInputStream::wxTarInputStream(wxInputStream& stream, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveInputStream(stream, conv) +{ + Init(); +} + +wxTarInputStream::wxTarInputStream(wxInputStream *stream, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveInputStream(stream, conv) +{ + Init(); +} + +void wxTarInputStream::Init() +{ + m_pos = wxInvalidOffset; + m_offset = 0; + m_size = wxInvalidOffset; + m_sumType = SUM_UNKNOWN; + m_tarType = TYPE_USTAR; + m_hdr = new wxTarHeaderBlock; + m_HeaderRecs = NULL; + m_GlobalHeaderRecs = NULL; + m_lasterror = m_parent_i_stream->GetLastError(); +} + +wxTarInputStream::~wxTarInputStream() +{ + delete m_hdr; + delete m_HeaderRecs; + delete m_GlobalHeaderRecs; +} + +wxTarEntry *wxTarInputStream::GetNextEntry() +{ + m_lasterror = ReadHeaders(); + + if (!IsOk()) + return NULL; + + wxTarEntryPtr_ entry(new wxTarEntry); + + entry->SetMode(GetHeaderNumber(TAR_MODE)); + entry->SetUserId(GetHeaderNumber(TAR_UID)); + entry->SetGroupId(GetHeaderNumber(TAR_UID)); + entry->SetSize(GetHeaderNumber(TAR_SIZE)); + + entry->SetOffset(m_offset); + + entry->SetDateTime(GetHeaderDate(_T("mtime"))); + entry->SetAccessTime(GetHeaderDate(_T("atime"))); + entry->SetCreateTime(GetHeaderDate(_T("ctime"))); + + entry->SetTypeFlag(*m_hdr->Get(TAR_TYPEFLAG)); + bool isDir = entry->IsDir(); + + entry->SetLinkName(GetHeaderString(TAR_LINKNAME)); + + if (m_tarType != TYPE_OLDTAR) { + entry->SetUserName(GetHeaderString(TAR_UNAME)); + entry->SetGroupName(GetHeaderString(TAR_GNAME)); + + entry->SetDevMajor(GetHeaderNumber(TAR_DEVMAJOR)); + entry->SetDevMinor(GetHeaderNumber(TAR_DEVMINOR)); + } + + entry->SetName(GetHeaderPath(), wxPATH_UNIX); + if (isDir) + entry->SetIsDir(); + + if (m_HeaderRecs) + m_HeaderRecs->clear(); + + m_size = GetDataSize(*entry); + m_pos = 0; + + return entry.release(); +} + +bool wxTarInputStream::OpenEntry(wxTarEntry& entry) +{ + wxFileOffset offset = entry.GetOffset(); + + if (GetLastError() != wxSTREAM_READ_ERROR + && m_parent_i_stream->IsSeekable() + && m_parent_i_stream->SeekI(offset) == offset) + { + m_offset = offset; + m_size = GetDataSize(entry); + m_pos = 0; + m_lasterror = wxSTREAM_NO_ERROR; + return true; + } else { + m_lasterror = wxSTREAM_READ_ERROR; + return false; + } +} + +bool wxTarInputStream::OpenEntry(wxArchiveEntry& entry) +{ + wxTarEntry *tarEntry = wxStaticCast(&entry, wxTarEntry); + return tarEntry ? OpenEntry(*tarEntry) : false; +} + +bool wxTarInputStream::CloseEntry() +{ + if (m_lasterror == wxSTREAM_READ_ERROR) + return false; + if (!IsOpened()) + return true; + + wxFileOffset size = RoundUpSize(m_size); + wxFileOffset remainder = size - m_pos; + + if (remainder && m_parent_i_stream->IsSeekable()) { + wxLogNull nolog; + if (m_parent_i_stream->SeekI(remainder, wxFromCurrent) + != wxInvalidOffset) + remainder = 0; + } + + if (remainder) { + const int BUFSIZE = 8192; + wxCharBuffer buf(BUFSIZE); + + while (remainder > 0 && m_parent_i_stream->IsOk()) + remainder -= m_parent_i_stream->Read( + buf.data(), wxMin(BUFSIZE, remainder)).LastRead(); + } + + m_pos = wxInvalidOffset; + m_offset += size; + m_lasterror = m_parent_i_stream->GetLastError(); + + return IsOk(); +} + +wxStreamError wxTarInputStream::ReadHeaders() +{ + if (!CloseEntry()) + return wxSTREAM_READ_ERROR; + + bool done = false; + + while (!done) { + m_hdr->Read(*m_parent_i_stream); + if (m_parent_i_stream->Eof()) + wxLogError(_("incomplete header block in tar")); + if (!*m_parent_i_stream) + return wxSTREAM_READ_ERROR; + m_offset += TAR_BLOCKSIZE; + + // an all-zero header marks the end of the tar + if (m_hdr->IsAllZeros()) + return wxSTREAM_EOF; + + // the checksum is supposed to be the unsigned sum of the header bytes, + // but there have been versions of tar that used the signed sum, so + // accept that too, but only if used throughout. + wxUint32 chksum = m_hdr->GetOctal(TAR_CHKSUM); + bool ok = false; + + if (m_sumType != SUM_SIGNED) { + ok = chksum == m_hdr->Sum(); + if (m_sumType == SUM_UNKNOWN) + m_sumType = ok ? SUM_UNSIGNED : SUM_SIGNED; + } + if (m_sumType == SUM_SIGNED) + ok = chksum == m_hdr->Sum(true); + if (!ok) { + wxLogError(_("checksum failure reading tar header block")); + return wxSTREAM_READ_ERROR; + } + + if (strcmp(m_hdr->Get(TAR_MAGIC), USTAR_MAGIC) == 0) + m_tarType = TYPE_USTAR; + else if (strcmp(m_hdr->Get(TAR_MAGIC), GNU_MAGIC) == 0 && + strcmp(m_hdr->Get(TAR_VERSION), GNU_VERION) == 0) + m_tarType = TYPE_GNUTAR; + else + m_tarType = TYPE_OLDTAR; + + if (m_tarType != TYPE_USTAR) + break; + + switch (*m_hdr->Get(TAR_TYPEFLAG)) { + case 'g': ReadExtendedHeader(m_GlobalHeaderRecs); break; + case 'x': ReadExtendedHeader(m_HeaderRecs); break; + default: done = true; + } + } + + return wxSTREAM_NO_ERROR; +} + +wxString wxTarInputStream::GetExtendedHeader(const wxString& key) const +{ + wxTarHeaderRecords::iterator it; + + // look at normal extended header records first + if (m_HeaderRecs) { + it = m_HeaderRecs->find(key); + if (it != m_HeaderRecs->end()) + return wxString(it->second.wc_str(wxConvUTF8), GetConv()); + } + + // if not found, look at the global header records + if (m_GlobalHeaderRecs) { + it = m_GlobalHeaderRecs->find(key); + if (it != m_GlobalHeaderRecs->end()) + return wxString(it->second.wc_str(wxConvUTF8), GetConv()); + } + + return wxEmptyString; +} + +wxString wxTarInputStream::GetHeaderPath() const +{ + wxString path; + + if ((path = GetExtendedHeader(_T("path"))) != wxEmptyString) + return path; + + path = wxString(m_hdr->Get(TAR_NAME), GetConv()); + if (m_tarType != TYPE_USTAR) + return path; + + const char *prefix = m_hdr->Get(TAR_PREFIX); + return *prefix ? wxString(prefix, GetConv()) + _T("/") + path : path; +} + +wxDateTime wxTarInputStream::GetHeaderDate(const wxString& key) const +{ + wxString value; + + // try extended header, stored as decimal seconds since the epoch + if ((value = GetExtendedHeader(key)) != wxEmptyString) { + wxLongLong ll; + ll.Assign(wxAtof(value) * 1000.0); + return ll; + } + + if (key == _T("mtime")) + return wxLongLong(m_hdr->GetOctal(TAR_MTIME)) * 1000L; + + return wxDateTime(); +} + +wxTarNumber wxTarInputStream::GetHeaderNumber(int id) const +{ + wxString value; + + if ((value = GetExtendedHeader(m_hdr->Name(id))) != wxEmptyString) { + wxTarNumber n = 0; + const wxChar *p = value; + while (*p == ' ') + p++; + while (isdigit(*p)) + n = n * 10 + (*p++ - '0'); + return n; + } else { + return m_hdr->GetOctal(id); + } +} + +wxString wxTarInputStream::GetHeaderString(int id) const +{ + wxString value; + + if ((value = GetExtendedHeader(m_hdr->Name(id))) != wxEmptyString) + return value; + + return wxString(m_hdr->Get(id), GetConv()); +} + +// An extended header consists of one or more records, each constructed: +// "%d %s=%s\n", , , +// is the byte length, and are UTF-8 + +bool wxTarInputStream::ReadExtendedHeader(wxTarHeaderRecords*& recs) +{ + if (!recs) + recs = new wxTarHeaderRecords; + + // round length up to a whole number of blocks + size_t len = m_hdr->GetOctal(TAR_SIZE); + size_t size = RoundUpSize(len); + + // read in the whole header since it should be small + wxCharBuffer buf(size); + size_t lastread = m_parent_i_stream->Read(buf.data(), size).LastRead(); + if (lastread < len) + len = lastread; + buf.data()[len] = 0; + m_offset += lastread; + + size_t recPos, recSize; + bool ok = true; + + for (recPos = 0; recPos < len; recPos += recSize) { + char *pRec = buf.data() + recPos; + char *p = pRec; + + // read the record size (byte count in ascii decimal) + recSize = 0; + while (isdigit(*p)) + recSize = recSize * 10 + *p++ - '0'; + + // validity checks + if (recPos + recSize > len) + break; + if (recSize < p - pRec + (size_t)3 || *p != ' ' + || pRec[recSize - 1] != '\012') { + ok = false; + continue; + } + + // replace the final '\n' with a nul, to terminate value + pRec[recSize - 1] = 0; + // the key is here, following the space + char *pKey = ++p; + + // look forward for the '=', the value follows + while (*p && *p != '=') + p++; + if (!*p) { + ok = false; + continue; + } + // replace the '=' with a nul, to terminate the key + *p++ = 0; + + wxString key(wxConvUTF8.cMB2WC(pKey), GetConv()); + wxString value(wxConvUTF8.cMB2WC(p), GetConv()); + + // an empty value unsets a previously given value + if (value.empty()) + recs->erase(key); + else + (*recs)[key] = value; + } + + if (!ok || recPos < len || size != lastread) { + wxLogWarning(_("invalid data in extended tar header")); + return false; + } + + return true; +} + +wxFileOffset wxTarInputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode) +{ + if (!IsOpened()) { + wxLogError(_("tar entry not open")); + m_lasterror = wxSTREAM_READ_ERROR; + } + if (!IsOk()) + return wxInvalidOffset; + + switch (mode) { + case wxFromStart: break; + case wxFromCurrent: pos += m_pos; break; + case wxFromEnd: pos += m_size; break; + } + + if (pos < 0 || m_parent_i_stream->SeekI(m_offset + pos) == wxInvalidOffset) + return wxInvalidOffset; + + m_pos = pos; + return m_pos; +} + +size_t wxTarInputStream::OnSysRead(void *buffer, size_t size) +{ + if (!IsOpened()) { + wxLogError(_("tar entry not open")); + m_lasterror = wxSTREAM_READ_ERROR; + } + if (!IsOk() || !size) + return 0; + + if (m_pos >= m_size) + size = 0; + else if (m_pos + size > m_size + (size_t)0) + size = m_size - m_pos; + + size_t lastread = m_parent_i_stream->Read(buffer, size).LastRead(); + m_pos += lastread; + + if (m_pos >= m_size) { + m_lasterror = wxSTREAM_EOF; + } else if (!m_parent_i_stream->IsOk()) { + // any other error will have been reported by the underlying stream + if (m_parent_i_stream->Eof()) + wxLogError(_("unexpected end of file")); + m_lasterror = wxSTREAM_READ_ERROR; + } + + return lastread; +} + + +///////////////////////////////////////////////////////////////////////////// +// Output stream + +wxTarOutputStream::wxTarOutputStream(wxOutputStream& stream, + wxTarFormat format /*=wxTAR_PAX*/, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveOutputStream(stream, conv) +{ + Init(format); +} + +wxTarOutputStream::wxTarOutputStream(wxOutputStream *stream, + wxTarFormat format /*=wxTAR_PAX*/, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveOutputStream(stream, conv) +{ + Init(format); +} + +void wxTarOutputStream::Init(wxTarFormat format) +{ + m_pos = wxInvalidOffset; + m_maxpos = wxInvalidOffset; + m_size = wxInvalidOffset; + m_headpos = wxInvalidOffset; + m_datapos = wxInvalidOffset; + m_tarstart = wxInvalidOffset; + m_tarsize = 0; + m_pax = format == wxTAR_PAX; + m_BlockingFactor = m_pax ? 10 : 20; + m_chksum = 0; + m_large = false; + m_hdr = new wxTarHeaderBlock; + m_hdr2 = NULL; + m_extendedHdr = NULL; + m_extendedSize = 0; + m_lasterror = m_parent_o_stream->GetLastError(); +} + +wxTarOutputStream::~wxTarOutputStream() +{ + if (m_tarsize) + Close(); + delete m_hdr; + delete m_hdr2; + delete [] m_extendedHdr; +} + +bool wxTarOutputStream::PutNextEntry(wxTarEntry *entry) +{ + wxTarEntryPtr_ e(entry); + + if (!CloseEntry()) + return false; + + if (!m_tarsize) { + wxLogNull nolog; + m_tarstart = m_parent_o_stream->TellO(); + } + + if (m_tarstart != wxInvalidOffset) + m_headpos = m_tarstart + m_tarsize; + + if (WriteHeaders(*e)) { + m_pos = 0; + m_maxpos = 0; + m_size = GetDataSize(*e); + if (m_tarstart != wxInvalidOffset) + m_datapos = m_tarstart + m_tarsize; + + // types that are not allowd any data + const char nodata[] = { + wxTAR_LNKTYPE, wxTAR_SYMTYPE, wxTAR_CHRTYPE, wxTAR_BLKTYPE, + wxTAR_DIRTYPE, wxTAR_FIFOTYPE, 0 + }; + char typeflag = e->GetTypeFlag(); + + // pax does now allow data for wxTAR_LNKTYPE + if (!m_pax || typeflag - wxTAR_LNKTYPE != 0) + if (strchr(nodata, typeflag) != NULL) + CloseEntry(); + } + + return IsOk(); +} + +bool wxTarOutputStream::PutNextEntry(const wxString& name, + const wxDateTime& dt, + wxFileOffset size) +{ + return PutNextEntry(new wxTarEntry(name, dt, size)); +} + +bool wxTarOutputStream::PutNextDirEntry(const wxString& name, + const wxDateTime& dt) +{ + wxTarEntry *entry = new wxTarEntry(name, dt); + entry->SetIsDir(); + return PutNextEntry(entry); +} + +bool wxTarOutputStream::PutNextEntry(wxArchiveEntry *entry) +{ + wxTarEntry *tarEntry = wxStaticCast(entry, wxTarEntry); + if (!tarEntry) + delete entry; + return PutNextEntry(tarEntry); +} + +bool wxTarOutputStream::CopyEntry(wxTarEntry *entry, + wxTarInputStream& inputStream) +{ + if (PutNextEntry(entry)) + Write(inputStream); + return IsOk() && inputStream.Eof(); +} + +bool wxTarOutputStream::CopyEntry(wxArchiveEntry *entry, + wxArchiveInputStream& inputStream) +{ + if (PutNextEntry(entry)) + Write(inputStream); + return IsOk() && inputStream.Eof(); +} + +bool wxTarOutputStream::CloseEntry() +{ + if (!IsOpened()) + return true; + + if (m_pos < m_maxpos) { + wxASSERT(m_parent_o_stream->IsSeekable()); + m_parent_o_stream->SeekO(m_datapos + m_maxpos); + m_lasterror = m_parent_o_stream->GetLastError(); + m_pos = m_maxpos; + } + + if (IsOk()) { + wxFileOffset size = RoundUpSize(m_pos); + if (size > m_pos) { + memset(m_hdr, 0, size - m_pos); + m_parent_o_stream->Write(m_hdr, size - m_pos); + m_lasterror = m_parent_o_stream->GetLastError(); + } + m_tarsize += size; + } + + if (IsOk() && m_pos != m_size) + ModifyHeader(); + + m_pos = wxInvalidOffset; + m_maxpos = wxInvalidOffset; + m_size = wxInvalidOffset; + m_headpos = wxInvalidOffset; + m_datapos = wxInvalidOffset; + + return IsOk(); +} + +bool wxTarOutputStream::Close() +{ + if (!CloseEntry()) + return false; + + memset(m_hdr, 0, sizeof(*m_hdr)); + int count = (RoundUpSize(m_tarsize + 2 * TAR_BLOCKSIZE, m_BlockingFactor) + - m_tarsize) / TAR_BLOCKSIZE; + while (count--) + m_parent_o_stream->Write(m_hdr, TAR_BLOCKSIZE); + + m_tarsize = 0; + m_tarstart = wxInvalidOffset; + m_lasterror = m_parent_o_stream->GetLastError(); + return IsOk(); +} + +bool wxTarOutputStream::WriteHeaders(wxTarEntry& entry) +{ + memset(m_hdr, 0, sizeof(*m_hdr)); + + SetHeaderPath(entry.GetName(wxPATH_UNIX)); + + SetHeaderNumber(TAR_MODE, entry.GetMode()); + SetHeaderNumber(TAR_UID, entry.GetUserId()); + SetHeaderNumber(TAR_GID, entry.GetGroupId()); + + if (entry.GetSize() == wxInvalidOffset) + entry.SetSize(0); + m_large = SetHeaderNumber(TAR_SIZE, entry.GetSize()); + + SetHeaderDate(_T("mtime"), entry.GetDateTime()); + if (entry.GetAccessTime().IsValid()) + SetHeaderDate(_T("atime"), entry.GetAccessTime()); + if (entry.GetCreateTime().IsValid()) + SetHeaderDate(_T("ctime"), entry.GetCreateTime()); + + *m_hdr->Get(TAR_TYPEFLAG) = entry.GetTypeFlag(); + + strcpy(m_hdr->Get(TAR_MAGIC), USTAR_MAGIC); + strcpy(m_hdr->Get(TAR_VERSION), USTAR_VERSION); + + SetHeaderString(TAR_LINKNAME, entry.GetLinkName()); + SetHeaderString(TAR_UNAME, entry.GetUserName()); + SetHeaderString(TAR_GNAME, entry.GetGroupName()); + + if (~entry.GetDevMajor()) + SetHeaderNumber(TAR_DEVMAJOR, entry.GetDevMajor()); + if (~entry.GetDevMinor()) + SetHeaderNumber(TAR_DEVMINOR, entry.GetDevMinor()); + + m_chksum = m_hdr->Sum(); + m_hdr->SetOctal(TAR_CHKSUM, m_chksum); + if (!m_large) + m_chksum -= m_hdr->SumField(TAR_SIZE); + + // The main header is now fully prepared so we know what extended headers + // (if any) will be needed. Output any extended headers before writing + // the main header. + if (m_extendedHdr && *m_extendedHdr) { + wxASSERT(m_pax); + // the extended headers are written to the tar as a file entry, + // so prepare a regular header block for the pseudo-file. + if (!m_hdr2) + m_hdr2 = new wxTarHeaderBlock; + memset(m_hdr2, 0, sizeof(*m_hdr2)); + + // an old tar that doesn't understand extended headers will + // extract it as a file, so give these fields reasonable values + // so that the user will have access to read and remove it. + m_hdr2->SetPath(PaxHeaderPath(_T("%d/PaxHeaders.%p/%f"), + entry.GetName(wxPATH_UNIX)), GetConv()); + m_hdr2->SetOctal(TAR_MODE, 0600); + strcpy(m_hdr2->Get(TAR_UID), m_hdr->Get(TAR_UID)); + strcpy(m_hdr2->Get(TAR_GID), m_hdr->Get(TAR_GID)); + size_t length = strlen(m_extendedHdr); + m_hdr2->SetOctal(TAR_SIZE, length); + strcpy(m_hdr2->Get(TAR_MTIME), m_hdr->Get(TAR_MTIME)); + *m_hdr2->Get(TAR_TYPEFLAG) = 'x'; + strcpy(m_hdr2->Get(TAR_MAGIC), USTAR_MAGIC); + strcpy(m_hdr2->Get(TAR_VERSION), USTAR_VERSION); + strcpy(m_hdr2->Get(TAR_UNAME), m_hdr->Get(TAR_UNAME)); + strcpy(m_hdr2->Get(TAR_GNAME), m_hdr->Get(TAR_GNAME)); + + m_hdr2->SetOctal(TAR_CHKSUM, m_hdr2->Sum()); + + m_hdr2->Write(*m_parent_o_stream); + m_tarsize += TAR_BLOCKSIZE; + + size_t rounded = RoundUpSize(length); + memset(m_extendedHdr + length, 0, rounded - length); + m_parent_o_stream->Write(m_extendedHdr, rounded); + m_tarsize += rounded; + + *m_extendedHdr = 0; + } + + // if don't have extended headers just report error + if (!m_badfit.empty()) { + wxASSERT(!m_pax); + wxLogWarning(_("%s did not fit the tar header for entry '%s'"), + m_badfit.c_str(), entry.GetName().c_str()); + m_badfit.clear(); + } + + m_hdr->Write(*m_parent_o_stream); + m_tarsize += TAR_BLOCKSIZE; + m_lasterror = m_parent_o_stream->GetLastError(); + + return IsOk(); +} + +wxString wxTarOutputStream::PaxHeaderPath(const wxString& format, + const wxString& path) +{ + wxString d = path.BeforeLast(_T('/')); + wxString f = path.AfterLast(_T('/')); + wxString ret; + + if (d.empty()) + d = _T("."); + + ret.reserve(format.length() + path.length() + 16); + + size_t begin = 0; + size_t end; + + for (;;) { + end = format.find('%', begin); + if (end == wxString::npos || end + 1 >= format.length()) + break; + ret << format.substr(begin, end - begin); + switch (format[end + 1]) { + case 'd': ret << d; break; + case 'f': ret << f; break; + case 'p': ret << wxGetProcessId(); break; + case '%': ret << _T("%"); break; + } + begin = end + 2; + } + + ret << format.substr(begin); + + return ret; +} + +bool wxTarOutputStream::ModifyHeader() +{ + wxFileOffset originalPos = wxInvalidOffset; + wxFileOffset sizePos = wxInvalidOffset; + + if (!m_large && m_headpos != wxInvalidOffset + && m_parent_o_stream->IsSeekable()) + { + wxLogNull nolog; + originalPos = m_parent_o_stream->TellO(); + if (originalPos != wxInvalidOffset) + sizePos = + m_parent_o_stream->SeekO(m_headpos + m_hdr->Offset(TAR_SIZE)); + } + + if (sizePos == wxInvalidOffset || !m_hdr->SetOctal(TAR_SIZE, m_pos)) { + wxLogError(_("incorrect size given for tar entry")); + m_lasterror = wxSTREAM_WRITE_ERROR; + return false; + } + + m_chksum += m_hdr->SumField(TAR_SIZE); + m_hdr->SetOctal(TAR_CHKSUM, m_chksum); + wxFileOffset sumPos = m_headpos + m_hdr->Offset(TAR_CHKSUM); + + return + m_hdr->WriteField(*m_parent_o_stream, TAR_SIZE) && + m_parent_o_stream->SeekO(sumPos) == sumPos && + m_hdr->WriteField(*m_parent_o_stream, TAR_CHKSUM) && + m_parent_o_stream->SeekO(originalPos) == originalPos; +} + +void wxTarOutputStream::SetHeaderPath(const wxString& name) +{ + if (!m_hdr->SetPath(name, GetConv()) || (m_pax && !name.IsAscii())) + SetExtendedHeader(_T("path"), name); +} + +bool wxTarOutputStream::SetHeaderNumber(int id, wxTarNumber n) +{ + if (m_hdr->SetOctal(id, n)) { + return true; + } else { + SetExtendedHeader(m_hdr->Name(id), wxLongLong(n).ToString()); + return false; + } +} + +void wxTarOutputStream::SetHeaderString(int id, const wxString& str) +{ + strncpy(m_hdr->Get(id), str.mb_str(GetConv()), m_hdr->Len(id)); + if (str.length() > m_hdr->Len(id)) + SetExtendedHeader(m_hdr->Name(id), str); +} + +void wxTarOutputStream::SetHeaderDate(const wxString& key, + const wxDateTime& datetime) +{ + wxLongLong ll = datetime.IsValid() ? datetime.GetValue() : wxLongLong(0); + wxLongLong secs = ll / 1000L; + + if (key != _T("mtime") || !m_hdr->SetOctal(TAR_MTIME, secs.GetValue()) + || secs <= 0 || secs >= 0x7fffffff) + { + wxString str; + if (ll >= LONG_MIN && ll <= LONG_MAX) { + str.Printf(_T("%g"), ll.ToLong() / 1000.0); + } else { + str = ll.ToString(); + str.insert(str.end() - 3, '.'); + } + SetExtendedHeader(key, str); + } +} + +void wxTarOutputStream::SetExtendedHeader(const wxString& key, + const wxString& value) +{ + if (m_pax) { + const wxWX2WCbuf wide_key = key.wc_str(GetConv()); + const wxCharBuffer utf_key = wxConvUTF8.cWC2MB(wide_key); + + const wxWX2WCbuf wide_value = value.wc_str(GetConv()); + const wxCharBuffer utf_value = wxConvUTF8.cWC2MB(wide_value); + + // a small buffer to format the length field in + char buf[32]; + // length of "99=\n" + unsigned long length = strlen(utf_value) + strlen(utf_key) + 5; + sprintf(buf, "%lu", length); + // the length includes itself + size_t lenlen = strlen(buf); + if (lenlen != 2) { + length += lenlen - 2; + sprintf(buf, "%lu", length); + if (strlen(buf) > lenlen) + sprintf(buf, "%lu", ++length); + } + + // reallocate m_extendedHdr if it's not big enough + if (m_extendedSize < length) { + size_t rounded = RoundUpSize(length); + m_extendedSize <<= 1; + if (rounded > m_extendedSize) + m_extendedSize = rounded; + char *oldHdr = m_extendedHdr; + m_extendedHdr = new char[m_extendedSize]; + if (oldHdr) { + strcpy(m_extendedHdr, oldHdr); + delete oldHdr; + } else { + *m_extendedHdr = 0; + } + } + + // append the new record + char *append = strchr(m_extendedHdr, 0); + sprintf(append, "%s %s=%s\012", buf, + (const char*)utf_key, (const char*)utf_value); + } + else { + // if not pax then make a list of fields to report as errors + if (!m_badfit.empty()) + m_badfit += _T(", "); + m_badfit += key; + } +} + +void wxTarOutputStream::Sync() +{ + m_parent_o_stream->Sync(); +} + +wxFileOffset wxTarOutputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode) +{ + if (!IsOpened()) { + wxLogError(_("tar entry not open")); + m_lasterror = wxSTREAM_WRITE_ERROR; + } + if (!IsOk() || m_datapos == wxInvalidOffset) + return wxInvalidOffset; + + switch (mode) { + case wxFromStart: break; + case wxFromCurrent: pos += m_pos; break; + case wxFromEnd: pos += m_maxpos; break; + } + + if (pos < 0 || m_parent_o_stream->SeekO(m_datapos + pos) == wxInvalidOffset) + return wxInvalidOffset; + + m_pos = pos; + return m_pos; +} + +size_t wxTarOutputStream::OnSysWrite(const void *buffer, size_t size) +{ + if (!IsOpened()) { + wxLogError(_("tar entry not open")); + m_lasterror = wxSTREAM_WRITE_ERROR; + } + if (!IsOk() || !size) + return 0; + + size_t lastwrite = m_parent_o_stream->Write(buffer, size).LastWrite(); + m_pos += lastwrite; + if (m_pos > m_maxpos) + m_maxpos = m_pos; + + if (lastwrite != size) + m_lasterror = wxSTREAM_WRITE_ERROR; + + return lastwrite; +} + +#endif // wxUSE_TARSTREAM