Date: Thu, 12 Jul 2001 15:31:22 -0700 From: Tim Kientzle <kientzle@acm.org> To: "Michael C . Wu" <keichii@peorth.iteration.net> Cc: keichii@FreeBSD.org, freebsd-bugs@FreeBSD.org Subject: Re: misc/28920: periodic scripts do not run on desktop systems that aren't always on Message-ID: <3B4E253A.9360EAAA@acm.org> References: <200107121939.f6CJd1n00272@freefall.freebsd.org> <3B4E21AF.1EEC97F8@acm.org> <20010712172912.A86119@peorth.iteration.net>
next in thread | previous in thread | raw e-mail | index | archive | help
[-- Attachment #1 --]
Something this simple hardly requires a 'tarball' ;-)
Attached; enjoy.
- Tim Kientzle
"Michael C . Wu" wrote:
>
> On Thu, Jul 12, 2001 at 03:16:15PM -0700, Tim Kientzle scribbled:
> | I would appreciate if you reconsider. As I mentioned
> | in my original post, I do have complete source implementing
> | this system if you'd like to take a more careful look at
> | the basic concept.
>
> Hi Tim,
>
> Please show us a URL leading to a tarball of your code.
> And I think that the best way to do this would be to make
> a port in the Ports system.
>
> Thanks,
> Michael
> --
> Michael C. Wu +1-512-7757700
> keichii@{iteration.net|freebsd.org}
[-- Attachment #2 --]
#!/usr/bin/perl5
#
# Copyright (C) 1998-2001, Tim Kientzle. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of Tim Kientzle may not be used to endorse or promote
# products derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR OR AUTHORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR AUTHORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
###########################################################################
#
# Run periodic tasks.
#
# The problem: 'daily'/'weekly'/'monthly' scripts are typically
# scheduled to run at a particular time, which doesn't work with
# systems that aren't always running.
#
# The solution: This script.
# * Each class of tasks is contained in a directory,
# e.g., '/etc/periodic/daily'
# * That directory contains a collection of tasks.
# * Each task is a separate executable (usually a shell script)
# * This script simply executes each task
# * It uses the '/var/db/periodic.db' database to keep track of when each
# job was last executed, so that it knows which jobs need to be run next
# * If invoked as "periodic <class>", then all jobs in <class>
# are executed immediately.
# * If invoked as "periodic <class> nice", then only a single
# task---the most overdue one---is executed. This task is
# executed at a reduced priority.
#
# Normally, 'periodic daily' is invoked every night in the
# wee hours, and 'periodic daily nice' is invoked every 20 minutes or so.
# If the system is on all night, the daily tasks will be run
# in the middle of the night; if not, they will still get run.
# Similarly, 'periodic weekly' and 'periodic monthly'
# are invoked from cron late at night, and 'periodic weekly nice'
# and 'periodic monthly nice' are invoked at much shorter intervals.
#
# With this approach, tasks get run periodically even if the system
# isn't always on at a particular time.
#
# This is designed to be invoked from cron via the following entries:
#
# #
# # do scheduled hourly/daily/weekly/monthly maintenance
# #
# 0 * * * * root TBKKperiodic hourly
# 0 2 * * * root TBKKperiodic daily
# 30 3 * * 6 root TBKKperiodic weekly
# 30 5 1 * * root TBKKperiodic monthly
# #
# # Perform any tasks that are overdue because the system wasn't on at 2am.
# #
# */20 * * * * root TBKKperiodic daily nice
# 30 * * * * root TBKKperiodic weekly nice
# 30 */2 * * * root TBKKperiodic monthly nice
#
#
# March, 1998: Initial version
# March 23, 1999: Made mostly compatible with FreeBSD 3.1 'periodic'
# TODO: support 'local_periodic' variable from rc.conf
#
###########################################################################
use File::Find;
use DB_File;
use Fcntl;
$periodicDbFile = "/var/db/periodic.db";
#
# Get directory to use as base (argument to this script)
#
if(scalar(@ARGV) == 0) { print "Usage: $0 <service> <nice>\n" }
$service = $ARGV[0]; # First argument: hourly, daily, weekly, or monthly
if($ARGV[1] eq 'nice') { # second arg is optional 'nice'
$nice = 1;
} else {
$nice = 0;
}
$jobsdir = "/etc/periodic/$service";
#
# Set up parameters based on job class.
#
$maxjobs = 100; # Maximum number of jobs to run before exiting
$niceness = 0; # How much to reduce priority
if($service eq "hourly") {
$period = 60*60; # Run jobs older than this (in seconds)
} elsif($service eq "daily") {
$period = 60*60*25; # Run jobs older than this (in seconds)
} elsif($service eq "weekly") {
$period = 60*60*24*8; # Run jobs older than this (in seconds)
} elsif($service eq "monthly") {
$period = 60*60*24*32; # Run jobs older than this (in seconds)
}
#
# If 'nice', run just one job at reduced priority
#
if($nice) {
$maxjobs = 1; # Maximum number of jobs to run before exiting
$niceness = 15; # How much to reduce priority
} else {
$period /= 8; # Even without 'nice', don't run jobs too often
}
#
# Useful constants
#
$rootuid=0;
$defaultpriority = getpriority(0,0); # Usual priority
$taskpriority = $defaultpriority + $niceness; # (Higher value = more nice)
# Open the database marking when each task was last run
if(!tie(%periodicDb,'DB_File',"$periodicDbFile",
O_RDWR, 0644, $DB_File::DB_BTREE)) {
# This section is partly a gaurd against changes to the DB library.
# There's pressure on the Perl community to switch to the
# newer but incompatible DB 2.x libraries. In case Perl gets
# re-linked against an incompatible DB, this will burn the old
# database and create a new one.
print "Couldn't open $periodicDbFile ... creating new database.\n\n\n";
unlink "$periodicDbFile"; # Delete it in case it's there but trashed
tie(%periodicDb,'DB_File',"$periodicDbFile",
O_RDWR | O_CREAT, 0644, $DB_File::DB_BTREE)
|| die "Failed to create new database.";
}
#
# Scan dir and determine which jobs are waiting to run
#
my(@taskrunlist);
chdir "$jobsdir";
&find(\&find_task,"$jobsdir");
#
# Determining if a task is ready to run is basically quite easy,
# but I've included a few sanity/security checks along the way.
#
sub find_task {
# Get info about file
if(!(($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_))) {return}
# Security: must be a file, owned by root, not writable by group/world
if((!-f _) || ($uid != $rootuid) || ($mode & 022)) { return }
# Sanity check: Must be readable and executable
if(($mode & 0500) != 0500) { return }
# Useful: don't execute '_norun' files, emacs backups, etc.
if(/^.*_norun$/ || /^.*~$/ || /\#/ || /^\./) { return }
# Decide if this task needs to be run again
if(defined $periodicDb{$File::Find::name}
&& ($periodicDb{$File::Find::name} > time-$period)) { return }
# Does need to be run, save the full filename.
push @taskrunlist,$File::Find::name;
return;
}
# Sort list so oldest jobs get run first
@taskrunlist = sort { $periodicDb{$a} <=> $periodicDb{$b} } @taskrunlist;
#
# Run the jobs
#
foreach $task (@taskrunlist) {
# Do this task
&run_task($task);
# Limit number of jobs run
if(++$totaljobs >= $maxjobs) { last }
}
untie %periodicDb;
exit;
###########################################################################
sub run_task {
my($task) = @_;
# Log this activity
system('logger','-p','cron.notice',"$service:",$task);
# Run the task (with altered priority) and collect the output.
setpriority 0,0,$taskpriority;
if (open TASK, "$task 2>&1 |") { # Collect stdout and stderr
@output = <TASK>;
close TASK;
# Mark when this task was last run
$periodicDb{$task} = time;
} else {
@output = ("Couldn't execute $task\n");
}
setpriority 0,0,$defaultpriority;
# Abbreviate task name for reporting
$task =~ s|.*/([^/]*)$|$1|;
# Mail the output (if any) to root
@filtered = grep { !/^\s+$/ } @output; # Don't mail a blank message
if(scalar(@filtered) > 0) { # If there was output, mail it to root
open(SENDMAIL,"|/usr/sbin/sendmail -F$service root");
print SENDMAIL "Subject: $service: $task\n";
print SENDMAIL "\n";
print SENDMAIL @output; # Send the original (with blank lines)
close SENDMAIL;
}
}
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?3B4E253A.9360EAAA>
