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