]> granicus.if.org Git - php/commitdiff
* implemented file transactions so installs may be safely aborted
authorStig Bakken <ssb@php.net>
Sun, 10 Nov 2002 03:03:20 +0000 (03:03 +0000)
committerStig Bakken <ssb@php.net>
Sun, 10 Nov 2002 03:03:20 +0000 (03:03 +0000)
* preparing 1.0b2 release

pear/PEAR/Installer.php
pear/package-PEAR.xml

index e6507a35adf3e552634e4b5f9f6b1df4e4f620df..8f7935fbf5f4ad10f4b16e7749ebd374d0dd1efe 100644 (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
index ff11ff3fd267b651fc7382538ee2837f92b26f99..13fcbb56f3fb8c479367e8597d5f8f863ecef017 100644 (file)
     </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>