From 9f4de680e302d6045ca778b2a93dc21adf952296 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Thu, 15 Jul 2021 16:27:51 +0100 Subject: [PATCH] ITS#6949 add support for logfile rotation Uses debuglevel, not sysloglevel. --- doc/man/man5/slapd-config.5 | 15 ++++- doc/man/man5/slapd.conf.5 | 13 ++++ servers/slapd/bconfig.c | 87 ++++++++++++++++++++++----- servers/slapd/main.c | 115 +++++++++++++++++++++++++++++++++--- servers/slapd/proto-slap.h | 10 ++++ 5 files changed, 215 insertions(+), 25 deletions(-) diff --git a/doc/man/man5/slapd-config.5 b/doc/man/man5/slapd-config.5 index 5686c08253..ac87c3c68c 100644 --- a/doc/man/man5/slapd-config.5 +++ b/doc/man/man5/slapd-config.5 @@ -567,10 +567,23 @@ option description. The default is 71. Specify a file for recording slapd debug messages. By default these messages only go to stderr, are not recorded anywhere else, and are unrelated to messages exposed by the -.B loglevel +.B olcLogLevel configuration parameter. Specifying a logfile copies messages to both stderr and the logfile. .TP +.B olcLogFileOnly: TRUE | FALSE +Specify that debug messages should only go to the configured logfile, and +not to stderr. +.TP +.B olcLogFileRotate: +Specify automatic rotation for the configured logfile as the maximum +number of old logfiles to retain, a maximum size in megabytes to allow a +logfile to grow before rotation, and a maximum age in hours for a logfile +to be used before rotation. The maximum number must be in the range 1-99. +Setting Mbytes or hours to zero disables the size or age check, respectively. +At least one of Mbytes or hours must be non-zero. By default no automatic +rotation will be performed. +.TP .B olcLogLevel: [...] Specify the level at which debugging statements and operation statistics should be syslogged (currently logged to the diff --git a/doc/man/man5/slapd.conf.5 b/doc/man/man5/slapd.conf.5 index d948d1bbe8..f0b43796b6 100644 --- a/doc/man/man5/slapd.conf.5 +++ b/doc/man/man5/slapd.conf.5 @@ -625,6 +625,19 @@ messages exposed by the configuration parameter. Specifying a logfile copies messages to both stderr and the logfile. .TP +.B logfile-only on | off +Specify that debug messages should only go to the configured logfile, and +not to stderr. +.TP +.B logfile-rotate +Specify automatic rotation for the configured logfile as the maximum +number of old logfiles to retain, a maximum size in megabytes to allow a +logfile to grow before rotation, and a maximum age in hours for a logfile +to be used before rotation. The maximum number must be in the range 1-99. +Setting Mbytes or hours to zero disables the size or age check, respectively. +At least one of Mbytes or hours must be non-zero. By default no automatic +rotation will be performed. +.TP .B loglevel [...] Specify the level at which debugging statements and operation statistics should be syslogged (currently logged to the diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c index 09d184daaf..364b4de31e 100644 --- a/servers/slapd/bconfig.c +++ b/servers/slapd/bconfig.c @@ -80,8 +80,6 @@ typedef struct { static CfBackInfo cfBackInfo; static char *passwd_salt; -static FILE *logfile; -static char *logfileName; static AccessControl *defacl_parsed = NULL; static struct berval cfdir; @@ -203,6 +201,7 @@ enum { CFG_TLS_CACERT, CFG_TLS_CERT, CFG_TLS_KEY, + CFG_LOGFILE_ROTATE, CFG_LAST }; @@ -486,6 +485,14 @@ static ConfigTable config_back_cf_table[] = { &config_generic, "( OLcfgGlAt:27 NAME 'olcLogFile' " "EQUALITY caseExactMatch " "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "logfile-only", "on|off", 2, 2, 0, ARG_ON_OFF, + &logfile_only, "( OLcfgGlAt:102 NAME 'olcLogFileOnly' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "logfile-rotate", "max> value_string = ch_strdup( logfileName ); else rc = 1; + } + break; + case CFG_LOGFILE_ROTATE: + rc = 1; + if ( logfile_max ) { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "%d %ld %ld", logfile_max, + (long) logfile_fslimit / 1048576, (long) logfile_age / 3600 ); + if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) { + bv.bv_val = buf; + value_add_one( &c->rvalue_vals, &bv ); + rc = 0; + } + } break; case CFG_LASTMOD: c->value_int = (SLAP_NOLASTMOD(c->be) == 0); @@ -1610,12 +1633,11 @@ config_generic(ConfigArgs *c) { break; case CFG_LOGFILE: - ch_free( logfileName ); - logfileName = NULL; - if ( logfile ) { - fclose( logfile ); - logfile = NULL; - } + logfile_close(); + break; + + case CFG_LOGFILE_ROTATE: + logfile_max = logfile_fslimit = logfile_age = 0; break; case CFG_SERVERID: { @@ -2392,11 +2414,44 @@ sortval_reject: } break; case CFG_LOGFILE: { - if ( logfileName ) ch_free( logfileName ); - logfileName = c->value_string; - logfile = fopen(logfileName, "w"); - if(logfile) lutil_debug_file(logfile); - } break; + int rc = logfile_open( c->value_string ); + ch_free( c->value_string ); + return rc; + } + break; + + case CFG_LOGFILE_ROTATE: { + unsigned lf_max, lf_mbyte, lf_hour; + if ( lutil_atoux( &lf_max, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " + "invalid max value \"%s\"", c->argv[0], c->argv[1] ); + return 1; + } + if ( !lf_max || lf_max > 99 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " + "invalid max value \"%s\" must be 1-99", c->argv[0], c->argv[1] ); + return 1; + } + if ( lutil_atoux( &lf_mbyte, c->argv[2], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " + "invalid Mbyte value \"%s\"", c->argv[0], c->argv[1] ); + return 1; + } + if ( lutil_atoux( &lf_hour, c->argv[3], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " + "invalid hours value \"%s\"", c->argv[0], c->argv[2] ); + return 1; + } + if ( !lf_mbyte && !lf_hour ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> " + "Mbyte and hours cannot both be zero", c->argv[0] ); + return 1; + } + logfile_max = lf_max; + logfile_fslimit = lf_mbyte * 1048576; /* Megabytes to bytes */ + logfile_age = lf_hour * 3600; /* hours to seconds */ + } + break; case CFG_LASTMOD: if(SLAP_NOLASTMODCMD(c->be)) { diff --git a/servers/slapd/main.c b/servers/slapd/main.c index d92f978dd0..6ca1b00a79 100644 --- a/servers/slapd/main.c +++ b/servers/slapd/main.c @@ -35,6 +35,10 @@ #include #include +#include +#include +#include + #include "slap.h" #include "lutil.h" #include "ldif.h" @@ -377,27 +381,121 @@ usage( char *name ) ); } +static char logfile_suffix[sizeof(".xx.gz")]; +char logfile_path[MAXPATHLEN - sizeof(logfile_suffix) -1]; +long logfile_fslimit; +int logfile_age, logfile_only, logfile_max; + +static ldap_pvt_thread_mutex_t logfile_mutex; +static off_t logfile_fsize; +static time_t logfile_fcreated; +static int logfile_fd; +static char logpaths[2][MAXPATHLEN]; +static int logpathlen; + typedef void (BER_logger)(const char *buf); static BER_logger *ber_logger; static void debug_print( const char *data ) { - char buf[4136]; /* 4096 + 40 */ + char prefix[sizeof("ssssssssssssssss.ffffffff 0xtttttttttttttttt ")]; + struct iovec iov[2]; + int rotate = 0; #ifdef HAVE_CLOCK_GETTIME struct timespec tv; #define TS "%08x" #define Tfrac tv.tv_nsec - clock_gettime( CLOCK_REALTIME, &tv ); +#define gettime(tv) clock_gettime( CLOCK_REALTIME, tv ) #else struct timeval tv; #define TS "%05x" #define Tfrac tv.tv_usec - gettimeofday( &tv, NULL ); +#define gettime(tv) gettimeofday( tv, NULL ) #endif - buf[sizeof(buf)-1] = '\0'; - snprintf( buf, sizeof(buf)-1, "%lx." TS " %p %s", - (long)tv.tv_sec, Tfrac, (void *)ldap_pvt_thread_self(), data ); - ber_logger( buf ); + gettime( &tv ); + iov[0].iov_base = prefix; + iov[0].iov_len = sprintf( prefix, "%lx." TS " %p ", + (long)tv.tv_sec, (unsigned int)Tfrac, (void *)ldap_pvt_thread_self() ); + iov[1].iov_base = (void *)data; + iov[1].iov_len = strlen( data ); + if ( !logfile_only ) + writev( 2, iov, 2 ); + if ( logfile_fd ) { + int len = iov[0].iov_len + iov[1].iov_len; + if ( logfile_fslimit || logfile_age ) { + ldap_pvt_thread_mutex_lock( &logfile_mutex ); + if ( logfile_fslimit && logfile_fsize + len > logfile_fslimit ) + rotate = 1; + if ( logfile_age && tv.tv_sec - logfile_fcreated >= logfile_age ) + rotate |= 2; + if ( rotate ) { + close( logfile_fd ); + strcpy( logpaths[0]+logpathlen, ".tmp" ); + rename( logfile_path, logpaths[0] ); + logfile_open( logfile_path ); + } + } + len = writev( logfile_fd, iov, 2 ); + if ( len > 0 ) + logfile_fsize += len; + if ( logfile_fslimit || logfile_age ) + ldap_pvt_thread_mutex_unlock( &logfile_mutex ); + } + if ( rotate ) { + int i; + for (i=logfile_max; i > 1; i--) { + sprintf( logpaths[0]+logpathlen, ".%02d", i ); + sprintf( logpaths[1]+logpathlen, ".%02d", i-1 ); + rename( logpaths[1], logpaths[0] ); + } + sprintf( logpaths[0]+logpathlen, ".tmp" ); + rename( logpaths[0], logpaths[1] ); + } +} + +void logfile_close() +{ + if ( logfile_fd ) { + close( logfile_fd ); + logfile_fd = 0; + } + logfile_path[0] = '\0'; +} + +int logfile_open( const char *path ) +{ + struct stat st; + int fd; + + fd = open( path, O_CREAT|O_WRONLY, 0640 ); + if ( fd < 0 ) + return errno; + + if ( fstat( fd, &st )) { + close( fd ); + return errno; + } + + if ( !logfile_path[0] ) { + logpathlen = strlen( path ); + if ( logpathlen >= sizeof(logfile_path) ) + return ENAMETOOLONG; + strcpy( logfile_path, path ); + strcpy( logpaths[0], path ); + strcpy( logpaths[1], path ); + } + + logfile_fsize = st.st_size; + logfile_fcreated = st.st_ctime; /* not strictly true but close enough */ + logfile_fd = fd; + lseek( fd, 0, SEEK_END ); + + return 0; +} + +const char *logfile_name() +{ + return logfile_path[0] ? logfile_path : NULL; } #ifdef HAVE_NT_SERVICE_MANAGER @@ -449,8 +547,8 @@ int main( int argc, char **argv ) slap_sl_mem_init(); - (void) ldap_pvt_thread_initialize(); + ldap_pvt_thread_mutex_init( &logfile_mutex ); serverName = lutil_progname( "slapd", argc, argv ); @@ -1164,6 +1262,7 @@ stop: mal_dumpleaktrace( leakfile ); #endif + ldap_pvt_thread_mutex_destroy( &logfile_mutex ); MAIN_RETURN(rc); } diff --git a/servers/slapd/proto-slap.h b/servers/slapd/proto-slap.h index d2ff1adae9..c4b6ccdb07 100644 --- a/servers/slapd/proto-slap.h +++ b/servers/slapd/proto-slap.h @@ -1254,6 +1254,16 @@ LDAP_SLAPD_F (int) parse_debug_unknowns LDAP_P(( char **unknowns, int *levelp )); LDAP_SLAPD_F (void) slap_check_unknown_level LDAP_P(( char *levelstr, int level )); +LDAP_SLAPD_F (int) +logfile_open LDAP_P(( const char *path )); +LDAP_SLAPD_F (void) +logfile_close LDAP_P(( void )); +LDAP_SLAPD_F (const char *) +logfile_name LDAP_P(( void )); +LDAP_SLAPD_V(int) logfile_age; +LDAP_SLAPD_V(int) logfile_only; +LDAP_SLAPD_V(int) logfile_max; +LDAP_SLAPD_V(long) logfile_fslimit; /* * matchedValues.c