util: reopen server logs on SIGUSR1 for external log rotation
[openafs.git] / src / util / serverLog.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*  serverLog.c     - Server logging                                      */
11 /*                                                                        */
12 /*  Information Technology Center                                         */
13 /*  Date: 05/21/97                                                        */
14 /*                                                                        */
15 /*  Function    - These routines implement logging from the servers.      */
16 /*                                                                        */
17 /* ********************************************************************** */
18
19 #include <afsconfig.h>
20 #include <afs/param.h>
21 #include <afs/stds.h>
22
23 #include <afs/procmgmt.h>       /* signal(), kill(), wait(), etc. */
24
25 #include <roken.h>              /* Must come after procmgmt.h */
26 #ifdef AFS_PTHREAD_ENV
27  #include <opr/softsig.h>
28  #include <afs/procmgmt_softsig.h>      /* Must come after softsig.h */
29 #endif
30 #include <afs/opr.h>
31 #include "afsutil.h"
32 #include "fileutil.h"
33 #include <lwp.h>
34
35 #if defined(AFS_PTHREAD_ENV)
36 #include <pthread.h>
37 static pthread_once_t serverLogOnce = PTHREAD_ONCE_INIT;
38 static pthread_mutex_t serverLogMutex;
39 #define LOCK_SERVERLOG() opr_Verify(pthread_mutex_lock(&serverLogMutex) == 0)
40 #define UNLOCK_SERVERLOG() opr_Verify(pthread_mutex_unlock(&serverLogMutex) == 0)
41
42 #ifdef AFS_NT40_ENV
43 #define NULLDEV "NUL"
44 #else
45 #define NULLDEV "/dev/null"
46 #endif
47
48 #else /* AFS_PTHREAD_ENV */
49 #define LOCK_SERVERLOG()
50 #define UNLOCK_SERVERLOG()
51 #endif /* AFS_PTHREAD_ENV */
52
53 #ifdef AFS_NT40_ENV
54 #define F_OK 0
55 #define O_NONBLOCK 0
56 #endif
57
58 /*!
59  * Placeholder function to return dummy thread number.
60  */
61 static int
62 dummyThreadNum(void)
63 {
64     return -1;
65 }
66 static int (*threadNumProgram) (void) = dummyThreadNum;
67
68 /* After single-threaded startup, accesses to serverlogFD and
69  * serverLogSyslog* are protected by LOCK_SERVERLOG(). */
70 static int serverLogFD = -1;    /*!< The log file descriptor. */
71 static struct logOptions serverLogOpts; /*!< logging options */
72
73 int LogLevel;                   /*!< The current logging level. */
74 static int threadIdLogs = 0;    /*!< Include the thread id in log messages when true. */
75 static int resetSignals = 0;    /*!< Reset signal handlers for the next signal when true. */
76 static char *ourName = NULL;    /*!< The fully qualified log file path, saved for reopens. */
77
78 static int OpenLogFile(const char *fileName);
79 static void RotateLogFile(void);
80
81 /*!
82  * Determine if the file is a named pipe.
83  *
84  * This check is performed to support named pipes as logs by not rotating them
85  * and opening them with a non-blocking flags.
86  *
87  * \param[in] fileName  log file name
88  *
89  * \returns non-zero if log file is a named pipe.
90  */
91 static int
92 IsFIFO(const char *fileName)
93 {
94     struct stat statbuf;
95     return (lstat(fileName, &statbuf) == 0) && (S_ISFIFO(statbuf.st_mode));
96 }
97
98 /*!
99  * Return the current logging level.
100  */
101 int
102 GetLogLevel(void)
103 {
104     return LogLevel;
105 }
106
107 /*!
108  * Return the log destination.
109  */
110 enum logDest
111 GetLogDest(void)
112 {
113     return serverLogOpts.lopt_dest;
114 }
115
116 /*!
117  * Get the log filename for file based logging.
118  *
119  * An empty string is returned if the log destination is not
120  * file based.  The caller must make a copy of the string
121  * if it is accessed after the CloseLog.
122  */
123 const char *
124 GetLogFilename(void)
125 {
126     return serverLogOpts.lopt_dest == logDest_file ? (const char*)ourName : "";
127 }
128
129 /*!
130  * Set the function to log thread numbers.
131  */
132 void
133 SetLogThreadNumProgram(int (*func) (void) )
134 {
135     threadNumProgram = func;
136 }
137
138 /*!
139  * Write a block of bytes to the log.
140  *
141  * Write a block of bytes directly to the log without formatting
142  * or prepending a timestamp.
143  *
144  * \param[in] buf  pointer to bytes to write
145  * \param[in] len  number of bytes to write
146  */
147 void
148 WriteLogBuffer(char *buf, afs_uint32 len)
149 {
150     LOCK_SERVERLOG();
151     if (serverLogFD >= 0) {
152         if (write(serverLogFD, buf, len) < 0)
153             ; /* don't care */
154     }
155     UNLOCK_SERVERLOG();
156 }
157
158 /*!
159  * Get the current thread number.
160  */
161 int
162 LogThreadNum(void)
163 {
164   return (*threadNumProgram) ();
165 }
166
167 /*!
168  * Write a message to the log.
169  *
170  * \param[in] format  printf-style format string
171  * \param[in] args    variable list of arguments
172  */
173 void
174 vFSLog(const char *format, va_list args)
175 {
176     time_t currenttime;
177     char tbuffer[1024];
178     char *info;
179     size_t len;
180     struct tm tm;
181     int num;
182
183     currenttime = time(NULL);
184     len = strftime(tbuffer, sizeof(tbuffer), "%a %b %d %H:%M:%S %Y ",
185                    localtime_r(&currenttime, &tm));
186     info = &tbuffer[len];
187
188     if (threadIdLogs) {
189         num = (*threadNumProgram) ();
190         if (num > -1) {
191             snprintf(info, (sizeof tbuffer) - strlen(tbuffer), "[%d] ",
192                      num);
193             info += strlen(info);
194         }
195     }
196
197     vsnprintf(info, (sizeof tbuffer) - strlen(tbuffer), format, args);
198
199     len = strlen(tbuffer);
200     LOCK_SERVERLOG();
201 #ifdef HAVE_SYSLOG
202     if (serverLogOpts.dest == logDest_syslog) {
203         syslog(LOG_INFO, "%s", info);
204     } else
205 #endif
206     if (serverLogFD >= 0) {
207         if (write(serverLogFD, tbuffer, len) < 0)
208             ; /* don't care */
209     }
210     UNLOCK_SERVERLOG();
211
212 #if !defined(AFS_PTHREAD_ENV) && !defined(AFS_NT40_ENV)
213     if (serverLogOpts.dest == logDest_file) {
214         fflush(stdout);
215         fflush(stderr);         /* in case they're sharing the same FD */
216     }
217 #endif
218 }                               /*vFSLog */
219
220 /*!
221  * Write a message to the log.
222  *
223  * \param[in] format  printf-style format specification
224  * \param[in] ...     arguments for format specification
225  */
226 void
227 FSLog(const char *format, ...)
228 {
229     va_list args;
230
231     va_start(args, format);
232     vFSLog(format, args);
233     va_end(args);
234 }                               /*FSLog */
235
236 /*!
237  * Write the command-line invocation to the log.
238  *
239  * \param[in] argc      argument count from main()
240  * \param[in] argv      argument vector from main()
241  * \param[in] progname  program name
242  * \param[in] version   program version
243  * \param[in] logstring log message string
244  * \param[in] log       printf-style log function
245  */
246 void
247 LogCommandLine(int argc, char **argv, const char *progname,
248                const char *version, const char *logstring,
249                void (*log) (const char *format, ...))
250 {
251     int i, l;
252     char *commandLine, *cx;
253
254     opr_Assert(argc > 0);
255
256     for (l = i = 0; i < argc; i++)
257         l += strlen(argv[i]) + 1;
258     if ((commandLine = malloc(l))) {
259         for (cx = commandLine, i = 0; i < argc; i++) {
260             strcpy(cx, argv[i]);
261             cx += strlen(cx);
262             *(cx++) = ' ';
263         }
264         commandLine[l-1] = '\0';
265         (*log)("%s %s %s%s(%s)\n", logstring, progname,
266                     version, strlen(version)>0?" ":"", commandLine);
267         free(commandLine);
268     } else {
269         /* What, we're out of memory already!? */
270         (*log)("%s %s%s%s\n", logstring,
271               progname, strlen(version)>0?" ":"", version);
272     }
273 }
274
275 /*!
276  * Write the single-DES deprecation warning to the log.
277  */
278 void
279 LogDesWarning(void)
280 {
281     /* The blank newlines help this stand out a bit more in the log. */
282     ViceLog(0, ("\n"));
283     ViceLog(0, ("WARNING: You are using single-DES keys in a KeyFile. Using single-DES\n"));
284     ViceLog(0, ("WARNING: long-term keys is considered insecure, and it is strongly\n"));
285     ViceLog(0, ("WARNING: recommended that you migrate to stronger encryption. See\n"));
286     ViceLog(0, ("WARNING: OPENAFS-SA-2013-003 on http://www.openafs.org/security/\n"));
287     ViceLog(0, ("WARNING: for details.\n"));
288     ViceLog(0, ("\n"));
289 }
290
291 /*!
292  * Move the current log file out of the way so a new one can be started.
293  *
294  * The format of the new name depends on the logging style.  The traditional
295  * Transarc style appends ".old" to the log file name.  When MR-AFS style
296  * logging is in effect, a time stamp is appended to the log file name instead
297  * of ".old".
298  *
299  * \bug  Unfortunately, no check is made to avoid overwriting
300  *       old logs in the traditional Transarc mode.
301  *
302  * \param fileName  fully qualified log file path
303  */
304 static void
305 RenameLogFile(const char *fileName)
306 {
307     int code;
308     char *nextName = NULL;
309     int tries;
310     time_t t;
311     struct stat buf;
312     struct tm *timeFields;
313
314     switch (serverLogOpts.lopt_rotateStyle) {
315     case logRotate_none:
316         break;
317     case logRotate_old:
318         code = asprintf(&nextName, "%s.old", fileName);
319         if (code < 0) {
320             nextName = NULL;
321         }
322         break;
323     case logRotate_timestamp:
324         time(&t);
325         for (tries = 0; nextName == NULL && tries < 100; t++, tries++) {
326             timeFields = localtime(&t);
327             code = asprintf(&nextName, "%s.%d%02d%02d%02d%02d%02d",
328                             fileName, timeFields->tm_year + 1900,
329                             timeFields->tm_mon + 1, timeFields->tm_mday,
330                             timeFields->tm_hour, timeFields->tm_min,
331                             timeFields->tm_sec);
332             if (code < 0) {
333                 nextName = NULL;
334                 break;
335             }
336             if (lstat(nextName, &buf) == 0) {
337                 /* Avoid clobbering a log. */
338                 free(nextName);
339                 nextName = NULL;
340             }
341         }
342         break;
343     default:
344         opr_Assert(0);
345     }
346     if (nextName != NULL) {
347         rk_rename(fileName, nextName);  /* Don't check the error code. */
348         free(nextName);
349     }
350 }
351
352 /*!
353  * Write message to the log to indicate the log level.
354  *
355  * This helper function is called by the signal handlers when the log level is
356  * changed, to write a message to the log to indicate the log level has been
357  * changed.
358  */
359 static void*
360 DebugOn(void *param)
361 {
362     int loglevel = (intptr_t)param;
363     if (loglevel == 0) {
364         ViceLog(0, ("Reset Debug levels to 0\n"));
365     } else {
366         ViceLog(0, ("Set Debug On level = %d\n", loglevel));
367     }
368     return 0;
369 }                               /*DebugOn */
370
371 /*!
372  * Signal handler to increase the logging level.
373  *
374  * Increase the current logging level to 1 if it in currently 0,
375  * otherwise, increase the current logging level by a factor of 5 if it
376  * is currently non-zero.
377  *
378  * Enables thread id logging when the log level is greater than 1.
379  */
380 void
381 SetDebug_Signal(int signo)
382 {
383     if (LogLevel > 0) {
384         LogLevel *= 5;
385
386 #if defined(AFS_PTHREAD_ENV)
387         if (LogLevel > 1 && threadNumProgram != NULL &&
388             threadIdLogs == 0) {
389             threadIdLogs = 1;
390         }
391 #endif
392     } else {
393         LogLevel = 1;
394
395 #if defined(AFS_PTHREAD_ENV)
396         if (threadIdLogs == 1)
397             threadIdLogs = 0;
398 #endif
399     }
400 #if defined(AFS_PTHREAD_ENV)
401     DebugOn((void *)(intptr_t)LogLevel);
402 #else /* AFS_PTHREAD_ENV */
403     IOMGR_SoftSig(DebugOn, (void *)(intptr_t)LogLevel);
404 #endif /* AFS_PTHREAD_ENV */
405
406     if (resetSignals) {
407         /* When pthreaded softsig handlers are not in use, some platforms
408          * require this signal handler to be set again. */
409         (void)signal(signo, SetDebug_Signal);
410     }
411 }                               /*SetDebug_Signal */
412
413 /*!
414  * Signal handler to reset the logging level.
415  *
416  * Reset the logging level and disable thread id logging.
417  *
418  * \note This handler has the side-effect of rotating and reopening
419  *       MR-AFS style logs.
420  */
421 void
422 ResetDebug_Signal(int signo)
423 {
424     LogLevel = 0;
425
426 #if defined(AFS_PTHREAD_ENV)
427     DebugOn((void *)(intptr_t)LogLevel);
428 #else /* AFS_PTHREAD_ENV */
429     IOMGR_SoftSig(DebugOn, (void *)(intptr_t)LogLevel);
430 #endif /* AFS_PTHREAD_ENV */
431
432     if (resetSignals) {
433         /* When pthreaded softsig handlers are not in use, some platforms
434          * require this signal handler to be set again. */
435         (void)signal(signo, ResetDebug_Signal);
436     }
437 #if defined(AFS_PTHREAD_ENV)
438     if (threadIdLogs == 1)
439         threadIdLogs = 0;
440 #endif
441     if (serverLogOpts.lopt_rotateOnReset) {
442         RotateLogFile();
443     }
444 }                               /*ResetDebug_Signal */
445
446 /*!
447  * Handle requests to reopen the log.
448  *
449  * This signal handler will reopen the log file. A new, empty log file
450  * will be created if the log file does not already exist.
451  *
452  * External log rotation programs may rotate a server log file by
453  * renaming the existing server log file and then immediately sending a
454  * signal to the corresponding server process.  Server log messages will
455  * continue to be appended to the renamed server log file until the
456  * server log is reopened.  After this signal handler completes, server
457  * log messages will be written to the new log file.  This allows
458  * external log rotation programs to rotate log files without
459  * messages being dropped.
460  */
461 void
462 ReOpenLog_Signal(int signo)
463 {
464     ReOpenLog();
465     if (resetSignals) {
466         (void)signal(signo, ReOpenLog_Signal);
467     }
468 }
469
470 #ifdef AFS_PTHREAD_ENV
471 /*!
472  * Register pthread-safe signal handlers for server log management.
473  *
474  * \note opr_softsig_Init() must be called before this function.
475  */
476 void
477 SetupLogSoftSignals(void)
478 {
479     opr_softsig_Register(SIGHUP, ResetDebug_Signal);
480     opr_softsig_Register(SIGTSTP, SetDebug_Signal);
481     opr_softsig_Register(SIGUSR1, ReOpenLog_Signal);
482 #ifndef AFS_NT40_ENV
483     (void)signal(SIGPIPE, SIG_IGN);
484 #endif
485 }
486 #endif /* AFS_PTHREAD_ENV */
487
488 /*!
489  * Register signal handlers for server log management.
490  *
491  * \note This function is deprecated and should not be used
492  *       in new code. This function should be removed when
493  *       all the servers have been converted to pthreads
494  *       and lwp has been removed.
495  */
496 void
497 SetupLogSignals(void)
498 {
499     resetSignals = 1;
500     (void)signal(SIGHUP, ResetDebug_Signal);
501     (void)signal(SIGTSTP, SetDebug_Signal);
502     (void)signal(SIGUSR1, ReOpenLog_Signal);
503 #ifndef AFS_NT40_ENV
504     (void)signal(SIGPIPE, SIG_IGN);
505 #endif
506 }
507
508 #if defined(AFS_PTHREAD_ENV)
509 static void
510 InitServerLogMutex(void)
511 {
512     opr_Verify(pthread_mutex_init(&serverLogMutex, NULL) == 0);
513 }
514 #endif /* AFS_PTHREAD_ENV */
515
516 /*!
517  * Redirect stdout and stderr to the log file.
518  *
519  * \note Call directly after opening the log file.
520  *
521  * \param[in] fileName  log file name
522  */
523 static void
524 RedirectStdStreams(const char *fileName)
525 {
526     if (freopen(fileName, "a", stdout) == NULL)
527         ; /* don't care */
528     if (freopen(fileName, "a", stderr) != NULL) {
529 #ifdef HAVE_SETVBUF
530         setvbuf(stderr, NULL, _IONBF, 0);
531 #else
532         setbuf(stderr, NULL);
533 #endif
534     }
535 }
536
537 /*!
538  * Open the log file.
539  *
540  * Open the log file using the options given in OpenLog().
541  *
542  * \returns 0 on success
543  */
544 static int
545 OpenLogFile(const char *fileName)
546 {
547     /*
548      * This function should allow various libraries that inconsistently
549      * use stdout/stderr to all go to the same place
550      */
551     int tempfd;
552     int flags = O_WRONLY | O_CREAT | O_APPEND;
553
554     opr_Assert(serverLogOpts.dest == logDest_file);
555
556     opr_Assert(fileName != NULL);
557
558     if (IsFIFO(fileName)) {
559         /* Support named pipes as logs by not rotating them. */
560         flags |= O_NONBLOCK;
561     } else if (serverLogOpts.lopt_rotateOnOpen) {
562         /* Old style logging always started a new log file. */
563         flags |= O_TRUNC;
564         RenameLogFile(fileName);
565     }
566
567     tempfd = open(fileName, flags, 0666);
568     if (tempfd < 0) {
569         printf("Unable to open log file %s\n", fileName);
570         return -1;
571     }
572     RedirectStdStreams(fileName);
573
574     /* Save our name for reopening. */
575     free(ourName);
576     ourName = strdup(fileName);
577     opr_Assert(ourName != NULL);
578
579     serverLogFD = tempfd;
580
581     return 0;
582 }
583
584 /*!
585  * Open the log file descriptor or a connection to the system log.
586  *
587  * This function should be called once during program initialization and
588  * must be called before calling FSLog() or WriteLogBuffer().  The
589  * fields of the given argument specify the logging destination and
590  * various optional features.
591  *
592  * The lopt_logLevel value specifies the initial logging level.
593  *
594  * The lopt_dest enum specifies the logging destination; either
595  * file based (logDest_file) or the system log (logDest_syslog).
596  *
597  * File Based Logging
598  * ------------------
599  *
600  * A file will be opened for log messages when the lopt_dest enum is set
601  * to logDest_file.  The file specified by lopt_filename will be opened
602  * for appending log messages.  A new file will be created if the log
603  * file does not exist.
604  *
605  * The lopt_rotateOnOpen flag specifies whether an existing log file is
606  * to be renamed and a new log file created during the call to OpenLog.
607  * The lopt_rotateOnOpen flag has no effect if the file given by
608  * lopt_filename is a named pipe (fifo).
609  *
610  * The lopt_rotateOnReset flag specifies whether the log file is renamed
611  * and then reopened when the reset signal (SIGHUP) is caught.
612  *
613  * The lopt_rotateStyle enum specifies how the new log file is renamed when
614  * lopt_rotateOnOpen or lopt_rotateOnReset are set. The lopt_rotateStyle
615  * may be the traditional Transarc style (logRotate_old) or the MR-AFS
616  * style (logRotate_timestamp).
617  *
618  * When lopt_rotateStyle is set to logRotate_old, the suffix ".old" is
619  * appended to the log file name. The existing ".old" log file is
620  * removed.
621  *
622  * When lopt_rotateStyle is set to logRotate_timestamp, a timestamp
623  * string is appended to the log file name and existing files are not
624  * removed.
625  *
626  * \note  Messages written to stdout and stderr are redirected to the log
627  *        file when file-based logging is in effect.
628  *
629  * System Logging
630  * --------------
631  *
632  * A connection to the system log (syslog) will be established for log
633  * messages when the lopt_dest enum is set to logDest_syslog.
634  *
635  * The lopt_facility specifies the system log facility to be used when
636  * writing messages to the system log.
637  *
638  * The lopt_tag string specifies the indentification string to be used
639  * when writing messages to the system log.
640  *
641  * \param opts  logging options. A copy of the logging
642  *              options will be made before returning to
643  *              the caller.
644  *
645  * \returns 0 on success
646  */
647 int
648 OpenLog(struct logOptions *opts)
649 {
650     int code;
651
652 #if defined(AFS_PTHREAD_ENV)
653     opr_Verify(pthread_once(&serverLogOnce, InitServerLogMutex) == 0);
654 #endif /* AFS_PTHREAD_ENV */
655
656     LogLevel = serverLogOpts.logLevel = opts->logLevel;
657     serverLogOpts.dest = opts->dest;
658     switch (serverLogOpts.dest) {
659     case logDest_file:
660         serverLogOpts.lopt_rotateOnOpen = opts->lopt_rotateOnOpen;
661         serverLogOpts.lopt_rotateOnReset = opts->lopt_rotateOnReset;
662         serverLogOpts.lopt_rotateStyle = opts->lopt_rotateStyle;
663         /* OpenLogFile() sets ourName; don't cache filename here. */
664         code = OpenLogFile(opts->lopt_filename);
665         break;
666 #ifdef HAVE_SYSLOG
667     case logDest_syslog:
668         serverLogOpts.lopt_rotateOnOpen = 0;
669         serverLogOpts.lopt_rotateOnReset = 0;
670         serverLogOpts.lopt_rotateStyle = logRotate_none;
671         openlog(opts->lopt_tag, LOG_PID, opts->lopt_facility);
672         code = 0;
673         break;
674 #endif
675     default:
676         opr_Assert(0);
677     }
678     return code;
679 }                               /*OpenLog */
680
681 /*!
682  * Reopen the log file descriptor.
683  *
684  * Reopen the log file descriptor in order to support rotation
685  * of the log files.  Has no effect when logging to the syslog.
686  *
687  * \returns  0 on success
688  */
689 int
690 ReOpenLog(void)
691 {
692     int flags = O_WRONLY | O_APPEND | O_CREAT;
693
694 #ifdef HAVE_SYSLOG
695     if (serverLogOpts.dest == logDest_syslog) {
696         return 0;
697     }
698 #endif
699
700     LOCK_SERVERLOG();
701     if (ourName == NULL) {
702         UNLOCK_SERVERLOG();
703         return -1;
704     }
705     if (IsFIFO(ourName)) {
706         flags |= O_NONBLOCK;
707     }
708     if (serverLogFD >= 0)
709         close(serverLogFD);
710     serverLogFD = open(ourName, flags, 0666);
711     if (serverLogFD >= 0) {
712         RedirectStdStreams(ourName);
713     }
714     UNLOCK_SERVERLOG();
715     return serverLogFD < 0 ? -1 : 0;
716 }
717
718 /*!
719  * Rotate the log file by renaming then truncating.
720  */
721 static void
722 RotateLogFile(void)
723 {
724     LOCK_SERVERLOG();
725     if (ourName != NULL) {
726         if (serverLogFD >= 0) {
727             close(serverLogFD);
728             serverLogFD = -1;
729         }
730         OpenLogFile(ourName);
731     }
732     UNLOCK_SERVERLOG();
733 }
734
735 /*!
736  * Close the server log file.
737  *
738  * \note Must be preceeded by OpenLog().
739  */
740 void
741 CloseLog(void)
742 {
743     LOCK_SERVERLOG();
744
745 #ifdef HAVE_SYSLOG
746     if (serverLogOpts.dest == logDest_syslog) {
747         closelog();
748     } else
749 #endif
750     {
751         if (serverLogFD >= 0) {
752             close(serverLogFD);
753             serverLogFD = -1;
754         }
755         free(ourName);
756         ourName = NULL;
757     }
758     UNLOCK_SERVERLOG();
759 }