diff options
Diffstat (limited to 'Source/CPack/cmCPackDebGenerator.cxx')
-rw-r--r-- | Source/CPack/cmCPackDebGenerator.cxx | 841 |
1 files changed, 841 insertions, 0 deletions
diff --git a/Source/CPack/cmCPackDebGenerator.cxx b/Source/CPack/cmCPackDebGenerator.cxx new file mode 100644 index 0000000..4494e8a --- /dev/null +++ b/Source/CPack/cmCPackDebGenerator.cxx @@ -0,0 +1,841 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2000-2009 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 "cmCPackDebGenerator.h" + +#include "cmSystemTools.h" +#include "cmMakefile.h" +#include "cmGeneratedFileStream.h" +#include "cmCPackLog.h" + +#include <cmsys/SystemTools.hxx> +#include <cmsys/Glob.hxx> + +#include <limits.h> // USHRT_MAX + +// NOTE: +// A debian package .deb is simply an 'ar' archive. The only subtle difference +// is that debian uses the BSD ar style archive whereas most Linux distro have +// a GNU ar. +// See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=161593 for more info +// Therefore we provide our own implementation of a BSD-ar: +static int ar_append(const char*archive,const std::vector<std::string>& files); + +//---------------------------------------------------------------------- +cmCPackDebGenerator::cmCPackDebGenerator() +{ +} + +//---------------------------------------------------------------------- +cmCPackDebGenerator::~cmCPackDebGenerator() +{ +} + +//---------------------------------------------------------------------- +int cmCPackDebGenerator::InitializeInternal() +{ + this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr"); + if (cmSystemTools::IsOff(this->GetOption("CPACK_SET_DESTDIR"))) + { + this->SetOption("CPACK_SET_DESTDIR", "I_ON"); + } + return this->Superclass::InitializeInternal(); +} + +//---------------------------------------------------------------------- +int cmCPackDebGenerator::PackageOnePack(std::string initialTopLevel, + std::string packageName) + { + int retval = 1; + // Begin the archive for this pack + std::string localToplevel(initialTopLevel); + std::string packageFileName( + cmSystemTools::GetParentDirectory(toplevel.c_str()) + ); + std::string outputFileName( + std::string(this->GetOption("CPACK_PACKAGE_FILE_NAME")) + +"-"+packageName + this->GetOutputExtension() + ); + + localToplevel += "/"+ packageName; + /* replace the TEMP DIRECTORY with the component one */ + this->SetOption("CPACK_TEMPORARY_DIRECTORY",localToplevel.c_str()); + packageFileName += "/"+ outputFileName; + /* replace proposed CPACK_OUTPUT_FILE_NAME */ + this->SetOption("CPACK_OUTPUT_FILE_NAME",outputFileName.c_str()); + /* replace the TEMPORARY package file name */ + this->SetOption("CPACK_TEMPORARY_PACKAGE_FILE_NAME", + packageFileName.c_str()); + // Tell CPackDeb.cmake the name of the component GROUP. + this->SetOption("CPACK_DEB_PACKAGE_COMPONENT",packageName.c_str()); + // Tell CPackDeb.cmake the path where the component is. + std::string component_path = "/"; + component_path += packageName; + this->SetOption("CPACK_DEB_PACKAGE_COMPONENT_PART_PATH", + component_path.c_str()); + if (!this->ReadListFile("CPackDeb.cmake")) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Error while execution CPackDeb.cmake" << std::endl); + retval = 0; + return retval; + } + + cmsys::Glob gl; + std::string findExpr(this->GetOption("WDIR")); + findExpr += "/*"; + gl.RecurseOn(); + if ( !gl.FindFiles(findExpr) ) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Cannot find any files in the installed directory" << std::endl); + return 0; + } + packageFiles = gl.GetFiles(); + + int res = createDeb(); + if (res != 1) + { + retval = 0; + } + // add the generated package to package file names list + packageFileNames.push_back(packageFileName); + return retval; +} + +//---------------------------------------------------------------------- +int cmCPackDebGenerator::PackageComponents(bool ignoreGroup) +{ + int retval = 1; + /* Reset package file name list it will be populated during the + * component packaging run*/ + packageFileNames.clear(); + std::string initialTopLevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY")); + + // The default behavior is to have one package by component group + // unless CPACK_COMPONENTS_IGNORE_GROUP is specified. + if (!ignoreGroup) + { + std::map<std::string, cmCPackComponentGroup>::iterator compGIt; + for (compGIt=this->ComponentGroups.begin(); + compGIt!=this->ComponentGroups.end(); ++compGIt) + { + cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Packaging component group: " + << compGIt->first + << std::endl); + // Begin the archive for this group + retval &= PackageOnePack(initialTopLevel,compGIt->first); + } + // Handle Orphan components (components not belonging to any groups) + std::map<std::string, cmCPackComponent>::iterator compIt; + for (compIt=this->Components.begin(); + compIt!=this->Components.end(); ++compIt ) + { + // Does the component belong to a group? + if (compIt->second.Group==NULL) + { + cmCPackLogger(cmCPackLog::LOG_VERBOSE, + "Component <" + << compIt->second.Name + << "> does not belong to any group, package it separately." + << std::endl); + // Begin the archive for this orphan component + retval &= PackageOnePack(initialTopLevel,compIt->first); + } + } + } + // CPACK_COMPONENTS_IGNORE_GROUPS is set + // We build 1 package per component + else + { + std::map<std::string, cmCPackComponent>::iterator compIt; + for (compIt=this->Components.begin(); + compIt!=this->Components.end(); ++compIt ) + { + retval &= PackageOnePack(initialTopLevel,compIt->first); + } + } + return retval; +} + +//---------------------------------------------------------------------- +int cmCPackDebGenerator::PackageComponentsAllInOne() +{ + int retval = 1; + std::string compInstDirName; + /* Reset package file name list it will be populated during the + * component packaging run*/ + packageFileNames.clear(); + std::string initialTopLevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY")); + + compInstDirName = "ALL_COMPONENTS_IN_ONE"; + + cmCPackLogger(cmCPackLog::LOG_VERBOSE, + "Packaging all groups in one package..." + "(CPACK_COMPONENTS_ALL_[GROUPS_]IN_ONE_PACKAGE is set)" + << std::endl); + + // The ALL GROUPS in ONE package case + std::string localToplevel(initialTopLevel); + std::string packageFileName( + cmSystemTools::GetParentDirectory(toplevel.c_str()) + ); + std::string outputFileName( + std::string(this->GetOption("CPACK_PACKAGE_FILE_NAME")) + + this->GetOutputExtension() + ); + // all GROUP in one vs all COMPONENT in one + localToplevel += "/"+compInstDirName; + + /* replace the TEMP DIRECTORY with the component one */ + this->SetOption("CPACK_TEMPORARY_DIRECTORY",localToplevel.c_str()); + packageFileName += "/"+ outputFileName; + /* replace proposed CPACK_OUTPUT_FILE_NAME */ + this->SetOption("CPACK_OUTPUT_FILE_NAME",outputFileName.c_str()); + /* replace the TEMPORARY package file name */ + this->SetOption("CPACK_TEMPORARY_PACKAGE_FILE_NAME", + packageFileName.c_str()); + // Tell CPackDeb.cmake the path where the component is. + std::string component_path = "/"; + component_path += compInstDirName; + this->SetOption("CPACK_DEB_PACKAGE_COMPONENT_PART_PATH", + component_path.c_str()); + if (!this->ReadListFile("CPackDeb.cmake")) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Error while execution CPackDeb.cmake" << std::endl); + retval = 0; + return retval; + } + + cmsys::Glob gl; + std::string findExpr(this->GetOption("WDIR")); + findExpr += "/*"; + gl.RecurseOn(); + if ( !gl.FindFiles(findExpr) ) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Cannot find any files in the installed directory" << std::endl); + return 0; + } + packageFiles = gl.GetFiles(); + + int res = createDeb(); + if (res != 1) + { + retval = 0; + } + // add the generated package to package file names list + packageFileNames.push_back(packageFileName); + return retval; +} + +//---------------------------------------------------------------------- +int cmCPackDebGenerator::PackageFiles() +{ + int retval = -1; + + /* Are we in the component packaging case */ + if (WantsComponentInstallation()) { + // CASE 1 : COMPONENT ALL-IN-ONE package + // If ALL GROUPS or ALL COMPONENTS in ONE package has been requested + // then the package file is unique and should be open here. + if (componentPackageMethod == ONE_PACKAGE) + { + return PackageComponentsAllInOne(); + } + // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one) + // There will be 1 package for each component group + // however one may require to ignore component group and + // in this case you'll get 1 package for each component. + else + { + return PackageComponents(componentPackageMethod == + ONE_PACKAGE_PER_COMPONENT); + } + } + // CASE 3 : NON COMPONENT package. + else + { + if (!this->ReadListFile("CPackDeb.cmake")) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Error while execution CPackDeb.cmake" << std::endl); + retval = 0; + } + else + { + packageFiles = files; + return createDeb(); + } + } + return retval; +} + +int cmCPackDebGenerator::createDeb() +{ + const char* cmakeExecutable = this->GetOption("CMAKE_COMMAND"); + + // debian-binary file + std::string dbfilename; + dbfilename += this->GetOption("WDIR"); + dbfilename += "/debian-binary"; + { // the scope is needed for cmGeneratedFileStream + cmGeneratedFileStream out(dbfilename.c_str()); + out << "2.0"; + out << std::endl; // required for valid debian package + } + + // control file + std::string ctlfilename; + ctlfilename = this->GetOption("WDIR"); + ctlfilename += "/control"; + + // debian policy enforce lower case for package name + // mandatory entries: + std::string debian_pkg_name = cmsys::SystemTools::LowerCase( + this->GetOption("CPACK_DEBIAN_PACKAGE_NAME") ); + const char* debian_pkg_version = + this->GetOption("CPACK_DEBIAN_PACKAGE_VERSION"); + const char* debian_pkg_section = + this->GetOption("CPACK_DEBIAN_PACKAGE_SECTION"); + const char* debian_pkg_priority = + this->GetOption("CPACK_DEBIAN_PACKAGE_PRIORITY"); + const char* debian_pkg_arch = + this->GetOption("CPACK_DEBIAN_PACKAGE_ARCHITECTURE"); + const char* maintainer = this->GetOption("CPACK_DEBIAN_PACKAGE_MAINTAINER"); + const char* desc = this->GetOption("CPACK_DEBIAN_PACKAGE_DESCRIPTION"); + + // optional entries + const char* debian_pkg_dep = this->GetOption("CPACK_DEBIAN_PACKAGE_DEPENDS"); + const char* debian_pkg_rec = + this->GetOption("CPACK_DEBIAN_PACKAGE_RECOMMENDS"); + const char* debian_pkg_sug = + this->GetOption("CPACK_DEBIAN_PACKAGE_SUGGESTS"); + const char* debian_pkg_url = + this->GetOption("CPACK_DEBIAN_PACKAGE_HOMEPAGE"); + const char* debian_pkg_predep = + this->GetOption("CPACK_DEBIAN_PACKAGE_PREDEPENDS"); + const char* debian_pkg_enhances = + this->GetOption("CPACK_DEBIAN_PACKAGE_ENHANCES"); + const char* debian_pkg_breaks = + this->GetOption("CPACK_DEBIAN_PACKAGE_BREAKS"); + const char* debian_pkg_conflicts = + this->GetOption("CPACK_DEBIAN_PACKAGE_CONFLICTS"); + const char* debian_pkg_provides = + this->GetOption("CPACK_DEBIAN_PACKAGE_PROVIDES"); + const char* debian_pkg_replaces = + this->GetOption("CPACK_DEBIAN_PACKAGE_REPLACES"); + + { // the scope is needed for cmGeneratedFileStream + cmGeneratedFileStream out(ctlfilename.c_str()); + out << "Package: " << debian_pkg_name << "\n"; + out << "Version: " << debian_pkg_version << "\n"; + out << "Section: " << debian_pkg_section << "\n"; + out << "Priority: " << debian_pkg_priority << "\n"; + out << "Architecture: " << debian_pkg_arch << "\n"; + if(debian_pkg_dep && *debian_pkg_dep) + { + out << "Depends: " << debian_pkg_dep << "\n"; + } + if(debian_pkg_rec && *debian_pkg_rec) + { + out << "Recommends: " << debian_pkg_rec << "\n"; + } + if(debian_pkg_sug && *debian_pkg_sug) + { + out << "Suggests: " << debian_pkg_sug << "\n"; + } + if(debian_pkg_url && *debian_pkg_url) + { + out << "Homepage: " << debian_pkg_url << "\n"; + } + if (debian_pkg_predep && *debian_pkg_predep) + { + out << "Pre-Depends: " << debian_pkg_predep << "\n"; + } + if (debian_pkg_enhances && *debian_pkg_enhances) + { + out << "Enhances: " << debian_pkg_enhances << "\n"; + } + if (debian_pkg_breaks && *debian_pkg_breaks) + { + out << "Breaks: " << debian_pkg_breaks << "\n"; + } + if (debian_pkg_conflicts && *debian_pkg_conflicts) + { + out << "Conflicts: " << debian_pkg_conflicts << "\n"; + } + if (debian_pkg_provides && *debian_pkg_provides) + { + out << "Provides: " << debian_pkg_provides << "\n"; + } + if (debian_pkg_replaces && *debian_pkg_replaces) + { + out << "Replaces: " << debian_pkg_replaces << "\n"; + } + unsigned long totalSize = 0; + { + std::string dirName = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + dirName += '/'; + for (std::vector<std::string>::const_iterator fileIt = + packageFiles.begin(); + fileIt != packageFiles.end(); ++ fileIt ) + { + totalSize += cmSystemTools::FileLength(fileIt->c_str()); + } + } + out << "Installed-Size: " << (totalSize + 1023) / 1024 << "\n"; + out << "Maintainer: " << maintainer << "\n"; + out << "Description: " << desc << "\n"; + out << std::endl; + } + + std::string cmd; + if (NULL != this->GetOption("CPACK_DEBIAN_FAKEROOT_EXECUTABLE")) { + cmd += this->GetOption("CPACK_DEBIAN_FAKEROOT_EXECUTABLE"); + } + cmd += " \""; + cmd += cmakeExecutable; + cmd += "\" -E tar cfz data.tar.gz "; + + // now add all directories which have to be compressed + // collect all top level install dirs for that + // e.g. /opt/bin/foo, /usr/bin/bar and /usr/bin/baz would give /usr and /opt + size_t topLevelLength = std::string(this->GetOption("WDIR")).length(); + cmCPackLogger(cmCPackLog::LOG_DEBUG, "WDIR: \"" << this->GetOption("WDIR") + << "\", length = " << topLevelLength + << std::endl); + std::set<std::string> installDirs; + for (std::vector<std::string>::const_iterator fileIt = + packageFiles.begin(); + fileIt != packageFiles.end(); ++ fileIt ) + { + cmCPackLogger(cmCPackLog::LOG_DEBUG, "FILEIT: \"" << *fileIt << "\"" + << std::endl); + std::string::size_type slashPos = fileIt->find('/', topLevelLength+1); + std::string relativeDir = fileIt->substr(topLevelLength, + slashPos - topLevelLength); + cmCPackLogger(cmCPackLog::LOG_DEBUG, "RELATIVEDIR: \"" << relativeDir + << "\"" << std::endl); + if (installDirs.find(relativeDir) == installDirs.end()) + { + installDirs.insert(relativeDir); + cmd += " ."; + cmd += relativeDir; + } + } + + std::string output; + int retval = -1; + int res = cmSystemTools::RunSingleCommand(cmd.c_str(), &output, + &retval, this->GetOption("WDIR"), this->GeneratorVerbose, 0); + + if ( !res || retval ) + { + std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + tmpFile += "/Deb.log"; + cmGeneratedFileStream ofs(tmpFile.c_str()); + ofs << "# Run command: " << cmd.c_str() << std::endl + << "# Working directory: " << toplevel << std::endl + << "# Output:" << std::endl + << output.c_str() << std::endl; + cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running tar command: " + << cmd.c_str() << std::endl + << "Please check " << tmpFile.c_str() << " for errors" << std::endl); + return 0; + } + + std::string md5filename; + md5filename = this->GetOption("WDIR"); + md5filename += "/md5sums"; + + { // the scope is needed for cmGeneratedFileStream + cmGeneratedFileStream out(md5filename.c_str()); + std::vector<std::string>::const_iterator fileIt; +// std::string topLevelWithTrailingSlash = toplevel; + std::string topLevelWithTrailingSlash = + this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + topLevelWithTrailingSlash += '/'; + for ( fileIt = packageFiles.begin(); + fileIt != packageFiles.end(); ++ fileIt ) + { + cmd = "\""; + cmd += cmakeExecutable; + cmd += "\" -E md5sum \""; + cmd += *fileIt; + cmd += "\""; + //std::string output; + //int retVal = -1; + res = cmSystemTools::RunSingleCommand(cmd.c_str(), &output, + &retval, toplevel.c_str(), this->GeneratorVerbose, 0); + // debian md5sums entries are like this: + // 014f3604694729f3bf19263bac599765 usr/bin/ccmake + // thus strip the full path (with the trailing slash) + cmSystemTools::ReplaceString(output, + topLevelWithTrailingSlash.c_str(), ""); + out << output; + } + // each line contains a eol. + // Do not end the md5sum file with yet another (invalid) + } + + cmd = ""; + if (NULL != this->GetOption("CPACK_DEBIAN_FAKEROOT_EXECUTABLE")) + { + cmd = this->GetOption("CPACK_DEBIAN_FAKEROOT_EXECUTABLE"); + } + cmd += " \""; + cmd += cmakeExecutable; + cmd += "\" -E tar cfz control.tar.gz ./control ./md5sums"; + const char* controlExtra = + this->GetOption("CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA"); + if( controlExtra ) + { + std::vector<std::string> controlExtraList; + cmSystemTools::ExpandListArgument(controlExtra, controlExtraList); + for(std::vector<std::string>::iterator i = + controlExtraList.begin(); i != controlExtraList.end(); ++i) + { + std::string filenamename = + cmsys::SystemTools::GetFilenameName(i->c_str()); + std::string localcopy = this->GetOption("WDIR"); + localcopy += "/"; + localcopy += filenamename; + // if we can copy the file, it means it does exist, let's add it: + if( cmsys::SystemTools::CopyFileIfDifferent( + i->c_str(), localcopy.c_str()) ) + { + // debian is picky and need relative to ./ path in the tar.gz + cmd += " ./"; + cmd += filenamename; + } + } + } + res = cmSystemTools::RunSingleCommand(cmd.c_str(), &output, + &retval, this->GetOption("WDIR"), this->GeneratorVerbose, 0); + + if ( !res || retval ) + { + std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + tmpFile += "/Deb.log"; + cmGeneratedFileStream ofs(tmpFile.c_str()); + ofs << "# Run command: " << cmd.c_str() << std::endl + << "# Working directory: " << toplevel << std::endl + << "# Output:" << std::endl + << output.c_str() << std::endl; + cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running tar command: " + << cmd.c_str() << std::endl + << "Please check " << tmpFile.c_str() << " for errors" << std::endl); + return 0; + } + + // ar -r your-package-name.deb debian-binary control.tar.gz data.tar.gz + // since debian packages require BSD ar (most Linux distros and even + // FreeBSD and NetBSD ship GNU ar) we use a copy of OpenBSD ar here. + std::vector<std::string> arFiles; + std::string topLevelString = this->GetOption("WDIR"); + topLevelString += "/"; + arFiles.push_back(topLevelString + "debian-binary"); + arFiles.push_back(topLevelString + "control.tar.gz"); + arFiles.push_back(topLevelString + "data.tar.gz"); + std::string outputFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + outputFileName += "/"; + outputFileName += this->GetOption("CPACK_OUTPUT_FILE_NAME"); + res = ar_append(outputFileName.c_str(), arFiles); + if ( res!=0 ) + { + std::string tmpFile = this->GetOption("CPACK_TEMPORARY_PACKAGE_FILE_NAME"); + tmpFile += "/Deb.log"; + cmGeneratedFileStream ofs(tmpFile.c_str()); + ofs << "# Problem creating archive using: " << res << std::endl; + return 0; + } + return 1; +} + +bool cmCPackDebGenerator::SupportsComponentInstallation() const + { + if (IsOn("CPACK_DEB_COMPONENT_INSTALL")) + { + return true; + } + else + { + return false; + } + } + +std::string cmCPackDebGenerator::GetComponentInstallDirNameSuffix( + const std::string& componentName) + { + if (componentPackageMethod == ONE_PACKAGE_PER_COMPONENT) { + return componentName; + } + + if (componentPackageMethod == ONE_PACKAGE) { + return std::string("ALL_COMPONENTS_IN_ONE"); + } + // We have to find the name of the COMPONENT GROUP + // the current COMPONENT belongs to. + std::string groupVar = "CPACK_COMPONENT_" + + cmSystemTools::UpperCase(componentName) + "_GROUP"; + if (NULL != GetOption(groupVar.c_str())) + { + return std::string(GetOption(groupVar.c_str())); + } + else + { + return componentName; + } + } + + +// The following code is taken from OpenBSD ar: +// http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ar/ +// It has been slightly modified: +// -return error codes instead exit() in functions +// -use the stdio file I/O functions instead the file descriptor based ones +// -merged into one cxx file +// -no additional options supported +// The coding style hasn't been modified. + +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Hugh Smith at The University of Guelph. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#define ARMAG "!<arch>\n" /* ar "magic number" */ +#define SARMAG 8 /* strlen(ARMAG); */ + +#define AR_EFMT1 "#1/" /* extended format #1 */ +#define ARFMAG "`\n" + +/* Header format strings. */ +#define HDR1 "%s%-13d%-12ld%-6u%-6u%-8o%-10lld%2s" +#define HDR2 "%-16.16s%-12ld%-6u%-6u%-8o%-10lld%2s" + +struct ar_hdr { + char ar_name[16]; /* name */ + char ar_date[12]; /* modification time */ + char ar_uid[6]; /* user id */ + char ar_gid[6]; /* group id */ + char ar_mode[8]; /* octal file permissions */ + char ar_size[10]; /* size in bytes */ + char ar_fmag[2]; /* consistency check */ +}; + +/* Set up file copy. */ +#define SETCF(from, fromname, to, toname, pad) { \ + cf.rFile = from; \ + cf.rname = fromname; \ + cf.wFile = to; \ + cf.wname = toname; \ + cf.flags = pad; \ +} + +/* File copy structure. */ +typedef struct { + FILE* rFile; /* read file descriptor */ + const char *rname; /* read name */ + FILE* wFile; /* write file descriptor */ + const char *wname; /* write name */ +#define NOPAD 0x00 /* don't pad */ +#define WPAD 0x02 /* pad on writes */ + unsigned int flags; /* pad flags */ +} CF; + +/* misc.c */ + +static const char * ar_rname(const char *path) +{ + const char *ind = strrchr(path, '/'); + return (ind ) ? ind + 1 : path; +} + +/* archive.c */ + +typedef struct ar_hdr HDR; +static char ar_hb[sizeof(HDR) + 1]; /* real header */ + +static size_t ar_already_written; + +/* copy_ar -- + * Copy size bytes from one file to another - taking care to handle the + * extra byte (for odd size files) when reading archives and writing an + * extra byte if necessary when adding files to archive. The length of + * the object is the long name plus the object itself; the variable + * already_written gets set if a long name was written. + * + * The padding is really unnecessary, and is almost certainly a remnant + * of early archive formats where the header included binary data which + * a PDP-11 required to start on an even byte boundary. (Or, perhaps, + * because 16-bit word addressed copies were faster?) Anyhow, it should + * have been ripped out long ago. + */ +static int copy_ar(CF *cfp, off_t size) +{ + static char pad = '\n'; + off_t sz = size; + size_t nr, nw; + char buf[8*1024]; + + if (sz == 0) + return 0; + + FILE* from = cfp->rFile; + FILE* to = cfp->wFile; + while (sz && + (nr = fread(buf, 1, sz < static_cast<off_t>(sizeof(buf)) + ? static_cast<size_t>(sz) : sizeof(buf), from )) + > 0) { + sz -= nr; + for (size_t off = 0; off < nr; nr -= off, off += nw) + if ((nw = fwrite(buf + off, 1, nr, to)) < nr) + return -1; + } + if (sz) + return -2; + + if (cfp->flags & WPAD && (size + ar_already_written) & 1 + && fwrite(&pad, 1, 1, to) != 1) + return -4; + + return 0; +} + +/* put_arobj -- Write an archive member to a file. */ +static int put_arobj(CF *cfp, struct stat *sb) +{ + int result = 0; + struct ar_hdr *hdr; + + /* If passed an sb structure, reading a file from disk. Get stat(2) + * information, build a name and construct a header. (Files are named + * by their last component in the archive.) */ + const char* name = ar_rname(cfp->rname); + (void)stat(cfp->rname, sb); + + /* If not truncating names and the name is too long or contains + * a space, use extended format 1. */ + size_t lname = strlen(name); + uid_t uid = sb->st_uid; + gid_t gid = sb->st_gid; + if (uid > USHRT_MAX) { + uid = USHRT_MAX; + } + if (gid > USHRT_MAX) { + gid = USHRT_MAX; + } + if (lname > sizeof(hdr->ar_name) || strchr(name, ' ')) + (void)sprintf(ar_hb, HDR1, AR_EFMT1, (int)lname, + (long int)sb->st_mtime, (unsigned)uid, (unsigned)gid, + sb->st_mode, (long long)sb->st_size + lname, ARFMAG); + else { + lname = 0; + (void)sprintf(ar_hb, HDR2, name, + (long int)sb->st_mtime, (unsigned)uid, (unsigned)gid, + sb->st_mode, (long long)sb->st_size, ARFMAG); + } + off_t size = sb->st_size; + + if (fwrite(ar_hb, 1, sizeof(HDR), cfp->wFile) != sizeof(HDR)) + return -1; + + if (lname) { + if (fwrite(name, 1, lname, cfp->wFile) != lname) + return -2; + ar_already_written = lname; + } + result = copy_ar(cfp, size); + ar_already_written = 0; + return result; +} + +/* append.c */ + +/* append -- + * Append files to the archive - modifies original archive or creates + * a new archive if named archive does not exist. + */ +static int ar_append(const char* archive,const std::vector<std::string>& files) +{ + int eval = 0; + FILE* aFile = fopen(archive, "wb+"); + if (aFile!=NULL) { + fwrite(ARMAG, SARMAG, 1, aFile); + if (fseek(aFile, 0, SEEK_END) != -1) { + CF cf; + struct stat sb; + /* Read from disk, write to an archive; pad on write. */ + SETCF(NULL, 0, aFile, archive, WPAD); + for(std::vector<std::string>::const_iterator fileIt = files.begin(); + fileIt!=files.end(); ++fileIt) { + const char* filename = fileIt->c_str(); + FILE* file = fopen(filename, "rb"); + if (file == NULL) { + eval = -1; + continue; + } + cf.rFile = file; + cf.rname = filename; + int result = put_arobj(&cf, &sb); + (void)fclose(file); + if (result!=0) { + eval = -2; + break; + } + } + } + else { + eval = -3; + } + fclose(aFile); + } + else { + eval = -4; + } + return eval; +} |