src/PEAR/Installer.php
function commitFileTransaction()
    {
        // {{{ first, check permissions and such manually
        $errors = array();
        foreach ($this->file_operations as $key => $tr) {
            list($type, $data) = $tr;
            switch ($type) {
                case 'rename':
                    if (!file_exists($data[0])) {
                        $errors[] = "cannot rename file $data[0], doesn't exist";
                    }

                    // 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] " . decoct($data[0]);
                    }
                    break;
                case 'delete':
                    if (!file_exists($data[0])) {
                        $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
                    }
                    // check that directory is writable
                    if (file_exists($data[0])) {
                        if (!is_writable(dirname($data[0]))) {
                            $errors[] = "permission denied ($type): $data[0]";
                        } else {
                            // make sure the file to be deleted can be opened for writing
                            $fp = false;
                            if (!is_dir($data[0]) &&
                                  (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
                                $errors[] = "permission denied ($type): $data[0]";
                            } elseif ($fp) {
                                fclose($fp);
                            }
                        }

                        /* Verify we are not deleting a file owned by another package
                         * This can happen when a file moves from package A to B in
                         * an upgrade ala http://pear.php.net/17986
                         */
                        $info = array(
                            'package' => strtolower($this->pkginfo->getName()),
                            'channel' => strtolower($this->pkginfo->getChannel()),
                        );
                        $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
                        if (is_array($result)) {
                            $res = array_diff($result, $info);
                            if (!empty($res)) {
                                $new = $this->_registry->getPackage($result[1], $result[0]);
                                $this->file_operations[$key] = false;
                                $this->log(3, "file $data[0] was scheduled for removal from {$this->pkginfo->getName()} but is owned by {$new->getChannel()}/{$new->getName()}, removal has been cancelled.");
                            }
                        }
                    }
                    break;
            }

        }
        // }}}

        $n = count($this->file_operations);
        $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());

        $m = count($errors);
        if ($m > 0) {
            foreach ($errors as $error) {
                if (!isset($this->_options['soft'])) {
                    $this->log(1, $error);
                }
            }

            if (!isset($this->_options['ignore-errors'])) {
                return false;
            }
        }

        $this->_dirtree = array();
        // {{{ really commit the transaction
        foreach ($this->file_operations as $i => $tr) {
            if (!$tr) {
                // support removal of non-existing backups
                continue;
            }

            list($type, $data) = $tr;
            switch ($type) {
                case 'backup':
                    if (!file_exists($data[0])) {
                        $this->file_operations[$i] = false;
                        break;
                    }

                    if (!@copy($data[0], $data[0] . '.bak')) {
                        $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
                            '.bak ' . $php_errormsg);
                        return false;
                    }
                    $this->log(3, "+ backup $data[0] to $data[0].bak");
                    break;
                case 'removebackup':
                    if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
                        unlink($data[0] . '.bak');
                        $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
                    }
                    break;
                case 'rename':
                    $test = file_exists($data[1]) ? @unlink($data[1]) : null;
                    if (!$test && file_exists($data[1])) {
                        if ($data[2]) {
                            $extra = ', this extension must be installed manually.  Rename to "' .
                                basename($data[1]) . '"';
                        } else {
                            $extra = '';
                        }

                        if (!isset($this->_options['soft'])) {
                            $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
                                $data[0] . $extra);
                        }

                        if (!isset($this->_options['ignore-errors'])) {
                            return false;
                        }
                    }

                    // permissions issues with rename - copy() is far superior
                    $perms = @fileperms($data[0]);
                    if (!@copy($data[0], $data[1])) {
                        $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
                            ' ' . $php_errormsg);
                        return false;
                    }

                    // copy over permissions, otherwise they are lost
                    @chmod($data[1], $perms);
                    @unlink($data[0]);
                    $this->log(3, "+ mv $data[0] $data[1]");
                    break;
                case 'chmod':
                    if (!@chmod($data[1], $data[0])) {
                        $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
                            decoct($data[0]) . ' ' . $php_errormsg);
                        return false;
                    }

                    $octmode = decoct($data[0]);
                    $this->log(3, "+ chmod $octmode $data[1]");
                    break;
                case 'delete':
                    if (file_exists($data[0])) {
                        if (!@unlink($data[0])) {
                            $this->log(1, 'Could not delete ' . $data[0] . ' ' .
                                $php_errormsg);
                            return false;
                        }
                        $this->log(3, "+ rm $data[0]");
                    }
                    break;
                case 'rmdir':
                    if (file_exists($data[0])) {
                        do {
                            $testme = opendir($data[0]);
                            while (false !== ($entry = readdir($testme))) {
                                if ($entry == '.' || $entry == '..') {
                                    continue;
                                }
                                closedir($testme);
                                break 2; // this directory is not empty and can't be
                                         // deleted
                            }

                            closedir($testme);
                            if (!@rmdir($data[0])) {
                                $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
                                    $php_errormsg);
                                return false;
                            }
                            $this->log(3, "+ rmdir $data[0]");
                        } while (false);
                    }
                    break;
                case 'installed_as':
                    $this->pkginfo->setInstalledAs($data[0], $data[1]);
                    if (!isset($this->_dirtree[dirname($data[1])])) {
                        $this->_dirtree[dirname($data[1])] = true;
                        $this->pkginfo->setDirtree(dirname($data[1]));

                        while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
                                $data[3] != '/' && $data[3] != '\\') {
                            $this->pkginfo->setDirtree($pp =
                                $this->_prependPath($data[3], $data[2]));
                            $this->_dirtree[$pp] = true;
                            $data[3] = dirname($data[3]);
                        }
                    }
                    break;
            }
        }
        // }}}
        $this->log(2, "successfully committed $n file operations");
        $this->file_operations = array();
        return true;
    }
function commitFileTransaction()
    {
        // {{{ first, check permissions and such manually
        $errors = array();
        foreach ($this->file_operations as $key => $tr) {
            list($type, $data) = $tr;
            switch ($type) {
                case 'rename':
                    if (!file_exists($data[0])) {
                        $errors[] = "cannot rename file $data[0], doesn't exist";
                    }

                    // 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] " . decoct($data[0]);
                    }
                    break;
                case 'delete':
                    if (!file_exists($data[0])) {
                        $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
                    }
                    // check that directory is writable
                    if (file_exists($data[0])) {
                        if (!is_writable(dirname($data[0]))) {
                            $errors[] = "permission denied ($type): $data[0]";
                        } else {
                            // make sure the file to be deleted can be opened for writing
                            $fp = false;
                            if (!is_dir($data[0]) &&
                                  (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
                                $errors[] = "permission denied ($type): $data[0]";
                            } elseif ($fp) {
                                fclose($fp);
                            }
                        }

                        /* Verify we are not deleting a file owned by another package
                         * This can happen when a file moves from package A to B in
                         * an upgrade ala http://pear.php.net/17986
                         */
                        $info = array(
                            'package' => strtolower($this->pkginfo->getName()),
                            'channel' => strtolower($this->pkginfo->getChannel()),
                        );
                        $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
                        if (is_array($result)) {
                            $res = array_diff($result, $info);
                            if (!empty($res)) {
                                $new = $this->_registry->getPackage($result[1], $result[0]);
                                $this->file_operations[$key] = false;
                                $this->log(3, "file $data[0] was scheduled for removal from {$this->pkginfo->getName()} but is owned by {$new->getChannel()}/{$new->getName()}, removal has been cancelled.");
                            }
                        }
                    }
                    break;
            }

        }
        // }}}

        $n = count($this->file_operations);
        $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());

        $m = count($errors);
        if ($m > 0) {
            foreach ($errors as $error) {
                if (!isset($this->_options['soft'])) {
                    $this->log(1, $error);
                }
            }

            if (!isset($this->_options['ignore-errors'])) {
                return false;
            }
        }

        $this->_dirtree = array();
        // {{{ really commit the transaction
        foreach ($this->file_operations as $i => $tr) {
            if (!$tr) {
                // support removal of non-existing backups
                continue;
            }

            list($type, $data) = $tr;
            switch ($type) {
                case 'backup':
                    if (!file_exists($data[0])) {
                        $this->file_operations[$i] = false;
                        break;
                    }

                    if (!@copy($data[0], $data[0] . '.bak')) {
                        $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
                            '.bak ' . $php_errormsg);
                        return false;
                    }
                    $this->log(3, "+ backup $data[0] to $data[0].bak");
                    break;
                case 'removebackup':
                    if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
                        unlink($data[0] . '.bak');
                        $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
                    }
                    break;
                case 'rename':
                    $test = file_exists($data[1]) ? @unlink($data[1]) : null;
                    if (!$test && file_exists($data[1])) {
                        if ($data[2]) {
                            $extra = ', this extension must be installed manually.  Rename to "' .
                                basename($data[1]) . '"';
                        } else {
                            $extra = '';
                        }

                        if (!isset($this->_options['soft'])) {
                            $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
                                $data[0] . $extra);
                        }

                        if (!isset($this->_options['ignore-errors'])) {
                            return false;
                        }
                    }

                    // permissions issues with rename - copy() is far superior
                    $perms = @fileperms($data[0]);
                    if (!@copy($data[0], $data[1])) {
                        $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
                            ' ' . $php_errormsg);
                        return false;
                    }

                    // copy over permissions, otherwise they are lost
                    @chmod($data[1], $perms);
                    @unlink($data[0]);
                    $this->log(3, "+ mv $data[0] $data[1]");
                    break;
                case 'chmod':
                    if (!@chmod($data[1], $data[0])) {
                        $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
                            decoct($data[0]) . ' ' . $php_errormsg);
                        return false;
                    }

                    $octmode = decoct($data[0]);
                    $this->log(3, "+ chmod $octmode $data[1]");
                    break;
                case 'delete':
                    if (file_exists($data[0])) {
                        if (!@unlink($data[0])) {
                            $this->log(1, 'Could not delete ' . $data[0] . ' ' .
                                $php_errormsg);
                            return false;
                        }
                        $this->log(3, "+ rm $data[0]");
                    }
                    break;
                case 'rmdir':
                    if (file_exists($data[0])) {
                        do {
                            $testme = opendir($data[0]);
                            while (false !== ($entry = readdir($testme))) {
                                if ($entry == '.' || $entry == '..') {
                                    continue;
                                }
                                closedir($testme);
                                break 2; // this directory is not empty and can't be
                                         // deleted
                            }

                            closedir($testme);
                            if (!@rmdir($data[0])) {
                                $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
                                    $php_errormsg);
                                return false;
                            }
                            $this->log(3, "+ rmdir $data[0]");
                        } while (false);
                    }
                    break;
                case 'installed_as':
                    $this->pkginfo->setInstalledAs($data[0], $data[1]);
                    if (!isset($this->_dirtree[dirname($data[1])])) {
                        $this->_dirtree[dirname($data[1])] = true;
                        $this->pkginfo->setDirtree(dirname($data[1]));

                        while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
                                $data[3] != '/' && $data[3] != '\\') {
                            $this->pkginfo->setDirtree($pp =
                                $this->_prependPath($data[3], $data[2]));
                            $this->_dirtree[$pp] = true;
                            $data[3] = dirname($data[3]);
                        }
                    }
                    break;
            }
        }
        // }}}
        $this->log(2, "successfully committed $n file operations");
        $this->file_operations = array();
        return true;
    }
{{{ commitFileTransaction()