Installer.php 68 KB

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