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