PageRenderTime 60ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/blog/www/system/library/PEAR/PEAR/Installer.php

https://bitbucket.org/vmihailenco/vladimirwebdev
PHP | 1723 lines | 1408 code | 69 blank | 246 comment | 327 complexity | 4d25c6c73bf62445a68d363a2e02beec MD5 | raw file
Possible License(s): BSD-3-Clause

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

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