home *** CD-ROM | disk | FTP | other *** search
- /* 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 $";
-