home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: SysTools
/
SysTools.zip
/
pmcron03.zip
/
tables.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-05-09
|
68KB
|
1,276 lines
/* Copyright (c) 1995 Florian Große-Coosmann, RCS section at the eof */
/* This module includes all functions necessary for the cron mechanism. You */
/* may be interested but you'll be disappointed. This is not a good looking */
/* code. */
#define INCL_NOPM
#define INCL_NOCOMMON
#define INCL_DOSSEMAPHORES
#define INCL_DOSFILEMGR
#define INCL_DOSPROCESS
#define INCL_DOSERRORS
#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <signal.h>
#include <stddef.h>
#include <setjmp.h>
#include <errno.h>
#include <io.h>
#include <sys/ioctl.h>
#include "server.h"
#define DAY_LOOKAHEAD 1 /* distance within to look for the */
/* next starting time */
time_t AutoDisConnect = (time_t) -1l; /* close the communication pipe after*/
/* this time */
time_t AutoCloseSocket = (time_t) -1l; /* close the communication socket */
/* after this time */
ULONG EditEntryNumber = (ULONG) -1; /* No. of job which is edited or -1 */
HMTX ThreadSem = (HMTX) 0; /* semaphore allowing exclusiv usage */
/* of the job list */
LIST_ENTRY ListHead = {NULL,NULL,}; /* the currently used job list */
static int AutoReWrite = 0; /* flag: do we need (another try of) */
/* a write of the Crontabs file? */
static time_t currtime; /* current time, don't compute on */
/* every line. */
static struct tm currtime_tm; /* current time in tm format */
static int CurrTimeIsOK = 0; /* Every function which needs the */
/* currtime should use the time */
/* function to get the value. */
static jmp_buf Continue; /* in case of an unexpected signal */
static int PrintSleepingTime = 0; /* debugging flag */
static int PrintEachStartingTime = 0; /* debugging flag */
/*****************************************************************************/
/* function name : BlockProcess */
/* */
/* description : the calling thread gets the exclusive access to the */
/* job list. This function must be called before any */
/* access to the ListHead. */
/* In case of an unallocated semaphore or a global stop */
/* this function works fine. */
/*****************************************************************************/
void BlockProcess(void)
{
ULONG err;
if (ThreadSem == (HMTX) 0)
return;
do {
err = DosRequestMutexSem(ThreadSem,SEM_INDEFINITE_WAIT);
} while (err && !GlobalStop);
}
/*****************************************************************************/
/* function name : UnBlockProcess */
/* */
/* description : releases the exclusive access to the job list. */
/* */
/* note : should only called by the thread that has called */
/* BlockProcess. */
/*****************************************************************************/
void UnBlockProcess(void)
{
DosReleaseMutexSem(ThreadSem);
}
/*****************************************************************************/
/* function name : LocalStop */
/* */
/* arguments : signal number generated by the runtime system */
/* */
/* description : This is a signal routine. This routine is called by a */
/* SIGTERM. This should never happen, since this the */
/* corresponding threads are not the main thread. */
/* */
/* note : don't call directly */
/*****************************************************************************/
static void LocalStop(int code)
{
GlobalStop++;
if (pipehandle != -1)
close(pipehandle);
pipehandle = -1;
signal(code,SIG_ACK);
longjmp(Continue,1); /* points to the atexit execution of */
} /* the job list, see end of this file*/
/*****************************************************************************/
/* function name : LookupEntryNum */
/* */
/* arguments : number of the entry (1-based!), flag if it should */
/* count comment lines too */
/* */
/* return value : the entry or NULL in case of an error */
/* */
/* description : looks for the requested entry. If the flag is set, the */
/* comment lines of the Crontabs file are returned, too. */
/* Call with the flag set only if you want to rewrite the */
/* Crontabs file. */
/* */
/* note : don't forget to block the list */
/*****************************************************************************/
LIST_ENTRY *LookupEntryNum(ULONG num,int AllowComment)
{
LIST_ENTRY *run = &ListHead;
if (num == 0) /* can't return &ListHead */
return(NULL);
while (num) {
run = run->next;
if (run == NULL) /* no more entries */
return(NULL);
if (!AllowComment)
if (run->IsComment)
continue; /* ignore comment lines */
num--;
}
return(run);
}
/*****************************************************************************/
/* function name : IsBitSet */
/* */
/* arguments : bitset, number of the bit (0-based) */
/* */
/* return value : 1 if the specified bit is set, 0 otherwise */
/* */
/* description : checks wether the specified bit in the bitset is set. */
/*****************************************************************************/
static int IsBitSet(unsigned long long val,unsigned bitno)
{
if (val & (1ull << bitno))
return(1);
return(0);
}
/*****************************************************************************/
/* function name : ComputeNextStart */
/* */
/* arguments : job list entry, start time of the day of computing, */
/* day count of the look ahead */
/* */
/* return value : the next start time of the job entry or (time_t) -1, */
/* if this job should never executed or should not been */
/* executed within the DayCount */
/* */
/* description : computes the next start time of the job entry. This */
/* may be "never" in case of a comment or an entry of */
/* "atstartup" or "atexit" execution. */
/* if DayStart >= 0 the computing starts from the */
/* "current time" placed in reference until the end of */
/* the day. The following DayCount days were checked for */
/* a possible start of the job, too. */
/* */
/* note : This function is called very frequently. I think we */
/* spend the most time in the called function localtime. */
/* Be careful if you want to change things. */
/*****************************************************************************/
static time_t ComputeNextStart(LIST_ENTRY *e,time_t reference,int DayCount)
{
int i,j;
struct tm tm;
if (DayCount < 0) /* nothing more to do? */
return(e->NextStartTime);
e->NextStartTime = (time_t) -1; /* default: never */
if (e->AtStartup || e->AtExit || e->IsComment || e->Daily) /* don't */
return(e->NextStartTime); /* execute this jobs automatically */
if ((i = (int) (reference % 60)) != 0) /* go to the next start of a minute*/
reference += 60 - i;
tm = *localtime(&reference); /* compute current day, weekday, etc.*/
if ((!IsBitSet(e->Month,tm.tm_mon + 1)) || /* Either month, day or */
(!IsBitSet(e->Day,tm.tm_mday)) || /* weekday are inacceptable */
(!IsBitSet(e->WeekDay,tm.tm_wday))) {
/* compute the job start time with a */
/* reference of "tomorrow at day */
/* start" */
reference -= tm.tm_hour * 60 * 60 + tm.tm_min * 60; /* back to the */
/* start of the current day */
reference += 24 * 60 * 60; /* add one day */
return(ComputeNextStart(e,reference,DayCount - 1));
}
/* check the next hour and minute we */
/* have to start the job */
if (IsBitSet(e->Hour,tm.tm_hour)) { /* within the current hour? */
for (j = tm.tm_min;j < 60;j++)
if (IsBitSet(e->Minute,j)) { /* found! start at this minute */
tm.tm_min = j;
return(e->NextStartTime = mktime(&tm));
}
}
/* not within the current hour, check*/
/* the rest of the day */
for (i = tm.tm_hour + 1;i < 24;i++) {
if (!IsBitSet(e->Hour,i)) /* not within this hour */
continue;
for (j = 0;j < 60;j++) /* hour correct, check the minutes */
if (IsBitSet(e->Minute,j)) { /* found! start at this minute */
tm.tm_hour = i;
tm.tm_min = j;
return(e->NextStartTime = mktime(&tm));
}
}
/* don't start this job today, */
/* check wether to start tomorrow */
reference -= tm.tm_hour * 60 * 60 + tm.tm_min * 60;
reference += 24 * 60 * 60;
return(ComputeNextStart(e,reference,DayCount - 1));
}
/*****************************************************************************/
/* function name : NextWord */
/* */
/* arguments : start of the 0-terminated string, buffer to hold the */
/* length of the word */
/* */
/* return value : the beginning of the next whitespace terminated word */
/* within the string or "" if there is no such word. */
/* The return value is NULL in case of an error. */
/* */
/* description : looks for the start of the next word. return the */
/* beginning of the word and places the length into the */
/* supplied buffer. After the fist call you may give a */
/* NULL as the string argument. In this case the search */
/* is continued after the last returned word. */
/* This function is pretty similar to strtok, but we */
/* don't destroy any char within the string and we return */
/* an empty string in case of no more words. */
/*****************************************************************************/
static const char *NextWord(const char *start,ULONG *len)
{
static const char *oldstart = NULL; /* not thread save! */
const char *retval,*ptr;
*len = 0;
if ((start == NULL) && (oldstart == NULL)) /* never started or at end */
return(NULL); /* of line? */
retval = (start == NULL) ? oldstart : start; /* continue with the last */
/* processed line? */
while (isspace(*retval)) /* jump over leading whitespace */
retval++;
ptr = retval;
while (*ptr != 0) { /* until string not empty */
(*len)++;
if (isspace(ptr[1])) { /* next char is a terminator (white)?*/
oldstart = ptr + 1; /* save pointer to allow the use of */
return(retval); /* NULL for the string argument */
}
ptr++;
}
oldstart = ptr; /* save "" to allow the use of NULL */
return(retval); /* for the string argument */
}
/*****************************************************************************/
/* function name : NextNumber */
/* */
/* arguments : string, buffer to hold the length of the converted */
/* characters, maximum length of string */
/* */
/* return value : 0xFFFF in case of an error, the converted number */
/* otherwise. */
/* */
/* description : returns the next number within the string. Leading */
/* whitespaces are not accepted. The length buffer is */
/* filled with the converted characters. The length of */
/* the string must be supplied, therefore the string may */
/* not be terminated. */
/* */
/* note : we accept only numbers in the range (0 - 65530)! */
/*****************************************************************************/
static unsigned NextNumber(const char *start,ULONG *len,ULONG maxlen)
{
unsigned retval = 0;
*len = 0;
while ((isdigit(*start)) && (*len < maxlen)) {
if (retval > 6552)
return(0xFFFF);
retval *= 10;
retval += (unsigned) (*start - '0');
start++;
(*len)++;
}
if (*len == 0) /* nothing done? */
return(0xFFFF);
return(retval);
}
/*****************************************************************************/
/* function name : ResolvWord */
/* */
/* arguments : bitset to hold the values, minimal acceptable value, */
/* maximal acceptable value, string with values (not */
/* 0-terminated) and its length */
/* */
/* return value : 1 on success, 0 otherwise */
/* */
/* description : this function converts the string to a bitset. The */
/* string consists of a comma separated list of numbers. */
/* The special string "*" is accepted as an abbreviation */
/* of "all valid numbers". Intervals are supported. */
/* Each number is check to fall into the given interval */
/* [min,max] inclusively. */
/* The bitset is zeroed at the start of the function. */
/* Any character except digits, commas and a minus sign */
/* leads to a failure. */
/* */
/* note : an empty string is accepted. */
/*****************************************************************************/
static int ResolvWord(unsigned long long *bits,int min,int max,const char *s,
ULONG len)
{
unsigned val,valend;
ULONG toklen;
*bits = 0ull;
if (len == 0)
return(0);
if ((len == 1) && (*s == '*')) { /* all valid numbers? */
for (val = min;val <= max;val++)
*bits |= (1ull << val);
return(1);
}
for (;;) {
val = NextNumber(s,&toklen,len);
if (((int) val < min) || ((int) val > max)) /* invalid value? */
return(0);
*bits |= (1ull << val);
s += toklen; /* eat the number */
len -= toklen;
if (len == 0) /* end of the string? */
return(1);
switch (*s) {
case ',': /* the list must be comma separated */
s++; /* eat the comma */
len--;
break;
case '-': /* the entry is an interval */
s++; /* eat the minus sign */
len--;
valend = NextNumber(s,&toklen,len);
if (((int) val < min) || ((int) val > max)) /* invalid value? */
return(0);
if (valend < val) /* Illegal interval */
return(0);
s += toklen; /* eat the number */
len -= toklen;
while (val <= valend) {
*bits |= (1ull << val);
val++;
}
if (len == 0) /* this was the last entry */
return(1);
if (*s != ',') /* only commas are expected */
return(0);
s++; /* eat the comma */
len--;
break;
default: /* other characters are not expected */
return(0);
}
}
}
/*****************************************************************************/
/* function name : PrepareListEntry */
/* */
/* arguments : job list entry */
/* */
/* return value : 0 on success, EINVAL otherwise */
/* */
/* description : parses the line new->s and sets all computable values */
/* of the job. */
/* Allowed starting time are: */
/* "CronStart" */
/* "CronStop" */
/* "Once" "CronStart" */
/* "Once" "CronStop" */
/* standard time list */
/* "Once" standard time list */
/* */
/* note : don't call this function if the entry has already been */
/* placed into the job list. currtime must been set. */
/*****************************************************************************/
static int PrepareListEntry(LIST_ENTRY *new)
{
const char *s,*word;
ULONG len;
unsigned long long ull;
s = new->s;
word = NextWord(s,&len);
if ((strnicmp(word,"Once",(size_t) len) == 0) && (len == 4)) {
new->Once = 1; /* the "Once" statement is allowed */
word = NextWord(NULL,&len); /* on every time string */
}
if ((strnicmp(word,"CronStart",(size_t) len) == 0) && (len == 9)) {
new->AtStartup = 1;
word = NextWord(NULL,&len);
if (len == 0) /* empty command string? */
return(EINVAL);
new->StartCmd = word;
return(0);
}
if ((strnicmp(word,"CronStop",(size_t) len) == 0) && (len == 8)) {
new->AtExit = 1;
word = NextWord(NULL,&len);
if (len == 0)
return(EINVAL);
new->StartCmd = word;
return(0);
}
if ((strnicmp(word,"Daily",(size_t) len) == 0) && (len == 5)) {
new->Daily = 1;
word = NextWord(NULL,&len);
if (len == 0)
return(EINVAL);
new->StartCmd = word;
return(0);
}
/* now we have to check for the */
/* standard time list (minutes hours */
/* days months weekdays) */
if (!ResolvWord(&(new->Minute),0,59,word,len)) /* resolve the minute list*/
return(EINVAL);
s = word + len; /* eat the list */
word = NextWord(NULL,&len);
if (!ResolvWord(&ull,0,23,word,len)) /* process the hour list */
return(EINVAL);
new->Hour = (unsigned) ull;
word = NextWord(NULL,&len);
if (!ResolvWord(&ull,1,31,word,len)) /* process the day list */
return(EINVAL);
new->Day = (unsigned) ull;
word = NextWord(NULL,&len);
if (!ResolvWord(&ull,1,12,word,len)) /* process the month list */
return(EINVAL);
new->Month = (unsigned) ull;
word = NextWord(NULL,&len);
if (!ResolvWord(&ull,0,6,word,len)) /* process the weekday list */
return(EINVAL);
new->WeekDay = (unsigned) ull;
word = NextWord(NULL,&len);
if (len == 0) /* empty command line? */
return(EINVAL);
new->StartCmd = word; /* assign the command line */
ComputeNextStart(new,currtime,DAY_LOOKAHEAD);
return(0);
}
/*****************************************************************************/
/* function name : CleanListEntry */
/* */
/* arguments : job list entry */
/* */
/* description : frees the entry and probably its command line */
/*****************************************************************************/
static void CleanListEntry(LIST_ENTRY *entry)
{
if (entry == NULL)
return;
if (entry->s != NULL)
free(entry->s);
free(entry);
}
/*****************************************************************************/
/* function name : ExtractListEntry */
/* */
/* arguments : job list entry to delete, head of the list of the job */
/* entries where to search for the extraction candidat. */
/* */
/* return value : NULL on error, the job list entry otherwise */
/* */
/* description : looks for the given entry in the list, cuts it off but */
/* doesn't delete it. This function is made for the */
/* exclusive use by DeleteListEntry and RunJobs (see */
/* below). */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. */
/* The EditEntryNumber is maintained to flag the PM a */
/* changed number of the job which is currently edited. */
/*****************************************************************************/
static LIST_ENTRY *ExtractListEntry(LIST_ENTRY *e,LIST_ENTRY *head)
{
LIST_ENTRY *father;
ULONG number = 0; /* number of the entry which should */
/* be deleted */
if (e == NULL)
return(NULL);
/* now look for the predecessor of */
/* the entry, it may be a comment */
/* line. */
father = head;
while ((father != NULL) && (father->next != e)) {
father = father->next;
number++;
}
if (father == NULL) /* what's that? orphaned son? */
return(NULL);
father->next = e->next;
e->next = NULL;
if (EditEntryNumber != (ULONG) -1) { /* There is a job in editing mode? */
if (EditEntryNumber > number) /* job after the deleted one */
EditEntryNumber--;
else if (EditEntryNumber == number) /* job IS the deleted one */
EditEntryNumber = (ULONG) -1;
}
return(e);
}
/*****************************************************************************/
/* function name : DeleteListEntry */
/* */
/* arguments : job list entry to delete, head of the list of the job */
/* entries where to search for the deletion candidat. */
/* */
/* return value : 0 on success, errno otherwise */
/* */
/* description : looks for the given entry in the list, cuts it off and */
/* deletes the entry. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. */
/*****************************************************************************/
int DeleteListEntry(LIST_ENTRY *entry,LIST_ENTRY *head)
{
if ((entry = ExtractListEntry(entry,head)) == NULL) /* can't extract? */
return(EACCES);
CleanListEntry(entry);
return(0);
}
/*****************************************************************************/
/* function name : InsertListEntry */
/* */
/* arguments : string with a new Crontabs statement, its length, flag */
/* to allow comment lines, head of the list of the job */
/* entries where to append the statement */
/* */
/* return value : 0 on success, errno otherwise */
/* */
/* description : duplicates and parses the string and appends it to the */
/* list. This function checks the validity and sets all */
/* necessary entry values, too. */
/* If the flag is set comment lines were accepted. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. currtime must been set. */
/*****************************************************************************/
static int InsertListEntry(const char *s,size_t len,int AllowComment,
LIST_ENTRY *head)
{
LIST_ENTRY *run = head,*new;
int err;
while (isspace(*s) && (len > 0)) { /* cut off leading and trailing */
s++; /* whitespaces */
len--;
}
while (len > 0) {
if (isspace(s[len - 1]))
len--;
else
break;
}
if (len == 0) { /* empty line? */
if (AllowComment) /* ignore the line if allowed */
return(0);
return(EINVAL);
}
while (run->next != NULL) /* look for the end of the list */
run = run->next;
if ((new = malloc(sizeof(LIST_ENTRY))) == NULL) /* create a new entry */
return(ENOMEM);
memset(new,0,sizeof(LIST_ENTRY));
if ((new->s = malloc(len + 1)) == NULL) { /* allocate buffer for the */
CleanListEntry(new); /* copy of the Crontabs statement */
return(ENOMEM);
}
memcpy(new->s,s,len); /* the statement isn't 0-terminated */
new->s[len] = '\0';
if (AllowComment && ((new->s[0] == ';') || (new->s[0] == '#'))) {
new->NextStartTime = (time_t) -1;
new->IsComment = 1;
} else if ((err = PrepareListEntry(new)) != 0) { /* error in the stmt? */
CleanListEntry(new);
return(err);
}
run->next = new; /* OK, append to the list */
return(0);
}
/*****************************************************************************/
/* function name : CreateList */
/* */
/* arguments : flag: which output should be generated */
/* */
/* return value : newly allocated buffer holding a long string with all */
/* the table entry lines. The string is terminated by a */
/* '\0' und each line is separated by a CR LF pair. */
/* NULL in case on an error. */
/* */
/* description : The flag may has one of the following states: */
/* CL_FILE: */
/* The output is generated for the Crontabs file. All */
/* comments and executable entries are included. */
/* CL_USER: */
/* The output is generated for the user communication */
/* pipe. No comments are included but each line is */
/* preceeded by a line number. */
/* A notice is generated in case of an empty list. */
/* CL_PM: */
/* The output is generated for the PM interface. Only */
/* executable entries are included. No other lines */
/* were generated. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. */
/*****************************************************************************/
char *CreateList(int flag)
{
ULONG i;
size_t buflen,len,pos;
LIST_ENTRY *e;
char *buf = NULL,*new;
if (flag == CL_USER) { /* check out an empty table */
if ((buf = malloc(200)) == NULL)
return(NULL);
sprintf(buf,Get(IDS_ThisIsTheJobList),
GetTimeString());
if (LookupEntryNum(1,0) == NULL) { /* empty list? */
strcat(buf,Get(IDS_NoJobList));
return(buf);
}
buflen = strlen(buf) + 1; /* 1 = termination 0 */
} else
buflen = 1; /* 1 = termination 0 */
if ((e = LookupEntryNum(1,(flag == CL_FILE) ? 1 : 0)) == NULL) {
if (buf != NULL) /* should never happen... */
free(buf);
return(NULL);
}
i = 1;
while (e != NULL) {
len = strlen(e->s);
pos = buflen - 1; /* appendance position */
buflen += len + 2; /* data + "\r\n" */
if (flag == CL_USER)
buflen += 12; /* 12 for numbering */
if ((new = realloc(buf,buflen)) == NULL) {
free(buf);
return(NULL);
}
buf = new;
if (flag == CL_USER)
sprintf(buf + pos,"%10lu: %s\r\n",i,e->s);
else
sprintf(buf + pos,"%s\r\n",e->s);
e = LookupEntryNum(++i,(flag == CL_FILE) ? 1 : 0); /* next entry */
}
return(buf);
}
/*****************************************************************************/
/* function name : ReWrite */
/* */
/* return value : 0 on success, errno otherwise */
/* */
/* description : rewrites the complete job table including the comments */
/* to the Crontabs file. A special flag AutoReWrite is */
/* maintained in case of an error. This flag can be used */
/* to write the table after a time. Thus, the possibility */
/* of loosing the complete table is minimized. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. */
/* The AutoReWrite flag has not been exported. In case */
/* of changing the Crontabs file the old file may been */
/* left in an unexpected state. */
/*****************************************************************************/
static int ReWrite(void)
{
size_t len;
char *buf;
int htype;
ioctl(cronhandle,FGETHTYPE,&htype);
if (htype == HT_DEV_NUL) { /* There is no need to rewrite the */
AutoReWrite = 0; /* "nul" device */
return(0);
}
buf = CreateList(CL_FILE);
AutoReWrite = 1; /* good assumption */
lseek(cronhandle,0l,SEEK_SET);
if (buf == NULL) { /* no buffer? */
if (ListHead.next != NULL) /* but any data? */
return(ENOMEM); /* this is an error */
/* else: empty file */
if (ftruncate(cronhandle,0l) == -1)
return(errno);
fsync(cronhandle); /* does ftruncate make a SYNC ? */
DosResetBuffer(cronhandle); /* let OS/2 update the directory */
return(0);
}
len = strlen(buf); /* some data available */
if (ftruncate(cronhandle,(long) len) == -1) /* first, try to truncate */
return(errno); /* the file size */
errno = 0; /* assume no error */
if (write(cronhandle,buf,len) != len) { /* shit! Maybe, the disk is full*/
if (errno == 0) /* but some data of the Crontabs */
errno = ENOSPC; /* may have been overwritten. This */
free(buf); /* is a critical error. */
return(errno);
}
DosResetBuffer(cronhandle); /* let OS/2 update the directory */
free(buf);
AutoReWrite = 0; /* no need to rewrite */
return(0);
}
/*****************************************************************************/
/* function name : DeleteEntryNum */
/* */
/* arguments : number of the entry, buffer to hold the answer in case */
/* of success (approx. 1K is sufficent) */
/* */
/* return value : 0 on success (buffer filled), errno otherwise (buffer */
/* not filled) */
/* */
/* description : deletes the job entry with the given number from the */
/* current job list. The Crontabs file is rewritten. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. */
/*****************************************************************************/
int DeleteEntryNum(ULONG num,char *replybuf)
{
int err;
if ((err = DeleteListEntry(LookupEntryNum(num,0),&ListHead)) != 0)
return(err);
if (ReWrite() != 0)
strcpy(replybuf,Get(IDS_JobDeletedNotWritten));
else
strcpy(replybuf,Get(IDS_JobDeleted));
return(0);
}
/*****************************************************************************/
/* function name : NewEntry */
/* */
/* arguments : string holding a new Crontabs statement, buffer to */
/* hold the answer in case of success (approx. 1K is */
/* sufficent) */
/* */
/* return value : 0 on success (buffer filled), errno otherwise (buffer */
/* not filled) */
/* */
/* description : parses the given string and checks its correctness. In */
/* this case the string is appended to the current job */
/* list and the Crontabs file is rewritten. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. */
/*****************************************************************************/
int NewEntry(char *line,size_t size,char *replybuf)
{
int err;
if (!CurrTimeIsOK) { /* Don't take the current time stamp */
time(&currtime); /* if the daemon thread is running */
currtime_tm = *localtime(&currtime); /* in the inner loop, we must */
} /* prevent a new setting while it */
/* believes in the unchangable of the*/
/* time stamp! */
if ((err = InsertListEntry(line,size,0,&ListHead)) != 0)
return(err);
if (ReWrite() != 0)
strcpy(replybuf,Get(IDS_JobSavedNotWritten));
else
strcpy(replybuf,Get(IDS_JobSaved));
return(0);
}
/*****************************************************************************/
/* function name : ParseLine */
/* */
/* arguments : buffer, its size, position where to continue reading */
/* the next line (will be updated), buffer to hold the */
/* length of the parsed line */
/* */
/* return value : NULL if no (more) lines are available else the pointer */
/* to the first character within the next line. */
/* */
/* description : This function implements a special "fgets" on a */
/* buffer. The buffer represents a complete file. The */
/* function has been written to allow a complete control */
/* and error detection on files. Another reason to write */
/* this function was the speed of this function. */
/* The function starts working at *size (set to 0 on the */
/* first call!) reading until a newline, a ^Z or the end */
/* of the file (buffer) is reached, whatever comes first. */
/* This start of the search is returned which is the */
/* start of the current line. Its length is put into the */
/* length buffer and start is updated allowing a */
/* consecutively calling to this function. */
/* */
/* note : The buffer must been 0-terminated! */
/*****************************************************************************/
const char *ParseLine(const char *buf,size_t bufsize,size_t *start,size_t *len)
{
size_t l = 0;
const char *ptr;
const char *retval = buf + *start;
if (*start >= bufsize) /* nothing more to do? */
return(NULL);
if ((ptr = strchr(retval,'\n')) == NULL) { /* no more newlines in the buf?*/
l = bufsize - *start; /* assume some characters left */
*start = bufsize;
while (isspace(*retval) && (l > 0)) { /* eat leading whitespace of the*/
l--; /* current line */
retval++;
}
if ((l == 0) || /* no characters */
((l == 1) && (*retval == '\x1A'))) /* ^Z is the only char on the */
return(NULL); /* line, that's OK */
*len = l;
return(retval);
}
/* lineend exists */
l = (ULONG) ptr - (ULONG) retval + 1; /* length including '\n' */
*start += l;
*len = l;
return(retval);
}
/*****************************************************************************/
/* function name : ReadInFile */
/* */
/* arguments : handle of an open file, head of the list where to */
/* append all valid lines. */
/* */
/* return value : 0 on success, errno otherwise */
/* */
/* description : reads a Crontabs file completely and builds a job */
/* list. The file must be opened in O_BINARY mode. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. The handle remains open. */
/*****************************************************************************/
int ReadInFile(int handle,LIST_ENTRY *head)
{
char *buf;
const char *ptr;
long flen;
size_t pos,len;
int err,htype;
ioctl(handle,FGETHTYPE,&htype);
if (htype == HT_DEV_NUL) /* we don't have to do anything */
return(0); /* while reading the "nul" device */
time(&currtime); /* needed to recompute the next */
currtime_tm = *localtime(&currtime); /* starting times of the jobs */
if ((flen = filelength(handle)) == -1l) /* device? */
return(errno);
if ((lseek(handle,0l,SEEK_SET)) == -1l) /* device? */
return(errno);
if (flen == 0) /* empty file? */
return(0);
if (flen >= MAX_CRONFILESIZE) /* potential mistake of the user? */
return(EACCES);
if ((buf = malloc((size_t) flen + 1)) == NULL) /* read the complete file */
return(ENOMEM); /* in one operation */
errno = 0;
if ((long) read(handle,buf,(size_t) flen) != flen) { /* do the operation */
if (errno == 0)
errno = EIO;
free(buf);
return(errno);
}
buf[(size_t) flen] = 0; /* terminate the buffer */
/* now we can use our fast function */
pos = 0;
while ((ptr = ParseLine(buf,(size_t) flen,&pos,&len)) != NULL) {
if ((err = InsertListEntry(ptr,len,1,head)) != 0) {
free(buf); /* error? free up the buffer and */
while (DeleteListEntry(head->next,head) == 0) /* delete all */
; /* already inserted jobs */
return(err);
}
}
free(buf);
return(0);
}
/*****************************************************************************/
/* function name : ComputeNextStartingTimes */
/* */
/* return value : time of the job starting time that has to start next */
/* */
/* description : computes the starting time for all periodically */
/* starting jobs and returns the time of the job which */
/* has to be started next. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the list. currtime must been set. */
/*****************************************************************************/
static time_t ComputeNextStartingTimes(void)
{
LIST_ENTRY *run = ListHead.next;
time_t retval = (time_t) -1,next,TomorrowMorning = (time_t) -1;
char timebuf[40];
while (run != NULL) {
if (!run->AtStartup && !run->AtExit && !run->Deletable &&
!run->IsComment) {
/* ignore all not periodically */
/* starting jobs */
if (run->Daily) {
if (TomorrowMorning == (time_t) -1) {
TomorrowMorning = currtime;
TomorrowMorning -= currtime_tm.tm_hour * 60 * 60 +
currtime_tm.tm_min * 60 +
currtime_tm.tm_sec;
/* back to the start of the current */
/* day */
TomorrowMorning += 24 * 60 * 60; /* add one day */
}
next = TomorrowMorning;
} else
next = ComputeNextStart(run,currtime,DAY_LOOKAHEAD);
if (PrintEachStartingTime) {
if (next == (time_t) -1)
strcpy(timebuf,"(unknown)");
else
GetTime(timebuf,sizeof(timebuf),next);
Message("DEBUG: start of \"%s\" at %s\n",
run->StartCmd,timebuf);
}
if (next != (time_t) -1) { /* valid time computed? */
if (retval == (time_t) -1)
retval = next;
else if (next < retval)
retval = next;
}
}
run = run->next;
}
return(retval);
}
/*****************************************************************************/
/* function name : RunJobs */
/* */
/* arguments : flags: run the AtStartup jobs, run the AtExit jobs, */
/* run Daily jobs */
/* */
/* description : runs the jobs with the given flag. If no flag is set */
/* the periodical jobs were checked and run if outdated. */
/* Jobs with the "Once" flag where deleted before */
/* startting the execution. */
/* */
/* note : there should only be one thread which has the */
/* exclusive access to the ListHead. */
/* currtime must been set. */
/* You should call ComputeNextStartingTimes after calling */
/* this function. */
/*****************************************************************************/
static void RunJobs(unsigned AtStartup,unsigned AtExit,unsigned Daily)
{
int start;
LIST_ENTRY *run = ListHead.next;
LIST_ENTRY *specialjob,*specialnext;
while (run != NULL) {
start = 0;
/* determine wether to start the job */
if (AtStartup && run->AtStartup)
start = 1;
else if (AtExit && run->AtExit)
start = 1;
else if (Daily && run->Daily)
start = 1;
else if (!AtStartup && !AtExit) /* periodically starting jobs */
if (!run->AtStartup && !run->AtExit && !run->Daily) /* right job */
/* type? */
if (run->NextStartTime <= currtime) /* job outdated? */
start = 1;
if (!start) {
run = run->next;
continue;
}
/* Well, we won't run into race */
/* conditions, look at this example: */
/* Some jobs: */
/* "Once Cronstart setboot /B" */
/* (other jobs that will be started */
/* too, loading costs some time) */
/* The first job will be started and */
/* the system shuts down before any */
/* writing will occur. On the next */
/* system start this will happen too.*/
/* Thus, we run into an endless */
/* booting loop. */
if (!run->Once) { /* no need to rewrite, simple */
StartProcess(run);
run = run->next;
continue;
}
specialnext = run->next; /* save the next entry to proceed */
if ((specialjob = ExtractListEntry(run,&ListHead)) == NULL) { /* ???? */
run = run->next;
continue;
}
ReWrite(); /* rewrite the new job list first!! */
JobsModified(); /* tell PM the changes */
run = specialnext; /* set new running variable */
StartProcess(specialjob); /* do the work */
CleanListEntry(specialjob); /* delete the extracted entry */
}
}
/*****************************************************************************/
/* function name : SetDebugFlags */
/* */
/* arguments : contents of the environment variable called */
/* CROND_DEBUG, buffer to hold the default maximum */
/* sleeping time of this thread */
/* */
/* description : evaluates the contents of the environment variable and */
/* sets the appropriate variables. */
/* Within the string we determine the following options: */
/* -s print time, this thread will sleep */
/* -t print each starting time of a job */
/* -w t set maximum sleeping time to t (in seconds) */
/* The options must be separated by spaces. */
/* The last value is copied to the argument buffer. */
/*****************************************************************************/
static void SetDebugFlags(const char *flags,time_t *DefaultWakeup)
{
const char *s;
char *ptr;
ULONG len,val = 0ul;
if (flags == NULL)
return;
BlockProcess(); /* NextWord uses static buffers! */
s = NextWord(flags,&len);
while (len > 0) {
if (strncmp("-s",s,(size_t) len) == 0) {
PrintSleepingTime = 1;
Message("DEBUG: the sleeping time will be displayed each going into "
"sleep\n");
}
else if (strncmp("-t",s,(size_t) len) == 0) {
PrintEachStartingTime = 1;
Message("DEBUG: each computed starting time will be displayed\n");
}
else if (strncmp("-w",s,2) == 0) {
if (len > 2) /* time follows -w? */
val = strtoul(s + 2,&ptr,10);
else {
s = NextWord(NULL,&len);
if (len == 0)
ptr = "x"; /* set ptr to "not space" */
else
val = strtoul(s,&ptr,10);
}
if ((val < 24ul * 3600ul) && ((*ptr == '\0') || (isspace(*ptr)))) {
*DefaultWakeup = (time_t) val;
Message("DEBUG: max. sleeping time is set to %lu seconds\n",val);
}
}
s = NextWord(NULL,&len);
}
UnBlockProcess();
}
/*****************************************************************************/
/* function name : CheckNewDay */
/* */
/* return value : bitcoded: 1 = run daily jobs */
/* 2 = problems writing data to disk, try this */
/* function again at a later time */
/* */
/* description : figures out if a new day has started since the last */
/* change. The value is saved in the profile data. If */
/* an error occurs, a 2 is or'ed into the return value. */
/*****************************************************************************/
static unsigned CheckNewDay(void)
{
ULONG NewDate = ((ULONG) (currtime_tm.tm_year + 1900) << 16) |
((ULONG) (currtime_tm.tm_mon) << 8) |
(ULONG) currtime_tm.tm_mday;
if (NewDate != LastDayStarted) {
LastDayStarted = NewDate;
ReWritePrfFlags |= REWRITE_DATE;
if (!ReWritePrf())
return(3); /* new day start and problems writing*/
/* profile data */
return(1); /* new day */
}
if (!ReWritePrf()) /* problems writing profile to disk? */
return(2); /* return problem error */
return(0);
}
/*****************************************************************************/
/* function name : CronThread */
/* */
/* arguments : not needed, must be void * */
/* */
/* description : this is a thread function. It makes all the work that */
/* needs to be done periodically. */
/* Stops running if GlobalStop is set. The function */
/* automatically disconnects the pipe or closes the */
/* socket if a timeout is set. Post the CronSem to awake */
/* this function. */
/* The maximum sleeping time is taken from WAKEUP or from */
/* a environment variable CROND_DEBUG. */
/* */
/* note : should be called via _beginthread */
/*****************************************************************************/
void CronDaemon(void *dummy)
{
time_t nextrestart,DefaultWakeup = WAKEUP;
ULONG posts,
maxpostcnt; /* expected posts to our semaphore */
int hlp;
unsigned dailycheck;
SetDebugFlags(getenv("CROND_DEBUG"),&DefaultWakeup);
signal(SIGCHLD,ChildDies); /* all signals should never occur */
signal(SIGINT,SIG_IGN); /* but it's not wrong to set handlers*/
signal(SIGBREAK,SIG_IGN); /* We won't be killed by a */
if (!setjmp(Continue)) { /* misprogrammed runtime system thus,*/
/* we set them. */
signal(SIGTERM,LocalStop);
time(&currtime);
currtime_tm = *localtime(&currtime);
DosSleep(1000); /* wait for the main thread to */
/* become ready */
if (ProgramFlags & PRG_RUNATSTARTUP) {
NewProgramStatus(IDS_StatusAtStartup);
dailycheck = CheckNewDay();
BlockProcess();
RunJobs(1,0,dailycheck & 1);
UnBlockProcess();
}
NewProgramStatus(IDS_StatusNormal);
maxpostcnt = 0; /* no posts expected */
while (!GlobalStop) {
time(&currtime);
currtime_tm = *localtime(&currtime);
CurrTimeIsOK = 1; /* Don't allow NewEntry to get its */
/* own time */
dailycheck = CheckNewDay();
if (currtime > AutoDisConnect) { /* timeout of the pipe? */
Message(Get(IDS_AutoCloseingPipe));
AutoDisConnect = (time_t) -1l;
DosDisConnectNPipe(pipehandle);
}
if (currtime > AutoCloseSocket) { /* timeout of the socket? */
Message(Get(IDS_AutoCloseingSocket));
AutoCloseSocket = (time_t) -1l;
hlp = CommSock;
CommSock = -1;
close(hlp);
}
ReapChildren();
BlockProcess();
RunJobs(0,0,dailycheck & 1); /* some jobs out of date? */
nextrestart = ComputeNextStartingTimes();
NewStartTime(nextrestart);
if (AutoReWrite)
ReWrite();
if ((AutoDisConnect != (time_t) -1) && (AutoDisConnect < nextrestart))
nextrestart = AutoDisConnect;
if ((AutoCloseSocket != (time_t) -1) && (AutoCloseSocket <
nextrestart))
nextrestart = AutoCloseSocket;
time(&currtime);
currtime_tm = *localtime(&currtime);
/* do we have problems with the */
/* writing of the profile? Try again */
/* after 10 seconds. */
if ((dailycheck & 2) && (nextrestart > currtime + 10))
nextrestart = currtime + 10;
CurrTimeIsOK = 0; /* Allow NewEntry to get its own time*/
posts = 0;
DosResetEventSem(CronSem,&posts);
UnBlockProcess();
if (GlobalStop)
break;
if ((nextrestart < currtime) || (posts > maxpostcnt)) {
/* Are there intermediate posts to */
/* our semaphore or do we spend too */
/* much time in the routine above? */
DosSleep(1); /* give up current time slice */
maxpostcnt = 0; /* no posts expected */
continue; /* and restart */
}
maxpostcnt = 1; /* we'll expect one post in case of */
/* a successfull WaitEventSem */
if (currtime + DefaultWakeup < nextrestart) /* sleep no longer than*/
nextrestart = currtime + DefaultWakeup; /* DefaultWakeup */
/* add one second. The timer may */
/* count incorrectly by some ticks. */
if (PrintSleepingTime)
Message("DEBUG: sleeping %lu seconds\n",
(unsigned long) (nextrestart - currtime + 1));
if (DosWaitEventSem(CronSem,
(ULONG) (nextrestart - currtime + 1) * 1000) != 0)
maxpostcnt = 0; /* don't expect posts in case of */
} /* timeouts or interrupts */
}
if (!setjmp(Continue))
if ((GlobalStop <= 1) && (ProgramFlags & PRG_RUNATSTARTUP)) {
NewProgramStatus(IDS_StatusAtExit);
ReapChildren();
BlockProcess();
RunJobs(0,1,0); /* no daily jobs! */
UnBlockProcess();
}
if (!setjmp(Continue))
if (AutoReWrite)
ReWrite();
if (GlobalStop <= 1) {
time(&nextrestart);
do {
DosSleep(500);
} while (ReapChildren() && (time(NULL) + 5 < nextrestart));
}
ShowStillRunnings();
StopPM();
}
/* RCS depending informations
*
* $Name: Version121 $
*
* $Log: tables.c $
* Revision 1.5 1995/10/18 09:46:10 Florian
* Some cosmetic changes.
* Editing of an entry is supported now.
* Daily and intervals introduced.
* Race conditions while reaping children and receiving signals eleminated.
*
* Revision 1.4 1995/03/30 16:50:20 Florian
* Debug code added.
* An ugly time computing error fixed.
*
* Revision 1.3 1995/03/15 09:07:34 Florian
* Some minor bugs fixed.
* TCP/IP support added.
*
* Revision 1.2 1995/02/20 12:53:23 Florian
* All dialogs are placed into a notebook.
* Some bugs fixed.
*
* Revision 1.1 1995/02/03 10:42:51 Florian
* Initial revision
*
*
*/
static char rcsid[] = "@(#)$Id: tables.c 1.5 1995/10/18 09:46:10 Florian Rel $";