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

Generated by cgit