io timeout after 180 seconds

chris allen maillinglist at mychristiannetwork.com
Mon Feb 28 10:15:42 GMT 2005


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
 
Thanks for all the help so far. I am learning a lot from it.


I have a script that I will post bellow that when I run it gives me
this error.

mysql/website_forum/nntp_settings.MYD
io timeout after 180 seconds - exiting
rsync error: timeout in data send/receive (code 30) at io.c(103)
rsync: connection unexpectedly closed (1493583 bytes read so far)
rsync error: error in rsync protocol data stream (code 12) at io.c(165)

WARNING: there seems to have been an rsync error.
Check the logs for more information.


The script is running php and the script is attached and posted bellow


#!/usr/bin/php -q
<?php
//** $Id: ribs 1625 2004-11-24 20:55:48Z jrust $ */
// {{{ version

define('RIBS_VERSION', '2.3');

// }}}
// {{{ description

/**
~ * RIBS (Rsync Incremental Backup System) by Jason Rust
<jrust at rustyaprts.com>
~ * Copyright (c) 2002-2003 Jason Rust <jrust at rustyparts.com>
~ * The latest version of this program can be found at
~ * http://rustyparts.com/scripts.php
~ *
~ * License:
~ *
~ * This source file is subject to the GNU Public License (GPL),
~ * that is bundled with this package in the file LICENSE, and is
~ * available at through the world-wide-web at
~ * http://www.fsf.org/copyleft/lesser.html
~ * If you did not receive a copy of the LGPL and are unable to
~ * obtain it through the world-wide-web, you can get it by writing the
~ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
~ * MA 02111-1307, USA.
~ *
~ * This program is distributed in the hope that it will be useful,
~ * but WITHOUT ANY WARRANTY; without even the implied warranty of
~ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
~ *
~ * See http://www.gnu.org/copyleft/gpl.html for a copy of the license.
~ *
~ * Description:
~ *
~ * RIBS is an incremental backup system written in PHP which utilizes
~ * some common *nix programs (specifically rsync, ssh and cp).
~ *
~ * Requirements:
~ *
~ * rsync - http://samba.anu.edu.au/rsync/
~ * cp & rm - http://www.gnu.org/software/fileutils/fileutils.html
~ * PHP - http://www.php.net/
~ * basic PEAR libraries (as of version 1.1) - http://pear.php.net/
~ *
~ * Use:
~ *
~ * See the README that comes with this package.
~ */

// }}}
// {{{ user settings

/****** Configuration Section ******/

/**
~ * This is the place on the server where the backups will end up WITH
trailing slash
~ * @type string
~ */
$s_destinationDir = dirname(__FILE__) . '/backups/';

/**
~ * An array of the different servers to back up, and what directories
on them to back up
~ * The structure is as follows:
~ * 'backup_name' => array(
~ *      'enabled'       => 'this config enabled? true or false',
~ *      'ip'            => 'server ip address/FQDN',
~ *      'ssh_user'      => 'the ssh user to use for rsync',
~ *      'ssh_port'      => 'the ssh port (optional, defaults to 22)',
~ *      'directories'   => 'backup directories separated by a space
WITHOUT a trailing slash (i.e. /home /root)',
~ *                          If a directory has a space in it, escape
it with a \
~ *      'excludes'      => 'the directories to exclude separated by a
space.
~ *                          See the README for more information on
exclude patterns.'
~ *      'limits'        => 'an array of directory limits that will
allow you to override the limits you set in
~ *                          $a_backupTypeSettings.  Completely
optional. e.g. array('hourly' => 2) if you back up
~ *                          hourly twice a day.'
~ *      'use_hard_links'=> 'Whether to do incremental backups using
hard links or real files.
~ *                          Default option is true.  See "Backup
Types" in the README for more info.'
~ *      'post_command'  => 'A shell command to run after a successful
backup.  Replaces %fullPath%
~ *                          and %backupType% with the real values.'
~ *      'post_error_command' => 'Same as post_command, but run when an
error occurs.'
~ *  ),
~ * @type array
~ */
$a_backupHosts = array(
~    'luke' => array(
~        'enabled'       => true,
~        'ip'            => 'mychristiannetwork.com',
~        'ssh_user'      => 'root',
~        'directories'   => '/etc /var/lib/mysql /home',
~        'excludes'      => '',
~        'limits'        => array('hourly' => 24), // because this is
backed up every hour
~    ),
~    'small_host' => array(
~        'enabled'       => false,
~        'ip'            => 'small_host.example.com',
~        'ssh_user'      => 'backup',
~        'ssh_port'      => '9999',
~        'directories'   => '/etc',
~        'excludes'      => '',
~    ),
~    'big_host' => array(
~        'enabled'       => false,
~        'ip'            => '10.52.1.1',
~        'ssh_user'      => 'root',
~        'directories'   => '/var',
~        'excludes'      => 'mp3/', // exclude any mp3 directories
~    ),
~    // A test example configuration that can be used with the test
~    // directory that comes with RIBS.
~    'example' => array(
~        'enabled'       => false,
~        'ip'            => 'localhost',
~        'ssh_user'      => get_current_user(),
~        'directories'   => dirname(__FILE__) . '/test\ dir',
~        'use_hard_links'=> true,
~        // An example of a post_command which will tar up each backup.
~        // NOTE: this is only useful if use_hard_links is false
~        // 'post_command'  => 'if [ -d %fullPath%%backupType%.1 ];
then tar czv -C %fullPath%%backupType%.1 -f backup.tar.gz .; rm -rf
%fullPath%%backupType%.1; mkdir %fullPath%%backupType%.1; mv
backup.tar.gz %fullPath%%backupType%.1/; fi',
~        'excludes'      => '/test\ dir/my\ mp3 +/test\ dir/include.mp3
*.mp3 *.svn/ junk/',
~                           // exclude the root mp3 directory,
~                           // any mp3 files anywhere else (except for
include.mp3),
~                           // and any .svn/ and junk/ directories
~    ),
);

/**
~ * Some settings for the four different backup types.  For each type
you must specify
~ * 'limit' => of directories to keep.  So, if you run every two hours,
make hourly 12,
~ * if every hour then 24, etc.  Try to span the entire day.  The below
setting runs 8
~ * times a day (every three hours), keeps 7 days, 4 weeks, and 12
months of backups.
~ * 'email' => whether to send email or not after completion
~ * 'log' => whether to log after completion
~ * @type array
~ */
$a_backupTypeSettings = array(
~    'hourly'    => array('limit' => 8, 'email' => true, 'log' => true),
~    'daily'     => array('limit' => 7, 'email' => true, 'log' => true),
~    'weekly'    => array('limit' => 4, 'email' => true, 'log' => true),
~    'monthly'   => array('limit' => 12, 'email' => true, 'log' => true),
);

/**
~ * The email address to send reports to (you can comma separate
multiple addresses)
~ * @type string
~ */
$s_email = 'cyo at mychristiannetwork.com';

/**
~ * The log file.
~ * @type string
~ */
$s_logFile = dirname(__FILE__) . '/ribs.log';

/**
~ * When to start the log over.  Note, this does not mean we rotate
the log
~ * it is just to keep the log file from getting too big.  Can be
hourly, daily,
~ * weekly, or monthly
~ * @type string
~ */
$s_restartBackupLog = 'weekly';

/**
~ * When to do an actual rsync.  Otherwise, we just copy the oldest
directory
~ * of the type below to the current type.  Example: if doing a monthly
backup
~ * and $s_rsyncType is not 'monthly' then the oldest weekly directory
will be
~ * copied to monthly.0.  This keeps down the amount of disk space
needed.  Usually you
~ * will want this to be 'hourly'
~ * @type string
~ */
$s_rsyncType = 'hourly';

/**
~ * Always email if there's an error?  This overrides the specific
backup setting
~ * to ensure you will get an email if there is a problem.
~ * @type bool
~ */
$b_emailOnError = true;

/**
~ * Die quietly?  If so, then on error we just shut up, email (see
above), and quit,
~ * otherwise we throw the error.
~ * @type bool
~ */
$b_silentOnError = false;

/**
~ * Default permissions for when we have to create a new directory
~ * @type int
~ */
$s_defaultDirPerms = 0750;

/**
~ * Additional comands to be run at the end of each backup (useful for
giving
~ * additional info about file sizes, directories, etc.).  Leave empty
for nothing.
~ * %fullPath% is a placeholder that will be filled in with the full
path to the current backup.
~ * @type string
~ */
$s_extraCommands = "ls -l %fullPath%; df -h";

/**
~ * The extra rsync commands to use
~ * @type string
~ */
$s_rsyncArgs = '-arztplv --delete --delete-excluded --stats
- --timeout=180';

/**
~ * The path to the rsync command
~ * @type string
~ */
$s_rsync = '/usr/bin/rsync';

/**
~ * The path to rm
~ * @var string
~ */
$s_rm = '/bin/rm';

/**
~ * The path to cp
~ * @type string
~ */
$s_cp = '/bin/cp';

/****** End Config. No need to edit anything else! ******/

// }}}
// {{{ readline()

/**
~ * Reads a line from standard input.  Have to put it up here so php
loads it in.
~ *
~ * @access public
~ * @return string The string from input
~ */
if (!function_exists('readline')) {
~    function readline () {
~        $fp = fopen('php://stdin', 'r');
~        $in = fgets($fp, 4094); // Maximum windows buffer size
~        fclose ($fp);
~        return $in;
~    }
}

// }}}
// {{{ requires

require_once 'Console/Getopt.php';

// }}}
// {{{ grab command line vars

// don't run out of time
set_time_limit(0);

// we want all errors
error_reporting(E_ALL);

$args = Console_Getopt::readPHPArgv();
if (PEAR::isError($args)) {
~    die('Fatal Error: ' . $args->getMessage() . "\n");
}

$options = Console_Getopt::getopt($args, 'dhtr', array('debug',
'help', 'test', 'reinit'));
if (PEAR::isError($options)) {
~    die($options->getMessage() . "\n");
}

$b_debug = false;
$b_dryRun = false;
$b_cleanupDryRun = false;
$b_reinit = false;
$s_log = '';
$b_error = false;

foreach ($options[0] as $option) {
~    switch ($option[0]) {
~        case 'h':
~        case '--help':
~            showUsage($args[0]);
~            exit;
~            break;
~        case 'd':
~        case '--debug':
~            $b_debug = true;
~            break;
~        case 't':
~        case '--test':
~            $b_dryRun = true;
~            break;
~        case 'r':
~        case '--reinit':
~            $b_reinit = true;
~            break;
~    }
}

if (empty($options[1][0])) {
~    writeln('Error: Configuation not specified' . "\n");
~    showUsage($args[0]);
~    exit;
}

// grab the config names from the command line
$a_configNames = explode(',', $options[1][0]);

// ALL is a special keyword to run all backups
if ($a_configNames[0] == 'ALL') {
~    $a_configNames = array_keys($a_backupHosts);
}

if ($b_reinit) {
~    reInitBackups();
~    exit;
}

if (empty($options[1][1])) {
~    writeln('Error: Backup type not specified' . "\n");
~    showUsage($args[0]);
~    exit;
}

// grab the backup type from the command line
$s_backupType = $options[1][1];

if ($b_debug) {
~    array_shift($args);
~    writeln('Debug mode is ON.');
~    writeln('Arguments received: ' . implode(' ', $args));
~    writeln('Configuration names: ' . implode(',', $a_configNames));
~    writeln('Backup type: ' . $s_backupType);
}

if ($b_dryRun) {
~    writeln('Dry run mode is ON.');
}

// }}}
// {{{ set up vars and dirs

// start log
$s_log .= date('F j, Y, g:i a') . ": $s_backupType backups for " .
implode(',', $a_configNames) . "\n";

// make sure it's a valid type
if ($s_backupType != 'hourly' &&
~    $s_backupType != 'daily' &&
~    $s_backupType != 'weekly' &&
~    $s_backupType != 'monthly') {
~    writeln("ERROR: The backup type: '$s_backupType' is not valid
(valid types are hourly, daily, weekly, and monthly).");
~    exit;
}

// check binaries
if (!is_executable($s_rsync)) {
~    writeln("WARNING: The rsync program: '$s_rsync' is not valid.");
~    exit;
}

if (!is_executable($s_rm)) {
~    writeln("WARNING: The rm program: '$s_rm' is not valid.");
~    exit;
}

if (!is_executable($s_cp)) {
~    writeln("WARNING: The cp program: '$s_cp' is not valid.");
~    exit;
}

if (!is_dir($s_destinationDir)) {
~    mkdir($s_destinationDir, $s_defaultDirPerms);
~    $s_log .= writeln("Created destination directory:
$s_destinationDir", true);
}

// determine the type below the current type
if ($s_backupType == 'daily') {
~    $s_typeBelow = 'hourly';
}
elseif ($s_backupType == 'weekly') {
~    $s_typeBelow = 'daily';
}
elseif ($s_backupType == 'monthly') {
~    $s_typeBelow = 'weekly';
}
else {
~    $s_typeBelow = 'monthly';
}

// }}}
// {{{ loop through backup configurations

foreach ($a_configNames as $s_configName) {
~    // make sure that we are using a valid configuration
~    if (!isset($a_backupHosts[$s_configName])) {
~        $tmp_msg = writeln("WARNING: The configuation: '$s_configName'
is not valid.\n", true, true);
~        $s_log .= $tmp_msg;
~        $b_error = true;
~        continue;
~    }
~    else {
~        $s_log .= writeln("-= Beginning backups for $s_configName\n",
true, true);
~    }

~    // make sure it's enabled
~    if (!$a_backupHosts[$s_configName]['enabled']) {
~        $s_log .= writeln("$s_configName is disabled.  Skipping
backup.", true);
~        continue;
~    }

~    $s_fullPath = $s_destinationDir . $s_configName . '/';

~    if (!is_dir($s_fullPath)) {
~        mkdir($s_fullPath, $s_defaultDirPerms);
~        $s_log .= writeln("Created directory for $s_configName:
$s_fullPath", true);
~    }

~    // {{{ rotate the current list of backups if we can

~    if ($tmp_handle = opendir($s_fullPath)) {
~        $a_dirList = array();
~        $a_dirListTypeBelow = array();
~        while (false !== ($tmp_file = readdir($tmp_handle))) {
~            if (is_dir($s_fullPath . $tmp_file)) {
~                if (preg_match(":$s_backupType\.\d+$:", $tmp_file)) {
~                    $a_dirList[] = $tmp_file;
~                }

~                // for later we need the directories of the type below
~                if (preg_match(":$s_typeBelow\.\d+$:", $tmp_file)) {
~                    $a_dirListTypeBelow[] = $tmp_file;
~                }
~            }
~        }

~        closedir($tmp_handle);

~        // determine maximum number of directories allowed for this
backup
~        if (isset($a_backupHosts[$s_configName]['limits']) &&

isset($a_backupHosts[$s_configName]['limits'][$s_backupType])) {
~            $tmp_max =
$a_backupHosts[$s_configName]['limits'][$s_backupType];
~        }
~        else {
~            $tmp_max = $a_backupTypeSettings[$s_backupType]['limit'];
~        }

~        // If the max is 1 (backing up only once a day) then don't
rotate anything
~        if ($tmp_max > 1) {
~            // go through directories in reverse order and rotate them
~            natsort($a_dirList);
~            $a_dirList = array_reverse($a_dirList);
~            foreach ($a_dirList as $tmp_dir) {
~                $tmp_key = preg_replace(':.*\.(\d+)$:', '\\1', $tmp_dir);
~                // rotate off any of the old backups
~                if (($tmp_key + 1) >= $tmp_max) {
~                    if ($b_debug || $b_dryRun) {
~                        $tmp_msg = 'Rotating off old backup: ' .
$s_fullPath . $tmp_dir;
~                        if ($b_dryRun) {
~                            $tmp_msg = str_replace('Rotating', 'Would
rotate', $tmp_msg);
~                        }

~                        writeln($tmp_msg);
~                    }

~                    if (!$b_dryRun) {
~                        // :NOTE: can use System::rm when it supports
symlinks in directories
~                        exec("$s_rm -rf $s_fullPath$tmp_dir");
~                    }
~                }
~                // otherwise rotate the directory up
~                else {
~                    $tmp_key++;
~                    $tmp_newDir = preg_replace(':\.\d+$:',
".$tmp_key", $tmp_dir);
~                    if ($b_debug || $b_dryRun) {
~                        $tmp_msg = "Rotating backup directory from
$s_fullPath$tmp_dir to $s_fullPath$tmp_newDir";
~                        if ($b_dryRun) {
~                            $tmp_msg = str_replace('Rotating', 'Would
rotate', $tmp_msg);
~                        }

~                        writeln($tmp_msg);
~                    }

~                    if (!$b_dryRun) {
~                        rename($s_fullPath . $tmp_dir, $s_fullPath .
$tmp_newDir);
~                    }
~                }
~            }
~        }
~    }

~    // }}}
~    // {{{ perform rsync

~    // this dir shouldn't be here, but check anyhow to make sure we
~    // don't get recursive directories
~    if (!$b_dryRun && $tmp_max > 1 &&
is_dir("$s_fullPath$s_backupType.0") && !$b_dryRun) {
~        exec("$s_rm -rf $s_fullPath$s_backupType.0");
~    }

~    // we only rsync if the type is hourly or on the type is the one
they want rsynced
~    if ($s_backupType == 'hourly' || $s_backupType == $s_rsyncType) {
~        // make hard-link-only copy of latest directory
~        if (is_dir("$s_fullPath$s_backupType.1") && !$b_dryRun) {
~            if
(!isset($a_backupHosts[$s_configName]['use_hard_links']) ||
~                $a_backupHosts[$s_configName]['use_hard_links']) {
~               exec("$s_cp -al $s_fullPath$s_backupType.1
$s_fullPath$s_backupType.0");
~            } else {
~                // When not using the hard-link method of backups we
~                // want the full backup to be in .0 and any files that
have
~                // been changed to be placed in .1
~                rename("$s_fullPath$s_backupType.1",
"$s_fullPath$s_backupType.0");
~                mkdir("$s_fullPath$s_backupType.1", $s_defaultDirPerms);
~            }
~        }

~        // directory needs to exist for dry run
~        if ($b_dryRun && !is_dir("$s_fullPath$s_backupType.0")) {
~            $b_cleanupDryRun = true;
~            mkdir("$s_fullPath$s_backupType.0");
~        }

~        $tmp_excludes = preg_split('/([^\\\\])\s/',
$a_backupHosts[$s_configName]['excludes'], -1, PREG_SPLIT_NO_EMPTY |
PREG_SPLIT_DELIM_CAPTURE);
~        $exclude_args = '';
~        for ($i = 0; $i < count($tmp_excludes); $i++) {
~            $s_exclude = str_replace('\\', '', $tmp_excludes[$i]);
~            if (isset($tmp_excludes[$i + 1]) &&
strlen($tmp_excludes[$i + 1]) == 1) {
~                $s_exclude .= $tmp_excludes[++$i];
~            }

~            // check if we want to include rather than exclude
~            if (substr($s_exclude, 0, 1) == '+') {
~                $s_exclude = '+ ' . substr($s_exclude, 1);
~            }
~            // in this case we want a literal '+'
~            elseif (substr($s_exclude, 0, 2) == '\+') {
~                $s_exclude = '+' . substr($s_exclude, 2);
~            }

~            $exclude_args = $exclude_args . " --exclude=\"$s_exclude\"";
~        }

~        $tmp_rsyncArgs = $s_rsyncArgs;
~        if ($b_dryRun) {
~            $tmp_rsyncArgs .= ' -n';
~        }

~        // See if ribs is running on the localhost as the current user,
~        // in which case we don't use ssh, making it possible to run ribs
~        // without a password.
~        if ($a_backupHosts[$s_configName]['ip'] == 'localhost' &&
~            $a_backupHosts[$s_configName]['ssh_user'] ==
get_current_user()) {
~            $tmp_rsyncArgs .= ' ' .
$a_backupHosts[$s_configName]['directories'];
~        }
~        else {
~            $tmp_dirs = preg_replace('/\\\\\s/', '\\\\\\\\ ',
$a_backupHosts[$s_configName]['directories']);
~            $s_port = isset($a_backupHosts[$s_configName]['ssh_port']) ?
~                $a_backupHosts[$s_configName]['ssh_port'] : 22;
~            $tmp_rsyncArgs .= " -e \"ssh -p $s_port\" ";
~            $tmp_rsyncArgs .=
"\"{$a_backupHosts[$s_configName]['ssh_user']}@";
~            $tmp_rsyncArgs .= "{$a_backupHosts[$s_configName]['ip']}:";
~            $tmp_rsyncArgs .= $tmp_dirs . '"';
~        }

~        if (isset($a_backupHosts[$s_configName]['use_hard_links']) &&
~            !$a_backupHosts[$s_configName]['use_hard_links']) {
~            $tmp_rsyncArgs .= " --backup
- --backup-dir=\"$s_fullPath$s_backupType.1\"";
~        }

~        // now we do the actual rsync from the system into the latest
snapshot (notice that
~        // rsync behaves like cp --remove-destination by default, so
the destination
~        // is unlinked first.  If it were not so, this would copy over
the other snapshot(s) too!
~        $s_args = "$exclude_args $tmp_rsyncArgs " .
~                  "$s_fullPath$s_backupType.0 2>&1";
~        $s_cmd = $s_rsync . ' ' . $s_args;
~        if ($b_debug) {
~            writeln('Executing rsync command: ' . $s_cmd);
~        }

~        $s_log .= `$s_cmd`;

~        if ($b_cleanupDryRun) {
~            rmdir("$s_fullPath$s_backupType.0");
~        }
~    }
~    // else we just rotate the most recent directory of the type below
up to the current
~    // type.  This saves space so that there is only one "real"
directory with files.  The
~    // other directories are hardlinked directories.
~    else {
~        natsort($a_dirListTypeBelow);
~        $tmp_lastDir = end($a_dirListTypeBelow);
~        if ($tmp_lastDir != '' && is_dir($s_fullPath . $tmp_lastDir)) {
~            if ($b_debug || $b_dryRun) {
~                $tmp_msg = "Performing backup by copying
$s_fullPath$tmp_lastDir to $s_fullPath$s_backupType.0";
~                if ($b_dryRun) {
~                    $tmp_msg = str_replace('Performing', 'Would
perform', $tmp_msg);
~                }

~                writeln($tmp_msg);
~            }
~            if (!$b_dryRun) {
~                exec("$s_cp -al $s_fullPath$tmp_lastDir
$s_fullPath$s_backupType.0");
~            }

~            $s_log .= writeln("Performed $s_backupType backup by
copying the last $s_typeBelow up to $s_backupType", true);
~        }
~        else {
~            $tmp_msg = writeln("WARNING: Could not perform
$s_backupType backup because the $s_typeBelow type does not exist.",
true, true);
~            $tmp_msg .= writeln("You must run the $s_typeBelow backup
first or change what type of backup performs the actual");
~            $tmp_msg .= writeln("rsync (\$s_rsyncType is the config
variable to check).", true);
~            $s_log .= $tmp_msg;
~            $b_error = true;
~            continue;
~        }
~    }

~    // }}}
~    // {{{ check for rsync problems

~    // see if there was an error
~    if (stristr($s_log, 'rsync error')) {
~        $tmp_msg = writeln("WARNING: there seems to have been an rsync
error.\nCheck the logs for more information.", true, true);
~        $s_log .= $tmp_msg;
~        $b_error = true;
~        if (isset($a_backupHosts[$s_configName]['post_error_command'])) {
~           $s_cmd =
replaceVars($a_backupHosts[$s_configName]['post_error_command']);
~           $s_log .= "\nPost error command: $s_cmd\n";
~           $s_log .= `($s_cmd) 2>&1`;
~        }
~    }
~    // let the snapshot reflect the current date
~    elseif (is_dir("$s_fullPath$s_backupType.0") && !$b_dryRun) {
~        touch("$s_fullPath$s_backupType.0");
~        if (($s_backupType == 'hourly' || $s_backupType ==
$s_rsyncType) &&
~            !is_link($s_fullPath . 'current')) {
~            symlink("$s_fullPath$s_backupType.0", $s_fullPath .
'current');
~        }

~        if (isset($a_backupHosts[$s_configName]['post_command'])) {
~           $s_cmd =
replaceVars($a_backupHosts[$s_configName]['post_command']);
~           $s_log .= "\nPost command: $s_cmd\n";
~           $s_log .= `($s_cmd) 2>&1`;
~        }

~        // give some extra helpful info
~        if ($s_extraCommands != '') {
~            $s_log .= "\nAdditional information:\n";
~            $s_cmd = replaceVars($s_extraCommands);
~            $s_log .= `($s_cmd) 2>&1`;
~        }
~    }
~    elseif (!$b_dryRun) {
~        $tmp_msg = writeln("WARNING: it looks like the backup for
config '$s_configName' was not successful.", true, true);
~        $tmp_msg .= writeln("The snapshot directory that is supposed
to be created was not created:", true);
~        $tmp_msg .= writeln("$s_fullPath$s_backupType.0", true);
~        $tmp_msg .= writeln("Check the logs for more information.",
true);
~        $s_log .= $tmp_msg;
~        $b_error = true;
~    }

~    // }}}
}

// send off the logs
if ($b_error) {
~    mailAndLog($s_log);
}
else {
~    mailAndLog();
}

exit;

// }}}
// {{{ mailAndLog()

/**
~ * Performs the necessary mailing and logging and exiting of the program
~ *
~ * @param string $in_errorMessage (optional) The error message
~ *
~ * @return void
~ */
function mailAndLog($in_errorMessage = false)
{
~    global $a_backupHosts, $a_backupTypeSettings, $a_configNames,
~           $s_backupType, $s_log, $s_email, $s_logFile,
$s_restartBackupLog,
~           $b_silentOnError, $b_emailOnError, $b_debug, $b_dryRun;

~    $s_log =
"-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n$s_log";
~    $s_log .=
"\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
~    // log the message to file
~    if ($a_backupTypeSettings[$s_backupType]['log'] && $s_logFile !=
'') {
~        // determine file mode
~        $tmp_fileMode = $s_backupType == $s_restartBackupLog ? 'w' : 'a';
~        $fp = @fopen($s_logFile, $tmp_fileMode);
~        if ($fp) {
~            fwrite($fp, $s_log);
~            fclose($fp);
~            if ($b_debug) {
~                writeln('Successfully wrote to logfile: ' . $s_logFile);
~            }
~        }
~        else {
~            $in_errorMessage .= writeln("WARNING: Could not write to
log file: $s_logFile", true, true);
~            $s_log .= $in_errorMessage;
~        }
~    }

~    // e-mail the log if they want it or it's an error
~    if (isset($a_backupTypeSettings[$s_backupType]) &&
~        ($a_backupTypeSettings[$s_backupType]['email'] ||
~        ($b_emailOnError && $in_errorMessage !== false)) &&
~        $s_email != '') {
~        $tmp_error = $in_errorMessage !== false ? ' (WARNING) ' : '';
~        $s_subject = "[ RIBS $tmp_error $s_backupType backups for " .
implode(',', $a_configNames) . " ]";
~        if ($b_debug) {
~            writeln('Emailing backup log to: ' . $s_email);
~        }

~        mail($s_email, $s_subject, $s_log);
~    }

~    if ($in_errorMessage !== false && (!$b_silentOnError || $b_debug)) {
~        echo $in_errorMessage;
~    }

~    if ($b_debug || $b_dryRun) {
~        $tmp_msg = 'Dumping log for this backup:';
~        if ($b_dryRun) {
~            $tmp_msg = str_replace('Dumping log', 'What the log would
be', $tmp_msg);
~        }

~        writeln($tmp_msg);
~        writeln($s_log);
~    }
}

// }}}
// {{{ reInitBackups()

/**
~ * Re-initialize a backup by removing the directory associated with it.
~ *
~ * @access public
~ * @return void
~ */
function reInitBackups()
{
~    global $a_backupHosts, $a_configNames, $s_log, $s_destinationDir,
$s_rm;

~    writeln('Beginning Re-initialization.');
~    foreach ($a_configNames as $s_configName) {
~        // make sure that we are using a valid configuration
~        if (!isset($a_backupHosts[$s_configName])) {
~            writeln("WARNING: The configuation: '$s_configName' is not
valid, skipping it.\n", false, true);
~            continue;
~        }

~        $answer = '';
~        $s_fullPath = $s_destinationDir . $s_configName . '/';
~        writeln ("Are you sure you want to re-initialize
$s_configName?", false, true);
~        echo 'This will completely remove it\'s backup directory and
start from scratch. [y/n]: ';
~        $answer = trim(readline());
~        if ($answer == 'y' || $answer == 'Y') {
~            exec("$s_rm -rf $s_fullPath");
~            writeln('Successfully re-initialized ' . $s_configName .
'.');
~            continue;
~        }
~        elseif ($answer == 'n' || $answer == 'N') {
~            writeln('Skipping re-initialization of ' . $s_configName .
'.');
~            continue;
~        }
~        else {
~            writeln('Aborting re-initialization.');
~            exit;
~        }
~    }

~    writeln('Re-initialization complete.');
}


// }}}
// {{{ showUsage()

/**
~ * Shows the usage message
~ *
~ * @param string $in_file The currently running file name
~ *
~ * @access public
~ * @return void
~ */
function showUsage($in_file)
{
~    $s_usage =
'RIBS version ' . RIBS_VERSION . '
Copyright (c) 2002-2003 Jason Rust <jrust at rustyparts.com>

RIBS (Rsync Incremental Backup System) is a command line PHP script
which will perform incremental backups to a variable number of hosts
(and directories on those hosts).

Usage: ' . $in_file . ' [options] CONFIG_NAME BACKUP_TYPE

Variables:
~    CONFIG_NAME A comma separated list of configurations to run
~                or ALL if you want to run all configurations
~    BACKUP_TYPE The type of backup to run.  Can be hourly, daily,
~                weekly, or monthly

Options:
~    -h, --help      Show this help message
~    -d, --debug     Show debug information
~    -t, --test      Do a dry run.  Does not transfer any files or move
~                    any directories, just shows what would happen.
~    -r, --reinit    Re-initialize the backup.  Removes any existing
~                    backups for the specified CONFIG_NAME so that the
~                    backups are started from scratch.  WARNING: Any
~                    existing data will be erased.

Report any bugs to: jrust at rustyparts.com';
~    writeln($s_usage);
}

// }}}
// {{{ writeln()

/**
~ * Writes a line to standard output
~ *
~ * @param string $in_message The message to write.
~ * @param bool $in_return (optional) Return the message instead of
echoing it?
~ * @param bool $in_pre (optional) Prepend a newline to the string?
~ *
~ * @access public
~ * @return void
~ */
function writeln($in_message, $in_return = false, $in_pre = false)
{
~    if ($in_pre) {
~        $in_message = "\n" . $in_message;
~    }

~    $in_message = $in_message . "\n";
~    if ($in_return) {
~        return $in_message;
~    }
~    else {
~        echo $in_message;
~    }
}

// }}}
// {{{ replaceVars()

/**
~ * Replaces the placeholders %fullPath% and %backupType% with the actual
~ * value.
~ *
~ * @var string $in_cmd The command that has the placeholders
~ *
~ * @return string The command with placeholders replaced
~ */
function replaceVars($in_cmd)
{
~   $in_cmd = str_replace('%fullPath%', $GLOBALS['s_fullPath'], $in_cmd);
~   $in_cmd = str_replace('%backupType%', $GLOBALS['s_backupType'],
$in_cmd);
~   return $in_cmd;
}

// }}}
?>












-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (MingW32)
 
iD8DBQFCIu82evoM7FFh31sRAnXwAJwPhI6CsNHjkvH+f04rMomz+XvsFgCfdVKs
K6nbLJTDXVkfbsZyBNx6UVc=
=2ZGS
-----END PGP SIGNATURE-----

-------------- next part --------------
#!/usr/bin/php -q
<?php
//** $Id: ribs 1625 2004-11-24 20:55:48Z jrust $ */
// {{{ version

define('RIBS_VERSION', '2.3');

// }}}
// {{{ description

/**
 * RIBS (Rsync Incremental Backup System) by Jason Rust <jrust at rustyaprts.com>
 * Copyright (c) 2002-2003 Jason Rust <jrust at rustyparts.com>
 * The latest version of this program can be found at 
 * http://rustyparts.com/scripts.php
 *
 * License: 
 *
 * This source file is subject to the GNU Public License (GPL),
 * that is bundled with this package in the file LICENSE, and is
 * available at through the world-wide-web at
 * http://www.fsf.org/copyleft/lesser.html
 * If you did not receive a copy of the LGPL and are unable to
 * obtain it through the world-wide-web, you can get it by writing the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See http://www.gnu.org/copyleft/gpl.html for a copy of the license.  
 *
 * Description:
 *
 * RIBS is an incremental backup system written in PHP which utilizes
 * some common *nix programs (specifically rsync, ssh and cp).
 *
 * Requirements:
 *
 * rsync - http://samba.anu.edu.au/rsync/
 * cp & rm - http://www.gnu.org/software/fileutils/fileutils.html
 * PHP - http://www.php.net/
 * basic PEAR libraries (as of version 1.1) - http://pear.php.net/
 *
 * Use:
 *
 * See the README that comes with this package.
 */

// }}}
// {{{ user settings

/****** Configuration Section ******/

/**
 * This is the place on the server where the backups will end up WITH trailing slash
 * @type string
 */
$s_destinationDir = dirname(__FILE__) . '/backups/';

/** 
 * An array of the different servers to back up, and what directories on them to back up
 * The structure is as follows:
 * 'backup_name' => array(
 *      'enabled'       => 'this config enabled? true or false',
 *      'ip'            => 'server ip address/FQDN',
 *      'ssh_user'      => 'the ssh user to use for rsync',
 *      'ssh_port'      => 'the ssh port (optional, defaults to 22)',
 *      'directories'   => 'backup directories separated by a space WITHOUT a trailing slash (i.e. /home /root)',
 *                          If a directory has a space in it, escape it with a \
 *      'excludes'      => 'the directories to exclude separated by a space.  
 *                          See the README for more information on exclude patterns.'
 *      'limits'        => 'an array of directory limits that will allow you to override the limits you set in
 *                          $a_backupTypeSettings.  Completely optional. e.g. array('hourly' => 2) if you back up
 *                          hourly twice a day.'
 *      'use_hard_links'=> 'Whether to do incremental backups using hard links or real files.  
 *                          Default option is true.  See "Backup Types" in the README for more info.'
 *      'post_command'  => 'A shell command to run after a successful backup.  Replaces %fullPath% 
 *                          and %backupType% with the real values.'
 *      'post_error_command' => 'Same as post_command, but run when an error occurs.'
 *  ),
 * @type array
 */
$a_backupHosts = array(
    'luke' => array(
        'enabled'       => true,
        'ip'            => 'mychristiannetwork.com',
        'ssh_user'      => 'root',
        'directories'   => '/etc /var/lib/mysql /home',
        'excludes'      => '',
        'limits'        => array('hourly' => 24), // because this is backed up every hour
    ),
    'small_host' => array(
        'enabled'       => false,
        'ip'            => 'small_host.example.com',
        'ssh_user'      => 'backup',
        'ssh_port'      => '9999',
        'directories'   => '/etc',
        'excludes'      => '',
    ),
    'big_host' => array(
        'enabled'       => false,
        'ip'            => '10.52.1.1',
        'ssh_user'      => 'root',
        'directories'   => '/var',
        'excludes'      => 'mp3/', // exclude any mp3 directories
    ),
    // A test example configuration that can be used with the test
    // directory that comes with RIBS.
    'example' => array(
        'enabled'       => false,
        'ip'            => 'localhost',
        'ssh_user'      => get_current_user(),
        'directories'   => dirname(__FILE__) . '/test\ dir',
        'use_hard_links'=> true, 
        // An example of a post_command which will tar up each backup.
        // NOTE: this is only useful if use_hard_links is false
        // 'post_command'  => 'if [ -d %fullPath%%backupType%.1 ]; then tar czv -C %fullPath%%backupType%.1 -f backup.tar.gz .; rm -rf %fullPath%%backupType%.1; mkdir %fullPath%%backupType%.1; mv backup.tar.gz %fullPath%%backupType%.1/; fi',
        'excludes'      => '/test\ dir/my\ mp3 +/test\ dir/include.mp3 *.mp3 *.svn/ junk/', 
                           // exclude the root mp3 directory,
                           // any mp3 files anywhere else (except for include.mp3),
                           // and any .svn/ and junk/ directories
    ),
);

/**
 * Some settings for the four different backup types.  For each type you must specify
 * 'limit' => of directories to keep.  So, if you run every two hours, make hourly 12, 
 * if every hour then 24, etc.  Try to span the entire day.  The below setting runs 8 
 * times a day (every three hours), keeps 7 days, 4 weeks, and 12 months of backups.
 * 'email' => whether to send email or not after completion
 * 'log' => whether to log after completion
 * @type array
 */
$a_backupTypeSettings = array(
    'hourly'    => array('limit' => 8, 'email' => true, 'log' => true),
    'daily'     => array('limit' => 7, 'email' => true, 'log' => true),
    'weekly'    => array('limit' => 4, 'email' => true, 'log' => true),
    'monthly'   => array('limit' => 12, 'email' => true, 'log' => true),
);

/**
 * The email address to send reports to (you can comma separate multiple addresses)
 * @type string
 */
$s_email = 'cyo at mychristiannetwork.com';

/**
 * The log file.
 * @type string
 */
$s_logFile = dirname(__FILE__) . '/ribs.log';

/**
 * When to start the log over.  Note, this does not mean we rotate the log
 * it is just to keep the log file from getting too big.  Can be hourly, daily,
 * weekly, or monthly
 * @type string
 */
$s_restartBackupLog = 'weekly';

/**
 * When to do an actual rsync.  Otherwise, we just copy the oldest directory 
 * of the type below to the current type.  Example: if doing a monthly backup
 * and $s_rsyncType is not 'monthly' then the oldest weekly directory will be
 * copied to monthly.0.  This keeps down the amount of disk space needed.  Usually you
 * will want this to be 'hourly'
 * @type string
 */
$s_rsyncType = 'hourly';

/**
 * Always email if there's an error?  This overrides the specific backup setting
 * to ensure you will get an email if there is a problem.
 * @type bool
 */
$b_emailOnError = true;

/**
 * Die quietly?  If so, then on error we just shut up, email (see above), and quit,
 * otherwise we throw the error.
 * @type bool
 */
$b_silentOnError = false;

/**
 * Default permissions for when we have to create a new directory
 * @type int
 */
$s_defaultDirPerms = 0750;

/**
 * Additional comands to be run at the end of each backup (useful for giving
 * additional info about file sizes, directories, etc.).  Leave empty for nothing.
 * %fullPath% is a placeholder that will be filled in with the full path to the current backup. 
 * @type string
 */
$s_extraCommands = "ls -l %fullPath%; df -h";

/**
 * The extra rsync commands to use 
 * @type string
 */
$s_rsyncArgs = '-arztplv --delete --delete-excluded --stats --timeout=180';

/**
 * The path to the rsync command
 * @type string
 */
$s_rsync = '/usr/bin/rsync'; 

/**
 * The path to rm 
 * @var string
 */
$s_rm = '/bin/rm';

/**
 * The path to cp 
 * @type string
 */
$s_cp = '/bin/cp';

/****** End Config. No need to edit anything else! ******/

// }}}
// {{{ readline()

/**
 * Reads a line from standard input.  Have to put it up here so php loads it in.
 *
 * @access public
 * @return string The string from input
 */
if (!function_exists('readline')) {
    function readline () {
        $fp = fopen('php://stdin', 'r');
        $in = fgets($fp, 4094); // Maximum windows buffer size
        fclose ($fp);
        return $in;
    }
}

// }}}
// {{{ requires

require_once 'Console/Getopt.php';

// }}}
// {{{ grab command line vars

// don't run out of time
set_time_limit(0);

// we want all errors
error_reporting(E_ALL);

$args = Console_Getopt::readPHPArgv();
if (PEAR::isError($args)) {
    die('Fatal Error: ' . $args->getMessage() . "\n");
}

$options = Console_Getopt::getopt($args, 'dhtr', array('debug', 'help', 'test', 'reinit'));
if (PEAR::isError($options)) {
    die($options->getMessage() . "\n");
}

$b_debug = false;
$b_dryRun = false;
$b_cleanupDryRun = false;
$b_reinit = false;
$s_log = '';
$b_error = false;

foreach ($options[0] as $option) {
    switch ($option[0]) {
        case 'h':
        case '--help':
            showUsage($args[0]);
            exit;
            break;
        case 'd':
        case '--debug':
            $b_debug = true;
            break;
        case 't':
        case '--test':
            $b_dryRun = true;
            break;
        case 'r':
        case '--reinit':
            $b_reinit = true;
            break;
    }
}

if (empty($options[1][0])) {
    writeln('Error: Configuation not specified' . "\n");
    showUsage($args[0]);
    exit;
}

// grab the config names from the command line
$a_configNames = explode(',', $options[1][0]);

// ALL is a special keyword to run all backups
if ($a_configNames[0] == 'ALL') {
    $a_configNames = array_keys($a_backupHosts);
}

if ($b_reinit) {
	reInitBackups();
	exit;
}

if (empty($options[1][1])) {
    writeln('Error: Backup type not specified' . "\n");
    showUsage($args[0]);
    exit;
}

// grab the backup type from the command line
$s_backupType = $options[1][1];

if ($b_debug) {
    array_shift($args);
    writeln('Debug mode is ON.');
    writeln('Arguments received: ' . implode(' ', $args));
    writeln('Configuration names: ' . implode(',', $a_configNames));
    writeln('Backup type: ' . $s_backupType);
}

if ($b_dryRun) {
    writeln('Dry run mode is ON.');
}

// }}}
// {{{ set up vars and dirs

// start log
$s_log .= date('F j, Y, g:i a') . ": $s_backupType backups for " . implode(',', $a_configNames) . "\n";

// make sure it's a valid type
if ($s_backupType != 'hourly' &&
    $s_backupType != 'daily' &&
    $s_backupType != 'weekly' &&
    $s_backupType != 'monthly') {
    writeln("ERROR: The backup type: '$s_backupType' is not valid (valid types are hourly, daily, weekly, and monthly).");
    exit;
}

// check binaries 
if (!is_executable($s_rsync)) {
    writeln("WARNING: The rsync program: '$s_rsync' is not valid.");
    exit;
}

if (!is_executable($s_rm)) {
    writeln("WARNING: The rm program: '$s_rm' is not valid.");
    exit;
}

if (!is_executable($s_cp)) {
    writeln("WARNING: The cp program: '$s_cp' is not valid.");
    exit;
}

if (!is_dir($s_destinationDir)) {
    mkdir($s_destinationDir, $s_defaultDirPerms);
    $s_log .= writeln("Created destination directory: $s_destinationDir", true);
}

// determine the type below the current type
if ($s_backupType == 'daily') {
    $s_typeBelow = 'hourly';
}
elseif ($s_backupType == 'weekly') {
    $s_typeBelow = 'daily';
}
elseif ($s_backupType == 'monthly') {
    $s_typeBelow = 'weekly';
}
else {
    $s_typeBelow = 'monthly';
}

// }}}
// {{{ loop through backup configurations

foreach ($a_configNames as $s_configName) {
    // make sure that we are using a valid configuration
    if (!isset($a_backupHosts[$s_configName])) {
        $tmp_msg = writeln("WARNING: The configuation: '$s_configName' is not valid.\n", true, true);
        $s_log .= $tmp_msg;
        $b_error = true;
        continue;
    }
    else {
        $s_log .= writeln("-= Beginning backups for $s_configName\n", true, true);
    }

    // make sure it's enabled
    if (!$a_backupHosts[$s_configName]['enabled']) {
        $s_log .= writeln("$s_configName is disabled.  Skipping backup.", true);
        continue;
    }

    $s_fullPath = $s_destinationDir . $s_configName . '/';

    if (!is_dir($s_fullPath)) {
        mkdir($s_fullPath, $s_defaultDirPerms);
        $s_log .= writeln("Created directory for $s_configName: $s_fullPath", true);
    }

    // {{{ rotate the current list of backups if we can 

    if ($tmp_handle = opendir($s_fullPath)) {
        $a_dirList = array();
        $a_dirListTypeBelow = array();
        while (false !== ($tmp_file = readdir($tmp_handle))) { 
            if (is_dir($s_fullPath . $tmp_file)) {
                if (preg_match(":$s_backupType\.\d+$:", $tmp_file)) {
                    $a_dirList[] = $tmp_file;
                }

                // for later we need the directories of the type below
                if (preg_match(":$s_typeBelow\.\d+$:", $tmp_file)) {
                    $a_dirListTypeBelow[] = $tmp_file;
                }
            }
        }

        closedir($tmp_handle);

        // determine maximum number of directories allowed for this backup
        if (isset($a_backupHosts[$s_configName]['limits']) &&
            isset($a_backupHosts[$s_configName]['limits'][$s_backupType])) {
            $tmp_max = $a_backupHosts[$s_configName]['limits'][$s_backupType];
        }
        else {
            $tmp_max = $a_backupTypeSettings[$s_backupType]['limit'];
        }

        // If the max is 1 (backing up only once a day) then don't rotate anything
        if ($tmp_max > 1) {
            // go through directories in reverse order and rotate them
            natsort($a_dirList);
            $a_dirList = array_reverse($a_dirList);
            foreach ($a_dirList as $tmp_dir) {
                $tmp_key = preg_replace(':.*\.(\d+)$:', '\\1', $tmp_dir);
                // rotate off any of the old backups
                if (($tmp_key + 1) >= $tmp_max) {
                    if ($b_debug || $b_dryRun) {
                        $tmp_msg = 'Rotating off old backup: ' . $s_fullPath . $tmp_dir;
                        if ($b_dryRun) {
                            $tmp_msg = str_replace('Rotating', 'Would rotate', $tmp_msg);
                        }

                        writeln($tmp_msg);
                    }

                    if (!$b_dryRun) {
                        // :NOTE: can use System::rm when it supports symlinks in directories
                        exec("$s_rm -rf $s_fullPath$tmp_dir");
                    }
                }
                // otherwise rotate the directory up
                else {
                    $tmp_key++;
                    $tmp_newDir = preg_replace(':\.\d+$:', ".$tmp_key", $tmp_dir);
                    if ($b_debug || $b_dryRun) {
                        $tmp_msg = "Rotating backup directory from $s_fullPath$tmp_dir to $s_fullPath$tmp_newDir";
                        if ($b_dryRun) {
                            $tmp_msg = str_replace('Rotating', 'Would rotate', $tmp_msg);
                        }

                        writeln($tmp_msg);
                    }

                    if (!$b_dryRun) {
                        rename($s_fullPath . $tmp_dir, $s_fullPath . $tmp_newDir);
                    }
                }
            }
        }
    }

    // }}}
    // {{{ perform rsync

    // this dir shouldn't be here, but check anyhow to make sure we
    // don't get recursive directories
    if (!$b_dryRun && $tmp_max > 1 && is_dir("$s_fullPath$s_backupType.0") && !$b_dryRun) {
        exec("$s_rm -rf $s_fullPath$s_backupType.0");
    }

    // we only rsync if the type is hourly or on the type is the one they want rsynced 
    if ($s_backupType == 'hourly' || $s_backupType == $s_rsyncType) {
        // make hard-link-only copy of latest directory
        if (is_dir("$s_fullPath$s_backupType.1") && !$b_dryRun) {
            if (!isset($a_backupHosts[$s_configName]['use_hard_links']) ||
                $a_backupHosts[$s_configName]['use_hard_links']) {
               exec("$s_cp -al $s_fullPath$s_backupType.1 $s_fullPath$s_backupType.0");
            } else {
                // When not using the hard-link method of backups we
                // want the full backup to be in .0 and any files that have
                // been changed to be placed in .1
                rename("$s_fullPath$s_backupType.1", "$s_fullPath$s_backupType.0");
                mkdir("$s_fullPath$s_backupType.1", $s_defaultDirPerms);
            }
        }

        // directory needs to exist for dry run
        if ($b_dryRun && !is_dir("$s_fullPath$s_backupType.0")) {
            $b_cleanupDryRun = true;
            mkdir("$s_fullPath$s_backupType.0");
        }

        $tmp_excludes = preg_split('/([^\\\\])\s/', $a_backupHosts[$s_configName]['excludes'], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
        $exclude_args = '';
        for ($i = 0; $i < count($tmp_excludes); $i++) {
            $s_exclude = str_replace('\\', '', $tmp_excludes[$i]);
            if (isset($tmp_excludes[$i + 1]) && strlen($tmp_excludes[$i + 1]) == 1) {
                $s_exclude .= $tmp_excludes[++$i];
            }

            // check if we want to include rather than exclude
            if (substr($s_exclude, 0, 1) == '+') {
                $s_exclude = '+ ' . substr($s_exclude, 1);
            }
            // in this case we want a literal '+'
            elseif (substr($s_exclude, 0, 2) == '\+') {
                $s_exclude = '+' . substr($s_exclude, 2);
            }

            $exclude_args = $exclude_args . " --exclude=\"$s_exclude\"";
        }

        $tmp_rsyncArgs = $s_rsyncArgs;
        if ($b_dryRun) {
            $tmp_rsyncArgs .= ' -n';
        }

        // See if ribs is running on the localhost as the current user,
        // in which case we don't use ssh, making it possible to run ribs
        // without a password.
        if ($a_backupHosts[$s_configName]['ip'] == 'localhost' && 
            $a_backupHosts[$s_configName]['ssh_user'] == get_current_user()) {
            $tmp_rsyncArgs .= ' ' . $a_backupHosts[$s_configName]['directories'];
        }
        else {
            $tmp_dirs = preg_replace('/\\\\\s/', '\\\\\\\\ ', $a_backupHosts[$s_configName]['directories']);
            $s_port = isset($a_backupHosts[$s_configName]['ssh_port']) ? 
                $a_backupHosts[$s_configName]['ssh_port'] : 22;
            $tmp_rsyncArgs .= " -e \"ssh -p $s_port\" ";
            $tmp_rsyncArgs .= "\"{$a_backupHosts[$s_configName]['ssh_user']}@";
            $tmp_rsyncArgs .= "{$a_backupHosts[$s_configName]['ip']}:";
            $tmp_rsyncArgs .= $tmp_dirs . '"';
        }

        if (isset($a_backupHosts[$s_configName]['use_hard_links']) &&
            !$a_backupHosts[$s_configName]['use_hard_links']) {
            $tmp_rsyncArgs .= " --backup --backup-dir=\"$s_fullPath$s_backupType.1\"";
        }
 
        // now we do the actual rsync from the system into the latest snapshot (notice that
        // rsync behaves like cp --remove-destination by default, so the destination
        // is unlinked first.  If it were not so, this would copy over the other snapshot(s) too!
        $s_args = "$exclude_args $tmp_rsyncArgs " . 
                  "$s_fullPath$s_backupType.0 2>&1";
        $s_cmd = $s_rsync . ' ' . $s_args;
        if ($b_debug) {
            writeln('Executing rsync command: ' . $s_cmd);
        }

        $s_log .= `$s_cmd`; 

        if ($b_cleanupDryRun) {
            rmdir("$s_fullPath$s_backupType.0");
        }
    }
    // else we just rotate the most recent directory of the type below up to the current
    // type.  This saves space so that there is only one "real" directory with files.  The
    // other directories are hardlinked directories.
    else {
        natsort($a_dirListTypeBelow);
        $tmp_lastDir = end($a_dirListTypeBelow);
        if ($tmp_lastDir != '' && is_dir($s_fullPath . $tmp_lastDir)) {
            if ($b_debug || $b_dryRun) {
                $tmp_msg = "Performing backup by copying $s_fullPath$tmp_lastDir to $s_fullPath$s_backupType.0";
                if ($b_dryRun) {
                    $tmp_msg = str_replace('Performing', 'Would perform', $tmp_msg);
                }

                writeln($tmp_msg);
            }
            if (!$b_dryRun) {
                exec("$s_cp -al $s_fullPath$tmp_lastDir $s_fullPath$s_backupType.0");
            }

            $s_log .= writeln("Performed $s_backupType backup by copying the last $s_typeBelow up to $s_backupType", true);
        }
        else {
            $tmp_msg = writeln("WARNING: Could not perform $s_backupType backup because the $s_typeBelow type does not exist.", true, true);
            $tmp_msg .= writeln("You must run the $s_typeBelow backup first or change what type of backup performs the actual");
            $tmp_msg .= writeln("rsync (\$s_rsyncType is the config variable to check).", true);
            $s_log .= $tmp_msg;
            $b_error = true;
            continue;
        }
    }

    // }}}
    // {{{ check for rsync problems

    // see if there was an error
    if (stristr($s_log, 'rsync error')) {
        $tmp_msg = writeln("WARNING: there seems to have been an rsync error.\nCheck the logs for more information.", true, true);
        $s_log .= $tmp_msg;
        $b_error = true;
        if (isset($a_backupHosts[$s_configName]['post_error_command'])) {
           $s_cmd = replaceVars($a_backupHosts[$s_configName]['post_error_command']);
           $s_log .= "\nPost error command: $s_cmd\n";
           $s_log .= `($s_cmd) 2>&1`;
        }
    }
    // let the snapshot reflect the current date
    elseif (is_dir("$s_fullPath$s_backupType.0") && !$b_dryRun) {
        touch("$s_fullPath$s_backupType.0");
        if (($s_backupType == 'hourly' || $s_backupType == $s_rsyncType) &&
            !is_link($s_fullPath . 'current')) {
            symlink("$s_fullPath$s_backupType.0", $s_fullPath . 'current');
        }

        if (isset($a_backupHosts[$s_configName]['post_command'])) {
           $s_cmd = replaceVars($a_backupHosts[$s_configName]['post_command']);
           $s_log .= "\nPost command: $s_cmd\n";
           $s_log .= `($s_cmd) 2>&1`;
        }

        // give some extra helpful info 
        if ($s_extraCommands != '') {
            $s_log .= "\nAdditional information:\n";
            $s_cmd = replaceVars($s_extraCommands); 
            $s_log .= `($s_cmd) 2>&1`;
        }
    }
    elseif (!$b_dryRun) {
        $tmp_msg = writeln("WARNING: it looks like the backup for config '$s_configName' was not successful.", true, true);
        $tmp_msg .= writeln("The snapshot directory that is supposed to be created was not created:", true);
        $tmp_msg .= writeln("$s_fullPath$s_backupType.0", true);
        $tmp_msg .= writeln("Check the logs for more information.", true);
        $s_log .= $tmp_msg;
        $b_error = true;
    }

    // }}}
}

// send off the logs
if ($b_error) {
	mailAndLog($s_log);
}
else {
	mailAndLog();
}

exit;

// }}}
// {{{ mailAndLog()

/**
 * Performs the necessary mailing and logging and exiting of the program 
 *
 * @param string $in_errorMessage (optional) The error message
 *
 * @return void
 */
function mailAndLog($in_errorMessage = false)
{
    global $a_backupHosts, $a_backupTypeSettings, $a_configNames, 
           $s_backupType, $s_log, $s_email, $s_logFile, $s_restartBackupLog, 
           $b_silentOnError, $b_emailOnError, $b_debug, $b_dryRun;

    $s_log = "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n$s_log";
    $s_log .= "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
    // log the message to file
    if ($a_backupTypeSettings[$s_backupType]['log'] && $s_logFile != '') {
        // determine file mode
        $tmp_fileMode = $s_backupType == $s_restartBackupLog ? 'w' : 'a';
        $fp = @fopen($s_logFile, $tmp_fileMode);
        if ($fp) {
            fwrite($fp, $s_log);
            fclose($fp);
            if ($b_debug) {
                writeln('Successfully wrote to logfile: ' . $s_logFile);
            }
        }
        else {
            $in_errorMessage .= writeln("WARNING: Could not write to log file: $s_logFile", true, true);
            $s_log .= $in_errorMessage;
        }
    }

    // e-mail the log if they want it or it's an error
    if (isset($a_backupTypeSettings[$s_backupType]) &&
        ($a_backupTypeSettings[$s_backupType]['email'] || 
        ($b_emailOnError && $in_errorMessage !== false)) && 
        $s_email != '') {
        $tmp_error = $in_errorMessage !== false ? ' (WARNING) ' : '';
        $s_subject = "[ RIBS $tmp_error $s_backupType backups for " . implode(',', $a_configNames) . " ]";
        if ($b_debug) {
            writeln('Emailing backup log to: ' . $s_email);
        }

        mail($s_email, $s_subject, $s_log);
    }

    if ($in_errorMessage !== false && (!$b_silentOnError || $b_debug)) {
        echo $in_errorMessage;
    }

    if ($b_debug || $b_dryRun) {
        $tmp_msg = 'Dumping log for this backup:';
        if ($b_dryRun) {
            $tmp_msg = str_replace('Dumping log', 'What the log would be', $tmp_msg);
        }

        writeln($tmp_msg);
        writeln($s_log);
    }
}

// }}}
// {{{ reInitBackups()

/**
 * Re-initialize a backup by removing the directory associated with it.
 *
 * @access public
 * @return void
 */
function reInitBackups() 
{
	global $a_backupHosts, $a_configNames, $s_log, $s_destinationDir, $s_rm;

	writeln('Beginning Re-initialization.');
	foreach ($a_configNames as $s_configName) {
        // make sure that we are using a valid configuration
        if (!isset($a_backupHosts[$s_configName])) {
            writeln("WARNING: The configuation: '$s_configName' is not valid, skipping it.\n", false, true);
            continue;
        }

		$answer = '';
        $s_fullPath = $s_destinationDir . $s_configName . '/';
		writeln ("Are you sure you want to re-initialize $s_configName?", false, true);
        echo 'This will completely remove it\'s backup directory and start from scratch. [y/n]: ';
		$answer = trim(readline());
		if ($answer == 'y' || $answer == 'Y') {
            exec("$s_rm -rf $s_fullPath");
            writeln('Successfully re-initialized ' . $s_configName . '.');
            continue;
		}
		elseif ($answer == 'n' || $answer == 'N') {
            writeln('Skipping re-initialization of ' . $s_configName . '.');
            continue;
		}
		else {
            writeln('Aborting re-initialization.');
            exit;
		}
	}

    writeln('Re-initialization complete.');
}


// }}}
// {{{ showUsage()

/**
 * Shows the usage message
 *
 * @param string $in_file The currently running file name
 *
 * @access public
 * @return void
 */
function showUsage($in_file)
{
    $s_usage =
'RIBS version ' . RIBS_VERSION . '
Copyright (c) 2002-2003 Jason Rust <jrust at rustyparts.com>

RIBS (Rsync Incremental Backup System) is a command line PHP script 
which will perform incremental backups to a variable number of hosts 
(and directories on those hosts).

Usage: ' . $in_file . ' [options] CONFIG_NAME BACKUP_TYPE

Variables:
    CONFIG_NAME A comma separated list of configurations to run
                or ALL if you want to run all configurations
    BACKUP_TYPE The type of backup to run.  Can be hourly, daily,
                weekly, or monthly

Options:
    -h, --help      Show this help message
    -d, --debug     Show debug information
    -t, --test      Do a dry run.  Does not transfer any files or move
                    any directories, just shows what would happen.
    -r, --reinit    Re-initialize the backup.  Removes any existing
                    backups for the specified CONFIG_NAME so that the
                    backups are started from scratch.  WARNING: Any
                    existing data will be erased.

Report any bugs to: jrust at rustyparts.com';
    writeln($s_usage);
}

// }}}
// {{{ writeln()

/**
 * Writes a line to standard output
 * 
 * @param string $in_message The message to write.
 * @param bool $in_return (optional) Return the message instead of echoing it?
 * @param bool $in_pre (optional) Prepend a newline to the string?
 * 
 * @access public
 * @return void
 */
function writeln($in_message, $in_return = false, $in_pre = false)
{
    if ($in_pre) {
        $in_message = "\n" . $in_message;
    }

    $in_message = $in_message . "\n";
    if ($in_return) {
        return $in_message;
    }
    else {
        echo $in_message;
    }
}

// }}}
// {{{ replaceVars()

/**
 * Replaces the placeholders %fullPath% and %backupType% with the actual
 * value.
 *
 * @var string $in_cmd The command that has the placeholders
 *
 * @return string The command with placeholders replaced
 */
function replaceVars($in_cmd)
{
   $in_cmd = str_replace('%fullPath%', $GLOBALS['s_fullPath'], $in_cmd);
   $in_cmd = str_replace('%backupType%', $GLOBALS['s_backupType'], $in_cmd);
   return $in_cmd;
}

// }}}
?>


More information about the rsync mailing list