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 }
|