Skip site navigation (1)Skip section navigation (2)
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
This is a multi-part message in MIME format.
--------------36A625E697425A4831054448
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

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}
--------------36A625E697425A4831054448
Content-Type: text/plain; charset=us-ascii;
 name="TBKKperiodic"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="TBKKperiodic"

#!/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;
    }
}

--------------36A625E697425A4831054448--


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?3B4E253A.9360EAAA>