From 3157976a3a2442a781a2ac41b21f3ad3634a9640 Mon Sep 17 00:00:00 2001 From: Michael McMaster Date: Fri, 27 May 2011 21:34:21 +1000 Subject: [PATCH] Added file modification time support. --- Decompressor.cc | 5 ++++ FileReader.cc | 21 +++++++++++++--- FileWriter.cc | 39 +++++++++++++++++++++++++---- NEWS | 5 ++-- README | 4 +-- debian/changelog | 3 ++- debian/control | 5 ++-- debian/rules | 18 +++++++++++--- gzip.cc | 15 ++++++++++-- zip.cc | 64 +++++++++++++++++++++++++++++++++++++++++------- zip.hh | 2 ++ zipper.cc | 5 ++-- zipper.hh | 36 ++++++++++++++++++++++++--- 13 files changed, 186 insertions(+), 36 deletions(-) diff --git a/Decompressor.cc b/Decompressor.cc index 79dbe07..94c4942 100644 --- a/Decompressor.cc +++ b/Decompressor.cc @@ -65,6 +65,11 @@ namespace } } + virtual const timeval& getModificationTime() const + { + return m_reader->getModTime(); + } + private: ReaderPtr m_reader; }; diff --git a/FileReader.cc b/FileReader.cc index 1517fdd..b43c393 100644 --- a/FileReader.cc +++ b/FileReader.cc @@ -28,6 +28,9 @@ using namespace zipper; +const timeval zipper::s_now = {0,0}; + + class FileReader::FileReaderImpl { public: @@ -47,7 +50,7 @@ public: errMsg; throw IOException(message.str()); } - initSize(); + initStats(); } FileReaderImpl(const std::string& filename, int fd, bool closeFd) : @@ -55,7 +58,7 @@ public: m_fd(fd), m_closeOnExit(closeFd) { - initSize(); + initStats(); } ~FileReaderImpl() @@ -64,6 +67,7 @@ public: } const std::string& getSourceName() const { return m_filename; } + const timeval& getModTime() const { return m_modTime; } zsize_t getSize() const { return m_size; } @@ -101,10 +105,10 @@ public: } private: - void initSize() + void initStats() { // If we fail here, we need to essentially run the dtor manually. - // initSize is called from the constructors, and so the dtor will + // initStats is called from the constructors, and so the dtor will // NOT run if an exception is thrown. struct stat buf; @@ -124,6 +128,8 @@ private: else { m_size = buf.st_size; + m_modTime.tv_sec = buf.st_mtime; + m_modTime.tv_usec = 0; } } @@ -139,6 +145,7 @@ private: int m_fd; bool m_closeOnExit; zsize_t m_size; + timeval m_modTime; }; FileReader::FileReader(const std::string& filename) : @@ -162,6 +169,12 @@ FileReader::getSourceName() const return m_impl->getSourceName(); } +const timeval& +FileReader::getModTime() const +{ + return m_impl->getModTime(); +} + zsize_t FileReader::getSize() const { diff --git a/FileWriter.cc b/FileWriter.cc index d4ccf7f..94cb48b 100644 --- a/FileWriter.cc +++ b/FileWriter.cc @@ -26,16 +26,23 @@ #include #include #include +#include using namespace zipper; class FileWriter::FileWriterImpl { public: - FileWriterImpl(const std::string& filename, mode_t createPermissions) : + FileWriterImpl( + const std::string& filename, + mode_t createPermissions, + const timeval& modTime + ) : m_filename(filename), + m_modTime(modTime), m_fd(-1), - m_closeOnExit(true) + m_closeOnExit(true), + m_setModTimeOnExit(true) { m_fd = ::open( @@ -57,13 +64,30 @@ public: FileWriterImpl(const std::string& filename, int fd, bool closeFd) : m_filename(filename), m_fd(fd), - m_closeOnExit(closeFd) + m_closeOnExit(closeFd), + m_setModTimeOnExit(false) { } ~FileWriterImpl() { close(); + + if (m_setModTimeOnExit) + { + struct timeval times[2]; + if (s_now.tv_sec == m_modTime.tv_sec) + { + gettimeofday(×[0], NULL); + times[1] = times[0]; + } + else + { + times[0] = m_modTime; + times[1] = m_modTime; + } + utimes(m_filename.c_str(), times); + } } virtual void writeData( @@ -113,12 +137,17 @@ private: } std::string m_filename; + timeval m_modTime; int m_fd; bool m_closeOnExit; + bool m_setModTimeOnExit; }; -FileWriter::FileWriter(const std::string& filename, mode_t createPermissions) : - m_impl(new FileWriterImpl(filename, createPermissions)) +FileWriter::FileWriter( + const std::string& filename, + mode_t createPermissions, + const timeval& modTime) : + m_impl(new FileWriterImpl(filename, createPermissions, modTime)) { } diff --git a/NEWS b/NEWS index a236cc5..967e5e1 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ -2011-05-22 Version 1.0.1 +2011-05-27 Version 1.0.1 - Removed private classes from doxygen output - - Removed private symbols from shared library + - Added the zipper utility executable + - Added timestamp support 2011-05-21 Version 1.0.0 - Initial release diff --git a/README b/README index 5c22b5d..ea954f4 100644 --- a/README +++ b/README @@ -9,8 +9,8 @@ libzipper currently supports plain, zip, and gzip formats. libzipper is not a general-purpose archive management library, as it does not provide access to the filesystem attributes of each file. -(ie. libzipper does not support the concepts of file owner, group, -permissions, or timestamps. +(ie. libzipper does not support the concepts of file owner, group or +permissions. Missing Features - zip64 support (for >4Gb zip files) diff --git a/debian/changelog b/debian/changelog index 57966f9..144992c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,8 +2,9 @@ libzipper1 (1.0.1-1) unstable; urgency=low * Fixed packaging to meet debian policy (renamed package, fixed shlibs file and symlinks + * Created the libzipper-tools and libzipper-docs binary packages. - -- Michael McMaster Sat, 22 May 2011 21:48:11 +1000 + -- Michael McMaster Fri, 27 May 2011 19:41:00 +1000 libzipper1 (1.0.0-1) unstable; urgency=low diff --git a/debian/control b/debian/control index 863f2f8..458a2c1 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: libzipper1 Priority: optional Maintainer: Michael McMaster -Build-Depends: debhelper (>= 7.0.50~), autotools-dev, cdbs, zlib1g-dev, doxygen, graphviz +Build-Depends: debhelper (>= 7.0.50~), autotools-dev, cdbs, pkg-config, zlib1g-dev, doxygen, texlive-font-utils, graphviz Standards-Version: 3.9.2 Section: libs Homepage: http://www.codesrc.com/src/libzipper @@ -12,6 +12,7 @@ Package: libzipper-dev Section: libdevel Architecture: any Depends: libzipper1 (= ${binary:Version}), ${misc:Depends} +Recommends: pkg-config Suggests: libzipper-doc Homepage: http://www.codesrc.com/src/libzipper Description: simple interface for reading and writing compressed files @@ -55,7 +56,7 @@ Description: utilities for reading and writing compressed files Package: libzipper-doc Section: doc Architecture: any -Depends: libjs-jquery, ${misc:Depends} +Depends: ${misc:Depends} Homepage: http://www.codesrc.com/src/libzipper Description: simple interface for reading and writing compressed files libzipper offers a flexible C++ interface for reading and writing compressed diff --git a/debian/rules b/debian/rules index cb7dd9c..844da17 100755 --- a/debian/rules +++ b/debian/rules @@ -5,9 +5,19 @@ DEB_DH_MAKESHLIBS_ARGS_ALL=--version-info "libzipper1 (>= 1.0.1)" include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/autotools.mk -# Make use of the libjs-jquery package -# Required by lintian rule: embedded-javascript-library +# Doxygen may copy an old version of jquery.js into the HTML documentation +# tree, even though it is not used. This causes the +# "embedded-javascript-library" lintian error. +# Delete the file to avoid creating a dependency on the libjs-jquery package in +# order to resolve the lintian error. +# This is fixed in doxygen 1.7.4-2 install/libzipper-doc:: - rm $(DEB_DESTDIR)/usr/share/doc/libzipper/html/jquery.js - ln -s ../../../javascript/jquery/jquery.js $(DEB_DESTDIR)/usr/share/doc/libzipper/html/jquery.js + if [ -f $(DEB_DESTDIR)/usr/share/doc/libzipper/html/jquery.js ]; then \ + if grep jquery.js $(DEB_DESTDIR)/usr/share/doc/libzipper/html/*.html; then \ + echo "ERROR: doxygen is making use of jquery.js in the HTML "; \ + echo "documentation. Please add a dependency on libjs-jquery."; \ + else \ + rm $(DEB_DESTDIR)/usr/share/doc/libzipper/html/jquery.js; \ + fi \ + fi diff --git a/gzip.cc b/gzip.cc index c1b9dd4..7ff1642 100644 --- a/gzip.cc +++ b/gzip.cc @@ -56,12 +56,15 @@ namespace FileEntry( const ReaderPtr& reader, zsize_t dataOffset, - const std::string& filename + const std::string& filename, + time_t modTime ) : m_reader(reader), m_dataOffset(dataOffset), m_fileName(filename) { + m_modTime.tv_sec = modTime; + m_modTime.tv_usec = 0; } virtual bool isDecompressSupported() const @@ -76,6 +79,7 @@ namespace virtual zsize_t getCompressedSize() const { return -1; } virtual zsize_t getUncompressedSize() const { return -1; } + virtual const timeval& getModificationTime() const { return m_modTime; } virtual void decompress(Writer& writer) { @@ -104,6 +108,7 @@ namespace ReaderPtr m_reader; zsize_t m_dataOffset; std::string m_fileName; + timeval m_modTime; }; } @@ -134,6 +139,8 @@ zipper::ungzip(const ReaderPtr& reader) bool fcomment = (header[3] & 0x10) != 0; bool fhcrc = (header[3] & 2) != 0; + time_t modTime = read32_le(&header[4]); + size_t offset(10); if (fextra) @@ -176,7 +183,8 @@ zipper::ungzip(const ReaderPtr& reader) std::vector result; result.push_back( - CompressedFilePtr(new FileEntry(reader, offset, embeddedName))); + CompressedFilePtr( + new FileEntry(reader, offset, embeddedName, modTime))); return result; } @@ -229,6 +237,9 @@ zipper::gzip( { uint8_t buffer[ChunkSize]; memcpy(buffer, Header, sizeof(Header)); + + write32_le(reader.getModTime().tv_sec, &buffer[4]); // modtime + zsize_t pos(sizeof(Header)); zsize_t filenameSize(filename.size()); diff --git a/zip.cc b/zip.cc index 4171517..caca8ef 100644 --- a/zip.cc +++ b/zip.cc @@ -25,12 +25,42 @@ #include #include +#include #include using namespace zipper; namespace { + time_t convertDosDateTime(uint16_t date, uint16_t time) + { + struct tm parts; + memset(&parts, 0, sizeof(parts)); + parts.tm_sec = time & 0x1F; + parts.tm_min = (time & 0x7E0) >> 5; + parts.tm_hour = (time >> 11); + parts.tm_mday = date & 0x1F; + parts.tm_mon = ((date & 0x1E0) >> 5) - 1; + parts.tm_year = (date >> 9) + 80; + return mktime(&parts); + } + + void convertDosDateTime(time_t in, uint16_t& date, uint16_t& time) + { + struct tm buf; + struct tm* parts(localtime_r(&in, &buf)); + + time = + parts->tm_sec + + (parts->tm_min << 5) + + (parts->tm_hour << 11); + + date = + parts->tm_mday + + ((parts->tm_mon + 1) << 5) + + ((parts->tm_year - 80) << 9); + } + class FileEntry : public CompressedFile { public: @@ -42,6 +72,7 @@ namespace uint32_t crc, zsize_t compressedSize, zsize_t uncompressedSize, + time_t modTime, zsize_t localHeaderOffset, std::string fileName ) : @@ -55,6 +86,8 @@ namespace m_localHeaderOffset(localHeaderOffset), m_fileName(fileName) { + m_modTime.tv_sec = modTime; + m_modTime.tv_usec = 0; } virtual bool isDecompressSupported() const @@ -75,6 +108,8 @@ namespace return m_uncompressedSize; } + virtual const timeval& getModificationTime() const { return m_modTime; } + virtual void decompress(Writer& writer) { enum @@ -175,6 +210,7 @@ namespace uint32_t m_crc; zsize_t m_compressedSize; zsize_t m_uncompressedSize; + timeval m_modTime; zsize_t m_localHeaderOffset; std::string m_fileName; }; @@ -281,6 +317,8 @@ namespace uint16_t versionNeeded(read16_le(buffer, pos + 6)); uint16_t gpFlag(read16_le(buffer, pos + 8)); uint16_t compressionMethod(read16_le(buffer, pos + 10)); + uint16_t modTime(read16_le(buffer, pos + 12)); + uint16_t modDate(read16_le(buffer, pos + 14)); uint32_t crc(read32_le(buffer, pos + 16)); uint32_t compressedSize(read32_le(buffer, pos + 20)); uint32_t uncompressedSize(read32_le(buffer, pos + 24)); @@ -311,6 +349,7 @@ namespace crc, compressedSize, uncompressedSize, + convertDosDateTime(modDate, modTime), localHeaderOffset, fileName ) @@ -336,7 +375,7 @@ zipper::zip( { ChunkSize = 64*1024, WindowBits = 15, - CRC32Pos = 14 + TimePos = 10 }; static uint8_t Header[] = @@ -388,12 +427,16 @@ zipper::zip( outRecord.crc32); // Go back and complete the header. - uint8_t trailer[12]; - write32_le(outRecord.crc32, &trailer[0]); - write32_le(outRecord.compressedSize, &trailer[4]); - write32_le(outRecord.uncompressedSize, &trailer[8]); + convertDosDateTime( + reader.getModTime().tv_sec, outRecord.dosDate, outRecord.dosTime); + uint8_t trailer[16]; + write16_le(outRecord.dosTime, &trailer[0]); + write16_le(outRecord.dosDate, &trailer[2]); + write32_le(outRecord.crc32, &trailer[4]); + write32_le(outRecord.compressedSize, &trailer[8]); + write32_le(outRecord.uncompressedSize, &trailer[12]); writer->writeData( - outRecord.localHeaderOffset + CRC32Pos, sizeof(trailer), &trailer[0]); + outRecord.localHeaderOffset + TimePos, sizeof(trailer), &trailer[0]); } void @@ -412,9 +455,7 @@ zipper::zipFinalise( 20, 0x00, // Version (2.0) 20, 0x00, // Version Needed to extract (2.0) 0,0, // gp flag. - 8,0, // deflate method - 0,0, // file time - 0,0 // file date + 8,0 // deflate method }; zsize_t outPos(writer->getSize()); @@ -426,6 +467,11 @@ zipper::zipFinalise( memcpy(buffer, FileHeader, sizeof(FileHeader)); zsize_t pos(sizeof(FileHeader)); + write16_le(records[i].dosTime, &buffer[pos]); + pos += 2; + write16_le(records[i].dosDate, &buffer[pos]); + pos += 2; + write32_le(records[i].crc32, &buffer[pos]); pos += 4; diff --git a/zip.hh b/zip.hh index c6df4b0..94bb7d4 100644 --- a/zip.hh +++ b/zip.hh @@ -28,6 +28,8 @@ namespace zipper uint32_t crc32; zsize_t compressedSize; zsize_t uncompressedSize; + uint16_t dosDate; + uint16_t dosTime; std::string filename; }; diff --git a/zipper.cc b/zipper.cc index 9f57ff2..64f1014 100644 --- a/zipper.cc +++ b/zipper.cc @@ -39,7 +39,7 @@ static void usage() "under certain conditions.\n\n" << "Usage: \n" << - argv0 << " {zip|gzip} archive [file...]\n" << + argv0 << " {zip|gzip} archive file [files...]\n" << argv0 << " {unzip|gunzip} archive" << std::endl; } @@ -148,7 +148,8 @@ command_extract(const std::deque& options) builtPath << '/'; } - FileWriter writer(entries[f]->getPath(), 0660); + FileWriter writer( + entries[f]->getPath(), 0660, entries[f]->getModificationTime()); entries[f]->decompress(writer); } } diff --git a/zipper.hh b/zipper.hh index 35fc230..50e6ec9 100644 --- a/zipper.hh +++ b/zipper.hh @@ -26,6 +26,7 @@ #include #include // For mode_t +#include // For timeval /** \mainpage libzipper C++ (de)compression library @@ -42,8 +43,8 @@ are compressed to save space. libzipper is not a general-purpose archive management library, as it does not provide access to the filesystem attributes of each file. -(ie. libzipper does not support the concepts of file owner, group, -permissions, or timestamps. +(ie. libzipper does not support the concepts of file owner, group or +permissions. \section formats Supported Formats
    @@ -111,6 +112,11 @@ public: return Name; } + virtual const timeval& getModTime() const + { + return zipper::s_now; + } + virtual zsize_t getSize() const { return m_data.size(); } virtual void readData(zsize_t offset, zsize_t bytes, uint8_t* dest) const @@ -216,6 +222,10 @@ namespace zipper uint32_t capabilities; }; + /// \brief When passed as a method parameter, it requests that the + /// current time be used instead. + extern const timeval s_now; + /// \brief Returns the capability details of the given format. const Container& getContainer(ContainerFormat format); @@ -285,6 +295,11 @@ namespace zipper /// the input filename. virtual const std::string& getSourceName() const = 0; + /// Return the last-modified timestamp of the data. + /// If the special s_now value is returned, the current time should be + /// used instead. + virtual const timeval& getModTime() const = 0; + /// Returns the number of bytes available via readData() /// /// \invariant getSize() is stable throughout the lifetime @@ -306,6 +321,7 @@ namespace zipper virtual void readData( zsize_t offset, zsize_t bytes, uint8_t* dest ) const = 0; + }; /// \brief FileReader is a file-based implementation of the Reader @@ -334,6 +350,9 @@ namespace zipper /// Inherited from Reader virtual const std::string& getSourceName() const; + /// Inherited from Reader + virtual const timeval& getModTime() const; + /// Inherited from Reader virtual zsize_t getSize() const; @@ -404,7 +423,15 @@ namespace zipper /// /// \param createPermissions The permissions set on the file if it is to /// be created. - FileWriter(const std::string& filename, mode_t createPermissions); + /// + /// \param modTime Set a specific modification time on the created file. + /// If the special s_now value is provided, the current time will be + /// used. + /// + FileWriter( + const std::string& filename, + mode_t createPermissions = 0664, + const timeval& modTime = s_now); /// Write data to the supplied file. /// @@ -478,6 +505,9 @@ namespace zipper /// getUncompressedSize() will return -1 of the FileSize capability /// bit of the container is false. virtual zsize_t getUncompressedSize() const = 0; + + /// Return the modification time of the original file + virtual const timeval& getModificationTime() const = 0; }; /// \typedef CompressedFilePtr /// A shared pointer to a CompressedFile -- 2.38.5