Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.bin/calendar/io.c
34677 views
1
/*-
2
* SPDX-License-Identifier: BSD-3-Clause
3
*
4
* Copyright (c) 1989, 1993, 1994
5
* The Regents of the University of California. All rights reserved.
6
*
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
9
* are met:
10
* 1. Redistributions of source code must retain the above copyright
11
* notice, this list of conditions and the following disclaimer.
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in the
14
* documentation and/or other materials provided with the distribution.
15
* 3. Neither the name of the University nor the names of its contributors
16
* may be used to endorse or promote products derived from this software
17
* without specific prior written permission.
18
*
19
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29
* SUCH DAMAGE.
30
*/
31
32
#include <sys/param.h>
33
#include <sys/stat.h>
34
#include <sys/wait.h>
35
#include <ctype.h>
36
#include <err.h>
37
#include <errno.h>
38
#include <fcntl.h>
39
#include <libutil.h>
40
#include <locale.h>
41
#include <pwd.h>
42
#include <stdbool.h>
43
#include <stdio.h>
44
#include <stdlib.h>
45
#include <string.h>
46
#include <stringlist.h>
47
#include <time.h>
48
#include <unistd.h>
49
50
#include "pathnames.h"
51
#include "calendar.h"
52
53
enum {
54
T_OK = 0,
55
T_ERR,
56
T_PROCESS,
57
};
58
59
const char *calendarFile = "calendar"; /* default calendar file */
60
static const char *calendarHomes[] = {".calendar", _PATH_INCLUDE_LOCAL, _PATH_INCLUDE}; /* HOME */
61
static const char *calendarNoMail = "nomail";/* don't sent mail if file exist */
62
63
static char path[MAXPATHLEN];
64
static const char *cal_home;
65
static const char *cal_dir;
66
static const char *cal_file;
67
static int cal_line;
68
69
struct fixs neaster, npaskha, ncny, nfullmoon, nnewmoon;
70
struct fixs nmarequinox, nsepequinox, njunsolstice, ndecsolstice;
71
72
static int cal_parse(FILE *in, FILE *out);
73
74
static StringList *definitions = NULL;
75
static struct event *events[MAXCOUNT];
76
static char *extradata[MAXCOUNT];
77
78
static char *
79
trimlr(char **buf)
80
{
81
char *walk = *buf;
82
char *sep;
83
char *last;
84
85
while (isspace(*walk))
86
walk++;
87
*buf = walk;
88
89
sep = walk;
90
while (*sep != '\0' && !isspace(*sep))
91
sep++;
92
93
if (*sep != '\0') {
94
last = sep + strlen(sep) - 1;
95
while (last > walk && isspace(*last))
96
last--;
97
*(last+1) = 0;
98
}
99
100
return (sep);
101
}
102
103
static FILE *
104
cal_fopen(const char *file)
105
{
106
static int cwdfd = -1;
107
FILE *fp;
108
char *home = getenv("HOME");
109
unsigned int i;
110
int fd;
111
struct stat sb;
112
static bool warned = false;
113
static char calendarhome[MAXPATHLEN];
114
115
if (home == NULL || *home == '\0') {
116
warnx("Cannot get home directory");
117
return (NULL);
118
}
119
120
/*
121
* On -a runs, we would have done a chdir() earlier on, but we also
122
* shouldn't have used the initial cwd anyways lest we bring
123
* unpredictable behavior upon us.
124
*/
125
if (!doall && cwdfd == -1) {
126
cwdfd = open(".", O_DIRECTORY | O_PATH);
127
if (cwdfd == -1)
128
err(1, "open(cwd)");
129
}
130
131
/*
132
* Check $PWD first as documented.
133
*/
134
if (cwdfd != -1) {
135
if ((fd = openat(cwdfd, file, O_RDONLY)) != -1) {
136
if ((fp = fdopen(fd, "r")) == NULL)
137
err(1, "fdopen(%s)", file);
138
139
cal_home = NULL;
140
cal_dir = NULL;
141
cal_file = file;
142
return (fp);
143
} else if (errno != ENOENT && errno != ENAMETOOLONG) {
144
err(1, "open(%s)", file);
145
}
146
}
147
148
if (chdir(home) != 0) {
149
warnx("Cannot enter home directory \"%s\"", home);
150
return (NULL);
151
}
152
153
for (i = 0; i < nitems(calendarHomes); i++) {
154
if (snprintf(calendarhome, sizeof (calendarhome), calendarHomes[i],
155
getlocalbase()) >= (int)sizeof (calendarhome))
156
continue;
157
158
if (chdir(calendarhome) != 0)
159
continue;
160
161
if ((fp = fopen(file, "r")) != NULL) {
162
cal_home = home;
163
cal_dir = calendarhome;
164
cal_file = file;
165
return (fp);
166
}
167
}
168
169
warnx("can't open calendar file \"%s\"", file);
170
if (!warned) {
171
snprintf(path, sizeof(path), _PATH_INCLUDE_LOCAL, getlocalbase());
172
if (stat(path, &sb) != 0) {
173
warnx("calendar data files now provided by calendar-data pkg.");
174
warned = true;
175
}
176
}
177
178
return (NULL);
179
}
180
181
static char*
182
cal_path(void)
183
{
184
static char buffer[MAXPATHLEN + 10];
185
186
if (cal_dir == NULL)
187
snprintf(buffer, sizeof(buffer), "%s", cal_file);
188
else if (cal_dir[0] == '/')
189
snprintf(buffer, sizeof(buffer), "%s/%s", cal_dir, cal_file);
190
else
191
snprintf(buffer, sizeof(buffer), "%s/%s/%s", cal_home, cal_dir, cal_file);
192
return (buffer);
193
}
194
195
#define WARN0(format) \
196
warnx(format " in %s line %d", cal_path(), cal_line)
197
#define WARN1(format, arg1) \
198
warnx(format " in %s line %d", arg1, cal_path(), cal_line)
199
200
static char*
201
cmptoken(char *line, const char* token)
202
{
203
char len = strlen(token);
204
205
if (strncmp(line, token, len) != 0)
206
return NULL;
207
return (line + len);
208
}
209
210
static int
211
token(char *line, FILE *out, int *skip, int *unskip)
212
{
213
char *walk, *sep, a, c;
214
const char *this_cal_home;
215
const char *this_cal_dir;
216
const char *this_cal_file;
217
int this_cal_line;
218
219
while (isspace(*line))
220
line++;
221
222
if (cmptoken(line, "endif")) {
223
if (*skip + *unskip == 0) {
224
WARN0("#endif without prior #ifdef or #ifndef");
225
return (T_ERR);
226
}
227
if (*skip > 0)
228
--*skip;
229
else
230
--*unskip;
231
232
return (T_OK);
233
}
234
235
walk = cmptoken(line, "ifdef");
236
if (walk != NULL) {
237
sep = trimlr(&walk);
238
239
if (*walk == '\0') {
240
WARN0("Expecting arguments after #ifdef");
241
return (T_ERR);
242
}
243
if (*sep != '\0') {
244
WARN1("Expecting a single word after #ifdef "
245
"but got \"%s\"", walk);
246
return (T_ERR);
247
}
248
249
if (*skip != 0 ||
250
definitions == NULL || sl_find(definitions, walk) == NULL)
251
++*skip;
252
else
253
++*unskip;
254
255
return (T_OK);
256
}
257
258
walk = cmptoken(line, "ifndef");
259
if (walk != NULL) {
260
sep = trimlr(&walk);
261
262
if (*walk == '\0') {
263
WARN0("Expecting arguments after #ifndef");
264
return (T_ERR);
265
}
266
if (*sep != '\0') {
267
WARN1("Expecting a single word after #ifndef "
268
"but got \"%s\"", walk);
269
return (T_ERR);
270
}
271
272
if (*skip != 0 ||
273
(definitions != NULL && sl_find(definitions, walk) != NULL))
274
++*skip;
275
else
276
++*unskip;
277
278
return (T_OK);
279
}
280
281
walk = cmptoken(line, "else");
282
if (walk != NULL) {
283
(void)trimlr(&walk);
284
285
if (*walk != '\0') {
286
WARN0("Expecting no arguments after #else");
287
return (T_ERR);
288
}
289
if (*skip + *unskip == 0) {
290
WARN0("#else without prior #ifdef or #ifndef");
291
return (T_ERR);
292
}
293
294
if (*skip == 0) {
295
++*skip;
296
--*unskip;
297
} else if (*skip == 1) {
298
--*skip;
299
++*unskip;
300
}
301
302
return (T_OK);
303
}
304
305
if (*skip != 0)
306
return (T_OK);
307
308
walk = cmptoken(line, "include");
309
if (walk != NULL) {
310
(void)trimlr(&walk);
311
312
if (*walk == '\0') {
313
WARN0("Expecting arguments after #include");
314
return (T_ERR);
315
}
316
317
if (*walk != '<' && *walk != '\"') {
318
WARN0("Excecting '<' or '\"' after #include");
319
return (T_ERR);
320
}
321
322
a = *walk == '<' ? '>' : '\"';
323
walk++;
324
c = walk[strlen(walk) - 1];
325
326
if (a != c) {
327
WARN1("Unterminated include expecting '%c'", a);
328
return (T_ERR);
329
}
330
walk[strlen(walk) - 1] = '\0';
331
332
this_cal_home = cal_home;
333
this_cal_dir = cal_dir;
334
this_cal_file = cal_file;
335
this_cal_line = cal_line;
336
if (cal_parse(cal_fopen(walk), out))
337
return (T_ERR);
338
cal_home = this_cal_home;
339
cal_dir = this_cal_dir;
340
cal_file = this_cal_file;
341
cal_line = this_cal_line;
342
343
return (T_OK);
344
}
345
346
walk = cmptoken(line, "define");
347
if (walk != NULL) {
348
if (definitions == NULL)
349
definitions = sl_init();
350
sep = trimlr(&walk);
351
*sep = '\0';
352
353
if (*walk == '\0') {
354
WARN0("Expecting arguments after #define");
355
return (T_ERR);
356
}
357
358
if (sl_find(definitions, walk) == NULL)
359
sl_add(definitions, strdup(walk));
360
return (T_OK);
361
}
362
363
walk = cmptoken(line, "undef");
364
if (walk != NULL) {
365
if (definitions != NULL) {
366
sep = trimlr(&walk);
367
368
if (*walk == '\0') {
369
WARN0("Expecting arguments after #undef");
370
return (T_ERR);
371
}
372
if (*sep != '\0') {
373
WARN1("Expecting a single word after #undef "
374
"but got \"%s\"", walk);
375
return (T_ERR);
376
}
377
378
walk = sl_find(definitions, walk);
379
if (walk != NULL)
380
walk[0] = '\0';
381
}
382
return (T_OK);
383
}
384
385
walk = cmptoken(line, "warning");
386
if (walk != NULL) {
387
(void)trimlr(&walk);
388
WARN1("Warning: %s", walk);
389
}
390
391
walk = cmptoken(line, "error");
392
if (walk != NULL) {
393
(void)trimlr(&walk);
394
WARN1("Error: %s", walk);
395
return (T_ERR);
396
}
397
398
WARN1("Undefined pre-processor command \"#%s\"", line);
399
return (T_ERR);
400
}
401
402
static void
403
setup_locale(const char *locale)
404
{
405
(void)setlocale(LC_ALL, locale);
406
#ifdef WITH_ICONV
407
if (!doall)
408
set_new_encoding();
409
#endif
410
setnnames();
411
}
412
413
#define REPLACE(string, slen, struct_) \
414
if (strncasecmp(buf, (string), (slen)) == 0 && buf[(slen)]) { \
415
if (struct_.name != NULL) \
416
free(struct_.name); \
417
if ((struct_.name = strdup(buf + (slen))) == NULL) \
418
errx(1, "cannot allocate memory"); \
419
struct_.len = strlen(buf + (slen)); \
420
continue; \
421
}
422
static int
423
cal_parse(FILE *in, FILE *out)
424
{
425
char *mylocale = NULL;
426
char *line = NULL;
427
char *buf, *bufp;
428
size_t linecap = 0;
429
ssize_t linelen;
430
ssize_t l;
431
static int count = 0;
432
int i;
433
int month[MAXCOUNT];
434
int day[MAXCOUNT];
435
int year[MAXCOUNT];
436
int skip = 0;
437
int unskip = 0;
438
char *pp, p;
439
int flags;
440
char *c, *cc;
441
bool incomment = false;
442
443
if (in == NULL)
444
return (1);
445
446
cal_line = 0;
447
while ((linelen = getline(&line, &linecap, in)) > 0) {
448
cal_line++;
449
buf = line;
450
if (buf[linelen - 1] == '\n')
451
buf[--linelen] = '\0';
452
453
if (incomment) {
454
c = strstr(buf, "*/");
455
if (c) {
456
c += 2;
457
linelen -= c - buf;
458
buf = c;
459
incomment = false;
460
} else {
461
continue;
462
}
463
}
464
if (!incomment) {
465
bufp = buf;
466
do {
467
c = strstr(bufp, "//");
468
cc = strstr(bufp, "/*");
469
if (c != NULL && (cc == NULL || c - cc < 0)) {
470
bufp = c + 2;
471
/* ignore "//" within string to allow it in an URL */
472
if (c == buf || isspace(c[-1])) {
473
/* single line comment */
474
*c = '\0';
475
linelen = c - buf;
476
break;
477
}
478
} else if (cc != NULL) {
479
c = strstr(cc + 2, "*/");
480
if (c != NULL) { // 'a /* b */ c' -- cc=2, c=7+2
481
/* multi-line comment ending on same line */
482
c += 2;
483
memmove(cc, c, buf + linelen + 1 - c);
484
linelen -= c - cc;
485
bufp = cc;
486
} else {
487
/* multi-line comment */
488
*cc = '\0';
489
linelen = cc - buf;
490
incomment = true;
491
break;
492
}
493
}
494
} while (c != NULL || cc != NULL);
495
}
496
497
for (l = linelen;
498
l > 0 && isspace((unsigned char)buf[l - 1]);
499
l--)
500
;
501
buf[l] = '\0';
502
if (buf[0] == '\0')
503
continue;
504
505
if (buf == line && *buf == '#') {
506
switch (token(buf+1, out, &skip, &unskip)) {
507
case T_ERR:
508
free(line);
509
return (1);
510
case T_OK:
511
continue;
512
case T_PROCESS:
513
break;
514
default:
515
break;
516
}
517
}
518
519
if (skip != 0)
520
continue;
521
522
/*
523
* Setting LANG in user's calendar was an old workaround
524
* for 'calendar -a' being run with C locale to properly
525
* print user's calendars in their native languages.
526
* Now that 'calendar -a' does fork with setusercontext(),
527
* and does not run iconv(), this variable has little use.
528
*/
529
if (strncmp(buf, "LANG=", 5) == 0) {
530
if (mylocale == NULL)
531
mylocale = strdup(setlocale(LC_ALL, NULL));
532
setup_locale(buf + 5);
533
continue;
534
}
535
/* Parse special definitions: Easter, Paskha etc */
536
REPLACE("Easter=", 7, neaster);
537
REPLACE("Paskha=", 7, npaskha);
538
REPLACE("ChineseNewYear=", 15, ncny);
539
REPLACE("NewMoon=", 8, nnewmoon);
540
REPLACE("FullMoon=", 9, nfullmoon);
541
REPLACE("MarEquinox=", 11, nmarequinox);
542
REPLACE("SepEquinox=", 11, nsepequinox);
543
REPLACE("JunSolstice=", 12, njunsolstice);
544
REPLACE("DecSolstice=", 12, ndecsolstice);
545
if (strncmp(buf, "SEQUENCE=", 9) == 0) {
546
setnsequences(buf + 9);
547
continue;
548
}
549
550
/*
551
* If the line starts with a tab, the data has to be
552
* added to the previous line
553
*/
554
if (buf[0] == '\t') {
555
for (i = 0; i < count; i++)
556
event_continue(events[i], buf);
557
continue;
558
}
559
560
/* Get rid of leading spaces (non-standard) */
561
while (isspace((unsigned char)buf[0]))
562
memcpy(buf, buf + 1, strlen(buf));
563
564
/* No tab in the line, then not a valid line */
565
if ((pp = strchr(buf, '\t')) == NULL)
566
continue;
567
568
/* Trim spaces in front of the tab */
569
while (isspace((unsigned char)pp[-1]))
570
pp--;
571
572
p = *pp;
573
*pp = '\0';
574
if ((count = parsedaymonth(buf, year, month, day, &flags,
575
extradata)) == 0)
576
continue;
577
*pp = p;
578
if (count < 0) {
579
/* Show error status based on return value */
580
if (debug)
581
WARN1("Ignored: \"%s\"", buf);
582
if (count == -1)
583
continue;
584
count = -count + 1;
585
}
586
587
/* Find the last tab */
588
while (pp[1] == '\t')
589
pp++;
590
591
for (i = 0; i < count; i++) {
592
if (debug)
593
WARN1("got \"%s\"", pp);
594
events[i] = event_add(year[i], month[i], day[i],
595
((flags &= F_VARIABLE) != 0) ? 1 : 0, pp,
596
extradata[i]);
597
}
598
}
599
while (skip-- > 0 || unskip-- > 0) {
600
cal_line++;
601
WARN0("Missing #endif assumed");
602
}
603
604
free(line);
605
fclose(in);
606
if (mylocale != NULL) {
607
setup_locale(mylocale);
608
free(mylocale);
609
}
610
611
return (0);
612
}
613
614
void
615
cal(void)
616
{
617
FILE *fpin;
618
FILE *fpout;
619
int i;
620
621
for (i = 0; i < MAXCOUNT; i++)
622
extradata[i] = (char *)calloc(1, 20);
623
624
625
if ((fpin = opencalin()) == NULL)
626
return;
627
628
if ((fpout = opencalout()) == NULL) {
629
fclose(fpin);
630
return;
631
}
632
633
if (cal_parse(fpin, fpout))
634
return;
635
636
event_print_all(fpout);
637
closecal(fpout);
638
}
639
640
FILE *
641
opencalin(void)
642
{
643
struct stat sbuf;
644
FILE *fpin;
645
646
/* open up calendar file */
647
cal_file = calendarFile;
648
if ((fpin = fopen(calendarFile, "r")) == NULL) {
649
if (doall) {
650
if (chdir(calendarHomes[0]) != 0)
651
return (NULL);
652
if (stat(calendarNoMail, &sbuf) == 0)
653
return (NULL);
654
if ((fpin = fopen(calendarFile, "r")) == NULL)
655
return (NULL);
656
} else {
657
fpin = cal_fopen(calendarFile);
658
}
659
}
660
return (fpin);
661
}
662
663
FILE *
664
opencalout(void)
665
{
666
int fd;
667
668
/* not reading all calendar files, just set output to stdout */
669
if (!doall)
670
return (stdout);
671
672
/* set output to a temporary file, so if no output don't send mail */
673
snprintf(path, sizeof(path), "%s/_calXXXXXX", _PATH_TMP);
674
if ((fd = mkstemp(path)) < 0)
675
return (NULL);
676
return (fdopen(fd, "w+"));
677
}
678
679
void
680
closecal(FILE *fp)
681
{
682
struct stat sbuf;
683
int nread, pdes[2], status;
684
char buf[1024];
685
686
if (!doall)
687
return;
688
689
rewind(fp);
690
if (fstat(fileno(fp), &sbuf) || !sbuf.st_size)
691
goto done;
692
if (pipe(pdes) < 0)
693
goto done;
694
switch (fork()) {
695
case -1: /* error */
696
(void)close(pdes[0]);
697
(void)close(pdes[1]);
698
goto done;
699
case 0:
700
/* child -- set stdin to pipe output */
701
if (pdes[0] != STDIN_FILENO) {
702
(void)dup2(pdes[0], STDIN_FILENO);
703
(void)close(pdes[0]);
704
}
705
(void)close(pdes[1]);
706
execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F",
707
"\"Reminder Service\"", (char *)NULL);
708
warn(_PATH_SENDMAIL);
709
_exit(1);
710
}
711
/* parent -- write to pipe input */
712
(void)close(pdes[0]);
713
714
write(pdes[1], "From: \"Reminder Service\" <", 26);
715
write(pdes[1], pw->pw_name, strlen(pw->pw_name));
716
write(pdes[1], ">\nTo: <", 7);
717
write(pdes[1], pw->pw_name, strlen(pw->pw_name));
718
write(pdes[1], ">\nSubject: ", 11);
719
write(pdes[1], dayname, strlen(dayname));
720
write(pdes[1], "'s Calendar\nPrecedence: bulk\n\n", 30);
721
722
while ((nread = read(fileno(fp), buf, sizeof(buf))) > 0)
723
(void)write(pdes[1], buf, nread);
724
(void)close(pdes[1]);
725
done: (void)fclose(fp);
726
(void)unlink(path);
727
while (wait(&status) >= 0);
728
}
729
730