Fixed some recent GCC + 64-bit arch warnings.
[timeplan.git] / timeplan.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <getopt.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <time.h>
7 #include <assert.h>
8 #include <limits.h>
9 #include <ctype.h>
10 #include <regex.h>
11
12 #define ACTS_MAX 20
13 #define HASH_SIZE 971
14 #define CONFIGFILE "/.timeplanrc" /* HOME prepended */
15 #define DEF_TIMEPLAN "/.timeplan" /* HOME prepended */
16 #define MARK_DAY "_days"
17 #define DEF_FORMTOTAL ":6"
18 #define LENGTH(x) (sizeof((x))/sizeof(*(x)))
19 #define WHERE1 " in \"%s\" on line %d!\n"
20 #define WHERE2 finame,line
21 #define NL "\r\n"
22 #define ERRH1 stderr,"%s: "
23 #define ERRH2 pname
24 #define ERRNO1 ": %s!\n"
25 #define ERRNO2 strerror(errno)
26 #define FAKEUSE =0
27
28 static const char version[]="This is TimePlan, version 1.0.1\n";
29 static const char *pname;
30 static char *finame;
31 static FILE *fi;
32 static int verbose=0,tree=0,doaverage=0;
33 static char *formtotal=DEF_FORMTOTAL;
34 static char buf[LINE_MAX];
35 static int line=0;
36 static enum { SORT_NO,SORT_TIMETOT,SORT_STORES } sortby=SORT_TIMETOT;
37 static unsigned lifetime_days=0;
38 static unsigned long lifetime_seq=1;
39
40 static void usage(void)
41 {
42         fprintf(stderr,"%s%s%s",
43 version,
44 "This command summarizes the timelog information:\n\
45 \n\
46 Usage: timeplan [-u|--unsort] [-s|--stores] [-T|--timetot]  [-t|--tree]\n\
47                 [-c|--condition <cond>] [-f|--formtotal <fmtstring>]\n\
48                 [-r|--rule <ruleL>:<ruleR>]\n\
49                 [-v|--verbose] [-h|--help] [-V|--version]\n\
50 \n\
51   -u, --unsort\t\tDon't sort the result in any way\n\
52   -s, --stores\t\tSort the result by stores count\n\
53   -T, --timetot\t\tSort the result by total time (default)\n\
54 ","\
55   -t, --tree\t\tOrganize data as hierarchy tree\n\
56   -a, --average\t\tDisplay all times as average-per-day value\n\
57   -c, --condition\tDefine condition variable\n\
58   -f, --formtotal\tFormat \"Total\" column (\"text *val/val:width text\")\n\
59   -r, --rule\t\tAdd to the end of .rc file this rule (':'->'\\t')\n\
60   -v, --verbose\t\tInform about phases of transfer\n\
61   -h, --help\t\tPrint a summary of the options\n\
62   -V, --version\t\tPrint the version number\n\
63 ");
64         exit(EXIT_FAILURE);
65 }
66
67 static const struct option longopts[]={
68 {"unsort"   ,0,0,'u'},
69 {"stores"   ,0,0,'s'},
70 {"timetot"  ,0,0,'T'},
71 {"tree"     ,0,0,'t'},
72 {"average"  ,0,0,'a'},
73 {"condition",1,0,'c'},
74 {"formtotal",1,0,'f'},
75 {"rule"     ,1,0,'r'},
76 {"verbose"  ,0,0,'v'},
77 {"help"     ,0,0,'h'},
78 {"version"  ,0,0,'V'}};
79
80 static int calctime(int timetot,int at,int of)
81 {
82         /* FIXME: better distribution */
83         return timetot/of;
84 }
85
86 static struct action {
87         struct action *next;
88         int timetot,stores;
89         unsigned long last_seq;
90         char what[1];
91         } *hashtable[HASH_SIZE];
92 static int hashtable_tot=0;
93
94 static int dumpaction(const struct action *action)
95 {
96 int tot,mins FAKEUSE,hours FAKEUSE,days FAKEUSE,origtot;
97 char *s,fmt[]="%?d";
98
99         if (action) {
100                 tot=(action->timetot+30)/60;
101                 if (doaverage && lifetime_days)
102                         tot/=lifetime_days;
103                 mins=tot; hours=mins/60; days=hours/24;
104                 mins%=60; hours%=24;
105                 }
106         else tot=0;
107         origtot=tot;
108         for (s=formtotal;*s;s++)
109                 switch (*s) {
110                         case '*': case '/': {
111 long l;
112 char *end,*s2;
113
114                                 if (s[1]==*s) { s++; goto dump; }
115                                 for (s2=s+1;isdigit(*s2);s2++);
116                                 l=strtol(s+1,&end,10);
117                                 if (end!=s2) {
118                                         fprintf(ERRH1"Number parse error at column %ld of formtotal string!\n",ERRH2,(long)(s-formtotal));
119                                         exit(EXIT_FAILURE);
120                                         }
121                                 if (*s=='*') tot*=l;
122                                 else         tot/=l;
123                                 s=s2-1;
124                                 break; }
125                         case ':':
126                                 if (s[1]==*s) { s++; goto dump; }
127                                 if (!isdigit(s[1])) goto dump;
128                                 if (action) {
129                                         fmt[1]=s[1];
130                                         printf(fmt,tot);
131                                         tot=origtot;
132                                         }
133                                 else origtot+=s[1]-'0';
134                                 s+=1;
135                                 break;
136                         default:
137 dump:
138                                 if (action) putchar(*s);
139                                 else origtot++;
140                         }
141         if (action)
142                 printf("=%03d/%02d:%02d %4d\t%s\n",days,hours,mins,
143                         action->stores,action->what);
144         return origtot;
145 }
146
147 #define A (*Ap)
148 #define B (*Bp)
149 #define FUNC(which) \
150         static int sort_##which(const struct action **Ap,const struct action **Bp) \
151         { return (B->which>A->which)-(A->which>B->which); }
152 FUNC(timetot)
153 FUNC(stores)
154 #undef FUNC
155 #undef B
156 #undef A
157
158 static void dumphashtable(void)
159 {
160 int item;
161 struct action *action;
162 struct action **sorta FAKEUSE,**sorti FAKEUSE;
163
164         if (sortby!=SORT_NO) {
165 int totalwidth=dumpaction(NULL);
166
167                 while (totalwidth-->5) putchar(' ');
168                 puts("Total Day Hr Mi Stor\tDescription");
169                 if (!(sorta=malloc(sizeof(*sorta)*hashtable_tot))) {
170                         fprintf(ERRH1"malloc() of %d pointers"ERRNO1,ERRH2,hashtable_tot,ERRNO2);
171                         exit(EXIT_FAILURE);
172                         }
173                 sorti=sorta;
174                 }
175         for (item=0;item<LENGTH(hashtable);item++)
176                 for (action=hashtable[item];action;action=action->next)
177                         if (sortby==SORT_NO)
178                                 dumpaction(action);
179                         else
180                                 *sorti++=action;
181         if (sortby==SORT_NO) return;
182         assert(sorti==sorta+hashtable_tot);
183         qsort(sorta,hashtable_tot,sizeof(*sorta),
184                 (int (*)(const void *,const void *))(sortby==SORT_TIMETOT ? sort_timetot : sort_stores));
185         for (sorti=sorta;sorti<sorta+hashtable_tot;sorti++)
186                 dumpaction(*sorti);
187         free(sorta);
188 }
189
190 static unsigned calchash(const char *s)
191 {
192 unsigned r=57;
193
194         while (*s) r=r*7+11*toupper(*s++);
195         r=r%HASH_SIZE;
196         return r;
197 }
198
199 static void storeone(char *what,int length)
200 {
201 struct action **actionp,*action;
202
203         if (!*what)
204                 return;
205         if (verbose) printf("storeone: %d: %s\n",length,what);
206         for (actionp=hashtable+calchash(what);(action=*actionp);actionp=&action->next)
207                 if (!strcasecmp(action->what,what)) break;
208         if (!action) {
209                 if (!(action=malloc(sizeof(*action)+strlen(what)))) {
210                         fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,what,ERRNO2);
211                         exit(EXIT_FAILURE);
212                         }
213                 action->next=NULL;
214                 action->timetot=0;
215                 action->stores=0;
216                 strcpy(action->what,what);
217                 *actionp=action;
218                 hashtable_tot++;
219                 }
220         else {
221                 if (action->last_seq==lifetime_seq) {
222                         if (verbose) puts("storeone: preventing duplicate");
223                         return; /* prevent duplicates like: TV-Music  -->  TV-Fun-Music-Fun */
224                         }
225                 }
226         action->last_seq=lifetime_seq;
227         action->timetot+=length;
228         action->stores++;
229 }
230
231 struct textlist {
232         struct textlist *next;
233         int line;
234         char text[1];
235         };
236 static struct textlist *conditions,**conditionstail=&conditions;
237
238 static int iscondition(const char *text)
239 {
240 struct textlist *cond;
241
242         for (cond=conditions;cond;cond=cond->next)
243                 if (!strcasecmp(text,cond->text)) return 1;
244         return 0;
245 }
246
247 static void addlist(struct textlist ***tail,const char *text,int line)
248 {
249 struct textlist *item;
250
251         if (!(item=malloc(sizeof(*item)+strlen(text)))) {
252                 fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,text,ERRNO2);
253                 exit(EXIT_FAILURE);
254                 }
255         strcpy(item->text,text);
256         item->next=NULL;
257         **tail=item;
258         *tail=&item->next;
259 }
260
261 static struct textlist *modifies,**modifiestail=&modifies,*modifiescmdl,**modifiescmdltail=&modifiescmdl;
262 static int modifies_tot;
263 static struct modistruct {
264         regex_t regex;
265         char *dst;
266         } *modistructs;
267
268 static void modify_load(void)
269 {
270 static int inited=0;
271 char *finame,*s,*s2;
272 FILE *fi;
273 int line=0,m;
274 struct textlist *item;
275 char buf[LINE_MAX];
276
277         if (inited) return;
278         inited=1;
279         if (asprintf(&finame,"%s"CONFIGFILE,getenv("HOME"))==-1) {
280                 fprintf(ERRH1"Config filename allocation",ERRH2);
281                 exit(EXIT_FAILURE);
282                 }
283         errno=0;
284         if (!(fi=fopen(finame,"rt"))) {
285                 if (errno!=ENOENT)
286                         fprintf(ERRH1"Config file \"%s\" read"ERRNO1,ERRH2,finame,ERRNO2);
287                 return;
288                 }
289
290         while (clearerr(fi),fgets(buf,sizeof(buf),fi)==buf) {
291                 line++;
292                 if ((s=strchr(buf,'\n')) && !s[1]) *s='\0';
293
294                 else {
295                         fprintf(ERRH1"fgets(3) results not newline-terminated"WHERE1,ERRH2,WHERE2);
296                         exit(EXIT_FAILURE);
297                         }
298                 if (!*buf || *buf=='#') {
299 nextline:
300                         continue;
301                         }
302                 if (*buf!='R') {
303                         fprintf(ERRH1"Unrecognized syntax"WHERE1,ERRH2,WHERE2);
304                         exit(EXIT_FAILURE);
305                         }
306                 s=buf+1;
307                 for (;;) {
308 int isplus;
309
310                         while (isspace(*s)) s++;
311                         if (*s==':') break;
312                         if (!isalpha(*s)) {
313                                 fprintf(ERRH1"Invalid character at offset %ld"WHERE1,ERRH2,(long)(s-buf+1),WHERE2);
314                                 exit(EXIT_FAILURE);
315                                 }
316                         for (s2=s+1;isalpha(*s2);s2++);
317                         if (*s2!='+' && *s2!='-') {
318                                 fprintf(ERRH1"Only plus ('+') or minus ('-'), not '%c' expected at offset %ld"WHERE1,ERRH2,*s2,(long)(s2-buf+1),WHERE2);
319                                 exit(EXIT_FAILURE);
320                                 }
321                         isplus=(*s2=='+'); *s2++='\0';
322                         if (iscondition(s)!=isplus)
323                                 goto nextline;
324                         s=s2;
325                         }
326                 s++; /* Skip ':' */
327                 addlist(&modifiestail,s,line);
328                 modifies_tot++;
329                 }
330         if (!feof(fi) || ferror(fi))  {
331                 fprintf(ERRH1"fgets(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
332                 exit(EXIT_FAILURE);
333                 }
334         if (fclose(fi))
335                 fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
336         *modifiestail=modifiescmdl; /* add all command-line arguments to the end */
337         if (!(modistructs=malloc(sizeof(*modistructs)*modifies_tot))) {
338                 fprintf(ERRH1"malloc() of %d modistructs's"ERRNO1,ERRH2,modifies_tot,ERRNO2);
339                 exit(EXIT_FAILURE);
340                 }
341         for (m=0,item=modifies;m<modifies_tot;m++,item=item->next) {
342 #define line (item->line)
343                 if (!(s=strchr(item->text,'\t'))) {
344                         fprintf(ERRH1"No delimiting tab-character found"WHERE1,ERRH2,WHERE2);
345                         exit(EXIT_FAILURE);
346                         }
347                 *s++='\0'; modistructs[m].dst=s;
348                 if (verbose) printf("regcomp: %s -> %s\n",item->text,s);
349                 if (regcomp(&modistructs[m].regex,item->text,REG_EXTENDED|REG_ICASE)) {
350                         fprintf(ERRH1"regcomp() failed for \"%s\" (%s)"WHERE1,ERRH2,item->text,ERRNO2,WHERE2);
351                         exit(EXIT_FAILURE);
352                         }
353 #undef line
354                 }
355         assert(!item);
356         free(finame);
357 }
358
359 static char *modify(char *what)
360 {
361 regmatch_t matches[10];
362 int i,m;
363 int doprep,willprep;
364 static char modbuf1[sizeof(buf)],modbuf2[sizeof(modbuf1)];
365 char *src=what,*dstbase=modbuf1,*dst=dstbase;
366 const char *start FAKEUSE,*end FAKEUSE,*patt;
367 const struct textlist *item;
368
369         if (verbose) printf("modify: %s\n",what);
370         modify_load();
371         for (m=0,item=modifies,willprep=1;;m++,item=item->next) {
372 enum { PATT_START,PATT_MID,PATT_END,PATT_TERM } pattpos;
373
374                 for (doprep=willprep;doprep>=0;doprep--) {
375                         if (doprep) {
376                                 *dst++='-';
377                                 for (patt=src;;patt++) {
378                                         if (*patt=='-' || !*patt) {
379                                                 if (dst[-1]!='-')
380                                                         *dst++='-';
381                                                 if (!*patt)
382                                                         break;
383                                                 }
384                                         else
385                                                 *dst++=*patt;
386                                         }
387                                 }
388                         else {
389                                 if (!item)
390                                         return src;
391                                 i=regexec(&modistructs[m].regex,src,LENGTH(matches),matches,0);
392                                 if (i==REG_NOMATCH) {
393                                         willprep=0;
394                                         break;
395                                         }
396                                 if (i) {
397                                         fprintf(ERRH1"regexec() failed for \"%s\""ERRNO1,ERRH2,item->text,ERRNO2);
398                                         exit(EXIT_FAILURE);
399                                         }
400                                 willprep=1;
401                                 if (verbose) printf("matched: %s -> %s\n",item->text,modistructs[m].dst);
402                                 pattpos=PATT_START; patt=NULL;
403                                 while (pattpos!=PATT_TERM) {
404                                         switch (pattpos) {
405                                                 case PATT_START:
406                                                         start=src;
407                                                         end=src+matches->rm_so;
408                                                         pattpos=PATT_MID; patt=modistructs[m].dst;
409                                                         break;
410                                                 case PATT_MID:
411                                                         if (!*patt) {
412                                                                 pattpos=PATT_END; patt=NULL;
413                                                                 continue;
414                                                                 }
415                                                         else if (*patt=='@') {
416                                                                 start=src; end=src+strlen(src);
417                                                                 }
418                                                         else if (*patt>='0' && *patt<='9') {
419                 regmatch_t *match=matches+(*patt-'0');
420                                                                 if (match->rm_so==-1
421                                                                  || match->rm_eo==-1) {
422                                                                         fprintf(ERRH1"Trying to substitute '%c' but no \"matches\" entry not set for \"%s\""WHERE1,
423                                                                                         ERRH2,*patt,item->text,WHERE2);
424                                                                         exit(EXIT_FAILURE);
425                                                                         }
426                                                                 if (match->rm_so>match->rm_eo) {
427                                                                         fprintf(ERRH1"Trying to substitute '%c' start>end (%d>%d) for \"%s\""WHERE1,
428                                                                                         ERRH2,*patt,match->rm_so,match->rm_eo,item->text,WHERE2);
429                                                                         exit(EXIT_FAILURE);
430                                                                         }
431                                                                 start=src+match->rm_so; end=src+match->rm_eo;
432                                                                 }
433                                                         else {
434                                                                 start=patt; end=patt+1;
435                                                                 }
436                                                         patt++;
437                                                         break;
438                                                 case PATT_END:
439                                                         start=src+matches->rm_eo;
440                                                         end=src+strlen(src);
441                                                         pattpos=PATT_TERM; /* assumed: patt=NULL; */
442                                                         break;
443                                                 default:
444                                                         assert(0);
445                                                 }
446                                         if ((dst-dstbase+(end-start))>=sizeof(modbuf1)-1/* -1 for '-' during prepping */) {
447                                                 fprintf(ERRH1"Maximum buffer size exceeded during substition for \"%s\""WHERE1,
448                                                                 ERRH2,item->text,WHERE2);
449                                                 exit(EXIT_FAILURE);
450                                                 }
451                                         memcpy(dst,start,end-start);
452                                         dst+=end-start;
453                                         }
454                                 }
455 /* SWAP buffers: */
456                         if (dst==dstbase || (dst==dstbase+1 && *dstbase=='-'))
457                                 return NULL;
458                         *dst='\0';
459                         if (src==what) {
460                                 assert(dstbase==modbuf1);
461                                 src=dstbase;
462                                 dst=dstbase=modbuf2;
463                                 }
464                         else {
465         char *swap;
466                                 assert((src==modbuf1 && dstbase==modbuf2)
467                                                  ||(src==modbuf2 && dstbase==modbuf1));
468                                 swap=src; src=dstbase; dst=dstbase=swap;
469                                 }
470                         } /* for (doprep) */
471                 }
472         /* NOTREACHED */
473 }
474
475 static struct modifycache {
476         struct modifycache *next;
477         char src[1]; /* dst[1] follows after '\0' */
478         } *mcachetable[HASH_SIZE];
479 static unsigned long modifycache_hits;
480
481 static char *modify_cached(char *what)
482 {
483 struct modifycache **mcachep,*mcache;
484 char *dst;
485 size_t whatl;
486
487         if (!*what)
488                 return(NULL);
489         whatl=strlen(what);
490         if (verbose) printf("modify_cached: %s\n",what);
491         for (mcachep=mcachetable+calchash(what);(mcache=*mcachep);mcachep=&mcache->next)
492                 if (!strcasecmp(mcache->src,what)) break;
493         if (!mcache) {
494                 dst=modify(what);
495                 if (!(mcache=malloc(sizeof(*mcache)+whatl+1+(dst?strlen(dst):0)))) {
496                         fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,what,ERRNO2);
497                         exit(EXIT_FAILURE);
498                         }
499                 mcache->next=NULL;
500                 memcpy(mcache->src,what,whatl+1);
501                 if (dst)
502                         strcpy(mcache->src+whatl+1,dst);
503                 else
504                         mcache->src[whatl+1]='\0'; /* dst will be empty */
505                 *mcachep=mcache;
506                 }
507         else {
508                 if (verbose) printf("cache hit.\n");
509                 /* if (verbose) would be performance hit */
510                         modifycache_hits++;
511                 }
512         if (!mcache->src[whatl+1])
513                 return(NULL);
514         return(mcache->src+whatl+1);
515 }
516
517 static void modify_cached_stats(void)
518 {
519 struct modifycache **mcachep,*mcache;
520 unsigned long depth,maxdepth=0,entries_total=0;
521 #define MODIFYCACHE_CALLS (modifycache_hits+entries_total)
522
523         for (mcachep=mcachetable;mcachep<mcachetable+HASH_SIZE;mcachep++) {
524                 for (depth=0,mcache=*mcachep;mcache;mcache=mcache->next)
525                         depth++;
526                 if (depth>maxdepth)
527                         maxdepth=depth;
528                 entries_total+=depth;
529                 }
530         printf("modify_cached cache stats: hits=%u.%02u%% (%lu/%lu), HASH_SIZE=%d, maxdepth=%lu\n",
531                 (unsigned)(   100*modifycache_hits/MODIFYCACHE_CALLS     ),
532                 (unsigned)((10000*modifycache_hits/MODIFYCACHE_CALLS)%100),
533                                   modifycache_hits,MODIFYCACHE_CALLS,
534                 HASH_SIZE,maxdepth);
535 #undef MODIFYCACHE_CALLS
536 }
537
538 static void store(char *what,int length)
539 {
540 char ce_trash,*ce,*ceo=&ce_trash;
541
542         if (!(what=modify_cached(what))) {
543                 if (verbose) puts("discarded.");
544                 return;
545                 }
546         if (verbose) printf("store: %d: %s\n",length,what);
547         while ((ce=(tree?strrchr(what,'-'):strchr(what,'-')))) {
548                 if (!tree) *ce='\0';
549                 storeone(what,length);
550                 if (!tree) { *ce='-'; what=ce+1; }
551                 else { *ceo='-'; *(ceo=ce)='\0'; }
552                 }
553         storeone(what,length);
554         if (tree) *ceo='-';
555         lifetime_seq++;
556 }
557
558 static void hit(time_t t,char *bufaction)
559 {
560 static time_t last=-1;
561 static char bufbackup[sizeof(buf)];
562 char *acts[ACTS_MAX],*s;
563 int timetot,acti,i;
564
565         if (verbose) printf("hit: %ld: %s\n",t,bufaction);
566         if (last!=-1) {
567                 if (t<last) {
568                         fprintf(ERRH1"Time goes backward"WHERE1,ERRH2,WHERE2);
569                         t=last;
570                         }
571                 acts[0]=bufbackup;
572                 acti=1;
573                 while ((s=strchr(acts[acti-1],'+'))) {
574                         *s='\0';
575                         acts[acti++]=s+1;
576                         if (acti>=LENGTH(acts)) {
577                                 fprintf(ERRH1"Too many '+'-delimited actions (%d)"WHERE1,ERRH2,acti,WHERE2);
578                                 exit(EXIT_FAILURE);
579                                 }
580                         }
581                 timetot=t-last;
582                 for (i=0;i<acti;i++)
583                         store(acts[i],calctime(timetot,i,acti));
584                 }
585         strcpy(bufbackup,bufaction);
586         last=t;
587 }
588
589 int main(int argc,char **argv)
590 {
591 int optc;
592 time_t basetime=-1,currtime;
593 int hour,min,sec;
594
595         pname=argv[0];
596         while ((optc=getopt_long(argc,argv,"b:pw:qusTtac:f:r:vhV",longopts,NULL))!=EOF) switch (optc) {
597                 
598                 case 'u':
599                         sortby=SORT_NO;
600                         break;
601                 case 's':
602                         sortby=SORT_STORES;
603                         break;
604                 case 'T':
605                         sortby=SORT_TIMETOT;
606                         break;
607                 case 't':
608                         tree=1;
609                         break;
610                 case 'a':
611                         doaverage=1;
612                         break;
613                 case 'c':
614                         if (iscondition(optarg))
615                                 fprintf(ERRH1"Condition \"%s\" already set!\n",ERRH2,optarg);
616                         else addlist(&conditionstail,optarg,-1);
617                         break;
618                 case 'f':
619                         formtotal=optarg;
620                         break;
621                 case 'r': {
622 char *s,*dup;
623
624                         if (!(s=strchr(optarg,':'))) {
625                                 fprintf(ERRH1"No delimiting ':'-character found in -r option \"%s\"!\n",ERRH2,optarg);
626                                 exit(EXIT_FAILURE);
627                                 }
628                         if (!(dup=strdup(optarg))) {
629                                 fprintf(ERRH1"malloc() of \"%s\" string"ERRNO1,ERRH2,optarg,ERRNO2);
630                                 exit(EXIT_FAILURE);
631                                 }
632                         dup[s-optarg]='\t';
633                         addlist(&modifiescmdltail,dup,0);
634                         modifies_tot++;
635                         } break;
636                 case 'v':
637                         verbose=1;
638                         break;
639                 case 'V':
640                         fprintf(stderr,version);
641                         exit(EXIT_FAILURE);
642                 default: /* also 'h' */
643                         usage();
644                         break;
645                 }
646         if (optind==argc) {
647                 if (asprintf(&finame,"%s"DEF_TIMEPLAN,getenv("HOME"))==-1) {
648                         fprintf(ERRH1"Default timeplan filename allocation",ERRH2);
649                         exit(EXIT_FAILURE);
650                         }
651                 }
652         else if (optind+1!=argc) usage();
653         else if (!strcmp(argv[optind],"-")) {
654                 finame="<stdin>";
655                 fi=stdin;
656                 }
657         else finame=argv[optind];
658         if (!fi && !(fi=fopen(finame,"r"))) {
659                 fprintf(ERRH1"open \"%s\" for reading"ERRNO1,ERRH2,finame,ERRNO2);
660                 exit(EXIT_FAILURE);
661                 }
662
663         while (clearerr(fi),fgets(buf,sizeof(buf),fi)==buf) {
664 char *s;
665
666                 line++;
667                 if ((s=strchr(buf,'\n')) && !s[1]) *s='\0';
668                 else {
669                         fprintf(ERRH1"fgets(3) results not newline-terminated"WHERE1,ERRH2,WHERE2);
670                         exit(EXIT_FAILURE);
671                         }
672                 if (!*buf) continue;
673                 if (*buf==':') { /* ":12.7.2000 (St)" */
674 struct tm tm;
675 char wday[3];
676 int i,parsed;
677 time_t t;
678 const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"};
679
680                         assert(LENGTH(days)==7);
681                         i=sscanf(buf,":%d.%d.%d (%2s)%n",&tm.tm_mday,&tm.tm_mon,&tm.tm_year,wday,&parsed);
682                         if ((i!=4 && i!=5) || parsed!=strlen(buf)) { /* See note in sscanf(3) man page */
683                                 fprintf(ERRH1"Timestamp with incorrect format"WHERE1,ERRH2,WHERE2);
684                                 exit(EXIT_FAILURE);
685                                 }
686                         for (i=0;i<7;i++) if (!strcmp(days[i],wday)) break;
687                         if (i>=7)
688                                 fprintf(ERRH1"Non-parsable week-day name \"%s\""WHERE1,ERRH2,wday,WHERE2);
689                         tm.tm_sec=tm.tm_min=tm.tm_hour=0;
690                         tm.tm_wday=tm.tm_yday=-1;
691                         tm.tm_isdst=0; /* FIXME */
692                         tm.tm_mon--;
693                         tm.tm_year-=1900;
694                         t=mktime(&tm);
695                         if (t==-1 || tm.tm_wday<0 || tm.tm_wday>6
696                             || tm.tm_mday<1 || tm.tm_mday>31
697                                         || tm.tm_mon <0 || tm.tm_mon >11
698                                         || tm.tm_year<80 || tm.tm_year>150
699                                   ) {
700                                 fprintf(ERRH1"Incorrect timestamp \"%s\""WHERE1,ERRH2,buf,WHERE2);
701                                 exit(EXIT_FAILURE);
702                                 }
703                         if (i<7 && tm.tm_wday!=i)
704                                 fprintf(ERRH1"Non-matching week-day, given \"%s\", calculated \"%s\""WHERE1,ERRH2,days[i],days[tm.tm_wday],WHERE2);
705 #define WANTED (60*60*24)
706                         if (basetime!=-1 && basetime+WANTED!=t)
707                                 fprintf(ERRH1"Non-continuous timestamp (%ld, wanted %d) \"%s\""WHERE1,ERRH2,t-basetime,WANTED,buf,WHERE2);
708 #undef WANTED
709                         basetime=t;
710                         store(MARK_DAY,0);
711                         lifetime_days++;
712                         continue;
713                         }
714
715                 if (!(isdigit(buf[0]) && isdigit(buf[1]) && buf[2]==':' && isdigit(buf[3]) && isdigit(buf[4])
716                  && (buf[5]=='-' || (buf[5]==':' && isdigit(buf[6]) && isdigit(buf[7]) && buf[8]=='-'))
717                       )
718                  || (sec=0,sscanf(buf,(buf[5]=='-'?"%d:%d-":"%d:%d:%d-"),&hour,&min,&sec)!=2+(buf[5]==':'))
719                  || hour<0 || hour>23
720                  || min <0 || min >59
721                  || sec <0 || sec >59
722                     ) {
723                         fprintf(ERRH1"Incorrect day-time \"%s\""WHERE1,ERRH2,buf,WHERE2);
724                         exit(EXIT_FAILURE);
725                         }
726                 if (basetime==-1) {
727                         fprintf(ERRH1"Day-time found but no basetime timestamp set"WHERE1,ERRH2,WHERE2);
728                         exit(EXIT_FAILURE);
729                         }
730                 currtime=basetime+(hour*60+min)*60+sec;
731                 hit(currtime,buf+(buf[5]=='-'?6:9));
732                 }
733
734         if (!feof(fi) || ferror(fi))  {
735                 fprintf(ERRH1"fgets(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
736                 exit(EXIT_FAILURE);
737                 }
738         if (fi!=stdin && fclose(fi))
739                 fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
740
741         if (verbose)
742                 modify_cached_stats();
743         dumphashtable();
744         return EXIT_SUCCESS;
745 }