PageRenderTime 60ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/php/PEAR/Installer.php

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