1#!/usr/bin/perl -w 2# $Id: slack-sync 180 2008-01-19 08:26:19Z alan $ 3# vim:sw=2 4# vim600:fdm=marker 5# Copyright (C) 2004-2008 Alan Sundell <alan@sundell.net> 6# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. 7# See the file COPYING for details. 8# 9# This script is in charge of copying files from the (possibly remote) 10# master directory to a local cache, using rsync 11 12require 5.006; 13use warnings FATAL => qw(all); 14use strict; 15use sigtrap qw(die untrapped normal-signals 16 stack-trace any error-signals); 17 18use File::Path; 19 20use constant LIB_DIR => '@SLACK_LIBDIR@'; 21use lib LIB_DIR; 22use Slack; 23 24my @rsync = ('rsync', 25 '--cvs-exclude', 26 '--recursive', 27 '--copy-links', 28 '--times', 29 '--perms', 30 '--sparse', 31 '--delete', 32 '--files-from=-', 33 '--from0', 34 ); 35 36(my $PROG = $0) =~ s#.*/##; 37 38sub check_cache ($); 39sub rsync_source ($$@); 40 41######################################## 42# Environment 43# Helpful prefix to die messages 44$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; 45# Set a reasonable umask 46umask 077; 47# Get out of wherever (possibly NFS-mounted) we were 48chdir("/") 49 or die "Could not chdir /: $!"; 50# Autoflush on STDERR 51select((select(STDERR), $|=1)[0]); 52 53######################################## 54# Config and option parsing {{{ 55my $usage = Slack::default_usage("$PROG [options] <role> [<role>...]"); 56# Option defaults 57my %opt = (); 58Slack::get_options( 59 opthash => \%opt, 60 usage => $usage, 61 required_options => [ qw(source cache) ], 62); 63 64# Arguments are required 65die "No roles given!\n\n$usage" unless @ARGV; 66 67# Prepare for backups 68if ($opt{backup} and $opt{'backup-dir'}) { 69 # Make sure backup directory exists 70 unless (-d $opt{'backup-dir'}) { 71 ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; 72 if (not $opt{'dry-run'}) { 73 eval { mkpath($opt{'backup-dir'}); }; 74 die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; 75 } 76 } 77 push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); 78} 79# Look at source type, and add options if necessary 80if ($opt{'rsh'} or $opt{source} =~ m/^[\w@\.-]+::/) { 81 # This is tunnelled rsync, and so needs an extra option 82 if ($opt{'rsh'}) { 83 push @rsync, '-e', $opt{'rsh'}; 84 } else { 85 push @rsync, '-e', 'ssh'; 86 } 87} 88 89# Pass options along to rsync 90if ($opt{'dry-run'}) { 91 push @rsync, '--dry-run'; 92} 93# Pass options along to rsync 94if ($opt{'verbose'} > 1) { 95 push @rsync, '--verbose'; 96} 97# }}} 98 99my @roles = (); 100 101{ 102 # This hash is just to avoid calling rsync twice if two subroles are 103 # installed. We only care since it's remote, and therefore slow. 104 my %roles_to_sync = (); 105 106 # copy over the new files 107 for my $full_role (@ARGV) { 108 # Get the first element of the role name (the base role) 109 # e.g., from "google.foogle.woogle", get "google" 110 my $base_role = (split /\./, $full_role, 2)[0]; 111 112 $roles_to_sync{$base_role} = 1; 113 } 114 @roles = keys %roles_to_sync; 115} 116 117my $cache = $opt{cache} . "/roles/"; 118# Make sure we've got the right perms before we copy stuff down 119check_cache($cache); 120 121rsync_source( 122 $opt{source} . '/roles/', 123 $cache, 124 @roles, 125); 126 127exit 0; 128 129# Make sure the cache directory exists and is mode 0700, to protect files 130# underneath in transit 131sub check_cache ($) { 132 my ($cache) = @_; 133 if (not $opt{'dry-run'}) { 134 if (not -d $cache) { 135 ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$cache'\n"; 136 eval { mkpath($cache); }; 137 die "Could not mkpath cache dir '$cache': $@\n" if $@; 138 } 139 ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$cache'\n"; 140 if ($> != 0) { 141 warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; 142 } else { 143 chown(0, 0, $cache) 144 or die "Could not chown 0:0 '$cache': $!\n"; 145 } 146 chmod(0700, $cache) 147 or die "Could not chmod 0700 '$cache': $!\n"; 148 } 149} 150 151# Pull down roles from an rsync source 152sub rsync_source($$@) { 153 my ($source, $destination, @roles) = @_; 154 my @command = (@rsync, $source, $destination); 155 156 ($opt{verbose} > 0) 157 and print STDERR "$PROG: Syncing cache with '@command'\n"; 158 159 my ($fh) = Slack::wrap_rsync_fh(@command); 160 161 # Shove the roles down its throat 162 print $fh join("\0", @roles), "\0"; 163 164 # Close fh, waitpid, and check return value 165 unless (close($fh)) { 166 Slack::check_system_exit(@command); 167 } 168} 169