1<?php 2/** 3 * PEAR_Installer 4 * 5 * PHP versions 4 and 5 6 * 7 * @category pear 8 * @package PEAR 9 * @author Stig Bakken <ssb@php.net> 10 * @author Tomas V.V. Cox <cox@idecnet.com> 11 * @author Martin Jansen <mj@php.net> 12 * @author Greg Beaver <cellog@php.net> 13 * @copyright 1997-2009 The Authors 14 * @license http://opensource.org/licenses/bsd-license.php New BSD License 15 * @link http://pear.php.net/package/PEAR 16 * @since File available since Release 0.1 17 */ 18 19/** 20 * Used for installation groups in package.xml 2.0 and platform exceptions 21 */ 22require_once 'OS/Guess.php'; 23require_once 'PEAR/Downloader.php'; 24 25define('PEAR_INSTALLER_NOBINARY', -240); 26/** 27 * Administration class used to install PEAR packages and maintain the 28 * installed package database. 29 * 30 * @category pear 31 * @package PEAR 32 * @author Stig Bakken <ssb@php.net> 33 * @author Tomas V.V. Cox <cox@idecnet.com> 34 * @author Martin Jansen <mj@php.net> 35 * @author Greg Beaver <cellog@php.net> 36 * @copyright 1997-2009 The Authors 37 * @license http://opensource.org/licenses/bsd-license.php New BSD License 38 * @version Release: @package_version@ 39 * @link http://pear.php.net/package/PEAR 40 * @since Class available since Release 0.1 41 */ 42class PEAR_Installer extends PEAR_Downloader 43{ 44 // {{{ properties 45 46 /** name of the package directory, for example Foo-1.0 47 * @var string 48 */ 49 var $pkgdir; 50 51 /** directory where PHP code files go 52 * @var string 53 */ 54 var $phpdir; 55 56 /** directory where PHP extension files go 57 * @var string 58 */ 59 var $extdir; 60 61 /** directory where documentation goes 62 * @var string 63 */ 64 var $docdir; 65 66 /** installation root directory (ala PHP's INSTALL_ROOT or 67 * automake's DESTDIR 68 * @var string 69 */ 70 var $installroot = ''; 71 72 /** debug level 73 * @var int 74 */ 75 var $debug = 1; 76 77 /** temporary directory 78 * @var string 79 */ 80 var $tmpdir; 81 82 /** 83 * PEAR_Registry object used by the installer 84 * @var PEAR_Registry 85 */ 86 var $registry; 87 88 /** 89 * array of PEAR_Downloader_Packages 90 * @var array 91 */ 92 var $_downloadedPackages; 93 94 /** List of file transactions queued for an install/upgrade/uninstall. 95 * 96 * Format: 97 * array( 98 * 0 => array("rename => array("from-file", "to-file")), 99 * 1 => array("delete" => array("file-to-delete")), 100 * ... 101 * ) 102 * 103 * @var array 104 */ 105 var $file_operations = array(); 106 107 // }}} 108 109 // {{{ constructor 110 111 /** 112 * PEAR_Installer constructor. 113 * 114 * @param object $ui user interface object (instance of PEAR_Frontend_*) 115 * 116 * @access public 117 */ 118 function __construct(&$ui) 119 { 120 parent::__construct($ui, array(), null); 121 $this->setFrontendObject($ui); 122 $this->debug = $this->config->get('verbose'); 123 } 124 125 function setOptions($options) 126 { 127 $this->_options = $options; 128 } 129 130 function setConfig(&$config) 131 { 132 $this->config = &$config; 133 $this->_registry = &$config->getRegistry(); 134 } 135 136 // }}} 137 138 function _removeBackups($files) 139 { 140 foreach ($files as $path) { 141 $this->addFileOperation('removebackup', array($path)); 142 } 143 } 144 145 // {{{ _deletePackageFiles() 146 147 /** 148 * Delete a package's installed files, does not remove empty directories. 149 * 150 * @param string package name 151 * @param string channel name 152 * @param bool if true, then files are backed up first 153 * @return bool TRUE on success, or a PEAR error on failure 154 * @access protected 155 */ 156 function _deletePackageFiles($package, $channel = false, $backup = false) 157 { 158 if (!$channel) { 159 $channel = 'pear.php.net'; 160 } 161 162 if (!strlen($package)) { 163 return $this->raiseError("No package to uninstall given"); 164 } 165 166 if (strtolower($package) == 'pear' && $channel == 'pear.php.net') { 167 // to avoid race conditions, include all possible needed files 168 require_once 'PEAR/Task/Common.php'; 169 require_once 'PEAR/Task/Replace.php'; 170 require_once 'PEAR/Task/Unixeol.php'; 171 require_once 'PEAR/Task/Windowseol.php'; 172 require_once 'PEAR/PackageFile/v1.php'; 173 require_once 'PEAR/PackageFile/v2.php'; 174 require_once 'PEAR/PackageFile/Generator/v1.php'; 175 require_once 'PEAR/PackageFile/Generator/v2.php'; 176 } 177 178 $filelist = $this->_registry->packageInfo($package, 'filelist', $channel); 179 if ($filelist == null) { 180 return $this->raiseError("$channel/$package not installed"); 181 } 182 183 $ret = array(); 184 foreach ($filelist as $file => $props) { 185 if (empty($props['installed_as'])) { 186 continue; 187 } 188 189 $path = $props['installed_as']; 190 if ($backup) { 191 $this->addFileOperation('backup', array($path)); 192 $ret[] = $path; 193 } 194 195 $this->addFileOperation('delete', array($path)); 196 } 197 198 if ($backup) { 199 return $ret; 200 } 201 202 return true; 203 } 204 205 // }}} 206 // {{{ _installFile() 207 208 /** 209 * @param string filename 210 * @param array attributes from <file> tag in package.xml 211 * @param string path to install the file in 212 * @param array options from command-line 213 * @access private 214 */ 215 function _installFile($file, $atts, $tmp_path, $options) 216 { 217 // {{{ return if this file is meant for another platform 218 static $os; 219 if (!isset($this->_registry)) { 220 $this->_registry = &$this->config->getRegistry(); 221 } 222 223 if (isset($atts['platform'])) { 224 if (empty($os)) { 225 $os = new OS_Guess(); 226 } 227 228 if (strlen($atts['platform']) && $atts['platform']{0} == '!') { 229 $negate = true; 230 $platform = substr($atts['platform'], 1); 231 } else { 232 $negate = false; 233 $platform = $atts['platform']; 234 } 235 236 if ((bool) $os->matchSignature($platform) === $negate) { 237 $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")"); 238 return PEAR_INSTALLER_SKIPPED; 239 } 240 } 241 // }}} 242 243 $channel = $this->pkginfo->getChannel(); 244 // {{{ assemble the destination paths 245 switch ($atts['role']) { 246 case 'src': 247 case 'extsrc': 248 $this->source_files++; 249 return; 250 case 'doc': 251 case 'data': 252 case 'test': 253 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . 254 DIRECTORY_SEPARATOR . $this->pkginfo->getPackage(); 255 unset($atts['baseinstalldir']); 256 break; 257 case 'ext': 258 case 'php': 259 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel); 260 break; 261 case 'script': 262 $dest_dir = $this->config->get('bin_dir', null, $channel); 263 break; 264 default: 265 return $this->raiseError("Invalid role `$atts[role]' for file $file"); 266 } 267 268 $save_destdir = $dest_dir; 269 if (!empty($atts['baseinstalldir'])) { 270 $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; 271 } 272 273 if (dirname($file) != '.' && empty($atts['install-as'])) { 274 $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); 275 } 276 277 if (empty($atts['install-as'])) { 278 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); 279 } else { 280 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; 281 } 282 $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; 283 284 // Clean up the DIRECTORY_SEPARATOR mess 285 $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; 286 list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), 287 array(DIRECTORY_SEPARATOR, 288 DIRECTORY_SEPARATOR, 289 DIRECTORY_SEPARATOR), 290 array($dest_file, $orig_file)); 291 $final_dest_file = $installed_as = $dest_file; 292 if (isset($this->_options['packagingroot'])) { 293 $installedas_dest_dir = dirname($final_dest_file); 294 $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); 295 $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']); 296 } else { 297 $installedas_dest_dir = dirname($final_dest_file); 298 $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); 299 } 300 301 $dest_dir = dirname($final_dest_file); 302 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); 303 if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { 304 return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); 305 } 306 // }}} 307 308 if (empty($this->_options['register-only']) && 309 (!file_exists($dest_dir) || !is_dir($dest_dir))) { 310 if (!$this->mkDirHier($dest_dir)) { 311 return $this->raiseError("failed to mkdir $dest_dir", 312 PEAR_INSTALLER_FAILED); 313 } 314 $this->log(3, "+ mkdir $dest_dir"); 315 } 316 317 // pretty much nothing happens if we are only registering the install 318 if (empty($this->_options['register-only'])) { 319 if (empty($atts['replacements'])) { 320 if (!file_exists($orig_file)) { 321 return $this->raiseError("file $orig_file does not exist", 322 PEAR_INSTALLER_FAILED); 323 } 324 325 if (!@copy($orig_file, $dest_file)) { 326 return $this->raiseError( 327 "failed to write $dest_file: " . error_get_last()["message"], 328 PEAR_INSTALLER_FAILED); 329 } 330 331 $this->log(3, "+ cp $orig_file $dest_file"); 332 if (isset($atts['md5sum'])) { 333 $md5sum = md5_file($dest_file); 334 } 335 } else { 336 // {{{ file with replacements 337 if (!file_exists($orig_file)) { 338 return $this->raiseError("file does not exist", 339 PEAR_INSTALLER_FAILED); 340 } 341 342 $contents = file_get_contents($orig_file); 343 if ($contents === false) { 344 $contents = ''; 345 } 346 347 if (isset($atts['md5sum'])) { 348 $md5sum = md5($contents); 349 } 350 351 $subst_from = $subst_to = array(); 352 foreach ($atts['replacements'] as $a) { 353 $to = ''; 354 if ($a['type'] == 'php-const') { 355 if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) { 356 eval("\$to = $a[to];"); 357 } else { 358 if (!isset($options['soft'])) { 359 $this->log(0, "invalid php-const replacement: $a[to]"); 360 } 361 continue; 362 } 363 } elseif ($a['type'] == 'pear-config') { 364 if ($a['to'] == 'master_server') { 365 $chan = $this->_registry->getChannel($channel); 366 if (!PEAR::isError($chan)) { 367 $to = $chan->getServer(); 368 } else { 369 $to = $this->config->get($a['to'], null, $channel); 370 } 371 } else { 372 $to = $this->config->get($a['to'], null, $channel); 373 } 374 if (is_null($to)) { 375 if (!isset($options['soft'])) { 376 $this->log(0, "invalid pear-config replacement: $a[to]"); 377 } 378 continue; 379 } 380 } elseif ($a['type'] == 'package-info') { 381 if ($t = $this->pkginfo->packageInfo($a['to'])) { 382 $to = $t; 383 } else { 384 if (!isset($options['soft'])) { 385 $this->log(0, "invalid package-info replacement: $a[to]"); 386 } 387 continue; 388 } 389 } 390 if (!is_null($to)) { 391 $subst_from[] = $a['from']; 392 $subst_to[] = $to; 393 } 394 } 395 396 $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file"); 397 if (sizeof($subst_from)) { 398 $contents = str_replace($subst_from, $subst_to, $contents); 399 } 400 401 $wp = @fopen($dest_file, "wb"); 402 if (!is_resource($wp)) { 403 return $this->raiseError( 404 "failed to create $dest_file: " . error_get_last()["message"], 405 PEAR_INSTALLER_FAILED); 406 } 407 408 if (@fwrite($wp, $contents) === false) { 409 return $this->raiseError( 410 "failed writing to $dest_file: " . error_get_last()["message"], 411 PEAR_INSTALLER_FAILED); 412 } 413 414 fclose($wp); 415 // }}} 416 } 417 418 // {{{ check the md5 419 if (isset($md5sum)) { 420 if (strtolower($md5sum) === strtolower($atts['md5sum'])) { 421 $this->log(2, "md5sum ok: $final_dest_file"); 422 } else { 423 if (empty($options['force'])) { 424 // delete the file 425 if (file_exists($dest_file)) { 426 unlink($dest_file); 427 } 428 429 if (!isset($options['ignore-errors'])) { 430 return $this->raiseError("bad md5sum for file $final_dest_file", 431 PEAR_INSTALLER_FAILED); 432 } 433 434 if (!isset($options['soft'])) { 435 $this->log(0, "warning : bad md5sum for file $final_dest_file"); 436 } 437 } else { 438 if (!isset($options['soft'])) { 439 $this->log(0, "warning : bad md5sum for file $final_dest_file"); 440 } 441 } 442 } 443 } 444 // }}} 445 // {{{ set file permissions 446 if (!OS_WINDOWS) { 447 if ($atts['role'] == 'script') { 448 $mode = 0777 & ~(int)octdec($this->config->get('umask')); 449 $this->log(3, "+ chmod +x $dest_file"); 450 } else { 451 $mode = 0666 & ~(int)octdec($this->config->get('umask')); 452 } 453 454 if ($atts['role'] != 'src') { 455 $this->addFileOperation("chmod", array($mode, $dest_file)); 456 if (!@chmod($dest_file, $mode)) { 457 if (!isset($options['soft'])) { 458 $this->log(0, "failed to change mode of $dest_file: " . 459 error_get_last()["message"]); 460 } 461 } 462 } 463 } 464 // }}} 465 466 if ($atts['role'] == 'src') { 467 rename($dest_file, $final_dest_file); 468 $this->log(2, "renamed source file $dest_file to $final_dest_file"); 469 } else { 470 $this->addFileOperation("rename", array($dest_file, $final_dest_file, 471 $atts['role'] == 'ext')); 472 } 473 } 474 475 // Store the full path where the file was installed for easy unistall 476 if ($atts['role'] != 'script') { 477 $loc = $this->config->get($atts['role'] . '_dir'); 478 } else { 479 $loc = $this->config->get('bin_dir'); 480 } 481 482 if ($atts['role'] != 'src') { 483 $this->addFileOperation("installed_as", array($file, $installed_as, 484 $loc, 485 dirname(substr($installedas_dest_file, strlen($loc))))); 486 } 487 488 //$this->log(2, "installed: $dest_file"); 489 return PEAR_INSTALLER_OK; 490 } 491 492 // }}} 493 // {{{ _installFile2() 494 495 /** 496 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 497 * @param string filename 498 * @param array attributes from <file> tag in package.xml 499 * @param string path to install the file in 500 * @param array options from command-line 501 * @access private 502 */ 503 function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options) 504 { 505 $atts = $real_atts; 506 if (!isset($this->_registry)) { 507 $this->_registry = &$this->config->getRegistry(); 508 } 509 510 $channel = $pkg->getChannel(); 511 // {{{ assemble the destination paths 512 if (!in_array($atts['attribs']['role'], 513 PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) { 514 return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . 515 "' for file $file"); 516 } 517 518 $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config); 519 $err = $role->setup($this, $pkg, $atts['attribs'], $file); 520 if (PEAR::isError($err)) { 521 return $err; 522 } 523 524 if (!$role->isInstallable()) { 525 return; 526 } 527 528 $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path); 529 if (PEAR::isError($info)) { 530 return $info; 531 } 532 533 list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info; 534 if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) { 535 return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED); 536 } 537 538 $final_dest_file = $installed_as = $dest_file; 539 if (isset($this->_options['packagingroot'])) { 540 $final_dest_file = $this->_prependPath($final_dest_file, 541 $this->_options['packagingroot']); 542 } 543 544 $dest_dir = dirname($final_dest_file); 545 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file); 546 // }}} 547 548 if (empty($this->_options['register-only'])) { 549 if (!file_exists($dest_dir) || !is_dir($dest_dir)) { 550 if (!$this->mkDirHier($dest_dir)) { 551 return $this->raiseError("failed to mkdir $dest_dir", 552 PEAR_INSTALLER_FAILED); 553 } 554 $this->log(3, "+ mkdir $dest_dir"); 555 } 556 } 557 558 $attribs = $atts['attribs']; 559 unset($atts['attribs']); 560 // pretty much nothing happens if we are only registering the install 561 if (empty($this->_options['register-only'])) { 562 if (!count($atts)) { // no tasks 563 if (!file_exists($orig_file)) { 564 return $this->raiseError("file $orig_file does not exist", 565 PEAR_INSTALLER_FAILED); 566 } 567 568 if (!@copy($orig_file, $dest_file)) { 569 return $this->raiseError( 570 "failed to write $dest_file: " . error_get_last()["message"], 571 PEAR_INSTALLER_FAILED); 572 } 573 574 $this->log(3, "+ cp $orig_file $dest_file"); 575 if (isset($attribs['md5sum'])) { 576 $md5sum = md5_file($dest_file); 577 } 578 } else { // file with tasks 579 if (!file_exists($orig_file)) { 580 return $this->raiseError("file $orig_file does not exist", 581 PEAR_INSTALLER_FAILED); 582 } 583 584 $contents = file_get_contents($orig_file); 585 if ($contents === false) { 586 $contents = ''; 587 } 588 589 if (isset($attribs['md5sum'])) { 590 $md5sum = md5($contents); 591 } 592 593 foreach ($atts as $tag => $raw) { 594 $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag); 595 $task = "PEAR_Task_$tag"; 596 $task = new $task($this->config, $this, PEAR_TASK_INSTALL); 597 if (!$task->isScript()) { // scripts are only handled after installation 598 $task->init($raw, $attribs, $pkg->getLastInstalledVersion()); 599 $res = $task->startSession($pkg, $contents, $final_dest_file); 600 if ($res === false) { 601 continue; // skip this file 602 } 603 604 if (PEAR::isError($res)) { 605 return $res; 606 } 607 608 $contents = $res; // save changes 609 } 610 611 $wp = @fopen($dest_file, "wb"); 612 if (!is_resource($wp)) { 613 return $this->raiseError( 614 "failed to create $dest_file: " . error_get_last()["message"], 615 PEAR_INSTALLER_FAILED); 616 } 617 618 if (fwrite($wp, $contents) === false) { 619 return $this->raiseError( 620 "failed writing to $dest_file: " . error_get_last()["message"], 621 PEAR_INSTALLER_FAILED); 622 } 623 624 fclose($wp); 625 } 626 } 627 628 // {{{ check the md5 629 if (isset($md5sum)) { 630 // Make sure the original md5 sum matches with expected 631 if (strtolower($md5sum) === strtolower($attribs['md5sum'])) { 632 $this->log(2, "md5sum ok: $final_dest_file"); 633 634 if (isset($contents)) { 635 // set md5 sum based on $content in case any tasks were run. 636 $real_atts['attribs']['md5sum'] = md5($contents); 637 } 638 } else { 639 if (empty($options['force'])) { 640 // delete the file 641 if (file_exists($dest_file)) { 642 unlink($dest_file); 643 } 644 645 if (!isset($options['ignore-errors'])) { 646 return $this->raiseError("bad md5sum for file $final_dest_file", 647 PEAR_INSTALLER_FAILED); 648 } 649 650 if (!isset($options['soft'])) { 651 $this->log(0, "warning : bad md5sum for file $final_dest_file"); 652 } 653 } else { 654 if (!isset($options['soft'])) { 655 $this->log(0, "warning : bad md5sum for file $final_dest_file"); 656 } 657 } 658 } 659 } else { 660 $real_atts['attribs']['md5sum'] = md5_file($dest_file); 661 } 662 663 // }}} 664 // {{{ set file permissions 665 if (!OS_WINDOWS) { 666 if ($role->isExecutable()) { 667 $mode = 0777 & ~(int)octdec($this->config->get('umask')); 668 $this->log(3, "+ chmod +x $dest_file"); 669 } else { 670 $mode = 0666 & ~(int)octdec($this->config->get('umask')); 671 } 672 673 if ($attribs['role'] != 'src') { 674 $this->addFileOperation("chmod", array($mode, $dest_file)); 675 if (!@chmod($dest_file, $mode)) { 676 if (!isset($options['soft'])) { 677 $this->log(0, "failed to change mode of $dest_file: " . 678 error_get_last()["message"]); 679 } 680 } 681 } 682 } 683 // }}} 684 685 if ($attribs['role'] == 'src') { 686 rename($dest_file, $final_dest_file); 687 $this->log(2, "renamed source file $dest_file to $final_dest_file"); 688 } else { 689 $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension())); 690 } 691 } 692 693 // Store the full path where the file was installed for easy uninstall 694 if ($attribs['role'] != 'src') { 695 $loc = $this->config->get($role->getLocationConfig(), null, $channel); 696 $this->addFileOperation('installed_as', array($file, $installed_as, 697 $loc, 698 dirname(substr($installed_as, strlen($loc))))); 699 } 700 701 //$this->log(2, "installed: $dest_file"); 702 return PEAR_INSTALLER_OK; 703 } 704 705 // }}} 706 // {{{ addFileOperation() 707 708 /** 709 * Add a file operation to the current file transaction. 710 * 711 * @see startFileTransaction() 712 * @param string $type This can be one of: 713 * - rename: rename a file ($data has 3 values) 714 * - backup: backup an existing file ($data has 1 value) 715 * - removebackup: clean up backups created during install ($data has 1 value) 716 * - chmod: change permissions on a file ($data has 2 values) 717 * - delete: delete a file ($data has 1 value) 718 * - rmdir: delete a directory if empty ($data has 1 value) 719 * - installed_as: mark a file as installed ($data has 4 values). 720 * @param array $data For all file operations, this array must contain the 721 * full path to the file or directory that is being operated on. For 722 * the rename command, the first parameter must be the file to rename, 723 * the second its new name, the third whether this is a PHP extension. 724 * 725 * The installed_as operation contains 4 elements in this order: 726 * 1. Filename as listed in the filelist element from package.xml 727 * 2. Full path to the installed file 728 * 3. Full path from the php_dir configuration variable used in this 729 * installation 730 * 4. Relative path from the php_dir that this file is installed in 731 */ 732 function addFileOperation($type, $data) 733 { 734 if (!is_array($data)) { 735 return $this->raiseError('Internal Error: $data in addFileOperation' 736 . ' must be an array, was ' . gettype($data)); 737 } 738 739 if ($type == 'chmod') { 740 $octmode = decoct($data[0]); 741 $this->log(3, "adding to transaction: $type $octmode $data[1]"); 742 } else { 743 $this->log(3, "adding to transaction: $type " . implode(" ", $data)); 744 } 745 $this->file_operations[] = array($type, $data); 746 } 747 748 // }}} 749 // {{{ startFileTransaction() 750 751 function startFileTransaction($rollback_in_case = false) 752 { 753 if (count($this->file_operations) && $rollback_in_case) { 754 $this->rollbackFileTransaction(); 755 } 756 $this->file_operations = array(); 757 } 758 759 // }}} 760 // {{{ commitFileTransaction() 761 762 function commitFileTransaction() 763 { 764 // {{{ first, check permissions and such manually 765 $errors = array(); 766 foreach ($this->file_operations as $key => $tr) { 767 list($type, $data) = $tr; 768 switch ($type) { 769 case 'rename': 770 if (!file_exists($data[0])) { 771 $errors[] = "cannot rename file $data[0], doesn't exist"; 772 } 773 774 // check that dest dir. is writable 775 if (!is_writable(dirname($data[1]))) { 776 $errors[] = "permission denied ($type): $data[1]"; 777 } 778 break; 779 case 'chmod': 780 // check that file is writable 781 if (!is_writable($data[1])) { 782 $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]); 783 } 784 break; 785 case 'delete': 786 if (!file_exists($data[0])) { 787 $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted"); 788 } 789 // check that directory is writable 790 if (file_exists($data[0])) { 791 if (!is_writable(dirname($data[0]))) { 792 $errors[] = "permission denied ($type): $data[0]"; 793 } else { 794 // make sure the file to be deleted can be opened for writing 795 $fp = false; 796 if (!is_dir($data[0]) && 797 (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) { 798 $errors[] = "permission denied ($type): $data[0]"; 799 } elseif ($fp) { 800 fclose($fp); 801 } 802 } 803 804 /* Verify we are not deleting a file owned by another package 805 * This can happen when a file moves from package A to B in 806 * an upgrade ala http://pear.php.net/17986 807 */ 808 $info = array( 809 'package' => strtolower($this->pkginfo->getName()), 810 'channel' => strtolower($this->pkginfo->getChannel()), 811 ); 812 $result = $this->_registry->checkFileMap($data[0], $info, '1.1'); 813 if (is_array($result)) { 814 $res = array_diff($result, $info); 815 if (!empty($res)) { 816 $new = $this->_registry->getPackage($result[1], $result[0]); 817 $this->file_operations[$key] = false; 818 $pkginfoName = $this->pkginfo->getName(); 819 $newChannel = $new->getChannel(); 820 $newPackage = $new->getName(); 821 $this->log(3, "file $data[0] was scheduled for removal from $pkginfoName but is owned by $newChannel/$newPackage, removal has been cancelled."); 822 } 823 } 824 } 825 break; 826 } 827 828 } 829 // }}} 830 831 $n = count($this->file_operations); 832 $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName()); 833 834 $m = count($errors); 835 if ($m > 0) { 836 foreach ($errors as $error) { 837 if (!isset($this->_options['soft'])) { 838 $this->log(1, $error); 839 } 840 } 841 842 if (!isset($this->_options['ignore-errors'])) { 843 return false; 844 } 845 } 846 847 $this->_dirtree = array(); 848 // {{{ really commit the transaction 849 foreach ($this->file_operations as $i => $tr) { 850 if (!$tr) { 851 // support removal of non-existing backups 852 continue; 853 } 854 855 list($type, $data) = $tr; 856 switch ($type) { 857 case 'backup': 858 if (!file_exists($data[0])) { 859 $this->file_operations[$i] = false; 860 break; 861 } 862 863 if (!@copy($data[0], $data[0] . '.bak')) { 864 $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . 865 '.bak ' . error_get_last()["message"]); 866 return false; 867 } 868 $this->log(3, "+ backup $data[0] to $data[0].bak"); 869 break; 870 case 'removebackup': 871 if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { 872 unlink($data[0] . '.bak'); 873 $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); 874 } 875 break; 876 case 'rename': 877 $test = file_exists($data[1]) ? @unlink($data[1]) : null; 878 if (!$test && file_exists($data[1])) { 879 if ($data[2]) { 880 $extra = ', this extension must be installed manually. Rename to "' . 881 basename($data[1]) . '"'; 882 } else { 883 $extra = ''; 884 } 885 886 if (!isset($this->_options['soft'])) { 887 $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . 888 $data[0] . $extra); 889 } 890 891 if (!isset($this->_options['ignore-errors'])) { 892 return false; 893 } 894 } 895 896 // permissions issues with rename - copy() is far superior 897 $perms = @fileperms($data[0]); 898 if (!@copy($data[0], $data[1])) { 899 $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . 900 ' ' . error_get_last()["message"]); 901 return false; 902 } 903 904 // copy over permissions, otherwise they are lost 905 @chmod($data[1], $perms); 906 @unlink($data[0]); 907 $this->log(3, "+ mv $data[0] $data[1]"); 908 break; 909 case 'chmod': 910 if (!@chmod($data[1], $data[0])) { 911 $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . 912 decoct($data[0]) . ' ' . error_get_last()["message"]); 913 return false; 914 } 915 916 $octmode = decoct($data[0]); 917 $this->log(3, "+ chmod $octmode $data[1]"); 918 break; 919 case 'delete': 920 if (file_exists($data[0])) { 921 if (!@unlink($data[0])) { 922 $this->log(1, 'Could not delete ' . $data[0] . ' ' . 923 error_get_last()["message"]); 924 return false; 925 } 926 $this->log(3, "+ rm $data[0]"); 927 } 928 break; 929 case 'rmdir': 930 if (file_exists($data[0])) { 931 do { 932 $testme = opendir($data[0]); 933 while (false !== ($entry = readdir($testme))) { 934 if ($entry == '.' || $entry == '..') { 935 continue; 936 } 937 closedir($testme); 938 break 2; // this directory is not empty and can't be 939 // deleted 940 } 941 942 closedir($testme); 943 if (!@rmdir($data[0])) { 944 $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . 945 error_get_last()["message"]); 946 return false; 947 } 948 $this->log(3, "+ rmdir $data[0]"); 949 } while (false); 950 } 951 break; 952 case 'installed_as': 953 $this->pkginfo->setInstalledAs($data[0], $data[1]); 954 if (!isset($this->_dirtree[dirname($data[1])])) { 955 $this->_dirtree[dirname($data[1])] = true; 956 $this->pkginfo->setDirtree(dirname($data[1])); 957 958 while(!empty($data[3]) && dirname($data[3]) != $data[3] && 959 $data[3] != '/' && $data[3] != '\\') { 960 $this->pkginfo->setDirtree($pp = 961 $this->_prependPath($data[3], $data[2])); 962 $this->_dirtree[$pp] = true; 963 $data[3] = dirname($data[3]); 964 } 965 } 966 break; 967 } 968 } 969 // }}} 970 $this->log(2, "successfully committed $n file operations"); 971 $this->file_operations = array(); 972 return true; 973 } 974 975 // }}} 976 // {{{ rollbackFileTransaction() 977 978 function rollbackFileTransaction() 979 { 980 $n = count($this->file_operations); 981 $this->log(2, "rolling back $n file operations"); 982 foreach ($this->file_operations as $tr) { 983 list($type, $data) = $tr; 984 switch ($type) { 985 case 'backup': 986 if (file_exists($data[0] . '.bak')) { 987 if (file_exists($data[0] && is_writable($data[0]))) { 988 unlink($data[0]); 989 } 990 @copy($data[0] . '.bak', $data[0]); 991 $this->log(3, "+ restore $data[0] from $data[0].bak"); 992 } 993 break; 994 case 'removebackup': 995 if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) { 996 unlink($data[0] . '.bak'); 997 $this->log(3, "+ rm backup of $data[0] ($data[0].bak)"); 998 } 999 break; 1000 case 'rename': 1001 @unlink($data[0]); 1002 $this->log(3, "+ rm $data[0]"); 1003 break; 1004 case 'mkdir': 1005 @rmdir($data[0]); 1006 $this->log(3, "+ rmdir $data[0]"); 1007 break; 1008 case 'chmod': 1009 break; 1010 case 'delete': 1011 break; 1012 case 'installed_as': 1013 $this->pkginfo->setInstalledAs($data[0], false); 1014 break; 1015 } 1016 } 1017 $this->pkginfo->resetDirtree(); 1018 $this->file_operations = array(); 1019 } 1020 1021 // }}} 1022 // {{{ mkDirHier($dir) 1023 1024 function mkDirHier($dir) 1025 { 1026 $this->addFileOperation('mkdir', array($dir)); 1027 return parent::mkDirHier($dir); 1028 } 1029 1030 // }}} 1031 // {{{ _parsePackageXml() 1032 1033 function _parsePackageXml(&$descfile) 1034 { 1035 // Parse xml file ----------------------------------------------- 1036 $pkg = new PEAR_PackageFile($this->config, $this->debug); 1037 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 1038 $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING); 1039 PEAR::staticPopErrorHandling(); 1040 if (PEAR::isError($p)) { 1041 if (is_array($p->getUserInfo())) { 1042 foreach ($p->getUserInfo() as $err) { 1043 $loglevel = $err['level'] == 'error' ? 0 : 1; 1044 if (!isset($this->_options['soft'])) { 1045 $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']); 1046 } 1047 } 1048 } 1049 return $this->raiseError('Installation failed: invalid package file'); 1050 } 1051 1052 $descfile = $p->getPackageFile(); 1053 return $p; 1054 } 1055 1056 // }}} 1057 /** 1058 * Set the list of PEAR_Downloader_Package objects to allow more sane 1059 * dependency validation 1060 * @param array 1061 */ 1062 function setDownloadedPackages(&$pkgs) 1063 { 1064 PEAR::pushErrorHandling(PEAR_ERROR_RETURN); 1065 $err = $this->analyzeDependencies($pkgs); 1066 PEAR::popErrorHandling(); 1067 if (PEAR::isError($err)) { 1068 return $err; 1069 } 1070 $this->_downloadedPackages = &$pkgs; 1071 } 1072 1073 /** 1074 * Set the list of PEAR_Downloader_Package objects to allow more sane 1075 * dependency validation 1076 * @param array 1077 */ 1078 function setUninstallPackages(&$pkgs) 1079 { 1080 $this->_downloadedPackages = &$pkgs; 1081 } 1082 1083 function getInstallPackages() 1084 { 1085 return $this->_downloadedPackages; 1086 } 1087 1088 // {{{ install() 1089 1090 /** 1091 * Installs the files within the package file specified. 1092 * 1093 * @param string|PEAR_Downloader_Package $pkgfile path to the package file, 1094 * or a pre-initialized packagefile object 1095 * @param array $options 1096 * recognized options: 1097 * - installroot : optional prefix directory for installation 1098 * - force : force installation 1099 * - register-only : update registry but don't install files 1100 * - upgrade : upgrade existing install 1101 * - soft : fail silently 1102 * - nodeps : ignore dependency conflicts/missing dependencies 1103 * - alldeps : install all dependencies 1104 * - onlyreqdeps : install only required dependencies 1105 * 1106 * @return array|PEAR_Error package info if successful 1107 */ 1108 function install($pkgfile, $options = array()) 1109 { 1110 $this->_options = $options; 1111 $this->_registry = &$this->config->getRegistry(); 1112 if (is_object($pkgfile)) { 1113 $dlpkg = &$pkgfile; 1114 $pkg = $pkgfile->getPackageFile(); 1115 $pkgfile = $pkg->getArchiveFile(); 1116 $descfile = $pkg->getPackageFile(); 1117 } else { 1118 $descfile = $pkgfile; 1119 $pkg = $this->_parsePackageXml($descfile); 1120 if (PEAR::isError($pkg)) { 1121 return $pkg; 1122 } 1123 } 1124 1125 $tmpdir = dirname($descfile); 1126 if (realpath($descfile) != realpath($pkgfile)) { 1127 // Use the temp_dir since $descfile can contain the download dir path 1128 $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net'); 1129 $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"'); 1130 1131 $tar = new Archive_Tar($pkgfile); 1132 if (!$tar->extract($tmpdir)) { 1133 return $this->raiseError("unable to unpack $pkgfile"); 1134 } 1135 } 1136 1137 $pkgname = $pkg->getName(); 1138 $channel = $pkg->getChannel(); 1139 1140 if (isset($options['installroot'])) { 1141 $this->config->setInstallRoot($options['installroot']); 1142 $this->_registry = &$this->config->getRegistry(); 1143 $installregistry = &$this->_registry; 1144 $this->installroot = ''; // all done automagically now 1145 $php_dir = $this->config->get('php_dir', null, $channel); 1146 } else { 1147 $this->config->setInstallRoot(false); 1148 $this->_registry = &$this->config->getRegistry(); 1149 if (isset($this->_options['packagingroot'])) { 1150 $regdir = $this->_prependPath( 1151 $this->config->get('php_dir', null, 'pear.php.net'), 1152 $this->_options['packagingroot']); 1153 1154 $metadata_dir = $this->config->get('metadata_dir', null, 'pear.php.net'); 1155 if ($metadata_dir) { 1156 $metadata_dir = $this->_prependPath( 1157 $metadata_dir, 1158 $this->_options['packagingroot']); 1159 } 1160 $packrootphp_dir = $this->_prependPath( 1161 $this->config->get('php_dir', null, $channel), 1162 $this->_options['packagingroot']); 1163 1164 $installregistry = new PEAR_Registry($regdir, false, false, $metadata_dir); 1165 if (!$installregistry->channelExists($channel, true)) { 1166 // we need to fake a channel-discover of this channel 1167 $chanobj = $this->_registry->getChannel($channel, true); 1168 $installregistry->addChannel($chanobj); 1169 } 1170 $php_dir = $packrootphp_dir; 1171 } else { 1172 $installregistry = &$this->_registry; 1173 $php_dir = $this->config->get('php_dir', null, $channel); 1174 } 1175 $this->installroot = ''; 1176 } 1177 1178 // {{{ checks to do when not in "force" mode 1179 if (empty($options['force']) && 1180 (file_exists($this->config->get('php_dir')) && 1181 is_dir($this->config->get('php_dir')))) { 1182 $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname); 1183 $instfilelist = $pkg->getInstallationFileList(true); 1184 if (PEAR::isError($instfilelist)) { 1185 return $instfilelist; 1186 } 1187 1188 // ensure we have the most accurate registry 1189 $installregistry->flushFileMap(); 1190 $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1'); 1191 if (PEAR::isError($test)) { 1192 return $test; 1193 } 1194 1195 if (sizeof($test)) { 1196 $pkgs = $this->getInstallPackages(); 1197 $found = false; 1198 foreach ($pkgs as $param) { 1199 if ($pkg->isSubpackageOf($param)) { 1200 $found = true; 1201 break; 1202 } 1203 } 1204 1205 if ($found) { 1206 // subpackages can conflict with earlier versions of parent packages 1207 $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel()); 1208 $tmp = $test; 1209 foreach ($tmp as $file => $info) { 1210 if (is_array($info)) { 1211 if (strtolower($info[1]) == strtolower($param->getPackage()) && 1212 strtolower($info[0]) == strtolower($param->getChannel()) 1213 ) { 1214 if (isset($parentreg['filelist'][$file])) { 1215 unset($parentreg['filelist'][$file]); 1216 } else{ 1217 $pos = strpos($file, '/'); 1218 $basedir = substr($file, 0, $pos); 1219 $file2 = substr($file, $pos + 1); 1220 if (isset($parentreg['filelist'][$file2]['baseinstalldir']) 1221 && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir 1222 ) { 1223 unset($parentreg['filelist'][$file2]); 1224 } 1225 } 1226 1227 unset($test[$file]); 1228 } 1229 } else { 1230 if (strtolower($param->getChannel()) != 'pear.php.net') { 1231 continue; 1232 } 1233 1234 if (strtolower($info) == strtolower($param->getPackage())) { 1235 if (isset($parentreg['filelist'][$file])) { 1236 unset($parentreg['filelist'][$file]); 1237 } else{ 1238 $pos = strpos($file, '/'); 1239 $basedir = substr($file, 0, $pos); 1240 $file2 = substr($file, $pos + 1); 1241 if (isset($parentreg['filelist'][$file2]['baseinstalldir']) 1242 && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir 1243 ) { 1244 unset($parentreg['filelist'][$file2]); 1245 } 1246 } 1247 1248 unset($test[$file]); 1249 } 1250 } 1251 } 1252 1253 $pfk = new PEAR_PackageFile($this->config); 1254 $parentpkg = &$pfk->fromArray($parentreg); 1255 $installregistry->updatePackage2($parentpkg); 1256 } 1257 1258 if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) { 1259 $tmp = $test; 1260 foreach ($tmp as $file => $info) { 1261 if (is_string($info)) { 1262 // pear.php.net packages are always stored as strings 1263 if (strtolower($info) == strtolower($param->getPackage())) { 1264 // upgrading existing package 1265 unset($test[$file]); 1266 } 1267 } 1268 } 1269 } 1270 1271 if (count($test)) { 1272 $msg = "$channel/$pkgname: conflicting files found:\n"; 1273 $longest = max(array_map("strlen", array_keys($test))); 1274 $fmt = "%${longest}s (%s)\n"; 1275 foreach ($test as $file => $info) { 1276 if (!is_array($info)) { 1277 $info = array('pear.php.net', $info); 1278 } 1279 $info = $info[0] . '/' . $info[1]; 1280 $msg .= sprintf($fmt, $file, $info); 1281 } 1282 1283 if (!isset($options['ignore-errors'])) { 1284 return $this->raiseError($msg); 1285 } 1286 1287 if (!isset($options['soft'])) { 1288 $this->log(0, "WARNING: $msg"); 1289 } 1290 } 1291 } 1292 } 1293 // }}} 1294 1295 $this->startFileTransaction(); 1296 1297 $usechannel = $channel; 1298 if ($channel == 'pecl.php.net') { 1299 $test = $installregistry->packageExists($pkgname, $channel); 1300 if (!$test) { 1301 $test = $installregistry->packageExists($pkgname, 'pear.php.net'); 1302 $usechannel = 'pear.php.net'; 1303 } 1304 } else { 1305 $test = $installregistry->packageExists($pkgname, $channel); 1306 } 1307 1308 if (empty($options['upgrade']) && empty($options['soft'])) { 1309 // checks to do only when installing new packages 1310 if (empty($options['force']) && $test) { 1311 return $this->raiseError("$channel/$pkgname is already installed"); 1312 } 1313 } else { 1314 // Upgrade 1315 if ($test) { 1316 $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel); 1317 $v2 = $pkg->getVersion(); 1318 $cmp = version_compare("$v1", "$v2", 'gt'); 1319 if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) { 1320 return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); 1321 } 1322 } 1323 } 1324 1325 // Do cleanups for upgrade and install, remove old release's files first 1326 if ($test && empty($options['register-only'])) { 1327 // when upgrading, remove old release's files first: 1328 if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel, 1329 true))) { 1330 if (!isset($options['ignore-errors'])) { 1331 return $this->raiseError($err); 1332 } 1333 1334 if (!isset($options['soft'])) { 1335 $this->log(0, 'WARNING: ' . $err->getMessage()); 1336 } 1337 } else { 1338 $backedup = $err; 1339 } 1340 } 1341 1342 // {{{ Copy files to dest dir --------------------------------------- 1343 1344 // info from the package it self we want to access from _installFile 1345 $this->pkginfo = &$pkg; 1346 // used to determine whether we should build any C code 1347 $this->source_files = 0; 1348 1349 $savechannel = $this->config->get('default_channel'); 1350 if (empty($options['register-only']) && !is_dir($php_dir)) { 1351 if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) { 1352 return $this->raiseError("no installation destination directory '$php_dir'\n"); 1353 } 1354 } 1355 1356 if (substr($pkgfile, -4) != '.xml') { 1357 $tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion(); 1358 } 1359 1360 $this->configSet('default_channel', $channel); 1361 // {{{ install files 1362 1363 $ver = $pkg->getPackagexmlVersion(); 1364 if (version_compare($ver, '2.0', '>=')) { 1365 $filelist = $pkg->getInstallationFilelist(); 1366 } else { 1367 $filelist = $pkg->getFileList(); 1368 } 1369 1370 if (PEAR::isError($filelist)) { 1371 return $filelist; 1372 } 1373 1374 $p = &$installregistry->getPackage($pkgname, $channel); 1375 $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false; 1376 1377 $pkg->resetFilelist(); 1378 $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(), 1379 'version', $pkg->getChannel())); 1380 foreach ($filelist as $file => $atts) { 1381 $this->expectError(PEAR_INSTALLER_FAILED); 1382 if ($pkg->getPackagexmlVersion() == '1.0') { 1383 $res = $this->_installFile($file, $atts, $tmpdir, $options); 1384 } else { 1385 $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options); 1386 } 1387 $this->popExpect(); 1388 1389 if (PEAR::isError($res)) { 1390 if (empty($options['ignore-errors'])) { 1391 $this->rollbackFileTransaction(); 1392 if ($res->getMessage() == "file does not exist") { 1393 $this->raiseError("file $file in package.xml does not exist"); 1394 } 1395 1396 return $this->raiseError($res); 1397 } 1398 1399 if (!isset($options['soft'])) { 1400 $this->log(0, "Warning: " . $res->getMessage()); 1401 } 1402 } 1403 1404 $real = isset($atts['attribs']) ? $atts['attribs'] : $atts; 1405 if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') { 1406 // Register files that were installed 1407 $pkg->installedFile($file, $atts); 1408 } 1409 } 1410 // }}} 1411 1412 // {{{ compile and install source files 1413 if ($this->source_files > 0 && empty($options['nobuild'])) { 1414 if (PEAR::isError($err = 1415 $this->_compileSourceFiles($savechannel, $pkg))) { 1416 return $err; 1417 } 1418 } 1419 // }}} 1420 1421 if (isset($backedup)) { 1422 $this->_removeBackups($backedup); 1423 } 1424 1425 if (!$this->commitFileTransaction()) { 1426 $this->rollbackFileTransaction(); 1427 $this->configSet('default_channel', $savechannel); 1428 return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED); 1429 } 1430 // }}} 1431 1432 $ret = false; 1433 $installphase = 'install'; 1434 $oldversion = false; 1435 // {{{ Register that the package is installed ----------------------- 1436 if (empty($options['upgrade'])) { 1437 // if 'force' is used, replace the info in registry 1438 $usechannel = $channel; 1439 if ($channel == 'pecl.php.net') { 1440 $test = $installregistry->packageExists($pkgname, $channel); 1441 if (!$test) { 1442 $test = $installregistry->packageExists($pkgname, 'pear.php.net'); 1443 $usechannel = 'pear.php.net'; 1444 } 1445 } else { 1446 $test = $installregistry->packageExists($pkgname, $channel); 1447 } 1448 1449 if (!empty($options['force']) && $test) { 1450 $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel); 1451 $installregistry->deletePackage($pkgname, $usechannel); 1452 } 1453 $ret = $installregistry->addPackage2($pkg); 1454 } else { 1455 if ($dirtree) { 1456 $this->startFileTransaction(); 1457 // attempt to delete empty directories 1458 uksort($dirtree, array($this, '_sortDirs')); 1459 foreach($dirtree as $dir => $notused) { 1460 $this->addFileOperation('rmdir', array($dir)); 1461 } 1462 $this->commitFileTransaction(); 1463 } 1464 1465 $usechannel = $channel; 1466 if ($channel == 'pecl.php.net') { 1467 $test = $installregistry->packageExists($pkgname, $channel); 1468 if (!$test) { 1469 $test = $installregistry->packageExists($pkgname, 'pear.php.net'); 1470 $usechannel = 'pear.php.net'; 1471 } 1472 } else { 1473 $test = $installregistry->packageExists($pkgname, $channel); 1474 } 1475 1476 // new: upgrade installs a package if it isn't installed 1477 if (!$test) { 1478 $ret = $installregistry->addPackage2($pkg); 1479 } else { 1480 if ($usechannel != $channel) { 1481 $installregistry->deletePackage($pkgname, $usechannel); 1482 $ret = $installregistry->addPackage2($pkg); 1483 } else { 1484 $ret = $installregistry->updatePackage2($pkg); 1485 } 1486 $installphase = 'upgrade'; 1487 } 1488 } 1489 1490 if (!$ret) { 1491 $this->configSet('default_channel', $savechannel); 1492 return $this->raiseError("Adding package $channel/$pkgname to registry failed"); 1493 } 1494 // }}} 1495 1496 $this->configSet('default_channel', $savechannel); 1497 if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist 1498 if (PEAR_Task_Common::hasPostinstallTasks()) { 1499 PEAR_Task_Common::runPostinstallTasks($installphase); 1500 } 1501 } 1502 1503 return $pkg->toArray(true); 1504 } 1505 1506 // }}} 1507 1508 // {{{ _compileSourceFiles() 1509 /** 1510 * @param string 1511 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 1512 */ 1513 function _compileSourceFiles($savechannel, &$filelist) 1514 { 1515 require_once 'PEAR/Builder.php'; 1516 $this->log(1, "$this->source_files source files, building"); 1517 $bob = new PEAR_Builder($this->ui); 1518 $bob->debug = $this->debug; 1519 $built = $bob->build($filelist, array(&$this, '_buildCallback')); 1520 if (PEAR::isError($built)) { 1521 $this->rollbackFileTransaction(); 1522 $this->configSet('default_channel', $savechannel); 1523 return $built; 1524 } 1525 1526 $this->log(1, "\nBuild process completed successfully"); 1527 foreach ($built as $ext) { 1528 $bn = basename($ext['file']); 1529 list($_ext_name, $_ext_suff) = explode('.', $bn); 1530 if ($_ext_suff == '.so' || $_ext_suff == '.dll') { 1531 if (extension_loaded($_ext_name)) { 1532 $this->raiseError("Extension '$_ext_name' already loaded. " . 1533 'Please unload it in your php.ini file ' . 1534 'prior to install or upgrade'); 1535 } 1536 $role = 'ext'; 1537 } else { 1538 $role = 'src'; 1539 } 1540 1541 $dest = $ext['dest']; 1542 $packagingroot = ''; 1543 if (isset($this->_options['packagingroot'])) { 1544 $packagingroot = $this->_options['packagingroot']; 1545 } 1546 1547 $copyto = $this->_prependPath($dest, $packagingroot); 1548 $extra = $copyto != $dest ? " as '$copyto'" : ''; 1549 $this->log(1, "Installing '$dest'$extra"); 1550 1551 $copydir = dirname($copyto); 1552 // pretty much nothing happens if we are only registering the install 1553 if (empty($this->_options['register-only'])) { 1554 if (!file_exists($copydir) || !is_dir($copydir)) { 1555 if (!$this->mkDirHier($copydir)) { 1556 return $this->raiseError("failed to mkdir $copydir", 1557 PEAR_INSTALLER_FAILED); 1558 } 1559 1560 $this->log(3, "+ mkdir $copydir"); 1561 } 1562 1563 if (!@copy($ext['file'], $copyto)) { 1564 return $this->raiseError( 1565 "failed to write $copyto (" . error_get_last()["message"] . ")", 1566 PEAR_INSTALLER_FAILED); 1567 } 1568 1569 $this->log(3, "+ cp $ext[file] $copyto"); 1570 $this->addFileOperation('rename', array($ext['file'], $copyto)); 1571 if (!OS_WINDOWS) { 1572 $mode = 0666 & ~(int)octdec($this->config->get('umask')); 1573 $this->addFileOperation('chmod', array($mode, $copyto)); 1574 if (!@chmod($copyto, $mode)) { 1575 $this->log(0, "failed to change mode of $copyto (" . 1576 error_get_last()["message"] . ")"); 1577 } 1578 } 1579 } 1580 1581 1582 $data = array( 1583 'role' => $role, 1584 'name' => $bn, 1585 'installed_as' => $dest, 1586 'php_api' => $ext['php_api'], 1587 'zend_mod_api' => $ext['zend_mod_api'], 1588 'zend_ext_api' => $ext['zend_ext_api'], 1589 ); 1590 1591 if ($filelist->getPackageXmlVersion() == '1.0') { 1592 $filelist->installedFile($bn, $data); 1593 } else { 1594 $filelist->installedFile($bn, array('attribs' => $data)); 1595 } 1596 } 1597 } 1598 1599 // }}} 1600 function &getUninstallPackages() 1601 { 1602 return $this->_downloadedPackages; 1603 } 1604 // {{{ uninstall() 1605 1606 /** 1607 * Uninstall a package 1608 * 1609 * This method removes all files installed by the application, and then 1610 * removes any empty directories. 1611 * @param string package name 1612 * @param array Command-line options. Possibilities include: 1613 * 1614 * - installroot: base installation dir, if not the default 1615 * - register-only : update registry but don't remove files 1616 * - nodeps: do not process dependencies of other packages to ensure 1617 * uninstallation does not break things 1618 */ 1619 function uninstall($package, $options = array()) 1620 { 1621 $installRoot = isset($options['installroot']) ? $options['installroot'] : ''; 1622 $this->config->setInstallRoot($installRoot); 1623 1624 $this->installroot = ''; 1625 $this->_registry = &$this->config->getRegistry(); 1626 if (is_object($package)) { 1627 $channel = $package->getChannel(); 1628 $pkg = $package; 1629 $package = $pkg->getPackage(); 1630 } else { 1631 $pkg = false; 1632 $info = $this->_registry->parsePackageName($package, 1633 $this->config->get('default_channel')); 1634 $channel = $info['channel']; 1635 $package = $info['package']; 1636 } 1637 1638 $savechannel = $this->config->get('default_channel'); 1639 $this->configSet('default_channel', $channel); 1640 if (!is_object($pkg)) { 1641 $pkg = $this->_registry->getPackage($package, $channel); 1642 } 1643 1644 if (!$pkg) { 1645 $this->configSet('default_channel', $savechannel); 1646 return $this->raiseError($this->_registry->parsedPackageNameToString( 1647 array( 1648 'channel' => $channel, 1649 'package' => $package 1650 ), true) . ' not installed'); 1651 } 1652 1653 if ($pkg->getInstalledBinary()) { 1654 // this is just an alias for a binary package 1655 return $this->_registry->deletePackage($package, $channel); 1656 } 1657 1658 $filelist = $pkg->getFilelist(); 1659 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); 1660 if (!class_exists('PEAR_Dependency2')) { 1661 require_once 'PEAR/Dependency2.php'; 1662 } 1663 1664 $depchecker = new PEAR_Dependency2($this->config, $options, 1665 array('channel' => $channel, 'package' => $package), 1666 PEAR_VALIDATE_UNINSTALLING); 1667 $e = $depchecker->validatePackageUninstall($this); 1668 PEAR::staticPopErrorHandling(); 1669 if (PEAR::isError($e)) { 1670 if (!isset($options['ignore-errors'])) { 1671 return $this->raiseError($e); 1672 } 1673 1674 if (!isset($options['soft'])) { 1675 $this->log(0, 'WARNING: ' . $e->getMessage()); 1676 } 1677 } elseif (is_array($e)) { 1678 if (!isset($options['soft'])) { 1679 $this->log(0, $e[0]); 1680 } 1681 } 1682 1683 $this->pkginfo = &$pkg; 1684 // pretty much nothing happens if we are only registering the uninstall 1685 if (empty($options['register-only'])) { 1686 // {{{ Delete the files 1687 $this->startFileTransaction(); 1688 PEAR::pushErrorHandling(PEAR_ERROR_RETURN); 1689 if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) { 1690 PEAR::popErrorHandling(); 1691 $this->rollbackFileTransaction(); 1692 $this->configSet('default_channel', $savechannel); 1693 if (!isset($options['ignore-errors'])) { 1694 return $this->raiseError($err); 1695 } 1696 1697 if (!isset($options['soft'])) { 1698 $this->log(0, 'WARNING: ' . $err->getMessage()); 1699 } 1700 } else { 1701 PEAR::popErrorHandling(); 1702 } 1703 1704 if (!$this->commitFileTransaction()) { 1705 $this->rollbackFileTransaction(); 1706 if (!isset($options['ignore-errors'])) { 1707 return $this->raiseError("uninstall failed"); 1708 } 1709 1710 if (!isset($options['soft'])) { 1711 $this->log(0, 'WARNING: uninstall failed'); 1712 } 1713 } else { 1714 $this->startFileTransaction(); 1715 $dirtree = $pkg->getDirTree(); 1716 if ($dirtree === false) { 1717 $this->configSet('default_channel', $savechannel); 1718 return $this->_registry->deletePackage($package, $channel); 1719 } 1720 1721 // attempt to delete empty directories 1722 uksort($dirtree, array($this, '_sortDirs')); 1723 foreach($dirtree as $dir => $notused) { 1724 $this->addFileOperation('rmdir', array($dir)); 1725 } 1726 1727 if (!$this->commitFileTransaction()) { 1728 $this->rollbackFileTransaction(); 1729 if (!isset($options['ignore-errors'])) { 1730 return $this->raiseError("uninstall failed"); 1731 } 1732 1733 if (!isset($options['soft'])) { 1734 $this->log(0, 'WARNING: uninstall failed'); 1735 } 1736 } 1737 } 1738 // }}} 1739 } 1740 1741 $this->configSet('default_channel', $savechannel); 1742 // Register that the package is no longer installed 1743 return $this->_registry->deletePackage($package, $channel); 1744 } 1745 1746 /** 1747 * Sort a list of arrays of array(downloaded packagefilename) by dependency. 1748 * 1749 * It also removes duplicate dependencies 1750 * @param array an array of PEAR_PackageFile_v[1/2] objects 1751 * @return array|PEAR_Error array of array(packagefilename, package.xml contents) 1752 */ 1753 function sortPackagesForUninstall(&$packages) 1754 { 1755 $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config); 1756 if (PEAR::isError($this->_dependencyDB)) { 1757 return $this->_dependencyDB; 1758 } 1759 usort($packages, array(&$this, '_sortUninstall')); 1760 } 1761 1762 function _sortUninstall($a, $b) 1763 { 1764 if (!$a->getDeps() && !$b->getDeps()) { 1765 return 0; // neither package has dependencies, order is insignificant 1766 } 1767 if ($a->getDeps() && !$b->getDeps()) { 1768 return -1; // $a must be installed after $b because $a has dependencies 1769 } 1770 if (!$a->getDeps() && $b->getDeps()) { 1771 return 1; // $b must be installed after $a because $b has dependencies 1772 } 1773 // both packages have dependencies 1774 if ($this->_dependencyDB->dependsOn($a, $b)) { 1775 return -1; 1776 } 1777 if ($this->_dependencyDB->dependsOn($b, $a)) { 1778 return 1; 1779 } 1780 return 0; 1781 } 1782 1783 // }}} 1784 // {{{ _sortDirs() 1785 function _sortDirs($a, $b) 1786 { 1787 if (strnatcmp($a, $b) == -1) return 1; 1788 if (strnatcmp($a, $b) == 1) return -1; 1789 return 0; 1790 } 1791 1792 // }}} 1793 1794 // {{{ _buildCallback() 1795 1796 function _buildCallback($what, $data) 1797 { 1798 if (($what == 'cmdoutput' && $this->debug > 1) || 1799 ($what == 'output' && $this->debug > 0)) { 1800 $this->ui->outputData(rtrim($data), 'build'); 1801 } 1802 } 1803 1804 // }}} 1805} 1806