summaryrefslogtreecommitdiff
path: root/src/installtransaction.cpp
blob: 70e5e3756f0cea4089ebee46b723da2423168c8e (plain)
    1 ////////////////////////////////////////////////////////////////////////
    2 // FILE:        installtransaction.cpp
    3 // AUTHOR:      Johannes Winkelmann, jw@tks6.net
    4 // COPYRIGHT:   (c) 2002 by Johannes Winkelmann
    5 // ---------------------------------------------------------------------
    6 //  This program is free software; you can redistribute it and/or modify
    7 //  it under the terms of the GNU General Public License as published by
    8 //  the Free Software Foundation; either version 2 of the License, or
    9 //  (at your option) any later version.
   10 ////////////////////////////////////////////////////////////////////////
   11 
   12 #include <unistd.h>
   13 #include <cstdio>
   14 #include <iostream>
   15 #include <algorithm>
   16 #include <list>
   17 
   18 #include <sys/types.h>
   19 #include <sys/stat.h>
   20 #include <fcntl.h>
   21 #include <time.h>
   22 using namespace std;
   23 
   24 
   25 #include "installtransaction.h"
   26 #include "repository.h"
   27 #include "pkgdb.h"
   28 #include "stringhelper.h"
   29 #include "argparser.h"
   30 #include "process.h"
   31 #include "configuration.h"
   32 
   33 #ifdef USE_LOCKING
   34 #include "lockfile.h"
   35 #endif
   36 
   37 using namespace StringHelper;
   38 
   39 
   40 const string InstallTransaction::PKGMK_DEFAULT_COMMAND =  "/usr/bin/pkgmk";
   41 const string InstallTransaction::PKGADD_DEFAULT_COMMAND = "/usr/bin/pkgadd";
   42 const string InstallTransaction::PKGRM_DEFAULT_COMMAND =  "/usr/bin/pkgrm";
   43 
   44 /*!
   45  Create a nice InstallTransaction
   46  \param names a list of port names to be installed
   47  \param repo the repository to look for packages
   48  \param pkgDB the pkgDB with already installed packages
   49 */
   50 InstallTransaction::InstallTransaction( const list<string>& names,
   51                                         const Repository* repo,
   52                                         PkgDB* pkgDB,
   53                                         const Configuration* config )
   54     : m_repo( repo ),
   55       m_pkgDB( pkgDB ),
   56       m_depCalced( false ),
   57       m_config( config )
   58 {
   59     list<string>::const_iterator it = names.begin();
   60     for ( ; it != names.end(); ++it ) {
   61         m_packages.push_back( make_pair( *it, m_repo->getPackage( *it ) ) );
   62     }
   63 
   64 }
   65 
   66 /*!
   67  Create a nice InstallTransaction
   68  \param names a list of port names to be installed
   69  \param repo the repository to look for packages
   70  \param pkgDB the pkgDB with already installed packages
   71 */
   72 InstallTransaction::InstallTransaction( const list<char*>& names,
   73                                         const Repository* repo,
   74                                         PkgDB* pkgDB,
   75                                         const Configuration* config )
   76     : m_repo( repo ),
   77       m_pkgDB( pkgDB ),
   78       m_depCalced( false ),
   79       m_config( config )
   80 {
   81     list<char*>::const_iterator it = names.begin();
   82     for ( ; it != names.end(); ++it ) {
   83         m_packages.push_back( make_pair( *it, m_repo->getPackage( *it ) ) );
   84     }
   85 
   86 }
   87 
   88 
   89 
   90 /*!
   91  Create a nice InstallTransaction
   92  \param names a list of port names to be installed
   93  \param repo the repository to look for packages
   94  \param pkgDB the pkgDB with already installed packages
   95 */
   96 InstallTransaction::InstallTransaction( const string& name,
   97                                         const Repository* repo,
   98                                         PkgDB* pkgDB,
   99                                         const Configuration* config )
  100     : m_repo( repo ),
  101       m_pkgDB( pkgDB ),
  102       m_depCalced( false ),
  103       m_config( config )
  104 {
  105     m_packages.push_back( make_pair( name, m_repo->getPackage( name ) ) );
  106 
  107 }
  108 
  109 /*!
  110   \return packages where building/installation failed
  111 */
  112 const list< pair<string, InstallTransaction::InstallInfo> >&
  113 InstallTransaction::installError() const
  114 {
  115     return m_installErrors;
  116 }
  117 
  118 /*!
  119   install (commit) a transaction
  120   \param parser the argument parser
  121   \param update whether this is an update operation
  122   \param group whether this is a group transaction (stops transaction on error)
  123   \return returns an InstallResult telling whether installation worked
  124 */
  125 InstallTransaction::InstallResult
  126 InstallTransaction::install( const ArgParser* parser,
  127                              bool update, bool group )
  128 {
  129     if ( m_packages.empty() ) {
  130         return NO_PACKAGE_GIVEN;
  131     }
  132 
  133     list<string> ignoredPackages;
  134     StringHelper::split(parser->ignore(), ',', ignoredPackages);
  135 
  136     list< pair<string, const Package*> >::iterator it = m_packages.begin();
  137     for ( ; it != m_packages.end(); ++it ) {
  138         const Package* package = it->second;
  139 
  140         if (find(ignoredPackages.begin(),
  141                  ignoredPackages.end(),
  142                  it->first) != ignoredPackages.end() ) {
  143             m_ignoredPackages.push_back(it->first);
  144             continue;
  145         }
  146 
  147         if ( package == NULL ) {
  148             m_missingPackages.push_back( make_pair( it->first, string("") ) );
  149             if ( group ) {
  150                 return PACKAGE_NOT_FOUND;
  151             }
  152             continue;
  153         }
  154 
  155         // consider aliases here, but don't show them specifically
  156         if ( !update && m_pkgDB->isInstalled( package->name(), true ) ) {
  157             // ignore
  158             m_alreadyInstalledPackages.push_back( package->name() );
  159             continue;
  160         }
  161 
  162         InstallTransaction::InstallResult result;
  163         InstallInfo info( package->hasReadme() );
  164         if ( parser->isTest() ||
  165              (result = installPackage( package, parser, update, info )) == SUCCESS) {
  166 
  167             m_installedPackages.push_back( make_pair( package->name(), info));
  168         } else {
  169 
  170             // log failures are critical
  171             if ( result == LOG_DIR_FAILURE ||
  172                  result == LOG_FILE_FAILURE ||
  173                  result == NO_LOG_FILE ||
  174                  result == CANT_LOCK_LOG_FILE ||
  175 
  176                  // or pkgdest
  177                  result == PKGDEST_ERROR ) {
  178                 return result;
  179             }
  180 
  181             m_installErrors.push_back( make_pair(package->name(), info) );
  182             if ( group ) {
  183                 return PKGMK_FAILURE;
  184             }
  185         }
  186     }
  187 
  188     return SUCCESS;
  189 }
  190 
  191 /*!
  192   Install a single package
  193   \param package the package to be installed
  194   \param parser the argument parser to be used
  195   \param update whether this is an update transaction
  196   \param info store pre and post install information
  197 */
  198 InstallTransaction::InstallResult
  199 InstallTransaction::installPackage( const Package* package,
  200                                     const ArgParser* parser,
  201                                     bool update,
  202                                     InstallTransaction::InstallInfo& info )
  203     const
  204 {
  205 
  206     InstallTransaction::InstallResult result = SUCCESS;
  207 #ifdef USE_LOCKING
  208     LockFile lockFile;
  209 #endif
  210 
  211     int fdlog = -1;
  212     string logFile = "";
  213     string timestamp;
  214 
  215     string commandName = "prt-get";
  216     if ( parser->wasCalledAsPrtCached() ) {
  217         commandName = "prt-cache";
  218     }
  219 
  220     // - initial information about the package to be build
  221     string message;
  222     message = commandName + ": ";
  223     if (update) {
  224         message += "updating ";
  225     } else {
  226         message += "installing ";
  227     }
  228     message += package->path() + "/" + package->name();
  229     cout << message << endl;
  230 
  231     if ( m_config->writeLog() ) {
  232         logFile = m_config->logFilePattern();
  233         if ( logFile == "" ) {
  234             return NO_LOG_FILE;
  235         }
  236 
  237         StringHelper::replaceAll( logFile, "%n", package->name() );
  238         StringHelper::replaceAll( logFile, "%p", package->path() );
  239         StringHelper::replaceAll( logFile, "%v", package->version() );
  240         StringHelper::replaceAll( logFile, "%r", package->release() );
  241 
  242 #ifdef USE_LOCKING
  243         lockFile.setFile( logFile );
  244         if ( !lockFile.lockWrite() ) {
  245             cout << "here" << logFile << endl;
  246             return CANT_LOCK_LOG_FILE;
  247         }
  248 #endif
  249 
  250         size_t pos = logFile.find_last_of( "/" );
  251         if ( pos != string::npos ) {
  252             if ( !Repository::createOutputDir( logFile.substr( 0, pos ) ) ) {
  253                 return LOG_DIR_FAILURE;
  254             }
  255         }
  256 
  257         if ( !m_config->appendLog() ) {
  258             unlink( logFile.c_str() );
  259         }
  260 
  261         fdlog = open( logFile.c_str(),
  262 		          O_APPEND | O_WRONLY | O_CREAT, 0666 );
  263 
  264         if ( fdlog == -1 ) {
  265             return LOG_FILE_FAILURE;
  266         }
  267 
  268         write( fdlog, message.c_str(), message.length());
  269         write( fdlog, "\n", 1);
  270 
  271         time_t startTime;
  272         time(&startTime);
  273         timestamp = ctime(&startTime);
  274         timestamp = commandName + ": starting build " + timestamp;
  275         write( fdlog, timestamp.c_str(), timestamp.length());
  276     }
  277 
  278     string pkgdir = package->path() + "/" + package->name();
  279     chdir( pkgdir.c_str() );
  280 
  281     string runscriptCommand = "sh";
  282     if (m_config->runscriptCommand() != "") {
  283         runscriptCommand = m_config->runscriptCommand();
  284     }
  285 
  286     // -- pre-install
  287     struct stat statData;
  288     if ((parser->execPreInstall() || m_config->runScripts()) &&
  289         stat((pkgdir + "/" + "pre-install").c_str(), &statData) == 0) {
  290         Process preProc( runscriptCommand,
  291                          pkgdir + "/" + "pre-install",
  292                          fdlog );
  293         if (preProc.executeShell()) {
  294             info.preState = FAILED;
  295         } else {
  296             info.preState = EXEC_SUCCESS;
  297         }
  298     }
  299 
  300     // -- build
  301     string cmd = PKGMK_DEFAULT_COMMAND;
  302     if (m_config->makeCommand() != "") {
  303         cmd = m_config->makeCommand();
  304     }
  305 
  306     string args = "-d " + parser->pkgmkArgs();
  307     Process makeProc( cmd, args, fdlog );
  308     if ( makeProc.executeShell() ) {
  309         result = PKGMK_FAILURE;
  310     } else {
  311         // -- update
  312         string pkgdest = getPkgmkPackageDir();
  313         if ( pkgdest != "" ) {
  314             // TODO: don't manipulate pkgdir
  315             pkgdir = pkgdest;
  316             string message = "prt-get: Using PKGMK_PACKAGE_DIR: " + pkgdir;
  317             if (parser->verbose() > 0) {
  318                 cout << message << endl;
  319             }
  320             if ( m_config->writeLog() ) {
  321                 write( fdlog, message.c_str(), message.length() );
  322                 write( fdlog, "\n", 1 );
  323             }
  324         }
  325 
  326         // the following chdir is a noop if usePkgDest() returns false
  327         if ( chdir( pkgdir.c_str() ) != 0 ) {
  328             result = PKGDEST_ERROR;
  329         } else {
  330             cmd = PKGADD_DEFAULT_COMMAND;
  331             if (m_config->addCommand() != "") {
  332                 cmd = m_config->addCommand();
  333             }
  334 
  335             args = "";
  336             if (parser->installRoot() != "") {
  337                 args = "-r " + parser->installRoot() + " ";
  338             }
  339 
  340 
  341             if ( update ) {
  342                 args += "-u ";
  343             }
  344             if ( !parser->pkgaddArgs().empty() ) {
  345                 args += parser->pkgaddArgs() + " ";
  346             }
  347             args +=
  348                 package->name()    + "#" +
  349                 package->version() + "-" +
  350                 package->release() + ".pkg.tar." + getPkgmkCompressionMode();
  351 
  352 
  353             // - inform the user about what's happening
  354             string fullCommand = commandName + ": " + cmd + " " + args;
  355             string summary;
  356             if (update) {
  357                 string from = m_pkgDB->getPackageVersion(package->name());
  358                 string to = package->version() + "-" + package->release();
  359                 if (from ==  to) {
  360                     summary = commandName + ": " + "reinstalling " +
  361                         package->name() + " " + to;
  362                 } else {
  363                     summary = commandName + ": " + "updating " +
  364                         package->name() + " from " + from + " to " + to;
  365                 }
  366             } else {
  367                 summary = commandName + ": " + "installing " +
  368                     package->name() + " " +
  369                     package->version() + "-" + package->release();
  370             }
  371 
  372             // - print and log
  373             cout << summary << endl;
  374             if (parser->verbose() > 0) {
  375                 cout << fullCommand << endl;
  376             }
  377             if ( m_config->writeLog() ) {
  378                 time_t endTime;
  379                 time(&endTime);
  380                 timestamp = ctime(&endTime);
  381                 timestamp = commandName + ": build done " + timestamp;
  382 
  383                 write( fdlog, summary.c_str(), summary.length() );
  384                 write( fdlog, "\n", 1 );
  385                 write( fdlog, fullCommand.c_str(), fullCommand.length() );
  386                 write( fdlog, "\n", 1 );
  387                 write( fdlog, timestamp.c_str(), timestamp.length());
  388                 write( fdlog, "\n", 1 );
  389             }
  390 
  391             Process installProc( cmd, args, fdlog );
  392             if ( installProc.executeShell() ) {
  393                 result = PKGADD_FAILURE;
  394             } else {
  395                 // exec post install
  396                 if ((parser->execPostInstall()  || m_config->runScripts() ) &&
  397                     stat((package->path() + "/" + package->name() +
  398                           "/" + "post-install").c_str(), &statData)
  399                     == 0) {
  400                     // Work around the pkgdir variable change
  401                     Process postProc( runscriptCommand,
  402                                       package->path() + "/" + package->name()+
  403                                       "/" + "post-install",
  404 				      fdlog );
  405                     if (postProc.executeShell()) {
  406                         info.postState = FAILED;
  407                     } else {
  408                         info.postState = EXEC_SUCCESS;
  409                     }
  410                 }
  411             }
  412         }
  413     }
  414 
  415     if ( m_config->writeLog() ) {
  416 
  417 #ifdef USE_LOCKING
  418         lockFile.unlock();
  419 #endif
  420 
  421         // Close logfile
  422         close ( fdlog );
  423 
  424         if (m_config->removeLogOnSuccess() && !m_config->appendLog() &&
  425             result == SUCCESS) {
  426             unlink(logFile.c_str());
  427         }
  428     }
  429     return result;
  430 }
  431 
  432 /*!
  433   Calculate dependencies for this transaction
  434   \return true on success
  435 */
  436 bool InstallTransaction::calculateDependencies()
  437 {
  438     if ( m_depCalced ) {
  439         return true;
  440     }
  441     m_depCalced = true;
  442     if ( m_packages.empty() ) {
  443         return false;
  444     }
  445 
  446     list< pair<string, const Package*> >::const_iterator it =
  447         m_packages.begin();
  448     for ( ; it != m_packages.end(); ++it ) {
  449         const Package* package = it->second;
  450         if ( package ) {
  451             checkDependecies( package );
  452         }
  453     }
  454     list<int> indexList;
  455     if ( ! m_resolver.resolve( indexList ) ) {
  456         m_depCalced = false;
  457         return false;
  458     }
  459 
  460     list<int>::iterator lit = indexList.begin();
  461     for ( ; lit != indexList.end(); ++lit ) {
  462         m_depNameList.push_back( m_depList[*lit] );
  463     }
  464 
  465     return true;
  466 }
  467 
  468 /*!
  469   recursive method to calculate dependencies
  470   \param package package for which we want to calculate dependencies
  471   \param depends index if the package \a package depends on (-1 for none)
  472 */
  473 void InstallTransaction::checkDependecies( const Package* package,
  474                                            int depends )
  475 {
  476     int index = -1;
  477     bool newPackage = true;
  478     for ( unsigned int i = 0; i < m_depList.size(); ++i ){
  479         if ( m_depList[i] == package->name() ) {
  480             index = i;
  481             newPackage = false;
  482             break;
  483         }
  484     }
  485 
  486 
  487     if ( index == -1 ) {
  488         index = m_depList.size();
  489         m_depList.push_back( package->name() );
  490     }
  491 
  492     if ( depends != -1 ) {
  493         m_resolver.addDependency( index, depends );
  494     } else {
  495         // this just adds index to the dependency resolver
  496         m_resolver.addDependency( index, index );
  497     }
  498 
  499     if ( newPackage ) {
  500         if ( !package->dependencies().empty() ) {
  501             list<string> deps;
  502             split( package->dependencies(), ',', deps );
  503             list<string>::iterator it = deps.begin();
  504             for ( ; it != deps.end(); ++it ) {
  505                 string dep = *it;
  506                 if ( !dep.empty() ) {
  507                     string::size_type pos = dep.find_last_of( '/' );
  508                     if ( pos != string::npos && (pos+1) < dep.length() ) {
  509                         dep = dep.substr( pos + 1 );
  510                     }
  511                     const Package* p = m_repo->getPackage( dep );
  512                     if ( p ) {
  513                         checkDependecies( p, index );
  514                     } else {
  515                         m_missingPackages.
  516                             push_back( make_pair( dep, package->name() ) );
  517                     }
  518                 }
  519             }
  520         }
  521     }
  522 }
  523 
  524 
  525 /*!
  526   This method returns a list of packages which should be installed to
  527   meet the requirements for the packages to be installed. Includes
  528   the packages to be installed. The packages are in the correct order,
  529   packages to be installed first come first :-)
  530 
  531   \return a list of packages required for the transaction
  532 */
  533 const list<string>& InstallTransaction::dependencies() const
  534 {
  535     return m_depNameList;
  536 }
  537 
  538 
  539 /*!
  540   This method returns a list of packages which could not be installed
  541   because they could not be found in the ports tree. The return value is
  542   a pair, \a pair.first is package name and \a pair.second is the package
  543   requiring \a pair.first.
  544 
  545   \return packages missing in the ports tree
  546 */
  547 const list< pair<string, string> >& InstallTransaction::missing() const
  548 {
  549     return m_missingPackages;
  550 }
  551 
  552 
  553 /*!
  554   \return packages which were requested to be installed but are already
  555   installed
  556 */
  557 const list<string>& InstallTransaction::alreadyInstalledPackages() const
  558 {
  559     return m_alreadyInstalledPackages;
  560 }
  561 
  562 
  563 /*!
  564   \return the packages which were installed in this transaction
  565 */
  566 const list< pair<string, InstallTransaction::InstallInfo> >&
  567 InstallTransaction::installedPackages() const
  568 {
  569     return m_installedPackages;
  570 }
  571 
  572 
  573 /*!
  574   calculate dependendencies for this package
  575 */
  576 InstallTransaction::InstallResult
  577 InstallTransaction::calcDependencies( )
  578 {
  579     if ( m_packages.empty() ) {
  580         return NO_PACKAGE_GIVEN;
  581     }
  582 
  583     bool validPackages = false;
  584     list< pair<string, const Package*> >::iterator it = m_packages.begin();
  585     for ( ; it != m_packages.end(); ++it ) {
  586         if ( it->second ) {
  587             validPackages = true;
  588         } else {
  589             // Note: moved here from calculateDependencies
  590             m_missingPackages.push_back( make_pair( it->first, string("") ) );
  591         }
  592     }
  593     if ( !validPackages ) {
  594         return PACKAGE_NOT_FOUND;
  595     }
  596 
  597     if ( !calculateDependencies() ) {
  598         return CYCLIC_DEPEND;
  599     }
  600     return SUCCESS;
  601 }
  602 
  603 
  604 /*
  605  * getPkgDest assumes that you're in the build directory already
  606  */
  607 string InstallTransaction::getPkgmkSetting(const string& setting)
  608 {
  609     string value = "";
  610     value = getPkgmkSettingFromFile(setting, "/etc/pkgmk.conf");
  611     if (value.size() == 0) {
  612         value = getPkgmkSettingFromFile(setting, "/usr/bin/pkgmk");
  613     }
  614 
  615     return value;
  616 }
  617 
  618 string InstallTransaction::getPkgmkSettingFromFile(const string& setting, const string& fileName)
  619 {
  620     FILE* fp = fopen(fileName.c_str(), "r");
  621     if (!fp)
  622         return "";
  623 
  624     string candidate;
  625     string s;
  626     char line[256];
  627     while (fgets(line, 256, fp)) {
  628         s = StringHelper::stripWhiteSpace(line);
  629         if (StringHelper::startsWith(s, setting + "=")) {
  630             candidate = s;
  631         }
  632     }
  633     fclose(fp);
  634 
  635     string value = "";
  636     if (candidate.length() > 0) {
  637         string cmd = "eval " + candidate + " && echo $" + setting;
  638         FILE* p = popen(cmd.c_str(), "r");
  639         if (p) {
  640             fgets(line, 256, p);
  641             value = StringHelper::stripWhiteSpace(line);
  642             fclose(p);
  643         }
  644     }
  645 
  646     return value;
  647 }
  648 
  649 const list<string>& InstallTransaction::ignoredPackages() const
  650 {
  651     return m_ignoredPackages;
  652 }
  653 
  654 string InstallTransaction::getPkgmkPackageDir()
  655 {
  656     return getPkgmkSetting("PKGMK_PACKAGE_DIR");
  657 }
  658 
  659 string InstallTransaction::getPkgmkCompressionMode()
  660 {
  661     string value = getPkgmkSetting("PKGMK_COMPRESSION_MODE");
  662 
  663     return value.size() ? value : "gz";
  664 }

Generated by cgit