PageRenderTime 51ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/script/lib/PEAR/Installer.php

https://bitbucket.org/renaatdemuynck/chamilo
PHP | 2151 lines | 1713 code | 198 blank | 240 comment | 289 complexity | 265b0b0c48bed2dc775fc3f9f294511e MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT, GPL-2.0

Large files files are truncated, but you can click here to view the full 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. * Used for installation groups in package.xml 2.0 and platform exceptions
  21. */
  22. require_once 'OS/Guess.php';
  23. require_once 'PEAR/Downloader.php';
  24. define('PEAR_INSTALLER_NOBINARY', - 240);
  25. /**
  26. * Administration class used to install PEAR packages and maintain the
  27. * installed package database.
  28. *
  29. * @category pear
  30. * @package PEAR
  31. * @author Stig Bakken <ssb@php.net>
  32. * @author Tomas V.V. Cox <cox@idecnet.com>
  33. * @author Martin Jansen <mj@php.net>
  34. * @author Greg Beaver <cellog@php.net>
  35. * @copyright 1997-2009 The Authors
  36. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  37. * @version Release: 1.9.1
  38. * @link http://pear.php.net/package/PEAR
  39. * @since Class available since Release 0.1
  40. */
  41. class PEAR_Installer extends PEAR_Downloader
  42. {
  43. // {{{ properties
  44. /** name of the package directory, for example Foo-1.0
  45. * @var string
  46. */
  47. var $pkgdir;
  48. /** directory where PHP code files go
  49. * @var string
  50. */
  51. var $phpdir;
  52. /** directory where PHP extension files go
  53. * @var string
  54. */
  55. var $extdir;
  56. /** directory where documentation goes
  57. * @var string
  58. */
  59. var $docdir;
  60. /** installation root directory (ala PHP's INSTALL_ROOT or
  61. * automake's DESTDIR
  62. * @var string
  63. */
  64. var $installroot = '';
  65. /** debug level
  66. * @var int
  67. */
  68. var $debug = 1;
  69. /** temporary directory
  70. * @var string
  71. */
  72. var $tmpdir;
  73. /**
  74. * PEAR_Registry object used by the installer
  75. * @var PEAR_Registry
  76. */
  77. var $registry;
  78. /**
  79. * array of PEAR_Downloader_Packages
  80. * @var array
  81. */
  82. var $_downloadedPackages;
  83. /** List of file transactions queued for an install/upgrade/uninstall.
  84. *
  85. * Format:
  86. * array(
  87. * 0 => array("rename => array("from-file", "to-file")),
  88. * 1 => array("delete" => array("file-to-delete")),
  89. * ...
  90. * )
  91. *
  92. * @var array
  93. */
  94. var $file_operations = array();
  95. // }}}
  96. // {{{ constructor
  97. /**
  98. * PEAR_Installer constructor.
  99. *
  100. * @param object $ui user interface object (instance of PEAR_Frontend_*)
  101. *
  102. * @access public
  103. */
  104. function PEAR_Installer(&$ui)
  105. {
  106. parent :: PEAR_Common();
  107. $this->setFrontendObject($ui);
  108. $this->debug = $this->config->get('verbose');
  109. }
  110. function setOptions($options)
  111. {
  112. $this->_options = $options;
  113. }
  114. function setConfig(&$config)
  115. {
  116. $this->config = &$config;
  117. $this->_registry = &$config->getRegistry();
  118. }
  119. // }}}
  120. function _removeBackups($files)
  121. {
  122. foreach ($files as $path)
  123. {
  124. $this->addFileOperation('removebackup', array($path));
  125. }
  126. }
  127. // {{{ _deletePackageFiles()
  128. /**
  129. * Delete a package's installed files, does not remove empty directories.
  130. *
  131. * @param string package name
  132. * @param string channel name
  133. * @param bool if true, then files are backed up first
  134. * @return bool TRUE on success, or a PEAR error on failure
  135. * @access protected
  136. */
  137. function _deletePackageFiles($package, $channel = false, $backup = false)
  138. {
  139. if (! $channel)
  140. {
  141. $channel = 'pear.php.net';
  142. }
  143. if (! strlen($package))
  144. {
  145. return $this->raiseError("No package to uninstall given");
  146. }
  147. if (strtolower($package) == 'pear' && $channel == 'pear.php.net')
  148. {
  149. // to avoid race conditions, include all possible needed files
  150. require_once 'PEAR/Task/Common.php';
  151. require_once 'PEAR/Task/Replace.php';
  152. require_once 'PEAR/Task/Unixeol.php';
  153. require_once 'PEAR/Task/Windowseol.php';
  154. require_once 'PEAR/PackageFile/v1.php';
  155. require_once 'PEAR/PackageFile/v2.php';
  156. require_once 'PEAR/PackageFile/Generator/v1.php';
  157. require_once 'PEAR/PackageFile/Generator/v2.php';
  158. }
  159. $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
  160. if ($filelist == null)
  161. {
  162. return $this->raiseError("$channel/$package not installed");
  163. }
  164. $ret = array();
  165. foreach ($filelist as $file => $props)
  166. {
  167. if (empty($props['installed_as']))
  168. {
  169. continue;
  170. }
  171. $path = $props['installed_as'];
  172. if ($backup)
  173. {
  174. $this->addFileOperation('backup', array($path));
  175. $ret[] = $path;
  176. }
  177. $this->addFileOperation('delete', array($path));
  178. }
  179. if ($backup)
  180. {
  181. return $ret;
  182. }
  183. return true;
  184. }
  185. // }}}
  186. // {{{ _installFile()
  187. /**
  188. * @param string filename
  189. * @param array attributes from <file> tag in package.xml
  190. * @param string path to install the file in
  191. * @param array options from command-line
  192. * @access private
  193. */
  194. function _installFile($file, $atts, $tmp_path, $options)
  195. {
  196. // {{{ return if this file is meant for another platform
  197. static $os;
  198. if (! isset($this->_registry))
  199. {
  200. $this->_registry = &$this->config->getRegistry();
  201. }
  202. if (isset($atts['platform']))
  203. {
  204. if (empty($os))
  205. {
  206. $os = new OS_Guess();
  207. }
  208. if (strlen($atts['platform']) && $atts['platform']{0} == '!')
  209. {
  210. $negate = true;
  211. $platform = substr($atts['platform'], 1);
  212. }
  213. else
  214. {
  215. $negate = false;
  216. $platform = $atts['platform'];
  217. }
  218. if ((bool) $os->matchSignature($platform) === $negate)
  219. {
  220. $this->log(3, "skipped $file (meant for $atts[platform], we are " . $os->getSignature() . ")");
  221. return PEAR_INSTALLER_SKIPPED;
  222. }
  223. }
  224. // }}}
  225. $channel = $this->pkginfo->getChannel();
  226. // {{{ assemble the destination paths
  227. switch ($atts['role'])
  228. {
  229. case 'src' :
  230. case 'extsrc' :
  231. $this->source_files ++;
  232. return;
  233. case 'doc' :
  234. case 'data' :
  235. case 'test' :
  236. $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) . DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
  237. unset($atts['baseinstalldir']);
  238. break;
  239. case 'ext' :
  240. case 'php' :
  241. $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
  242. break;
  243. case 'script' :
  244. $dest_dir = $this->config->get('bin_dir', null, $channel);
  245. break;
  246. default :
  247. return $this->raiseError("Invalid role `$atts[role]' for file $file");
  248. }
  249. $save_destdir = $dest_dir;
  250. if (! empty($atts['baseinstalldir']))
  251. {
  252. $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  253. }
  254. if (dirname($file) != '.' && empty($atts['install-as']))
  255. {
  256. $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  257. }
  258. if (empty($atts['install-as']))
  259. {
  260. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  261. }
  262. else
  263. {
  264. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  265. }
  266. $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  267. // Clean up the DIRECTORY_SEPARATOR mess
  268. $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  269. list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"), array(DIRECTORY_SEPARATOR,
  270. DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), array($dest_file, $orig_file));
  271. $final_dest_file = $installed_as = $dest_file;
  272. if (isset($this->_options['packagingroot']))
  273. {
  274. $installedas_dest_dir = dirname($final_dest_file);
  275. $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  276. $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
  277. }
  278. else
  279. {
  280. $installedas_dest_dir = dirname($final_dest_file);
  281. $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  282. }
  283. $dest_dir = dirname($final_dest_file);
  284. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  285. if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file)))
  286. {
  287. return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
  288. }
  289. // }}}
  290. if (empty($this->_options['register-only']) && (! file_exists($dest_dir) || ! is_dir($dest_dir)))
  291. {
  292. if (! $this->mkDirHier($dest_dir))
  293. {
  294. return $this->raiseError("failed to mkdir $dest_dir", PEAR_INSTALLER_FAILED);
  295. }
  296. $this->log(3, "+ mkdir $dest_dir");
  297. }
  298. // pretty much nothing happens if we are only registering the install
  299. if (empty($this->_options['register-only']))
  300. {
  301. if (empty($atts['replacements']))
  302. {
  303. if (! file_exists($orig_file))
  304. {
  305. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  306. }
  307. if (! @copy($orig_file, $dest_file))
  308. {
  309. return $this->raiseError("failed to write $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  310. }
  311. $this->log(3, "+ cp $orig_file $dest_file");
  312. if (isset($atts['md5sum']))
  313. {
  314. $md5sum = md5_file($dest_file);
  315. }
  316. }
  317. else
  318. {
  319. // {{{ file with replacements
  320. if (! file_exists($orig_file))
  321. {
  322. return $this->raiseError("file does not exist", PEAR_INSTALLER_FAILED);
  323. }
  324. $contents = file_get_contents($orig_file);
  325. if ($contents === false)
  326. {
  327. $contents = '';
  328. }
  329. if (isset($atts['md5sum']))
  330. {
  331. $md5sum = md5($contents);
  332. }
  333. $subst_from = $subst_to = array();
  334. foreach ($atts['replacements'] as $a)
  335. {
  336. $to = '';
  337. if ($a['type'] == 'php-const')
  338. {
  339. if (preg_match('/^[a-z0-9_]+\\z/i', $a['to']))
  340. {
  341. eval("\$to = $a[to];");
  342. }
  343. else
  344. {
  345. if (! isset($options['soft']))
  346. {
  347. $this->log(0, "invalid php-const replacement: $a[to]");
  348. }
  349. continue;
  350. }
  351. }
  352. elseif ($a['type'] == 'pear-config')
  353. {
  354. if ($a['to'] == 'master_server')
  355. {
  356. $chan = $this->_registry->getChannel($channel);
  357. if (! PEAR :: isError($chan))
  358. {
  359. $to = $chan->getServer();
  360. }
  361. else
  362. {
  363. $to = $this->config->get($a['to'], null, $channel);
  364. }
  365. }
  366. else
  367. {
  368. $to = $this->config->get($a['to'], null, $channel);
  369. }
  370. if (is_null($to))
  371. {
  372. if (! isset($options['soft']))
  373. {
  374. $this->log(0, "invalid pear-config replacement: $a[to]");
  375. }
  376. continue;
  377. }
  378. }
  379. elseif ($a['type'] == 'package-info')
  380. {
  381. if ($t = $this->pkginfo->packageInfo($a['to']))
  382. {
  383. $to = $t;
  384. }
  385. else
  386. {
  387. if (! isset($options['soft']))
  388. {
  389. $this->log(0, "invalid package-info replacement: $a[to]");
  390. }
  391. continue;
  392. }
  393. }
  394. if (! is_null($to))
  395. {
  396. $subst_from[] = $a['from'];
  397. $subst_to[] = $to;
  398. }
  399. }
  400. $this->log(3, "doing " . sizeof($subst_from) . " substitution(s) for $final_dest_file");
  401. if (sizeof($subst_from))
  402. {
  403. $contents = str_replace($subst_from, $subst_to, $contents);
  404. }
  405. $wp = @fopen($dest_file, "wb");
  406. if (! is_resource($wp))
  407. {
  408. return $this->raiseError("failed to create $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  409. }
  410. if (@fwrite($wp, $contents) === false)
  411. {
  412. return $this->raiseError("failed writing to $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  413. }
  414. fclose($wp);
  415. // }}}
  416. }
  417. // {{{ check the md5
  418. if (isset($md5sum))
  419. {
  420. if (strtolower($md5sum) === strtolower($atts['md5sum']))
  421. {
  422. $this->log(2, "md5sum ok: $final_dest_file");
  423. }
  424. else
  425. {
  426. if (empty($options['force']))
  427. {
  428. // delete the file
  429. if (file_exists($dest_file))
  430. {
  431. unlink($dest_file);
  432. }
  433. if (! isset($options['ignore-errors']))
  434. {
  435. return $this->raiseError("bad md5sum for file $final_dest_file", PEAR_INSTALLER_FAILED);
  436. }
  437. if (! isset($options['soft']))
  438. {
  439. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  440. }
  441. }
  442. else
  443. {
  444. if (! isset($options['soft']))
  445. {
  446. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  447. }
  448. }
  449. }
  450. }
  451. // }}}
  452. // {{{ set file permissions
  453. if (! OS_WINDOWS)
  454. {
  455. if ($atts['role'] == 'script')
  456. {
  457. $mode = 0777 & ~ (int) octdec($this->config->get('umask'));
  458. $this->log(3, "+ chmod +x $dest_file");
  459. }
  460. else
  461. {
  462. $mode = 0666 & ~ (int) octdec($this->config->get('umask'));
  463. }
  464. if ($atts['role'] != 'src')
  465. {
  466. $this->addFileOperation("chmod", array($mode, $dest_file));
  467. if (! @chmod($dest_file, $mode))
  468. {
  469. if (! isset($options['soft']))
  470. {
  471. $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
  472. }
  473. }
  474. }
  475. }
  476. // }}}
  477. if ($atts['role'] == 'src')
  478. {
  479. rename($dest_file, $final_dest_file);
  480. $this->log(2, "renamed source file $dest_file to $final_dest_file");
  481. }
  482. else
  483. {
  484. $this->addFileOperation("rename", array($dest_file, $final_dest_file, $atts['role'] == 'ext'));
  485. }
  486. }
  487. // Store the full path where the file was installed for easy unistall
  488. if ($atts['role'] != 'script')
  489. {
  490. $loc = $this->config->get($atts['role'] . '_dir');
  491. }
  492. else
  493. {
  494. $loc = $this->config->get('bin_dir');
  495. }
  496. if ($atts['role'] != 'src')
  497. {
  498. $this->addFileOperation("installed_as", array($file, $installed_as, $loc,
  499. dirname(substr($installedas_dest_file, strlen($loc)))));
  500. }
  501. //$this->log(2, "installed: $dest_file");
  502. return PEAR_INSTALLER_OK;
  503. }
  504. // }}}
  505. // {{{ _installFile2()
  506. /**
  507. * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  508. * @param string filename
  509. * @param array attributes from <file> tag in package.xml
  510. * @param string path to install the file in
  511. * @param array options from command-line
  512. * @access private
  513. */
  514. function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
  515. {
  516. $atts = $real_atts;
  517. if (! isset($this->_registry))
  518. {
  519. $this->_registry = &$this->config->getRegistry();
  520. }
  521. $channel = $pkg->getChannel();
  522. // {{{ assemble the destination paths
  523. if (! in_array($atts['attribs']['role'], PEAR_Installer_Role :: getValidRoles($pkg->getPackageType())))
  524. {
  525. return $this->raiseError('Invalid role `' . $atts['attribs']['role'] . "' for file $file");
  526. }
  527. $role = &PEAR_Installer_Role :: factory($pkg, $atts['attribs']['role'], $this->config);
  528. $err = $role->setup($this, $pkg, $atts['attribs'], $file);
  529. if (PEAR :: isError($err))
  530. {
  531. return $err;
  532. }
  533. if (! $role->isInstallable())
  534. {
  535. return;
  536. }
  537. $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
  538. if (PEAR :: isError($info))
  539. {
  540. return $info;
  541. }
  542. list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
  543. if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file)))
  544. {
  545. return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
  546. }
  547. $final_dest_file = $installed_as = $dest_file;
  548. if (isset($this->_options['packagingroot']))
  549. {
  550. $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
  551. }
  552. $dest_dir = dirname($final_dest_file);
  553. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  554. // }}}
  555. if (empty($this->_options['register-only']))
  556. {
  557. if (! file_exists($dest_dir) || ! is_dir($dest_dir))
  558. {
  559. if (! $this->mkDirHier($dest_dir))
  560. {
  561. return $this->raiseError("failed to mkdir $dest_dir", PEAR_INSTALLER_FAILED);
  562. }
  563. $this->log(3, "+ mkdir $dest_dir");
  564. }
  565. }
  566. $attribs = $atts['attribs'];
  567. unset($atts['attribs']);
  568. // pretty much nothing happens if we are only registering the install
  569. if (empty($this->_options['register-only']))
  570. {
  571. if (! count($atts))
  572. { // no tasks
  573. if (! file_exists($orig_file))
  574. {
  575. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  576. }
  577. if (! @copy($orig_file, $dest_file))
  578. {
  579. return $this->raiseError("failed to write $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  580. }
  581. $this->log(3, "+ cp $orig_file $dest_file");
  582. if (isset($attribs['md5sum']))
  583. {
  584. $md5sum = md5_file($dest_file);
  585. }
  586. }
  587. else
  588. { // file with tasks
  589. if (! file_exists($orig_file))
  590. {
  591. return $this->raiseError("file $orig_file does not exist", PEAR_INSTALLER_FAILED);
  592. }
  593. $contents = file_get_contents($orig_file);
  594. if ($contents === false)
  595. {
  596. $contents = '';
  597. }
  598. if (isset($attribs['md5sum']))
  599. {
  600. $md5sum = md5($contents);
  601. }
  602. foreach ($atts as $tag => $raw)
  603. {
  604. $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
  605. $task = "PEAR_Task_$tag";
  606. $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
  607. if (! $task->isScript())
  608. { // scripts are only handled after installation
  609. $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
  610. $res = $task->startSession($pkg, $contents, $final_dest_file);
  611. if ($res === false)
  612. {
  613. continue; // skip this file
  614. }
  615. if (PEAR :: isError($res))
  616. {
  617. return $res;
  618. }
  619. $contents = $res; // save changes
  620. }
  621. $wp = @fopen($dest_file, "wb");
  622. if (! is_resource($wp))
  623. {
  624. return $this->raiseError("failed to create $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  625. }
  626. if (fwrite($wp, $contents) === false)
  627. {
  628. return $this->raiseError("failed writing to $dest_file: $php_errormsg", PEAR_INSTALLER_FAILED);
  629. }
  630. fclose($wp);
  631. }
  632. }
  633. // {{{ check the md5
  634. if (isset($md5sum))
  635. {
  636. // Make sure the original md5 sum matches with expected
  637. if (strtolower($md5sum) === strtolower($attribs['md5sum']))
  638. {
  639. $this->log(2, "md5sum ok: $final_dest_file");
  640. if (isset($contents))
  641. {
  642. // set md5 sum based on $content in case any tasks were run.
  643. $real_atts['attribs']['md5sum'] = md5($contents);
  644. }
  645. }
  646. else
  647. {
  648. if (empty($options['force']))
  649. {
  650. // delete the file
  651. if (file_exists($dest_file))
  652. {
  653. unlink($dest_file);
  654. }
  655. if (! isset($options['ignore-errors']))
  656. {
  657. return $this->raiseError("bad md5sum for file $final_dest_file", PEAR_INSTALLER_FAILED);
  658. }
  659. if (! isset($options['soft']))
  660. {
  661. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  662. }
  663. }
  664. else
  665. {
  666. if (! isset($options['soft']))
  667. {
  668. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  669. }
  670. }
  671. }
  672. }
  673. else
  674. {
  675. $real_atts['attribs']['md5sum'] = md5_file($dest_file);
  676. }
  677. // }}}
  678. // {{{ set file permissions
  679. if (! OS_WINDOWS)
  680. {
  681. if ($role->isExecutable())
  682. {
  683. $mode = 0777 & ~ (int) octdec($this->config->get('umask'));
  684. $this->log(3, "+ chmod +x $dest_file");
  685. }
  686. else
  687. {
  688. $mode = 0666 & ~ (int) octdec($this->config->get('umask'));
  689. }
  690. if ($attribs['role'] != 'src')
  691. {
  692. $this->addFileOperation("chmod", array($mode, $dest_file));
  693. if (! @chmod($dest_file, $mode))
  694. {
  695. if (! isset($options['soft']))
  696. {
  697. $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
  698. }
  699. }
  700. }
  701. }
  702. // }}}
  703. if ($attribs['role'] == 'src')
  704. {
  705. rename($dest_file, $final_dest_file);
  706. $this->log(2, "renamed source file $dest_file to $final_dest_file");
  707. }
  708. else
  709. {
  710. $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
  711. }
  712. }
  713. // Store the full path where the file was installed for easy uninstall
  714. if ($attribs['role'] != 'src')
  715. {
  716. $loc = $this->config->get($role->getLocationConfig(), null, $channel);
  717. $this->addFileOperation('installed_as', array($file, $installed_as, $loc,
  718. dirname(substr($installed_as, strlen($loc)))));
  719. }
  720. //$this->log(2, "installed: $dest_file");
  721. return PEAR_INSTALLER_OK;
  722. }
  723. // }}}
  724. // {{{ addFileOperation()
  725. /**
  726. * Add a file operation to the current file transaction.
  727. *
  728. * @see startFileTransaction()
  729. * @param string $type This can be one of:
  730. * - rename: rename a file ($data has 3 values)
  731. * - backup: backup an existing file ($data has 1 value)
  732. * - removebackup: clean up backups created during install ($data has 1 value)
  733. * - chmod: change permissions on a file ($data has 2 values)
  734. * - delete: delete a file ($data has 1 value)
  735. * - rmdir: delete a directory if empty ($data has 1 value)
  736. * - installed_as: mark a file as installed ($data has 4 values).
  737. * @param array $data For all file operations, this array must contain the
  738. * full path to the file or directory that is being operated on. For
  739. * the rename command, the first parameter must be the file to rename,
  740. * the second its new name, the third whether this is a PHP extension.
  741. *
  742. * The installed_as operation contains 4 elements in this order:
  743. * 1. Filename as listed in the filelist element from package.xml
  744. * 2. Full path to the installed file
  745. * 3. Full path from the php_dir configuration variable used in this
  746. * installation
  747. * 4. Relative path from the php_dir that this file is installed in
  748. */
  749. function addFileOperation($type, $data)
  750. {
  751. if (! is_array($data))
  752. {
  753. return $this->raiseError('Internal Error: $data in addFileOperation' . ' must be an array, was ' . gettype($data));
  754. }
  755. if ($type == 'chmod')
  756. {
  757. $octmode = decoct($data[0]);
  758. $this->log(3, "adding to transaction: $type $octmode $data[1]");
  759. }
  760. else
  761. {
  762. $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  763. }
  764. $this->file_operations[] = array($type, $data);
  765. }
  766. // }}}
  767. // {{{ startFileTransaction()
  768. function startFileTransaction($rollback_in_case = false)
  769. {
  770. if (count($this->file_operations) && $rollback_in_case)
  771. {
  772. $this->rollbackFileTransaction();
  773. }
  774. $this->file_operations = array();
  775. }
  776. // }}}
  777. // {{{ commitFileTransaction()
  778. function commitFileTransaction()
  779. {
  780. $n = count($this->file_operations);
  781. $this->log(2, "about to commit $n file operations");
  782. // {{{ first, check permissions and such manually
  783. $errors = array();
  784. foreach ($this->file_operations as $tr)
  785. {
  786. list($type, $data) = $tr;
  787. switch ($type)
  788. {
  789. case 'rename' :
  790. if (! file_exists($data[0]))
  791. {
  792. $errors[] = "cannot rename file $data[0], doesn't exist";
  793. }
  794. // check that dest dir. is writable
  795. if (! is_writable(dirname($data[1])))
  796. {
  797. $errors[] = "permission denied ($type): $data[1]";
  798. }
  799. break;
  800. case 'chmod' :
  801. // check that file is writable
  802. if (! is_writable($data[1]))
  803. {
  804. $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  805. }
  806. break;
  807. case 'delete' :
  808. if (! file_exists($data[0]))
  809. {
  810. $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  811. }
  812. // check that directory is writable
  813. if (file_exists($data[0]))
  814. {
  815. if (! is_writable(dirname($data[0])))
  816. {
  817. $errors[] = "permission denied ($type): $data[0]";
  818. }
  819. else
  820. {
  821. // make sure the file to be deleted can be opened for writing
  822. $fp = false;
  823. if (! is_dir($data[0]) && (! is_writable($data[0]) || ! ($fp = @fopen($data[0], 'a'))))
  824. {
  825. $errors[] = "permission denied ($type): $data[0]";
  826. }
  827. elseif ($fp)
  828. {
  829. fclose($fp);
  830. }
  831. }
  832. }
  833. break;
  834. }
  835. }
  836. // }}}
  837. $m = count($errors);
  838. if ($m > 0)
  839. {
  840. foreach ($errors as $error)
  841. {
  842. if (! isset($this->_options['soft']))
  843. {
  844. $this->log(1, $error);
  845. }
  846. }
  847. if (! isset($this->_options['ignore-errors']))
  848. {
  849. return false;
  850. }
  851. }
  852. $this->_dirtree = array();
  853. // {{{ really commit the transaction
  854. foreach ($this->file_operations as $i => $tr)
  855. {
  856. if (! $tr)
  857. {
  858. // support removal of non-existing backups
  859. continue;
  860. }
  861. list($type, $data) = $tr;
  862. switch ($type)
  863. {
  864. case 'backup' :
  865. if (! file_exists($data[0]))
  866. {
  867. $this->file_operations[$i] = false;
  868. break;
  869. }
  870. if (! @copy($data[0], $data[0] . '.bak'))
  871. {
  872. $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] . '.bak ' . $php_errormsg);
  873. return false;
  874. }
  875. $this->log(3, "+ backup $data[0] to $data[0].bak");
  876. break;
  877. case 'removebackup' :
  878. if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak'))
  879. {
  880. unlink($data[0] . '.bak');
  881. $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  882. }
  883. break;
  884. case 'rename' :
  885. $test = file_exists($data[1]) ? @unlink($data[1]) : null;
  886. if (! $test && file_exists($data[1]))
  887. {
  888. if ($data[2])
  889. {
  890. $extra = ', this extension must be installed manually. Rename to "' . basename($data[1]) . '"';
  891. }
  892. else
  893. {
  894. $extra = '';
  895. }
  896. if (! isset($this->_options['soft']))
  897. {
  898. $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' . $data[0] . $extra);
  899. }
  900. if (! isset($this->_options['ignore-errors']))
  901. {
  902. return false;
  903. }
  904. }
  905. // permissions issues with rename - copy() is far superior
  906. $perms = @fileperms($data[0]);
  907. if (! @copy($data[0], $data[1]))
  908. {
  909. $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] . ' ' . $php_errormsg);
  910. return false;
  911. }
  912. // copy over permissions, otherwise they are lost
  913. @chmod($data[1], $perms);
  914. @unlink($data[0]);
  915. $this->log(3, "+ mv $data[0] $data[1]");
  916. break;
  917. case 'chmod' :
  918. if (! @chmod($data[1], $data[0]))
  919. {
  920. $this->log(1, 'Could not chmod ' . $data[1] . ' to ' . decoct($data[0]) . ' ' . $php_errormsg);
  921. return false;
  922. }
  923. $octmode = decoct($data[0]);
  924. $this->log(3, "+ chmod $octmode $data[1]");
  925. break;
  926. case 'delete' :
  927. if (file_exists($data[0]))
  928. {
  929. if (! @unlink($data[0]))
  930. {
  931. $this->log(1, 'Could not delete ' . $data[0] . ' ' . $php_errormsg);
  932. return false;
  933. }
  934. $this->log(3, "+ rm $data[0]");
  935. }
  936. break;
  937. case 'rmdir' :
  938. if (file_exists($data[0]))
  939. {
  940. do
  941. {
  942. $testme = opendir($data[0]);
  943. while (false !== ($entry = readdir($testme)))
  944. {
  945. if ($entry == '.' || $entry == '..')
  946. {
  947. continue;
  948. }
  949. closedir($testme);
  950. break 2; // this directory is not empty and can't be
  951. // deleted
  952. }
  953. closedir($testme);
  954. if (! @rmdir($data[0]))
  955. {
  956. $this->log(1, 'Could not rmdir ' . $data[0] . ' ' . $php_errormsg);
  957. return false;
  958. }
  959. $this->log(3, "+ rmdir $data[0]");
  960. }
  961. while (false);
  962. }
  963. break;
  964. case 'installed_as' :
  965. $this->pkginfo->setInstalledAs($data[0], $data[1]);
  966. if (! isset($this->_dirtree[dirname($data[1])]))
  967. {
  968. $this->_dirtree[dirname($data[1])] = true;
  969. $this->pkginfo->setDirtree(dirname($data[1]));
  970. while (! empty($data[3]) && dirname($data[3]) != $data[3] && $data[3] != '/' && $data[3] != '\\')
  971. {
  972. $this->pkginfo->setDirtree($pp = $this->_prependPath($data[3], $data[2]));
  973. $this->_dirtree[$pp] = true;
  974. $data[3] = dirname($data[3]);
  975. }
  976. }
  977. break;
  978. }
  979. }
  980. // }}}
  981. $this->log(2, "successfully committed $n file operations");
  982. $this->file_operations = array();
  983. return true;
  984. }
  985. // }}}
  986. // {{{ rollbackFileTransaction()
  987. function rollbackFileTransaction()
  988. {
  989. $n = count($this->file_operations);
  990. $this->log(2, "rolling back $n file operations");
  991. foreach ($this->file_operations as $tr)
  992. {
  993. list($type, $data) = $tr;
  994. switch ($type)
  995. {
  996. case 'backup' :
  997. if (file_exists($data[0] . '.bak'))
  998. {
  999. if (file_exists($data[0] && is_writable($data[0])))
  1000. {
  1001. unlink($data[0]);
  1002. }
  1003. @copy($data[0] . '.bak', $data[0]);
  1004. $this->log(3, "+ restore $data[0] from $data[0].bak");
  1005. }
  1006. break;
  1007. case 'removebackup' :
  1008. if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak'))
  1009. {
  1010. unlink($data[0] . '.bak');
  1011. $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  1012. }
  1013. break;
  1014. case 'rename' :
  1015. @unlink($data[0]);
  1016. $this->log(3, "+ rm $data[0]");
  1017. break;
  1018. case 'mkdir' :
  1019. @rmdir($data[0]);
  1020. $this->log(3, "+ rmdir $data[0]");
  1021. break;
  1022. case 'chmod' :
  1023. break;
  1024. case 'delete' :
  1025. break;
  1026. case 'installed_as' :
  1027. $this->pkginfo->setInstalledAs($data[0], false);
  1028. break;
  1029. }
  1030. }
  1031. $this->pkginfo->resetDirtree();
  1032. $this->file_operations = array();
  1033. }
  1034. // }}}
  1035. // {{{ mkDirHier($dir)
  1036. function mkDirHier($dir)
  1037. {
  1038. $this->addFileOperation('mkdir', array($dir));
  1039. return parent :: mkDirHier($dir);
  1040. }
  1041. // }}}
  1042. // {{{ download()
  1043. /**
  1044. * Download any files and their dependencies, if necessary
  1045. *
  1046. * @param array a mixed list of package names, local files, or package.xml
  1047. * @param PEAR_Config
  1048. * @param array options from the command line
  1049. * @param array this is the array that will be populated with packages to
  1050. * install. Format of each entry:
  1051. *
  1052. * <code>
  1053. * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  1054. * 'info' => array() // parsed package.xml
  1055. * );
  1056. * </code>
  1057. * @param array this will be populated with any error messages
  1058. * @param false private recursion variable
  1059. * @param false private recursion variable
  1060. * @param false private recursion variable
  1061. * @deprecated in favor of PEAR_Downloader
  1062. */
  1063. function download($packages, $options, &$config, &$installpackages, &$errors, $installed = false, $willinstall = false, $state = false)
  1064. {
  1065. // trickiness: initialize here
  1066. parent :: PEAR_Downloader($this->ui, $options, $config);
  1067. $ret = parent :: download($packages);
  1068. $errors = $this->getErrorMsgs();
  1069. $installpackages = $this->getDownloadedPackages();
  1070. trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . "in favor of PEAR_Downloader class", E_USER_WARNING);
  1071. return $ret;
  1072. }
  1073. // }}}
  1074. // {{{ _parsePackageXml()
  1075. function _parsePackageXml(&$descfile, &$tmpdir)
  1076. {
  1077. if (substr($descfile, - 4) == '.xml')
  1078. {
  1079. $tmpdir = false;
  1080. }
  1081. else
  1082. {
  1083. // {{{ Decompress pack in tmp dir -------------------------------------
  1084. // To allow relative package file names
  1085. $descfile = realpath($descfile);
  1086. if (PEAR :: isError($tmpdir = System :: mktemp('-d')))
  1087. {
  1088. return $tmpdir;
  1089. }
  1090. $this->log(3, '+ tmp dir created at ' . $tmpdir);
  1091. // }}}
  1092. }
  1093. // Parse xml file -----------------------------------------------
  1094. $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir);
  1095. PEAR :: staticPushErrorHandling(PEAR_ERROR_RETURN);
  1096. $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
  1097. PEAR :: staticPopErrorHandling();
  1098. if (PEAR :: isError($p))
  1099. {
  1100. if (is_array($p->getUserInfo()))
  1101. {
  1102. foreach ($p->getUserInfo() as $err)
  1103. {
  1104. $loglevel = $err['level'] == 'error' ? 0 : 1;
  1105. if (! isset($this->_options['soft']))
  1106. {
  1107. $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
  1108. }
  1109. }
  1110. }
  1111. return $this->raiseError('Installation failed: invalid package file');
  1112. }
  1113. $descfile = $p->getPackageFile();
  1114. return $p;
  1115. }
  1116. // }}}
  1117. /**
  1118. * Set the list of PEAR_Downloader_Package objects to allow more sane
  1119. * dependency validation
  1120. * @param array
  1121. */
  1122. function setDownloadedPackages(&$pkgs)
  1123. {
  1124. PEAR :: pushErrorHandling(PEAR_ERROR_RETURN);
  1125. $err = $this->analyzeDependencies($pkgs);
  1126. PEAR :: popErrorHandling();
  1127. if (PEAR :: isError($err))
  1128. {
  1129. return $err;
  1130. }
  1131. $this->_downloadedPackages = &$pkgs;
  1132. }
  1133. /**
  1134. * Set the list of PEAR_Downloader_Package objects to allow more sane
  1135. * dependency validation
  1136. * @param array
  1137. */
  1138. function setUninstallPackages(&$pkgs)
  1139. {
  1140. $this->_downloadedPackages = &$pkgs;
  1141. }
  1142. function getInstallPackages()
  1143. {
  1144. return $this->_downloadedPackages;
  1145. }
  1146. // {{{ install()
  1147. /**
  1148. * Installs the files within the package file specified.
  1149. *
  1150. * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
  1151. * or a pre-initialized packagefile object
  1152. * @param array $options
  1153. * recognized options:
  1154. * - installroot : optional prefix directory for installation
  1155. * - force : force installation
  1156. * - register-only : update registry but don't install files
  1157. * - upgrade : upgrade existing install
  1158. * - soft : fail silently
  1159. * - nodeps : ignore dependency conflicts/missing dependencies
  1160. * - alldeps : install all dependencies
  1161. * - onlyreqdeps : install only required dependencies
  1162. *
  1163. * @return array|PEAR_Error package info if successful
  1164. */
  1165. function install($pkgfile, $options = array())
  1166. {
  1167. $this->_options = $options;
  1168. $this->_registry = &$this->config->getRegistry();
  1169. if (is_object($pkgfile))
  1170. {
  1171. $dlpkg = &$pkgfile;
  1172. $pkg = $pkgfile->getPackageFile();
  1173. $pkgfile = $pkg->getArchiveFile();
  1174. $descfile = $pkg->getPackageFile();
  1175. $tmpdir = dirname($descfile);
  1176. }
  1177. else
  1178. {
  1179. $descfile = $pkgfile;
  1180. $tmpdir = '';
  1181. $pkg = $this->_parsePackageXml($descfile, $tmpdir);
  1182. if (PEAR :: isError($pkg))
  1183. {
  1184. return $pkg;
  1185. }
  1186. }
  1187. if (realpath($descfile) != realpath($pkgfile))
  1188. {
  1189. $tar = new Archive_Tar($pkgfile);
  1190. if (! $tar->extract($tmpdir))
  1191. {
  1192. return $this->raiseError("unable to unpack $pkgfile");
  1193. }
  1194. }
  1195. $pkgname = $pkg->getName();
  1196. $channel = $pkg->getChannel();
  1197. if (isset($this->_options['packagingroot']))
  1198. {
  1199. $regdir = $this->_prependPath($this->config->get('php_dir', null, 'pear.php.net'), $this->_options['packagingroot']);
  1200. $packrootphp_dir = $this->_prependPath($this->config->get('php_dir', null, $channel), $this->_options['packagingroot']);
  1201. }
  1202. if (isset($options['installroot']))
  1203. {
  1204. $this->config->setInstallRoot($options['installroot']);
  1205. $this->_registry = &$this->config->getRegistry();
  1206. $installregistry = &$this->_registry;
  1207. $this->installroot = ''; // all done automagically now
  1208. $php_dir = $this->config->get('php_dir', null, $channel);
  1209. }
  1210. else
  1211. {
  1212. $this->config->setInstallRoot(false);
  1213. $this->_registry = &$this->config->getRegistry();
  1214. if (isset($this->_options['packagingroot']))
  1215. {
  1216. $installregistry = &new PEAR_Registry($regdir);
  1217. if (! $installregistry->channelExists($channel, true))
  1218. {
  1219. // we need to fake a channel-discover of this channel
  1220. $chanobj = $this->_registry->getChannel($channel, true);
  1221. $installregistry->addChannel($chanobj);
  1222. }
  1223. $php_dir = $packrootphp_dir;
  1224. }
  1225. else
  1226. {
  1227. $installregistry = &$this->_registry;
  1228. $php_dir = $this->config->get('php_dir', null, $channel);
  1229. }
  1230. $this->installroot = '';
  1231. }
  1232. // {{{ checks to do when not in "force" mode
  1233. if (empty($options['force']) && (file_exists($this->config->get('php_dir')) && is_dir($this->config->get('php_dir'))))
  1234. {
  1235. $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
  1236. $instfilelist = $pkg->getInstallationFileList(true);
  1237. if (PEAR :: isError($instfilelist))
  1238. {
  1239. return $instfilelist;
  1240. }
  1241. // ensure we have the most accurate registry
  1242. $installregistry->flushFileMap();
  1243. $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1'

Large files files are truncated, but you can click here to view the full file