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 = getPkgDest();
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.gz";
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::getPkgDest() const
608 {
609 string pkgdest = "";
610 pkgdest = getPkgDestFromFile("/etc/pkgmk.conf");
611 if (pkgdest.size() == 0) {
612 pkgdest = getPkgDestFromFile("/usr/bin/pkgmk");
613 }
614
615 m_pkgDest = pkgdest;
616 return pkgdest;
617 }
618
619 string InstallTransaction::getPkgDestFromFile(const string& fileName)
620 {
621 FILE* fp = fopen(fileName.c_str(), "r");
622 if (!fp)
623 return "";
624
625 string candidate;
626 string s;
627 char line[256];
628 while (fgets(line, 256, fp)) {
629 s = StringHelper::stripWhiteSpace(line);
630 if (StringHelper::startsWith(s, "PKGMK_PACKAGE_DIR=")) {
631 candidate = s;
632 }
633 }
634 fclose(fp);
635
636 string pkgdest = "";
637 if (candidate.length() > 0) {
638 string cmd = "eval " + candidate + " && echo $PKGMK_PACKAGE_DIR";
639 FILE* p = popen(cmd.c_str(), "r");
640 if (p) {
641 fgets(line, 256, p);
642 pkgdest = StringHelper::stripWhiteSpace(line);
643 fclose(p);
644 }
645 }
646
647 return pkgdest;
648 }
649
650 const list<string>& InstallTransaction::ignoredPackages() const
651 {
652 return m_ignoredPackages;
653 }
654
655 string InstallTransaction::pkgDest() const
656 {
657 return m_pkgDest;
658 }
|