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
 
  20  * Used for installation groups in package.xml 2.0 and platform exceptions
 
  22 require_once 'OS/Guess.php';
 
  23 require_once 'PEAR/Downloader.php';
 
  25 define('PEAR_INSTALLER_NOBINARY', -240);
 
  27  * Administration class used to install PEAR packages and maintain the
 
  28  * installed package database.
 
  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: 1.10.1
 
  39  * @link       http://pear.php.net/package/PEAR
 
  40  * @since      Class available since Release 0.1
 
  42 class PEAR_Installer extends PEAR_Downloader
 
  46     /** name of the package directory, for example Foo-1.0
 
  51     /** directory where PHP code files go
 
  56     /** directory where PHP extension files go
 
  61     /** directory where documentation goes
 
  66     /** installation root directory (ala PHP's INSTALL_ROOT or
 
  70     var $installroot = '';
 
  77     /** temporary directory
 
  83      * PEAR_Registry object used by the installer
 
  89      * array of PEAR_Downloader_Packages
 
  92     var $_downloadedPackages;
 
  94     /** List of file transactions queued for an install/upgrade/uninstall.
 
  98      *      0 => array("rename => array("from-file", "to-file")),
 
  99      *      1 => array("delete" => array("file-to-delete")),
 
 105     var $file_operations = array();
 
 112      * PEAR_Installer constructor.
 
 114      * @param object $ui user interface object (instance of PEAR_Frontend_*)
 
 118     function __construct(&$ui)
 
 120         parent::__construct($ui, array(), null);
 
 121         $this->setFrontendObject($ui);
 
 122         $this->debug = $this->config->get('verbose');
 
 125     function setOptions($options)
 
 127         $this->_options = $options;
 
 130     function setConfig(&$config)
 
 132         $this->config    = &$config;
 
 133         $this->_registry = &$config->getRegistry();
 
 138     function _removeBackups($files)
 
 140         foreach ($files as $path) {
 
 141             $this->addFileOperation('removebackup', array($path));
 
 145     // {{{ _deletePackageFiles()
 
 148      * Delete a package's installed files, does not remove empty directories.
 
 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
 
 156     function _deletePackageFiles($package, $channel = false, $backup = false)
 
 159             $channel = 'pear.php.net';
 
 162         if (!strlen($package)) {
 
 163             return $this->raiseError("No package to uninstall given");
 
 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';
 
 178         $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
 
 179         if ($filelist == null) {
 
 180             return $this->raiseError("$channel/$package not installed");
 
 184         foreach ($filelist as $file => $props) {
 
 185             if (empty($props['installed_as'])) {
 
 189             $path = $props['installed_as'];
 
 191                 $this->addFileOperation('backup', array($path));
 
 195             $this->addFileOperation('delete', array($path));
 
 206     // {{{ _installFile()
 
 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
 
 215     function _installFile($file, $atts, $tmp_path, $options)
 
 217         // {{{ return if this file is meant for another platform
 
 219         if (!isset($this->_registry)) {
 
 220             $this->_registry = &$this->config->getRegistry();
 
 223         if (isset($atts['platform'])) {
 
 225                 $os = new OS_Guess();
 
 228             if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
 
 230                 $platform = substr($atts['platform'], 1);
 
 233                 $platform = $atts['platform'];
 
 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;
 
 243         $channel = $this->pkginfo->getChannel();
 
 244         // {{{ assemble the destination paths
 
 245         switch ($atts['role']) {
 
 248                 $this->source_files++;
 
 253                 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
 
 254                             DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
 
 255                 unset($atts['baseinstalldir']);
 
 259                 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
 
 262                 $dest_dir = $this->config->get('bin_dir', null, $channel);
 
 265                 return $this->raiseError("Invalid role `$atts[role]' for file $file");
 
 268         $save_destdir = $dest_dir;
 
 269         if (!empty($atts['baseinstalldir'])) {
 
 270             $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
 
 273         if (dirname($file) != '.' && empty($atts['install-as'])) {
 
 274             $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
 
 277         if (empty($atts['install-as'])) {
 
 278             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
 
 280             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
 
 282         $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
 
 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,
 
 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']);
 
 297             $installedas_dest_dir  = dirname($final_dest_file);
 
 298             $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
 
 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);
 
 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);
 
 314             $this->log(3, "+ mkdir $dest_dir");
 
 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);
 
 325                 if (!@copy($orig_file, $dest_file)) {
 
 326                     return $this->raiseError("failed to write $dest_file: $php_errormsg",
 
 327                                              PEAR_INSTALLER_FAILED);
 
 330                 $this->log(3, "+ cp $orig_file $dest_file");
 
 331                 if (isset($atts['md5sum'])) {
 
 332                     $md5sum = md5_file($dest_file);
 
 335                 // {{{ file with replacements
 
 336                 if (!file_exists($orig_file)) {
 
 337                     return $this->raiseError("file does not exist",
 
 338                                              PEAR_INSTALLER_FAILED);
 
 341                 $contents = file_get_contents($orig_file);
 
 342                 if ($contents === false) {
 
 346                 if (isset($atts['md5sum'])) {
 
 347                     $md5sum = md5($contents);
 
 350                 $subst_from = $subst_to = array();
 
 351                 foreach ($atts['replacements'] as $a) {
 
 353                     if ($a['type'] == 'php-const') {
 
 354                         if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
 
 355                             eval("\$to = $a[to];");
 
 357                             if (!isset($options['soft'])) {
 
 358                                 $this->log(0, "invalid php-const replacement: $a[to]");
 
 362                     } elseif ($a['type'] == 'pear-config') {
 
 363                         if ($a['to'] == 'master_server') {
 
 364                             $chan = $this->_registry->getChannel($channel);
 
 365                             if (!PEAR::isError($chan)) {
 
 366                                 $to = $chan->getServer();
 
 368                                 $to = $this->config->get($a['to'], null, $channel);
 
 371                             $to = $this->config->get($a['to'], null, $channel);
 
 374                             if (!isset($options['soft'])) {
 
 375                                 $this->log(0, "invalid pear-config replacement: $a[to]");
 
 379                     } elseif ($a['type'] == 'package-info') {
 
 380                         if ($t = $this->pkginfo->packageInfo($a['to'])) {
 
 383                             if (!isset($options['soft'])) {
 
 384                                 $this->log(0, "invalid package-info replacement: $a[to]");
 
 390                         $subst_from[] = $a['from'];
 
 395                 $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
 
 396                 if (sizeof($subst_from)) {
 
 397                     $contents = str_replace($subst_from, $subst_to, $contents);
 
 400                 $wp = @fopen($dest_file, "wb");
 
 401                 if (!is_resource($wp)) {
 
 402                     return $this->raiseError("failed to create $dest_file: $php_errormsg",
 
 403                                              PEAR_INSTALLER_FAILED);
 
 406                 if (@fwrite($wp, $contents) === false) {
 
 407                     return $this->raiseError("failed writing to $dest_file: $php_errormsg",
 
 408                                              PEAR_INSTALLER_FAILED);
 
 416             if (isset($md5sum)) {
 
 417                 if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
 
 418                     $this->log(2, "md5sum ok: $final_dest_file");
 
 420                     if (empty($options['force'])) {
 
 422                         if (file_exists($dest_file)) {
 
 426                         if (!isset($options['ignore-errors'])) {
 
 427                             return $this->raiseError("bad md5sum for file $final_dest_file",
 
 428                                                  PEAR_INSTALLER_FAILED);
 
 431                         if (!isset($options['soft'])) {
 
 432                             $this->log(0, "warning : bad md5sum for file $final_dest_file");
 
 435                         if (!isset($options['soft'])) {
 
 436                             $this->log(0, "warning : bad md5sum for file $final_dest_file");
 
 442             // {{{ set file permissions
 
 444                 if ($atts['role'] == 'script') {
 
 445                     $mode = 0777 & ~(int)octdec($this->config->get('umask'));
 
 446                     $this->log(3, "+ chmod +x $dest_file");
 
 448                     $mode = 0666 & ~(int)octdec($this->config->get('umask'));
 
 451                 if ($atts['role'] != 'src') {
 
 452                     $this->addFileOperation("chmod", array($mode, $dest_file));
 
 453                     if (!@chmod($dest_file, $mode)) {
 
 454                         if (!isset($options['soft'])) {
 
 455                             $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
 
 462             if ($atts['role'] == 'src') {
 
 463                 rename($dest_file, $final_dest_file);
 
 464                 $this->log(2, "renamed source file $dest_file to $final_dest_file");
 
 466                 $this->addFileOperation("rename", array($dest_file, $final_dest_file,
 
 467                     $atts['role'] == 'ext'));
 
 471         // Store the full path where the file was installed for easy unistall
 
 472         if ($atts['role'] != 'script') {
 
 473             $loc = $this->config->get($atts['role'] . '_dir');
 
 475             $loc = $this->config->get('bin_dir');
 
 478         if ($atts['role'] != 'src') {
 
 479             $this->addFileOperation("installed_as", array($file, $installed_as,
 
 481                                     dirname(substr($installedas_dest_file, strlen($loc)))));
 
 484         //$this->log(2, "installed: $dest_file");
 
 485         return PEAR_INSTALLER_OK;
 
 489     // {{{ _installFile2()
 
 492      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
 
 493      * @param string filename
 
 494      * @param array attributes from <file> tag in package.xml
 
 495      * @param string path to install the file in
 
 496      * @param array options from command-line
 
 499     function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
 
 502         if (!isset($this->_registry)) {
 
 503             $this->_registry = &$this->config->getRegistry();
 
 506         $channel = $pkg->getChannel();
 
 507         // {{{ assemble the destination paths
 
 508         if (!in_array($atts['attribs']['role'],
 
 509               PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
 
 510             return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
 
 514         $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
 
 515         $err  = $role->setup($this, $pkg, $atts['attribs'], $file);
 
 516         if (PEAR::isError($err)) {
 
 520         if (!$role->isInstallable()) {
 
 524         $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
 
 525         if (PEAR::isError($info)) {
 
 529         list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
 
 530         if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
 
 531             return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
 
 534         $final_dest_file = $installed_as = $dest_file;
 
 535         if (isset($this->_options['packagingroot'])) {
 
 536             $final_dest_file = $this->_prependPath($final_dest_file,
 
 537                 $this->_options['packagingroot']);
 
 540         $dest_dir  = dirname($final_dest_file);
 
 541         $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
 
 544         if (empty($this->_options['register-only'])) {
 
 545             if (!file_exists($dest_dir) || !is_dir($dest_dir)) {
 
 546                 if (!$this->mkDirHier($dest_dir)) {
 
 547                     return $this->raiseError("failed to mkdir $dest_dir",
 
 548                                              PEAR_INSTALLER_FAILED);
 
 550                 $this->log(3, "+ mkdir $dest_dir");
 
 554         $attribs = $atts['attribs'];
 
 555         unset($atts['attribs']);
 
 556         // pretty much nothing happens if we are only registering the install
 
 557         if (empty($this->_options['register-only'])) {
 
 558             if (!count($atts)) { // no tasks
 
 559                 if (!file_exists($orig_file)) {
 
 560                     return $this->raiseError("file $orig_file does not exist",
 
 561                                              PEAR_INSTALLER_FAILED);
 
 564                 if (!@copy($orig_file, $dest_file)) {
 
 565                     return $this->raiseError("failed to write $dest_file: $php_errormsg",
 
 566                                              PEAR_INSTALLER_FAILED);
 
 569                 $this->log(3, "+ cp $orig_file $dest_file");
 
 570                 if (isset($attribs['md5sum'])) {
 
 571                     $md5sum = md5_file($dest_file);
 
 573             } else { // file with tasks
 
 574                 if (!file_exists($orig_file)) {
 
 575                     return $this->raiseError("file $orig_file does not exist",
 
 576                                              PEAR_INSTALLER_FAILED);
 
 579                 $contents = file_get_contents($orig_file);
 
 580                 if ($contents === false) {
 
 584                 if (isset($attribs['md5sum'])) {
 
 585                     $md5sum = md5($contents);
 
 588                 foreach ($atts as $tag => $raw) {
 
 589                     $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
 
 590                     $task = "PEAR_Task_$tag";
 
 591                     $task = new $task($this->config, $this, PEAR_TASK_INSTALL);
 
 592                     if (!$task->isScript()) { // scripts are only handled after installation
 
 593                         $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
 
 594                         $res = $task->startSession($pkg, $contents, $final_dest_file);
 
 595                         if ($res === false) {
 
 596                             continue; // skip this file
 
 599                         if (PEAR::isError($res)) {
 
 603                         $contents = $res; // save changes
 
 606                     $wp = @fopen($dest_file, "wb");
 
 607                     if (!is_resource($wp)) {
 
 608                         return $this->raiseError("failed to create $dest_file: $php_errormsg",
 
 609                                                  PEAR_INSTALLER_FAILED);
 
 612                     if (fwrite($wp, $contents) === false) {
 
 613                         return $this->raiseError("failed writing to $dest_file: $php_errormsg",
 
 614                                                  PEAR_INSTALLER_FAILED);
 
 622             if (isset($md5sum)) {
 
 623                 // Make sure the original md5 sum matches with expected
 
 624                 if (strtolower($md5sum) === strtolower($attribs['md5sum'])) {
 
 625                     $this->log(2, "md5sum ok: $final_dest_file");
 
 627                     if (isset($contents)) {
 
 628                         // set md5 sum based on $content in case any tasks were run.
 
 629                         $real_atts['attribs']['md5sum'] = md5($contents);
 
 632                     if (empty($options['force'])) {
 
 634                         if (file_exists($dest_file)) {
 
 638                         if (!isset($options['ignore-errors'])) {
 
 639                             return $this->raiseError("bad md5sum for file $final_dest_file",
 
 640                                                      PEAR_INSTALLER_FAILED);
 
 643                         if (!isset($options['soft'])) {
 
 644                             $this->log(0, "warning : bad md5sum for file $final_dest_file");
 
 647                         if (!isset($options['soft'])) {
 
 648                             $this->log(0, "warning : bad md5sum for file $final_dest_file");
 
 653                 $real_atts['attribs']['md5sum'] = md5_file($dest_file);
 
 657             // {{{ set file permissions
 
 659                 if ($role->isExecutable()) {
 
 660                     $mode = 0777 & ~(int)octdec($this->config->get('umask'));
 
 661                     $this->log(3, "+ chmod +x $dest_file");
 
 663                     $mode = 0666 & ~(int)octdec($this->config->get('umask'));
 
 666                 if ($attribs['role'] != 'src') {
 
 667                     $this->addFileOperation("chmod", array($mode, $dest_file));
 
 668                     if (!@chmod($dest_file, $mode)) {
 
 669                         if (!isset($options['soft'])) {
 
 670                             $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
 
 677             if ($attribs['role'] == 'src') {
 
 678                 rename($dest_file, $final_dest_file);
 
 679                 $this->log(2, "renamed source file $dest_file to $final_dest_file");
 
 681                 $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
 
 685         // Store the full path where the file was installed for easy uninstall
 
 686         if ($attribs['role'] != 'src') {
 
 687             $loc = $this->config->get($role->getLocationConfig(), null, $channel);
 
 688             $this->addFileOperation('installed_as', array($file, $installed_as,
 
 690                                 dirname(substr($installed_as, strlen($loc)))));
 
 693         //$this->log(2, "installed: $dest_file");
 
 694         return PEAR_INSTALLER_OK;
 
 698     // {{{ addFileOperation()
 
 701      * Add a file operation to the current file transaction.
 
 703      * @see startFileTransaction()
 
 704      * @param string $type This can be one of:
 
 705      *    - rename:  rename a file ($data has 3 values)
 
 706      *    - backup:  backup an existing file ($data has 1 value)
 
 707      *    - removebackup:  clean up backups created during install ($data has 1 value)
 
 708      *    - chmod:   change permissions on a file ($data has 2 values)
 
 709      *    - delete:  delete a file ($data has 1 value)
 
 710      *    - rmdir:   delete a directory if empty ($data has 1 value)
 
 711      *    - installed_as: mark a file as installed ($data has 4 values).
 
 712      * @param array $data For all file operations, this array must contain the
 
 713      *    full path to the file or directory that is being operated on.  For
 
 714      *    the rename command, the first parameter must be the file to rename,
 
 715      *    the second its new name, the third whether this is a PHP extension.
 
 717      *    The installed_as operation contains 4 elements in this order:
 
 718      *    1. Filename as listed in the filelist element from package.xml
 
 719      *    2. Full path to the installed file
 
 720      *    3. Full path from the php_dir configuration variable used in this
 
 722      *    4. Relative path from the php_dir that this file is installed in
 
 724     function addFileOperation($type, $data)
 
 726         if (!is_array($data)) {
 
 727             return $this->raiseError('Internal Error: $data in addFileOperation'
 
 728                 . ' must be an array, was ' . gettype($data));
 
 731         if ($type == 'chmod') {
 
 732             $octmode = decoct($data[0]);
 
 733             $this->log(3, "adding to transaction: $type $octmode $data[1]");
 
 735             $this->log(3, "adding to transaction: $type " . implode(" ", $data));
 
 737         $this->file_operations[] = array($type, $data);
 
 741     // {{{ startFileTransaction()
 
 743     function startFileTransaction($rollback_in_case = false)
 
 745         if (count($this->file_operations) && $rollback_in_case) {
 
 746             $this->rollbackFileTransaction();
 
 748         $this->file_operations = array();
 
 752     // {{{ commitFileTransaction()
 
 754     function commitFileTransaction()
 
 756         // {{{ first, check permissions and such manually
 
 758         foreach ($this->file_operations as $key => $tr) {
 
 759             list($type, $data) = $tr;
 
 762                     if (!file_exists($data[0])) {
 
 763                         $errors[] = "cannot rename file $data[0], doesn't exist";
 
 766                     // check that dest dir. is writable
 
 767                     if (!is_writable(dirname($data[1]))) {
 
 768                         $errors[] = "permission denied ($type): $data[1]";
 
 772                     // check that file is writable
 
 773                     if (!is_writable($data[1])) {
 
 774                         $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
 
 778                     if (!file_exists($data[0])) {
 
 779                         $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
 
 781                     // check that directory is writable
 
 782                     if (file_exists($data[0])) {
 
 783                         if (!is_writable(dirname($data[0]))) {
 
 784                             $errors[] = "permission denied ($type): $data[0]";
 
 786                             // make sure the file to be deleted can be opened for writing
 
 788                             if (!is_dir($data[0]) &&
 
 789                                   (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
 
 790                                 $errors[] = "permission denied ($type): $data[0]";
 
 796                         /* Verify we are not deleting a file owned by another package
 
 797                          * This can happen when a file moves from package A to B in
 
 798                          * an upgrade ala http://pear.php.net/17986
 
 801                             'package' => strtolower($this->pkginfo->getName()),
 
 802                             'channel' => strtolower($this->pkginfo->getChannel()),
 
 804                         $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
 
 805                         if (is_array($result)) {
 
 806                             $res = array_diff($result, $info);
 
 808                                 $new = $this->_registry->getPackage($result[1], $result[0]);
 
 809                                 $this->file_operations[$key] = false;
 
 810                                 $pkginfoName = $this->pkginfo->getName();
 
 811                                 $newChannel  = $new->getChannel();
 
 812                                 $newPackage  = $new->getName();
 
 813                                 $this->log(3, "file $data[0] was scheduled for removal from $pkginfoName but is owned by $newChannel/$newPackage, removal has been cancelled.");
 
 823         $n = count($this->file_operations);
 
 824         $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());
 
 828             foreach ($errors as $error) {
 
 829                 if (!isset($this->_options['soft'])) {
 
 830                     $this->log(1, $error);
 
 834             if (!isset($this->_options['ignore-errors'])) {
 
 839         $this->_dirtree = array();
 
 840         // {{{ really commit the transaction
 
 841         foreach ($this->file_operations as $i => $tr) {
 
 843                 // support removal of non-existing backups
 
 847             list($type, $data) = $tr;
 
 850                     if (!file_exists($data[0])) {
 
 851                         $this->file_operations[$i] = false;
 
 855                     if (!@copy($data[0], $data[0] . '.bak')) {
 
 856                         $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
 
 857                             '.bak ' . $php_errormsg);
 
 860                     $this->log(3, "+ backup $data[0] to $data[0].bak");
 
 863                     if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
 
 864                         unlink($data[0] . '.bak');
 
 865                         $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
 
 869                     $test = file_exists($data[1]) ? @unlink($data[1]) : null;
 
 870                     if (!$test && file_exists($data[1])) {
 
 872                             $extra = ', this extension must be installed manually.  Rename to "' .
 
 873                                 basename($data[1]) . '"';
 
 878                         if (!isset($this->_options['soft'])) {
 
 879                             $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
 
 883                         if (!isset($this->_options['ignore-errors'])) {
 
 888                     // permissions issues with rename - copy() is far superior
 
 889                     $perms = @fileperms($data[0]);
 
 890                     if (!@copy($data[0], $data[1])) {
 
 891                         $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
 
 892                             ' ' . $php_errormsg);
 
 896                     // copy over permissions, otherwise they are lost
 
 897                     @chmod($data[1], $perms);
 
 899                     $this->log(3, "+ mv $data[0] $data[1]");
 
 902                     if (!@chmod($data[1], $data[0])) {
 
 903                         $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
 
 904                             decoct($data[0]) . ' ' . $php_errormsg);
 
 908                     $octmode = decoct($data[0]);
 
 909                     $this->log(3, "+ chmod $octmode $data[1]");
 
 912                     if (file_exists($data[0])) {
 
 913                         if (!@unlink($data[0])) {
 
 914                             $this->log(1, 'Could not delete ' . $data[0] . ' ' .
 
 918                         $this->log(3, "+ rm $data[0]");
 
 922                     if (file_exists($data[0])) {
 
 924                             $testme = opendir($data[0]);
 
 925                             while (false !== ($entry = readdir($testme))) {
 
 926                                 if ($entry == '.' || $entry == '..') {
 
 930                                 break 2; // this directory is not empty and can't be
 
 935                             if (!@rmdir($data[0])) {
 
 936                                 $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
 
 940                             $this->log(3, "+ rmdir $data[0]");
 
 945                     $this->pkginfo->setInstalledAs($data[0], $data[1]);
 
 946                     if (!isset($this->_dirtree[dirname($data[1])])) {
 
 947                         $this->_dirtree[dirname($data[1])] = true;
 
 948                         $this->pkginfo->setDirtree(dirname($data[1]));
 
 950                         while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
 
 951                                 $data[3] != '/' && $data[3] != '\\') {
 
 952                             $this->pkginfo->setDirtree($pp =
 
 953                                 $this->_prependPath($data[3], $data[2]));
 
 954                             $this->_dirtree[$pp] = true;
 
 955                             $data[3] = dirname($data[3]);
 
 962         $this->log(2, "successfully committed $n file operations");
 
 963         $this->file_operations = array();
 
 968     // {{{ rollbackFileTransaction()
 
 970     function rollbackFileTransaction()
 
 972         $n = count($this->file_operations);
 
 973         $this->log(2, "rolling back $n file operations");
 
 974         foreach ($this->file_operations as $tr) {
 
 975             list($type, $data) = $tr;
 
 978                     if (file_exists($data[0] . '.bak')) {
 
 979                         if (file_exists($data[0] && is_writable($data[0]))) {
 
 982                         @copy($data[0] . '.bak', $data[0]);
 
 983                         $this->log(3, "+ restore $data[0] from $data[0].bak");
 
 987                     if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
 
 988                         unlink($data[0] . '.bak');
 
 989                         $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
 
 994                     $this->log(3, "+ rm $data[0]");
 
 998                     $this->log(3, "+ rmdir $data[0]");
 
1004                 case 'installed_as':
 
1005                     $this->pkginfo->setInstalledAs($data[0], false);
 
1009         $this->pkginfo->resetDirtree();
 
1010         $this->file_operations = array();
 
1014     // {{{ mkDirHier($dir)
 
1016     function mkDirHier($dir)
 
1018         $this->addFileOperation('mkdir', array($dir));
 
1019         return parent::mkDirHier($dir);
 
1023     // {{{ _parsePackageXml()
 
1025     function _parsePackageXml(&$descfile)
 
1027         // Parse xml file -----------------------------------------------
 
1028         $pkg = new PEAR_PackageFile($this->config, $this->debug);
 
1029         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
 
1030         $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
 
1031         PEAR::staticPopErrorHandling();
 
1032         if (PEAR::isError($p)) {
 
1033             if (is_array($p->getUserInfo())) {
 
1034                 foreach ($p->getUserInfo() as $err) {
 
1035                     $loglevel = $err['level'] == 'error' ? 0 : 1;
 
1036                     if (!isset($this->_options['soft'])) {
 
1037                         $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
 
1041             return $this->raiseError('Installation failed: invalid package file');
 
1044         $descfile = $p->getPackageFile();
 
1050      * Set the list of PEAR_Downloader_Package objects to allow more sane
 
1051      * dependency validation
 
1054     function setDownloadedPackages(&$pkgs)
 
1056         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
 
1057         $err = $this->analyzeDependencies($pkgs);
 
1058         PEAR::popErrorHandling();
 
1059         if (PEAR::isError($err)) {
 
1062         $this->_downloadedPackages = &$pkgs;
 
1066      * Set the list of PEAR_Downloader_Package objects to allow more sane
 
1067      * dependency validation
 
1070     function setUninstallPackages(&$pkgs)
 
1072         $this->_downloadedPackages = &$pkgs;
 
1075     function getInstallPackages()
 
1077         return $this->_downloadedPackages;
 
1083      * Installs the files within the package file specified.
 
1085      * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
 
1086      *        or a pre-initialized packagefile object
 
1087      * @param array $options
 
1088      * recognized options:
 
1089      * - installroot   : optional prefix directory for installation
 
1090      * - force         : force installation
 
1091      * - register-only : update registry but don't install files
 
1092      * - upgrade       : upgrade existing install
 
1093      * - soft          : fail silently
 
1094      * - nodeps        : ignore dependency conflicts/missing dependencies
 
1095      * - alldeps       : install all dependencies
 
1096      * - onlyreqdeps   : install only required dependencies
 
1098      * @return array|PEAR_Error package info if successful
 
1100     function install($pkgfile, $options = array())
 
1102         $this->_options = $options;
 
1103         $this->_registry = &$this->config->getRegistry();
 
1104         if (is_object($pkgfile)) {
 
1106             $pkg      = $pkgfile->getPackageFile();
 
1107             $pkgfile  = $pkg->getArchiveFile();
 
1108             $descfile = $pkg->getPackageFile();
 
1110             $descfile = $pkgfile;
 
1111             $pkg      = $this->_parsePackageXml($descfile);
 
1112             if (PEAR::isError($pkg)) {
 
1117         $tmpdir = dirname($descfile);
 
1118         if (realpath($descfile) != realpath($pkgfile)) {
 
1119             // Use the temp_dir since $descfile can contain the download dir path
 
1120             $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net');
 
1121             $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"');
 
1123             $tar = new Archive_Tar($pkgfile);
 
1124             if (!$tar->extract($tmpdir)) {
 
1125                 return $this->raiseError("unable to unpack $pkgfile");
 
1129         $pkgname = $pkg->getName();
 
1130         $channel = $pkg->getChannel();
 
1132         if (isset($options['installroot'])) {
 
1133             $this->config->setInstallRoot($options['installroot']);
 
1134             $this->_registry = &$this->config->getRegistry();
 
1135             $installregistry = &$this->_registry;
 
1136             $this->installroot = ''; // all done automagically now
 
1137             $php_dir = $this->config->get('php_dir', null, $channel);
 
1139             $this->config->setInstallRoot(false);
 
1140             $this->_registry = &$this->config->getRegistry();
 
1141             if (isset($this->_options['packagingroot'])) {
 
1142                 $regdir = $this->_prependPath(
 
1143                     $this->config->get('php_dir', null, 'pear.php.net'),
 
1144                     $this->_options['packagingroot']);
 
1146                 $metadata_dir = $this->config->get('metadata_dir', null, 'pear.php.net');
 
1147                 if ($metadata_dir) {
 
1148                     $metadata_dir = $this->_prependPath(
 
1150                         $this->_options['packagingroot']);
 
1152                 $packrootphp_dir = $this->_prependPath(
 
1153                     $this->config->get('php_dir', null, $channel),
 
1154                     $this->_options['packagingroot']);
 
1156                 $installregistry = new PEAR_Registry($regdir, false, false, $metadata_dir);
 
1157                 if (!$installregistry->channelExists($channel, true)) {
 
1158                     // we need to fake a channel-discover of this channel
 
1159                     $chanobj = $this->_registry->getChannel($channel, true);
 
1160                     $installregistry->addChannel($chanobj);
 
1162                 $php_dir = $packrootphp_dir;
 
1164                 $installregistry = &$this->_registry;
 
1165                 $php_dir = $this->config->get('php_dir', null, $channel);
 
1167             $this->installroot = '';
 
1170         // {{{ checks to do when not in "force" mode
 
1171         if (empty($options['force']) &&
 
1172               (file_exists($this->config->get('php_dir')) &&
 
1173                is_dir($this->config->get('php_dir')))) {
 
1174             $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
 
1175             $instfilelist = $pkg->getInstallationFileList(true);
 
1176             if (PEAR::isError($instfilelist)) {
 
1177                 return $instfilelist;
 
1180             // ensure we have the most accurate registry
 
1181             $installregistry->flushFileMap();
 
1182             $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
 
1183             if (PEAR::isError($test)) {
 
1187             if (sizeof($test)) {
 
1188                 $pkgs = $this->getInstallPackages();
 
1190                 foreach ($pkgs as $param) {
 
1191                     if ($pkg->isSubpackageOf($param)) {
 
1198                     // subpackages can conflict with earlier versions of parent packages
 
1199                     $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
 
1201                     foreach ($tmp as $file => $info) {
 
1202                         if (is_array($info)) {
 
1203                             if (strtolower($info[1]) == strtolower($param->getPackage()) &&
 
1204                                   strtolower($info[0]) == strtolower($param->getChannel())
 
1206                                 if (isset($parentreg['filelist'][$file])) {
 
1207                                     unset($parentreg['filelist'][$file]);
 
1209                                     $pos     = strpos($file, '/');
 
1210                                     $basedir = substr($file, 0, $pos);
 
1211                                     $file2   = substr($file, $pos + 1);
 
1212                                     if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
 
1213                                         && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
 
1215                                         unset($parentreg['filelist'][$file2]);
 
1219                                 unset($test[$file]);
 
1222                             if (strtolower($param->getChannel()) != 'pear.php.net') {
 
1226                             if (strtolower($info) == strtolower($param->getPackage())) {
 
1227                                 if (isset($parentreg['filelist'][$file])) {
 
1228                                     unset($parentreg['filelist'][$file]);
 
1230                                     $pos     = strpos($file, '/');
 
1231                                     $basedir = substr($file, 0, $pos);
 
1232                                     $file2   = substr($file, $pos + 1);
 
1233                                     if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
 
1234                                         && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
 
1236                                         unset($parentreg['filelist'][$file2]);
 
1240                                 unset($test[$file]);
 
1245                     $pfk = new PEAR_PackageFile($this->config);
 
1246                     $parentpkg = &$pfk->fromArray($parentreg);
 
1247                     $installregistry->updatePackage2($parentpkg);
 
1250                 if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
 
1252                     foreach ($tmp as $file => $info) {
 
1253                         if (is_string($info)) {
 
1254                             // pear.php.net packages are always stored as strings
 
1255                             if (strtolower($info) == strtolower($param->getPackage())) {
 
1256                                 // upgrading existing package
 
1257                                 unset($test[$file]);
 
1264                     $msg = "$channel/$pkgname: conflicting files found:\n";
 
1265                     $longest = max(array_map("strlen", array_keys($test)));
 
1266                     $fmt = "%${longest}s (%s)\n";
 
1267                     foreach ($test as $file => $info) {
 
1268                         if (!is_array($info)) {
 
1269                             $info = array('pear.php.net', $info);
 
1271                         $info = $info[0] . '/' . $info[1];
 
1272                         $msg .= sprintf($fmt, $file, $info);
 
1275                     if (!isset($options['ignore-errors'])) {
 
1276                         return $this->raiseError($msg);
 
1279                     if (!isset($options['soft'])) {
 
1280                         $this->log(0, "WARNING: $msg");
 
1287         $this->startFileTransaction();
 
1289         $usechannel = $channel;
 
1290         if ($channel == 'pecl.php.net') {
 
1291             $test = $installregistry->packageExists($pkgname, $channel);
 
1293                 $test = $installregistry->packageExists($pkgname, 'pear.php.net');
 
1294                 $usechannel = 'pear.php.net';
 
1297             $test = $installregistry->packageExists($pkgname, $channel);
 
1300         if (empty($options['upgrade']) && empty($options['soft'])) {
 
1301             // checks to do only when installing new packages
 
1302             if (empty($options['force']) && $test) {
 
1303                 return $this->raiseError("$channel/$pkgname is already installed");
 
1308                 $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel);
 
1309                 $v2 = $pkg->getVersion();
 
1310                 $cmp = version_compare("$v1", "$v2", 'gt');
 
1311                 if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
 
1312                     return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
 
1317         // Do cleanups for upgrade and install, remove old release's files first
 
1318         if ($test && empty($options['register-only'])) {
 
1319             // when upgrading, remove old release's files first:
 
1320             if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
 
1322                 if (!isset($options['ignore-errors'])) {
 
1323                     return $this->raiseError($err);
 
1326                 if (!isset($options['soft'])) {
 
1327                     $this->log(0, 'WARNING: ' . $err->getMessage());
 
1334         // {{{ Copy files to dest dir ---------------------------------------
 
1336         // info from the package it self we want to access from _installFile
 
1337         $this->pkginfo = &$pkg;
 
1338         // used to determine whether we should build any C code
 
1339         $this->source_files = 0;
 
1341         $savechannel = $this->config->get('default_channel');
 
1342         if (empty($options['register-only']) && !is_dir($php_dir)) {
 
1343             if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
 
1344                 return $this->raiseError("no installation destination directory '$php_dir'\n");
 
1348         if (substr($pkgfile, -4) != '.xml') {
 
1349             $tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
 
1352         $this->configSet('default_channel', $channel);
 
1353         // {{{ install files
 
1355         $ver = $pkg->getPackagexmlVersion();
 
1356         if (version_compare($ver, '2.0', '>=')) {
 
1357             $filelist = $pkg->getInstallationFilelist();
 
1359             $filelist = $pkg->getFileList();
 
1362         if (PEAR::isError($filelist)) {
 
1366         $p = &$installregistry->getPackage($pkgname, $channel);
 
1367         $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
 
1369         $pkg->resetFilelist();
 
1370         $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
 
1371             'version', $pkg->getChannel()));
 
1372         foreach ($filelist as $file => $atts) {
 
1373             $this->expectError(PEAR_INSTALLER_FAILED);
 
1374             if ($pkg->getPackagexmlVersion() == '1.0') {
 
1375                 $res = $this->_installFile($file, $atts, $tmpdir, $options);
 
1377                 $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options);
 
1381             if (PEAR::isError($res)) {
 
1382                 if (empty($options['ignore-errors'])) {
 
1383                     $this->rollbackFileTransaction();
 
1384                     if ($res->getMessage() == "file does not exist") {
 
1385                         $this->raiseError("file $file in package.xml does not exist");
 
1388                     return $this->raiseError($res);
 
1391                 if (!isset($options['soft'])) {
 
1392                     $this->log(0, "Warning: " . $res->getMessage());
 
1396             $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
 
1397             if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
 
1398                 // Register files that were installed
 
1399                 $pkg->installedFile($file, $atts);
 
1404         // {{{ compile and install source files
 
1405         if ($this->source_files > 0 && empty($options['nobuild'])) {
 
1406             if (PEAR::isError($err =
 
1407                   $this->_compileSourceFiles($savechannel, $pkg))) {
 
1413         if (isset($backedup)) {
 
1414             $this->_removeBackups($backedup);
 
1417         if (!$this->commitFileTransaction()) {
 
1418             $this->rollbackFileTransaction();
 
1419             $this->configSet('default_channel', $savechannel);
 
1420             return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
 
1425         $installphase = 'install';
 
1426         $oldversion   = false;
 
1427         // {{{ Register that the package is installed -----------------------
 
1428         if (empty($options['upgrade'])) {
 
1429             // if 'force' is used, replace the info in registry
 
1430             $usechannel = $channel;
 
1431             if ($channel == 'pecl.php.net') {
 
1432                 $test = $installregistry->packageExists($pkgname, $channel);
 
1434                     $test = $installregistry->packageExists($pkgname, 'pear.php.net');
 
1435                     $usechannel = 'pear.php.net';
 
1438                 $test = $installregistry->packageExists($pkgname, $channel);
 
1441             if (!empty($options['force']) && $test) {
 
1442                 $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
 
1443                 $installregistry->deletePackage($pkgname, $usechannel);
 
1445             $ret = $installregistry->addPackage2($pkg);
 
1448                 $this->startFileTransaction();
 
1449                 // attempt to delete empty directories
 
1450                 uksort($dirtree, array($this, '_sortDirs'));
 
1451                 foreach($dirtree as $dir => $notused) {
 
1452                     $this->addFileOperation('rmdir', array($dir));
 
1454                 $this->commitFileTransaction();
 
1457             $usechannel = $channel;
 
1458             if ($channel == 'pecl.php.net') {
 
1459                 $test = $installregistry->packageExists($pkgname, $channel);
 
1461                     $test = $installregistry->packageExists($pkgname, 'pear.php.net');
 
1462                     $usechannel = 'pear.php.net';
 
1465                 $test = $installregistry->packageExists($pkgname, $channel);
 
1468             // new: upgrade installs a package if it isn't installed
 
1470                 $ret = $installregistry->addPackage2($pkg);
 
1472                 if ($usechannel != $channel) {
 
1473                     $installregistry->deletePackage($pkgname, $usechannel);
 
1474                     $ret = $installregistry->addPackage2($pkg);
 
1476                     $ret = $installregistry->updatePackage2($pkg);
 
1478                 $installphase = 'upgrade';
 
1483             $this->configSet('default_channel', $savechannel);
 
1484             return $this->raiseError("Adding package $channel/$pkgname to registry failed");
 
1488         $this->configSet('default_channel', $savechannel);
 
1489         if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
 
1490             if (PEAR_Task_Common::hasPostinstallTasks()) {
 
1491                 PEAR_Task_Common::runPostinstallTasks($installphase);
 
1495         return $pkg->toArray(true);
 
1500     // {{{ _compileSourceFiles()
 
1503      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
 
1505     function _compileSourceFiles($savechannel, &$filelist)
 
1507         require_once 'PEAR/Builder.php';
 
1508         $this->log(1, "$this->source_files source files, building");
 
1509         $bob = new PEAR_Builder($this->ui);
 
1510         $bob->debug = $this->debug;
 
1511         $built = $bob->build($filelist, array(&$this, '_buildCallback'));
 
1512         if (PEAR::isError($built)) {
 
1513             $this->rollbackFileTransaction();
 
1514             $this->configSet('default_channel', $savechannel);
 
1518         $this->log(1, "\nBuild process completed successfully");
 
1519         foreach ($built as $ext) {
 
1520             $bn = basename($ext['file']);
 
1521             list($_ext_name, $_ext_suff) = explode('.', $bn);
 
1522             if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
 
1523                 if (extension_loaded($_ext_name)) {
 
1524                     $this->raiseError("Extension '$_ext_name' already loaded. " .
 
1525                                       'Please unload it in your php.ini file ' .
 
1526                                       'prior to install or upgrade');
 
1533             $dest = $ext['dest'];
 
1534             $packagingroot = '';
 
1535             if (isset($this->_options['packagingroot'])) {
 
1536                 $packagingroot = $this->_options['packagingroot'];
 
1539             $copyto = $this->_prependPath($dest, $packagingroot);
 
1540             $extra  = $copyto != $dest ? " as '$copyto'" : '';
 
1541             $this->log(1, "Installing '$dest'$extra");
 
1543             $copydir = dirname($copyto);
 
1544             // pretty much nothing happens if we are only registering the install
 
1545             if (empty($this->_options['register-only'])) {
 
1546                 if (!file_exists($copydir) || !is_dir($copydir)) {
 
1547                     if (!$this->mkDirHier($copydir)) {
 
1548                         return $this->raiseError("failed to mkdir $copydir",
 
1549                             PEAR_INSTALLER_FAILED);
 
1552                     $this->log(3, "+ mkdir $copydir");
 
1555                 if (!@copy($ext['file'], $copyto)) {
 
1556                     return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
 
1559                 $this->log(3, "+ cp $ext[file] $copyto");
 
1560                 $this->addFileOperation('rename', array($ext['file'], $copyto));
 
1562                     $mode = 0666 & ~(int)octdec($this->config->get('umask'));
 
1563                     $this->addFileOperation('chmod', array($mode, $copyto));
 
1564                     if (!@chmod($copyto, $mode)) {
 
1565                         $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
 
1574                 'installed_as' => $dest,
 
1575                 'php_api'      => $ext['php_api'],
 
1576                 'zend_mod_api' => $ext['zend_mod_api'],
 
1577                 'zend_ext_api' => $ext['zend_ext_api'],
 
1580             if ($filelist->getPackageXmlVersion() == '1.0') {
 
1581                 $filelist->installedFile($bn, $data);
 
1583                 $filelist->installedFile($bn, array('attribs' => $data));
 
1589     function &getUninstallPackages()
 
1591         return $this->_downloadedPackages;
 
1596      * Uninstall a package
 
1598      * This method removes all files installed by the application, and then
 
1599      * removes any empty directories.
 
1600      * @param string package name
 
1601      * @param array Command-line options.  Possibilities include:
 
1603      *              - installroot: base installation dir, if not the default
 
1604      *              - register-only : update registry but don't remove files
 
1605      *              - nodeps: do not process dependencies of other packages to ensure
 
1606      *                        uninstallation does not break things
 
1608     function uninstall($package, $options = array())
 
1610         $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
 
1611         $this->config->setInstallRoot($installRoot);
 
1613         $this->installroot = '';
 
1614         $this->_registry = &$this->config->getRegistry();
 
1615         if (is_object($package)) {
 
1616             $channel = $package->getChannel();
 
1618             $package = $pkg->getPackage();
 
1621             $info = $this->_registry->parsePackageName($package,
 
1622                 $this->config->get('default_channel'));
 
1623             $channel = $info['channel'];
 
1624             $package = $info['package'];
 
1627         $savechannel = $this->config->get('default_channel');
 
1628         $this->configSet('default_channel', $channel);
 
1629         if (!is_object($pkg)) {
 
1630             $pkg = $this->_registry->getPackage($package, $channel);
 
1634             $this->configSet('default_channel', $savechannel);
 
1635             return $this->raiseError($this->_registry->parsedPackageNameToString(
 
1637                     'channel' => $channel,
 
1638                     'package' => $package
 
1639                 ), true) . ' not installed');
 
1642         if ($pkg->getInstalledBinary()) {
 
1643             // this is just an alias for a binary package
 
1644             return $this->_registry->deletePackage($package, $channel);
 
1647         $filelist = $pkg->getFilelist();
 
1648         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
 
1649         if (!class_exists('PEAR_Dependency2')) {
 
1650             require_once 'PEAR/Dependency2.php';
 
1653         $depchecker = new PEAR_Dependency2($this->config, $options,
 
1654             array('channel' => $channel, 'package' => $package),
 
1655             PEAR_VALIDATE_UNINSTALLING);
 
1656         $e = $depchecker->validatePackageUninstall($this);
 
1657         PEAR::staticPopErrorHandling();
 
1658         if (PEAR::isError($e)) {
 
1659             if (!isset($options['ignore-errors'])) {
 
1660                 return $this->raiseError($e);
 
1663             if (!isset($options['soft'])) {
 
1664                 $this->log(0, 'WARNING: ' . $e->getMessage());
 
1666         } elseif (is_array($e)) {
 
1667             if (!isset($options['soft'])) {
 
1668                 $this->log(0, $e[0]);
 
1672         $this->pkginfo = &$pkg;
 
1673         // pretty much nothing happens if we are only registering the uninstall
 
1674         if (empty($options['register-only'])) {
 
1675             // {{{ Delete the files
 
1676             $this->startFileTransaction();
 
1677             PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
 
1678             if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
 
1679                 PEAR::popErrorHandling();
 
1680                 $this->rollbackFileTransaction();
 
1681                 $this->configSet('default_channel', $savechannel);
 
1682                 if (!isset($options['ignore-errors'])) {
 
1683                     return $this->raiseError($err);
 
1686                 if (!isset($options['soft'])) {
 
1687                     $this->log(0, 'WARNING: ' . $err->getMessage());
 
1690                 PEAR::popErrorHandling();
 
1693             if (!$this->commitFileTransaction()) {
 
1694                 $this->rollbackFileTransaction();
 
1695                 if (!isset($options['ignore-errors'])) {
 
1696                     return $this->raiseError("uninstall failed");
 
1699                 if (!isset($options['soft'])) {
 
1700                     $this->log(0, 'WARNING: uninstall failed');
 
1703                 $this->startFileTransaction();
 
1704                 $dirtree = $pkg->getDirTree();
 
1705                 if ($dirtree === false) {
 
1706                     $this->configSet('default_channel', $savechannel);
 
1707                     return $this->_registry->deletePackage($package, $channel);
 
1710                 // attempt to delete empty directories
 
1711                 uksort($dirtree, array($this, '_sortDirs'));
 
1712                 foreach($dirtree as $dir => $notused) {
 
1713                     $this->addFileOperation('rmdir', array($dir));
 
1716                 if (!$this->commitFileTransaction()) {
 
1717                     $this->rollbackFileTransaction();
 
1718                     if (!isset($options['ignore-errors'])) {
 
1719                         return $this->raiseError("uninstall failed");
 
1722                     if (!isset($options['soft'])) {
 
1723                         $this->log(0, 'WARNING: uninstall failed');
 
1730         $this->configSet('default_channel', $savechannel);
 
1731         // Register that the package is no longer installed
 
1732         return $this->_registry->deletePackage($package, $channel);
 
1736      * Sort a list of arrays of array(downloaded packagefilename) by dependency.
 
1738      * It also removes duplicate dependencies
 
1739      * @param array an array of PEAR_PackageFile_v[1/2] objects
 
1740      * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
 
1742     function sortPackagesForUninstall(&$packages)
 
1744         $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
 
1745         if (PEAR::isError($this->_dependencyDB)) {
 
1746             return $this->_dependencyDB;
 
1748         usort($packages, array(&$this, '_sortUninstall'));
 
1751     function _sortUninstall($a, $b)
 
1753         if (!$a->getDeps() && !$b->getDeps()) {
 
1754             return 0; // neither package has dependencies, order is insignificant
 
1756         if ($a->getDeps() && !$b->getDeps()) {
 
1757             return -1; // $a must be installed after $b because $a has dependencies
 
1759         if (!$a->getDeps() && $b->getDeps()) {
 
1760             return 1; // $b must be installed after $a because $b has dependencies
 
1762         // both packages have dependencies
 
1763         if ($this->_dependencyDB->dependsOn($a, $b)) {
 
1766         if ($this->_dependencyDB->dependsOn($b, $a)) {
 
1774     function _sortDirs($a, $b)
 
1776         if (strnatcmp($a, $b) == -1) return 1;
 
1777         if (strnatcmp($a, $b) == 1) return -1;
 
1783     // {{{ _buildCallback()
 
1785     function _buildCallback($what, $data)
 
1787         if (($what == 'cmdoutput' && $this->debug > 1) ||
 
1788             ($what == 'output' && $this->debug > 0)) {
 
1789             $this->ui->outputData(rtrim($data), 'build');