From: Stig Bakken Date: Sun, 10 Nov 2002 03:03:20 +0000 (+0000) Subject: * implemented file transactions so installs may be safely aborted X-Git-Tag: php-4.3.0RC1~158 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=888a9bd2fe8bb9abd236e07194c7d87c9ff0455f;p=php * implemented file transactions so installs may be safely aborted * preparing 1.0b2 release --- diff --git a/pear/PEAR/Installer.php b/pear/PEAR/Installer.php index e6507a35ad..8f7935fbf5 100644 --- a/pear/PEAR/Installer.php +++ b/pear/PEAR/Installer.php @@ -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 diff --git a/pear/package-PEAR.xml b/pear/package-PEAR.xml index ff11ff3fd2..13fcbb56f3 100644 --- a/pear/package-PEAR.xml +++ b/pear/package-PEAR.xml @@ -31,50 +31,13 @@ - 1.0b1 + 1.0b2 stable - 2002-10-12 + 2002-11-11 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") @@ -125,6 +88,58 @@ Bugfixes, PEAR Library: + + 1.0b1 + stable + 2002-10-12 + +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 + + + + Archive_Tar + Console_Getopt + + 0.90 beta