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();
// }}}
*/
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'));
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";
$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'])) {
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",
$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) {
} 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()
return $package;
}
+ // }}}
+ // {{{ mkDirHier($dir)
+
+ function mkDirHier($dir)
+ {
+ $this->addFileOperation('mkdir', $dir);
+ return parent::mkDirHier($dir);
+ }
+
// }}}
// {{{ _prependPath($path, $prepend)
$this->popExpect();
if (PEAR::isError($res)) {
if (empty($options['force'])) {
+ $this->revertFileTransaction();
return $this->raiseError($res);
} else {
$this->log(0, "Warning: " . $res->getMessage());
$bob->debug = $this->debug;
$built = $bob->build($descfile, array(&$this, '_buildCallback'));
if (PEAR::isError($built)) {
+ $this->revertFileTransaction();
return $built;
}
foreach ($built as $ext) {
$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(
}
}
+ 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
</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"/>
</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>