diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index b098da122..71284b2dc 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -112,6 +112,7 @@ ENDIF(CMAKE_USE_ELF_PARSER) # SET(SRCS cmStandardIncludes.cxx + cmArchiveWrite.cxx cmBootstrapCommands.cxx cmCacheManager.cxx cmCacheManager.h diff --git a/Source/cmArchiveWrite.cxx b/Source/cmArchiveWrite.cxx new file mode 100644 index 000000000..0a4416824 --- /dev/null +++ b/Source/cmArchiveWrite.cxx @@ -0,0 +1,243 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2000-2010 Kitware, Inc., Insight Software Consortium + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ +#include "cmArchiveWrite.h" + +#include "cmSystemTools.h" +#include +#include + +//---------------------------------------------------------------------------- +class cmArchiveWrite::Entry +{ + struct archive_entry* Object; +public: + Entry(): Object(archive_entry_new()) {} + ~Entry() { archive_entry_free(this->Object); } + operator struct archive_entry*() { return this->Object; } +}; + +//---------------------------------------------------------------------------- +struct cmArchiveWrite::Callback +{ + // archive_write_callback + static __LA_SSIZE_T Write(struct archive*, void *cd, + const void *b, size_t n) + { + cmArchiveWrite* self = static_cast(cd); + if(self->Stream.write(static_cast(b), n)) + { + return static_cast<__LA_SSIZE_T>(n); + } + else + { + return static_cast<__LA_SSIZE_T>(-1); + } + } +}; + +//---------------------------------------------------------------------------- +cmArchiveWrite::cmArchiveWrite(std::ostream& os, Compress c): + Stream(os), + Archive(archive_write_new()), + Disk(archive_read_disk_new()), + Verbose(false) +{ + switch (c) + { + case CompressNone: + if(archive_write_set_compression_none(this->Archive) != ARCHIVE_OK) + { + this->Error = "archive_write_set_compression_none: "; + this->Error += archive_error_string(this->Archive); + return; + } + break; + case CompressGZip: + if(archive_write_set_compression_gzip(this->Archive) != ARCHIVE_OK) + { + this->Error = "archive_write_set_compression_gzip: "; + this->Error += archive_error_string(this->Archive); + return; + } + break; + case CompressBZip2: + if(archive_write_set_compression_bzip2(this->Archive) != ARCHIVE_OK) + { + this->Error = "archive_write_set_compression_bzip2: "; + this->Error += archive_error_string(this->Archive); + return; + } + break; + }; + archive_read_disk_set_standard_lookup(this->Disk); + if(archive_write_set_format_pax_restricted(this->Archive) != ARCHIVE_OK) + { + this->Error = "archive_write_set_format_pax_restricted: "; + this->Error += archive_error_string(this->Archive); + return; + } + + if(archive_write_open( + this->Archive, this, 0, + reinterpret_cast(&Callback::Write), + 0) != ARCHIVE_OK) + { + this->Error = "archive_write_open: "; + this->Error += archive_error_string(this->Archive); + return; + } +} + +//---------------------------------------------------------------------------- +cmArchiveWrite::~cmArchiveWrite() +{ + archive_read_finish(this->Disk); + archive_write_finish(this->Archive); +} + +//---------------------------------------------------------------------------- +bool cmArchiveWrite::Add(std::string path, size_t skip, const char* prefix) +{ + if(this->Okay()) + { + if(!path.empty() && path[path.size()-1] == '/') + { + path.erase(path.size()-1); + } + this->AddPath(path.c_str(), skip, prefix); + } + return this->Okay(); +} + +//---------------------------------------------------------------------------- +bool cmArchiveWrite::AddPath(const char* path, + size_t skip, const char* prefix) +{ + if(!this->AddFile(path, skip, prefix)) + { + return false; + } + if(!cmSystemTools::FileIsDirectory(path)) + { + return true; + } + cmsys::Directory d; + if(d.Load(path)) + { + std::string next = path; + next += "/"; + std::string::size_type end = next.size(); + unsigned long n = d.GetNumberOfFiles(); + for(unsigned long i = 0; i < n; ++i) + { + const char* file = d.GetFile(i); + if(strcmp(file, ".") != 0 && strcmp(file, "..") != 0) + { + next.erase(end); + next += file; + if(!this->AddPath(next.c_str(), skip, prefix)) + { + return false; + } + } + } + } + return true; +} + +//---------------------------------------------------------------------------- +bool cmArchiveWrite::AddFile(const char* file, + size_t skip, const char* prefix) +{ + // Skip the file if we have no name for it. This may happen on a + // top-level directory, which does not need to be included anyway. + if(skip >= strlen(file)) + { + return true; + } + const char* out = file + skip; + + // Meta-data. + std::string dest = prefix? prefix : ""; + dest += out; + if(this->Verbose) + { + std::cout << dest << "\n"; + } + Entry e; + archive_entry_copy_sourcepath(e, file); + archive_entry_set_pathname(e, dest.c_str()); + if(archive_read_disk_entry_from_file(this->Disk, e, -1, 0) != ARCHIVE_OK) + { + this->Error = "archive_read_disk_entry_from_file: "; + this->Error += archive_error_string(this->Disk); + return false; + } + if(archive_write_header(this->Archive, e) != ARCHIVE_OK) + { + this->Error = "archive_write_header: "; + this->Error += archive_error_string(this->Archive); + return false; + } + + // Content. + if(size_t size = static_cast(archive_entry_size(e))) + { + return this->AddData(file, size); + } + return true; +} + +//---------------------------------------------------------------------------- +bool cmArchiveWrite::AddData(const char* file, size_t size) +{ + std::ifstream fin(file, std::ios::in | cmsys_ios_binary); + if(!fin) + { + this->Error = "Error opening \""; + this->Error += file; + this->Error += "\": "; + this->Error += cmSystemTools::GetLastSystemError(); + return false; + } + + char buffer[16384]; + size_t nleft = size; + while(nleft > 0) + { + size_t nnext = nleft > sizeof(buffer)? sizeof(buffer) : nleft; + fin.read(buffer, nnext); + // Some stream libraries (older HPUX) return failure at end of + // file on the last read even if some data were read. Check + // gcount instead of trusting the stream error status. + if(fin.gcount() != nnext) + { + break; + } + if(archive_write_data(this->Archive, buffer, nnext) != nnext) + { + this->Error = "archive_write_data: "; + this->Error += archive_error_string(this->Archive); + return false; + } + nleft -= nnext; + } + if(nleft > 0) + { + this->Error = "Error reading \""; + this->Error += file; + this->Error += "\": "; + this->Error += cmSystemTools::GetLastSystemError(); + return false; + } + return true; +} diff --git a/Source/cmArchiveWrite.h b/Source/cmArchiveWrite.h new file mode 100644 index 000000000..92c0c7320 --- /dev/null +++ b/Source/cmArchiveWrite.h @@ -0,0 +1,84 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2000-2010 Kitware, Inc., Insight Software Consortium + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ +#ifndef cmArchiveWrite_h +#define cmArchiveWrite_h + +#include "cmStandardIncludes.h" + +#if !defined(CMAKE_BUILD_WITH_CMAKE) +# error "cmArchiveWrite not allowed during bootstrap build!" +#endif + +/** \class cmArchiveWrite + * \brief Wrapper around libarchive for writing. + * + */ +class cmArchiveWrite +{ + typedef void (cmArchiveWrite::* safe_bool)(); + void safe_bool_true() {} +public: + /** Compression type. */ + enum Compress + { + CompressNone, + CompressGZip, + CompressBZip2 + }; + + /** Construct with output stream to which to write archive. */ + cmArchiveWrite(std::ostream& os, Compress c = CompressNone); + ~cmArchiveWrite(); + + /** + * Add a path (file or directory) to the archive. Directories are + * added recursively. The "path" must be readable on disk, either + * full path or relative to current working directory. The "skip" + * value indicates how many leading bytes from the input path to + * skip. The remaining part of the input path is appended to the + * "prefix" value to construct the final name in the archive. + */ + bool Add(std::string path, size_t skip = 0, const char* prefix = 0); + + /** Returns true if there has been no error. */ + operator safe_bool() const + { return this->Okay()? &cmArchiveWrite::safe_bool_true : 0; } + + /** Returns true if there has been an error. */ + bool operator!() const { return !this->Okay(); } + + /** Return the error string; empty if none. */ + std::string GetError() const { return this->Error; } + + // TODO: More general callback instead of hard-coding calls to + // std::cout. + void SetVerbose(bool v) { this->Verbose = v; } + +private: + bool Okay() const { return this->Error.empty(); } + bool AddPath(const char* path, size_t skip, const char* prefix); + bool AddFile(const char* file, size_t skip, const char* prefix); + bool AddData(const char* file, size_t size); + + struct Callback; + friend struct Callback; + + class Entry; + + std::ostream& Stream; + struct archive* Archive; + struct archive* Disk; + bool Verbose; + std::string Error; +}; + +#endif