PageRenderTime 64ms CodeModel.GetById 3ms app.highlight 49ms RepoModel.GetById 1ms app.codeStats 0ms

/gespac/config/PEAR/PEAR/Installer.php

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