Skip to content

Instantly share code, notes, and snippets.

@anyheck
Last active April 5, 2017 21:08
Show Gist options
  • Save anyheck/b5f53e5f12df1e988c02d9cbf369d9f0 to your computer and use it in GitHub Desktop.
Save anyheck/b5f53e5f12df1e988c02d9cbf369d9f0 to your computer and use it in GitHub Desktop.
ZFS Backup
# Number of DAYS to keep the daily backups
keep-daily 7
# Number of DAYS to keep the weekly backups -- note this is NOT the
# number of weeks to keep them
keep-weekly 42
# The three letter abbreviation of the day to consider a weekly backup
weekly-backup-day Sun
default-protocol rsh
# Show a status line when a backup starts, set to yes to enable
show-start-line no
# The path to the find command on remote the remote hosts
# (used for globbing paths)
find-path /usr/bin/find
# The path to rsync on remote the remote hosts
rsync-path /usr/local/bin/rsync
# Whether rsync should use compression or not (valid: 'yes' or 'no')
compression yes
# Each protocol must be defined with the path to it's binary
#protcol-path name=/path/to/program
protocol-path rsh=/usr/bin/rsh
protocol-path ssh=/usr/bin/ssh
#debug yes # comment out this line to disable debugging
#
# Sample host entry:
#
# host hostname.domain {
#
# # The thread-id assigns this host to a specific thread. Each thread runs
# # in parallel, but each host in the thread is backed up serially. This
# # allows long running rsync's to be separate from other hosts and allows
# # utilizing the bandwidth capabilities for separate WAN links at the same
# # time.
# thread-id any-thread-id
#
# # The protocol statement defines the protocol to use for this host The
# # protocol must be defined using the protocol-path global statement. If
# # protocol is omitted, the global default-protocol option is used.
# protocol rsh
#
# # The path statement defines a path to be backed up from the host
# path /path/to/dir
#
# # Using a '*' at the end of the path will have the backup program used
# # the defined (or default) protocol to connect to the host and get a list
# # of directories based on the glob. This is useful for directories that
# # contain very large numbers of files, which causes rsync to use more
# # system resources.
# # The command used to glob is:
# # find /some/other/path/* -type d -maxdepth 0
# #
# # Note that this will not include hidden directories or any files in the
# # specified path
# path /some/other/path/*
#
# # This format for the path statement allows you to relocate a remote path
# # to another location on the backup server. This is useful for backing
# # up ZFS snapshots of a filesystem or other preporatory directories
# # created by a pre-script
# path /remote/path/=/local/path
# path /path/.zfs/snapshot/snapname=/path
#
# # The exclude statement defines an exclude regular expression. The
# # pattern excluded is excluded from ALL paths defined for host.
# # This path is relative to ALL paths specified with the "path"
# # configuration. For example, if you define:
# #
# # path /foo
# # exclude /bar
# #
# # This will recursively exclude /foo/bar from the backup.
# exclude /exclude-dir
#
# }
#############################################################################
# home directories go into it's own thread because it takes forever
host homedirnfs.mydomain.com {
thread-id homedirs
path /home/*
}
#############################################################################
# Remote Site 1
host svn.mydomain.com {
thread-id remote-site-1
path /svnroot-backup=/svnroot
}
host devserver.mydomain.com {
thread-id remote-site-1
path /http
path /usr/local/etc
exclude /static
exclude /backup*
}
host nameserver1.mydomain.com {
thread-id remote-site-1
path /usr/local/etc
}
host nameserver2.mydomain.com {
thread-id remote-site-1
path /usr/local/etc
}
host ftpserver.mydomain.com {
thread-id remote-site-1
path /usr/local/etc
path /ftp/*
}
#############################################################################
# Remote Site 2
host mysql.mydomain.com {
thread-id remote-site-2
path /usr/local/etc
path /mysql/.zfs/snapshot/backup=/mysql
pre-script /usr/local/bin/snapshot-db tank/mysql@backup
exclude /master-log.*
force-checksum yes # this is HIGHLY recommended for MySQL backups
}
host server1.mydomain.com {
thread-id remote-site-2
path /usr/local/etc
}
host server2.mydomain.com {
thread-id remote-site-2
path /usr/local/etc
}
host server3.mydomain.com {
thread-id remote-site-2
path /usr/local/etc
}
host solrlucene.mydomain.com {
thread-id remote-site-2
path /usr/local/etc
path /solr
exclude /*/solr/data/*
exclude /*/solr/logs/*
}
#!/usr/local/bin/php
<?
// :set cindent et sts=4 sw=4
/*
* ypass-backup: Backup script for Solaris 10 with ZFS and rsync
*
* Copyright (c) 2009 Eric Kilfoil ([email protected])
* http://www.ypass.net/solaris/zfsbackup/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(CONFIG_FILE, "/usr/local/etc/backup.conf");
// This is the name of the zfs filesystem to snapshot
// This should move to the config file
define(BACKUP_ZFS_NAME, "tank/backup");
// This is the mountpoint for the above zfs dataset
// This should move to the config file
define(BACKUP_DIR, "/backup/");
define(VERSION, '1.0');
$config = array();
$thread_id_list = array();
$debug = FALSE;
$compression = FALSE;
$opt_debug = $opt_thread = $opt_host = $opt_path = NULL;
function display_status($step)
{
$dots = "..............................................................................";
echo substr($step . $dots, 0, 65) . " ";
}
function display_result($resultcode, $resulttext = NULL)/*{{{*/
{
if ($resultcode) {
echo ($resulttext === NULL ? "done" : $resulttext) . "\n";
} else {
echo ($resulttext === NULL ? "failed" : $resulttext) . "\n";
}
}
function status_line($line) {
echo date("H:i:s") . " $line\n";
}
// Remove any double slashes from the path
function normalize_path($path) {
while (preg_match('%//%', $path)) {
$path = preg_replace('%//%', '/', $path);
}
return($path);
}
// Read in the configuration file and populate the global $config array
function read_config($config_file) {
global $config, $thread_id_list;
if (! file_exists($config_file)) {
echo "error: config file not found at $config_file\n";
echo "edit this script and set the location of CONFIG_FILE at the top\n";
exit(1);
}
// normalize the path and remove any trailing slashes
$config['backup-dir'] = preg_replace('%/$%', '', normalize_path(BACKUP_DIR));
$cfglines = file($config_file);
$inhostblock = FALSE;
$linectr = 0;
for ($ctr = 0; $ctr < count($cfglines); $ctr++) {
$l = $cfglines[$ctr];
$linectr++;
// skip blank lines and comments
if (trim($l) == "") {
continue;
}
if (preg_match('/^\s*#.*$/', $l)) {
continue;
}
// make sure we're in the global context and this line isn't a
// hostblock definition
if (!$inhostblock && ! preg_match('/^\s*host\s+([^{]+){\s+$/', $l, $regs)) {
// parse a global config option
if (preg_match('/^\s*(\S+)\s+([^#]+)/', $l, $regs)) {
$option = trim($regs[1]);
$value = trim($regs[2]);
switch ($option) {
case 'default-protocol':
$config['default-protocol'] = $value;
break;
case 'keep-daily':
$config['keep-daily'] = $value;
break;
case 'keep-weekly':
$config['keep-weekly'] = $value;
break;
case 'weekly-backup-day':
$config['weekly-backup-day'] = $value;
break;
case 'show-start-line':
$config['show-start-line'] = $value;
break;
case 'find-path':
$config['find-path'] = $value;
break;
case 'rsync-path':
$config['rsync-path'] = $value;
break;
case 'compression':
if ($value != 'yes' && $value != 'no') {
display_result(FALSE);
echo "error: valid options for compression are 'yes' and 'no' (line $linectr)\n";
exit(1);
}
$config['compression'] = $value;
$GLOBALS['compression'] = TRUE;
break;
case 'debug':
$config['debug'] = 'yes';
$GLOBALS['debug'] = TRUE;
break;
case 'protocol-path':
if (! preg_match('/(.+)=(.+)/', $value, $regs)) {
display_result(FALSE);
echo "error: bad protocol-path format: name=path (ie. rsh=/usr/bin/rsh) (line $linectr)\n";
exit(1);
}
$config['protocols'][$regs[1]]['path'] = trim($regs[2]);
break;
default:
display_result(FALSE);
echo "error: unknown global option '" . trim($regs[2]) . "' on line $linectr\n";
exit(1);
break;
}
} else {
display_result(FALSE);
echo "error: expected host block start\n";
echo "error parsing line $linectr: $l";
exit(1);
}
} else if ($inhostblock && preg_match('/^\s*host\s+([^{]+){\s+$/', $l, $regs)) {
// not able to have a host def inside of another host
// definition. Probably forgot a closing brace.
display_result(FALSE);
echo "error: previous hostblock not ended at line $linectr: $l";
exit(1);
} else if (preg_match('/^\s*host\s+([^{]+){\s+$/', $l, $regs)) {
// start a new host block
$inhostblock = TRUE;
$hostname = trim($regs[1]);
} else if (preg_match('/^\s*}\s+/', $l)) {
// end a new host block
$inhostblock = FALSE;
$hostname = '';
} else {
// we're inside of a hostblock, make sure the syntax is correct
// and set host options
if (preg_match('/^\s*(\S+)\s+([^#]+)/', $l, $regs)) {
$option = trim($regs[1]);
$value = trim($regs[2]);
switch ($option) {
case 'thread-id':
if ($config['hosts'][$hostname]['thread-id'] != "") {
display_result(FALSE);
echo "error: attempting to reset thread-id to '$value' on line $linectr\n";
exit(0);
}
$config['hosts'][$hostname]['thread-id'] = $value;
if (! array_key_exists($value, $thread_id_list)) {
$thread_id_list[$value] = 0;
}
break;
case 'protocol':
if ($config['hosts'][$hostname]['protocol'] != "") {
display_result(FALSE);
echo "error: attempting to reset protocol to '$value' on line $linectr\n";
exit(0);
}
$config['hosts'][$hostname]['protocol'] = $value;
break;
case 'force-checksum':
if ($config['hosts'][$hostname]['force-checksum'] != "") {
display_result(FALSE);
echo "error: attempting to specify force-checksum twice on line $linectr\n";
exit(0);
}
if ($value != "yes" && $value != "no") {
display_result(FALSE);
echo "error: invalid value for force-checksum on line $linectr\n";
exit(0);
}
$config['hosts'][$hostname]['force-checksum'] = $value;
break;
case 'path':
if (is_array($config['hosts'][$hostname]['path']) && in_array($value, $config['hosts'][$hostname]['path'])) {
display_result(FALSE);
echo "error: path '$value' listed twice on line $linectr\n";
exit(0);
}
$config['hosts'][$hostname]['path'][] = $value;
break;
case 'exclude':
if (is_array($config['hosts'][$hostname]['exclude']) && in_array($value, $config['hosts'][$hostname]['exclude'])) {
display_result(FALSE);
echo "error: exclude path '$value' listed twice on line $linectr\n";
exit(0);
}
$config['hosts'][$hostname]['exclude'][] = $value;
break;
case 'pre-script':
$config['hosts'][$hostname]['pre-script'][] = $value;
break;
case 'post-script':
$config['hosts'][$hostname]['post-script'][] = $value;
break;
default:
display_result(FALSE);
echo "error: unknown option on $linectr: $l";
exit(1);
break;
}
} else {
display_result(FALSE);
echo "error: cannot parse line $linectr: $l";
exit(1);
break;
}
}
}
}
// this function goes through the config array and makes sure all of the
// required global variables are set in the array, performs a sanity check
// on the host configuration and globs any paths specified in path
// configuration statements
function check_config() {
global $config;
if (! isset($config['keep-daily'])) {
echo "warning: keep-daily not defined in the configuration file\n";
echo "defaulting keep-daily to 7\n";
$config['keep-daily'] = 7;
}
if (! isset($config['keep-weekly'])) {
echo "warning: keep-weekly not defined in the configuration file\n";
echo "defaulting keep-weekly to 42\n";
$config['keep-weekly'] = 42;
}
if (! isset($config['weekly-backup-day'])) {
echo "warning: weekly-backup-day not defined in the configuration file\n";
echo "defaulting weekly-backup-day to Sun\n";
$config['weekly-backup-day'] = 'Sun';
}
if (! isset($config['find-path'])) {
echo "warning: find-path not defined in the configuration file\n";
echo "defaulting find-path to /usr/local/bin/find\n";
$config['find-path'] = '/usr/bin/find';
}
if (! isset($config['rsync-path'])) {
echo "warning: rsync-path not defined in the configuration file\n";
echo "defaulting rsync-path to /usr/local/bin/rsync\n";
$config['rsync-path'] = '/usr/local/bin/rsync';
}
if (! isset($config['default-protocol'])) {
display_result(FALSE);
echo "error: default-protocol not defined in the configuration file\n";
exit(1);
}
foreach ($config['hosts'] as $hostname => $host) {
if (! isset($host['protocol'])) {
$config['hosts'][$hostname]['protocol'] = $config['default-protocol'];
}
if (! is_array($host['path'])) {
display_result(FALSE);
echo "error: $hostname has no backup paths defined\n";
exit(1);
}
if (! is_array($config['protocols'][$config['hosts'][$hostname]['protocol']])) {
display_result(FALSE);
echo "error: $hostname has unknown protocol '" . $host['protocol'] . "'\n";
echo "All protocols must be defined with the global protocol-path statement\n";
exit(1);
}
foreach ($host['path'] as $i => $path) {
if ($path[strlen($path)-1] == '*') {
$command = $config['protocols'][$config['hosts'][$hostname]['protocol']]['path'] . " " . $hostname . " '" . $config['find-path'] . " $path -type d -maxdepth 0'";
$output = array();
exec($command, $output, $ret);
if ($ret != 0) {
display_result(FALSE);
echo "error: could not glob path '$path' for host $hostname\n";
exit(1);
}
$path[strlen($path)-1] = '';
foreach ($output as $subdir) {
$config['hosts'][$hostname]['path'][] = $subdir;
}
unset($config['hosts'][$hostname]['path'][$i]);
}
}
}
}
// This function actually performs the backup for a specific thread. This
// is called by a child after is forked off to handle the thread.
function run_thread($id) {
global $config, $debug, $opt_host, $opt_path;
foreach ($config['hosts'] as $hostname => $host) {
if ($host['thread-id'] != $id) {
// skip hosts not in the specified thread
continue;
}
if ($opt_host && $opt_host != $hostname) {
continue;
}
if ($host['force-checksum'] == 'yes') {
$checksum = TRUE;
} else {
$checksum = FALSE;
}
// loop through each path, build an rsync command, and execute
foreach ($host['path'] as $path) {
if (preg_match('/^(.*)=(.*)$/', $path, $regs)) {
$remotepath = $regs[1];
$localpath = $regs[2];
} else {
$remotepath = $path;
$localpath = $path;
}
if ($opt_path && $opt_path != $remotepath) {
continue;
}
if ( $config['show-start-line'] == 'yes' ||
$config['show-start-line'] == 'y' ||
$config['show-start-line'] == 'on') {
status_line("[$id] Starting backup of $hostname:$remotepath");
}
$rsync_path = $config['rsync-path'];
$protocol_path = $config['protocols'][$host['protocol']]['path'];
$excludeparams='';
// create a --exclude param for each excluded path
if (is_array($host['exclude'])) {
foreach ($host['exclude'] as $ex) {
$excludeparams="$excludeparams --exclude=$ex";
}
}
// if the path we're backing up to does exist, create it
// locally (and recursively)
if (! file_exists("/" . $config['backup-dir'] . "/$hostname/$localpath")) {
mkdir("/" . $config['backup-dir'] . "/$hostname/$localpath", 0755, TRUE);
}
$starttime = time();
// if there's a pre-script for the host, run it
if (is_array($host['pre-script'])) {
$scriptfailed = FALSE;
status_line("[$id] Executing pre-scripts on $hostname");
foreach ($host['pre-script'] as $scriptname) {
$command = $config['protocols'][$config['hosts'][$hostname]['protocol']]['path'] . " " . $hostname . " '$scriptname'";
$output = array();
if ($debug) {
echo $command . "\n";
} else {
exec("truss -f -o /tmp/e " . $command, $output, $ret);
}
if ($ret != 0) {
echo "error: pre-script failed execution -- skipping backup\n";
echo "---------[ Error Output ]--------------------------------------\n";
echo implode("\n", $output) . "\n\n";
$scriptfailed = TRUE;
break;
}
}
if ($scriptfailed) {
continue;
}
}
// put our actual rsync command together
$command = "$rsync_path -av " . ($checksum ? " -c " : "") . " " . ($config['compression'] ? " -z " : "") . " --rsync-path=$rsync_path --rsh=$protocol_path --exclude=/lost+found $excludeparams --delete $hostname:$remotepath/ /" . $config['backup-dir'] . "/$hostname/$localpath/ 2>&1";
if ($debug) {
echo $command . "\n";
} else {
$output='';
exec($command, $output, $ret);
$endtime = time();
$runtime = sprintf("%2.2lf min", ($endtime - $starttime) / 60);
$speedline = $output[count($output)-2];
$sizeline = $output[count($output)-1];
// when the command finishes, parse the output of rsync and
// build a string for the status line
if (preg_match('/^.*sent\s+(\d+)\s+bytes\s+received\s+(\d+)\s+bytes\s+(\d+).*$/', $speedline, $regs)) {
$xmitbytes = $regs[2];
if ($xmitbytes > 1024*1024*1024) {
$xmitbytes = sprintf("%2.2lfGB", ($xmitbytes/1024/1024/1024));
} else if ($xmitbytes > 1024*1024) {
$xmitbytes = sprintf("%2.2lfMB", ($xmitbytes/1024/1024));
} else if ($xmitbytes > 1024) {
$xmitbytes = sprintf("%2.2lfKB", ($xmitbytes/1024));
} else {
$xmitbytes = sprintf("%dB", ($xmitbytes));
}
$speed = sprintf("%2.2lfKbps", $regs[3] / 1024 * 8);
} else {
$speed = "unknown";
$xmitbytes = "unknown";
}
if (preg_match('/^.*total size is (\d+)\s+speedup.*$/', $sizeline, $regs)) {
$sizebytes = $regs[1];
if ($sizebytes > 1024*1024*1024) {
$size = sprintf("%2.2lfGB", ($sizebytes/1024/1024/1024));
} else if ($sizebytes > 1024*1024) {
$size = sprintf("%2.2lfMB", ($sizebytes/1024/1024));
} else if ($sizebytes > 1024) {
$size = sprintf("%2.2lfKB", ($sizebytes/1024));
} else {
$size = $sizebytes;
}
} else {
$size = "unknown";
}
if ($ret != 0) {
// if the rsync failed, just dump all of the output
status_line("[$id] $hostname:$remotepath failed:");
echo "---------[ Error Output ]--------------------------------------\n";
echo implode("\n", $output) . "\n\n";
} else {
// touch all of the paths down the line so we can see
// when something was last backed up in ls output
$e = 0;
$rpath = $config['backup-dir'] . "/$hostname/$localpath/";
do {
touch($rpath);
} while (($rpath = dirname($rpath)) != $config['backup-dir']);
status_line("[$id] $hostname:$remotepath: $size, xfer: $xmitbytes, $speed, $runtime");
// run any post backup scripts on the remote host
if (is_array($host['post-script'])) {
status_line("[$id] Executing post-scripts on $hostname");
foreach ($host['post-script'] as $scriptname) {
$command = $config['protocols'][$config['hosts'][$hostname]['protocol']]['path'] . " " . $hostname . " '$scriptname'";
$output = array();
if ($debug) {
echo $command . "\n";
} else {
exec($command, $output, $ret);
}
if ($ret != 0) {
echo "error: post-script failed execution\n";
echo "---------[ Error Output ]--------------------------------------\n";
echo implode("\n", $output) . "\n\n";
}
}
}
}
}
}
}
}
// remove zfs snapshots older than the specified limits
function zfs_cleanup() {
global $config;
$bnpath = basename(BACKUP_DIR);
exec('/usr/sbin/zfs list | grep ' . $bnpath . '@ | awk \'{ print $1 }\'', $snaplist, $ret);
if (! is_array($snaplist)) {
echo "warning: no snapshots exist\n\n";
return(0);
}
foreach ($snaplist as $snap) {
if (!preg_match('/^.*@(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/', $snap, $regs)) {
echo "warning: Ignoring unknown snapshot format on $snap\n";
}
$snaptime = mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
$snapage = time() - $snaptime;
$snapday = date("D", $snaptime);
if ($snapday == $config['weekly-backup-day']) {
if ($snapage >= $config['keep-weekly'] * 86400) {
status_line("$snap: removing - older than " . $config['keep-weekly'] . ' days');
`/usr/sbin/zfs destroy $snap`;
} else {
status_line("$snap: keeping - newer than " . $config['keep-weekly'] . ' days');
}
} else {
if ($snapage >= $config['keep-daily'] * 86400) {
status_line("$snap: removing - older than " . $config['keep-daily'] . ' days');
`/usr/sbin/zfs destroy $snap`;
} else {
status_line("$snap: keeping - newer than " . $config['keep-daily'] . ' days');
}
}
}
}
// create a zfs snapshot with a timestamp
function zfs_snapshot() {
global $config, $debug;
$snapname = BACKUP_ZFS_NAME . '@' . date("Ymd-His");
$output = array();
$command = '/usr/sbin/zfs snapshot ' . $snapname . " 2>&1";
if ($debug) {
echo $command . "\n";
} else {
exec($command, $output, $ret);
}
if ($ret != 0) {
echo "error: couldn't snapshot $snapname:\n";
echo implode("\n", $output) . "\n\n";
}
status_line("Created snapshot: $snapname");
}
// display the zfs filesystems and snapshots
function zfs_info() {
global $config;
$output = array();
exec('/usr/sbin/zfs list -r ' . BACKUP_ZFS_NAME, $output, $ret);
echo implode("\n", $output);
echo "\n";
}
// parse command line options
function parse_options() {
global $config, $debug, $opt_thread, $opt_host, $opt_path;
$longopts = array(
'thread:',
'host:',
'path:',
'debug',
'help',
'version',
);
if (version_compare(phpversion(), "5.3.0") >= 0) {
$optlist = getopt("dvt:h:p:", $longopts);
} else {
$optlist = getopt("dvt:h:p:");
}
foreach ($optlist as $optind => $optarg) {
switch ($optind) {
case 'debug':
case 'd':
$config['debug'] = TRUE;
$debug = TRUE;
break;
case 'thread':
case 't':
$opt_thread = $optarg;
break;
case 'host':
case 'h':
$opt_host = $optarg;
break;
case 'version':
case 'v':
case 'help':
echo "ypass-backup v" . VERSION . " Copyright (C) 2009 Eric Kilfoil <[email protected]>\n" .
"\n" .
"This program comes with ABSOLUTELY NO WARRANTY; This is free software, and you\n" .
"are welcome to redistribute it under certain conditions.\n" .
"For more information, visit http://www.ypass.net/solaris/zfsbackup/license.html\n" .
"\n" .
"Valid options:\n" .
"-t, --thread Specify a thread id to back up\n" .
"-h, --host Specify a hostname from the config file\n" .
"-p, --path Specify a pathname from the config file. If you\n" .
" specify a pathname, you must specify a host as well.\n" .
"-d, --debug Enable debugging\n";
exit(0);
case 'path':
case 'p':
$opt_path = $optarg;
break;
default:
echo "error: unknown option '$optind'\n";
exit(1);
break;
}
}
if (strlen($opt_host) && strlen($opt_thread)) {
echo "error: you cannot specify a host and a thread at the same time\n";
exit(1);
}
if (strlen($opt_path) && ! strlen($opt_host)) {
echo "error: you cannot specify a path without selecting a host\n";
exit(1);
}
}
// main code execution start
parse_options();
echo "---------[ Configuration File Processing ]---------------------------\n";
display_status("Reading configuration file");
read_config(CONFIG_FILE);
display_result(TRUE);
display_status("Validating configuration");
check_config();
display_result(TRUE);
echo "\n---------[ Filesystem Cleanup ]------------------------------------\n";
zfs_cleanup();
echo "\n---------[ Initializing Threads ]------------------------------------\n";
display_status("Checking number of threads");
if ($opt_thread) {
$thread_id_list = array($opt_thread => 0);
}
if ($opt_host) {
$thread_id_list = array($config['hosts'][$opt_host]['thread-id'] => 0);
}
display_result(TRUE, count($thread_id_list));
display_status("Checking for pcntl extension");
if (! extension_loaded('pcntl')) {
if (! dl('pcntl.so')) {
display_result(FALSE);
echo "error: could not load pcntl.so";
exit(1);
}
display_result(TRUE, 'loaded');
} else{
display_result(TRUE, 'done');
}
echo "\n---------[ Starting Backups ]------------------------------------\n";
// fork a child for every thread-id and start run running
foreach ($thread_id_list as $id => $pid) {
$pid = pcntl_fork();
if ($pid == -1 ) {
echo "error: could not fork -- aborting\n";
exit(1);
} else if ($pid) {
// parent (master) process
$thread_id_list[$id] = $pid;
// sleep for a second or the children clutter up the output
continue;
} else {
// child
display_status("Starting thread-id $id");
display_result(TRUE, "pid " . posix_getpid());
run_thread($id);
exit(0);
}
}
// master process comes here after creating children. children never get
// to this code.
// wait on each child and print a status line when each child finishes
for ($ctr = count($thread_id_list); $ctr > 0; $ctr--) {
$pid = pcntl_waitpid(0, $status);
$id = array_search($pid, $thread_id_list);
status_line(" Thread [$id] completed - " . ($ctr-1) . " still running");
}
echo "\n---------[ Creating Snapshot ]------------------------------------\n";
// only create a snapshot if we did a full backup
if (! $opt_host && ! $opt_path && ! $opt_thread) {
zfs_snapshot();
} else {
status_line("not creating snapshot for partial backup");
}
echo "\n---------[ Filesystem Info ]------------------------------------\n";
zfs_info();
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment