###
### 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 <<EOF;
###
### crontab for $self->{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 = <CRONTAB_CMD>)
{
$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<PSGConf::Action::Crontab> module provides a B<PSGConf> action class
for generating crontab files.
The B<PSGConf::Action::Crontab> class is derived from the
B<PSGConf::Action> 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<user> attribute. Returns true if the files
differ, and false otherwise.
=item diff()
Uses the C<diff> command to display the differences between the existing
crontab and the newly generated version.
=item do()
Uses the C<crontab> command to install the newly generated crontab.
=back
In addition to the attributes supported by the B<PSGConf::Action>
class, the B<PSGConf::Action::Crontab> class supports the following
attributes:
=over 4
=item I<user>
The name of the user whose crontab is to be generated.
=item I<entries>
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<minute>
The minute of the entry (range 0-59). If not set, defaults to C<*>.
=item I<hour>
The hour of the entry (range 0-23). If not set, defaults to C<*>.
=item I<dom>
The day of month of the entry (range 1-31). If not set, defaults to C<*>.
=item I<month>
The month of the entry (range 1-12). If not set, defaults to C<*>.
=item I<dow>
The day of week of the entry (range 0-6, with 0 being Sunday).
If not set, defaults to C<*>.
=item I<command>
The command to execute. This field is required.
=back
=back
=head1 SEE ALSO
L<perl>
crontab(5)
L<PSGConf>
L<PSGConf::Action>
L<PSGConf::Util>
=cut
syntax highlighted by Code2HTML, v. 0.9.1