From:	CRDGW2::CRDGW2::MRGATE::"SMTP::CRVAX.SRI.COM::RELAY-INFO-VAX"  3-NOV-1989 00:05
To:	MRGATE::"ARISIA::EVERHART"
Subj:	CRON utility, part 1/2

Received: From UCBVAX.BERKELEY.EDU by GIZMO.SRI.COM with TCP; Thu,  2 NOV 89 11:33:59 PDT
Received: by ucbvax.Berkeley.EDU (5.61/1.39)
	id AA23171; Thu, 2 Nov 89 11:17:26 -0800
Received: from USENET by ucbvax.Berkeley.EDU with netnews
	for info-vax@kl.sri.com (info-vax@kl.sri.com)
	(contact usenet@ucbvax.Berkeley.EDU if you have questions)
Date: 2 Nov 89 14:18:47 GMT
From: gem.mps.ohio-state.edu!uakari.primate.wisc.edu!uwm.edu!fps!brodie@tut.cis.ohio-state.edu
Organization: Medical College of Wisconsin, Milwaukee, WI
Subject: CRON utility, part 1/2
Message-Id: <3239.25500678@fps.mcw.edu>
Sender: info-vax-request@kl.sri.com
To: info-vax@kl.sri.com

$! ------------------ CUT HERE -----------------------
$ v='f$verify(f$trnlnm("SHARE_VERIFY"))'
$!
$! This archive created by VMS_SHARE Version 7.1-004  3-AUG-1989
$!   On  2-NOV-1989 09:13:07.81   By user BRODIE 
$!
$! This VMS_SHARE Written by:
$!    Andy Harper, Kings College London UK
$!
$! Acknowledgements to:
$!    James Gray       - Original VMS_SHARE
$!    Michael Bednarek - Original Concept and implementation
$!
$!+ THIS PACKAGE DISTRIBUTED IN 2 PARTS, TO KEEP EACH PART
$!  BELOW 30 BLOCKS
$!
$! TO UNPACK THIS SHARE FILE, CONCATENATE ALL PARTS IN ORDER
$! AND EXECUTE AS A COMMAND PROCEDURE  (  @name  )
$!
$! THE FOLLOWING FILE(S) WILL BE CREATED AFTER UNPACKING:
$!       1. CRON.COM;3
$!       2. CRONTAB.DAT;31
$!       3. CRON_CHECK.COM;12
$!       4. CRON_START.COM;4
$!       5. CRON_STOP.COM;1
$!
$set="set"
$set symbol/scope=(nolocal,noglobal)
$f=f$parse("SHARE_TEMP","SYS$SCRATCH:.TMP_"+f$getjpi("","PID"))
$e="write sys$error  ""%UNPACK"", "
$w="write sys$output ""%UNPACK"", "
$ if f$trnlnm("SHARE_LOG") then $ w = "!"
$ if f$getsyi("version") .ges. "V4.4" then $ goto START
$ e "-E-OLDVER, Must run at least VMS 4.4"
$ v=f$verify(v)
$ exit 44
$UNPACK: SUBROUTINE ! P1=filename, P2=checksum
$ if f$search(P1) .eqs. "" then $ goto file_absent
$ e "-W-EXISTS, File ''P1' exists. Skipped."
$ delete/nolog 'f'*
$ exit
$file_absent:
$ if f$parse(P1) .nes. "" then $ goto dirok
$ dn=f$parse(P1,,,"DIRECTORY")
$ w "-I-CREDIR, Creating directory ''dn'."
$ create/dir 'dn'
$ if $status then $ goto dirok
$ e "-E-CREDIRFAIL, Unable to create ''dn'. File skipped."
$ delete/nolog 'f'*
$ exit
$dirok:
$ w "-I-PROCESS, Processing file ''P1'."
$ define/user sys$output nl:
$ EDIT/TPU/NOSEC/NODIS/COM=SYS$INPUT 'f'/OUT='P1'
PROCEDURE Unpacker ON_ERROR ENDON_ERROR;SET(FACILITY_NAME,"UNPACK");SET(
SUCCESS,OFF);SET(INFORMATIONAL,OFF);f:=GET_INFO(COMMAND_LINE,"file_name");
buff:=CREATE_BUFFER(f,f);p:=SPAN(" ")@r&LINE_END;POSITION(BEGINNING_OF(buff))
;LOOP EXITIF SEARCH(p,FORWARD)=0;POSITION(r);ERASE(r);ENDLOOP;POSITION(
BEGINNING_OF(buff));g:=0;LOOP EXITIF MARK(NONE)=END_OF(buff);x:=
ERASE_CHARACTER(1);IF g = 0 THEN IF x="X" THEN MOVE_VERTICAL(1);ENDIF;IF x=
"V" THEN APPEND_LINE;MOVE_HORIZONTAL(-CURRENT_OFFSET);MOVE_VERTICAL(1);ENDIF;
IF x="+" THEN g:=1;ERASE_LINE;ENDIF;ELSE IF x="-" THEN g:=0;ENDIF;ERASE_LINE;
ENDIF;ENDLOOP;p:="`";POSITION(BEGINNING_OF(buff));LOOP r:=SEARCH(p,FORWARD);
EXITIF r=0;POSITION(r);ERASE(r);COPY_TEXT(ASCII(INT(ERASE_CHARACTER(3))));
ENDLOOP;o:=GET_INFO(COMMAND_LINE,"output_file");WRITE_FILE(buff,o);
ENDPROCEDURE;Unpacker;EXIT;
$ delete/nolog 'f'*
$ CHECKSUM 'P1'
$ IF CHECKSUM$CHECKSUM .eqs. P2 THEN $ EXIT
$ e "-E-CHKSMFAIL, Checksum of ''P1' failed."
$ ENDSUBROUTINE
$START:
$ create/nolog 'f'
X$! CRON.COM
X$!
X$! A VMS version of the UNIX cron facility.  Reads the file`032
X$! mcw$sys:crontab.dat once each hour and performs the commands`032
X$! according to the following rules:
X$!`032
X$! each line contains 5 fields separated by tabs or spaces
X$! The fields are hours (0-23), days (1-31), months (1-12),
X$! day of week (1-7) and command line. (unix has a minutes field too.)
X$!
X$! The number parameters can be one of these: (can mix and match types)
X$!    single number    - matches exactly
X$!    number-number    - matches inclusive range, note that`032
X$!                       1-3 matches 1,2, or 3 but 22-2 (for hours) matches
X$!                       22,23,0,1,2.  Time goes around.
X$!    number,number... - matches any in list
X$!    *                - matches any
X$!
X$! All four time fields must match for command to be executed.
X$!
X$! lines starting with # are comments.
X$!
X$! The command line goes to the end of line or to a %.  If there is a % then
V`032
X$! it is taken as a EOL for the command, further elements separated by % are
X$! taken as input data lines to the command.  The command is executed as
X$! a subprocess from the account running cron.com, except if it is a submit
X$! command in which case it is executed immediately.  Since this is usually
X$! the system account you should consider this facility mostly for system
X$! functions.  You can do something for another user by making the command
X$! a submit/user=somebody of a command file or a run/uic=something.
X$!
X$! logging of activity is done with writes to sys$output.  This can
X$! be directed to a file during debugging or send to nl: when cron
X$! is run as a detached process.
X$!
X$! define some constants
X$ delim = " "  ! field delimiter, change here if desired
X$ dash  = "-" `032
X$ comma = ","
X$ percent = "%"
X$ asterix = "*"
X$!
X$! begin, first get the current time components       `032
X$ set noverify
X$ set message/fac/sev/id/text
X$ again:
X$ show time
X$ time        = f$time()
X$ current_0   = f$integer(f$cvtime(time,,"hour"))
X$ current_1   = f$integer(f$cvtime(time,,"day"))
X$ current_2   = f$integer(f$cvtime(time,,"month"))
X$ day_of_week_name = f$cvtime(time,,"weekday")
X$ if day_of_week_name .eqs. "Sunday"    then current_3 = 1
X$ if day_of_week_name .eqs. "Monday"    then current_3 = 2
X$ if day_of_week_name .eqs. "Tuesday"   then current_3 = 3
X$ if day_of_week_name .eqs. "Wednesday" then current_3 = 4
X$ if day_of_week_name .eqs. "Thursday"  then current_3 = 5
X$ if day_of_week_name .eqs. "Friday"    then current_3 = 6
X$ if day_of_week_name .eqs. "Saturday"  then current_3 = 7
X$!
X$ write sys$output -
X "Starting cron run at: hour:''current_0'  day:''current_1'  month:''current
V_2'  dow:''current_3'"
X$!
X$! get a line from the file
X$ cronfile = "mcw$sys:crontab.dat"
X$ open/read/error=wait crontab 'cronfile
X$!
X$! process each line in turn
X$ line_number = 0   ! used if neccessary for unique file name
X$ next_line:
X$ line_number = line_number + 1
X$ read/end_of_file=wait crontab line
X$ write sys$output "Reading line:"
X$ show symbol line
X$ if f$extract(0,1,line) .eqs. "#" then goto next_line  ! comment line
X$ line = f$edit(line,"compress,trim,upcase")            !condition input dat
Va
X$!
X$! parse out line parts, all parts must match to get command done
X$! loop through first four elements of line calling check_element
X$! with parameters current and element.  Check $status on return.  If
X$! any element doesn't match then get next line.
X$ element_number = 0
X$ next_element:
X$ element = f$element(element_number,delim,line)
X$ current = current_'element_number'
X$ gosub check_element
X$ if $status .ne. 1 then goto next_line   !no match
X$ element_number = element_number + 1
X$ if element_number .le. 3 then goto next_element
X$!
X$! here if elements 0-3 all match, time to do the command
X$! find command in line.
X$ write sys$output "Executing line:"
X$ show symbol line
X$ command = f$extract(f$locate(f$element(4,delim,line),line),200,line)`032
X$ if f$locate(percent,command) .ne. f$length(command) then goto data_lines
X$ if f$extract(0,3,command) .eqs. "SUB" then goto immediate_submit
X$ spawn/output=nl:/log/nowait 'command'
X$ goto next_line
X$!
X$immediate_submit:  ! here if command is submit so do immediately
X$'command'
X$ goto next_line
X$!
X$data_lines: ! here if command has data lines following
X$ data_line_number = 0
X$ open/write temp line'line_number'.com
X$next_data_line:
X$ write temp f$element(data_line_number,percent,command)
X$ data_line_number = data_line_number + 1
X$ if f$element(data_line_number,percent,command) .nes. percent then -
X    goto next_data_line
X$ close temp
X$ spawn/output=nl:/log/nowait @line'line_number'.com
X$ goto next_line
X$!
X$! wait for next go around, always synch to next even hour,
X$! this code to be sure that the procedure restarts within
X$! a few seconds after even hour.
X$wait:
X$ close crontab
X$ spawn/output=nl:/log/nowait purge line*.com
X$ time           = f$time()
X$ elapsed_minute = f$integer(f$cvtime(time,,"minute"))
X$ elapsed_second = f$integer(f$cvtime(time,,"second"))             `032
X$ write sys$output -
X "Current minute: ''elapsed_minute',Current second: ''elapsed_second'"
X$!
X$ if elapsed_minute .gt. 0 then goto more_minute
X$ if elapsed_second .gt. 0 then goto more_second
X$ write sys$output "Waiting 01:00:00"
X$ wait 01:00:00 ! minutes and seconds both zero.  probably rare :-).
X$ goto again
X$!
X$ more_second:  ! minutes zero, seconds more than zero. possible.
X$ write sys$output "Waiting 00:59:''f$string(60-elapsed_second)'"
X$ wait 00:59:'f$string(60-elapsed_second)'
X$ goto again
X$!
X$ more_minute: `032
X$ if elapsed_second .gt. 0 then goto more_second2
X$ write sys$output "Waiting 00:''f$string(60-elapsed_minute)':00"
X$ wait 00:'f$string(60-elapsed_minute)':00  ! minute nonzero, seconds zero
X$ goto again                                       `032
X$!
X$ more_second2: ! minutes and seconds more than zero
X$ write sys$output -
X"Waiting 00:''f$string(60-elapsed_minute-1)':''f$string(60-elapsed_second)'"
X$ wait 00:'f$string(60-elapsed_minute-1)':'f$string(60-elapsed_second)'
X$ goto again
X$!
X$! subroutine to check for matches, current is the part of the real
X$! current date and time and element is the chunk of the crontab file.
X$! Need to check element and cut up into smaller pieces if neccesary
X$! and then compare to current.  Note that current is an integer and
X$! element (and it's pieces) is a string.
X$check_element:
X$! parse out parts of element, if there are any
X$! check simplest case first, i.e. *
X$ if f$locate(asterix,element) .eq. f$length(element) then goto no_asterix
X$ return 1  ! asterix found, immediate match
X$!
X$ no_asterix:
X$ sub_element_number = -1
X$!
X$ find_sub_element: ! looking for comma separated list
X$ sub_element_number = sub_element_number + 1
X$ sub_element = f$element(sub_element_number,comma,element)
X$ if sub_element .eqs. comma then goto out_of_sub_elements
X$!
X$ left_side_of_dash = f$element(0,dash,sub_element)
X$ if left_side_of_dash .nes. sub_element then goto range_element`032
X$ if current .eq. f$integer(sub_element) then return 1  !found a match
X$ goto find_sub_element`032
X$!`032
X$ range_element:  ! sub_element has a dash in it
X$ right_side_of_dash = f$element(1,dash,sub_element)
X$! if left is greater than right then things are not_straightforward
X$ if f$integer(left_side_of_dash) .gt. f$integer(right_side_of_dash) -
X    then goto not_straightforward
X$ if current .ge. f$integer(left_side_of_dash) .and. -
X     current .le. f$integer(right_side_of_dash) then return 1 ! in the range
X$ goto find_sub_element
X$!
X$not_straightforward: !because time is circular at least in measurement
X$! example:  a range of days such as 27-3.  this is taken to mean
X$! any day from 27-31 or 1-3.  Need to see if current falls into
X$! 27-max_day_of_month or min_day_of_month-3.  Note that each time
X$! component has a different max and min.  Set them now.
X$ component_min = 1  !for days, months and dow
X$ if element_number .eq. 0 then component_min = 0  !hours
X$ if element_number .eq. 0 then component_max = 23 !hours
X$ if element_number .eq. 1 then component_max = 31 !days of month
X$ if element_number .eq. 2 then component_max = 12 !months of year
X$ if element_number .eq. 3 then component_max = 7  !day of week
X$ if (current .ge. f$integer(left_side_of_dash) .and. -
X      current .le. component_max) .or. -
X     (current .ge. component_min  .and. -
X      current .le. f$integer(right_side_of_dash)) then return 1 !whew!
X$goto find_sub_element
X$!
X$out_of_sub_elements:
X$return 3 ! ran through all sub_elements with any match
X$! end of subroutine check_element !
X$!
X$! end of CRON.COM
$ CALL UNPACK CRON.COM;3 710760113
$ create/nolog 'f'
X# cron dataset - Medical College of Wisconsin - ** 8350 **.
X#  hour,  day,   month,  dow,  command
X# (0-23) (1-31) (1-12)  (1-7)
X#
X# Run different DISKEEPER jobs, depending on what day of the week it is...
X##1  * * 1 purge/log/keep=3 mcw$diskeeper:*.log
X##20 * * 1 submit/user=SYSTEM/notify/keep/print/log=mcw$diskeeper:/name="Dis
Vkeeper0"/queue=sys$batch mcw$diskeeper:diskeeper_plus_startup_batch /param=(
V"DUA0")
X##20 * * 2 submit/user=SYSTEM/notify/keep/print/log=mcw$diskeeper:/name="Dis
Vkeeper1"/queue=sys$batch mcw$diskeeper:diskeeper_plus_startup_batch /param=(
V"DUA1")
X##20 * * 3 submit/user=SYSTEM/notify/keep/print/log=mcw$diskeeper:/name="Dis
Vkeeper2"/queue=sys$batch mcw$diskeeper:diskeeper_plus_startup_batch /param=(
V"DUA2")
X##20 * * 4 submit/user=SYSTEM/notify/keep/print/log=mcw$diskeeper:/name="Dis
Vkeeper9"/queue=sys$batch mcw$diskeeper:diskeeper_plus_startup_batch /param=(
V"DUA9")
X#
X# Daily FREESPACE check
X##4 * * * submit/notify/queue=sys$admin/noprint/name="Freespace"/keep/log=sy
Vs$sysdevice:`091mvx`093 sys$sysdevice:`091mvx`093freespace.com
X#
X# Start daily system Monitor job
X##0 * * * submit/notify/queue=sys$batch/noprint/name="Monitor"/keep/log=dua9
V:`091brodie.monitor`093 dua9:`091brodie.monitor`093monitor.com
X#
X# Check for inactive (interactive) processes, log them off.
X##1-3 * * *  SUBMIT/NOPRINT/LOG=mcw$sys:jkiller.log/NAME="Jkiller"/QUEUE=SYS
V$ADMIN/USER=SYSTEM MCW$SYS:JKILLER.COM
X##1-3 * * *  purge/log/keep=10 mcw$sys:jkiller.log
X#
$ CALL UNPACK CRONTAB.DAT;31 844654992
$ create/nolog 'f'
X$! CHECK to see that 'Cron_DAEMON' (mcw job scheduler) exists
X$ SAY:== WRITE SYS$OUTPUT
X$ SAY "Checking to see if ""Cron_DAEMON"" (the detached job scheduler) exist
Vs.."
X$ say ""
X$!
X$ world = f$setprv("world")
X$ context = ""
X$check_cron:`032
X$ pid = f$pid(context)
X$ if (pid .eqs. "") then goto no_more_pids
X$ procname = f$edit(f$getjpi(pid,"prcnam"),"TRIM")
X$ if procname .nes. "Cron_DAEMON" then goto check_cron
X$ prior = f$getjpi(pid,"PRIB")
X$ say "Cron_DAEMON job scheduler exists (pid = ",pid,",base prior=",prior,")
V."
X$ restore_priv = f$setprv(world)
X$ exit
X$no_more_pids:
X$ say "*** Cron_DAEMON job scheduler not found!! ***"
X$ restore_priv = f$setprv(world)
X$ exit
$ CALL UNPACK CRON_CHECK.COM;12 122692234
$ create/nolog 'f'
X$! CRON_START.COM    - Start up the CRON utility as a detached system proces
Vs.
X$!                     See MCW$SYS:CRON.COM for details of what CRON does.
X$!                     Downloaded from ARIS - 9/26/88 Kent C. Brodie (modifi
Ved)
X$!
X$ save_priv = f$setprv("ALL")
X$!
X$ request "Cron_DAEMON process initialized."
X$ systemname = f$getsyi("NODENAME")
X$ run  -`032
X      /detach -
X      /input   = mcw$sys:cron.com -
X      /output  = mcw$sys:cron.log -
X      /error   = mcw$sys:cron.error -
X      /process = "Cron_DAEMON" -                     `032
X      /queue_limit = 20 -
+-+-+-+-+-+-+-+-  END  OF PART 1 +-+-+-+-+-+-+-+-

