* implemented file transactions so installs may be safely aborted

* preparing 1.0b2 release
This commit is contained in:
Stig Bakken 2002-11-10 03:03:20 +00:00
parent 609992baeb
commit 888a9bd2fe
2 changed files with 206 additions and 52 deletions

View file

@ -87,9 +87,17 @@ class PEAR_Installer extends PEAR_Common
var $registry;
/** List of file transactions queued for an install/upgrade/uninstall.
*
* Format:
* array(
* 0 => array("rename => array("from-file", "to-file")),
* 1 => array("delete" => array("file-to-delete")),
* ...
* )
*
* @var array
*/
var $transactions = array();
var $file_operations = array();
// }}}
@ -104,7 +112,7 @@ class PEAR_Installer extends PEAR_Common
*/
function PEAR_Installer(&$ui)
{
$this->PEAR_Common();
parent::PEAR_Common();
$this->setFrontendObject($ui);
$this->debug = $this->config->get('verbose');
$this->registry = &new PEAR_Registry($this->config->get('php_dir'));
@ -156,6 +164,7 @@ class PEAR_Installer extends PEAR_Common
function _installFile($file, $atts, $tmp_path)
{
static $os;
ini_set("track_errors", 1);
if (isset($atts['platform'])) {
if (empty($os)) {
include_once "OS/Guess.php";
@ -188,7 +197,6 @@ class PEAR_Installer extends PEAR_Common
$this->source_files++;
return;
default:
// Files with no role will end in "/"
return $this->raiseError("Invalid role `$atts[role]' for file $file");
}
if (!empty($atts['baseinstalldir'])) {
@ -210,8 +218,9 @@ class PEAR_Installer extends PEAR_Common
DIRECTORY_SEPARATOR,
array($dest_file, $orig_file));
$installed_as = $dest_file;
$dest_file = $this->_prependPath($dest_file, $this->installroot);
$dest_dir = dirname($dest_file);
$final_dest_file = $this->_prependPath($dest_file, $this->installroot);
$dest_dir = dirname($final_dest_file);
$dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
if (!@is_dir($dest_dir)) {
if (!$this->mkDirHier($dest_dir)) {
return $this->raiseError("failed to mkdir $dest_dir",
@ -255,23 +264,26 @@ class PEAR_Installer extends PEAR_Common
$subst_to[] = $to;
}
}
$this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $dest_file");
$this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
if (sizeof($subst_from)) {
$contents = str_replace($subst_from, $subst_to, $contents);
}
$wp = @fopen($dest_file, "w");
if (!is_resource($wp)) {
return $this->raiseError("failed to create $dest_file",
return $this->raiseError("failed to create $dest_file: $php_errormsg",
PEAR_INSTALLER_FAILED);
}
if (!fwrite($wp, $contents)) {
return $this->raiseError("failed writing to $dest_file: $php_errormsg",
PEAR_INSTALLER_FAILED);
}
fwrite($wp, $contents);
fclose($wp);
}
if (isset($md5sum)) {
if ($md5sum == $atts['md5sum']) {
$this->log(3, "md5sum ok: $dest_file");
$this->log(3, "md5sum ok: $final_dest_file");
} else {
$this->log(0, "warning : bad md5sum for file $dest_file");
$this->log(0, "warning : bad md5sum for file $final_dest_file");
}
}
if (!OS_WINDOWS) {
@ -281,18 +293,128 @@ class PEAR_Installer extends PEAR_Common
} else {
$mode = 0666 & ~(int)octdec($this->config->get('umask'));
}
$this->addFileOperation("chmod", array($mode, $dest_file));
if (!@chmod($dest_file, $mode)) {
$this->log(0, "failed to change mode of $dest_file");
}
}
$this->addFileOperation("rename", array($dest_file, $final_dest_file));
// XXX SHOULD BE DONE ONLY AFTER COMMIT
// Store the full path where the file was installed for easy unistall
$this->pkginfo['filelist'][$file]['installed_as'] = $installed_as;
$this->log(2, "installed: $dest_file");
//$this->log(2, "installed: $dest_file");
return PEAR_INSTALLER_OK;
}
// }}}
// {{{ addFileOperation()
function addFileOperation($type, $data)
{
$this->log(3, "adding to transaction: $type " . implode(" ", $data));
$this->file_operations[] = array($type, $data);
}
// }}}
// {{{ startFileTransaction()
function startFileTransaction($revert_in_case = false)
{
if (count($this->file_operations) && $revert_in_case) {
$this->revertFileTransaction();
}
$this->file_operations = array();
}
// }}}
// {{{ commitFileTransaction()
function commitFileTransaction()
{
$n = count($this->file_operations);
$this->log(2, "about to commit $n file operations");
// first, check permissions and such manually
$errors = array();
foreach ($this->file_operations as $tr) {
list($type, $data) = $tr;
switch ($type) {
case 'rename':
// check that dest dir. is writable
if (!is_writable(dirname($data[1]))) {
$errors[] = "permission denied ($type): $data[1]";
}
break;
case 'chmod':
// check that file is writable
if (!is_writable($data[1])) {
$errors[] = "permission denied ($type): $data[1]";
}
break;
case 'delete':
// check that directory is writable
if (!is_writable(dirname($data[0]))) {
$errors[] = "permission denied ($type): $data[0]";
}
break;
}
}
$m = sizeof($errors);
if ($m > 0) {
foreach ($errors as $error) {
$this->log(1, $error);
}
return false;
}
// really commit the transaction
foreach ($this->file_operations as $tr) {
list($type, $data) = $tr;
switch ($type) {
case 'rename':
@rename($data[0], $data[1]);
break;
case 'chmod':
@chmod($data[0], $data[1]);
break;
case 'delete':
@unlink($data[0]);
break;
}
}
$this->log(2, "successfully commited $n file operations");
$this->file_operations = array();
return true;
}
// }}}
// {{{ revertFileTransaction()
function revertFileTransaction()
{
$n = count($this->file_operations);
$this->log(2, "reverting $n file operations");
foreach ($this->file_operations as $tr) {
list($type, $data) = $tr;
switch ($type) {
case 'rename':
@unlink($data[0]);
$this->log(3, "+ rm $data[0]");
break;
case 'mkdir':
@rmdir($data[0]);
$this->log(3, "+ rmdir $data[0]");
break;
case 'chmod':
break;
case 'delete':
break;
}
}
$this->file_operations = array();
}
// }}}
// {{{ getPackageDownloadUrl()
@ -310,6 +432,15 @@ class PEAR_Installer extends PEAR_Common
return $package;
}
// }}}
// {{{ mkDirHier($dir)
function mkDirHier($dir)
{
$this->addFileOperation('mkdir', $dir);
return parent::mkDirHier($dir);
}
// }}}
// {{{ _prependPath($path, $prepend)
@ -550,6 +681,7 @@ class PEAR_Installer extends PEAR_Common
$this->popExpect();
if (PEAR::isError($res)) {
if (empty($options['force'])) {
$this->revertFileTransaction();
return $this->raiseError($res);
} else {
$this->log(0, "Warning: " . $res->getMessage());
@ -567,6 +699,7 @@ class PEAR_Installer extends PEAR_Common
$bob->debug = $this->debug;
$built = $bob->build($descfile, array(&$this, '_buildCallback'));
if (PEAR::isError($built)) {
$this->revertFileTransaction();
return $built;
}
foreach ($built as $ext) {
@ -576,6 +709,7 @@ class PEAR_Installer extends PEAR_Common
$this->log(3, "+ cp $ext[file] ext_dir");
$copyto = $this->_prependPath($dest, $this->installroot);
if (!@copy($ext['file'], $copyto)) {
$this->revertFileTransaction();
return $this->raiseError("failed to copy $bn to $copyto");
}
$pkginfo['filelist'][$bn] = array(
@ -589,6 +723,11 @@ class PEAR_Installer extends PEAR_Common
}
}
if (!$this->commitFileTransaction()) {
$this->revertFileTransaction();
return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
}
// Register that the package is installed -----------------------
if (empty($options['upgrade'])) {
// if 'force' is used, replace the info in registry

View file

@ -31,50 +31,13 @@
</maintainer>
</maintainers>
<release>
<version>1.0b1</version>
<version>1.0b2</version>
<state>stable</state>
<date>2002-10-12</date>
<date>2002-11-11</date>
<notes>
New Features, Installer:
* new command: "pear makerpm"
* new command: "pear search"
* new command: "pear upgrade-all"
* new command: "pear config-help"
* new command: "pear sign"
* Windows support for "pear build" (requires
msdev)
* new dependency type: "zend"
* XML-RPC results may now be cached (see
cache_dir and cache_ttl config)
* HTTP proxy authorization support
* install/upgrade install-root support
Bugfixes, Installer:
* fix for XML-RPC bug that made some remote
commands fail
* fix problems under Windows with
DIRECTORY_SEPARATOR
* lots of other minor fixes
* --force option did not work for "pear install
Package"
* http downloader used "4.2.1" rather than
"PHP/4.2.1" as user agent
* bending over a little more to figure out how
PHP is installed
* "platform" file attribute was not included
during "pear package"
New Features, PEAR Library:
* added PEAR::loadExtension($ext)
* added PEAR::delExpect()
* System::mkTemp() now cleans up at shutdown
* defined PEAR_ZE2 constant (boolean)
* added PEAR::throwError() with a simpler API
than raiseError()
Bugfixes, PEAR Library:
* ZE2 compatibility fixes
* use getenv() as fallback for $_ENV
* installer aborts failed installs nicely (using
file "transactions")
</notes>
<filelist>
<file role="data" name="package.dtd"/>
@ -125,6 +88,58 @@ Bugfixes, PEAR Library:
</deps>
</release>
<changelog>
<release>
<version>1.0b1</version>
<state>stable</state>
<date>2002-10-12</date>
<notes>
New Features, Installer:
* new command: "pear makerpm"
* new command: "pear search"
* new command: "pear upgrade-all"
* new command: "pear config-help"
* new command: "pear sign"
* Windows support for "pear build" (requires
msdev)
* new dependency type: "zend"
* XML-RPC results may now be cached (see
cache_dir and cache_ttl config)
* HTTP proxy authorization support
* install/upgrade install-root support
Bugfixes, Installer:
* fix for XML-RPC bug that made some remote
commands fail
* fix problems under Windows with
DIRECTORY_SEPARATOR
* lots of other minor fixes
* --force option did not work for "pear install
Package"
* http downloader used "4.2.1" rather than
"PHP/4.2.1" as user agent
* bending over a little more to figure out how
PHP is installed
* "platform" file attribute was not included
during "pear package"
New Features, PEAR Library:
* added PEAR::loadExtension($ext)
* added PEAR::delExpect()
* System::mkTemp() now cleans up at shutdown
* defined PEAR_ZE2 constant (boolean)
* added PEAR::throwError() with a simpler API
than raiseError()
Bugfixes, PEAR Library:
* ZE2 compatibility fixes
* use getenv() as fallback for $_ENV
</notes>
<deps>
<dep type="php" rel="ge" version="4.1"/>
<dep type="pkg" rel="ge" version="0.4">Archive_Tar</dep>
<dep type="pkg" rel="ge" version="0.11">Console_Getopt</dep>
</deps>
</release>
<release>
<version>0.90</version>
<state>beta</state>