2 * SPDX-License-Identifier: BSD-3-Clause
4 * Copyright (c) 2020 The DragonFly Project. All rights reserved.
5 * Copyright (c) 1989, 1993, 1994
6 * The Regents of the University of California. All rights reserved.
8 * This code is derived from software contributed to The DragonFly Project
9 * by Aaron LI <aly@aaronly.me>
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * @(#)calendar.c 8.3 (Berkeley) 3/25/94
36 * $FreeBSD: head/usr.bin/calendar/io.c 327117 2017-12-23 21:04:32Z eadler $
39 #include <sys/param.h>
59 #include "gregorian.h"
62 #include "parsedata.h"
66 enum { C_NONE, C_LINE, C_BLOCK };
67 enum { T_NONE, T_TOKEN, T_VARIABLE, T_DATE };
70 int type; /* type of the read entry */
71 char *token; /* token to process (T_TOKEN) */
72 char *variable; /* variable name (T_VARIABLE) */
73 char *value; /* variable value (T_VARIABLE) */
74 char *date; /* event date (T_DATE) */
75 struct cal_desc *description; /* event description (T_DATE) */
80 char *line; /* line string read from file */
81 size_t line_cap; /* capacity of the 'line' buffer */
82 char *nextline; /* to store the rewinded line */
83 size_t nextline_cap; /* capacity of the 'nextline' buffer */
84 bool rewinded; /* if 'nextline' has the rewinded line */
87 static struct cal_desc *descriptions = NULL;
88 static struct node *definitions = NULL;
90 static FILE *cal_fopen(const char *file);
91 static bool cal_parse(FILE *in);
92 static bool process_token(char *line, bool *skip);
93 static void send_mail(FILE *fp);
94 static char *skip_comment(char *line, int *comment);
95 static void write_mailheader(FILE *fp);
97 static bool cal_readentry(struct cal_file *cfile,
98 struct cal_entry *entry, bool skip);
99 static char *cal_readline(struct cal_file *cfile);
100 static void cal_rewindline(struct cal_file *cfile);
101 static bool is_date_entry(char *line, char **content);
102 static bool is_variable_entry(char *line, char **value);
104 static struct cal_desc *cal_desc_new(struct cal_desc **head);
105 static void cal_desc_freeall(struct cal_desc *head);
106 static void cal_desc_addline(struct cal_desc *desc, const char *line);
109 * XXX: Quoted or escaped comment marks are not supported yet.
112 skip_comment(char *line, int *comment)
116 if (*comment == C_LINE) {
120 } else if (*comment == C_BLOCK) {
121 for (p = line, pp = p + 1; *p; p++, pp = p + 1) {
122 if (*p == '*' && *pp == '/') {
131 for (p = line, pp = p + 1; *p; p++, pp = p + 1) {
132 if (*p == '/' && (*pp == '/' || *pp == '*')) {
133 *comment = (*pp == '/') ? C_LINE : C_BLOCK;
137 if (*comment != C_NONE) {
138 pp = skip_comment(p, comment);
140 memmove(p, pp, strlen(pp) + 1);
150 cal_fopen(const char *file)
153 char fpath[MAXPATHLEN];
155 for (size_t i = 0; calendarDirs[i] != NULL; i++) {
156 snprintf(fpath, sizeof(fpath), "%s/%s",
157 calendarDirs[i], file);
158 if ((fp = fopen(fpath, "r")) != NULL)
162 warnx("Cannot open calendar file: '%s'", file);
167 * NOTE: input 'line' should have trailing comment and whitespace trimmed.
170 process_token(char *line, bool *skip)
174 if (strcmp(line, "#endif") == 0) {
179 if (*skip) /* deal with nested #ifndef */
182 if (string_startswith(line, "#include ") ||
183 string_startswith(line, "#include\t")) {
184 walk = triml(line + sizeof("#include"));
186 warnx("Expecting arguments after #include");
189 if (*walk != '<' && *walk != '\"') {
190 warnx("Expecting '<' or '\"' after #include");
195 char c = walk[strlen(walk) - 1];
200 warnx("Unterminated include expecting '\"'");
206 warnx("Unterminated include expecting '>'");
211 warnx("Unterminated include expecting '%c'",
212 (a == '<') ? '>' : '\"' );
217 walk[strlen(walk) - 1] = '\0';
219 FILE *fpin = cal_fopen(walk);
222 if (!cal_parse(fpin)) {
223 warnx("Failed to parse calendar files");
231 } else if (string_startswith(line, "#define ") ||
232 string_startswith(line, "#define\t")) {
233 walk = triml(line + sizeof("#define"));
235 warnx("Expecting arguments after #define");
239 struct node *new = list_newnode(xstrdup(walk), NULL);
240 definitions = list_addfront(definitions, new);
244 } else if (string_startswith(line, "#ifndef ") ||
245 string_startswith(line, "#ifndef\t")) {
246 walk = triml(line + sizeof("#ifndef"));
248 warnx("Expecting arguments after #ifndef");
252 if (list_lookup(definitions, walk, strcmp, NULL))
258 warnx("Unknown token line: |%s|", line);
263 locale_day_first(void)
265 char *d_fmt = nl_langinfo(D_FMT);
266 DPRINTF("%s: d_fmt=|%s|\n", __func__, d_fmt);
267 /* NOTE: BSDs use '%e' in D_FMT while Linux uses '%d' */
268 return (strpbrk(d_fmt, "ed") < strchr(d_fmt, 'm'));
274 struct cal_file cfile = { 0 };
275 struct cal_entry entry = { 0 };
276 struct cal_desc *desc;
277 struct cal_line *line;
278 struct cal_day *cdays[CAL_MAX_REPEAT] = { NULL };
279 struct specialday *sday;
280 char *extradata[CAL_MAX_REPEAT] = { NULL };
281 bool d_first, skip, var_handled;
282 bool locale_changed, calendar_changed;
287 d_first = locale_day_first();
289 locale_changed = false;
290 calendar_changed = false;
292 while (cal_readentry(&cfile, &entry, skip)) {
293 if (entry.type == T_TOKEN) {
294 DPRINTF2("%s: T_TOKEN: |%s|\n",
295 __func__, entry.token);
296 if (!process_token(entry.token, &skip)) {
305 if (entry.type == T_VARIABLE) {
306 DPRINTF2("%s: T_VARIABLE: |%s|=|%s|\n",
307 __func__, entry.variable, entry.value);
310 if (strcasecmp(entry.variable, "LANG") == 0) {
311 if (setlocale(LC_ALL, entry.value) == NULL) {
312 warnx("Failed to set LC_ALL='%s'",
315 d_first = locale_day_first();
317 locale_changed = true;
318 DPRINTF("%s: set LC_ALL='%s' (day_first=%s)\n",
319 __func__, entry.value,
320 d_first ? "true" : "false");
324 if (strcasecmp(entry.variable, "CALENDAR") == 0) {
325 if (!set_calendar(entry.value)) {
326 warnx("Failed to set CALENDAR='%s'",
329 calendar_changed = true;
330 DPRINTF("%s: set CALENDAR='%s'\n",
331 __func__, entry.value);
335 if (strcasecmp(entry.variable, "SEQUENCE") == 0) {
336 set_nsequences(entry.value);
340 for (size_t i = 0; specialdays[i].name; i++) {
341 sday = &specialdays[i];
342 if (strcasecmp(entry.variable, sday->name) == 0) {
344 sday->n_name = xstrdup(entry.value);
345 sday->n_len = strlen(sday->n_name);
352 warnx("Unknown variable: |%s|=|%s|",
353 entry.variable, entry.value);
356 free(entry.variable);
361 if (entry.type == T_DATE) {
362 desc = entry.description;
363 DPRINTF2("----------------\n%s: T_DATE: |%s|\n",
364 __func__, entry.date);
365 for (line = desc->firstline; line; line = line->next)
366 DPRINTF3("\t|%s|\n", line->str);
368 count = parse_cal_date(entry.date, &flags, cdays,
371 warnx("Cannot parse date |%s| with content |%s|",
372 entry.date, desc->firstline->str);
374 } else if (count == 0) {
375 DPRINTF2("Ignore out-of-range date |%s| "
376 "with content |%s|\n",
377 entry.date, desc->firstline->str);
381 for (int i = 0; i < count; i++) {
382 event_add(cdays[i], d_first,
383 ((flags & F_VARIABLE) != 0),
393 errx(1, "Invalid calendar entry type: %d", entry.type);
397 * Reset to the default locale, so that one calendar file that changed
398 * the locale (by defining the "LANG" variable) does not interfere the
399 * following calendar files without the "LANG" definition.
401 if (locale_changed) {
402 setlocale(LC_ALL, "");
404 DPRINTF("%s: reset LC_ALL\n", __func__);
407 if (calendar_changed) {
409 DPRINTF("%s: reset CALENDAR\n", __func__);
413 free(cfile.nextline);
419 cal_readentry(struct cal_file *cfile, struct cal_entry *entry, bool skip)
421 char *p, *value, *content;
424 memset(entry, 0, sizeof(*entry));
425 entry->type = T_NONE;
428 while ((p = cal_readline(cfile)) != NULL) {
429 p = skip_comment(p, &comment);
430 p = trimr(p); /* Need to keep the leading tabs */
435 entry->type = T_TOKEN;
436 entry->token = xstrdup(p);
441 /* skip entries but tokens (e.g., '#endif') */
442 DPRINTF2("%s: skip line: |%s|\n", __func__, p);
446 if (is_variable_entry(p, &value)) {
447 value = triml(value);
448 if (*value == '\0') {
449 warnx("%s: varaible |%s| has no value",
454 entry->type = T_VARIABLE;
455 entry->variable = xstrdup(p);
456 entry->value = xstrdup(value);
460 if (is_date_entry(p, &content)) {
461 content = triml(content);
462 if (*content == '\0') {
463 warnx("%s: date |%s| has no content",
468 entry->type = T_DATE;
469 entry->date = xstrdup(p);
470 entry->description = cal_desc_new(&descriptions);
471 cal_desc_addline(entry->description, content);
473 /* Continuous description of the event */
474 while ((p = cal_readline(cfile)) != NULL) {
475 p = trimr(skip_comment(p, &comment));
481 cal_desc_addline(entry->description,
484 cal_rewindline(cfile);
492 warnx("%s: unknown line: |%s|", __func__, p);
499 cal_readline(struct cal_file *cfile)
501 if (cfile->rewinded) {
502 cfile->rewinded = false;
503 return cfile->nextline;
506 if (getline(&cfile->line, &cfile->line_cap, cfile->fp) <= 0)
513 cal_rewindline(struct cal_file *cfile)
515 if (cfile->nextline_cap == 0)
516 cfile->nextline = xmalloc(cfile->line_cap);
517 else if (cfile->nextline_cap < cfile->line_cap)
518 cfile->nextline = xrealloc(cfile->nextline, cfile->line_cap);
520 memcpy(cfile->nextline, cfile->line, cfile->line_cap);
521 cfile->nextline_cap = cfile->line_cap;
522 cfile->rewinded = true;
526 is_variable_entry(char *line, char **value)
532 if (!(*line == '_' || isalpha((unsigned int)*line)))
534 if ((eq = strchr(line, '=')) == NULL)
536 for (p = line+1; p < eq; p++) {
537 if (!isalnum((unsigned int)*p))
549 is_date_entry(char *line, char **content)
555 if ((p = strchr(line, '\t')) == NULL)
566 static struct cal_desc *
567 cal_desc_new(struct cal_desc **head)
569 struct cal_desc *desc = xcalloc(1, sizeof(*desc));
582 cal_desc_freeall(struct cal_desc *head)
584 struct cal_desc *desc;
585 struct cal_line *line;
587 while ((desc = head) != NULL) {
589 while ((line = desc->firstline) != NULL) {
590 desc->firstline = desc->firstline->next;
599 cal_desc_addline(struct cal_desc *desc, const char *line)
601 struct cal_line *cline;
603 cline = xcalloc(1, sizeof(*cline));
604 cline->str = xstrdup(line);
605 if (desc->lastline != NULL) {
606 desc->lastline->next = cline;
607 desc->lastline = cline;
609 desc->firstline = desc->lastline = cline;
617 if (!cal_parse(fpin)) {
618 warnx("Failed to parse calendar files");
622 if (Options.allmode) {
626 * Use a temporary output file, so we can skip sending mail
627 * if there is no output.
629 if ((fpout = tmpfile()) == NULL) {
633 event_print_all(fpout);
636 event_print_all(stdout);
639 list_freeall(definitions, free, NULL);
641 cal_desc_freeall(descriptions);
654 assert(Options.allmode == true);
656 if (fseek(fp, 0L, SEEK_END) == -1 || ftell(fp) == 0) {
657 DPRINTF("%s: no events; skip sending mail\n", __func__);
660 if (pipe(pdes) < 0) {
671 /* child -- set stdin to pipe output */
672 if (pdes[0] != STDIN_FILENO) {
673 dup2(pdes[0], STDIN_FILENO);
677 execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F",
678 "\"Reminder Service\"", (char *)NULL);
679 warn(_PATH_SENDMAIL);
682 /* parent -- write to pipe input */
685 fpipe = fdopen(pdes[1], "w");
691 write_mailheader(fpipe);
693 while ((ch = fgetc(fp)) != EOF)
695 fclose(fpipe); /* will also close the underlying fd */
699 while (wait(NULL) >= 0)
704 write_mailheader(FILE *fp)
706 uid_t uid = getuid();
707 struct passwd *pw = getpwuid(uid);
709 char dayname[32] = { 0 };
712 gregorian_from_fixed(Options.today, &date);
713 dow = dayofweek_from_fixed(Options.today);
714 sprintf(dayname, "%s, %d %s %d",
715 dow_names[dow].f_name, date.day,
716 month_names[date.month-1].f_name, date.year);
719 "From: %s (Reminder Service)\n"
721 "Subject: %s's Calendar\n"
723 "Auto-Submitted: auto-generated\n\n",
724 pw->pw_name, pw->pw_name, dayname);