Installer.php 40 KB

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