/***************************************************************/ /* */ /* MAIN.C */ /* */ /* Main program loop, as well as miscellaneous conversion */ /* routines, etc. */ /* */ /* This file is part of REMIND. */ /* Copyright (C) 1992-2024 by Dianne Skoll */ /* SPDX-License-Identifier: GPL-2.0-only */ /* */ /***************************************************************/ #define _XOPEN_SOURCE 600 #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #include #ifdef HAVE_LOCALE_H #include #endif #include #if defined(HAVE_SYS_TIME_H) #include #endif #include #include #ifdef REM_USE_WCHAR #include #include #endif #include "types.h" #include "protos.h" #include "globals.h" #include "err.h" static void DoReminders(void); /* Macro for simplifying common block so as not to litter code */ #define OUTPUT(c) do { if (output) { DBufPutc(output, c); } else { putchar(c); } } while(0) void exitfunc(void) { /* Kill any execution-time-limiter process */ unlimit_execution_time(); if (DebugFlag & DB_PARSE_EXPR) { fflush(stdout); fflush(stderr); UnsetAllUserFuncs(); print_expr_nodes_stats(); } } static void sigalrm(int) { if (ExpressionEvaluationTimeLimit) { ExpressionTimeLimitExceeded = 1; } } static void sigxcpu(int) { write(STDERR_FILENO, "\n\nmax-execution-time exceeded.\n\n", 32); _exit(1); } /***************************************************************/ /***************************************************************/ /** **/ /** Main Program Loop **/ /** **/ /***************************************************************/ /***************************************************************/ int main(int argc, char *argv[]) { int pid; struct sigaction act; #ifdef HAVE_SETLOCALE setlocale(LC_ALL, ""); #endif /* The very first thing to do is to set up ErrFp to be stderr */ ErrFp = stderr; /* Set up global vars */ ArgC = argc; ArgV = (char const **) argv; InitRemind(argc, (char const **) argv); act.sa_handler = sigalrm; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; if (sigaction(SIGALRM, &act, NULL) < 0) { fprintf(stderr, "%s: sigaction() failed: %s\n", argv[0], strerror(errno)); exit(1); } act.sa_handler = sigxcpu; act.sa_flags = SA_RESTART; sigemptyset(&act.sa_mask); if (sigaction(SIGXCPU, &act, NULL) < 0) { fprintf(stderr, "%s: sigaction() failed: %s\n", argv[0], strerror(errno)); exit(1); } DBufInit(&(LastTrigger.tags)); ClearLastTriggers(); atexit(exitfunc); if (DoCalendar || (DoSimpleCalendar && (!NextMode || PsCal))) { ProduceCalendar(); return 0; } /* Are we purging old reminders? Then just run through the loop once! */ if (PurgeMode) { DoReminders(); return 0; } /* Not doing a calendar. Do the regular remind loop */ ShouldCache = (Iterations > 1); while (Iterations--) { DoReminders(); if (DebugFlag & DB_DUMP_VARS) { DumpVarTable(); DumpSysVarByName(NULL); } if (!Hush) { if (DestroyOmitContexts(1)) Eprint("%s", ErrMsg[E_PUSH_NOPOP]); if (!Daemon && !NextMode && !NumTriggered && !NumQueued) { printf("%s\n", ErrMsg[E_NOREMINDERS]); } else if (!Daemon && !NextMode && !NumTriggered) { printf(ErrMsg[M_QUEUED], NumQueued); } } /* If there are sorted reminders, handle them */ if (SortByDate) IssueSortedReminders(); /* If there are any background reminders queued up, handle them */ if (NumQueued || Daemon) { if (DontFork) { HandleQueuedReminders(); return 0; } else { pid = fork(); if (pid == 0) { HandleQueuedReminders(); return 0; } if (pid == -1) { fprintf(ErrFp, "%s", ErrMsg[E_CANTFORK]); return 1; } } } if (Iterations) { PerIterationInit(); DSEToday++; } } return 0; } void PurgeEchoLine(char const *fmt, ...) { va_list argptr; va_start(argptr, fmt); if (PurgeFP != NULL) { (void) vfprintf(PurgeFP, fmt, argptr); } va_end(argptr); } void PerIterationInit(void) { ClearGlobalOmits(); DestroyOmitContexts(1); DestroyVars(0); DefaultColorR = -1; DefaultColorG = -1; DefaultColorB = -1; NumTriggered = 0; ClearLastTriggers(); } /***************************************************************/ /* */ /* DoReminders */ /* */ /* The normal case - we're not doing a calendar. */ /* */ /***************************************************************/ static void DoReminders(void) { int r; Token tok; char const *s; Parser p; int purge_handled; DidMsgReminder = 0; if (!UseStdin) { FileAccessDate = GetAccessDate(InitialFile); } else { FileAccessDate = DSEToday; } if (FileAccessDate < 0) { fprintf(ErrFp, "%s: `%s': %s.\n", ErrMsg[E_CANTACCESS], InitialFile, strerror(errno)); exit(EXIT_FAILURE); } r=IncludeFile(InitialFile); if (r) { fprintf(ErrFp, "%s %s: %s\n", ErrMsg[E_ERR_READING], InitialFile, ErrMsg[r]); exit(EXIT_FAILURE); } while(1) { r = ReadLine(); if (r == E_EOF) return; if (r) { Eprint("%s: %s", ErrMsg[E_ERR_READING], ErrMsg[r]); exit(EXIT_FAILURE); } s = FindInitialToken(&tok, CurLine); /* Should we ignore it? */ if (NumIfs && tok.type != T_If && tok.type != T_Else && tok.type != T_EndIf && tok.type != T_IfTrig && ShouldIgnoreLine()) { /*** IGNORE THE LINE ***/ if (PurgeMode) { if (strncmp(CurLine, "#!P", 3)) { PurgeEchoLine("%s\n", CurLine); } } } else { purge_handled = 0; /* Create a parser to parse the line */ CreateParser(s, &p); switch(tok.type) { case T_Empty: case T_Comment: if (!strncmp(CurLine, "#!P", 3)) { purge_handled = 1; } break; case T_Rem: r=DoRem(&p); purge_handled = 1; break; case T_ErrMsg: r=DoErrMsg(&p); break; case T_If: r=DoIf(&p); break; case T_IfTrig: r=DoIfTrig(&p); break; case T_Else: r=DoElse(&p); break; case T_EndIf: r=DoEndif(&p); break; case T_Include: case T_IncludeR: /* In purge mode, include closes file, so we need to echo it here! */ if (PurgeMode) { PurgeEchoLine("%s\n", CurLine); } r=DoInclude(&p, tok.type); purge_handled = 1; break; case T_IncludeCmd: /* In purge mode, include closes file, so we need to echo it here! */ if (PurgeMode) { PurgeEchoLine("%s\n", CurLine); } r=DoIncludeCmd(&p); purge_handled = 1; break; case T_Exit: DoExit(&p); break; case T_Flush: r=DoFlush(&p); break; case T_Set: r=DoSet(&p); break; case T_Fset: r=DoFset(&p); break; case T_Funset: r=DoFunset(&p); break; case T_UnSet: r=DoUnset(&p); break; case T_Clr: r=DoClear(&p); break; case T_Debug: r=DoDebug(&p); break; case T_Dumpvars: r=DoDump(&p); break; case T_Banner: r=DoBanner(&p); break; case T_Omit: r=DoOmit(&p); if (r == E_PARSE_AS_REM) { DestroyParser(&p); CreateParser(s, &p); r=DoRem(&p); purge_handled = 1; } break; case T_Pop: r=PopOmitContext(&p); break; case T_Preserve: r=DoPreserve(&p); break; case T_Push: r=PushOmitContext(&p); break; case T_Expr: r = DoExpr(&p); break; case T_RemType: if (tok.val == RUN_TYPE) { r=DoRun(&p); } else { DestroyParser(&p); CreateParser(CurLine, &p); r=DoRem(&p); purge_handled = 1; } break; /* If we don't recognize the command, do a REM by default */ default: DestroyParser(&p); CreateParser(CurLine, &p); purge_handled = 1; r=DoRem(&p); break; } if (r && (!Hush || r != E_RUN_DISABLED)) { Eprint("%s", ErrMsg[r]); } if (PurgeMode) { if (!purge_handled) { PurgeEchoLine("%s\n", CurLine); } else { if (r) { PurgeEchoLine("#!P! Could not parse next line: %s\n", ErrMsg[r]); PurgeEchoLine("%s\n", CurLine); } } } /* Destroy the parser - free up resources it may be tying up */ DestroyParser(&p); } } } /***************************************************************/ /* */ /* DSE */ /* */ /* DSE stands for "Days Since Epoch"; the Remind epoch is */ /* midnight on 1990-01-01 */ /* */ /* Given day, month, year, return DSE date in days since */ /* 1 January 1990. */ /* */ /***************************************************************/ int DSE(int year, int month, int day) { int y1 = BASE-1, y2 = year-1; int y4 = (y2 / 4) - (y1 / 4); /* Correct for leap years */ int y100 = (y2 / 100) - (y1 / 100); /* Don't count multiples of 100... */ int y400 = (y2 / 400) - (y1 / 400); /* ... but do count multiples of 400 */ return 365 * (year-BASE) + y4 - y100 + y400 + MonthIndex[IsLeapYear(year)][month] + day - 1; } /***************************************************************/ /* */ /* FromDSE */ /* */ /* Convert a DSE date to year, month, day. You may supply */ /* NULL for y, m or d if you're not interested in that value */ /* */ /***************************************************************/ void FromDSE(int dse, int *y, int *m, int *d) { int try_yr = (dse / 365) + BASE; int try_mon = 0; int t; /* Inline code for speed... */ int y1 = BASE-1, y2 = try_yr-1; int y4 = (y2 / 4) - (y1 / 4); /* Correct for leap years */ int y100 = (y2 / 100) - (y1 / 100); /* Don't count multiples of 100... */ int y400 = (y2 / 400) - (y1 / 400); /* ... but do count multiples of 400 */ int try_dse= 365 * (try_yr-BASE) + y4 - y100 + y400; while (try_dse > dse) { try_yr--; try_dse -= DaysInYear(try_yr); } dse -= try_dse; t = DaysInMonth(try_mon, try_yr); while (dse >= t) { dse -= t; try_mon++; t = DaysInMonth(try_mon, try_yr); } if (y) { *y = try_yr; } if (m) { *m = try_mon; } if (d) { *d = dse + 1; } return; } int JulianToGregorianOffset(int y, int m) { int offset = 13; int centuries; int four_centuries; if (y >= 2100) { centuries = (y - 2000) / 100; four_centuries = (y - 2000) / 400; offset += centuries - four_centuries; if (!(y%100) && (y % 400)) { if (m < 2) { offset--; /* Offset increments in March */ } } } return offset; } /***************************************************************/ /* */ /* ParseChar */ /* */ /* Parse a character from a parse pointer. If peek is non- */ /* zero, then just peek ahead; don't advance pointer. */ /* */ /***************************************************************/ int ParseChar(ParsePtr p, int *err, int peek) { Value val; int r; *err = 0; if (p->tokenPushed && *p->tokenPushed) { if (peek) return *p->tokenPushed; else { r = *p->tokenPushed++; if (!r) { DBufFree(&p->pushedToken); p->tokenPushed = NULL; } return r; } } while(1) { if (p->isnested) { if (*(p->epos)) { if (peek) { return *(p->epos); } else { return *(p->epos++); } } free((void *) p->etext); /* End of substituted expression */ p->etext = NULL; p->epos = NULL; p->isnested = 0; } if (!*(p->pos)) { return 0; } if (*p->pos != BEG_OF_EXPR || !p->allownested) { if (peek) { return *(p->pos); } else { return *(p->pos++); } } /* Convert [[ to just a literal [ */ if (*p->pos == BEG_OF_EXPR && *(p->pos+1) == BEG_OF_EXPR) { if (peek) { return *(p->pos+1); } else { p->pos++; return *(p->pos++); } } p->expr_happened = 1; p->pos++; r = EvalExpr(&(p->pos), &val, p); if (r) { *err = r; DestroyParser(p); return 0; } while(*p->pos && (isempty(*p->pos))) { p->pos++; } if (*p->pos != END_OF_EXPR) { if (*p->pos) { *err = E_PARSE_ERR; } else { *err = E_MISS_END; } DestroyParser(p); DestroyValue(val); return 0; } p->pos++; r = DoCoerce(STR_TYPE, &val); if (r) { *err = r; return 0; } p->etext = val.v.str; val.type = ERR_TYPE; /* So it's not accidentally destroyed! */ p->isnested = 1; p->epos = p->etext; } } /***************************************************************/ /* */ /* ParseNonSpaceChar */ /* */ /* Parse the next non-space character. */ /* */ /***************************************************************/ int ParseNonSpaceChar(ParsePtr p, int *err, int peek) { int ch; ch = ParseChar(p, err, 1); if (*err) return 0; while (isempty(ch)) { ParseChar(p, err, 0); /* Guaranteed to work */ ch = ParseChar(p, err, 1); if (*err) return 0; } if (!peek) ch = ParseChar(p, err, 0); /* Guaranteed to work */ return ch; } /***************************************************************/ /* */ /* ParseToken */ /* */ /* Parse a token delimited by whitespace. */ /* */ /***************************************************************/ int ParseToken(ParsePtr p, DynamicBuffer *dbuf) { int c, err; DBufFree(dbuf); c = ParseChar(p, &err, 0); if (err) return err; while (c && isempty(c)) { c = ParseChar(p, &err, 0); if (err) return err; } if (!c) return OK; while (c && !isempty(c)) { if (DBufPutc(dbuf, c) != OK) { DBufFree(dbuf); return E_NO_MEM; } c = ParseChar(p, &err, 0); if (err) { DBufFree(dbuf); return err; } } return OK; } /***************************************************************/ /* */ /* ParseIdentifier */ /* */ /* Parse a valid identifier - ie, alpha or underscore */ /* followed by alphanum. Return E_BAD_ID if identifier is */ /* invalid. */ /* */ /***************************************************************/ int ParseIdentifier(ParsePtr p, DynamicBuffer *dbuf) { int c, err; DBufFree(dbuf); c = ParseChar(p, &err, 0); if (err) return err; while (c && isempty(c)) { c = ParseChar(p, &err, 0); if (err) return err; } if (!c) return E_EOLN; if (c != '$' && c != '_' && !isalpha(c)) return E_BAD_ID; if (DBufPutc(dbuf, c) != OK) { DBufFree(dbuf); return E_NO_MEM; } while (1) { c = ParseChar(p, &err, 1); if (err) { DBufFree(dbuf); return err; } if (c != '_' && !isalnum(c)) return OK; c = ParseChar(p, &err, 0); /* Guaranteed to work */ if (DBufPutc(dbuf, c) != OK) { DBufFree(dbuf); return E_NO_MEM; } } } /***************************************************************/ /* */ /* ParseExpr */ /* */ /* We are expecting an expression here. Parse it and return */ /* the value node tree. */ /* */ /***************************************************************/ expr_node * ParseExpr(ParsePtr p, int *r) { int bracketed = 0; expr_node *node; if (p->isnested) { *r = E_PARSE_ERR; /* Can't nest expressions */ return NULL; } if (!p->pos) { *r = E_PARSE_ERR; /* Missing expression */ return NULL; } while (isempty(*p->pos)) (p->pos)++; if (!*(p->pos)) { *r = E_EOLN; return NULL; } if (*p->pos == BEG_OF_EXPR) { (p->pos)++; bracketed = 1; } node = parse_expression(&(p->pos), r, NULL); if (*r) { return free_expr_tree(node); } if (bracketed) { if (*p->pos != END_OF_EXPR) { if (*p->pos) { *r = E_PARSE_ERR; } else { *r = E_MISS_END; } return free_expr_tree(node); } (p->pos)++; } return node; } /***************************************************************/ /* */ /* EvaluateExpr */ /* */ /* We are expecting an expression here. Evaluate it and */ /* return the value. */ /* */ /***************************************************************/ int EvaluateExpr(ParsePtr p, Value *v) { int r; int nonconst = 0; expr_node *node = ParseExpr(p, &r); if (r != OK) { return r; } if (!node) { return E_SWERR; } r = evaluate_expression(node, NULL, v, &nonconst); free_expr_tree(node); if (r) return r; if (nonconst) { p->nonconst_expr = 1; } return OK; } /***************************************************************/ /* */ /* Wprint - print a warning message. */ /* */ /***************************************************************/ void Wprint(char const *fmt, ...) { va_list argptr; if (FileName) { if (strcmp(FileName, "-")) (void) fprintf(ErrFp, "%s(%d): ", FileName, LineNo); else (void) fprintf(ErrFp, "-stdin-(%d): ", LineNo); } va_start(argptr, fmt); (void) vfprintf(ErrFp, fmt, argptr); (void) fputc('\n', ErrFp); va_end(argptr); return; } /***************************************************************/ /* */ /* Eprint - print an error message. */ /* */ /***************************************************************/ void Eprint(char const *fmt, ...) { va_list argptr; char const *fname; /* Check if more than one error msg. from this line */ if (!FreshLine && !ShowAllErrors) return; if (!FileName) { return; } if (strcmp(FileName, "-")) { fname = FileName; } else { fname = "-stdin-"; } if (FreshLine) { (void) fprintf(ErrFp, "%s(%d): ", fname, LineNo); } else { fprintf(ErrFp, " "); } va_start(argptr, fmt); (void) vfprintf(ErrFp, fmt, argptr); (void) fputc('\n', ErrFp); va_end(argptr); if (print_callstack(ErrFp)) { (void) fprintf(ErrFp, "\n"); } if (FreshLine) { if (DebugFlag & DB_PRTLINE) OutputLine(ErrFp); } FreshLine = 0; } /***************************************************************/ /* */ /* OutputLine */ /* */ /* Output a line from memory buffer to a file pointer. This */ /* simply involves escaping newlines. */ /* */ /***************************************************************/ void OutputLine(FILE *fp) { char const *s = CurLine; char c = 0; while (*s) { if (*s == '\n') putc('\\', fp); putc(*s, fp); c = *s++; } if (c != '\n') putc('\n', fp); } /***************************************************************/ /* */ /* CreateParser */ /* */ /* Create a parser given a string buffer */ /* */ /***************************************************************/ void CreateParser(char const *s, ParsePtr p) { p->text = s; p->pos = s; p->isnested = 0; p->epos = NULL; p->etext = NULL; p->allownested = 1; p->tokenPushed = NULL; p->expr_happened = 0; p->nonconst_expr = 0; DBufInit(&p->pushedToken); } /***************************************************************/ /* */ /* DestroyParser */ /* */ /* Destroy a parser, freeing up resources used. */ /* */ /***************************************************************/ void DestroyParser(ParsePtr p) { if (p->isnested && p->etext) { free((void *) p->etext); p->etext = NULL; p->isnested = 0; } DBufFree(&p->pushedToken); } /***************************************************************/ /* */ /* PushToken - one level of token pushback. This is */ /* on a per-parser basis. */ /* */ /***************************************************************/ int PushToken(char const *tok, ParsePtr p) { DBufFree(&p->pushedToken); if (DBufPuts(&p->pushedToken, tok) != OK || DBufPutc(&p->pushedToken, ' ') != OK) { DBufFree(&p->pushedToken); return E_NO_MEM; } p->tokenPushed = DBufValue(&p->pushedToken); return OK; } /***************************************************************/ /* */ /* SystemTime */ /* */ /* Return the system time in seconds past midnight */ /* */ /***************************************************************/ int SystemTime(int realtime) { time_t now; struct tm *t; if (!realtime && (SysTime != -1)) return SysTime; now = time(NULL); t = localtime(&now); return t->tm_hour * 3600L + t->tm_min * 60L + t->tm_sec; } /***************************************************************/ /* */ /* MinutesPastMidnight */ /* */ /* Return the system time in minutes past midnight */ /* */ /***************************************************************/ int MinutesPastMidnight(int realtime) { return (SystemTime(realtime) / 60); } /***************************************************************/ /* */ /* SystemDate */ /* */ /* Obtains today's date. Returns DSE date or -1 for */ /* failure. (Failure happens if sys date is before BASE */ /* year.) */ /* */ /***************************************************************/ int SystemDate(int *y, int *m, int *d) { time_t now; struct tm *t; now = time(NULL); t = localtime(&now); *d = t->tm_mday; *m = t->tm_mon; *y = t->tm_year + 1900; return DSE(*y, *m, *d); } /***************************************************************/ /* */ /* DoIf - handle the IF command. */ /* */ /***************************************************************/ int DoIf(ParsePtr p) { Value v; int r; unsigned syndrome; if ((size_t) NumIfs >= IF_NEST) return E_NESTED_IF; if (ShouldIgnoreLine()) syndrome = IF_TRUE | BEFORE_ELSE; else { if ( (r = EvaluateExpr(p, &v)) ) { syndrome = IF_TRUE | BEFORE_ELSE; Eprint("%s", ErrMsg[r]); } else if ( (v.type != STR_TYPE && v.v.val) || (v.type == STR_TYPE && strcmp(v.v.str, "")) ) { syndrome = IF_TRUE | BEFORE_ELSE; } else { syndrome = IF_FALSE | BEFORE_ELSE; if (PurgeMode) { PurgeEchoLine("%s\n", "#!P: The next IF evaluated false..."); PurgeEchoLine("%s\n", "#!P: REM statements in IF block not checked for purging."); } } } IfLinenos[NumIfs] = LineNo; NumIfs++; IfFlags &= ~(IF_MASK << (2*NumIfs - 2)); IfFlags |= syndrome << (2 * NumIfs - 2); if (ShouldIgnoreLine()) return OK; return VerifyEoln(p); } /***************************************************************/ /* */ /* DoElse - handle the ELSE command. */ /* */ /***************************************************************/ int DoElse(ParsePtr p) { unsigned syndrome; int was_ignoring = ShouldIgnoreLine(); if (!NumIfs) return E_ELSE_NO_IF; syndrome = IfFlags >> (2 * NumIfs - 2); if ((syndrome & IF_ELSE_MASK) == AFTER_ELSE) return E_ELSE_NO_IF; IfFlags |= AFTER_ELSE << (2 * NumIfs - 2); if (PurgeMode && ShouldIgnoreLine() && !was_ignoring) { PurgeEchoLine("%s\n", "#!P: The previous IF evaluated true."); PurgeEchoLine("%s\n", "#!P: REM statements in ELSE block not checked for purging"); } return VerifyEoln(p); } /***************************************************************/ /* */ /* DoEndif - handle the Endif command. */ /* */ /***************************************************************/ int DoEndif(ParsePtr p) { if (!NumIfs) return E_ENDIF_NO_IF; NumIfs--; return VerifyEoln(p); } /***************************************************************/ /* */ /* DoIfTrig */ /* */ /* Handle the IFTRIG command. */ /* */ /***************************************************************/ int DoIfTrig(ParsePtr p) { int r, err; unsigned syndrome; Trigger trig; TimeTrig tim; int dse; if ((size_t) NumIfs >= IF_NEST) return E_NESTED_IF; if (ShouldIgnoreLine()) syndrome = IF_TRUE | BEFORE_ELSE; else { if ( (r=ParseRem(p, &trig, &tim)) ) return r; if (trig.typ != NO_TYPE) return E_PARSE_ERR; dse = ComputeTrigger(trig.scanfrom, &trig, &tim, &r, 1); if (r) { if (r != E_CANT_TRIG || !trig.maybe_uncomputable) { if (!Hush || r != E_RUN_DISABLED) { Eprint("%s", ErrMsg[r]); } } syndrome = IF_FALSE | BEFORE_ELSE; } else { if (ShouldTriggerReminder(&trig, &tim, dse, &err)) { syndrome = IF_TRUE | BEFORE_ELSE; } else { syndrome = IF_FALSE | BEFORE_ELSE; } } if (syndrome == (IF_FALSE | BEFORE_ELSE) && PurgeMode) { PurgeEchoLine("%s\n", "#!P: The next IFTRIG did not trigger."); PurgeEchoLine("%s\n", "#!P: REM statements in IFTRIG block not checked for purging."); } FreeTrig(&trig); } NumIfs++; IfFlags &= ~(IF_MASK << (2*NumIfs - 2)); IfFlags |= syndrome << (2 * NumIfs - 2); return OK; } /***************************************************************/ /* */ /* ShouldIgnoreLine - given the current state of the IF */ /* stack, should we ignore the current line? */ /* */ /***************************************************************/ int ShouldIgnoreLine(void) { register int i, syndrome; /* Algorithm - go from outer to inner, and if any should be ignored, then ignore the whole. */ for (i=0; i> (i*2)) & IF_MASK; if (syndrome == IF_TRUE+AFTER_ELSE || syndrome == IF_FALSE+BEFORE_ELSE) return 1; } return 0; } /***************************************************************/ /* */ /* VerifyEoln */ /* */ /* Verify that current line contains no more tokens. */ /* */ /***************************************************************/ int VerifyEoln(ParsePtr p) { int r; DynamicBuffer buf; DBufInit(&buf); if ( (r = ParseToken(p, &buf)) ) return r; if (*DBufValue(&buf) && (*DBufValue(&buf) != '#') && (*DBufValue(&buf) != ';')) { Eprint("%s: `%s'", ErrMsg[E_EXPECTING_EOL], DBufValue(&buf)); DBufFree(&buf); return E_EXTRANEOUS_TOKEN; } DBufFree(&buf); return OK; } /***************************************************************/ /* */ /* DoDebug */ /* */ /* Set the debug options under program control. */ /* */ /***************************************************************/ int DoDebug(ParsePtr p) { int err; int ch; int val=1; while(1) { ch = ParseChar(p, &err, 0); if (err) return err; switch(ch) { case '#': case ';': case 0: return OK; case ' ': case '\t': break; case '+': val = 1; break; case '-': val = 0; break; case 'e': case 'E': if (val) DebugFlag |= DB_ECHO_LINE; else DebugFlag &= ~DB_ECHO_LINE; break; case 's': case 'S': if (val) DebugFlag |= DB_PARSE_EXPR; else DebugFlag &= ~DB_PARSE_EXPR; break; case 'x': case 'X': if (val) DebugFlag |= DB_PRTEXPR; else DebugFlag &= ~DB_PRTEXPR; break; case 't': case 'T': if (val) DebugFlag |= DB_PRTTRIG; else DebugFlag &= ~DB_PRTTRIG; break; case 'v': case 'V': if (val) DebugFlag |= DB_DUMP_VARS; else DebugFlag &= ~DB_DUMP_VARS; break; case 'l': case 'L': if (val) DebugFlag |= DB_PRTLINE; else DebugFlag &= ~DB_PRTLINE; break; case 'f': case 'F': if (val) DebugFlag |= DB_TRACE_FILES; else DebugFlag &= ~DB_TRACE_FILES; break; } } } /***************************************************************/ /* */ /* DoBanner */ /* */ /* Set the banner to be printed just before the first */ /* reminder is issued. */ /* */ /***************************************************************/ int DoBanner(ParsePtr p) { int err; int c; DynamicBuffer buf; DBufInit(&buf); c = ParseChar(p, &err, 0); if (err) return err; while (isempty(c)) { c = ParseChar(p, &err, 0); if (err) return err; } if (!c) return E_EOLN; while(c) { if (DBufPutc(&buf, c) != OK) return E_NO_MEM; c = ParseChar(p, &err, 0); if (err) { DBufFree(&buf); return err; } } DBufFree(&Banner); err = DBufPuts(&Banner, DBufValue(&buf)); DBufFree(&buf); return err; } /***************************************************************/ /* */ /* DoRun */ /* */ /* Enable or disable the RUN command under program control */ /* */ /***************************************************************/ int DoRun(ParsePtr p) { int r; DynamicBuffer buf; DBufInit(&buf); if ( (r=ParseToken(p, &buf)) ) return r; /* Only allow RUN ON in top-level script */ if (! StrCmpi(DBufValue(&buf), "ON")) { if (TopLevel()) RunDisabled &= ~RUN_SCRIPT; } /* But allow RUN OFF anywhere */ else if (! StrCmpi(DBufValue(&buf), "OFF")) RunDisabled |= RUN_SCRIPT; else { DBufFree(&buf); return E_PARSE_ERR; } DBufFree(&buf); return VerifyEoln(p); } /***************************************************************/ /* */ /* DoExpr */ /* */ /* Enable or disable expression evaluation */ /* */ /***************************************************************/ int DoExpr(ParsePtr p) { int r; DynamicBuffer buf; DBufInit(&buf); if ( (r=ParseToken(p, &buf)) ) return r; /* Only allow EXPR ON in top-level script */ if (! StrCmpi(DBufValue(&buf), "ON")) { if (TopLevel()) ExpressionEvaluationDisabled = 0; } /* But allow EXPR OFF anywhere */ else if (! StrCmpi(DBufValue(&buf), "OFF")) ExpressionEvaluationDisabled = 1; else { DBufFree(&buf); return E_PARSE_ERR; } DBufFree(&buf); return VerifyEoln(p); } /***************************************************************/ /* */ /* DoFlush */ /* */ /* Flush stdout and stderr */ /* */ /***************************************************************/ int DoFlush(ParsePtr p) { fflush(stdout); fflush(stderr); return VerifyEoln(p); } /***************************************************************/ /* */ /* DoExit */ /* */ /* Handle the EXIT command. */ /* */ /***************************************************************/ void DoExit(ParsePtr p) { int r; Value v; if (PurgeMode) return; r = EvaluateExpr(p, &v); if (r || v.type != INT_TYPE) exit(99); exit(v.v.val); } /***************************************************************/ /* */ /* DoErrMsg */ /* */ /* Issue an error message under program control. */ /* */ /***************************************************************/ int DoErrMsg(ParsePtr p) { TimeTrig tt; Trigger t; int r; char const *s; DynamicBuffer buf; if (PurgeMode) return OK; DBufInit(&buf); t.typ = MSG_TYPE; tt.ttime = SystemTime(0) / 60; if ( (r=DoSubst(p, &buf, &t, &tt, DSEToday, NORMAL_MODE)) ) { return r; } s = DBufValue(&buf); while (isempty(*s)) s++; fprintf(ErrFp, "%s\n", s); DBufFree(&buf); return OK; } /***************************************************************/ /* */ /* CalcMinsFromUTC */ /* */ /* Attempt to calculate the minutes from UTC for a specific */ /* date. */ /* */ /***************************************************************/ /* The array FoldArray[2][7] contains sample years which begin on the specified weekday. For example, FoldArray[0][2] is a non-leap year beginning on Wednesday, and FoldArray[1][5] is a leap year beginning on Saturday. Used to fold back dates which are too high for the standard Unix representation. NOTE: This implies that you cannot set BASE > 2001!!!!! */ static int FoldArray[2][7] = { {2001, 2002, 2003, 2009, 2010, 2005, 2006}, {2024, 2008, 2020, 2004, 2016, 2000, 2012} }; int CalcMinsFromUTC(int dse, int tim, int *mins, int *isdst) { /* Convert dse and tim to an Unix tm struct */ int yr, mon, day; int tdiff; struct tm local, utc, *temp; time_t loc_t, utc_t; int isdst_tmp; FromDSE(dse, &yr, &mon, &day); /* If the year is greater than 2037, some Unix machines have problems. Fold it back to a "similar" year and trust that the UTC calculations are still valid... */ if (FoldYear && yr>2037) { dse = DSE(yr, 0, 1); yr = FoldArray[IsLeapYear(yr)][dse%7]; } local.tm_sec = 0; local.tm_min = tim % 60; local.tm_hour = tim / 60; local.tm_mday = day; local.tm_mon = mon; local.tm_year = yr-1900; local.tm_isdst = -1; /* We don't know whether or not dst is in effect */ /* Horrible contortions to get minutes from UTC portably */ loc_t = mktime(&local); if (loc_t == -1) return 1; isdst_tmp = local.tm_isdst; local.tm_isdst = 0; loc_t = mktime(&local); if (loc_t == -1) return 1; temp = gmtime(&loc_t); utc = *temp; utc.tm_isdst = 0; utc_t = mktime(&utc); if (utc_t == -1) return 1; /* Compute difference between local time and UTC in seconds. Be careful, since time_t might be unsigned. */ tdiff = (int) difftime(loc_t, utc_t); if (isdst_tmp) tdiff += 60*60; if (mins) *mins = (int)(tdiff / 60); if (isdst) *isdst = isdst_tmp; return 0; } static char const *OutputEscapeSequences(char const *s, int print, DynamicBuffer *output) { while (*s == 0x1B && *(s+1) == '[') { if (print) OUTPUT(*s); s++; if (print) OUTPUT(*s); s++; while (*s && (*s < 0x40 || *s > 0x7E)) { if (print) OUTPUT(*s); s++; } if (*s) { if (print) OUTPUT(*s); s++; } } return s; } #ifdef REM_USE_WCHAR #define ISWBLANK(c) (iswspace(c) && (c) != '\n') static wchar_t const *OutputEscapeSequencesWS(wchar_t const *s, int print, DynamicBuffer *output) { while (*s == 0x1B && *(s+1) == '[') { if (print) PutWideChar(*s, output); s++; if (print) PutWideChar(*s, output); s++; while (*s && (*s < 0x40 || *s > 0x7E)) { if (print) PutWideChar(*s, output); s++; } if (*s) { if (print) PutWideChar(*s, output); s++; } } return s; } static void FillParagraphWCAux(wchar_t const *s, DynamicBuffer *output) { int line = 0; int i, j; int doublespace = 1; int pendspace; int len; wchar_t const *t; int roomleft; /* Start formatting */ while(1) { /* If it's a carriage return, output it and start new paragraph */ if (*s == '\n') { OUTPUT('\n'); s++; line = 0; while(ISWBLANK(*s)) s++; continue; } if (!*s) { return; } /* Over here, we're at the beginning of a line. Emit the correct number of spaces */ j = line ? SubsIndent : FirstIndent; for (i=0; i= MINUTES_PER_DAY) { loctime -= MINUTES_PER_DAY; locdate++; } *utcdate = locdate; *utctime = loctime; } /***************************************************************/ /* */ /* UTCToLocal */ /* */ /* Convert a UTC date/time to a local date/time. */ /* */ /***************************************************************/ void UTCToLocal(int utcdate, int utctime, int *locdate, int *loctime) { int diff; int dummy; /* Hack -- not quite right when DST changes. */ if (!CalculateUTC || CalcMinsFromUTC(utcdate, utctime, &diff, &dummy)) diff=MinsFromUTC; utctime += diff; if (utctime < 0) { utctime += MINUTES_PER_DAY; utcdate--; } else if (utctime >= MINUTES_PER_DAY) { utctime -= MINUTES_PER_DAY; utcdate++; } *locdate = utcdate; *loctime = utctime; } /***************************************************************/ /* */ /* SigIntHandler */ /* */ /* For debugging purposes, when sent a SIGINT, we print the */ /* contents of the queue. This does NOT work when the -f */ /* command-line flag is supplied. */ /* */ /***************************************************************/ static sig_atomic_t got_sigint = 0; void SigIntHandler(int d) { UNUSED(d); got_sigint = 1; } int GotSigInt(void) { if (got_sigint) { got_sigint = 0; return 1; } return 0; } void AppendTag(DynamicBuffer *buf, char const *s) { if (*(DBufValue(buf))) { DBufPutc(buf, ','); } DBufPuts(buf, s); } void FreeTrig(Trigger *t) { DBufFree(&(t->tags)); } void ClearLastTriggers(void) { LastTrigger.expired = 0; LastTrigger.wd = NO_WD; LastTrigger.d = NO_DAY; LastTrigger.m = NO_MON; LastTrigger.y = NO_YR; LastTrigger.back = NO_BACK; LastTrigger.delta = NO_DELTA; LastTrigger.rep = NO_REP; LastTrigger.localomit = NO_WD; LastTrigger.skip = NO_SKIP; LastTrigger.until = NO_UNTIL; LastTrigger.typ = NO_TYPE; LastTrigger.once = NO_ONCE; LastTrigger.scanfrom = NO_DATE; LastTrigger.from = NO_DATE; LastTrigger.priority = DefaultPrio; LastTrigger.sched[0] = 0; LastTrigger.warn[0] = 0; LastTrigger.omitfunc[0] = 0; LastTrigger.passthru[0] = 0; DBufFree(&(LastTrigger.tags)); LastTimeTrig.ttime = NO_TIME; LastTimeTrig.delta = NO_DELTA; LastTimeTrig.rep = NO_REP; LastTimeTrig.duration = NO_TIME; } void SaveAllTriggerInfo(Trigger const *t, TimeTrig const *tt, int trigdate, int trigtime, int valid) { SaveLastTrigger(t); SaveLastTimeTrig(tt); LastTriggerDate = trigdate; LastTriggerTime = trigtime; LastTrigValid = valid; } void SaveLastTrigger(Trigger const *t) { DBufFree(&(LastTrigger.tags)); memcpy(&LastTrigger, t, sizeof(LastTrigger)); DBufInit(&(LastTrigger.tags)); DBufPuts(&(LastTrigger.tags), DBufValue(&(t->tags))); } void SaveLastTimeTrig(TimeTrig const *t) { memcpy(&LastTimeTrig, t, sizeof(LastTimeTrig)); } /* Wrapper to ignore warnings about ignoring return value of system() Also redirects stdin and stdout to /dev/null for queued reminders */ void System(char const *cmd, int is_queued) { int r; pid_t kid; int fd; int status; int do_exit = 0; if (is_queued && IsServerMode()) { do_exit = 1; /* Server mode... redirect stdin and stdout to /dev/null */ kid = fork(); if (kid == (pid_t) -1) { /* Fork failed... nothing we can do */ return; } else if (kid == 0) { /* In the child */ (void) close(STDIN_FILENO); (void) close(STDOUT_FILENO); fd = open("/dev/null", O_RDONLY); if (fd >= 0 && fd != STDIN_FILENO) { dup2(fd, STDIN_FILENO); close(STDIN_FILENO); } fd = open("/dev/null", O_WRONLY); if (fd >= 0 && fd != STDOUT_FILENO) { dup2(fd, STDOUT_FILENO); close(STDOUT_FILENO); } } else { /* In the parent */ while (waitpid(kid, &status, 0) != kid) /* continue */ ; return; } } /* This is the child process or original if we never forked */ r = system(cmd); if (do_exit) { /* In the child process, so exit! */ exit(0); } if (r == 0) { return; } } char const * get_day_name(int wkday) { if (wkday < 0 || wkday > 6) { return "INVALID_WKDAY"; } if (DynamicDayName[wkday]) return DynamicDayName[wkday]; return DayName[wkday]; } char const * get_month_name(int mon) { if (mon < 0 || mon > 11) { return "INVALID_MON"; } if (DynamicMonthName[mon]) return DynamicMonthName[mon]; return MonthName[mon]; }