Installer.php 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 5 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available through the world-wide-web at the following url: |
  11. // | http://www.php.net/license/3_0.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stig Bakken <ssb@php.net> |
  17. // | Tomas V.V.Cox <cox@idecnet.com> |
  18. // | Martin Jansen <mj@php.net> |
  19. // +----------------------------------------------------------------------+
  20. //
  21. // $Id: Installer.php,v 1.147 2004/01/08 17:33:12 sniper Exp $
  22. require_once 'PEAR/Common.php';
  23. require_once 'PEAR/Registry.php';
  24. require_once 'PEAR/Dependency.php';
  25. require_once 'PEAR/Downloader.php';
  26. require_once 'System.php';
  27. define('PEAR_INSTALLER_OK', 1);
  28. define('PEAR_INSTALLER_FAILED', 0);
  29. define('PEAR_INSTALLER_SKIPPED', -1);
  30. define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
  31. /**
  32. * Administration class used to install PEAR packages and maintain the
  33. * installed package database.
  34. *
  35. * TODO:
  36. * - Check dependencies break on package uninstall (when no force given)
  37. * - add a guessInstallDest() method with the code from _installFile() and
  38. * use that method in Registry::_rebuildFileMap() & Command_Registry::doList(),
  39. * others..
  40. *
  41. * @since PHP 4.0.2
  42. * @author Stig Bakken <ssb@php.net>
  43. * @author Martin Jansen <mj@php.net>
  44. * @author Greg Beaver <cellog@php.net>
  45. */
  46. class PEAR_Installer extends PEAR_Downloader
  47. {
  48. // {{{ properties
  49. /** name of the package directory, for example Foo-1.0
  50. * @var string
  51. */
  52. var $pkgdir;
  53. /** directory where PHP code files go
  54. * @var string
  55. */
  56. var $phpdir;
  57. /** directory where PHP extension files go
  58. * @var string
  59. */
  60. var $extdir;
  61. /** directory where documentation goes
  62. * @var string
  63. */
  64. var $docdir;
  65. /** installation root directory (ala PHP's INSTALL_ROOT or
  66. * automake's DESTDIR
  67. * @var string
  68. */
  69. var $installroot = '';
  70. /** debug level
  71. * @var int
  72. */
  73. var $debug = 1;
  74. /** temporary directory
  75. * @var string
  76. */
  77. var $tmpdir;
  78. /** PEAR_Registry object used by the installer
  79. * @var object
  80. */
  81. var $registry;
  82. /** List of file transactions queued for an install/upgrade/uninstall.
  83. *
  84. * Format:
  85. * array(
  86. * 0 => array("rename => array("from-file", "to-file")),
  87. * 1 => array("delete" => array("file-to-delete")),
  88. * ...
  89. * )
  90. *
  91. * @var array
  92. */
  93. var $file_operations = array();
  94. // }}}
  95. // {{{ constructor
  96. /**
  97. * PEAR_Installer constructor.
  98. *
  99. * @param object $ui user interface object (instance of PEAR_Frontend_*)
  100. *
  101. * @access public
  102. */
  103. function PEAR_Installer(&$ui)
  104. {
  105. parent::PEAR_Common();
  106. $this->setFrontendObject($ui);
  107. $this->debug = $this->config->get('verbose');
  108. //$this->registry = &new PEAR_Registry($this->config->get('php_dir'));
  109. }
  110. // }}}
  111. // {{{ _deletePackageFiles()
  112. /**
  113. * Delete a package's installed files, does not remove empty directories.
  114. *
  115. * @param string $package package name
  116. *
  117. * @return bool TRUE on success, or a PEAR error on failure
  118. *
  119. * @access private
  120. */
  121. function _deletePackageFiles($package)
  122. {
  123. if (!strlen($package)) {
  124. return $this->raiseError("No package to uninstall given");
  125. }
  126. $filelist = $this->registry->packageInfo($package, 'filelist');
  127. if ($filelist == null) {
  128. return $this->raiseError("$package not installed");
  129. }
  130. foreach ($filelist as $file => $props) {
  131. if (empty($props['installed_as'])) {
  132. continue;
  133. }
  134. $path = $this->_prependPath($props['installed_as'], $this->installroot);
  135. $this->addFileOperation('delete', array($path));
  136. }
  137. return true;
  138. }
  139. // }}}
  140. // {{{ _installFile()
  141. /**
  142. * @param string filename
  143. * @param array attributes from <file> tag in package.xml
  144. * @param string path to install the file in
  145. * @param array options from command-line
  146. * @access private
  147. */
  148. function _installFile($file, $atts, $tmp_path, $options)
  149. {
  150. // {{{ return if this file is meant for another platform
  151. static $os;
  152. if (isset($atts['platform'])) {
  153. if (empty($os)) {
  154. include_once "OS/Guess.php";
  155. $os = new OS_Guess();
  156. }
  157. if (!$os->matchSignature($atts['platform'])) {
  158. $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
  159. return PEAR_INSTALLER_SKIPPED;
  160. }
  161. }
  162. // }}}
  163. // {{{ assemble the destination paths
  164. switch ($atts['role']) {
  165. case 'doc':
  166. case 'data':
  167. case 'test':
  168. $dest_dir = $this->config->get($atts['role'] . '_dir') .
  169. DIRECTORY_SEPARATOR . $this->pkginfo['package'];
  170. unset($atts['baseinstalldir']);
  171. break;
  172. case 'ext':
  173. case 'php':
  174. $dest_dir = $this->config->get($atts['role'] . '_dir');
  175. break;
  176. case 'script':
  177. $dest_dir = $this->config->get('bin_dir');
  178. break;
  179. case 'src':
  180. case 'extsrc':
  181. $this->source_files++;
  182. return;
  183. default:
  184. return $this->raiseError("Invalid role `$atts[role]' for file $file");
  185. }
  186. $save_destdir = $dest_dir;
  187. if (!empty($atts['baseinstalldir'])) {
  188. $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  189. }
  190. if (dirname($file) != '.' && empty($atts['install-as'])) {
  191. $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  192. }
  193. if (empty($atts['install-as'])) {
  194. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  195. } else {
  196. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  197. }
  198. $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  199. // Clean up the DIRECTORY_SEPARATOR mess
  200. $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  201. list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
  202. DIRECTORY_SEPARATOR,
  203. array($dest_file, $orig_file));
  204. $installed_as = $dest_file;
  205. $final_dest_file = $this->_prependPath($dest_file, $this->installroot);
  206. $dest_dir = dirname($final_dest_file);
  207. $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  208. // }}}
  209. if (!@is_dir($dest_dir)) {
  210. if (!$this->mkDirHier($dest_dir)) {
  211. return $this->raiseError("failed to mkdir $dest_dir",
  212. PEAR_INSTALLER_FAILED);
  213. }
  214. $this->log(3, "+ mkdir $dest_dir");
  215. }
  216. if (empty($atts['replacements'])) {
  217. if (!file_exists($orig_file)) {
  218. return $this->raiseError("file does not exist",
  219. PEAR_INSTALLER_FAILED);
  220. }
  221. if (!@copy($orig_file, $dest_file)) {
  222. return $this->raiseError("failed to write $dest_file",
  223. PEAR_INSTALLER_FAILED);
  224. }
  225. $this->log(3, "+ cp $orig_file $dest_file");
  226. if (isset($atts['md5sum'])) {
  227. $md5sum = md5_file($dest_file);
  228. }
  229. } else {
  230. // {{{ file with replacements
  231. if (!file_exists($orig_file)) {
  232. return $this->raiseError("file does not exist",
  233. PEAR_INSTALLER_FAILED);
  234. }
  235. $fp = fopen($orig_file, "r");
  236. $contents = fread($fp, filesize($orig_file));
  237. fclose($fp);
  238. if (isset($atts['md5sum'])) {
  239. $md5sum = md5($contents);
  240. }
  241. $subst_from = $subst_to = array();
  242. foreach ($atts['replacements'] as $a) {
  243. $to = '';
  244. if ($a['type'] == 'php-const') {
  245. if (preg_match('/^[a-z0-9_]+$/i', $a['to'])) {
  246. eval("\$to = $a[to];");
  247. } else {
  248. $this->log(0, "invalid php-const replacement: $a[to]");
  249. continue;
  250. }
  251. } elseif ($a['type'] == 'pear-config') {
  252. $to = $this->config->get($a['to']);
  253. if (is_null($to)) {
  254. $this->log(0, "invalid pear-config replacement: $a[to]");
  255. continue;
  256. }
  257. } elseif ($a['type'] == 'package-info') {
  258. if (isset($this->pkginfo[$a['to']]) && is_string($this->pkginfo[$a['to']])) {
  259. $to = $this->pkginfo[$a['to']];
  260. } else {
  261. $this->log(0, "invalid package-info replacement: $a[to]");
  262. continue;
  263. }
  264. }
  265. if (!is_null($to)) {
  266. $subst_from[] = $a['from'];
  267. $subst_to[] = $to;
  268. }
  269. }
  270. $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
  271. if (sizeof($subst_from)) {
  272. $contents = str_replace($subst_from, $subst_to, $contents);
  273. }
  274. $wp = @fopen($dest_file, "wb");
  275. if (!is_resource($wp)) {
  276. return $this->raiseError("failed to create $dest_file: $php_errormsg",
  277. PEAR_INSTALLER_FAILED);
  278. }
  279. if (!fwrite($wp, $contents)) {
  280. return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  281. PEAR_INSTALLER_FAILED);
  282. }
  283. fclose($wp);
  284. // }}}
  285. }
  286. // {{{ check the md5
  287. if (isset($md5sum)) {
  288. if (strtolower($md5sum) == strtolower($atts['md5sum'])) {
  289. $this->log(2, "md5sum ok: $final_dest_file");
  290. } else {
  291. if (empty($options['force'])) {
  292. // delete the file
  293. @unlink($dest_file);
  294. return $this->raiseError("bad md5sum for file $final_dest_file",
  295. PEAR_INSTALLER_FAILED);
  296. } else {
  297. $this->log(0, "warning : bad md5sum for file $final_dest_file");
  298. }
  299. }
  300. }
  301. // }}}
  302. // {{{ set file permissions
  303. if (!OS_WINDOWS) {
  304. if ($atts['role'] == 'script') {
  305. $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  306. $this->log(3, "+ chmod +x $dest_file");
  307. } else {
  308. $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  309. }
  310. $this->addFileOperation("chmod", array($mode, $dest_file));
  311. if (!@chmod($dest_file, $mode)) {
  312. $this->log(0, "failed to change mode of $dest_file");
  313. }
  314. }
  315. // }}}
  316. $this->addFileOperation("rename", array($dest_file, $final_dest_file));
  317. // Store the full path where the file was installed for easy unistall
  318. $this->addFileOperation("installed_as", array($file, $installed_as,
  319. $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
  320. //$this->log(2, "installed: $dest_file");
  321. return PEAR_INSTALLER_OK;
  322. }
  323. // }}}
  324. // {{{ addFileOperation()
  325. /**
  326. * Add a file operation to the current file transaction.
  327. *
  328. * @see startFileTransaction()
  329. * @var string $type This can be one of:
  330. * - rename: rename a file ($data has 2 values)
  331. * - chmod: change permissions on a file ($data has 2 values)
  332. * - delete: delete a file ($data has 1 value)
  333. * - rmdir: delete a directory if empty ($data has 1 value)
  334. * - installed_as: mark a file as installed ($data has 4 values).
  335. * @var array $data For all file operations, this array must contain the
  336. * full path to the file or directory that is being operated on. For
  337. * the rename command, the first parameter must be the file to rename,
  338. * the second its new name.
  339. *
  340. * The installed_as operation contains 4 elements in this order:
  341. * 1. Filename as listed in the filelist element from package.xml
  342. * 2. Full path to the installed file
  343. * 3. Full path from the php_dir configuration variable used in this
  344. * installation
  345. * 4. Relative path from the php_dir that this file is installed in
  346. */
  347. function addFileOperation($type, $data)
  348. {
  349. if (!is_array($data)) {
  350. return $this->raiseError('Internal Error: $data in addFileOperation'
  351. . ' must be an array, was ' . gettype($data));
  352. }
  353. if ($type == 'chmod') {
  354. $octmode = decoct($data[0]);
  355. $this->log(3, "adding to transaction: $type $octmode $data[1]");
  356. } else {
  357. $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  358. }
  359. $this->file_operations[] = array($type, $data);
  360. }
  361. // }}}
  362. // {{{ startFileTransaction()
  363. function startFileTransaction($rollback_in_case = false)
  364. {
  365. if (count($this->file_operations) && $rollback_in_case) {
  366. $this->rollbackFileTransaction();
  367. }
  368. $this->file_operations = array();
  369. }
  370. // }}}
  371. // {{{ commitFileTransaction()
  372. function commitFileTransaction()
  373. {
  374. $n = count($this->file_operations);
  375. $this->log(2, "about to commit $n file operations");
  376. // {{{ first, check permissions and such manually
  377. $errors = array();
  378. foreach ($this->file_operations as $tr) {
  379. list($type, $data) = $tr;
  380. switch ($type) {
  381. case 'rename':
  382. if (!file_exists($data[0])) {
  383. $errors[] = "cannot rename file $data[0], doesn't exist";
  384. }
  385. // check that dest dir. is writable
  386. if (!is_writable(dirname($data[1]))) {
  387. $errors[] = "permission denied ($type): $data[1]";
  388. }
  389. break;
  390. case 'chmod':
  391. // check that file is writable
  392. if (!is_writable($data[1])) {
  393. $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  394. }
  395. break;
  396. case 'delete':
  397. if (!file_exists($data[0])) {
  398. $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  399. }
  400. // check that directory is writable
  401. if (file_exists($data[0]) && !is_writable(dirname($data[0]))) {
  402. $errors[] = "permission denied ($type): $data[0]";
  403. }
  404. break;
  405. }
  406. }
  407. // }}}
  408. $m = sizeof($errors);
  409. if ($m > 0) {
  410. foreach ($errors as $error) {
  411. $this->log(1, $error);
  412. }
  413. return false;
  414. }
  415. // {{{ really commit the transaction
  416. foreach ($this->file_operations as $tr) {
  417. list($type, $data) = $tr;
  418. switch ($type) {
  419. case 'rename':
  420. @unlink($data[1]);
  421. @rename($data[0], $data[1]);
  422. $this->log(3, "+ mv $data[0] $data[1]");
  423. break;
  424. case 'chmod':
  425. @chmod($data[1], $data[0]);
  426. $octmode = decoct($data[0]);
  427. $this->log(3, "+ chmod $octmode $data[1]");
  428. break;
  429. case 'delete':
  430. @unlink($data[0]);
  431. $this->log(3, "+ rm $data[0]");
  432. break;
  433. case 'rmdir':
  434. @rmdir($data[0]);
  435. $this->log(3, "+ rmdir $data[0]");
  436. break;
  437. case 'installed_as':
  438. $this->pkginfo['filelist'][$data[0]]['installed_as'] = $data[1];
  439. if (!isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
  440. $this->pkginfo['filelist']['dirtree'][dirname($data[1])] = true;
  441. while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
  442. && $data[3] != '.') {
  443. $this->pkginfo['filelist']['dirtree']
  444. [$this->_prependPath($data[3], $data[2])] = true;
  445. $data[3] = dirname($data[3]);
  446. }
  447. }
  448. break;
  449. }
  450. }
  451. // }}}
  452. $this->log(2, "successfully committed $n file operations");
  453. $this->file_operations = array();
  454. return true;
  455. }
  456. // }}}
  457. // {{{ rollbackFileTransaction()
  458. function rollbackFileTransaction()
  459. {
  460. $n = count($this->file_operations);
  461. $this->log(2, "rolling back $n file operations");
  462. foreach ($this->file_operations as $tr) {
  463. list($type, $data) = $tr;
  464. switch ($type) {
  465. case 'rename':
  466. @unlink($data[0]);
  467. $this->log(3, "+ rm $data[0]");
  468. break;
  469. case 'mkdir':
  470. @rmdir($data[0]);
  471. $this->log(3, "+ rmdir $data[0]");
  472. break;
  473. case 'chmod':
  474. break;
  475. case 'delete':
  476. break;
  477. case 'installed_as':
  478. if (isset($this->pkginfo['filelist'])) {
  479. unset($this->pkginfo['filelist'][$data[0]]['installed_as']);
  480. }
  481. if (isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
  482. unset($this->pkginfo['filelist']['dirtree'][dirname($data[1])]);
  483. while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
  484. && $data[3] != '.') {
  485. unset($this->pkginfo['filelist']['dirtree']
  486. [$this->_prependPath($data[3], $data[2])]);
  487. $data[3] = dirname($data[3]);
  488. }
  489. }
  490. if (isset($this->pkginfo['filelist']['dirtree'])
  491. && !count($this->pkginfo['filelist']['dirtree'])) {
  492. unset($this->pkginfo['filelist']['dirtree']);
  493. }
  494. break;
  495. }
  496. }
  497. $this->file_operations = array();
  498. }
  499. // }}}
  500. // {{{ mkDirHier($dir)
  501. function mkDirHier($dir)
  502. {
  503. $this->addFileOperation('mkdir', array($dir));
  504. return parent::mkDirHier($dir);
  505. }
  506. // }}}
  507. // {{{ _prependPath($path, $prepend)
  508. function _prependPath($path, $prepend)
  509. {
  510. if (strlen($prepend) > 0) {
  511. if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
  512. $path = $prepend . substr($path, 2);
  513. } else {
  514. $path = $prepend . $path;
  515. }
  516. }
  517. return $path;
  518. }
  519. // }}}
  520. // {{{ download()
  521. /**
  522. * Download any files and their dependencies, if necessary
  523. *
  524. * @param array a mixed list of package names, local files, or package.xml
  525. * @param PEAR_Config
  526. * @param array options from the command line
  527. * @param array this is the array that will be populated with packages to
  528. * install. Format of each entry:
  529. *
  530. * <code>
  531. * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  532. * 'info' => array() // parsed package.xml
  533. * );
  534. * </code>
  535. * @param array this will be populated with any error messages
  536. * @param false private recursion variable
  537. * @param false private recursion variable
  538. * @param false private recursion variable
  539. * @deprecated in favor of PEAR_Downloader
  540. */
  541. function download($packages, $options, &$config, &$installpackages,
  542. &$errors, $installed = false, $willinstall = false, $state = false)
  543. {
  544. // trickiness: initialize here
  545. parent::PEAR_Downloader($this->ui, $options, $config);
  546. $ret = parent::download($packages);
  547. $errors = $this->getErrorMsgs();
  548. $installpackages = $this->getDownloadedPackages();
  549. trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
  550. "in favor of PEAR_Downloader class", E_USER_WARNING);
  551. return $ret;
  552. }
  553. // }}}
  554. // {{{ install()
  555. /**
  556. * Installs the files within the package file specified.
  557. *
  558. * @param string $pkgfile path to the package file
  559. * @param array $options
  560. * recognized options:
  561. * - installroot : optional prefix directory for installation
  562. * - force : force installation
  563. * - register-only : update registry but don't install files
  564. * - upgrade : upgrade existing install
  565. * - soft : fail silently
  566. * - nodeps : ignore dependency conflicts/missing dependencies
  567. * - alldeps : install all dependencies
  568. * - onlyreqdeps : install only required dependencies
  569. *
  570. * @return array|PEAR_Error package info if successful
  571. */
  572. function install($pkgfile, $options = array())
  573. {
  574. $php_dir = $this->config->get('php_dir');
  575. if (isset($options['installroot'])) {
  576. if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
  577. $options['installroot'] = substr($options['installroot'], 0, -1);
  578. }
  579. $php_dir = $this->_prependPath($php_dir, $options['installroot']);
  580. $this->installroot = $options['installroot'];
  581. } else {
  582. $this->installroot = '';
  583. }
  584. $this->registry = &new PEAR_Registry($php_dir);
  585. // ==> XXX should be removed later on
  586. $flag_old_format = false;
  587. if (substr($pkgfile, -4) == '.xml') {
  588. $descfile = $pkgfile;
  589. } else {
  590. // {{{ Decompress pack in tmp dir -------------------------------------
  591. // To allow relative package file names
  592. $pkgfile = realpath($pkgfile);
  593. if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
  594. return $tmpdir;
  595. }
  596. $this->log(3, '+ tmp dir created at ' . $tmpdir);
  597. $tar = new Archive_Tar($pkgfile);
  598. if (!@$tar->extract($tmpdir)) {
  599. return $this->raiseError("unable to unpack $pkgfile");
  600. }
  601. // {{{ Look for existing package file
  602. $descfile = $tmpdir . DIRECTORY_SEPARATOR . 'package.xml';
  603. if (!is_file($descfile)) {
  604. // ----- Look for old package archive format
  605. // In this format the package.xml file was inside the
  606. // Package-n.n directory
  607. $dp = opendir($tmpdir);
  608. do {
  609. $pkgdir = readdir($dp);
  610. } while ($pkgdir{0} == '.');
  611. $descfile = $tmpdir . DIRECTORY_SEPARATOR . $pkgdir . DIRECTORY_SEPARATOR . 'package.xml';
  612. $flag_old_format = true;
  613. $this->log(0, "warning : you are using an archive with an old format");
  614. }
  615. // }}}
  616. // <== XXX This part should be removed later on
  617. // }}}
  618. }
  619. if (!is_file($descfile)) {
  620. return $this->raiseError("no package.xml file after extracting the archive");
  621. }
  622. // Parse xml file -----------------------------------------------
  623. $pkginfo = $this->infoFromDescriptionFile($descfile);
  624. if (PEAR::isError($pkginfo)) {
  625. return $pkginfo;
  626. }
  627. $this->validatePackageInfo($pkginfo, $errors, $warnings);
  628. // XXX We allow warnings, do we have to do it?
  629. if (count($errors)) {
  630. if (empty($options['force'])) {
  631. return $this->raiseError("The following errors where found (use force option to install anyway):\n".
  632. implode("\n", $errors));
  633. } else {
  634. $this->log(0, "warning : the following errors were found:\n".
  635. implode("\n", $errors));
  636. }
  637. }
  638. $pkgname = $pkginfo['package'];
  639. // {{{ Check dependencies -------------------------------------------
  640. if (isset($pkginfo['release_deps']) && empty($options['nodeps'])) {
  641. $dep_errors = '';
  642. $error = $this->checkDeps($pkginfo, $dep_errors);
  643. if ($error == true) {
  644. if (empty($options['soft'])) {
  645. $this->log(0, substr($dep_errors, 1));
  646. }
  647. return $this->raiseError("$pkgname: Dependencies failed");
  648. } else if (!empty($dep_errors)) {
  649. // Print optional dependencies
  650. if (empty($options['soft'])) {
  651. $this->log(0, $dep_errors);
  652. }
  653. }
  654. }
  655. // }}}
  656. // {{{ checks to do when not in "force" mode
  657. if (empty($options['force'])) {
  658. $test = $this->registry->checkFileMap($pkginfo);
  659. if (sizeof($test)) {
  660. $tmp = $test;
  661. foreach ($tmp as $file => $pkg) {
  662. if ($pkg == $pkgname) {
  663. unset($test[$file]);
  664. }
  665. }
  666. if (sizeof($test)) {
  667. $msg = "$pkgname: conflicting files found:\n";
  668. $longest = max(array_map("strlen", array_keys($test)));
  669. $fmt = "%${longest}s (%s)\n";
  670. foreach ($test as $file => $pkg) {
  671. $msg .= sprintf($fmt, $file, $pkg);
  672. }
  673. return $this->raiseError($msg);
  674. }
  675. }
  676. }
  677. // }}}
  678. $this->startFileTransaction();
  679. if (empty($options['upgrade'])) {
  680. // checks to do only when installing new packages
  681. if (empty($options['force']) && $this->registry->packageExists($pkgname)) {
  682. return $this->raiseError("$pkgname already installed");
  683. }
  684. } else {
  685. if ($this->registry->packageExists($pkgname)) {
  686. $v1 = $this->registry->packageInfo($pkgname, 'version');
  687. $v2 = $pkginfo['version'];
  688. $cmp = version_compare("$v1", "$v2", 'gt');
  689. if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
  690. return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
  691. }
  692. if (empty($options['register-only'])) {
  693. // when upgrading, remove old release's files first:
  694. if (PEAR::isError($err = $this->_deletePackageFiles($pkgname))) {
  695. return $this->raiseError($err);
  696. }
  697. }
  698. }
  699. }
  700. // {{{ Copy files to dest dir ---------------------------------------
  701. // info from the package it self we want to access from _installFile
  702. $this->pkginfo = &$pkginfo;
  703. // used to determine whether we should build any C code
  704. $this->source_files = 0;
  705. if (empty($options['register-only'])) {
  706. if (!is_dir($php_dir)) {
  707. return $this->raiseError("no script destination directory\n",
  708. null, PEAR_ERROR_DIE);
  709. }
  710. $tmp_path = dirname($descfile);
  711. if (substr($pkgfile, -4) != '.xml') {
  712. $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkginfo['version'];
  713. }
  714. // ==> XXX This part should be removed later on
  715. if ($flag_old_format) {
  716. $tmp_path = dirname($descfile);
  717. }
  718. // <== XXX This part should be removed later on
  719. // {{{ install files
  720. foreach ($pkginfo['filelist'] as $file => $atts) {
  721. $this->expectError(PEAR_INSTALLER_FAILED);
  722. $res = $this->_installFile($file, $atts, $tmp_path, $options);
  723. $this->popExpect();
  724. if (PEAR::isError($res)) {
  725. if (empty($options['ignore-errors'])) {
  726. $this->rollbackFileTransaction();
  727. if ($res->getMessage() == "file does not exist") {
  728. $this->raiseError("file $file in package.xml does not exist");
  729. }
  730. return $this->raiseError($res);
  731. } else {
  732. $this->log(0, "Warning: " . $res->getMessage());
  733. }
  734. }
  735. if ($res != PEAR_INSTALLER_OK) {
  736. // Do not register files that were not installed
  737. unset($pkginfo['filelist'][$file]);
  738. }
  739. }
  740. // }}}
  741. // {{{ compile and install source files
  742. if ($this->source_files > 0 && empty($options['nobuild'])) {
  743. $this->log(1, "$this->source_files source files, building");
  744. $bob = &new PEAR_Builder($this->ui);
  745. $bob->debug = $this->debug;
  746. $built = $bob->build($descfile, array(&$this, '_buildCallback'));
  747. if (PEAR::isError($built)) {
  748. $this->rollbackFileTransaction();
  749. return $built;
  750. }
  751. $this->log(1, "\nBuild process completed successfully");
  752. foreach ($built as $ext) {
  753. $bn = basename($ext['file']);
  754. list($_ext_name, ) = explode('.', $bn);
  755. if (extension_loaded($_ext_name)) {
  756. $this->raiseError("Extension '$_ext_name' already loaded. Please unload it ".
  757. "in your php.ini file prior to install or upgrade it.");
  758. }
  759. $dest = $this->config->get('ext_dir') . DIRECTORY_SEPARATOR . $bn;
  760. $this->log(1, "Installing '$bn' at ext_dir ($dest)");
  761. $this->log(3, "+ cp $ext[file] ext_dir ($dest)");
  762. $copyto = $this->_prependPath($dest, $this->installroot);
  763. if (!@copy($ext['file'], $copyto)) {
  764. $this->rollbackFileTransaction();
  765. return $this->raiseError("failed to copy $bn to $copyto");
  766. }
  767. $pkginfo['filelist'][$bn] = array(
  768. 'role' => 'ext',
  769. 'installed_as' => $dest,
  770. 'php_api' => $ext['php_api'],
  771. 'zend_mod_api' => $ext['zend_mod_api'],
  772. 'zend_ext_api' => $ext['zend_ext_api'],
  773. );
  774. }
  775. }
  776. // }}}
  777. }
  778. if (!$this->commitFileTransaction()) {
  779. $this->rollbackFileTransaction();
  780. return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
  781. }
  782. // }}}
  783. $ret = false;
  784. // {{{ Register that the package is installed -----------------------
  785. if (empty($options['upgrade'])) {
  786. // if 'force' is used, replace the info in registry
  787. if (!empty($options['force']) && $this->registry->packageExists($pkgname)) {
  788. $this->registry->deletePackage($pkgname);
  789. }
  790. $ret = $this->registry->addPackage($pkgname, $pkginfo);
  791. } else {
  792. // new: upgrade installs a package if it isn't installed
  793. if (!$this->registry->packageExists($pkgname)) {
  794. $ret = $this->registry->addPackage($pkgname, $pkginfo);
  795. } else {
  796. $ret = $this->registry->updatePackage($pkgname, $pkginfo, false);
  797. }
  798. }
  799. if (!$ret) {
  800. return $this->raiseError("Adding package $pkgname to registry failed");
  801. }
  802. // }}}
  803. return $pkginfo;
  804. }
  805. // }}}
  806. // {{{ uninstall()
  807. /**
  808. * Uninstall a package
  809. *
  810. * This method removes all files installed by the application, and then
  811. * removes any empty directories.
  812. * @param string package name
  813. * @param array Command-line options. Possibilities include:
  814. *
  815. * - installroot: base installation dir, if not the default
  816. * - nodeps: do not process dependencies of other packages to ensure
  817. * uninstallation does not break things
  818. */
  819. function uninstall($package, $options = array())
  820. {
  821. $php_dir = $this->config->get('php_dir');
  822. if (isset($options['installroot'])) {
  823. if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
  824. $options['installroot'] = substr($options['installroot'], 0, -1);
  825. }
  826. $this->installroot = $options['installroot'];
  827. $php_dir = $this->_prependPath($php_dir, $this->installroot);
  828. } else {
  829. $this->installroot = '';
  830. }
  831. $this->registry = &new PEAR_Registry($php_dir);
  832. $filelist = $this->registry->packageInfo($package, 'filelist');
  833. if ($filelist == null) {
  834. return $this->raiseError("$package not installed");
  835. }
  836. if (empty($options['nodeps'])) {
  837. $depchecker = &new PEAR_Dependency($this->registry);
  838. $error = $depchecker->checkPackageUninstall($errors, $warning, $package);
  839. if ($error) {
  840. return $this->raiseError($errors . 'uninstall failed');
  841. }
  842. if ($warning) {
  843. $this->log(0, $warning);
  844. }
  845. }
  846. // {{{ Delete the files
  847. $this->startFileTransaction();
  848. if (PEAR::isError($err = $this->_deletePackageFiles($package))) {
  849. $this->rollbackFileTransaction();
  850. return $this->raiseError($err);
  851. }
  852. if (!$this->commitFileTransaction()) {
  853. $this->rollbackFileTransaction();
  854. return $this->raiseError("uninstall failed");
  855. } else {
  856. $this->startFileTransaction();
  857. if (!isset($filelist['dirtree']) || !count($filelist['dirtree'])) {
  858. return $this->registry->deletePackage($package);
  859. }
  860. // attempt to delete empty directories
  861. uksort($filelist['dirtree'], array($this, '_sortDirs'));
  862. foreach($filelist['dirtree'] as $dir => $notused) {
  863. $this->addFileOperation('rmdir', array($dir));
  864. }
  865. if (!$this->commitFileTransaction()) {
  866. $this->rollbackFileTransaction();
  867. }
  868. }
  869. // }}}
  870. // Register that the package is no longer installed
  871. return $this->registry->deletePackage($package);
  872. }
  873. // }}}
  874. // {{{ _sortDirs()
  875. function _sortDirs($a, $b)
  876. {
  877. if (strnatcmp($a, $b) == -1) return 1;
  878. if (strnatcmp($a, $b) == 1) return -1;
  879. return 0;
  880. }
  881. // }}}
  882. // {{{ checkDeps()
  883. /**
  884. * Check if the package meets all dependencies
  885. *
  886. * @param array Package information (passed by reference)
  887. * @param string Error message (passed by reference)
  888. * @return boolean False when no error occured, otherwise true
  889. */
  890. function checkDeps(&$pkginfo, &$errors)
  891. {
  892. if (empty($this->registry)) {
  893. $this->registry = &new PEAR_Registry($this->config->get('php_dir'));
  894. }
  895. $depchecker = &new PEAR_Dependency($this->registry);
  896. $error = $errors = '';
  897. $failed_deps = $optional_deps = array();
  898. if (is_array($pkginfo['release_deps'])) {
  899. foreach($pkginfo['release_deps'] as $dep) {
  900. $code = $depchecker->callCheckMethod($error, $dep);
  901. if ($code) {
  902. if (isset($dep['optional']) && $dep['optional'] == 'yes') {
  903. $optional_deps[] = array($dep, $code, $error);
  904. } else {
  905. $failed_deps[] = array($dep, $code, $error);
  906. }
  907. }
  908. }
  909. // {{{ failed dependencies
  910. $n = count($failed_deps);
  911. if ($n > 0) {
  912. for ($i = 0; $i < $n; $i++) {
  913. if (isset($failed_deps[$i]['type'])) {
  914. $type = $failed_deps[$i]['type'];
  915. } else {
  916. $type = 'pkg';
  917. }
  918. switch ($failed_deps[$i][1]) {
  919. case PEAR_DEPENDENCY_MISSING:
  920. if ($type == 'pkg') {
  921. // install
  922. }
  923. $errors .= "\n" . $failed_deps[$i][2];
  924. break;
  925. case PEAR_DEPENDENCY_UPGRADE_MINOR:
  926. if ($type == 'pkg') {
  927. // upgrade
  928. }
  929. $errors .= "\n" . $failed_deps[$i][2];
  930. break;
  931. default:
  932. $errors .= "\n" . $failed_deps[$i][2];
  933. break;
  934. }
  935. }
  936. return true;
  937. }
  938. // }}}
  939. // {{{ optional dependencies
  940. $count_optional = count($optional_deps);
  941. if ($count_optional > 0) {
  942. $errors = "Optional dependencies:";
  943. for ($i = 0; $i < $count_optional; $i++) {
  944. if (isset($optional_deps[$i]['type'])) {
  945. $type = $optional_deps[$i]['type'];
  946. } else {
  947. $type = 'pkg';
  948. }
  949. switch ($optional_deps[$i][1]) {
  950. case PEAR_DEPENDENCY_MISSING:
  951. case PEAR_DEPENDENCY_UPGRADE_MINOR:
  952. default:
  953. $errors .= "\n" . $optional_deps[$i][2];
  954. break;
  955. }
  956. }
  957. return false;
  958. }
  959. // }}}
  960. }
  961. return false;
  962. }
  963. // }}}
  964. // {{{ _buildCallback()
  965. function _buildCallback($what, $data)
  966. {
  967. if (($what == 'cmdoutput' && $this->debug > 1) ||
  968. ($what == 'output' && $this->debug > 0)) {
  969. $this->ui->outputData(rtrim($data), 'build');
  970. }
  971. }
  972. // }}}
  973. }
  974. // {{{ md5_file() utility function
  975. if (!function_exists("md5_file")) {
  976. function md5_file($filename) {
  977. $fp = fopen($filename, "r");
  978. if (!$fp) return null;
  979. $contents = fread($fp, filesize($filename));
  980. fclose($fp);
  981. return md5($contents);
  982. }
  983. }
  984. // }}}
  985. ?>