### ### Copyright 2000-2007 University of Illinois Board of Trustees ### All rights reserved. ### ### PSGConf::Action::Crontab - crontab action type for psgconf ### ### Campus Information Technologies and Educational Services ### University of Illinois at Urbana-Champaign ### package PSGConf::Action::Crontab; use strict; use File::Basename; use File::Compare; use File::Copy; use File::Path; use Text::Diff (); # Use () so we do not get diff() redefinition errors use PSGConf::Action; use PSGConf::Util; use Cwd; our @ISA = qw(PSGConf::Action); ############################################################################### ### constructor ############################################################################### sub new { my ($class, %opts) = @_; die "PSGConf::Action::Crontab->new(): entries attribute missing\n" if (!exists($opts{entries})); $opts{user} = 'root' if (!exists($opts{user})); $opts{name} = "$opts{user} crontab" if (!exists($opts{name})); return PSGConf::Action::new($class, %opts); } ############################################################################### ### check() method ############################################################################### sub check { my ($self, $psgconf) = @_; my ($entry, $entries, $cmd, $line, $skip_flag, $olddir); ### set tmpfile name $self->_set_tmpfile($psgconf); ### open tempfile if (!open(TMPFILE, '>' . $self->{tmpfile})) { warn "\n\t!!! open('>$self->{tmpfile}'): $!\n"; return -1; } ### print banner print TMPFILE <{user} ### ### Automatically generated by psgconf - DO NOT EDIT! ### EOF ### print crontab entries foreach $entry (sort { $a->{command} cmp $b->{command} } @{$self->{entries}}) { ### If this is a Vixie cron entry, ignore it here if ( ! exists $entry->{type} || $entry->{type} ne 'vixie' ) { $entries++; print TMPFILE join(' ', (map { (defined($entry->{$_}) ? $entry->{$_} : '*'); } qw(minute hour dom month dow))); print TMPFILE "\t" . $entry->{command} . "\n"; } } ### close tmpfile close(TMPFILE) || die $0 . ": close('$self->{tmpfile}'): $!"; ### Since we did not write any entries to the crontab ### return with no entries, like we are not managing ### the crontab file at all. if ( ! $entries ) { unlink($self->{tmpfile}); return 0; } ### grab copy of original file (but move to what we think ### is a world readable/executable directory so the su ### does not fail. $olddir = getcwd; chdir (dirname($psgconf->{tmpdir})); $self->{orig_file} = $self->{tmpfile} . '.orig'; $cmd = ''; $cmd .= "su $self->{user} -c \"" if ($self->{user} ne 'root'); $cmd .= 'crontab -l'; $cmd .= '"' if ($self->{user} ne 'root'); $cmd .= ' 2>/dev/null |'; open(ORIGFILE, ">$self->{orig_file}") || die $0 . ": open('>$self->{orig_file}'): $!"; open(CRONTAB_CMD, $cmd) || die $0 . ": open('$cmd'): $!"; while ($line = ) { $skip_flag = 1 if ($. == 1 && $psgconf->data_obj('platform')->match('linux') && $line =~ m/^# DO NOT EDIT THIS FILE -/); next if ($skip_flag && $. <= 3); print ORIGFILE $line; } chdir ($olddir); close(ORIGFILE) || die $0 . ": close('$self->{orig_file}'): $!"; if (!close(CRONTAB_CMD) && $! != 0) { die $0 . ": close('$cmd'): $!"; } ### check for differences if (compare($self->{orig_file}, $self->{tmpfile}) != 0) { $self->{changed} = 1; return 1; } ### no change - unlink tempfile unlink($self->{tmpfile}); return 0; } ############################################################################### ### diff() method ############################################################################### sub diff { my ($self, $psgconf) = @_; my ($cmd, $rc); print "###############################################################################\n"; print "### DIFF FOR $self->{name}\n"; print "###############################################################################\n"; print Text::Diff::diff $self->{orig_file}, $self->{tmpfile}; print "\n"; unlink($self->{orig_file}); } ############################################################################### ### do() method ############################################################################### sub do { my ($self, $psgconf) = @_; my ($cmd, $res, $crontab_tmpfile, $olddir); $res=1; ### When updating the crontab for a non-root user, the crontab ### command running as that user will not be able to read the ### file from psgconf's tmpdir, since it's owned by root and ### mode 0700. To work around this, we copy the file to another ### directory and make it owned by the user. ### This code originally tried to get around this problem with ### "crontab < filename", but it turns out that that doesn't ### work with vixie cron. if ($self->{user} ne 'root') { $crontab_tmpfile = $psgconf->{tmpdir} . '.1'; my $umask = umask 022; mkpath ($crontab_tmpfile, 0, 0755); umask $umask; $crontab_tmpfile .= '/' . basename $self->{tmpfile}; copy($self->{tmpfile}, $crontab_tmpfile); chown($self->{uid}, -1, $crontab_tmpfile); $cmd = "su $self->{user} -c \"crontab $crontab_tmpfile\""; } else { $cmd = "crontab $self->{tmpfile}"; } ### Jump to what we think is a world readable directory to run ### the crontab command as possibly non root. $olddir = getcwd; chdir (dirname($psgconf->{tmpdir})); $res = -1 if (&PSGConf::Util::RunCommand($cmd)); chdir ($olddir); ### remove the file we created for the crontab command to read if ($self->{user} ne 'root') { rmtree(basename($crontab_tmpfile)); } ### remove tmpfile if applicable if (-e $self->{tmpfile} && $psgconf->{rm_tmpfiles} && !unlink($self->{tmpfile})) { warn "\t!!! unlink('$self->{tmpfile}') : $!\n"; } return ($res); } ############################################################################### ### documentation ############################################################################### 1; =head1 NAME PSGConf::Action::Crontab - root crontab action class for PSGConf =head1 SYNOPSIS use PSGConf::Action::Crontab; $psgconf->register_actions( PSGConf::Action::Crontab->new( entries => [ { minute => 0, hour => 0, command => '/usr/local/sbin/nightly' }, ... ], user => 'root', ... ), ... ); =head1 DESCRIPTION The B module provides a B action class for generating crontab files. The B class is derived from the B class, but it defines/overrides the following methods: =over 4 =item check() Generates the new crontab file and compares it to the existing one for the user specified by the I attribute. Returns true if the files differ, and false otherwise. =item diff() Uses the C command to display the differences between the existing crontab and the newly generated version. =item do() Uses the C command to install the newly generated crontab. =back In addition to the attributes supported by the B class, the B class supports the following attributes: =over 4 =item I The name of the user whose crontab is to be generated. =item I An anonymous array containing the entries to be placed in the user's crontab. Each entry is an anonymous hash containing the following fields: =over 4 =item I The minute of the entry (range 0-59). If not set, defaults to C<*>. =item I The hour of the entry (range 0-23). If not set, defaults to C<*>. =item I The day of month of the entry (range 1-31). If not set, defaults to C<*>. =item I The month of the entry (range 1-12). If not set, defaults to C<*>. =item I The day of week of the entry (range 0-6, with 0 being Sunday). If not set, defaults to C<*>. =item I The command to execute. This field is required. =back =back =head1 SEE ALSO L crontab(5) L L L =cut