1<?php 2/** 3 * PEAR_DependencyDB, advanced installed packages dependency database 4 * 5 * PHP versions 4 and 5 6 * 7 * LICENSE: This source file is subject to version 3.0 of the PHP license 8 * that is available through the world-wide-web at the following URI: 9 * http://www.php.net/license/3_0.txt. If you did not receive a copy of 10 * the PHP License and are unable to obtain it through the web, please 11 * send a note to license@php.net so we can mail you a copy immediately. 12 * 13 * @category pear 14 * @package PEAR 15 * @author Tomas V. V. Cox <cox@idecnet.com> 16 * @author Greg Beaver <cellog@php.net> 17 * @copyright 1997-2006 The PHP Group 18 * @license http://www.php.net/license/3_0.txt PHP License 3.0 19 * @version CVS: $Id: DependencyDB.php,v 1.30.2.1 2006/05/25 22:00:05 cellog Exp $ 20 * @link http://pear.php.net/package/PEAR 21 * @since File available since Release 1.4.0a1 22 */ 23 24/** 25 * Needed for error handling 26 */ 27require_once 'PEAR.php'; 28require_once 'PEAR/Config.php'; 29 30/** 31 * Track dependency relationships between installed packages 32 * @category pear 33 * @package PEAR 34 * @author Greg Beaver <cellog@php.net> 35 * @author Tomas V.V.Cox <cox@idec.net.com> 36 * @copyright 1997-2006 The PHP Group 37 * @license http://www.php.net/license/3_0.txt PHP License 3.0 38 * @version Release: 1.4.11 39 * @link http://pear.php.net/package/PEAR 40 * @since Class available since Release 1.4.0a1 41 */ 42class PEAR_DependencyDB 43{ 44 // {{{ properties 45 46 /** 47 * This is initialized by {@link setConfig()} 48 * @var PEAR_Config 49 * @access private 50 */ 51 var $_config; 52 /** 53 * This is initialized by {@link setConfig()} 54 * @var PEAR_Registry 55 * @access private 56 */ 57 var $_registry; 58 /** 59 * Filename of the dependency DB (usually .depdb) 60 * @var string 61 * @access private 62 */ 63 var $_depdb = false; 64 /** 65 * File name of the lockfile (usually .depdblock) 66 * @var string 67 * @access private 68 */ 69 var $_lockfile = false; 70 /** 71 * Open file resource for locking the lockfile 72 * @var resource|false 73 * @access private 74 */ 75 var $_lockFp = false; 76 /** 77 * API version of this class, used to validate a file on-disk 78 * @var string 79 * @access private 80 */ 81 var $_version = '1.0'; 82 /** 83 * Cached dependency database file 84 * @var array|null 85 * @access private 86 */ 87 var $_cache; 88 89 // }}} 90 // {{{ & singleton() 91 92 /** 93 * Get a raw dependency database. Calls setConfig() and assertDepsDB() 94 * @param PEAR_Config 95 * @param string|false full path to the dependency database, or false to use default 96 * @return PEAR_DependencyDB|PEAR_Error 97 * @static 98 */ 99 function &singleton(&$config, $depdb = false) 100 { 101 if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] 102 [$config->get('php_dir', null, 'pear.php.net')])) { 103 $a = new PEAR_DependencyDB; 104 $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] 105 [$config->get('php_dir', null, 'pear.php.net')] = &$a; 106 $a->setConfig($config, $depdb); 107 if (PEAR::isError($e = $a->assertDepsDB())) { 108 return $e; 109 } 110 } 111 return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] 112 [$config->get('php_dir', null, 'pear.php.net')]; 113 } 114 115 /** 116 * Set up the registry/location of dependency DB 117 * @param PEAR_Config|false 118 * @param string|false full path to the dependency database, or false to use default 119 */ 120 function setConfig(&$config, $depdb = false) 121 { 122 if (!$config) { 123 $this->_config = &PEAR_Config::singleton(); 124 } else { 125 $this->_config = &$config; 126 } 127 $this->_registry = &$this->_config->getRegistry(); 128 if (!$depdb) { 129 $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') . 130 DIRECTORY_SEPARATOR . '.depdb'; 131 } else { 132 $this->_depdb = $depdb; 133 } 134 $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock'; 135 } 136 // }}} 137 138 function hasWriteAccess() 139 { 140 if (!@file_exists($this->_depdb)) { 141 $dir = $this->_depdb; 142 while ($dir && $dir != '.') { 143 $dir = dirname($dir); // cd .. 144 if ($dir != '.' && @file_exists($dir)) { 145 if (@is_writeable($dir)) { 146 return true; 147 } else { 148 return false; 149 } 150 } 151 } 152 return false; 153 } 154 return @is_writeable($this->_depdb); 155 } 156 157 // {{{ assertDepsDB() 158 159 /** 160 * Create the dependency database, if it doesn't exist. Error if the database is 161 * newer than the code reading it. 162 * @return void|PEAR_Error 163 */ 164 function assertDepsDB() 165 { 166 if (!is_file($this->_depdb)) { 167 $this->rebuildDB(); 168 } else { 169 $depdb = $this->_getDepDB(); 170 // Datatype format has been changed, rebuild the Deps DB 171 if ($depdb['_version'] < $this->_version) { 172 $this->rebuildDB(); 173 } 174 if ($depdb['_version']{0} > $this->_version{0}) { 175 return PEAR::raiseError('Dependency database is version ' . 176 $depdb['_version'] . ', and we are version ' . 177 $this->_version . ', cannot continue'); 178 } 179 } 180 } 181 182 /** 183 * Get a list of installed packages that depend on this package 184 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array 185 * @return array|false 186 */ 187 function getDependentPackages(&$pkg) 188 { 189 $data = $this->_getDepDB(); 190 if (is_object($pkg)) { 191 $channel = strtolower($pkg->getChannel()); 192 $package = strtolower($pkg->getPackage()); 193 } else { 194 $channel = strtolower($pkg['channel']); 195 $package = strtolower($pkg['package']); 196 } 197 if (isset($data['packages'][$channel][$package])) { 198 return $data['packages'][$channel][$package]; 199 } 200 return false; 201 } 202 203 /** 204 * Get a list of the actual dependencies of installed packages that depend on 205 * a package. 206 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array 207 * @return array|false 208 */ 209 function getDependentPackageDependencies(&$pkg) 210 { 211 $data = $this->_getDepDB(); 212 if (is_object($pkg)) { 213 $channel = strtolower($pkg->getChannel()); 214 $package = strtolower($pkg->getPackage()); 215 } else { 216 $channel = strtolower($pkg['channel']); 217 $package = strtolower($pkg['package']); 218 } 219 $depend = $this->getDependentPackages($pkg); 220 if (!$depend) { 221 return false; 222 } 223 $dependencies = array(); 224 foreach ($depend as $info) { 225 $temp = $this->getDependencies($info); 226 foreach ($temp as $dep) { 227 if (strtolower($dep['dep']['channel']) == strtolower($channel) && 228 strtolower($dep['dep']['name']) == strtolower($package)) { 229 $dependencies[$info['channel']][$info['package']][] = $dep; 230 } 231 } 232 } 233 return $dependencies; 234 } 235 236 /** 237 * Get a list of dependencies of this installed package 238 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array 239 * @return array|false 240 */ 241 function getDependencies(&$pkg) 242 { 243 if (is_object($pkg)) { 244 $channel = strtolower($pkg->getChannel()); 245 $package = strtolower($pkg->getPackage()); 246 } else { 247 $channel = strtolower($pkg['channel']); 248 $package = strtolower($pkg['package']); 249 } 250 $data = $this->_getDepDB(); 251 if (isset($data['dependencies'][$channel][$package])) { 252 return $data['dependencies'][$channel][$package]; 253 } 254 return false; 255 } 256 257 /** 258 * Determine whether $parent depends on $child, near or deep 259 * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 260 * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 261 */ 262 function dependsOn($parent, $child) 263 { 264 $c = array(); 265 $this->_getDepDB(); 266 return $this->_dependsOn($parent, $child, $c); 267 } 268 269 function _dependsOn($parent, $child, &$checked) 270 { 271 if (is_object($parent)) { 272 $channel = strtolower($parent->getChannel()); 273 $package = strtolower($parent->getPackage()); 274 } else { 275 $channel = strtolower($parent['channel']); 276 $package = strtolower($parent['package']); 277 } 278 if (is_object($child)) { 279 $depchannel = strtolower($child->getChannel()); 280 $deppackage = strtolower($child->getPackage()); 281 } else { 282 $depchannel = strtolower($child['channel']); 283 $deppackage = strtolower($child['package']); 284 } 285 if (isset($checked[$channel][$package][$depchannel][$deppackage])) { 286 return false; // avoid endless recursion 287 } 288 $checked[$channel][$package][$depchannel][$deppackage] = true; 289 if (!isset($this->_cache['dependencies'][$channel][$package])) { 290 return false; 291 } 292 foreach ($this->_cache['dependencies'][$channel][$package] as $info) { 293 if (isset($info['dep']['uri'])) { 294 if (is_object($child)) { 295 if ($info['dep']['uri'] == $child->getURI()) { 296 return true; 297 } 298 } elseif (isset($child['uri'])) { 299 if ($info['dep']['uri'] == $child['uri']) { 300 return true; 301 } 302 } 303 return false; 304 } 305 if (strtolower($info['dep']['channel']) == strtolower($depchannel) && 306 strtolower($info['dep']['name']) == strtolower($deppackage)) { 307 return true; 308 } 309 } 310 foreach ($this->_cache['dependencies'][$channel][$package] as $info) { 311 if (isset($info['dep']['uri'])) { 312 if ($this->_dependsOn(array( 313 'uri' => $info['dep']['uri'], 314 'package' => $info['dep']['name']), $child, $checked)) { 315 return true; 316 } 317 } else { 318 if ($this->_dependsOn(array( 319 'channel' => $info['dep']['channel'], 320 'package' => $info['dep']['name']), $child, $checked)) { 321 return true; 322 } 323 } 324 } 325 return false; 326 } 327 328 /** 329 * Register dependencies of a package that is being installed or upgraded 330 * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2 331 */ 332 function installPackage(&$package) 333 { 334 $data = $this->_getDepDB(); 335 unset($this->_cache); 336 $this->_setPackageDeps($data, $package); 337 $this->_writeDepDB($data); 338 } 339 340 /** 341 * Remove dependencies of a package that is being uninstalled, or upgraded. 342 * 343 * Upgraded packages first uninstall, then install 344 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have 345 * indices 'channel' and 'package' 346 */ 347 function uninstallPackage(&$pkg) 348 { 349 $data = $this->_getDepDB(); 350 unset($this->_cache); 351 if (is_object($pkg)) { 352 $channel = strtolower($pkg->getChannel()); 353 $package = strtolower($pkg->getPackage()); 354 } else { 355 $channel = strtolower($pkg['channel']); 356 $package = strtolower($pkg['package']); 357 } 358 if (!isset($data['dependencies'][$channel][$package])) { 359 return true; 360 } 361 foreach ($data['dependencies'][$channel][$package] as $dep) { 362 $found = false; 363 if (isset($dep['dep']['uri'])) { 364 $depchannel = '__uri'; 365 } else { 366 $depchannel = strtolower($dep['dep']['channel']); 367 } 368 if (isset($data['packages'][$depchannel][strtolower($dep['dep']['name'])])) { 369 foreach ($data['packages'][$depchannel][strtolower($dep['dep']['name'])] as 370 $i => $info) { 371 if ($info['channel'] == $channel && 372 $info['package'] == $package) { 373 $found = true; 374 break; 375 } 376 } 377 } 378 if ($found) { 379 unset($data['packages'][$depchannel][strtolower($dep['dep']['name'])][$i]); 380 if (!count($data['packages'][$depchannel][strtolower($dep['dep']['name'])])) { 381 unset($data['packages'][$depchannel][strtolower($dep['dep']['name'])]); 382 if (!count($data['packages'][$depchannel])) { 383 unset($data['packages'][$depchannel]); 384 } 385 } else { 386 $data['packages'][$depchannel][strtolower($dep['dep']['name'])] = 387 array_values( 388 $data['packages'][$depchannel][strtolower($dep['dep']['name'])]); 389 } 390 } 391 } 392 unset($data['dependencies'][$channel][$package]); 393 if (!count($data['dependencies'][$channel])) { 394 unset($data['dependencies'][$channel]); 395 } 396 if (!count($data['dependencies'])) { 397 unset($data['dependencies']); 398 } 399 if (!count($data['packages'])) { 400 unset($data['packages']); 401 } 402 $this->_writeDepDB($data); 403 } 404 405 /** 406 * Rebuild the dependency DB by reading registry entries. 407 * @return true|PEAR_Error 408 */ 409 function rebuildDB() 410 { 411 $depdb = array('_version' => $this->_version); 412 if (!$this->hasWriteAccess()) { 413 // allow startup for read-only with older Registry 414 return $depdb; 415 } 416 $packages = $this->_registry->listAllPackages(); 417 foreach ($packages as $channel => $ps) { 418 foreach ($ps as $package) { 419 $package = $this->_registry->getPackage($package, $channel); 420 $this->_setPackageDeps($depdb, $package); 421 } 422 } 423 $error = $this->_writeDepDB($depdb); 424 if (PEAR::isError($error)) { 425 return $error; 426 } 427 $this->_cache = $depdb; 428 return true; 429 } 430 431 /** 432 * Register usage of the dependency DB to prevent race conditions 433 * @param int one of the LOCK_* constants 434 * @return true|PEAR_Error 435 * @access private 436 */ 437 function _lock($mode = LOCK_EX) 438 { 439 if (!eregi('Windows 9', php_uname())) { 440 if ($mode != LOCK_UN && is_resource($this->_lockFp)) { 441 // XXX does not check type of lock (LOCK_SH/LOCK_EX) 442 return true; 443 } 444 $open_mode = 'w'; 445 // XXX People reported problems with LOCK_SH and 'w' 446 if ($mode === LOCK_SH) { 447 if (@!is_file($this->_lockfile)) { 448 touch($this->_lockfile); 449 } 450 $open_mode = 'r'; 451 } 452 453 if (!is_resource($this->_lockFp)) { 454 $this->_lockFp = @fopen($this->_lockfile, $open_mode); 455 } 456 if (!is_resource($this->_lockFp)) { 457 return PEAR::raiseError("could not create Dependency lock file" . 458 (isset($php_errormsg) ? ": " . $php_errormsg : "")); 459 } 460 if (!(int)flock($this->_lockFp, $mode)) { 461 switch ($mode) { 462 case LOCK_SH: $str = 'shared'; break; 463 case LOCK_EX: $str = 'exclusive'; break; 464 case LOCK_UN: $str = 'unlock'; break; 465 default: $str = 'unknown'; break; 466 } 467 return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)"); 468 } 469 } 470 return true; 471 } 472 473 /** 474 * Release usage of dependency DB 475 * @return true|PEAR_Error 476 * @access private 477 */ 478 function _unlock() 479 { 480 $ret = $this->_lock(LOCK_UN); 481 if (is_resource($this->_lockFp)) { 482 fclose($this->_lockFp); 483 } 484 $this->_lockFp = null; 485 return $ret; 486 } 487 488 /** 489 * Load the dependency database from disk, or return the cache 490 * @return array|PEAR_Error 491 */ 492 function _getDepDB() 493 { 494 if (!$this->hasWriteAccess()) { 495 return array('_version' => $this->_version); 496 } 497 if (isset($this->_cache)) { 498 return $this->_cache; 499 } 500 if (!$fp = fopen($this->_depdb, 'r')) { 501 $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'"); 502 return $err; 503 } 504 $rt = get_magic_quotes_runtime(); 505 set_magic_quotes_runtime(0); 506 clearstatcache(); 507 if (function_exists('file_get_contents')) { 508 fclose($fp); 509 $data = unserialize(file_get_contents($this->_depdb)); 510 } else { 511 $data = unserialize(fread($fp, filesize($this->_depdb))); 512 fclose($fp); 513 } 514 set_magic_quotes_runtime($rt); 515 $this->_cache = $data; 516 return $data; 517 } 518 519 /** 520 * Write out the dependency database to disk 521 * @param array the database 522 * @return true|PEAR_Error 523 * @access private 524 */ 525 function _writeDepDB(&$deps) 526 { 527 if (PEAR::isError($e = $this->_lock(LOCK_EX))) { 528 return $e; 529 } 530 if (!$fp = fopen($this->_depdb, 'wb')) { 531 $this->_unlock(); 532 return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing"); 533 } 534 $rt = get_magic_quotes_runtime(); 535 set_magic_quotes_runtime(0); 536 fwrite($fp, serialize($deps)); 537 set_magic_quotes_runtime($rt); 538 fclose($fp); 539 $this->_unlock(); 540 $this->_cache = $deps; 541 return true; 542 } 543 544 /** 545 * Register all dependencies from a package in the dependencies database, in essence 546 * "installing" the package's dependency information 547 * @param array the database 548 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 549 * @access private 550 */ 551 function _setPackageDeps(&$data, &$pkg) 552 { 553 $pkg->setConfig($this->_config); 554 if ($pkg->getPackagexmlVersion() == '1.0') { 555 $gen = &$pkg->getDefaultGenerator(); 556 $deps = $gen->dependenciesToV2(); 557 } else { 558 $deps = $pkg->getDeps(true); 559 } 560 if (!$deps) { 561 return; 562 } 563 $data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())] 564 = array(); 565 if (isset($deps['required']['package'])) { 566 if (!isset($deps['required']['package'][0])) { 567 $deps['required']['package'] = array($deps['required']['package']); 568 } 569 foreach ($deps['required']['package'] as $dep) { 570 $this->_registerDep($data, $pkg, $dep, 'required'); 571 } 572 } 573 if (isset($deps['optional']['package'])) { 574 if (!isset($deps['optional']['package'][0])) { 575 $deps['optional']['package'] = array($deps['optional']['package']); 576 } 577 foreach ($deps['optional']['package'] as $dep) { 578 $this->_registerDep($data, $pkg, $dep, 'optional'); 579 } 580 } 581 if (isset($deps['required']['subpackage'])) { 582 if (!isset($deps['required']['subpackage'][0])) { 583 $deps['required']['subpackage'] = array($deps['required']['subpackage']); 584 } 585 foreach ($deps['required']['subpackage'] as $dep) { 586 $this->_registerDep($data, $pkg, $dep, 'required'); 587 } 588 } 589 if (isset($deps['optional']['subpackage'])) { 590 if (!isset($deps['optional']['subpackage'][0])) { 591 $deps['optional']['subpackage'] = array($deps['optional']['subpackage']); 592 } 593 foreach ($deps['optional']['subpackage'] as $dep) { 594 $this->_registerDep($data, $pkg, $dep, 'optional'); 595 } 596 } 597 if (isset($deps['group'])) { 598 if (!isset($deps['group'][0])) { 599 $deps['group'] = array($deps['group']); 600 } 601 foreach ($deps['group'] as $group) { 602 if (isset($group['package'])) { 603 if (!isset($group['package'][0])) { 604 $group['package'] = array($group['package']); 605 } 606 foreach ($group['package'] as $dep) { 607 $this->_registerDep($data, $pkg, $dep, 'optional', 608 $group['attribs']['name']); 609 } 610 } 611 if (isset($group['subpackage'])) { 612 if (!isset($group['subpackage'][0])) { 613 $group['subpackage'] = array($group['subpackage']); 614 } 615 foreach ($group['subpackage'] as $dep) { 616 $this->_registerDep($data, $pkg, $dep, 'optional', 617 $group['attribs']['name']); 618 } 619 } 620 } 621 } 622 if ($data['dependencies'][strtolower($pkg->getChannel())] 623 [strtolower($pkg->getPackage())] == array()) { 624 unset($data['dependencies'][strtolower($pkg->getChannel())] 625 [strtolower($pkg->getPackage())]); 626 if (!count($data['dependencies'][strtolower($pkg->getChannel())])) { 627 unset($data['dependencies'][strtolower($pkg->getChannel())]); 628 } 629 } 630 } 631 632 /** 633 * @param array the database 634 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 635 * @param array the specific dependency 636 * @param required|optional whether this is a required or an optional dep 637 * @param string|false dependency group this dependency is from, or false for ordinary dep 638 */ 639 function _registerDep(&$data, &$pkg, $dep, $type, $group = false) 640 { 641 $info = array( 642 'dep' => $dep, 643 'type' => $type, 644 'group' => $group); 645 646 if (isset($dep['channel'])) { 647 $depchannel = $dep['channel']; 648 } else { 649 $depchannel = '__uri'; 650 } 651 $data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())][] 652 = $info; 653 if (isset($data['packages'][strtolower($depchannel)][strtolower($dep['name'])])) { 654 $found = false; 655 foreach ($data['packages'][strtolower($depchannel)][strtolower($dep['name'])] 656 as $i => $p) { 657 if ($p['channel'] == strtolower($pkg->getChannel()) && 658 $p['package'] == strtolower($pkg->getPackage())) { 659 $found = true; 660 break; 661 } 662 } 663 if (!$found) { 664 $data['packages'][strtolower($depchannel)][strtolower($dep['name'])][] 665 = array('channel' => strtolower($pkg->getChannel()), 666 'package' => strtolower($pkg->getPackage())); 667 } 668 } else { 669 $data['packages'][strtolower($depchannel)][strtolower($dep['name'])][] 670 = array('channel' => strtolower($pkg->getChannel()), 671 'package' => strtolower($pkg->getPackage())); 672 } 673 } 674} 675?>