chiark / gitweb /
WIP input file handling
[innduct.git] / innfeed / misc.c
1 /*  $Id: misc.c 7420 2005-10-09 04:40:13Z eagle $
2 **
3 **  Helper routines for the innfeed program.
4 **
5 **  Written by James Brister <brister@vix.com>
6 */
7
8 #include "innfeed.h"
9 #include "config.h"
10 #include "clibrary.h"
11
12 #include <assert.h>
13 #include <ctype.h>
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <netdb.h>
17 #include <netinet/in.h>
18 #include <signal.h>
19 #include <syslog.h>
20 #include <sys/param.h>
21 #include <sys/stat.h>
22 #include <time.h>
23
24 /* FIXME: Default to a max length of 256 characters for path names if the
25    host headers doesn't give better information.  Should be replaced by the
26    code from Stevens. */
27 #ifndef PATH_MAX
28 # define PATH_MAX 256
29 #endif
30
31 #include "inn/messages.h"
32 #include "libinn.h"
33
34 #include "endpoint.h"
35 #include "misc.h"
36 #include "tape.h"
37
38 unsigned int openfds ;
39 int debuggingOutput ;
40 unsigned int loggingLevel ;
41 char **PointersFreedOnExit ;
42
43 bool debuggingDump = true ;
44 extern void (*gPrintInfo) (void) ;
45 void (*gCleanUp) (void) = 0 ;
46
47
48 /* Log a message to stderr, called from warn or die.  Mostly the same as the
49    standard message_log_stderr, but prepends the date to each line. */
50 void
51 error_log_stderr_date(int len UNUSED, const char *fmt, va_list args, int err)
52 {
53     char timebuff[30];
54     time_t now;
55     struct tm *tm;
56
57     now = time(NULL);
58     tm = localtime(&now);
59     strftime(timebuff, sizeof(timebuff), "%Y-%m-%d %H:%M:%S", tm);
60     fprintf(stderr, "%s %s: ", timebuff,
61             (message_program_name ? message_program_name : "UNKNOWN"));
62     vfprintf(stderr, fmt, args);
63     if (err) fprintf(stderr, ": %s", strerror(err));
64     fprintf(stderr, "\n");
65 }
66
67 /* If desired, print out the state of innfeed, call a cleanup function, and
68    then dump core.  Used as an exit handler for die. */
69 int
70 dump_core(void)
71 {
72 #if SNAPSHOT_ON_DIE
73     if (debuggingDump && gPrintInfo != NULL)
74         (*gPrintInfo)();
75 #endif
76
77     if (gCleanUp != NULL)
78         (*gCleanUp)();
79   
80     if (CORE_DIRECTORY != NULL)
81         chdir(CORE_DIRECTORY);
82     else
83         chdir(getTapeDirectory());
84   
85     sleep(5);
86     abort();
87
88     /* Not reached. */
89     return 1;
90 }
91
92 /* An alternate version of die, used when we don't want to dump core.  This
93    should somehow eventually be phased out to simplify things; it's
94    basically a copy of die() from lib/error.c that ignores the cleanup
95    handler and has innfeed's handlers hard-coded (ugh). */
96 void
97 logAndExit(int status, const char *format, ...)
98 {
99     va_list args;
100     int length;
101
102     va_start(args, format);
103     length = vsnprintf(NULL, 0, format, args);
104     va_end(args);
105     va_start(args, format);
106     error_log_stderr_date(length, format, args, 0);
107     va_end(args);
108     va_start(args, format);
109     message_log_syslog_err(length, format, args, 0);
110     va_end(args);
111     exit(status);
112 }
113
114
115
116 void d_printf (unsigned int level, const char *fmt, ...) 
117 {
118   static pid_t myPid ;
119   char timeString [30] ;
120   time_t now ;
121   va_list ap ;
122     
123   if (myPid == 0)
124     myPid = getpid ()  ;
125   
126   if (loggingLevel < level)
127     return ;
128   
129   now = theTime() ;
130   /* strip off leading day name */
131   strlcpy (timeString, ctime (&now) + 4, sizeof (timeString)) ;
132   timeString [15] = '\0' ;      /* strip off trailing year and newline */
133
134   va_start (ap, fmt) ;
135   fprintf (stderr, "%s %s[%ld]: ",timeString,
136            (message_program_name ? message_program_name : "UNKNOWN"),
137            (long) myPid) ;
138   vfprintf (stderr, fmt, ap) ;
139   va_end (ap) ;
140 }
141
142 void logOrPrint (int level, FILE *fp, const char *fmt, ...)
143 {
144   va_list ap ;
145
146   va_start (ap,fmt) ;
147   if (fp != NULL)
148     {
149       vfprintf (fp,fmt,ap) ;
150       fputc ('\n',fp) ;
151     }
152   else
153     {
154       char buffer [512] ;      /* gag me */
155
156       vsnprintf (buffer,sizeof (buffer),fmt,ap) ;
157       syslog (level,"%s",buffer) ;
158     }
159   va_end (ap) ;
160 }
161
162
163
164 /* return true if the file exists and is a regular file. */
165 bool fileExistsP (const char *filename)
166 {
167   struct stat buf ;
168
169   if (stat (filename,&buf) < 0)
170     return false ;
171
172   return (S_ISREG (buf.st_mode) ? true : false) ;
173 }
174
175
176 bool isDirectory (const char *filename)
177 {
178   struct stat buf ;
179
180   if (stat (filename,&buf) < 0)
181     return false ;
182
183   return (S_ISDIR (buf.st_mode) ? true : false) ;
184 }
185
186
187
188 bool getNntpResponse (char *p, int *code, char **rest)
189 {
190   bool rval = true ;
191   int cd = 0 ;
192   int digits = 0 ;
193
194   if (rest)
195     *rest = 0 ;
196   *code = 0 ;
197
198   if (p == NULL)
199     return false ;
200   
201   while (*p && CTYPE (isspace, *p))
202     p++ ;
203
204   while (*p && CTYPE (isdigit, *p))
205     {
206       digits++ ;
207       cd = (cd * 10) + (*p - '0') ;
208       p++ ;
209     }
210
211   if (digits != 3)
212     return false ;
213   
214   if (*p == '-')
215     p++ ;
216   
217   while (*p && CTYPE (isspace, *p))
218     p++ ;
219       
220   if (rest)
221     *rest = p ;
222
223   *code = cd ;
224   
225   return rval ;
226 }
227
228
229
230 /* Pull out a message id from a response on to a streaming command */
231 char *getMsgId (const char *p)
232 {
233   const char *q ;
234   char *rval ;
235   
236   while (*p && CTYPE (isspace, *p)) p++ ;
237   while (*p && !CTYPE (isspace, *p)) p++ ; /* skip response code */
238   while (*p && CTYPE (isspace, *p)) p++ ;
239
240   if ( *p == '\0' )
241     return NULL ;
242
243   q = p ;
244   while ( *q && !CTYPE (isspace, *q) )
245     q++ ;
246
247   rval = xstrndup (p, q - p) ;
248
249   return rval ;
250 }
251
252
253
254
255 char *findNonBlankString (char *ptr, char **tail)
256 {
257   char *p, *q ;
258
259   for (p = ptr ; *p && CTYPE (isspace, *p) ; p++)
260     /* nada */ ;
261   if ( ! *p )
262     return NULL ;
263
264   for (q = p ; *q && !CTYPE (isspace, *q) ; q++)
265     /* nada */ ;
266
267   *tail = q ;
268
269   return p ;
270 }
271
272
273 /* strtok can't handle zero length tokens. */
274 char *mystrtok (char *line, const char *sep)
275 {
276   static char *newPoint ;
277   char *oldline ;
278   
279   if (line == NULL && newPoint == NULL)
280     return NULL ;
281
282   if (line != NULL)
283     {
284       oldline = line ;
285       while (*line != '\0' && strchr (sep,*line) == NULL)
286         line++ ;
287
288       if (*line == '\0')
289         newPoint = NULL ;
290       else
291         {
292           newPoint = line + 1 ;
293           *line = '\0' ;
294         }
295     }
296   else
297     {
298       if (newPoint == NULL)
299         return NULL ;
300       
301       oldline = newPoint ;
302       line = oldline ;
303       
304       while (*line != '\0' && strchr (sep,*line) == NULL)
305         line++ ;
306
307       if (*line == '\0')
308         newPoint = NULL ;
309       else
310         {
311           newPoint = line + 1 ;
312           *line = '\0' ;
313         }
314     }
315
316   return oldline ;
317 }
318
319
320
321 void trim_ws (char *string)
322 {
323   char *p ;
324   unsigned int len ;
325
326   assert (string != NULL) ;
327
328   len = strlen (string) ;
329   if (len == 0)
330     return ;
331   
332   for (p = string + len - 1 ; p >= string && CTYPE (isspace, *p) ; p--)
333     /* nada */ ;
334   *++p = '\0' ;
335 }
336
337
338 #if 0
339 /* Scribble on top of memory we're about to free. */
340 void deadBeef (void *base, size_t byteCount)
341 {
342   unsigned char *b = (unsigned char *) base ;
343   int i ;
344
345 #if 0
346
347   memset (base, 0, byteCount) ;
348
349 #else
350
351   assert (b != NULL) ;
352
353   for (i = 0 ; i < ((int) byteCount) - 4 ; i += 4)
354     {
355 #if 0
356       *((int *) (b + i)) = 0xdeadbeef ;
357 #else
358       b [i + 0] = (unsigned char) 0xde ;
359       b [i + 1] = (unsigned char) 0xad ;
360       b [i + 2] = (unsigned char) 0xbe ;
361       b [i + 3] = (unsigned char) 0xef ;
362 #endif
363     }
364   
365   switch (byteCount % 4)
366     {
367       case 0:
368         *(b + i + 3) = (unsigned char) 0xef ;
369         
370       case 3:
371         *(b + i + 2) = (unsigned char) 0xbe ;
372
373       case 2:
374         *(b + i + 1) = (unsigned char) 0xad ;
375
376       case 1:
377         *b = (unsigned char) 0xde ;
378     }
379
380 #endif
381 }
382 #endif 
383
384 /* Not using plain flock or lockf 'cause I don't want to waste file
385    descriptors. This routine is based on the file shlock.c from INN. */
386 bool lockFile (const char *fileName)
387 {
388   char buff [20] ;
389   char tmpName [PATH_MAX], realName [PATH_MAX] ;
390   char *p ;
391   int fd, i ;
392   pid_t pid = getpid () ;
393
394   strlcpy (realName,fileName,sizeof (realName)) ;
395   if ((p = strrchr (realName, '/')) != NULL)
396     {
397       *p = '\0' ;
398       snprintf (tmpName, sizeof(tmpName), "%s/lockf%ld", realName,
399                 (long) pid) ;
400       *p = '/' ;
401     }
402   else
403     snprintf (tmpName, sizeof(tmpName), "lockf%ld", (long) pid) ;
404   
405   /* Create the temporary name for the lock file. */
406   while ((fd = open (tmpName, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0)
407     {
408       switch (errno)
409         {
410           default:
411             unlink (tmpName) ;
412             syswarn ("ME lock file open: %s", tmpName) ;
413             return false ;
414
415           case EEXIST:
416             if (unlink (tmpName) < 0)
417               {
418                 syswarn ("ME lock file unlink: %s", tmpName) ;
419                 return false ;
420               }
421             break;
422         }
423     }
424
425   /* stick our pid in the temp file. */
426   snprintf (buff,sizeof(buff),"%ld\n",(long) pid) ;
427   if (write (fd,buff,(size_t) strlen (buff)) != (int) strlen (buff))
428     {
429       syswarn ("ME lock file pid-write") ;
430       close (fd) ;
431       unlink (tmpName) ;
432       return false ;
433     }
434   close (fd) ;
435
436   /* now link the real name to the temp file. */
437   while (link (tmpName,realName) < 0)
438     {
439       switch (errno) 
440         {
441           default:              /* opps. bailing out. */
442             syswarn ("ME lock file link: %s", realName) ;
443             unlink (tmpName) ;
444             return false ;
445
446           case EEXIST:          
447             /* the real lock file exists. So pull out the pid in there and
448                see if that process is still alive. */
449             if ((fd = open (realName,O_RDONLY)) < 0)
450               {
451                 syswarn ("ME lock file open: %s", realName) ;
452                 unlink (tmpName) ;
453                 return false ;
454               }
455
456             if ((i = read (fd,buff,sizeof (buff) - 1)) <= 0)
457               {
458                 close (fd) ;
459                 unlink (tmpName) ;
460                 return false ;
461               }
462             close (fd) ;
463             
464             buff [i] = '\0' ;
465             pid = (pid_t) atol (buff) ;
466             if (pid <= 0)
467               {
468                 warn ("ME lock bad-pid info in %s: %s", realName, buff) ;
469                 unlink (tmpName) ;
470                 return false ;
471               }
472
473             /* now send a null signal to the process named inside to see if
474                it's still alive. */
475             if (kill (pid,0) == 0)
476               {
477                 warn ("ME lock in-use already: %s by pid %ld", realName,
478                       (unsigned long) pid);
479                 unlink (tmpName) ;
480                 return false ;    /* process is still alive */
481               }
482
483             /* process that took out the lock is gone */
484             if (unlink (realName) < 0)
485               {
486                 syswarn ("ME lock file unlink: %s", realName) ;
487                 unlink (tmpName) ;
488                 return false ;
489               }
490         }
491     }
492
493   unlink (tmpName) ;
494
495   return true ;
496 }
497
498
499 void unlockFile (const char *lockfile)
500 {
501   unlink (lockfile) ;
502 }
503
504
505 bool endsIn (const char *string, const char *tail)
506 {
507   size_t len = strlen (tail) ;
508   size_t slen = strlen (string) ;
509
510   if (slen < len)
511     return false ;
512   else if (strcmp (string + slen - len, tail) == 0)
513     return true ;
514   else
515     return false ;
516 }
517
518       
519 /* append the contents of src to dest. src is removed if append if
520    successful */
521 bool appendFile (const char *dest, const char *src)
522 {
523   FILE *inTmp, *outTmp ;
524   char buff [BUFSIZ] ;
525   size_t rval ;
526
527       /* append the outputFilename file to the inputFilename file */
528   if ((outTmp = fopen (dest, "a")) == NULL)
529     die ("fopen (%s): %s",dest, strerror (errno)) ;
530   if ((inTmp = fopen (src, "r")) == NULL)
531     die ("fopen (%s): %s",src, strerror (errno)) ;
532
533   while ((rval = fread (buff,sizeof (char),BUFSIZ,inTmp)) > 0)
534     {
535       if (fwrite (buff,sizeof (char), rval, outTmp) != rval)
536         die ("fwrite: %s", strerror (errno)) ;
537     }
538
539   if (ferror (inTmp))
540     die ("Error on inTmp in newTape") ;
541   if (ferror (outTmp))
542     die ("Error on outTmp in newTape") ;
543
544   if (fclose (inTmp) != 0)
545     die ("fclose (inTmp): appendFile (%s,%s): %s",dest,src,strerror (errno)) ;
546       
547   if (fclose (outTmp) != 0)
548     die ("fclose (outTmp): appendFile (%s,%s): %s",dest,src,strerror (errno)) ;
549
550   if (unlink (src) != 0)
551     die ("unlink (%s): %s", src, strerror (errno)) ;
552
553   return true ;
554 }
555
556
557 /* return true if file1 is older than file2 */
558 bool isOlder (const char *file1, const char *file2)
559 {
560   struct stat buf1 ;
561   struct stat buf2 ;
562
563   if (stat (file1,&buf1) < 0)
564     return false ;
565
566   if (stat (file2,&buf2) < 0)
567     return false ;
568
569   return ((buf1.st_mtime < buf2.st_mtime) ? true : false) ;
570 }
571
572
573 void freeCharP (char *charp)
574 {
575   free (charp) ;
576 }
577
578
579 /* return the length of the file reference by the given file descriptor */
580 long fileLength (int fd)
581 {
582   struct stat buf ;
583
584   if (fstat (fd,&buf) < 0)
585     return false ;
586
587   return ((long) buf.st_size) ;
588 }
589
590
591
592 const char *boolToString (bool val)
593 {
594   return val ? "true" : "false" ;
595 }
596
597 void addPointerFreedOnExit (char *pointerToFree)
598 {
599   static int totalPointers = 0 ;
600   static int nextPointer = 0 ;
601
602   if (nextPointer == 0 || nextPointer == totalPointers - 1)
603     {
604       int i;
605
606       totalPointers += 16 ;
607       if (PointersFreedOnExit == NULL)
608         PointersFreedOnExit = xmalloc (sizeof(char *) * totalPointers) ;
609       else
610         PointersFreedOnExit =
611           xrealloc (PointersFreedOnExit, sizeof(char *) * totalPointers) ;
612
613       for (i = nextPointer; i < totalPointers; i++)
614         PointersFreedOnExit [i] = NULL;
615     }
616   PointersFreedOnExit [nextPointer++] = pointerToFree ;
617 }
618
619 /* malloc a buffer and build the filename in it. */
620 char *buildFilename (const char *directory, const char *fname)
621 {
622   int len = 0 ;
623   char *p = NULL ;
624
625   if (fname == NULL)
626     return NULL ;
627
628   if (directory == NULL)
629     directory = "." ;
630   
631   len = strlen (directory) + strlen (fname) + 2 + 1 ;
632
633   if (len < pathMax(directory) - 2)
634     {
635       p = xmalloc (len) ;
636       p [0] = '\0' ;
637       if (fname [0] != '/')
638         {
639           strlcat (p,directory,len) ;
640           if (p [strlen(p) - 1] != '/')
641             strlcat (p,"/",len) ;
642         }
643       strlcat (p,fname,len) ;
644     }
645
646   return p ;
647 }
648
649
650
651 /* borrows heavily from the shrinkfile program by chongo. */
652 bool shrinkfile (FILE *fp, long size, char *name, const char *mode)
653 {
654   long currlen = ftello (fp) ;
655   char *tmpname ;
656   char buffer [BUFSIZ] ;
657   FILE *tmpFp ;
658   int c ;
659   int i ;
660   int fd ;
661
662   if (currlen <= size)
663     {
664       d_printf (1,"No need to shrink file (%s %ld vs %ld\n",
665                name,size,currlen) ;
666       return true ;
667     }
668
669   /* create a temp file. */
670   tmpname = concat (name,".XXXXXX",(char *)0) ;
671   fd = mkstemp (tmpname) ;
672
673   if (fd < 0)
674     {
675       syswarn ("ME error creating temp shrink file for %s", name) ;
676       free (tmpname) ;
677       return false ;
678     }
679
680   if ((tmpFp = fdopen (fd,"w")) == NULL)
681     {
682       syswarn ("ME error opening temp shrink file %s", tmpname) ;
683       free (tmpname) ;
684       return false ;
685     }
686
687   if (fseeko (fp,currlen - size,SEEK_SET) != 0)
688     {
689       fclose (tmpFp) ;
690       warn ("ME error seeking to point %ld in %s", currlen - size, name) ;
691       free (tmpname) ;
692       return false ;
693     }
694
695   /* find the end of the next line in the shrinking file. */
696   while ((c = fgetc (fp)) != '\n')
697     if (c == EOF)
698       {
699         warn ("ME no newline in shrinking file %s", name) ;
700         fclose (tmpFp) ;
701         fseeko (fp,currlen,SEEK_SET) ;
702         free (tmpname) ;
703         return false ;
704     }
705
706   /* copy the tail of the shrinking file to the temp file. */
707   while ((i = fread (buffer,1,sizeof (buffer),fp)) > 0)
708     {
709       if (fwrite (buffer,1,i,tmpFp) != (size_t) i)
710         {
711           fclose (tmpFp) ;
712           syswarn ("ME fwrite failed to temp shrink file %s", tmpname) ;
713           fseeko (fp,currlen, SEEK_SET) ;
714           free (tmpname) ;
715           return false ;
716         }
717     }
718
719   if (i < 0)
720     logAndExit (1,"ME fread failed on file %s: %s",name, strerror (errno)) ;
721
722   fclose (tmpFp) ;
723
724   if (unlink (name) != 0)
725     logAndExit (1,"ME oserr unlink %s: %s",name, strerror (errno)) ;
726
727   /* we're in the same directory so this is ok. */
728   if (rename (tmpname,name) != 0)
729     logAndExit (1,"ME oserr rename %s, %s: %s", tmpname, name,
730                 strerror (errno)) ;
731   
732   if (freopen (name,mode,fp) != fp)
733     logAndExit (1,"ME freopen on shrink file failed %s: %s", name,
734                 strerror (errno)) ;
735
736   fseeko (fp,0,SEEK_END) ;
737   size = ftello (fp) ;
738
739   notice ("ME file %s shrunk from %ld to %ld", name, currlen, size) ;
740
741   free (tmpname) ;
742   
743   return true ;
744 }
745
746
747
748 long pathMax (const char *pathname UNUSED)
749 {
750   static long rval = 0 ;
751
752   if (rval > 0)
753     return rval ;
754   
755 #if defined (PATH_MAX)
756
757   rval = PATH_MAX ;
758
759 #elif defined (_POSIX_PATH_MAX)
760
761   rval = _POSIX_PATH_MAX ;
762
763 #elif defined (DO_HAVE_PATHCONF) && defined (_PC_PATH_MAX)
764
765   if (pathname == NULL)
766     pathname = "/tmp" ;
767   
768   rval = pathconf (pathname,_PC_PATH_MAX) ;
769
770 #else
771
772   rval = 255 ;
773   if (!logged) 
774     {
775       syslog (LOG_ERR,NO_PATH_MAX,rval) ;
776       logged = true ;
777     }
778   
779 #endif
780
781   return rval ;
782 }