diff --git a/slack-dist/dist/Makefile b/slack-dist/dist/Makefile index 6a931c4..0f62449 100644 --- a/slack-dist/dist/Makefile +++ b/slack-dist/dist/Makefile @@ -1,39 +1,39 @@ -# Makefile for slack/src -# $Id: Makefile 187 2008-03-03 02:00:18Z alan $ -include Makefile.common - -BACKENDS = slack-getroles slack-installfiles slack-runscript slack-sync slack-stage slack-rolediff - -all: - -install: install-bin install-conf install-lib install-man - -install-bin: all - $(MKDIR) $(DESTDIR)$(sbindir) - $(INSTALL) slack $(DESTDIR)$(sbindir) - $(MKDIR) $(DESTDIR)$(bindir) - $(INSTALL) slack-diff $(DESTDIR)$(bindir) - $(MKDIR) $(DESTDIR)$(slack_libexecdir) - @set -ex;\ - for i in $(BACKENDS); do \ - $(INSTALL) $$i $(DESTDIR)$(slack_libexecdir); done - $(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localstatedir) - $(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localcachedir) - -install-conf: all - $(MKDIR) $(DESTDIR)$(sysconfdir) - $(INSTALL) -m 0644 slack.conf $(DESTDIR)$(sysconfdir) - -install-lib: all - $(MKDIR) $(DESTDIR)$(slack_libdir) - $(INSTALL) -m 0644 Slack.pm $(DESTDIR)$(slack_libdir) - -install-man: all - -clean: - -realclean: clean - -distclean: clean - -test: +# Makefile for slack/src +# $Id: Makefile 187 2008-03-03 02:00:18Z alan $ +include Makefile.common + +BACKENDS = slack-getroles slack-installfiles slack-runscript slack-sync slack-stage slack-rolediff + +all: + +install: install-bin install-conf install-lib install-man + +install-bin: all + $(MKDIR) $(DESTDIR)$(sbindir) + $(INSTALL) slack $(DESTDIR)$(sbindir) + $(MKDIR) $(DESTDIR)$(bindir) + $(INSTALL) slack-diff $(DESTDIR)$(bindir) + $(MKDIR) $(DESTDIR)$(slack_libexecdir) + @set -ex;\ + for i in $(BACKENDS); do \ + $(INSTALL) $$i $(DESTDIR)$(slack_libexecdir); done + $(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localstatedir) + $(INSTALL) -d -m $(PRIVDIRMODE) $(DESTDIR)$(slack_localcachedir) + +install-conf: all + $(MKDIR) $(DESTDIR)$(sysconfdir) + $(INSTALL) -m 0644 slack.conf $(DESTDIR)$(sysconfdir) + +install-lib: all + $(MKDIR) $(DESTDIR)$(slack_libdir) + $(INSTALL) -m 0644 Slack.pm $(DESTDIR)$(slack_libdir) + +install-man: all + +clean: + +realclean: clean + +distclean: clean + +test: diff --git a/slack-dist/dist/Makefile.common b/slack-dist/dist/Makefile.common index 3da4bdd..198f557 100755 --- a/slack-dist/dist/Makefile.common +++ b/slack-dist/dist/Makefile.common @@ -1,27 +1,27 @@ -# Common code included in every Makefile -# $Id: Makefile.common 189 2008-04-21 00:52:56Z sundell $ - -PACKAGE=slack -VERSION=0.15.2 - -DESTDIR = - -prefix = / -exec_prefix = /usr -sysconfdir = ${prefix}/etc -mandir = ${exec_prefix}/share/man -bindir = ${exec_prefix}/bin -sbindir = ${exec_prefix}/sbin -libdir = ${exec_prefix}/lib -libexecdir = ${exec_prefix}/lib -localstatedir = ${prefix}/var - -slack_libdir = ${libdir}/slack -slack_libexecdir = ${libexecdir}/slack -slack_localstatedir = ${localstatedir}/lib/slack -slack_localcachedir = ${localstatedir}/cache/slack - -INSTALL = install -MKDIR = mkdir -p - -PRIVDIRMODE = 0700 +# Common code included in every Makefile +# $Id: Makefile.common 189 2008-04-21 00:52:56Z sundell $ + +PACKAGE=slack +VERSION=0.15.2 + +DESTDIR = + +prefix = / +exec_prefix = /usr +sysconfdir = ${prefix}/etc +mandir = ${exec_prefix}/share/man +bindir = ${exec_prefix}/bin +sbindir = ${exec_prefix}/sbin +libdir = ${exec_prefix}/lib +libexecdir = ${exec_prefix}/lib +localstatedir = ${prefix}/var + +slack_libdir = ${libdir}/slack +slack_libexecdir = ${libexecdir}/slack +slack_localstatedir = ${localstatedir}/lib/slack +slack_localcachedir = ${localstatedir}/cache/slack + +INSTALL = install +MKDIR = mkdir -p + +PRIVDIRMODE = 0700 diff --git a/slack-dist/dist/Slack.pm b/slack-dist/dist/Slack.pm index d524ac2..9f0a57d 100644 --- a/slack-dist/dist/Slack.pm +++ b/slack-dist/dist/Slack.pm @@ -1,371 +1,371 @@ -# $Id: Slack.pm 189 2008-04-21 00:52:56Z sundell $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2008 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. - -package Slack; - -require 5.006; -use strict; -use Carp qw(cluck confess croak); -use File::Find; -use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG); - -use base qw(Exporter); -use vars qw($VERSION @EXPORT @EXPORT_OK $DEFAULT_CONFIG_FILE); -$VERSION = '0.15.2'; -@EXPORT = qw(); -@EXPORT_OK = qw(); - -$DEFAULT_CONFIG_FILE = '/etc/slack.conf'; - -my $term; - -my @default_options = ( - 'help|h|?', - 'version', - 'verbose|v+', - 'quiet', - 'config|C=s', - 'source|s=s', - 'rsh|e=s', - 'cache|c=s', - 'stage|t=s', - 'root|r=s', - 'dry-run|n', - 'backup|b', - 'backup-dir=s', - 'hostname|H=s', -); - -sub default_usage ($) { - my ($synopsis) = @_; - return < config file to read -# opthash => hashref in which to store the options -# verbose => whether to be verbose -sub read_config (%) { - my %arg = @_; - my ($config_fh); - local $_; - - confess "Slack::read_config: no config file given" - if not defined $arg{file}; - $arg{opthash} = {} - if not defined $arg{opthash}; - - open($config_fh, '<', $arg{file}) - or confess "Could not open config file '$arg{file}': $!"; - - # Make this into a hash so we can quickly see if we're looking - # for a particular option - my %looking_for; - if (ref $arg{options} eq 'ARRAY') { - %looking_for = map { $_ => 1 } @{$arg{options}}; - } - - while(<$config_fh>) { - chomp; - s/#.*//; # delete comments - s/\s+$//; # delete trailing spaces - next if m/^$/; # skip empty lines - - if (m/^[A-Z_]+=\S+/) { - my ($key, $value) = split(/=/, $_, 2); - $key =~ tr/A-Z_/a-z-/; - # Only set options we're looking for - next if (%looking_for and not $looking_for{$key}); - # Don't set options that are already set - next if defined $arg{opthash}->{$key}; - - $arg{verbose} and print STDERR "Slack::read_config: Setting '$key' to '$value'\n"; - $arg{opthash}->{$key} = $value; - } else { - cluck "Slack::read_config: Garbage line '$_' in '$arg{file}' line $. ignored"; - } - } - - close($config_fh) - or confess "Slack::read_config: Could not close config file: $!"; - - # The verbose option is treated specially in so many places that - # we need to make sure it's defined. - $arg{opthash}->{verbose} ||= 0; - - return $arg{opthash}; -} - -# Just get the exit code from a command that failed. -# croaks if anything weird happened. -sub get_system_exit (@) { - my @command = @_; - - if (WIFEXITED($?)) { - my $exit = WEXITSTATUS($?); - return $exit if $exit; - } - if (WIFSIGNALED($?)) { - my $sig = WTERMSIG($?); - croak "'@command' caught sig $sig"; - } - if ($!) { - croak "Syserr on system '@command': $!"; - } - croak "Unknown error on '@command'"; -} - -sub check_system_exit (@) { - my @command = @_; - my $exit = get_system_exit(@command); - # Exit is non-zero if get_system_exit() didn't croak. - croak "'@command' exited $exit"; -} - -# get options from the command line and the config file -# Arguments -# opthash => hashref in which to store options -# usage => usage statement -# required_options => arrayref of options to require -- an exception -# will be thrown if these options are not defined -# command_line_hash => store options specified on the command line here -sub get_options { - my %arg = @_; - use Getopt::Long; - Getopt::Long::Configure('bundling'); - - if (not defined $arg{opthash}) { - $arg{opthash} = {}; - } - - if (not defined $arg{usage}) { - $arg{usage} = default_usage($0); - } - - my @extra_options = (); # extra arguments to getoptions - if (defined $arg{command_line_options}) { - @extra_options = @{$arg{command_line_options}}; - } - - # Make a --quiet function that turns off verbosity - $arg{opthash}->{quiet} = sub { $arg{opthash}->{verbose} = 0; }; - - unless (GetOptions($arg{opthash}, - @default_options, - @extra_options, - )) { - print STDERR $arg{usage}; - exit 1; - } - if ($arg{opthash}->{help}) { - print $arg{usage}; - exit 0; - } - - if ($arg{opthash}->{version}) { - print "slack version $VERSION\n"; - exit 0; - } - - # Get rid of the quiet handler - delete $arg{opthash}->{quiet}; - - # If we've been given a hashref, save our options there at this - # stage, so the caller can see what was passed on the command line. - # Unfortunately, perl has no .replace function, so we iterate. - if (ref $arg{command_line_hash} eq 'HASH') { - while (my ($k, $v) = each %{$arg{opthash}}) { - $arg{command_line_hash}->{$k} = $v; - } - } - - # Use the default config file - if (not defined $arg{opthash}->{config}) { - $arg{opthash}->{config} = $DEFAULT_CONFIG_FILE; - } - - # We need to decide whether to be verbose about reading the config file - # Currently we just do it if global verbosity > 2 - my $verbose_config = 0; - if (defined $arg{opthash}->{verbose} - and $arg{opthash}->{verbose} > 2) { - $verbose_config = 1; - } - - # Read options from the config file, passing along the options we've - # gotten so far - read_config( - file => $arg{opthash}->{config}, - opthash => $arg{opthash}, - verbose => $verbose_config, - ); - - # The "verbose" option gets compared a lot and needs to be defined - $arg{opthash}->{verbose} ||= 0; - - # The "hostname" option is set specially if it's not defined - if (not defined $arg{opthash}->{hostname}) { - use Sys::Hostname; - $arg{opthash}->{hostname} = hostname; - } - - # We can require some options to be set - if (ref $arg{required_options} eq 'ARRAY') { - for my $option (@{$arg{required_options}}) { - if (not defined $arg{opthash}->{$option}) { - croak "Required option '$option' not given on command line or specified in config file!\n"; - } - } - } - - return $arg{opthash}; -} - -sub prompt ($) { - my ($prompt) = @_; - if (not defined $term) { - require Term::ReadLine; - $term = new Term::ReadLine 'slack' - } - - $term->readline($prompt); -} - - -# Calls the callback on absolute pathnames of files in the source directory, -# and also on names of directories that don't exist in the destination -# directory (i.e. where $source/foo exists but $destination/foo does not). -sub find_files_to_install ($$$) { - my ($source, $destination, $callback) = @_; - return find ({ - wanted => sub { - if (-l or not -d _) { - # Copy all files, links, etc - my $file = $File::Find::name; - &$callback($file); - } elsif (-d _) { - # For directories, we only want to copy it if it doesn't - # exist in the destination yet. - my $dir = $File::Find::name; - # We know the root directory will exist (we make it above), - # so skip the base of the source - (my $short_source = $source) =~ s#/$##; - return if $dir eq $short_source; - - # Strip the $source from the path, - # so we can build the destination dir from it. - my $subdir = $dir; - ($subdir =~ s#^$source##) - or croak "sub failed: $source|$subdir"; - - if (not -d "$destination/$subdir") { - &$callback($dir); - } - } - } - }, - $source, - ); -} - -# Runs rsync with the necessary redirection to its filehandles -sub wrap_rsync (@) { - my @command = @_; - my ($pid); - - if ($pid = fork) { - # Parent - } elsif (defined $pid) { - # Child - open(STDIN, "<", "/dev/null") - or die "Could not redirect STDIN from /dev/null\n"; - # This redirection is necessary because rsync sends - # verbose output to STDOUT - open(STDOUT, ">&STDERR") - or die "Could not redirect STDOUT to STDERR\n"; - exec(@command); - die "Could not exec '@command': $!\n"; - } else { - die "Could not fork: $!\n"; - } - - my $kid = waitpid($pid, 0); - if ($kid != $pid) { - die "waitpid returned $kid\n"; - } elsif ($?) { - Slack::check_system_exit(@command); - } -} - -# Runs rsync with the necessary redirection to its filehandles, but also -# returns an FH to stdin and a PID. -sub wrap_rsync_fh (@) { - my @command = @_; - my ($fh, $pid); - - if ($pid = open($fh, "|-")) { - # Parent - } elsif (defined $pid) { - # Child - # This redirection is necessary because rsync sends - # verbose output to STDOUT - open(STDOUT, ">&STDERR") - or die "Could not redirect STDOUT to STDERR\n"; - exec(@command); - die "Could not exec '@command': $!\n"; - } else { - die "Could not fork: $!\n"; - } - return($fh, $pid); -} - -1; +# $Id: Slack.pm 189 2008-04-21 00:52:56Z sundell $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2008 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. + +package Slack; + +require 5.006; +use strict; +use Carp qw(cluck confess croak); +use File::Find; +use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG); + +use base qw(Exporter); +use vars qw($VERSION @EXPORT @EXPORT_OK $DEFAULT_CONFIG_FILE); +$VERSION = '0.15.2'; +@EXPORT = qw(); +@EXPORT_OK = qw(); + +$DEFAULT_CONFIG_FILE = '/etc/slack.conf'; + +my $term; + +my @default_options = ( + 'help|h|?', + 'version', + 'verbose|v+', + 'quiet', + 'config|C=s', + 'source|s=s', + 'rsh|e=s', + 'cache|c=s', + 'stage|t=s', + 'root|r=s', + 'dry-run|n', + 'backup|b', + 'backup-dir=s', + 'hostname|H=s', +); + +sub default_usage ($) { + my ($synopsis) = @_; + return < config file to read +# opthash => hashref in which to store the options +# verbose => whether to be verbose +sub read_config (%) { + my %arg = @_; + my ($config_fh); + local $_; + + confess "Slack::read_config: no config file given" + if not defined $arg{file}; + $arg{opthash} = {} + if not defined $arg{opthash}; + + open($config_fh, '<', $arg{file}) + or confess "Could not open config file '$arg{file}': $!"; + + # Make this into a hash so we can quickly see if we're looking + # for a particular option + my %looking_for; + if (ref $arg{options} eq 'ARRAY') { + %looking_for = map { $_ => 1 } @{$arg{options}}; + } + + while(<$config_fh>) { + chomp; + s/#.*//; # delete comments + s/\s+$//; # delete trailing spaces + next if m/^$/; # skip empty lines + + if (m/^[A-Z_]+=\S+/) { + my ($key, $value) = split(/=/, $_, 2); + $key =~ tr/A-Z_/a-z-/; + # Only set options we're looking for + next if (%looking_for and not $looking_for{$key}); + # Don't set options that are already set + next if defined $arg{opthash}->{$key}; + + $arg{verbose} and print STDERR "Slack::read_config: Setting '$key' to '$value'\n"; + $arg{opthash}->{$key} = $value; + } else { + cluck "Slack::read_config: Garbage line '$_' in '$arg{file}' line $. ignored"; + } + } + + close($config_fh) + or confess "Slack::read_config: Could not close config file: $!"; + + # The verbose option is treated specially in so many places that + # we need to make sure it's defined. + $arg{opthash}->{verbose} ||= 0; + + return $arg{opthash}; +} + +# Just get the exit code from a command that failed. +# croaks if anything weird happened. +sub get_system_exit (@) { + my @command = @_; + + if (WIFEXITED($?)) { + my $exit = WEXITSTATUS($?); + return $exit if $exit; + } + if (WIFSIGNALED($?)) { + my $sig = WTERMSIG($?); + croak "'@command' caught sig $sig"; + } + if ($!) { + croak "Syserr on system '@command': $!"; + } + croak "Unknown error on '@command'"; +} + +sub check_system_exit (@) { + my @command = @_; + my $exit = get_system_exit(@command); + # Exit is non-zero if get_system_exit() didn't croak. + croak "'@command' exited $exit"; +} + +# get options from the command line and the config file +# Arguments +# opthash => hashref in which to store options +# usage => usage statement +# required_options => arrayref of options to require -- an exception +# will be thrown if these options are not defined +# command_line_hash => store options specified on the command line here +sub get_options { + my %arg = @_; + use Getopt::Long; + Getopt::Long::Configure('bundling'); + + if (not defined $arg{opthash}) { + $arg{opthash} = {}; + } + + if (not defined $arg{usage}) { + $arg{usage} = default_usage($0); + } + + my @extra_options = (); # extra arguments to getoptions + if (defined $arg{command_line_options}) { + @extra_options = @{$arg{command_line_options}}; + } + + # Make a --quiet function that turns off verbosity + $arg{opthash}->{quiet} = sub { $arg{opthash}->{verbose} = 0; }; + + unless (GetOptions($arg{opthash}, + @default_options, + @extra_options, + )) { + print STDERR $arg{usage}; + exit 1; + } + if ($arg{opthash}->{help}) { + print $arg{usage}; + exit 0; + } + + if ($arg{opthash}->{version}) { + print "slack version $VERSION\n"; + exit 0; + } + + # Get rid of the quiet handler + delete $arg{opthash}->{quiet}; + + # If we've been given a hashref, save our options there at this + # stage, so the caller can see what was passed on the command line. + # Unfortunately, perl has no .replace function, so we iterate. + if (ref $arg{command_line_hash} eq 'HASH') { + while (my ($k, $v) = each %{$arg{opthash}}) { + $arg{command_line_hash}->{$k} = $v; + } + } + + # Use the default config file + if (not defined $arg{opthash}->{config}) { + $arg{opthash}->{config} = $DEFAULT_CONFIG_FILE; + } + + # We need to decide whether to be verbose about reading the config file + # Currently we just do it if global verbosity > 2 + my $verbose_config = 0; + if (defined $arg{opthash}->{verbose} + and $arg{opthash}->{verbose} > 2) { + $verbose_config = 1; + } + + # Read options from the config file, passing along the options we've + # gotten so far + read_config( + file => $arg{opthash}->{config}, + opthash => $arg{opthash}, + verbose => $verbose_config, + ); + + # The "verbose" option gets compared a lot and needs to be defined + $arg{opthash}->{verbose} ||= 0; + + # The "hostname" option is set specially if it's not defined + if (not defined $arg{opthash}->{hostname}) { + use Sys::Hostname; + $arg{opthash}->{hostname} = hostname; + } + + # We can require some options to be set + if (ref $arg{required_options} eq 'ARRAY') { + for my $option (@{$arg{required_options}}) { + if (not defined $arg{opthash}->{$option}) { + croak "Required option '$option' not given on command line or specified in config file!\n"; + } + } + } + + return $arg{opthash}; +} + +sub prompt ($) { + my ($prompt) = @_; + if (not defined $term) { + require Term::ReadLine; + $term = new Term::ReadLine 'slack' + } + + $term->readline($prompt); +} + + +# Calls the callback on absolute pathnames of files in the source directory, +# and also on names of directories that don't exist in the destination +# directory (i.e. where $source/foo exists but $destination/foo does not). +sub find_files_to_install ($$$) { + my ($source, $destination, $callback) = @_; + return find ({ + wanted => sub { + if (-l or not -d _) { + # Copy all files, links, etc + my $file = $File::Find::name; + &$callback($file); + } elsif (-d _) { + # For directories, we only want to copy it if it doesn't + # exist in the destination yet. + my $dir = $File::Find::name; + # We know the root directory will exist (we make it above), + # so skip the base of the source + (my $short_source = $source) =~ s#/$##; + return if $dir eq $short_source; + + # Strip the $source from the path, + # so we can build the destination dir from it. + my $subdir = $dir; + ($subdir =~ s#^$source##) + or croak "sub failed: $source|$subdir"; + + if (not -d "$destination/$subdir") { + &$callback($dir); + } + } + } + }, + $source, + ); +} + +# Runs rsync with the necessary redirection to its filehandles +sub wrap_rsync (@) { + my @command = @_; + my ($pid); + + if ($pid = fork) { + # Parent + } elsif (defined $pid) { + # Child + open(STDIN, "<", "/dev/null") + or die "Could not redirect STDIN from /dev/null\n"; + # This redirection is necessary because rsync sends + # verbose output to STDOUT + open(STDOUT, ">&STDERR") + or die "Could not redirect STDOUT to STDERR\n"; + exec(@command); + die "Could not exec '@command': $!\n"; + } else { + die "Could not fork: $!\n"; + } + + my $kid = waitpid($pid, 0); + if ($kid != $pid) { + die "waitpid returned $kid\n"; + } elsif ($?) { + Slack::check_system_exit(@command); + } +} + +# Runs rsync with the necessary redirection to its filehandles, but also +# returns an FH to stdin and a PID. +sub wrap_rsync_fh (@) { + my @command = @_; + my ($fh, $pid); + + if ($pid = open($fh, "|-")) { + # Parent + } elsif (defined $pid) { + # Child + # This redirection is necessary because rsync sends + # verbose output to STDOUT + open(STDOUT, ">&STDERR") + or die "Could not redirect STDOUT to STDERR\n"; + exec(@command); + die "Could not exec '@command': $!\n"; + } else { + die "Could not fork: $!\n"; + } + return($fh, $pid); +} + +1; diff --git a/slack-dist/dist/slack b/slack-dist/dist/slack index d1171ca..d8e9fbb 100644 --- a/slack-dist/dist/slack +++ b/slack-dist/dist/slack @@ -1,329 +1,329 @@ -#!/usr/bin/perl -w -# $Id: slack 180 2008-01-19 08:26:19Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2008 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. - -# This script is in charge of copying files from the (possibly remote) -# master directory to a local cache, using rsync - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use File::Path; -use File::Find; -use POSIX; # for strftime - -use constant LIBEXEC_DIR => '/usr/lib/slack'; -use constant LIB_DIR => '/usr/lib/slack'; -use lib LIB_DIR; -use Slack; - -sub run_backend(@); -sub run_conditional_backend($@); - -(my $PROG = $0) =~ s#.*/##; - -# Arguments to pass to each backends (initialized to a hash of empty arrays) -my %backend_flags = ( map { $_ => [] } - qw(getroles sync stage preview preinstall fixfiles installfiles postinstall) -); - -my @roles; - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Get out of wherever (possibly NFS-mounted) we were -chdir("/") - or die "Could not chdir /: $!"; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); - -######################################## -# Config and option parsing {{{ -my $usage = Slack::default_usage("$PROG [options] [...]"); -$usage .= < \%opt, - command_line_options => [ - 'preview=s', - 'role-list=s', - 'no-scripts|noscripts', - 'no-files|nofiles', - 'no-sync|nosync', - 'libexec-dir=s', - 'diff=s', - 'sleep=i', - ], - required_options => [ qw(source cache stage root) ], - command_line_hash => \%command_line_opt, - usage => $usage, -); - -# Special options -if ($opt{'dry-run'}) { - $opt{'no-scripts'} = 1; - $opt{'no-files'} = 1; -} -if ($opt{'no-scripts'}) { - for my $action (qw(fixfiles preinstall postinstall)) { - push @{$backend_flags{$action}}, - '--dry-run'; - } -} -if ($opt{'no-files'}) { - push @{$backend_flags{installfiles}}, - '--dry-run'; -} -# propagate verbosity - 1 to all backends -if (defined $command_line_opt{'verbose'} and - $command_line_opt{'verbose'} > 1) { - for my $action (keys %backend_flags) { - push @{$backend_flags{$action}}, - ('--verbose') x ($command_line_opt{'verbose'} - 1); - } -} -# propagate these flags to all the backends -for my $option (qw(config root cache stage source hostname rsh)) { - if ($command_line_opt{$option}) { - for my $action (keys %backend_flags) { - push @{$backend_flags{$action}}, - "--$option=$command_line_opt{$option}"; - } - } -} -# getroles also can take 'role-list' -if ($command_line_opt{'role-list'}) { - push @{$backend_flags{'getroles'}}, - "--role-list=$command_line_opt{'role-list'}"; -} - -# The libexec dir defaults to this if it wasn't specified -# on the command line or in a config file. -if (not defined $opt{'libexec-dir'}) { - $opt{'libexec-dir'} = LIBEXEC_DIR; -} - -# Pass diff option along to slack-rolediff -if ($opt{'diff'}) { - push @{$backend_flags{preview}}, - "--diff=$opt{'diff'}"; -} - -# Preview takes an optional argument. If no argument is given, -# it gets "" from getopt. -if (defined $opt{'preview'}) { - if (not grep /^$opt{'preview'}$/, qw(simple prompt)) { - die "Unknown preview mode '$opt{'preview'}'!"; - } -} - -# The backup option defaults to on if it wasn't specified -# on the command line or in a config file -if (not defined $opt{backup}) { - $opt{backup} = 1; -} -# Figure out a place to put backups -if ($opt{backup} and $opt{'backup-dir'}) { - push @{$backend_flags{installfiles}}, - '--backup', - '--backup-dir='. - $opt{'backup-dir'}. - "/". - strftime('%F-%T', localtime(time)) - ; -} -# }}} - -# Random sleep, helpful when called from cron. -if ($opt{sleep}) { - my $secs = int(rand($opt{sleep})) + 1; - $opt{verbose} and print STDERR "$PROG: sleep $secs\n"; - sleep($secs); -} - -# Get a list of roles to install from slack-getroles {{{ -if (not @ARGV) { - my @command = ($opt{'libexec-dir'}.'/slack-getroles', - @{$backend_flags{'getroles'}}); - $opt{verbose} and print STDERR "$PROG: getroles\n"; - ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command' to get a list of roles for this host.\n"; - my ($roles_pid, $roles_fh); - if ($roles_pid = open($roles_fh, "-|")) { - # Parent - } elsif (defined $roles_pid) { - # Child - exec(@command); - die "Could not exec '@command': $!\n"; - } else { - die "Could not fork to run '@command': $!\n"; - } - @roles = split(/\s+/, join(" ", <$roles_fh>)); - unless (close($roles_fh)) { - Slack::check_system_exit(@command); - } -} else { - @roles = @ARGV; -} -# }}} - -# Check role name syntax {{{ -for my $role (@roles) { - # Roles MUST begin with a letter. All else is reserved. - if ($role !~ m/^[a-zA-Z]/) { - die "Role '$role' does not begin with a letter!"; - } -} -# }}} - -$opt{verbose} and print STDERR "$PROG: installing roles: @roles\n"; - -unless ($opt{'no-sync'}) { - # sync all the roles down at once - $opt{verbose} and print STDERR "$PROG: sync @roles\n"; - run_backend('slack-sync', - @{$backend_flags{sync}}, @roles); -} - -ROLE: for my $role (@roles) { - # stage - $opt{verbose} and print STDERR "$PROG: stage files $role\n"; - run_backend('slack-stage', - @{$backend_flags{stage}}, '--subdir=files', $role); - - if ($opt{preview}) { - if ($opt{preview} eq 'simple') { - $opt{verbose} and print STDERR "$PROG: preview $role\n"; - # Here, we run the backend in no-prompt mode. - run_conditional_backend(0, 'slack-rolediff', - @{$backend_flags{preview}}, $role); - # ...and we skip further action in the ROLE after showing the diff. - next ROLE; - } elsif ($opt{preview} eq 'prompt') { - $opt{verbose} and print STDERR "$PROG: preview scripts $role\n"; - # Here, we want to prompt and just do the scripts, since - # we need to run preinstall and fixfiles before doing the files. - run_conditional_backend(1, 'slack-rolediff', - @{$backend_flags{preview}}, '--subdir=scripts', $role); - } else { - # Should get caught in option processing, above - die "Unknown preview mode!\n"; - } - } - - $opt{verbose} and print STDERR "$PROG: stage scripts $role\n"; - run_backend('slack-stage', - @{$backend_flags{stage}}, '--subdir=scripts', $role); - - # preinstall - $opt{verbose} and print STDERR "$PROG: preinstall $role\n"; - run_backend('slack-runscript', - @{$backend_flags{preinstall}}, 'preinstall', $role); - - # fixfiles - $opt{verbose} and print STDERR "$PROG: fixfiles $role\n"; - run_backend('slack-runscript', - @{$backend_flags{fixfiles}}, 'fixfiles', $role); - - # preview files - if ($opt{preview} and $opt{preview} eq 'prompt') { - $opt{verbose} and print STDERR "$PROG: preview files $role\n"; - run_conditional_backend(1, 'slack-rolediff', - @{$backend_flags{preview}}, '--subdir=files', $role); - } - - # installfiles - $opt{verbose} and print STDERR "$PROG: install $role\n"; - run_backend('slack-installfiles', - @{$backend_flags{installfiles}}, $role); - - # postinstall - $opt{verbose} and print STDERR "$PROG: postinstall $role\n"; - run_backend('slack-runscript', - @{$backend_flags{postinstall}}, 'postinstall', $role); -} -exit 0; - -sub run_backend (@) { - my ($backend, @args) = @_; - # If we weren't given an explicit path, prepend the libexec dir - unless ($backend =~ m#^/#) { - $backend = $opt{'libexec-dir'} . '/' . $backend; - } - - # Assemble our command line - my (@command) = ($backend, @args); - ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command'\n"; - unless (system(@command) == 0) { - Slack::check_system_exit(@command); - } -} - -sub run_conditional_backend ($@) { - my ($prompt, $backend, @args) = @_; - # If we weren't given an explicit path, prepend the libexec dir - unless ($backend =~ m#^/#) { - $backend = $opt{'libexec-dir'} . '/' . $backend; - } - - # Assemble our command line - my (@command) = ($backend, @args); - ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command'\n"; - unless (system(@command) == 0) { - my $exit = Slack::get_system_exit(@command); - - if ($exit == 1) { - # exit 1 means a difference found or something normal that requires - # a prompt before continuing. - if ($prompt) { - exit 1 unless Slack::prompt("Continue? [yN] ") eq 'y'; - } - } else { - # any other non-successful exit is a serious error. - die "'@command' exited $exit"; - } - } -} +#!/usr/bin/perl -w +# $Id: slack 180 2008-01-19 08:26:19Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2008 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. + +# This script is in charge of copying files from the (possibly remote) +# master directory to a local cache, using rsync + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use File::Path; +use File::Find; +use POSIX; # for strftime + +use constant LIBEXEC_DIR => '/usr/lib/slack'; +use constant LIB_DIR => '/usr/lib/slack'; +use lib LIB_DIR; +use Slack; + +sub run_backend(@); +sub run_conditional_backend($@); + +(my $PROG = $0) =~ s#.*/##; + +# Arguments to pass to each backends (initialized to a hash of empty arrays) +my %backend_flags = ( map { $_ => [] } + qw(getroles sync stage preview preinstall fixfiles installfiles postinstall) +); + +my @roles; + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Get out of wherever (possibly NFS-mounted) we were +chdir("/") + or die "Could not chdir /: $!"; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); + +######################################## +# Config and option parsing {{{ +my $usage = Slack::default_usage("$PROG [options] [...]"); +$usage .= < \%opt, + command_line_options => [ + 'preview=s', + 'role-list=s', + 'no-scripts|noscripts', + 'no-files|nofiles', + 'no-sync|nosync', + 'libexec-dir=s', + 'diff=s', + 'sleep=i', + ], + required_options => [ qw(source cache stage root) ], + command_line_hash => \%command_line_opt, + usage => $usage, +); + +# Special options +if ($opt{'dry-run'}) { + $opt{'no-scripts'} = 1; + $opt{'no-files'} = 1; +} +if ($opt{'no-scripts'}) { + for my $action (qw(fixfiles preinstall postinstall)) { + push @{$backend_flags{$action}}, + '--dry-run'; + } +} +if ($opt{'no-files'}) { + push @{$backend_flags{installfiles}}, + '--dry-run'; +} +# propagate verbosity - 1 to all backends +if (defined $command_line_opt{'verbose'} and + $command_line_opt{'verbose'} > 1) { + for my $action (keys %backend_flags) { + push @{$backend_flags{$action}}, + ('--verbose') x ($command_line_opt{'verbose'} - 1); + } +} +# propagate these flags to all the backends +for my $option (qw(config root cache stage source hostname rsh)) { + if ($command_line_opt{$option}) { + for my $action (keys %backend_flags) { + push @{$backend_flags{$action}}, + "--$option=$command_line_opt{$option}"; + } + } +} +# getroles also can take 'role-list' +if ($command_line_opt{'role-list'}) { + push @{$backend_flags{'getroles'}}, + "--role-list=$command_line_opt{'role-list'}"; +} + +# The libexec dir defaults to this if it wasn't specified +# on the command line or in a config file. +if (not defined $opt{'libexec-dir'}) { + $opt{'libexec-dir'} = LIBEXEC_DIR; +} + +# Pass diff option along to slack-rolediff +if ($opt{'diff'}) { + push @{$backend_flags{preview}}, + "--diff=$opt{'diff'}"; +} + +# Preview takes an optional argument. If no argument is given, +# it gets "" from getopt. +if (defined $opt{'preview'}) { + if (not grep /^$opt{'preview'}$/, qw(simple prompt)) { + die "Unknown preview mode '$opt{'preview'}'!"; + } +} + +# The backup option defaults to on if it wasn't specified +# on the command line or in a config file +if (not defined $opt{backup}) { + $opt{backup} = 1; +} +# Figure out a place to put backups +if ($opt{backup} and $opt{'backup-dir'}) { + push @{$backend_flags{installfiles}}, + '--backup', + '--backup-dir='. + $opt{'backup-dir'}. + "/". + strftime('%F-%T', localtime(time)) + ; +} +# }}} + +# Random sleep, helpful when called from cron. +if ($opt{sleep}) { + my $secs = int(rand($opt{sleep})) + 1; + $opt{verbose} and print STDERR "$PROG: sleep $secs\n"; + sleep($secs); +} + +# Get a list of roles to install from slack-getroles {{{ +if (not @ARGV) { + my @command = ($opt{'libexec-dir'}.'/slack-getroles', + @{$backend_flags{'getroles'}}); + $opt{verbose} and print STDERR "$PROG: getroles\n"; + ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command' to get a list of roles for this host.\n"; + my ($roles_pid, $roles_fh); + if ($roles_pid = open($roles_fh, "-|")) { + # Parent + } elsif (defined $roles_pid) { + # Child + exec(@command); + die "Could not exec '@command': $!\n"; + } else { + die "Could not fork to run '@command': $!\n"; + } + @roles = split(/\s+/, join(" ", <$roles_fh>)); + unless (close($roles_fh)) { + Slack::check_system_exit(@command); + } +} else { + @roles = @ARGV; +} +# }}} + +# Check role name syntax {{{ +for my $role (@roles) { + # Roles MUST begin with a letter. All else is reserved. + if ($role !~ m/^[a-zA-Z]/) { + die "Role '$role' does not begin with a letter!"; + } +} +# }}} + +$opt{verbose} and print STDERR "$PROG: installing roles: @roles\n"; + +unless ($opt{'no-sync'}) { + # sync all the roles down at once + $opt{verbose} and print STDERR "$PROG: sync @roles\n"; + run_backend('slack-sync', + @{$backend_flags{sync}}, @roles); +} + +ROLE: for my $role (@roles) { + # stage + $opt{verbose} and print STDERR "$PROG: stage files $role\n"; + run_backend('slack-stage', + @{$backend_flags{stage}}, '--subdir=files', $role); + + if ($opt{preview}) { + if ($opt{preview} eq 'simple') { + $opt{verbose} and print STDERR "$PROG: preview $role\n"; + # Here, we run the backend in no-prompt mode. + run_conditional_backend(0, 'slack-rolediff', + @{$backend_flags{preview}}, $role); + # ...and we skip further action in the ROLE after showing the diff. + next ROLE; + } elsif ($opt{preview} eq 'prompt') { + $opt{verbose} and print STDERR "$PROG: preview scripts $role\n"; + # Here, we want to prompt and just do the scripts, since + # we need to run preinstall and fixfiles before doing the files. + run_conditional_backend(1, 'slack-rolediff', + @{$backend_flags{preview}}, '--subdir=scripts', $role); + } else { + # Should get caught in option processing, above + die "Unknown preview mode!\n"; + } + } + + $opt{verbose} and print STDERR "$PROG: stage scripts $role\n"; + run_backend('slack-stage', + @{$backend_flags{stage}}, '--subdir=scripts', $role); + + # preinstall + $opt{verbose} and print STDERR "$PROG: preinstall $role\n"; + run_backend('slack-runscript', + @{$backend_flags{preinstall}}, 'preinstall', $role); + + # fixfiles + $opt{verbose} and print STDERR "$PROG: fixfiles $role\n"; + run_backend('slack-runscript', + @{$backend_flags{fixfiles}}, 'fixfiles', $role); + + # preview files + if ($opt{preview} and $opt{preview} eq 'prompt') { + $opt{verbose} and print STDERR "$PROG: preview files $role\n"; + run_conditional_backend(1, 'slack-rolediff', + @{$backend_flags{preview}}, '--subdir=files', $role); + } + + # installfiles + $opt{verbose} and print STDERR "$PROG: install $role\n"; + run_backend('slack-installfiles', + @{$backend_flags{installfiles}}, $role); + + # postinstall + $opt{verbose} and print STDERR "$PROG: postinstall $role\n"; + run_backend('slack-runscript', + @{$backend_flags{postinstall}}, 'postinstall', $role); +} +exit 0; + +sub run_backend (@) { + my ($backend, @args) = @_; + # If we weren't given an explicit path, prepend the libexec dir + unless ($backend =~ m#^/#) { + $backend = $opt{'libexec-dir'} . '/' . $backend; + } + + # Assemble our command line + my (@command) = ($backend, @args); + ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command'\n"; + unless (system(@command) == 0) { + Slack::check_system_exit(@command); + } +} + +sub run_conditional_backend ($@) { + my ($prompt, $backend, @args) = @_; + # If we weren't given an explicit path, prepend the libexec dir + unless ($backend =~ m#^/#) { + $backend = $opt{'libexec-dir'} . '/' . $backend; + } + + # Assemble our command line + my (@command) = ($backend, @args); + ($opt{verbose} > 2) and print STDERR "$PROG: Calling '@command'\n"; + unless (system(@command) == 0) { + my $exit = Slack::get_system_exit(@command); + + if ($exit == 1) { + # exit 1 means a difference found or something normal that requires + # a prompt before continuing. + if ($prompt) { + exit 1 unless Slack::prompt("Continue? [yN] ") eq 'y'; + } + } else { + # any other non-successful exit is a serious error. + die "'@command' exited $exit"; + } + } +} diff --git a/slack-dist/dist/slack-diff b/slack-dist/dist/slack-diff index 61a1b98..dde4f16 100644 --- a/slack-dist/dist/slack-diff +++ b/slack-dist/dist/slack-diff @@ -1,514 +1,514 @@ -#!/usr/bin/perl -w -# $Id: slack-diff 122 2006-09-27 07:34:32Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2006 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. -# -# This script is a wrapper for diff that gives output about special files -# and file modes. (diff can only compare regular files) - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use Errno; -use File::stat; -use File::Basename; -use File::Find; -use Getopt::Long; -use POSIX qw(SIGPIPE strftime); -use Fcntl qw(:mode); # provides things like S_IFMT that POSIX does not - - -my $VERSION = '0.1'; -(my $PROG = $0) =~ s#.*/##; -my @diff; # diff program to use -my $exit = 0; # our exit code - -sub compare ($$); -sub recursive_compare ($$); -sub filetype_to_string ($;$); -sub compare_files ($$); -sub diff ($$); - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Autoflush on STDOUT -$|=1; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); - -# Default options -my %opt = ( - fakediff => 1, - perms => 1, - 'new-file' => 1, - diff => 'diff', -); - -# Config and option parsing -my $usage = < - $PROG -r - -Options: - -u, -U NUM, --unified=NUM - Tell diff to use unified output format. - --diff PROG - Use this program for diffing, instead of "$opt{diff}" - --fakediff - Make a fake diff for file modes and other things that are not file - contents. Default is on, can be disabled with --nofakediff. - --perms - Care about owner, group, and permissions when doing fakediff. - Default is on, can be disabled with --noperms. - -r, --recursive - Recursively compare directories. - -N, --new-file - Treat missing files as empty. Default is on, can be disabled with - --nonew-file. - --unidirectional-new-file - Treat only missing files in the first directory as empty. - --from-file - Treat arguments as a list of files from which to read filenames to - compare, two lines at a time. - -0, --null - Use NULLs instead of newlines as the separator in --from-file mode - --devnullhack - You have a version of diff that can't deal with -N when not in - recursive mode, so we need to feed it /dev/null instead of the - missing file. Default is on, can be disabled with --nodevnullhack. - --version - Output version info - --help - Output this help text - -Exit codes: - 0 Found no differences - 1 Found a difference - 2 Had a serious error - 3 Found a difference and had a serious error -EOF - -{ - Getopt::Long::Configure ("bundling"); - GetOptions(\%opt, - 'help|h|?', - 'version', - 'null|0', - 'devnullhack', - 'new-file|N', - 'u', - 'unified|U=i', - 'recursive|r', - 'from-file', - 'unidirectional-new-file', - 'fakediff!', - 'perms!', - 'diff=s', - ) or die $usage; - if ($opt{help}) { - print $usage; - exit 0; - } - if ($opt{version}) { - print "$PROG version $VERSION\n"; - exit 0; - } -} - -if ($opt{diff}) { - # We split on spaces here to be useful -- so that people can give - # their diff options. - @diff = split(/\s+/, $opt{diff}); -} else { - die "$PROG: No diff program!\n"; -} - -if ($opt{'u'}) { - push @diff, '-u'; -} elsif ($opt{'unified'}) { - $opt{'u'} = 1; # We use this value later - push @diff, "--unified=$opt{'unified'}"; -} - -if (not $opt{'devnullhack'}) { - push @diff, '-N'; -} - -# usually, sigpipe would be someone quitting their pager, so don't sweat it -$SIG{PIPE} = sub { exit $exit }; - -if ($opt{'from-file'}) { - local $/ = "\0" if $opt{'null'}; - while (my $old = <>) { - my $new = <>; - die "Uneven number of lines in --from-file mode!\n" - if not defined $new; - chomp($old); - chomp($new); - $exit |= compare($old, $new); - } -} else { - die $usage unless $#ARGV == 1; - $exit |= compare($ARGV[0], $ARGV[1]); -} -exit $exit; - -## -# Subroutines - -sub compare ($$) { - my ($old, $new) = @_; - - if ($opt{recursive}) { - return recursive_compare($old, $new); - } else { - return compare_files($old, $new); - } -} - -# compare two directories. We do this by walking down the *new* -# directory, and comparing everything that's there to the stuff in -# the old directory -sub recursive_compare ($$) { - my ($olddir, $newdir) = @_; - my ($retval, $basere, $wanted); - my (%seen); - - $retval = 0; - - if (-d $newdir) { - $basere = qr(^$newdir); - $wanted = sub { - my ($newfile) = $_; - my $oldfile = $newfile; - - $oldfile =~ s#$basere#$olddir#; - $seen{$oldfile} = 1; - $retval |= compare_files($oldfile, $newfile); - }; - - eval { find({ wanted => $wanted , no_chdir => 1}, $newdir) }; - if ($@) { - warn "$PROG: error during find: $@\n"; - $retval |= 2; - } - } - return $retval - if $opt{'unidirectional-new-file'}; - - # If we're not unidirectional, we want to go through the old directory - # and diff any files we didn't see in the newdir. - if (-d $olddir) { - $basere = qr(^$olddir); - $wanted = sub { - my ($oldfile) = $_; - my $newfile; - - return if $seen{$oldfile}; - $newfile = $oldfile; - - $newfile =~ s#$basere#$newdir#; - $retval |= compare_files($oldfile, $newfile); - }; - - eval { find({ wanted => $wanted , no_chdir => 1}, $olddir) }; - if ($@) { - warn "$PROG: error during find: $@\n"; - $retval |= 2; - } - } - return $retval; -} - -# filetype_to_string(mode) -# filetype_to_string(mode, plural) -# -# Takes a mode returned from stat(), returns a noune describing the filetype, -# e.g. "directory", "symlink". -# If the "plural" argument is provided and true, returns the plural form of -# the noun, e.g. "directories", "symlinks". -sub filetype_to_string ($;$) { - my ($mode, $plural) = @_; - - if (S_ISREG($mode)) { - return "regular file".($plural ? "s" : ""); - } elsif (S_ISDIR($mode)) { - return "director".($plural ? "ies" : "y"); - } elsif (S_ISLNK($mode)) { - return "symlink".($plural ? "s" : ""); - } elsif (S_ISBLK($mode)) { - return "block device".($plural ? "s" : ""); - } elsif (S_ISCHR($mode)) { - return "character device".($plural ? "s" : ""); - } elsif (S_ISFIFO($mode)) { - return "fifo".($plural ? "s" : ""); - } elsif (S_ISSOCK($mode)) { - return "socket".($plural ? "s" : ""); - } else { - return "unknown filetype".($plural ? "s" : ""); - } -} - -# compare_files(oldfile, newfile) -# This is the actual diffing routine. It's quite long because we need to -# deal with all sorts of special cases. It will print to STDOUT a -# description of the differences between the two files. For regular files, -# diff(1) will be run to show the differences. -# -# return codes: -# 1 found a difference -# 2 had an error -# 3 found a difference and had an error -sub compare_files ($$) { - my ($oldname, $newname) = @_; - my ($old, $new); # stat buffers - my $return = 0; - - # Get rid of unsightly double slashes - $oldname =~ s#//#/#g; - $newname =~ s#//#/#g; - - eval { $old = lstat($oldname); }; - if (not defined $old and not $!{ENOENT}) { - warn "$PROG: Could not stat $oldname: $!\n"; - return 2; - } - eval { $new = lstat($newname); }; - if (not defined $new and not $!{ENOENT}) { - warn "$PROG: Could not stat $newname: $!\n"; - return 2; - } - # At this point, $old or $new should only be undefined if the - # file does not exist. - - if (defined $old and defined $new) { - if (S_IFMT($old->mode) != S_IFMT($new->mode)) { - if ($opt{fakediff}) { - fakediff('filetype', - $oldname => filetype_to_string($old->mode), - $newname => filetype_to_string($new->mode), - ); - } else { - print "File types differ between ". - filetype_to_string($old->mode)." $oldname and ". - filetype_to_string($new->mode)." $newname\n"; - } - return 1; - } - if ($old->nlink != $new->nlink) { - # In recursive mode, we don't care about link counts in directories, - # as we'll pick that up with what files do and don't exist. - unless ($opt{recursive} and S_ISDIR($old->mode)) { - if ($opt{fakediff}) { - fakediff('nlink', - $oldname => $old->nlink, - $newname => $new->nlink, - ); - } else { - print "Link counts differ between ". - filetype_to_string($old->mode, 1). - " $oldname and $newname\n"; - } - $return = 1; - } - } - if ($old->uid != $new->uid and $opt{perms}) { - if ($opt{fakediff}) { - fakediff('uid', - $oldname => $old->uid, - $newname => $new->uid, - ); - } else { - print "Owner differs between ". - filetype_to_string($old->mode, 1). - " $oldname and $newname\n"; - } - $return = 1; - } - if ($old->gid != $new->gid and $opt{perms}) { - if ($opt{fakediff}) { - fakediff('gid', - $oldname => $old->gid, - $newname => $new->gid, - ); - } else { - print "Group differs between ". - filetype_to_string($old->mode, 1). - " $oldname and $newname\n"; - } - $return = 1; - } - if (S_IMODE($old->mode) != S_IMODE($new->mode) and $opt{perms}) { - if ($opt{fakediff}) { - fakediff('mode', - $oldname => sprintf('%04o', S_IMODE($old->mode)), - $newname => sprintf('%04o', S_IMODE($new->mode)), - ); - } else { - print "Modes differ between ". - filetype_to_string($old->mode, 1). - " $oldname and $newname\n"; - } - $return = 1; - } - - # We don't want to compare anything more about sockets, fifos, or - # directories, once we've checked the permissions and link counts - if (S_ISSOCK($old->mode) or - S_ISFIFO($old->mode) or - S_ISDIR($old->mode)) { - return $return; - } - - # Check device file devs, and that's it for them - if (S_ISCHR($old->mode) or - S_ISBLK($old->mode)) { - if ($old->rdev != $new->rdev) { - if ($opt{fakediff}) { - fakediff('rdev', - $oldname => $old->rdev, - $newname => $new->rdev, - ); - } else { - print "Device numbers differ between ". - filetype_to_string($old->mode, 1). - " $oldname and $newname\n"; - } - $return = 1; - } - return $return; - } - - # Compare the targets of symlinks - if (S_ISLNK($old->mode)) { - my $oldtarget = readlink $oldname - or (warn("$PROG: Could not readlink($oldname): $!\n"), - return $return | 2); - my $newtarget = readlink $newname - or (warn("$PROG: Could not readlink($newname): $!\n"), - return $return | 2); - if ($oldtarget ne $newtarget) { - if ($opt{fakediff}) { - fakediff('target', - $oldname => $oldtarget, - $newname => $newtarget, - ); - } else { - print "Symlink targets differ between $oldname and $newname\n"; - } - $return = 1; - } - return $return; - } - - if (not S_ISREG($old->mode)) { - warn "$PROG: Don't know what to do with file mode $old->mode!\n"; - return 2; - } - } elsif (not defined $old and not defined $new) { - print "Neither $oldname nor $newname exists\n"; - return $return; - } elsif (not defined $old) { - if (not S_ISREG($new->mode) or not $opt{'new-file'}) { - print "Only in ".dirname($newname).": ". - filetype_to_string($new->mode)." ".basename($newname)."\n"; - return 1; - } elsif ($opt{'devnullhack'}) { - $oldname = '/dev/null'; - } - } elsif (not defined $new) { - if (not S_ISREG($old->mode) or not $opt{'new-file'}) { - print "Only in ".dirname($oldname).": ". - filetype_to_string($old->mode)." ".basename($oldname)."\n"; - return 1; - } elsif ($opt{'devnullhack'}) { - $newname = '/dev/null'; - } - } - # They are regular files! We can actually run diff! - return diff($oldname, $newname) | $return; -} - -sub diff ($$) { - my ($oldname, $newname) = @_; - my @command = (@diff, $oldname, $newname); - my $status; - - # If we're not specifying unified diff, we need to print a header - # to indicate what's being diffed. (I'm not sure if this actually would - # work for patch, but it does tell our user what's going on). - # FIXME: We only need to specify this if the files are different - print "@command\n" - if not $opt{u}; - - { - # There is a bug in perl with use warnings FATAL => qw(all) - # that will cause the child process from system() to stick - # around if there is a warning generated. - # Shut off warnings -- we'll catch the error below. - no warnings; - $status = system(@command); - } - return 0 if ($status == 0); - if ($? == -1) { - die "$PROG: failed to execute '@command': $!\n"; - } - if ($? & 128) { - die "$PROG: '@command' dumped core\n"; - } - if (my $sig = $? & 127) { - die "$PROG: '@command' caught sig $sig\n" - unless ($sig == SIGPIPE); - } - if (my $exit = $? >> 8) { - if ($exit == 1) { - return 1; - } else { - die "$PROG: '@command' returned $exit\n"; - } - } - return 0; -} - - -sub fakediff ($$) { - my ($type, $oldname, $oldvalue, $newname, $newvalue) = @_; - - return unless $opt{fakediff}; - my $time = strftime('%F %T.000000000 %z', localtime(0)); - - # We add a suffix onto the filenames to show we're not actually looking - # at file contents. There's no good way to indicate this that's compatible - # with patch, and this is simple enough. - $oldname .= '#~~' . $type; - $newname .= '#~~' . $type; - - if ($opt{u}) { - # fake up a unified diff - print < $newvalue -EOF - } -} +#!/usr/bin/perl -w +# $Id: slack-diff 122 2006-09-27 07:34:32Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2006 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. +# +# This script is a wrapper for diff that gives output about special files +# and file modes. (diff can only compare regular files) + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use Errno; +use File::stat; +use File::Basename; +use File::Find; +use Getopt::Long; +use POSIX qw(SIGPIPE strftime); +use Fcntl qw(:mode); # provides things like S_IFMT that POSIX does not + + +my $VERSION = '0.1'; +(my $PROG = $0) =~ s#.*/##; +my @diff; # diff program to use +my $exit = 0; # our exit code + +sub compare ($$); +sub recursive_compare ($$); +sub filetype_to_string ($;$); +sub compare_files ($$); +sub diff ($$); + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Autoflush on STDOUT +$|=1; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); + +# Default options +my %opt = ( + fakediff => 1, + perms => 1, + 'new-file' => 1, + diff => 'diff', +); + +# Config and option parsing +my $usage = < + $PROG -r + +Options: + -u, -U NUM, --unified=NUM + Tell diff to use unified output format. + --diff PROG + Use this program for diffing, instead of "$opt{diff}" + --fakediff + Make a fake diff for file modes and other things that are not file + contents. Default is on, can be disabled with --nofakediff. + --perms + Care about owner, group, and permissions when doing fakediff. + Default is on, can be disabled with --noperms. + -r, --recursive + Recursively compare directories. + -N, --new-file + Treat missing files as empty. Default is on, can be disabled with + --nonew-file. + --unidirectional-new-file + Treat only missing files in the first directory as empty. + --from-file + Treat arguments as a list of files from which to read filenames to + compare, two lines at a time. + -0, --null + Use NULLs instead of newlines as the separator in --from-file mode + --devnullhack + You have a version of diff that can't deal with -N when not in + recursive mode, so we need to feed it /dev/null instead of the + missing file. Default is on, can be disabled with --nodevnullhack. + --version + Output version info + --help + Output this help text + +Exit codes: + 0 Found no differences + 1 Found a difference + 2 Had a serious error + 3 Found a difference and had a serious error +EOF + +{ + Getopt::Long::Configure ("bundling"); + GetOptions(\%opt, + 'help|h|?', + 'version', + 'null|0', + 'devnullhack', + 'new-file|N', + 'u', + 'unified|U=i', + 'recursive|r', + 'from-file', + 'unidirectional-new-file', + 'fakediff!', + 'perms!', + 'diff=s', + ) or die $usage; + if ($opt{help}) { + print $usage; + exit 0; + } + if ($opt{version}) { + print "$PROG version $VERSION\n"; + exit 0; + } +} + +if ($opt{diff}) { + # We split on spaces here to be useful -- so that people can give + # their diff options. + @diff = split(/\s+/, $opt{diff}); +} else { + die "$PROG: No diff program!\n"; +} + +if ($opt{'u'}) { + push @diff, '-u'; +} elsif ($opt{'unified'}) { + $opt{'u'} = 1; # We use this value later + push @diff, "--unified=$opt{'unified'}"; +} + +if (not $opt{'devnullhack'}) { + push @diff, '-N'; +} + +# usually, sigpipe would be someone quitting their pager, so don't sweat it +$SIG{PIPE} = sub { exit $exit }; + +if ($opt{'from-file'}) { + local $/ = "\0" if $opt{'null'}; + while (my $old = <>) { + my $new = <>; + die "Uneven number of lines in --from-file mode!\n" + if not defined $new; + chomp($old); + chomp($new); + $exit |= compare($old, $new); + } +} else { + die $usage unless $#ARGV == 1; + $exit |= compare($ARGV[0], $ARGV[1]); +} +exit $exit; + +## +# Subroutines + +sub compare ($$) { + my ($old, $new) = @_; + + if ($opt{recursive}) { + return recursive_compare($old, $new); + } else { + return compare_files($old, $new); + } +} + +# compare two directories. We do this by walking down the *new* +# directory, and comparing everything that's there to the stuff in +# the old directory +sub recursive_compare ($$) { + my ($olddir, $newdir) = @_; + my ($retval, $basere, $wanted); + my (%seen); + + $retval = 0; + + if (-d $newdir) { + $basere = qr(^$newdir); + $wanted = sub { + my ($newfile) = $_; + my $oldfile = $newfile; + + $oldfile =~ s#$basere#$olddir#; + $seen{$oldfile} = 1; + $retval |= compare_files($oldfile, $newfile); + }; + + eval { find({ wanted => $wanted , no_chdir => 1}, $newdir) }; + if ($@) { + warn "$PROG: error during find: $@\n"; + $retval |= 2; + } + } + return $retval + if $opt{'unidirectional-new-file'}; + + # If we're not unidirectional, we want to go through the old directory + # and diff any files we didn't see in the newdir. + if (-d $olddir) { + $basere = qr(^$olddir); + $wanted = sub { + my ($oldfile) = $_; + my $newfile; + + return if $seen{$oldfile}; + $newfile = $oldfile; + + $newfile =~ s#$basere#$newdir#; + $retval |= compare_files($oldfile, $newfile); + }; + + eval { find({ wanted => $wanted , no_chdir => 1}, $olddir) }; + if ($@) { + warn "$PROG: error during find: $@\n"; + $retval |= 2; + } + } + return $retval; +} + +# filetype_to_string(mode) +# filetype_to_string(mode, plural) +# +# Takes a mode returned from stat(), returns a noune describing the filetype, +# e.g. "directory", "symlink". +# If the "plural" argument is provided and true, returns the plural form of +# the noun, e.g. "directories", "symlinks". +sub filetype_to_string ($;$) { + my ($mode, $plural) = @_; + + if (S_ISREG($mode)) { + return "regular file".($plural ? "s" : ""); + } elsif (S_ISDIR($mode)) { + return "director".($plural ? "ies" : "y"); + } elsif (S_ISLNK($mode)) { + return "symlink".($plural ? "s" : ""); + } elsif (S_ISBLK($mode)) { + return "block device".($plural ? "s" : ""); + } elsif (S_ISCHR($mode)) { + return "character device".($plural ? "s" : ""); + } elsif (S_ISFIFO($mode)) { + return "fifo".($plural ? "s" : ""); + } elsif (S_ISSOCK($mode)) { + return "socket".($plural ? "s" : ""); + } else { + return "unknown filetype".($plural ? "s" : ""); + } +} + +# compare_files(oldfile, newfile) +# This is the actual diffing routine. It's quite long because we need to +# deal with all sorts of special cases. It will print to STDOUT a +# description of the differences between the two files. For regular files, +# diff(1) will be run to show the differences. +# +# return codes: +# 1 found a difference +# 2 had an error +# 3 found a difference and had an error +sub compare_files ($$) { + my ($oldname, $newname) = @_; + my ($old, $new); # stat buffers + my $return = 0; + + # Get rid of unsightly double slashes + $oldname =~ s#//#/#g; + $newname =~ s#//#/#g; + + eval { $old = lstat($oldname); }; + if (not defined $old and not $!{ENOENT}) { + warn "$PROG: Could not stat $oldname: $!\n"; + return 2; + } + eval { $new = lstat($newname); }; + if (not defined $new and not $!{ENOENT}) { + warn "$PROG: Could not stat $newname: $!\n"; + return 2; + } + # At this point, $old or $new should only be undefined if the + # file does not exist. + + if (defined $old and defined $new) { + if (S_IFMT($old->mode) != S_IFMT($new->mode)) { + if ($opt{fakediff}) { + fakediff('filetype', + $oldname => filetype_to_string($old->mode), + $newname => filetype_to_string($new->mode), + ); + } else { + print "File types differ between ". + filetype_to_string($old->mode)." $oldname and ". + filetype_to_string($new->mode)." $newname\n"; + } + return 1; + } + if ($old->nlink != $new->nlink) { + # In recursive mode, we don't care about link counts in directories, + # as we'll pick that up with what files do and don't exist. + unless ($opt{recursive} and S_ISDIR($old->mode)) { + if ($opt{fakediff}) { + fakediff('nlink', + $oldname => $old->nlink, + $newname => $new->nlink, + ); + } else { + print "Link counts differ between ". + filetype_to_string($old->mode, 1). + " $oldname and $newname\n"; + } + $return = 1; + } + } + if ($old->uid != $new->uid and $opt{perms}) { + if ($opt{fakediff}) { + fakediff('uid', + $oldname => $old->uid, + $newname => $new->uid, + ); + } else { + print "Owner differs between ". + filetype_to_string($old->mode, 1). + " $oldname and $newname\n"; + } + $return = 1; + } + if ($old->gid != $new->gid and $opt{perms}) { + if ($opt{fakediff}) { + fakediff('gid', + $oldname => $old->gid, + $newname => $new->gid, + ); + } else { + print "Group differs between ". + filetype_to_string($old->mode, 1). + " $oldname and $newname\n"; + } + $return = 1; + } + if (S_IMODE($old->mode) != S_IMODE($new->mode) and $opt{perms}) { + if ($opt{fakediff}) { + fakediff('mode', + $oldname => sprintf('%04o', S_IMODE($old->mode)), + $newname => sprintf('%04o', S_IMODE($new->mode)), + ); + } else { + print "Modes differ between ". + filetype_to_string($old->mode, 1). + " $oldname and $newname\n"; + } + $return = 1; + } + + # We don't want to compare anything more about sockets, fifos, or + # directories, once we've checked the permissions and link counts + if (S_ISSOCK($old->mode) or + S_ISFIFO($old->mode) or + S_ISDIR($old->mode)) { + return $return; + } + + # Check device file devs, and that's it for them + if (S_ISCHR($old->mode) or + S_ISBLK($old->mode)) { + if ($old->rdev != $new->rdev) { + if ($opt{fakediff}) { + fakediff('rdev', + $oldname => $old->rdev, + $newname => $new->rdev, + ); + } else { + print "Device numbers differ between ". + filetype_to_string($old->mode, 1). + " $oldname and $newname\n"; + } + $return = 1; + } + return $return; + } + + # Compare the targets of symlinks + if (S_ISLNK($old->mode)) { + my $oldtarget = readlink $oldname + or (warn("$PROG: Could not readlink($oldname): $!\n"), + return $return | 2); + my $newtarget = readlink $newname + or (warn("$PROG: Could not readlink($newname): $!\n"), + return $return | 2); + if ($oldtarget ne $newtarget) { + if ($opt{fakediff}) { + fakediff('target', + $oldname => $oldtarget, + $newname => $newtarget, + ); + } else { + print "Symlink targets differ between $oldname and $newname\n"; + } + $return = 1; + } + return $return; + } + + if (not S_ISREG($old->mode)) { + warn "$PROG: Don't know what to do with file mode $old->mode!\n"; + return 2; + } + } elsif (not defined $old and not defined $new) { + print "Neither $oldname nor $newname exists\n"; + return $return; + } elsif (not defined $old) { + if (not S_ISREG($new->mode) or not $opt{'new-file'}) { + print "Only in ".dirname($newname).": ". + filetype_to_string($new->mode)." ".basename($newname)."\n"; + return 1; + } elsif ($opt{'devnullhack'}) { + $oldname = '/dev/null'; + } + } elsif (not defined $new) { + if (not S_ISREG($old->mode) or not $opt{'new-file'}) { + print "Only in ".dirname($oldname).": ". + filetype_to_string($old->mode)." ".basename($oldname)."\n"; + return 1; + } elsif ($opt{'devnullhack'}) { + $newname = '/dev/null'; + } + } + # They are regular files! We can actually run diff! + return diff($oldname, $newname) | $return; +} + +sub diff ($$) { + my ($oldname, $newname) = @_; + my @command = (@diff, $oldname, $newname); + my $status; + + # If we're not specifying unified diff, we need to print a header + # to indicate what's being diffed. (I'm not sure if this actually would + # work for patch, but it does tell our user what's going on). + # FIXME: We only need to specify this if the files are different + print "@command\n" + if not $opt{u}; + + { + # There is a bug in perl with use warnings FATAL => qw(all) + # that will cause the child process from system() to stick + # around if there is a warning generated. + # Shut off warnings -- we'll catch the error below. + no warnings; + $status = system(@command); + } + return 0 if ($status == 0); + if ($? == -1) { + die "$PROG: failed to execute '@command': $!\n"; + } + if ($? & 128) { + die "$PROG: '@command' dumped core\n"; + } + if (my $sig = $? & 127) { + die "$PROG: '@command' caught sig $sig\n" + unless ($sig == SIGPIPE); + } + if (my $exit = $? >> 8) { + if ($exit == 1) { + return 1; + } else { + die "$PROG: '@command' returned $exit\n"; + } + } + return 0; +} + + +sub fakediff ($$) { + my ($type, $oldname, $oldvalue, $newname, $newvalue) = @_; + + return unless $opt{fakediff}; + my $time = strftime('%F %T.000000000 %z', localtime(0)); + + # We add a suffix onto the filenames to show we're not actually looking + # at file contents. There's no good way to indicate this that's compatible + # with patch, and this is simple enough. + $oldname .= '#~~' . $type; + $newname .= '#~~' . $type; + + if ($opt{u}) { + # fake up a unified diff + print < $newvalue +EOF + } +} diff --git a/slack-dist/dist/slack-getroles b/slack-dist/dist/slack-getroles index 85f814b..77eba20 100644 --- a/slack-dist/dist/slack-getroles +++ b/slack-dist/dist/slack-getroles @@ -1,161 +1,161 @@ -#!/usr/bin/perl -w -# $Id: slack-getroles 180 2008-01-19 08:26:19Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2008 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. - -# This script is in charge of copying files from the (possibly remote) -# master directory to a local cache, using rsync - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use File::Path; - -use constant LIB_DIR => '/usr/lib/slack'; -use lib LIB_DIR; -use Slack; - -my @rsync = ('rsync', - '--links', - '--times', - ); - -(my $PROG = $0) =~ s#.*/##; - -sub sync_list (); - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Get out of wherever (possibly NFS-mounted) we were -chdir("/") - or die "Could not chdir /: $!"; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); - -######################################## -# Config and option parsing {{{ -my $usage = Slack::default_usage("$PROG [options]"); -$usage .= < \%opt, - command_line_options => [ - 'role-list=s', - 'remote-role-list', - ], - required_options => [ qw(role-list hostname) ], - usage => $usage, -); - -# Prepare for backups -if ($opt{backup} and $opt{'backup-dir'}) { - # Make sure backup directory exists - unless (-d $opt{'backup-dir'}) { - ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; - if (not $opt{'dry-run'}) { - eval { mkpath($opt{'backup-dir'}); }; - die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; - } - } - push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); -} -# Pass options along to rsync -if ($opt{'dry-run'}) { - push @rsync, '--dry-run'; -} -# Pass options along to rsync -if ($opt{'verbose'} > 1) { - push @rsync, '--verbose'; -} -# }}} - -# See if role-list is actually relative to source, and pre-pend source -# if need be. -unless ($opt{'role-list'} =~ m#^/# or - $opt{'role-list'} =~ m#^\./# or - $opt{'role-list'} =~ m#^[\w@\.-]+:#) { - if (not defined $opt{source}) { - die "Relative path to role-list given, but source not defined!\n\n$usage\n"; - } - $opt{'role-list'} = $opt{source} . '/' . $opt{'role-list'}; -} - -# auto-detect remote role list -if ($opt{'role-list'} =~ m#^[\w@\.-]+:#) { - $opt{'remote-role-list'} = 1; -} - -# Copy a remote list locally -if ($opt{'remote-role-list'}) { - # We need a cache directory if the role list is not local - if (not defined $opt{cache}) { - die "Remote path to role-list given, but cache not defined!\n\n$usage\n"; - } - # Look at source type, and add options if necessary - if ($opt{'rsh'} or $opt{'role-list'} =~ m/^[\w@\.-]+::/) { - # This is tunnelled rsync, and so needs an extra option - if ($opt{'rsh'}) { - push @rsync, '-e', $opt{'rsh'}; - } else { - push @rsync, '-e', 'ssh'; - } - } - sync_list(); -} - -# Read in the roles list -my @roles = (); -my $host_found = 0; -($opt{verbose} > 0) and print STDERR "$PROG: Reading '$opt{'role-list'}'\n"; -open(ROLES, "<", $opt{'role-list'}) - or die "Could not open '$opt{'role-list'}' for reading: $!\n"; -while() { - s/#.*//; # Strip comments - chomp; - if (s/^$opt{hostname}:\s*//) { - $host_found++; - push @roles, split(); - } -} -close(ROLES) - or die "Could not close '$opt{'role-list'}': $!\n"; -if (not $host_found) { - die "Host '$opt{hostname}' not found in '$opt{'role-list'}'!\n"; -} -print join("\n", @roles), "\n"; -exit 0; - -sub sync_list () { - my $source = $opt{'role-list'}; - my $destination = $opt{cache} . "/_role_list"; - unless (-d $opt{cache}) { - eval { mkpath($opt{cache}); }; - die "Could not mkpath '$opt{cache}': $@\n" if $@; - } - # All this to run an rsync command - my @command = (@rsync, $source, $destination); - ($opt{verbose} > 0) and print STDERR "$PROG: Calling '@command'\n"; - Slack::wrap_rsync(@command); - $opt{'role-list'} = $destination; -} - +#!/usr/bin/perl -w +# $Id: slack-getroles 180 2008-01-19 08:26:19Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2008 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. + +# This script is in charge of copying files from the (possibly remote) +# master directory to a local cache, using rsync + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use File::Path; + +use constant LIB_DIR => '/usr/lib/slack'; +use lib LIB_DIR; +use Slack; + +my @rsync = ('rsync', + '--links', + '--times', + ); + +(my $PROG = $0) =~ s#.*/##; + +sub sync_list (); + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Get out of wherever (possibly NFS-mounted) we were +chdir("/") + or die "Could not chdir /: $!"; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); + +######################################## +# Config and option parsing {{{ +my $usage = Slack::default_usage("$PROG [options]"); +$usage .= < \%opt, + command_line_options => [ + 'role-list=s', + 'remote-role-list', + ], + required_options => [ qw(role-list hostname) ], + usage => $usage, +); + +# Prepare for backups +if ($opt{backup} and $opt{'backup-dir'}) { + # Make sure backup directory exists + unless (-d $opt{'backup-dir'}) { + ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; + if (not $opt{'dry-run'}) { + eval { mkpath($opt{'backup-dir'}); }; + die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; + } + } + push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); +} +# Pass options along to rsync +if ($opt{'dry-run'}) { + push @rsync, '--dry-run'; +} +# Pass options along to rsync +if ($opt{'verbose'} > 1) { + push @rsync, '--verbose'; +} +# }}} + +# See if role-list is actually relative to source, and pre-pend source +# if need be. +unless ($opt{'role-list'} =~ m#^/# or + $opt{'role-list'} =~ m#^\./# or + $opt{'role-list'} =~ m#^[\w@\.-]+:#) { + if (not defined $opt{source}) { + die "Relative path to role-list given, but source not defined!\n\n$usage\n"; + } + $opt{'role-list'} = $opt{source} . '/' . $opt{'role-list'}; +} + +# auto-detect remote role list +if ($opt{'role-list'} =~ m#^[\w@\.-]+:#) { + $opt{'remote-role-list'} = 1; +} + +# Copy a remote list locally +if ($opt{'remote-role-list'}) { + # We need a cache directory if the role list is not local + if (not defined $opt{cache}) { + die "Remote path to role-list given, but cache not defined!\n\n$usage\n"; + } + # Look at source type, and add options if necessary + if ($opt{'rsh'} or $opt{'role-list'} =~ m/^[\w@\.-]+::/) { + # This is tunnelled rsync, and so needs an extra option + if ($opt{'rsh'}) { + push @rsync, '-e', $opt{'rsh'}; + } else { + push @rsync, '-e', 'ssh'; + } + } + sync_list(); +} + +# Read in the roles list +my @roles = (); +my $host_found = 0; +($opt{verbose} > 0) and print STDERR "$PROG: Reading '$opt{'role-list'}'\n"; +open(ROLES, "<", $opt{'role-list'}) + or die "Could not open '$opt{'role-list'}' for reading: $!\n"; +while() { + s/#.*//; # Strip comments + chomp; + if (s/^$opt{hostname}:\s*//) { + $host_found++; + push @roles, split(); + } +} +close(ROLES) + or die "Could not close '$opt{'role-list'}': $!\n"; +if (not $host_found) { + die "Host '$opt{hostname}' not found in '$opt{'role-list'}'!\n"; +} +print join("\n", @roles), "\n"; +exit 0; + +sub sync_list () { + my $source = $opt{'role-list'}; + my $destination = $opt{cache} . "/_role_list"; + unless (-d $opt{cache}) { + eval { mkpath($opt{cache}); }; + die "Could not mkpath '$opt{cache}': $@\n" if $@; + } + # All this to run an rsync command + my @command = (@rsync, $source, $destination); + ($opt{verbose} > 0) and print STDERR "$PROG: Calling '@command'\n"; + Slack::wrap_rsync(@command); + $opt{'role-list'} = $destination; +} + diff --git a/slack-dist/dist/slack-installfiles b/slack-dist/dist/slack-installfiles index 4cc3bc3..65d1e22 100644 --- a/slack-dist/dist/slack-installfiles +++ b/slack-dist/dist/slack-installfiles @@ -1,149 +1,149 @@ -#!/usr/bin/perl -w -# $Id: slack-installfiles 180 2008-01-19 08:26:19Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2008 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. -# -# This script is in charge of copying files from the local stage to the root -# of the local filesystem - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use File::Path; - -use constant LIB_DIR => '/usr/lib/slack'; -use lib LIB_DIR; -use Slack; - -my @rsync = ('rsync', - '--relative', - '--times', - '--perms', - '--group', - '--owner', - '--links', - '--devices', - '--sparse', - '--no-implied-dirs', # SO GOOD! - '--files-from=-', - '--from0', - ); - -(my $PROG = $0) =~ s#.*/##; - -sub install_files ($); - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Get out of wherever (possibly NFS-mounted) we were -chdir("/") - or die "Could not chdir /: $!"; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); - -######################################## -# Config and option parsing {{{ -my $usage = Slack::default_usage("$PROG [options] [...]"); -# Option defaults -my %opt = (); -Slack::get_options( - opthash => \%opt, - usage => $usage, - required_options => [ qw(root stage) ], -); -# }}} - -# Arguments are required -die "No roles given!\n\n$usage" unless @ARGV; - -unless (-d $opt{root}) { - if (not $opt{'dry-run'}) { - eval { - mkpath($opt{root}); - # We have a tight umask, and a root of mode 0700 would be undesirable - # in most cases. - chmod(0755, $opt{root}); - }; - die "Could not mkpath destination directory '$opt{root}': $@\n" if $@; - } - warn "WARNING[$PROG]: Created destination directory '".$opt{root}."'\n"; -} - -# Prepare for backups -if ($opt{backup} and $opt{'backup-dir'}) { - # Make sure backup directory exists - unless (-d $opt{'backup-dir'}) { - ($opt{verbose} > 0) and print STDERR "$PROG: Creating backup directory '$opt{'backup-dir'}'\n"; - if (not $opt{'dry-run'}) { - eval { mkpath($opt{'backup-dir'}); }; - die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; - } - } - push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); -} -# Pass options along to rsync -if ($opt{'dry-run'}) { - push @rsync, '--dry-run'; -} -if ($opt{'verbose'} > 1) { - push @rsync, '--verbose'; -} - -# copy over the new files -for my $role (@ARGV) { - install_files($role); -} -exit 0; - -# This subroutine takes care of actually installing the files for a role -sub install_files ($) { - my ($role) = @_; - # final / is important for rsync - my $source = $opt{stage} . "/roles/" . $role . "/files/"; - my $destination = $opt{root} . "/"; - my @command = (@rsync, $source, $destination); - - if (not -d $source) { - ($opt{verbose} > 0) and - print STDERR "$PROG: No files to install -- '$source' does not exist\n"; - return; - } - - # Try to give some sensible message here - if ($opt{verbose} > 0) { - if ($opt{'dry-run'}) { - print STDERR "$PROG: Dry-run syncing '$source' to '$destination'\n"; - } else { - print STDERR "$PROG: Syncing '$source' to '$destination'\n"; - } - } - - my ($fh) = Slack::wrap_rsync_fh(@command); - - select((select($fh), $|=1)[0]); # Turn on autoflush - - my $callback = sub { - my ($file) = @_; - ($file =~ s#^$source##) - or die "sub failed: $source|$file"; - print $fh "$file\0"; - }; - - # This will print files to be synced to the $fh - Slack::find_files_to_install($source, $destination, $callback); - - # Close fh, waitpid, and check return value - unless (close($fh)) { - Slack::check_system_exit(@command); - } -} +#!/usr/bin/perl -w +# $Id: slack-installfiles 180 2008-01-19 08:26:19Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2008 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. +# +# This script is in charge of copying files from the local stage to the root +# of the local filesystem + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use File::Path; + +use constant LIB_DIR => '/usr/lib/slack'; +use lib LIB_DIR; +use Slack; + +my @rsync = ('rsync', + '--relative', + '--times', + '--perms', + '--group', + '--owner', + '--links', + '--devices', + '--sparse', + '--no-implied-dirs', # SO GOOD! + '--files-from=-', + '--from0', + ); + +(my $PROG = $0) =~ s#.*/##; + +sub install_files ($); + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Get out of wherever (possibly NFS-mounted) we were +chdir("/") + or die "Could not chdir /: $!"; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); + +######################################## +# Config and option parsing {{{ +my $usage = Slack::default_usage("$PROG [options] [...]"); +# Option defaults +my %opt = (); +Slack::get_options( + opthash => \%opt, + usage => $usage, + required_options => [ qw(root stage) ], +); +# }}} + +# Arguments are required +die "No roles given!\n\n$usage" unless @ARGV; + +unless (-d $opt{root}) { + if (not $opt{'dry-run'}) { + eval { + mkpath($opt{root}); + # We have a tight umask, and a root of mode 0700 would be undesirable + # in most cases. + chmod(0755, $opt{root}); + }; + die "Could not mkpath destination directory '$opt{root}': $@\n" if $@; + } + warn "WARNING[$PROG]: Created destination directory '".$opt{root}."'\n"; +} + +# Prepare for backups +if ($opt{backup} and $opt{'backup-dir'}) { + # Make sure backup directory exists + unless (-d $opt{'backup-dir'}) { + ($opt{verbose} > 0) and print STDERR "$PROG: Creating backup directory '$opt{'backup-dir'}'\n"; + if (not $opt{'dry-run'}) { + eval { mkpath($opt{'backup-dir'}); }; + die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; + } + } + push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); +} +# Pass options along to rsync +if ($opt{'dry-run'}) { + push @rsync, '--dry-run'; +} +if ($opt{'verbose'} > 1) { + push @rsync, '--verbose'; +} + +# copy over the new files +for my $role (@ARGV) { + install_files($role); +} +exit 0; + +# This subroutine takes care of actually installing the files for a role +sub install_files ($) { + my ($role) = @_; + # final / is important for rsync + my $source = $opt{stage} . "/roles/" . $role . "/files/"; + my $destination = $opt{root} . "/"; + my @command = (@rsync, $source, $destination); + + if (not -d $source) { + ($opt{verbose} > 0) and + print STDERR "$PROG: No files to install -- '$source' does not exist\n"; + return; + } + + # Try to give some sensible message here + if ($opt{verbose} > 0) { + if ($opt{'dry-run'}) { + print STDERR "$PROG: Dry-run syncing '$source' to '$destination'\n"; + } else { + print STDERR "$PROG: Syncing '$source' to '$destination'\n"; + } + } + + my ($fh) = Slack::wrap_rsync_fh(@command); + + select((select($fh), $|=1)[0]); # Turn on autoflush + + my $callback = sub { + my ($file) = @_; + ($file =~ s#^$source##) + or die "sub failed: $source|$file"; + print $fh "$file\0"; + }; + + # This will print files to be synced to the $fh + Slack::find_files_to_install($source, $destination, $callback); + + # Close fh, waitpid, and check return value + unless (close($fh)) { + Slack::check_system_exit(@command); + } +} diff --git a/slack-dist/dist/slack-rolediff b/slack-dist/dist/slack-rolediff index 35a2619..92def93 100644 --- a/slack-dist/dist/slack-rolediff +++ b/slack-dist/dist/slack-rolediff @@ -1,146 +1,146 @@ -#!/usr/bin/perl -w -# $Id: slack-rolediff 125 2006-09-27 07:50:07Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2006 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. -# -# This script provides a preview of scripts or files about to be installed. -# Basically, it calls diff -- its smarts are in knowing where things are. - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use File::Path; -use File::Find; - -use constant LIB_DIR => '/usr/lib/slack'; -use lib LIB_DIR; -use Slack; - -my @diff = ('slack-diff', - '-uN', - ); - -# directories to compare -my %subdir = ( - files => 1, - scripts => 1, -); - -(my $PROG = $0) =~ s#.*/##; - -sub diff ($$;@); - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Get out of wherever (possibly NFS-mounted) we were -chdir("/") - or die "Could not chdir /: $!"; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); - -######################################## -# Config and option parsing {{{ -my $usage = Slack::default_usage("$PROG [options] [...]"); -$usage .= < \%opt, - command_line_options => [ - 'subdir=s', - 'diff=s', - ], - usage => $usage, - required_options => [ qw(cache stage root) ], -); - -# Arguments are required -die "No roles given!\n\n$usage" unless @ARGV; - -# We only allow certain values for this option -if ($opt{subdir}) { - unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') { - die "--subdir option must be 'files' or 'scripts'\n\n$usage"; - } - # Only do this subdir - %subdir = ( $opt{subdir} => 1 ); -} - -# Let people override our diff. Split on spaces so they can pass args. -if ($opt{diff}) { - @diff = split(/\s+/, $opt{diff}); -} - -# }}} - -my $exit = 0; -# Do the diffs -for my $full_role (@ARGV) { - # Split the full role (e.g. google.foogle.woogle) into components - my @role = split(/\./, $full_role); - - if ($subdir{scripts}) { - # Then we compare the cache vs the stage - my $old = $opt{stage} . "/roles/" . $full_role . "/scripts"; - my $new = $opt{cache} . "/roles/" . $role[0] . "/scripts"; - # For scripts, we don't care so much about mode and owner (since those are - # inherited in the CACHE from the SOURCE), so --noperms. - $exit |= diff($old, $new, '--noperms'); - } - - if ($subdir{files}) { - # Then we compare the stage vs the root - my $old = $opt{root}; - my $new = $opt{stage} . "/roles/" . $full_role . "/files"; - # For files, we don't care about files that exist in $old but not $new - $exit |= diff($old, $new, '--unidirectional-new-file'); - } -} -exit $exit; - -sub diff ($$;@) { - my ($old, $new, @options) = @_; - - my @command = (@diff, @options); - - # return if there's nothing to do - return 0 if (not -d $old and not -d $new); - - ($opt{verbose} > 0) and print STDERR "$PROG: Previewing with '@command'\n"; - - my $return = 0; - my $callback = sub { - my ($new_file) = @_; - my $old_file = $new_file; - ($old_file =~ s#^$new#$old#) - or die "sub failed: $new|$new_file"; - if (system(@command, $old_file, $new_file) != 0) { - $return |= Slack::get_system_exit(@command); - } - }; - - # We have to use this function, rather than recursive mode for slack-diff, - # because otherwise we'll print a bunch of bogus stuff about directories - # that exist in $ROOT and therefore aren't being synced. - Slack::find_files_to_install($new, $old, $callback); - - return $return; -} +#!/usr/bin/perl -w +# $Id: slack-rolediff 125 2006-09-27 07:50:07Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2006 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. +# +# This script provides a preview of scripts or files about to be installed. +# Basically, it calls diff -- its smarts are in knowing where things are. + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use File::Path; +use File::Find; + +use constant LIB_DIR => '/usr/lib/slack'; +use lib LIB_DIR; +use Slack; + +my @diff = ('slack-diff', + '-uN', + ); + +# directories to compare +my %subdir = ( + files => 1, + scripts => 1, +); + +(my $PROG = $0) =~ s#.*/##; + +sub diff ($$;@); + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Get out of wherever (possibly NFS-mounted) we were +chdir("/") + or die "Could not chdir /: $!"; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); + +######################################## +# Config and option parsing {{{ +my $usage = Slack::default_usage("$PROG [options] [...]"); +$usage .= < \%opt, + command_line_options => [ + 'subdir=s', + 'diff=s', + ], + usage => $usage, + required_options => [ qw(cache stage root) ], +); + +# Arguments are required +die "No roles given!\n\n$usage" unless @ARGV; + +# We only allow certain values for this option +if ($opt{subdir}) { + unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') { + die "--subdir option must be 'files' or 'scripts'\n\n$usage"; + } + # Only do this subdir + %subdir = ( $opt{subdir} => 1 ); +} + +# Let people override our diff. Split on spaces so they can pass args. +if ($opt{diff}) { + @diff = split(/\s+/, $opt{diff}); +} + +# }}} + +my $exit = 0; +# Do the diffs +for my $full_role (@ARGV) { + # Split the full role (e.g. google.foogle.woogle) into components + my @role = split(/\./, $full_role); + + if ($subdir{scripts}) { + # Then we compare the cache vs the stage + my $old = $opt{stage} . "/roles/" . $full_role . "/scripts"; + my $new = $opt{cache} . "/roles/" . $role[0] . "/scripts"; + # For scripts, we don't care so much about mode and owner (since those are + # inherited in the CACHE from the SOURCE), so --noperms. + $exit |= diff($old, $new, '--noperms'); + } + + if ($subdir{files}) { + # Then we compare the stage vs the root + my $old = $opt{root}; + my $new = $opt{stage} . "/roles/" . $full_role . "/files"; + # For files, we don't care about files that exist in $old but not $new + $exit |= diff($old, $new, '--unidirectional-new-file'); + } +} +exit $exit; + +sub diff ($$;@) { + my ($old, $new, @options) = @_; + + my @command = (@diff, @options); + + # return if there's nothing to do + return 0 if (not -d $old and not -d $new); + + ($opt{verbose} > 0) and print STDERR "$PROG: Previewing with '@command'\n"; + + my $return = 0; + my $callback = sub { + my ($new_file) = @_; + my $old_file = $new_file; + ($old_file =~ s#^$new#$old#) + or die "sub failed: $new|$new_file"; + if (system(@command, $old_file, $new_file) != 0) { + $return |= Slack::get_system_exit(@command); + } + }; + + # We have to use this function, rather than recursive mode for slack-diff, + # because otherwise we'll print a bunch of bogus stuff about directories + # that exist in $ROOT and therefore aren't being synced. + Slack::find_files_to_install($new, $old, $callback); + + return $return; +} diff --git a/slack-dist/dist/slack-runscript b/slack-dist/dist/slack-runscript index 963a943..05790c4 100644 --- a/slack-dist/dist/slack-runscript +++ b/slack-dist/dist/slack-runscript @@ -1,111 +1,111 @@ -#!/usr/bin/perl -w -# $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2006 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. -# -# This script is in charge of running scripts out of the local stage - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use File::Path; -use File::Find; - -use constant LIB_DIR => '/usr/lib/slack'; -use lib LIB_DIR; -use Slack; - -# Export these options to the environment of the script -my @export_options = qw(root stage hostname verbose); - -(my $PROG = $0) =~ s#.*/##; - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); -# Get out of wherever (possibly NFS-mounted) we were -chdir('/') - or die "Could not chdir '/': $!"; - -######################################## -# Config and option parsing {{{ -my $usage = Slack::default_usage("$PROG [options] [...]"); -# Option defaults -my %opt = (); -Slack::get_options( - opthash => \%opt, - usage => $usage, - required_options => \@export_options, -); - -my $action = shift || die "No script to run!\n\n$usage"; -# Arguments are required -die "No roles given!\n\n$usage" unless @ARGV; - -# }}} - -# Start with a clean environment -%ENV = ( - PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', -); -# Export certain variables to the environment. These are guaranteed to -# be set because we require them in get_options above. -for my $option (@export_options) { - my $env_var = $option; - $env_var =~ tr/a-z-/A-Z_/; - $ENV{$env_var} = $opt{$option}; -} -# We want to decrement the verbose value for the child if it's set. -$ENV{VERBOSE}-- if $ENV{VERBOSE}; - -# Run the script for each role given, if it exists and is executable -for my $role (@ARGV) { - my $script_to_run = "$opt{stage}/roles/$role/scripts/$action"; - unless (-x $script_to_run) { - if (-e _) { - # A helpful warning - warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n"; - } elsif ($opt{verbose} > 0) { - print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n"; - } - next; - } - my $dir; - if ($action eq 'fixfiles') { - $dir = "$opt{stage}/roles/$role/files"; - } else { - $dir = "$opt{stage}/roles/$role/scripts"; - } - my @command = ($script_to_run , $role); - - # It's OK to chdir even if we're not going to run the script. - # Might as well see if it works. - chdir($dir) - or die "Could not chdir '$dir': $!\n"; - if ($opt{'dry-run'}) { - ($opt{verbose} > 0) - and print STDERR "$PROG: Not calling '@command' in '$dir' ". - "because --dry-run specified.\n"; - } else { - ($opt{verbose} > 0) - and print STDERR "$PROG: Calling '@command' in '$dir'.\n"; - unless (system("script /root/slackLog -a -f -c @command") == 0) { - Slack::check_system_exit(@command); - } - } - chdir('/') - or die "Could not chdir '/': $!\n" -} -exit 0; - +#!/usr/bin/perl -w +# $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2006 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. +# +# This script is in charge of running scripts out of the local stage + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use File::Path; +use File::Find; + +use constant LIB_DIR => '/usr/lib/slack'; +use lib LIB_DIR; +use Slack; + +# Export these options to the environment of the script +my @export_options = qw(root stage hostname verbose); + +(my $PROG = $0) =~ s#.*/##; + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); +# Get out of wherever (possibly NFS-mounted) we were +chdir('/') + or die "Could not chdir '/': $!"; + +######################################## +# Config and option parsing {{{ +my $usage = Slack::default_usage("$PROG [options] [...]"); +# Option defaults +my %opt = (); +Slack::get_options( + opthash => \%opt, + usage => $usage, + required_options => \@export_options, +); + +my $action = shift || die "No script to run!\n\n$usage"; +# Arguments are required +die "No roles given!\n\n$usage" unless @ARGV; + +# }}} + +# Start with a clean environment +%ENV = ( + PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', +); +# Export certain variables to the environment. These are guaranteed to +# be set because we require them in get_options above. +for my $option (@export_options) { + my $env_var = $option; + $env_var =~ tr/a-z-/A-Z_/; + $ENV{$env_var} = $opt{$option}; +} +# We want to decrement the verbose value for the child if it's set. +$ENV{VERBOSE}-- if $ENV{VERBOSE}; + +# Run the script for each role given, if it exists and is executable +for my $role (@ARGV) { + my $script_to_run = "$opt{stage}/roles/$role/scripts/$action"; + unless (-x $script_to_run) { + if (-e _) { + # A helpful warning + warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n"; + } elsif ($opt{verbose} > 0) { + print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n"; + } + next; + } + my $dir; + if ($action eq 'fixfiles') { + $dir = "$opt{stage}/roles/$role/files"; + } else { + $dir = "$opt{stage}/roles/$role/scripts"; + } + my @command = ($script_to_run , $role); + + # It's OK to chdir even if we're not going to run the script. + # Might as well see if it works. + chdir($dir) + or die "Could not chdir '$dir': $!\n"; + if ($opt{'dry-run'}) { + ($opt{verbose} > 0) + and print STDERR "$PROG: Not calling '@command' in '$dir' ". + "because --dry-run specified.\n"; + } else { + ($opt{verbose} > 0) + and print STDERR "$PROG: Calling '@command' in '$dir'.\n"; + unless (system("script /root/slackLog -a -f -c @command") == 0) { + Slack::check_system_exit(@command); + } + } + chdir('/') + or die "Could not chdir '/': $!\n" +} +exit 0; + diff --git a/slack-dist/dist/slack-runscript.orig b/slack-dist/dist/slack-runscript.orig index 710bd0b..5125a83 100644 --- a/slack-dist/dist/slack-runscript.orig +++ b/slack-dist/dist/slack-runscript.orig @@ -1,111 +1,111 @@ -#!/usr/bin/perl -w -# $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2006 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. -# -# This script is in charge of running scripts out of the local stage - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use File::Path; -use File::Find; - -use constant LIB_DIR => '/usr/lib/slack'; -use lib LIB_DIR; -use Slack; - -# Export these options to the environment of the script -my @export_options = qw(root stage hostname verbose); - -(my $PROG = $0) =~ s#.*/##; - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); -# Get out of wherever (possibly NFS-mounted) we were -chdir('/') - or die "Could not chdir '/': $!"; - -######################################## -# Config and option parsing {{{ -my $usage = Slack::default_usage("$PROG [options] [...]"); -# Option defaults -my %opt = (); -Slack::get_options( - opthash => \%opt, - usage => $usage, - required_options => \@export_options, -); - -my $action = shift || die "No script to run!\n\n$usage"; -# Arguments are required -die "No roles given!\n\n$usage" unless @ARGV; - -# }}} - -# Start with a clean environment -%ENV = ( - PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', -); -# Export certain variables to the environment. These are guaranteed to -# be set because we require them in get_options above. -for my $option (@export_options) { - my $env_var = $option; - $env_var =~ tr/a-z-/A-Z_/; - $ENV{$env_var} = $opt{$option}; -} -# We want to decrement the verbose value for the child if it's set. -$ENV{VERBOSE}-- if $ENV{VERBOSE}; - -# Run the script for each role given, if it exists and is executable -for my $role (@ARGV) { - my $script_to_run = "$opt{stage}/roles/$role/scripts/$action"; - unless (-x $script_to_run) { - if (-e _) { - # A helpful warning - warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n"; - } elsif ($opt{verbose} > 0) { - print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n"; - } - next; - } - my $dir; - if ($action eq 'fixfiles') { - $dir = "$opt{stage}/roles/$role/files"; - } else { - $dir = "$opt{stage}/roles/$role/scripts"; - } - my @command = ($script_to_run, $role); - - # It's OK to chdir even if we're not going to run the script. - # Might as well see if it works. - chdir($dir) - or die "Could not chdir '$dir': $!\n"; - if ($opt{'dry-run'}) { - ($opt{verbose} > 0) - and print STDERR "$PROG: Not calling '@command' in '$dir' ". - "because --dry-run specified.\n"; - } else { - ($opt{verbose} > 0) - and print STDERR "$PROG: Calling '@command' in '$dir'.\n"; - unless (system(@command) == 0) { - Slack::check_system_exit(@command); - } - } - chdir('/') - or die "Could not chdir '/': $!\n" -} -exit 0; - +#!/usr/bin/perl -w +# $Id: slack-runscript 118 2006-09-25 18:35:17Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2006 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. +# +# This script is in charge of running scripts out of the local stage + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use File::Path; +use File::Find; + +use constant LIB_DIR => '/usr/lib/slack'; +use lib LIB_DIR; +use Slack; + +# Export these options to the environment of the script +my @export_options = qw(root stage hostname verbose); + +(my $PROG = $0) =~ s#.*/##; + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); +# Get out of wherever (possibly NFS-mounted) we were +chdir('/') + or die "Could not chdir '/': $!"; + +######################################## +# Config and option parsing {{{ +my $usage = Slack::default_usage("$PROG [options] [...]"); +# Option defaults +my %opt = (); +Slack::get_options( + opthash => \%opt, + usage => $usage, + required_options => \@export_options, +); + +my $action = shift || die "No script to run!\n\n$usage"; +# Arguments are required +die "No roles given!\n\n$usage" unless @ARGV; + +# }}} + +# Start with a clean environment +%ENV = ( + PATH => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', +); +# Export certain variables to the environment. These are guaranteed to +# be set because we require them in get_options above. +for my $option (@export_options) { + my $env_var = $option; + $env_var =~ tr/a-z-/A-Z_/; + $ENV{$env_var} = $opt{$option}; +} +# We want to decrement the verbose value for the child if it's set. +$ENV{VERBOSE}-- if $ENV{VERBOSE}; + +# Run the script for each role given, if it exists and is executable +for my $role (@ARGV) { + my $script_to_run = "$opt{stage}/roles/$role/scripts/$action"; + unless (-x $script_to_run) { + if (-e _) { + # A helpful warning + warn "WARNING[$PROG]: Skipping '$script_to_run' because it's not executable\n"; + } elsif ($opt{verbose} > 0) { + print STDERR "$PROG: Skipping '$script_to_run' because it doesn't exist\n"; + } + next; + } + my $dir; + if ($action eq 'fixfiles') { + $dir = "$opt{stage}/roles/$role/files"; + } else { + $dir = "$opt{stage}/roles/$role/scripts"; + } + my @command = ($script_to_run, $role); + + # It's OK to chdir even if we're not going to run the script. + # Might as well see if it works. + chdir($dir) + or die "Could not chdir '$dir': $!\n"; + if ($opt{'dry-run'}) { + ($opt{verbose} > 0) + and print STDERR "$PROG: Not calling '@command' in '$dir' ". + "because --dry-run specified.\n"; + } else { + ($opt{verbose} > 0) + and print STDERR "$PROG: Calling '@command' in '$dir'.\n"; + unless (system(@command) == 0) { + Slack::check_system_exit(@command); + } + } + chdir('/') + or die "Could not chdir '/': $!\n" +} +exit 0; + diff --git a/slack-dist/dist/slack-stage b/slack-dist/dist/slack-stage index 6ba533f..3833736 100644 --- a/slack-dist/dist/slack-stage +++ b/slack-dist/dist/slack-stage @@ -1,278 +1,278 @@ -#!/usr/bin/perl -w -# $Id: slack-stage 180 2008-01-19 08:26:19Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2008 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. -# -# This script is in charge of copying files from the local cache -# directory to the local stage, building a unified single tree onstage -# from the multiple trees that are the role + subroles in the cache - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use File::Path; -use File::Find; - -use constant LIB_DIR => '/usr/lib/slack'; -use lib LIB_DIR; -use Slack; - -my @rsync = ('rsync', - '--recursive', - '--times', - '--ignore-times', - '--perms', - '--sparse', - ); - -(my $PROG = $0) =~ s#.*/##; - -sub check_stage (); -sub sync_role ($$@); -sub apply_default_perms_to_role ($$); - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Get out of wherever (possibly NFS-mounted) we were -chdir("/") - or die "Could not chdir /: $!"; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); - -######################################## -# Config and option parsing {{{ -my $usage = Slack::default_usage("$PROG [options] [...]"); -$usage .= < \%opt, - command_line_options => [ - 'subdir=s', - ], - usage => $usage, - required_options => [ qw(cache stage) ], -); - -# Arguments are required -die "No roles given!\n\n$usage" unless @ARGV; - -# We only allow certain values for this option -if ($opt{subdir}) { - unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') { - die "--subdir option must be 'files' or 'scripts'\n\n$usage"; - } -} else { - $opt{subdir} = ''; -} - -# Prepare for backups -if ($opt{backup} and $opt{'backup-dir'}) { - # Make sure backup directory exists - unless (-d $opt{'backup-dir'}) { - ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; - if (not $opt{'dry-run'}) { - eval { mkpath($opt{'backup-dir'}); }; - die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; - } - } - push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); -} - -# Pass options along to rsync -if ($opt{'dry-run'}) { - push @rsync, '--dry-run'; -} -# Pass options along to rsync -if ($opt{'verbose'} > 1) { - push @rsync, '--verbose'; -} -# }}} - -# copy over the new files -for my $full_role (@ARGV) { - # Split the full role (e.g. google.foogle.woogle) into components - my @role_parts = split(/\./, $full_role); - die "Internal error: Expect at least one role part" if not @role_parts; - # Reassemble parts one at a time onto @role and sync as we go, - # so we do "google", then "google.foogle", then "google.foogle.woogle" - my @role = (); - # Make sure we've got the right perms before we copy stuff down - check_stage(); - - # For the base role, do both files and scripts. - push @role, shift @role_parts; - for my $subdir(qw(files scripts)) { - if (not $opt{subdir} or $opt{subdir} eq $subdir) { - ($opt{verbose} > 1) - and print STDERR "$PROG: Calling sync_role for $full_role, @role\n"; - # @role here will have one element, so sync_role will use --delete - sync_role($full_role, $subdir, @role) - } - } - - # For all subroles, just do the files. - # (If we wanted script subroles to work like files, we'd get rid of this - # distinction and simplify the code.) - if (not $opt{subdir} or $opt{subdir} eq 'files') { - while (@role_parts) { - push @role, shift @role_parts; - ($opt{verbose} > 1) - and print STDERR "$PROG: Calling sync_role for $full_role, @role\n"; - sync_role($full_role, 'files', @role); - } - } - - for my $subdir (qw(files scripts)) { - apply_default_perms_to_role($full_role, $subdir) - if (not $opt{subdir} or $opt{subdir} eq $subdir); - } -} -exit 0; - -# Make sure the stage directory exists and is mode 0700, to protect files -# underneath in transit -sub check_stage () { - my $stage = $opt{stage} . "/roles"; - if (not $opt{'dry-run'}) { - if (not -d $stage) { - ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$stage'\n"; - eval { mkpath($stage); }; - die "Could not mkpath cache dir '$stage': $@\n" if $@; - } - ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$stage'\n"; - if ($> != 0) { - warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; - } else { - chown(0, 0, $stage) - or die "Could not chown 0:0 '$stage': $!\n"; - } - chmod(0700, $stage) - or die "Could not chmod 0700 '$stage': $!\n"; - } -} - -# Copy the files for a role from CACHE to STAGE -sub sync_role ($$@) { - my ($full_role, $subdir, @role) = @_; - my @this_rsync = @rsync; - - # If we were only given one role part, we're in the base role - my $in_base_role = (scalar @role == 1); - - # For the base role, delete any files that don't exist in the cache. - # Not for the subrole (otherwise we'll delete all files not in - # the subrole, which may be most of them!) - if ($in_base_role) { - push @this_rsync, "--delete"; - } - - # (a) => a/files - # (a,b,c) => a/files.b.c - my $src_path = $role[0].'/'.join(".", $subdir, @role[1 .. $#role]); - # This one's a little simpler: - my $dst_path = $full_role.'/'.$subdir; - - # final / is important for rsync - my $source = $opt{cache} . "/roles/" . $src_path . "/"; - my $destination = $opt{stage} . "/roles/" . $dst_path . "/"; - if (not -d $destination and -d $source) { - ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$destination'\n"; - if (not $opt{'dry-run'}) { - eval { mkpath($destination); }; - die "Could not mkpath stage dir '$destination': $@\n" if $@; - } - } - - # We no longer require the source to exist - if (not -d $source) { - # but we need to remove the destination if the source - # doesn't exist and we're in the base role - if ($in_base_role) { - rmtree($destination); - # rmtree() doesn't throw exceptions or give a return value useful - # for detecting failure, so we just check after the fact. - die "Could not rmtree '$destination' when '$source' missing\n" - if -e $destination; - } - # if we continue, rsync will fail because source is missing, - # so we don't. - return; - } - - # All this to run an rsync command - my @command = (@this_rsync, $source, $destination); - ($opt{verbose} > 0) and print STDERR "$PROG: Syncing $src_path with '@command'\n"; - Slack::wrap_rsync(@command); -} - -# This just takes the base role, and chowns/chmods everything under it to -# give it some sensible permissions. Basically, the only thing we preserve -# about the original permissions is the executable bit, since that's the -# only thing source code controls systems like CVS, RCS, Perforce seem to -# preserve. -sub apply_default_perms_to_role ($$) { - my ($role, $subdir) = @_; - my $destination = $opt{stage} . "/roles/" . $role; - - if ($subdir) { - $destination .= '/' . $subdir; - } - - # If the destination doesn't exist, it's probably because the source didn't - return if not -d $destination; - - ($opt{verbose} > 0) and print STDERR "$PROG: Setting default perms on $destination\n"; - if ($> != 0) { - warn "WARNING[$PROG]: Not superuser; won't be able to chown files\n"; - } - # Use File::Find to recurse the directory - find({ - # The "wanted" subroutine is called for every directory entry - wanted => sub { - return if $opt{'dry-run'}; - ($opt{verbose} > 2) and print STDERR "$File::Find::name\n"; - if (-l) { - # symlinks shouldn't be in here, - # since we dereference when copying - warn "WARNING[$PROG]: Skipping symlink at $File::Find::name: $!\n"; - return; - } elsif (-f _) { # results of last stat saved in the "_" - if (-x _) { - chmod 0555, $_ - or die "Could not chmod 0555 $File::Find::name: $!"; - } else { - chmod 0444, $_ - or die "Could not chmod 0444 $File::Find::name: $!"; - } - } elsif (-d _) { - chmod 0755, $_ - or die "Could not chmod 0755 $File::Find::name: $!"; - } else { - warn "WARNING[$PROG]: Unknown file type at $File::Find::name: $!\n"; - } - return if $> != 0; # skip chowning if not superuser - chown 0, 0, $_ - or die "Could not chown 0:0 $File::Find::name: $!"; - }, - # end of wanted function - }, - # way down here, we have the directory to traverse with File::Find - $destination, - ); -} +#!/usr/bin/perl -w +# $Id: slack-stage 180 2008-01-19 08:26:19Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2008 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. +# +# This script is in charge of copying files from the local cache +# directory to the local stage, building a unified single tree onstage +# from the multiple trees that are the role + subroles in the cache + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use File::Path; +use File::Find; + +use constant LIB_DIR => '/usr/lib/slack'; +use lib LIB_DIR; +use Slack; + +my @rsync = ('rsync', + '--recursive', + '--times', + '--ignore-times', + '--perms', + '--sparse', + ); + +(my $PROG = $0) =~ s#.*/##; + +sub check_stage (); +sub sync_role ($$@); +sub apply_default_perms_to_role ($$); + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Get out of wherever (possibly NFS-mounted) we were +chdir("/") + or die "Could not chdir /: $!"; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); + +######################################## +# Config and option parsing {{{ +my $usage = Slack::default_usage("$PROG [options] [...]"); +$usage .= < \%opt, + command_line_options => [ + 'subdir=s', + ], + usage => $usage, + required_options => [ qw(cache stage) ], +); + +# Arguments are required +die "No roles given!\n\n$usage" unless @ARGV; + +# We only allow certain values for this option +if ($opt{subdir}) { + unless ($opt{subdir} eq 'files' or $opt{subdir} eq 'scripts') { + die "--subdir option must be 'files' or 'scripts'\n\n$usage"; + } +} else { + $opt{subdir} = ''; +} + +# Prepare for backups +if ($opt{backup} and $opt{'backup-dir'}) { + # Make sure backup directory exists + unless (-d $opt{'backup-dir'}) { + ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; + if (not $opt{'dry-run'}) { + eval { mkpath($opt{'backup-dir'}); }; + die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; + } + } + push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); +} + +# Pass options along to rsync +if ($opt{'dry-run'}) { + push @rsync, '--dry-run'; +} +# Pass options along to rsync +if ($opt{'verbose'} > 1) { + push @rsync, '--verbose'; +} +# }}} + +# copy over the new files +for my $full_role (@ARGV) { + # Split the full role (e.g. google.foogle.woogle) into components + my @role_parts = split(/\./, $full_role); + die "Internal error: Expect at least one role part" if not @role_parts; + # Reassemble parts one at a time onto @role and sync as we go, + # so we do "google", then "google.foogle", then "google.foogle.woogle" + my @role = (); + # Make sure we've got the right perms before we copy stuff down + check_stage(); + + # For the base role, do both files and scripts. + push @role, shift @role_parts; + for my $subdir(qw(files scripts)) { + if (not $opt{subdir} or $opt{subdir} eq $subdir) { + ($opt{verbose} > 1) + and print STDERR "$PROG: Calling sync_role for $full_role, @role\n"; + # @role here will have one element, so sync_role will use --delete + sync_role($full_role, $subdir, @role) + } + } + + # For all subroles, just do the files. + # (If we wanted script subroles to work like files, we'd get rid of this + # distinction and simplify the code.) + if (not $opt{subdir} or $opt{subdir} eq 'files') { + while (@role_parts) { + push @role, shift @role_parts; + ($opt{verbose} > 1) + and print STDERR "$PROG: Calling sync_role for $full_role, @role\n"; + sync_role($full_role, 'files', @role); + } + } + + for my $subdir (qw(files scripts)) { + apply_default_perms_to_role($full_role, $subdir) + if (not $opt{subdir} or $opt{subdir} eq $subdir); + } +} +exit 0; + +# Make sure the stage directory exists and is mode 0700, to protect files +# underneath in transit +sub check_stage () { + my $stage = $opt{stage} . "/roles"; + if (not $opt{'dry-run'}) { + if (not -d $stage) { + ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$stage'\n"; + eval { mkpath($stage); }; + die "Could not mkpath cache dir '$stage': $@\n" if $@; + } + ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$stage'\n"; + if ($> != 0) { + warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; + } else { + chown(0, 0, $stage) + or die "Could not chown 0:0 '$stage': $!\n"; + } + chmod(0700, $stage) + or die "Could not chmod 0700 '$stage': $!\n"; + } +} + +# Copy the files for a role from CACHE to STAGE +sub sync_role ($$@) { + my ($full_role, $subdir, @role) = @_; + my @this_rsync = @rsync; + + # If we were only given one role part, we're in the base role + my $in_base_role = (scalar @role == 1); + + # For the base role, delete any files that don't exist in the cache. + # Not for the subrole (otherwise we'll delete all files not in + # the subrole, which may be most of them!) + if ($in_base_role) { + push @this_rsync, "--delete"; + } + + # (a) => a/files + # (a,b,c) => a/files.b.c + my $src_path = $role[0].'/'.join(".", $subdir, @role[1 .. $#role]); + # This one's a little simpler: + my $dst_path = $full_role.'/'.$subdir; + + # final / is important for rsync + my $source = $opt{cache} . "/roles/" . $src_path . "/"; + my $destination = $opt{stage} . "/roles/" . $dst_path . "/"; + if (not -d $destination and -d $source) { + ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$destination'\n"; + if (not $opt{'dry-run'}) { + eval { mkpath($destination); }; + die "Could not mkpath stage dir '$destination': $@\n" if $@; + } + } + + # We no longer require the source to exist + if (not -d $source) { + # but we need to remove the destination if the source + # doesn't exist and we're in the base role + if ($in_base_role) { + rmtree($destination); + # rmtree() doesn't throw exceptions or give a return value useful + # for detecting failure, so we just check after the fact. + die "Could not rmtree '$destination' when '$source' missing\n" + if -e $destination; + } + # if we continue, rsync will fail because source is missing, + # so we don't. + return; + } + + # All this to run an rsync command + my @command = (@this_rsync, $source, $destination); + ($opt{verbose} > 0) and print STDERR "$PROG: Syncing $src_path with '@command'\n"; + Slack::wrap_rsync(@command); +} + +# This just takes the base role, and chowns/chmods everything under it to +# give it some sensible permissions. Basically, the only thing we preserve +# about the original permissions is the executable bit, since that's the +# only thing source code controls systems like CVS, RCS, Perforce seem to +# preserve. +sub apply_default_perms_to_role ($$) { + my ($role, $subdir) = @_; + my $destination = $opt{stage} . "/roles/" . $role; + + if ($subdir) { + $destination .= '/' . $subdir; + } + + # If the destination doesn't exist, it's probably because the source didn't + return if not -d $destination; + + ($opt{verbose} > 0) and print STDERR "$PROG: Setting default perms on $destination\n"; + if ($> != 0) { + warn "WARNING[$PROG]: Not superuser; won't be able to chown files\n"; + } + # Use File::Find to recurse the directory + find({ + # The "wanted" subroutine is called for every directory entry + wanted => sub { + return if $opt{'dry-run'}; + ($opt{verbose} > 2) and print STDERR "$File::Find::name\n"; + if (-l) { + # symlinks shouldn't be in here, + # since we dereference when copying + warn "WARNING[$PROG]: Skipping symlink at $File::Find::name: $!\n"; + return; + } elsif (-f _) { # results of last stat saved in the "_" + if (-x _) { + chmod 0555, $_ + or die "Could not chmod 0555 $File::Find::name: $!"; + } else { + chmod 0444, $_ + or die "Could not chmod 0444 $File::Find::name: $!"; + } + } elsif (-d _) { + chmod 0755, $_ + or die "Could not chmod 0755 $File::Find::name: $!"; + } else { + warn "WARNING[$PROG]: Unknown file type at $File::Find::name: $!\n"; + } + return if $> != 0; # skip chowning if not superuser + chown 0, 0, $_ + or die "Could not chown 0:0 $File::Find::name: $!"; + }, + # end of wanted function + }, + # way down here, we have the directory to traverse with File::Find + $destination, + ); +} diff --git a/slack-dist/dist/slack-sync b/slack-dist/dist/slack-sync index 5aa541c..e334ef4 100644 --- a/slack-dist/dist/slack-sync +++ b/slack-dist/dist/slack-sync @@ -1,169 +1,169 @@ -#!/usr/bin/perl -w -# $Id: slack-sync 180 2008-01-19 08:26:19Z alan $ -# vim:sw=2 -# vim600:fdm=marker -# Copyright (C) 2004-2008 Alan Sundell -# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. -# See the file COPYING for details. -# -# This script is in charge of copying files from the (possibly remote) -# master directory to a local cache, using rsync - -require 5.006; -use warnings FATAL => qw(all); -use strict; -use sigtrap qw(die untrapped normal-signals - stack-trace any error-signals); - -use File::Path; - -use constant LIB_DIR => '/usr/lib/slack'; -use lib LIB_DIR; -use Slack; - -my @rsync = ('rsync', - '--cvs-exclude', - '--recursive', - '--links', - '--copy-links', - '--times', - '--perms', - '--sparse', - '--delete', - '--files-from=-', - '--from0', - ); - -(my $PROG = $0) =~ s#.*/##; - -sub check_cache ($); -sub rsync_source ($$@); - -######################################## -# Environment -# Helpful prefix to die messages -$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; -# Set a reasonable umask -umask 077; -# Get out of wherever (possibly NFS-mounted) we were -chdir("/") - or die "Could not chdir /: $!"; -# Autoflush on STDERR -select((select(STDERR), $|=1)[0]); - -######################################## -# Config and option parsing {{{ -my $usage = Slack::default_usage("$PROG [options] [...]"); -# Option defaults -my %opt = (); -Slack::get_options( - opthash => \%opt, - usage => $usage, - required_options => [ qw(source cache) ], -); - -# Arguments are required -die "No roles given!\n\n$usage" unless @ARGV; - -# Prepare for backups -if ($opt{backup} and $opt{'backup-dir'}) { - # Make sure backup directory exists - unless (-d $opt{'backup-dir'}) { - ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; - if (not $opt{'dry-run'}) { - eval { mkpath($opt{'backup-dir'}); }; - die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; - } - } - push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); -} -# Look at source type, and add options if necessary -if ($opt{'rsh'} or $opt{source} =~ m/^[\w@\.-]+::/) { - # This is tunnelled rsync, and so needs an extra option - if ($opt{'rsh'}) { - push @rsync, '-e', $opt{'rsh'}; - } else { - push @rsync, '-e', 'ssh'; - } -} - -# Pass options along to rsync -if ($opt{'dry-run'}) { - push @rsync, '--dry-run'; -} -# Pass options along to rsync -if ($opt{'verbose'} > 1) { - push @rsync, '--verbose'; -} -# }}} - -my @roles = (); - -{ - # This hash is just to avoid calling rsync twice if two subroles are - # installed. We only care since it's remote, and therefore slow. - my %roles_to_sync = (); - - # copy over the new files - for my $full_role (@ARGV) { - # Get the first element of the role name (the base role) - # e.g., from "google.foogle.woogle", get "google" - my $base_role = (split /\./, $full_role, 2)[0]; - - $roles_to_sync{$base_role} = 1; - } - @roles = keys %roles_to_sync; -} - -my $cache = $opt{cache} . "/roles/"; -# Make sure we've got the right perms before we copy stuff down -check_cache($cache); - -rsync_source( - $opt{source} . '/roles/', - $cache, - @roles, -); - -exit 0; - -# Make sure the cache directory exists and is mode 0700, to protect files -# underneath in transit -sub check_cache ($) { - my ($cache) = @_; - if (not $opt{'dry-run'}) { - if (not -d $cache) { - ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$cache'\n"; - eval { mkpath($cache); }; - die "Could not mkpath cache dir '$cache': $@\n" if $@; - } - ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$cache'\n"; - if ($> != 0) { - warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; - } else { - chown(0, 0, $cache) - or die "Could not chown 0:0 '$cache': $!\n"; - } - chmod(0700, $cache) - or die "Could not chmod 0700 '$cache': $!\n"; - } -} - -# Pull down roles from an rsync source -sub rsync_source($$@) { - my ($source, $destination, @roles) = @_; - my @command = (@rsync, $source, $destination); - - ($opt{verbose} > 0) - and print STDERR "$PROG: Syncing cache with '@command'\n"; - - my ($fh) = Slack::wrap_rsync_fh(@command); - - # Shove the roles down its throat - print $fh join("\0", @roles), "\0"; - - # Close fh, waitpid, and check return value - unless (close($fh)) { - Slack::check_system_exit(@command); - } -} +#!/usr/bin/perl -w +# $Id: slack-sync 180 2008-01-19 08:26:19Z alan $ +# vim:sw=2 +# vim600:fdm=marker +# Copyright (C) 2004-2008 Alan Sundell +# All Rights Reserved. This program comes with ABSOLUTELY NO WARRANTY. +# See the file COPYING for details. +# +# This script is in charge of copying files from the (possibly remote) +# master directory to a local cache, using rsync + +require 5.006; +use warnings FATAL => qw(all); +use strict; +use sigtrap qw(die untrapped normal-signals + stack-trace any error-signals); + +use File::Path; + +use constant LIB_DIR => '/usr/lib/slack'; +use lib LIB_DIR; +use Slack; + +my @rsync = ('rsync', + '--cvs-exclude', + '--recursive', + '--links', + '--copy-links', + '--times', + '--perms', + '--sparse', + '--delete', + '--files-from=-', + '--from0', + ); + +(my $PROG = $0) =~ s#.*/##; + +sub check_cache ($); +sub rsync_source ($$@); + +######################################## +# Environment +# Helpful prefix to die messages +$SIG{__DIE__} = sub { die "FATAL[$PROG]: @_"; }; +# Set a reasonable umask +umask 077; +# Get out of wherever (possibly NFS-mounted) we were +chdir("/") + or die "Could not chdir /: $!"; +# Autoflush on STDERR +select((select(STDERR), $|=1)[0]); + +######################################## +# Config and option parsing {{{ +my $usage = Slack::default_usage("$PROG [options] [...]"); +# Option defaults +my %opt = (); +Slack::get_options( + opthash => \%opt, + usage => $usage, + required_options => [ qw(source cache) ], +); + +# Arguments are required +die "No roles given!\n\n$usage" unless @ARGV; + +# Prepare for backups +if ($opt{backup} and $opt{'backup-dir'}) { + # Make sure backup directory exists + unless (-d $opt{'backup-dir'}) { + ($opt{verbose} > 0) and print STDERR "Creating backup directory '$opt{'backup-dir'}'\n"; + if (not $opt{'dry-run'}) { + eval { mkpath($opt{'backup-dir'}); }; + die "Could not mkpath backup dir '$opt{'backup-dir'}': $@\n" if $@; + } + } + push(@rsync, "--backup", "--backup-dir=$opt{'backup-dir'}"); +} +# Look at source type, and add options if necessary +if ($opt{'rsh'} or $opt{source} =~ m/^[\w@\.-]+::/) { + # This is tunnelled rsync, and so needs an extra option + if ($opt{'rsh'}) { + push @rsync, '-e', $opt{'rsh'}; + } else { + push @rsync, '-e', 'ssh'; + } +} + +# Pass options along to rsync +if ($opt{'dry-run'}) { + push @rsync, '--dry-run'; +} +# Pass options along to rsync +if ($opt{'verbose'} > 1) { + push @rsync, '--verbose'; +} +# }}} + +my @roles = (); + +{ + # This hash is just to avoid calling rsync twice if two subroles are + # installed. We only care since it's remote, and therefore slow. + my %roles_to_sync = (); + + # copy over the new files + for my $full_role (@ARGV) { + # Get the first element of the role name (the base role) + # e.g., from "google.foogle.woogle", get "google" + my $base_role = (split /\./, $full_role, 2)[0]; + + $roles_to_sync{$base_role} = 1; + } + @roles = keys %roles_to_sync; +} + +my $cache = $opt{cache} . "/roles/"; +# Make sure we've got the right perms before we copy stuff down +check_cache($cache); + +rsync_source( + $opt{source} . '/roles/', + $cache, + @roles, +); + +exit 0; + +# Make sure the cache directory exists and is mode 0700, to protect files +# underneath in transit +sub check_cache ($) { + my ($cache) = @_; + if (not $opt{'dry-run'}) { + if (not -d $cache) { + ($opt{verbose} > 0) and print STDERR "$PROG: Creating '$cache'\n"; + eval { mkpath($cache); }; + die "Could not mkpath cache dir '$cache': $@\n" if $@; + } + ($opt{verbose} > 0) and print STDERR "$PROG: Checking perms on '$cache'\n"; + if ($> != 0) { + warn "WARNING[$PROG]: Not superuser; unable to chown files\n"; + } else { + chown(0, 0, $cache) + or die "Could not chown 0:0 '$cache': $!\n"; + } + chmod(0700, $cache) + or die "Could not chmod 0700 '$cache': $!\n"; + } +} + +# Pull down roles from an rsync source +sub rsync_source($$@) { + my ($source, $destination, @roles) = @_; + my @command = (@rsync, $source, $destination); + + ($opt{verbose} > 0) + and print STDERR "$PROG: Syncing cache with '@command'\n"; + + my ($fh) = Slack::wrap_rsync_fh(@command); + + # Shove the roles down its throat + print $fh join("\0", @roles), "\0"; + + # Close fh, waitpid, and check return value + unless (close($fh)) { + Slack::check_system_exit(@command); + } +}