#!/usr/local/bin/perl # # Build cron jobs from comments in files. # # $Id: make_cron,v 1.5 2003-01-21 12:24:40-05 mprewitt Exp $ # # ----- # Copyright (C) 2003 Chelsea Networks, under the GNU GPL. # make_cron comes with ABSOLUTELY NO WARRANTY. This is free software, and you are # welcome to redistribute it under certain conditions; see the COPYING file # for details. # # make_cron is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # make_cron is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA use strict; use Net::NIS::Listgroup; use Cwd; use Getopt::Std; my $RSH = 'ssh'; sub Usage { my $error = shift; print STDERR "Error: $error\n" if $error; print STDERR <{user}->{host}->{ cron }->[ min hour day mon weekday args ] # ->{ nightly }->{order} my $cron = {}; my $cwd = cwd; # # OK, now we are ready!; # foreach my $file ( @ARGV ) { if ( -d $file ) { print "Skipping directory \"$file\"....\n" if $verbose; next; } die "Cannot open/read \"$file\". [$!]\n" unless open( FILE, $file ); # # Convert file relative. # unless ( $file =~ m:^/: ) { $file = "$cwd/$file"; $file =~ s://:/:g; while ( $file =~ s:[^/]+/\.\./:: ) { 1 }; } # # Storage units # my $tmpgroup; my $tmpjob; my $tmpuser; while() { my $command; do { next unless s/^#CRON:\s*(\w+)\s*//; $command = $1; }; s/#.*$//; # netgroup or host # # Build the job list # if (($command eq 'netgroup') or $command eq 'host') { my ($day, @exclude, $host, $hostexpr, $hour, $ignore, $min, $mon, $weekday, @args); # # The value of $host is the netgroup name, here. # if ($command eq 'netgroup') { do { /\(?(([^(].*?)(?=\))|(\@\w+))(.*)/; $hostexpr = $1; ($ignore, $min, $hour, $day, $mon, $weekday, @args ) = split /\s+/, $4; $min =~ s/\)//; $hostexpr =~ s/(\()|(\))|(\@)|(-)//g; }; ($host, @exclude) = split(/\s+/, $hostexpr); my $hostref = listgroup($host); die "$0: netgroup $host not found.\n" unless $hostref; my @hosts = @{$hostref}; if (@exclude) { my $expr = join "|", @exclude; @hosts = grep !/$expr/, @hosts; } @hosts = ($thost) if $thost; $tmpgroup->{$hostexpr} = \@hosts; } else { ($hostexpr, $min, $hour, $day, $mon, $weekday, @args ) = split; $hostexpr = $thost if $thost; } if ( $min =~ /nightly/i ) { die "Illegal value for nightly order \"$hour\" in file \"$file\".\n" if $hour =~ /\D/; # # Temporary storage. # push @{$tmpjob->{$hostexpr}->{nightly}}, [ $hour, $file, $day, $mon, $weekday, @args ]; } else { # # Check validity # die "Illegal value for job parm \"min\" ($min) in file \"$file\".\n" if $min !~ /^((\*)|(\d+[,\d]*)|(\d+-\d+))$/; die "Illegal value for job parm \"hour\" ($hour) in file \"$file\".\n" if $hour !~ /^((\*)|(\d+[,\d]*)|(\d+-\d+))$/; die "Illegal value for job parm \"day\" ($day) in file \"$file\".\n" if $day !~ /^((\*)|(\d+[,\d]*)|(\d+-\d+))$/; die "Illegal value for job parm \"mon\" ($mon) in file \"$file\".\n" if $mon !~ /^((\*)|(\d+[,\d]*)|(\d+-\d+))$/; die "Illegal value for job parm \"weekday\" ($weekday) in file \"$file\".\n" if $weekday !~ /^((\*)|(\d+[,\d]*)|(\d+-\d+))$/; # # Temporary storage. # push @{$tmpjob->{$hostexpr}->{cron}}, [ $min, $hour, $day, $mon, $weekday, $file, @args ]; } } elsif ( $command eq "user" ) { # # Duplicate user? # die "Can't set user twice in same script.\n" if $tmpuser; $tmpuser = (split)[0]; die "Invalid user \"$tmpuser\"\n" unless getpwnam($tmpuser); } else { Usage( "Error in CRON comments in file \"$file\" ($command)\n" ); } } # # Convert groups to hosts # foreach my $host ( keys %$tmpjob ) { if ( exists $tmpgroup->{$host} ) { # # Expand it out! # foreach my $grouphost ( @{$tmpgroup->{$host}} ) { # # Is it real? # unless (gethostbyname( $grouphost )) { warn "Skipping invalid host \"$grouphost\" in group \"$host\".\n"; next; } foreach my $jobtype ( keys %{$tmpjob->{$host}} ) { push @{$tmpjob->{$grouphost}->{$jobtype}}, @{$tmpjob->{$host}->{$jobtype}}; } } delete $tmpjob->{$host}; } else { # # Check to see the host if valid. # unless (gethostbyname( $host )) { warn "Skipping invalid host \"$host\".\n"; next; } } # # If we're root, must have user definition in files, or -u option. # if ( $user eq "root" ) { die "No user defined in file \"$file\"\n" unless $tmpuser; } else { $tmpuser = $user; } } # # We've loaded it all in. Now, assemble the real thing. # foreach my $host ( keys %$tmpjob ) { foreach my $type ( keys %{$tmpjob->{$host}} ) { push @{$cron->{$tmpuser}->{$host}->{$type}}, @{$tmpjob->{$host}->{$type}}; } } } unless ( %$cron ) { print STDERR "Hmmm...nothing to do!\n"; exit 0; } # # Are we root or that user? # foreach my $checkuser ( sort keys %$cron ) { die "Cannot install as user other than self ($user) unless running as root.\n" unless $debug || $wuser eq "root" || $user eq $wuser; } # # Build our CRON output. # my ($crons, %uniq); foreach my $cuser ( sort keys %$cron ) { foreach my $host ( sort keys %{$cron->{$cuser}} ) { if ( exists $cron->{$cuser}->{$host}->{nightly} ) { # # Make nightly CRON job. # my( $hour, $min ) = split(/:/, $time); push @{$cron->{$cuser}->{$host}->{cron}}, [ $min, $hour, "*", "*", "*", "/usr/local/etc/nightly/$cuser" ]; $crons->{$host}->{$cuser}->{nightly} = "#!/bin/ksh\n"; foreach my $job ( sort NIGHTLY @{$cron->{$cuser}->{$host}->{nightly}} ) { my $jobstr = join( " ", @$job[1..$#{$job} ] ); my $key = "$host $cuser nightly $jobstr"; unless ($uniq{$key}) { $crons->{$host}->{$cuser}->{nightly} .= "$jobstr\n"; $uniq{$key}++; } } } if ( exists $cron->{$cuser}->{$host}->{cron} ) { foreach my $job ( sort CRON @{$cron->{$cuser}->{$host}->{cron}} ) { my $key = join(" ", @$job); unless ($uniq{"$host $cuser cron $key"}) { $crons->{$host}->{$cuser}->{cron} .= sprintf "%-4s %-4s %-4s %-4s %-4s %s\n", $job->[0], $job->[1], $job->[2], $job->[3], $job->[4], join( " ", @$job[5..$#{$job}] ); $uniq{"$host $cuser cron $key"}++; } } } } # # Print it? # if ( $verbose ) { foreach my $host ( keys %$crons ) { foreach my $cuser ( keys %{$crons->{$host}} ) { next if $dohost && $host ne $dohost; print "HOST $host, USER: $cuser\n"; if ( exists $crons->{$host}->{$cuser}->{cron} ) { print $crons->{$host}->{$cuser}->{cron}; } if ( exists $crons->{$host}->{$cuser}->{nightly} ) { print "NIGHTLY job starts at: $time\n"; print $crons->{$host}->{$cuser}->{nightly}; } } } } if ( $debug ) { print "No work being done - debug flag set.\n"; exit 0; } # # OK, do the install! # foreach my $host ( keys %$crons ) { foreach my $instuser ( keys %{$crons->{$host}} ) { next if $dohost && $host ne $dohost; # # Do we need to become this user? # print "host $host, user $instuser...\n" if $verbose; my $suargs = 1 if $instuser ne $wuser; if ( exists $crons->{$host}->{$instuser}->{cron} ) { # # Install the CRON job # print " cron jobs...\n" if $verbose; if ( $suargs ) { open( RSH, qq{| $RSH $host "su $instuser -c crontab"} ) || die "$RSH open failed - check error messages [$!]\n"; } else { open( RSH, qq{| $RSH $host crontab} ) || die "$RSH open failed - check error messages [$!]\n"; } print RSH $crons->{$host}->{$instuser}->{cron}; close RSH || die "$RSH close failed - check error messages [$!]\n"; } if ( exists $crons->{$host}->{$instuser}->{nightly} ) { # # Install the NIGHTLY job # print " nightly job...\n" if $verbose; system("$RSH $host 'rm -f /usr/local/etc/nightly/$instuser'"); if ( $suargs ) { open( RSH, qq{| $RSH $host "su $instuser -c 'cat > /usr/local/etc/nightly/$instuser; chmod 540 /usr/local/etc/nightly/$instuser'"} ) || die "copy of nightly failed - check error messages, directory may not exist [$!]\n"; } else { open( RSH, qq{| $RSH $host "cat > /usr/local/etc/nightly/$instuser; chmod 540 /usr/local/etc/nightly/$instuser'"}) || die "copy of nightly failed - check error messages, directory may not exist [$!]\n"; } print RSH $crons->{$host}->{$instuser}->{nightly}; close RSH || die "$RSH close failed - check error messages [$!]\n"; } } } } sub CRON { return $a->[3] <=> $b->[3] or $a->[2] <=> $b->[2] or $a->[1] <=> $b->[1] or $a->[0] <=> $b->[0] or $a->[4] <=> $b->[4]; } sub NIGHTLY { return $a->[0] <=> $b->[0] or $a->[1] <=> $b->[1]; }