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