home *** CD-ROM | disk | FTP | other *** search
/ PC World 2001 August / PCWorld_2001-08_cd.bin / Komunikace / phptriad / phptriadsetup2-11.exe / php / pear / PEAR / Installer.php
PHP Script  |  2001-01-10  |  14KB  |  508 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4.0                                                      |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2001 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stig Bakken <ssb@fast.no>                                   |
  17. // |                                                                      |
  18. // +----------------------------------------------------------------------+
  19. //
  20.  
  21. require_once "PEAR.php";
  22.  
  23. register_shutdown_function("__PEAR_Installer_cleanup");
  24.  
  25. /**
  26.  * Administration class used to install PEAR packages and maintain the
  27.  * class definition cache.
  28.  *
  29.  * @since PHP 4.0.2
  30.  * @author Stig Bakken <ssb@fast.no>
  31.  */
  32. class PEAR_Installer extends PEAR
  33. {
  34.     // {{{ properties
  35.  
  36.     /** stack of elements, gives some sort of XML context */
  37.     var $element_stack;
  38.  
  39.     /** name of currently parsed XML element */
  40.     var $current_element;
  41.  
  42.     /** array of attributes of the currently parsed XML element */
  43.     var $current_attributes = array();
  44.  
  45.     /** assoc with information about the package */
  46.     var $pkginfo = array();
  47.  
  48.     /** name of the package directory, for example Foo-1.0 */
  49.     var $pkgdir;
  50.  
  51.     /** directory where PHP code files go */
  52.     var $pear_phpdir = PEAR_INSTALL_DIR;
  53.  
  54.     /** directory where PHP extension files go */
  55.     var $pear_extdir = PEAR_EXTENSION_DIR;
  56.  
  57.     /** directory where documentation goes */
  58.     var $pear_docdir = '';
  59.  
  60.     /** directory where the package wants to put files, relative
  61.      *  to one of the three previous dirs
  62.      */
  63.     var $destdir = '';
  64.  
  65.     /** debug mode (boolean) */
  66.     var $debug = false;
  67.  
  68.     /** class loading cache */
  69.     var $cache = array();
  70.  
  71.     /** temporary directory */
  72.     var $tmpdir;
  73.  
  74.     /** file pointer for cache file if open */
  75.     var $cache_fp;
  76.  
  77.     // }}}
  78.  
  79.     // {{{ constructor
  80.  
  81.     function PEAR_Installer() {
  82.     $this->PEAR();
  83.     $this->cacheLoad("$this->pear_phpdir/.cache");
  84.     }
  85.  
  86.     // }}}
  87.     // {{{ destructor
  88.  
  89.     function _PEAR_Installer() {
  90.     $this->_PEAR();
  91.     if ($this->tmpdir && is_dir($this->tmpdir)) {
  92.         system("rm -rf $this->tmpdir");
  93.     }
  94.     if ($this->cache_fp && is_resource($this->cache_fp)) {
  95.         flock($this->cache_fp, LOCK_UN);
  96.         fclose($this->cache_fp);
  97.     }
  98.     $this->tmpdir = null;
  99.     $this->cache_fp = null;
  100.     }
  101.  
  102.     // }}}
  103.  
  104.     // {{{ raiseError()
  105.  
  106.     function raiseError($msg)
  107.     {
  108.         return new PEAR_Error("$msg\n", 0, PEAR_ERROR_DIE);
  109.     }
  110.  
  111.     // }}}
  112.     // {{{ mkDirHier()
  113.  
  114.     function mkDirHier($dir)
  115.     {
  116.         $dirstack = array();
  117.         // XXX FIXME this does not work on Windows!
  118.         while (!is_dir($dir) && $dir != "/") {
  119.             array_unshift($dirstack, $dir);
  120.             $dir = dirname($dir);
  121.         }
  122.         while ($newdir = array_shift($dirstack)) {
  123.             if (mkdir($newdir, 0777)) {
  124.                 $this->log(1, "created dir $newdir");
  125.             } else {
  126.                 return $this->raiseError("mkdir($newdir) failed");
  127.             }
  128.         }
  129.     }
  130.  
  131.     // }}}
  132.     // {{{ log()
  133.  
  134.     function log($level, $msg)
  135.     {
  136.         if ($this->debug >= $level) {
  137.             print "$msg\n";
  138.         }
  139.     }
  140.  
  141.     // }}}
  142.  
  143.     // {{{ cacheLock()
  144.  
  145.     function cacheLock() {
  146.     $fp = $this->cache_fp;
  147.     if (!is_resource($fp)) {
  148.         $this->cache_fp = $fp = fopen($this->cache_file, "r");
  149.     }
  150.     return flock($fp, LOCK_EX);
  151.     }
  152.  
  153.     // }}}
  154.     // {{{ cacheUnlock()
  155.  
  156.     function cacheUnlock() {
  157.     $fp = $this->cache_fp;
  158.     if (!is_resource($fp)) {
  159.         $this->cache_fp = $fp = fopen($this->cache_file, "r");
  160.         $doclose = true;
  161.     }
  162.     $ret = flock($fp, LOCK_EX);
  163.     if ($doclose) {
  164.         fclose($fp);
  165.     }
  166.     return $ret;
  167.     }
  168.  
  169.     // }}}
  170.     // {{{ cacheLoad()
  171.  
  172.     function cacheLoad($file) {
  173.     $this->cache_file = $file;
  174.     if (!file_exists($file)) {
  175.         touch($file);
  176.     }
  177.     $fp = $this->cache_fp = fopen($file, "r");
  178.     $this->cacheLock();
  179.     while ($line = fgets($fp, 2048)) {
  180.         list($type, $name, $file) = explode(" ", trim($line));
  181.         $this->cache[$type][$name] = $file;
  182.     }
  183.     }
  184.  
  185.     // }}}
  186.     // {{{ cacheSave()
  187.  
  188.     function cacheSave() {
  189.     $fp = $this->cache_fp;
  190.     $wfp = fopen($this->cache_file, "w");
  191.     if (!$wfp) {
  192.         return false;
  193.     }
  194.     if (is_resource($fp)) {
  195.         fclose($fp);
  196.     }
  197.     $this->cache_fp = $fp = $wfp;
  198.     reset($this->cache);
  199.     while (list($type, $entry) = each($this->cache)) {
  200.         reset($entry);
  201.         while (list($name, $file) = each($entry)) {
  202.         fwrite($fp, "$type $name $file\n");
  203.         }
  204.     }
  205.     fclose($fp);
  206.     $this->cache_fp = $fp = null;
  207.     }
  208.  
  209.     // }}}
  210.     // {{{ cacheUpdateFrom()
  211.  
  212.     function cacheUpdateFrom($file) {
  213.         /*
  214.     $new = $this->classesDeclaredBy($file);
  215.     reset($new);
  216.     while (list($i, $name) = each($new)) {
  217.         $this->cache['class'][$name] = $file;
  218.     }
  219.         */
  220.     }
  221.  
  222.     // }}}
  223.  
  224.     // {{{ install()
  225.  
  226.     /**
  227.      * Installs the files within the package file specified.
  228.      *
  229.      * @param $pkgfile path to the package file
  230.      *
  231.      * @return bool true if successful, false if not
  232.      */
  233.     function install($pkgfile) {
  234.         global $_PEAR_Installer_tempfiles;
  235.         if (preg_match('#^(http|ftp)://#', $pkgfile)) {
  236.             $need_download = true;
  237.         } elseif (!file_exists($pkgfile)) {
  238.         return $this->raiseError("$pkgfile: no such file");
  239.     }
  240.  
  241.         if ($need_download) {
  242.             $file = basename($pkgfile);
  243.             $downloaddir = "/tmp/pearinstall";
  244.             $this->mkDirHier($downloaddir);
  245.             $downloadfile = "$downloaddir/$file";
  246.             $this->log(1, "downloading $pkgfile...");
  247.             $fp = @fopen($pkgfile, "r");
  248.             if (!$fp) {
  249.                 return $this->raiseError("$pkgfile: failed to download ($php_errormsg)");
  250.             }
  251.             $wp = @fopen($downloadfile, "w");
  252.             if (!$wp) {
  253.                 return $this->raiseError("$downloadfile: write failed ($php_errormsg)");
  254.             }
  255.             $bytes = 0;
  256.             while ($data = @fread($fp, 16384)) {
  257.                 $bytes += strlen($data);
  258.                 if (!@fwrite($wp, $data)) {
  259.                     return $this->raiseError("$downloadfile: write failed ($php_errormsg)");
  260.                 }
  261.             }
  262.             $pkgfile = $downloadfile;
  263.             fclose($fp);
  264.             fclose($wp);
  265.             $this->log(1, "...done, $bytes bytes");
  266.             $_PEAR_Installer_tempfiles[] = $downloadfile;
  267.         }
  268.     $fp = popen("gzip -dc $pkgfile | tar -tf -", "r");
  269.     if (!$fp) {
  270.         return $this->raiseError("Unable to examine $pkgfile (gzip or tar failed)");
  271.     }
  272.     while ($line = fgets($fp, 4096)) {
  273.         $line = rtrim($line);
  274.         if (preg_match('!^[^/]+/package.xml$!', $line)) {
  275.         if ($descfile) {
  276.             return $this->raiseError("Invalid package: multiple package.xml files at depth one!");
  277.         }
  278.         $descfile = $line;
  279.         }
  280.     }
  281.     pclose($fp);
  282.  
  283.     if (!$descfile) {
  284.         return $this->raiseError("Invalid package: no package.xml file found!");
  285.     }
  286.  
  287.     $this->tmpdir = tempnam("/tmp", "pear");
  288.         unlink($this->tmpdir);
  289.     if (!mkdir($this->tmpdir, 0755)) {
  290.         return $this->raiseError("Unable to create temporary directory $this->tmpdir.");
  291.     }
  292.         $_PEAR_Installer_tempfiles[] = $this->tmpdir;
  293.     $pwd = trim(`pwd`);
  294.  
  295.     if (substr($pkgfile, 0, 1) == "/") {
  296.         $pkgfilepath = $pkgfile;
  297.     } else {
  298.         $pkgfilepath = $pwd.'/'.$pkgfile;
  299.     }
  300.  
  301.     if (!chdir($this->tmpdir)) {
  302.         return $this->raiseError("Unable to chdir to $this->tmpdir.");
  303.     }
  304.  
  305.     system("gzip -dc $pkgfilepath | tar -xf -");
  306.  
  307.     if (!file_exists($descfile)) {
  308.         return $this->raiseError("Huh?  No package.xml file after extracting the archive.");
  309.     }
  310.  
  311.     $this->pkgdir = dirname($descfile);
  312.  
  313.     $fp = fopen($descfile, "r");
  314.     $xp = xml_parser_create();
  315.     if (!$xp) {
  316.         return $this->raiseError("Unable to create XML parser.");
  317.     }
  318.     xml_set_object($xp, &$this);
  319.     xml_set_element_handler($xp, "startHandler", "endHandler");
  320.     xml_set_character_data_handler($xp, "charHandler");
  321.     xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false);
  322.  
  323.     $this->element_stack = array();
  324.     $this->pkginfo = array();
  325.     $this->current_element = false;
  326.     $destdir = '';
  327.  
  328.     while ($data = fread($fp, 2048)) {
  329.         if (!xml_parse($xp, $data, feof($fp))) {
  330.         $err = $this->raiseError(sprintf("XML error: %s at line %d",
  331.                             xml_error_string(xml_get_error_code($xp)),
  332.                             xml_get_current_line_number($xp)));
  333.         xml_parser_free($xp);
  334.         return $err;
  335.         }
  336.     }
  337.  
  338.     xml_parser_free($xp);
  339.  
  340.     return true;
  341.     }
  342.  
  343.     // }}}
  344.     // {{{ startHandler()
  345.  
  346.     function startHandler($xp, $name, $attribs) {
  347.     array_push($this->element_stack, $name);
  348.     $this->current_element = $name;
  349.     $this->current_attributes = $attribs;
  350.     switch ($name) {
  351.         case "Package":
  352.         if (strtolower($attribs["Type"]) != "binary") {
  353.             return $this->raiseError("Invalid package: only binary packages supported yet.");
  354.         }
  355.         $this->pkginfo['pkgtype'] = strtolower($attribs["Type"]);
  356.         break;
  357.     }
  358.     }
  359.  
  360.     // }}}
  361.     // {{{ endHandler()
  362.  
  363.     function endHandler($xp, $name) {
  364.     array_pop($this->element_stack);
  365.     $this->current_element = $this->element_stack[sizeof($this->element_stack)-1];
  366.     }
  367.  
  368.     // }}}
  369.     // {{{ charHandler()
  370.  
  371.     function charHandler($xp, $data) {
  372.         // XXX FIXME: $data may be incomplete, all of this code should
  373.         // actually be in endHandler.
  374.         //
  375.     switch ($this->current_element) {
  376.         case "Dir":
  377.         if (!$this->pear_phpdir) {
  378.             break;
  379.         }
  380.                 $type = $this->current_attributes["Type"];
  381.         $dir = trim($data);
  382.         $d = "$this->pear_phpdir/$this->destdir/$dir";
  383.         if (substr($dir, 0, 1) == "/") {
  384.             $this->destdir = substr($dir, 1);
  385.         } else {
  386.                     $this->destdir = $dir;
  387.                 }
  388.         break;
  389.         if (is_file($d)) {
  390.                     return $this->raiseError("mkdir $d failed: is a file");
  391.         }
  392.         if (is_dir($d)) {
  393.             break;
  394.         }
  395.         if (!mkdir($d, 0755)) {
  396.                     return $this->raiseError("mkdir $d failed");
  397.             break;
  398.         }
  399.                 $this->log(1, "created dir $d");
  400.         break;
  401.         case "File":
  402.         if (!$this->pear_phpdir) {
  403.             break;
  404.         }
  405.                 $type = strtolower($this->current_attributes["Role"]);
  406.         $file = trim($data);
  407.                 $updatecache = false;
  408.                 switch ($type) {
  409.                     case "test":
  410.                         $d = ""; // don't install test files for now
  411.                         break;
  412.                     default:
  413.                         if ($this->destdir) {
  414.                             $d = "$this->pear_phpdir/$this->destdir";
  415.                         } else {
  416.                             $d = $this->pear_phpdir;
  417.                         }
  418.                         $updatecache = true;
  419.                         break;
  420.                 }
  421.                 if (!$d) {
  422.                     break;
  423.                 }
  424.                 if (!is_dir($d)) {
  425.                     $this->mkDirHier($d);
  426.                 }
  427.                 $bfile = basename($file);
  428.         if (!copy("$this->pkgdir/$file", "$d/$bfile")) {
  429.             $this->log(0, "failed to copy $this->pkgdir/$file to $d");
  430.             break;
  431.         }
  432.                 if ($updatecache) {
  433.                     $this->cacheUpdateFrom("$d/$file");
  434.                 }
  435.                 $this->log(1, "installed $d/$bfile");
  436.         break;
  437.     }
  438.     }
  439.  
  440.     // }}}
  441.  
  442.     // {{{ classesDeclaredBy()
  443.  
  444.     /**
  445.      * Find out which new classes are defined by a file.
  446.      *
  447.      * @param $file file name passed to "include"
  448.      *
  449.      * @return array classes that were defined
  450.      */
  451.     function classesDeclaredBy($file) {
  452.     $before = get_declared_classes();
  453.         ob_start();
  454.     include($file);
  455.         ob_end_clean();
  456.     $after = get_declared_classes();
  457.     // using array_slice to renumber array
  458.     $diff = array_slice(array_diff($after, $before), 0);
  459.     return $diff;
  460.     }
  461.  
  462. // }}}
  463.  
  464.     // {{{ lockDir()
  465.  
  466.     /**
  467.      * Uses advisory locking (flock) to temporarily claim $dir as its
  468.      * own.
  469.      *
  470.      * @param $dir the directory to lock
  471.      *
  472.      * @return bool true if successful, false if not
  473.      */
  474.     function lockDir($dir) {
  475.     $lockfile = "$dir/.lock";
  476.     if (!file_exists($lockfile)) {
  477.         if (!touch($lockfile)) {
  478.         // could not create lockfile!
  479.         return false;
  480.         }
  481.     }
  482.     $fp = fopen($lockfile, "r");
  483.     if (!flock($fp, LOCK_EX)) {
  484.         // could not create lock!
  485.         return false;
  486.     }
  487.     return true;
  488.     }
  489.  
  490.     // }}}
  491. }
  492.  
  493. function __PEAR_Installer_cleanup()
  494. {
  495.     global $_PEAR_Installer_tempfiles;
  496.     if (is_array($_PEAR_Installer_tempfiles)) {
  497.         while ($file = array_shift($_PEAR_Installer_tempfiles)) {
  498.             if (is_dir($file)) {
  499.                 system("rm -rf $file"); // XXX FIXME Windows
  500.             } else {
  501.                 unlink($file);
  502.             }
  503.         }
  504.     }
  505. }
  506.  
  507. ?>
  508.