diff options
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/conf.c | 730 | ||||
-rw-r--r-- | daemon/fifo.c | 105 | ||||
-rw-r--r-- | daemon/listener.c | 335 | ||||
-rw-r--r-- | daemon/logrotate.c | 264 | ||||
-rw-r--r-- | daemon/main.c | 660 | ||||
-rw-r--r-- | daemon/names.c | 63 | ||||
-rw-r--r-- | daemon/pathnames.c | 139 | ||||
-rw-r--r-- | daemon/purger.c | 155 | ||||
-rw-r--r-- | daemon/syslogd.c | 600 | ||||
-rw-r--r-- | daemon/syslogd.h | 204 | ||||
-rw-r--r-- | daemon/writer.c | 498 |
11 files changed, 3753 insertions, 0 deletions
diff --git a/daemon/conf.c b/daemon/conf.c new file mode 100644 index 0000000..654e46b --- /dev/null +++ b/daemon/conf.c @@ -0,0 +1,730 @@ +/* + * conf.c - syslogd implementation for windows, configuration reader + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* define SYSLOG_CONF_DIR where syslog.host should be + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <winsock2.h> + +#include <glib.h> + +#include <syslog.h> +#include <syslogd.h> + +#ifndef SYSLOG_CONF_DIR +static char syslog_conf_dir[] = "."; +#else +static char syslog_conf_dir[] = SYSLOG_CONF_DIR; +#endif + +/* options and their default values */ +gboolean use_dns = TRUE; +gchar *source_encoding = NULL; +gchar *destination_encoding = NULL; +int mark_interval = 0; +gchar *mark_message = "-- MARK --"; +int hold = 3; +gchar *logdir = NULL; + +/* sources, destinations, filters and logpaths */ +struct logpath_names +{ + gchar *source; + gchar *filter; + gchar *destination; +}; + +GList *sources = NULL; +GList *destinations = NULL; +GList *filters = NULL; +GList *logpaths = NULL; + +GList *purger_dirs = NULL; + +/* Glib markup wrapper data */ +static gchar *encoding = NULL; +static gboolean prolog_expected = TRUE; +/* parser data */ +static struct filter *current_filter = NULL; + +/****************************************************************************** + * xml_start_element + * + * parse configuration elements + */ +static void xml_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + const gchar *aname, *aval; + int line_number; + + prolog_expected = FALSE; + + g_markup_parse_context_get_position( context, &line_number, NULL ); + + /* top-level elements */ + if( strcmp( element_name, "source" ) == 0 ) + { + struct source *source = g_malloc( sizeof(struct source) ); + + source->name = NULL; + source->type = ST_UNDEFINED; + memset( &source->udp, 0, sizeof(source->udp) ); + source->udp.sin_family = AF_INET; + source->udp.sin_port = htons( SYSLOG_PORT ); + + for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ ) + { + aval = *attribute_values; + if( strcmp( aname, "name" ) == 0 ) + source->name = g_strdup( aval ); + else if( strcmp( aname, "type" ) == 0 ) + { + if( strcmp( aval, "internal" ) == 0 ) + source->type = ST_INTERNAL; + else if( strcmp( aval, "udp" ) == 0 ) + source->type = ST_UDP; + } + else if( strcmp( aname, "interface" ) == 0 ) + { + struct hostent *he = gethostbyname( aval ); + if( !he ) + { + ERR( "Cannot resolve hostname %s; error %lu\n", aval, WSAGetLastError() ); + g_free( source ); + return; + } + memcpy( &source->udp.sin_addr.s_addr, he->h_addr, he->h_length ); + } + else if( strcmp( aname, "port" ) == 0 ) + source->udp.sin_port = htons( strtoul( aval, NULL, 0 ) ); + } + + if( !source->name ) + ERR( "Undefined source name at line %d\n", line_number ); + if( ST_UNDEFINED == source->type ) + ERR( "Undefined source type at line %d\n", line_number ); + if( (!source->name) || ST_UNDEFINED == source->type ) + { + g_free( source ); + return; + } + sources = g_list_append( sources, source ); + } + else if( strcmp( element_name, "destination" ) == 0 ) + { + struct destination *dest = g_malloc0( sizeof(struct destination) ); + + for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ ) + { + aval = *attribute_values; + if( strcmp( aname, "name" ) == 0 ) + dest->name = g_strdup( aval ); + else if( strcmp( aname, "file" ) == 0 ) + dest->file = normalize_pathname( aval ); + else if( strcmp( aname, "rotate" ) == 0 ) + { + if( strcmp( aval, "daily" ) == 0 ) + dest->rotate = RP_DAILY; + else if( strcmp( aval, "weekly" ) == 0 ) + dest->rotate = RP_WEEKLY; + else if( strcmp( aval, "monthly" ) == 0 ) + dest->rotate = RP_MONTHLY; + else + { + ERR( "Invalid rotation period at line %d\n", line_number ); + dest->rotate = RP_INVALID; + } + } + else if( strcmp( aname, "size" ) == 0 ) + { + char *endptr; + dest->size = strtoul( aval, &endptr, 0 ); + if( 'k' == *endptr ) + dest->size *= 1024; + else if( 'M' == *endptr ) + dest->size *= 1024 * 1024; + } + else if( strcmp( aname, "backlogs" ) == 0 ) + dest->backlogs = strtoul( aval, NULL, 0 ); + else if( strcmp( aname, "ifempty" ) == 0 ) + { + if( strcmp( aval, "yes" ) == 0 ) + dest->ifempty = TRUE; + else if( strcmp( aval, "no" ) == 0 ) + dest->ifempty = FALSE; + else + { + dest->ifempty = TRUE; + ERR( "Invalid value \"%s\" of attribute \"%s\" at line %d; assumed \"yes\"\n", + aval, aname, line_number ); + } + } + else if( strcmp( aname, "olddir" ) == 0 ) + dest->olddir = normalize_pathname( aval ); + else if( strcmp( aname, "compresscmd" ) == 0 ) + dest->compresscmd = g_strdup( aval ); + else if( strcmp( aname, "compressoptions" ) == 0 ) + dest->compressoptions = g_strdup( aval ); + } + if( !dest->name ) + ERR( "Undefined destination name at line %d\n", line_number ); + if( !dest->file ) + ERR( "Undefined destination file at line %d\n", line_number ); + if( (!dest->name) || (!dest->file) || RP_INVALID == dest->rotate ) + { + if( dest->name ) g_free( dest->name ); + if( dest->file ) g_free( dest->file ); + if( dest->olddir ) g_free( dest->olddir ); + if( dest->compresscmd ) g_free( dest->compresscmd ); + if( dest->compressoptions ) g_free( dest->compressoptions ); + g_free( dest ); + return; + } + if( dest->compresscmd && !dest->compressoptions ) + dest->compressoptions = g_strdup( "$PATHNAME" ); + + dest->file_writers = NULL; + InitializeCriticalSection( &dest->cs_file_writers ); + + destinations = g_list_append( destinations, dest ); + } + else if( strcmp( element_name, "filter" ) == 0 ) + { + current_filter = g_malloc0( sizeof(struct filter) ); + + for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ ) + { + if( strcmp( aname, "name" ) == 0 ) + current_filter->name = g_strdup( *attribute_values ); + } + if( !current_filter->name ) + { + ERR( "Undefined filter name at line %d\n", line_number ); + g_free( current_filter ); + current_filter = NULL; + return; + } + } + else if( strcmp( element_name, "logpath" ) == 0 ) + { + /* at first, fill logpaths list with logpath_names structures + and replace them later with logpath structures after configuration has been read + */ + struct logpath_names *logpath = g_malloc0( sizeof(struct logpath_names) ); + + for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ ) + { + aval = *attribute_values; + if( strcmp( aname, "source" ) == 0 ) + logpath->source = g_strdup( aval ); + else if( strcmp( aname, "filter" ) == 0 ) + logpath->filter = g_strdup( aval ); + else if( strcmp( aname, "destination" ) == 0 ) + logpath->destination = g_strdup( aval ); + } + if( !logpath->source ) + ERR( "Undefined log path source at line %d\n", line_number ); + if( !logpath->destination ) + ERR( "Undefined log path destination at line %d\n", line_number ); + if( (!logpath->source) || (!logpath->destination) ) + { + if( logpath->source ) g_free( logpath->source ); + if( logpath->filter ) g_free( logpath->filter ); + if( logpath->destination ) g_free( logpath->destination ); + g_free( logpath ); + return; + } + logpaths = g_list_append( logpaths, logpath ); + } + else if( strcmp( element_name, "options" ) == 0 ) + { + for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ ) + { + aval = *attribute_values; + if( strcmp( aname, "dns" ) == 0 ) + { + if( strcmp( aval, "yes" ) == 0 ) + use_dns = TRUE; + else if( strcmp( aval, "no" ) == 0 ) + use_dns = FALSE; + else + ERR( "Invalid value \"%s\" of attribute \"%s\" at line %d\n", aval, aname, line_number ); + } + else if( strcmp( aname, "source_encoding" ) == 0 ) + source_encoding = g_strdup( aval ); + else if( strcmp( aname, "destination_encoding" ) == 0 ) + destination_encoding = g_strdup( aval ); + else if( strcmp( aname, "mark_interval" ) == 0 ) + mark_interval = strtoul( aval, NULL, 0 ); + else if( strcmp( aname, "mark_message" ) == 0 ) + mark_message = g_strdup( aval ); + else if( strcmp( aname, "hold" ) == 0 ) + { + hold = strtoul( aval, NULL, 0 ); + if( hold < 1 ) + hold = 1; + } + else if( strcmp( aname, "logdir" ) == 0 ) + logdir = normalize_pathname( aval ); + } + } + else if( strcmp( element_name, "purge" ) == 0 ) + { + struct purger_dir *pdir = g_malloc0( sizeof(struct purger_dir) ); + + for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ ) + { + aval = *attribute_values; + if( strcmp( aname, "directory" ) == 0 ) + pdir->directory = normalize_pathname( aval ); + else if( strcmp( aname, "keep_days" ) == 0 ) + pdir->keep_days = strtoul( aval, NULL, 0 ); + } + if( !pdir->directory ) + ERR( "Undefined purge directory at line %d\n", line_number ); + if( !pdir->keep_days ) + ERR( "Undefined keep_days parameter at line %d\n", line_number ); + if( (!pdir->directory) || (!pdir->keep_days) ) + { + if( pdir->directory ) g_free( pdir->directory ); + g_free( pdir ); + return; + } + purger_dirs = g_list_append( purger_dirs, pdir ); + } + else if( current_filter ) + { + /* sub-elements of filter */ + int val = -2; + + if( strcmp( element_name, "facility" ) == 0 ) + { + for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ ) + { + aval = *attribute_values; + if( strcmp( aname, "value" ) == 0 ) + { + val = strtol( aval, NULL, 0 ); + if( val < 0 || val >= LOG_NFACILITIES ) + { + val = -1; + break; + } + } + else if( strcmp( aname, "name" ) == 0 ) + { + CODE *c; + for( c = facilitynames; c->c_name; c++ ) + if( strcmp( aval, c->c_name ) == 0 ) + { + val = LOG_FAC( c->c_val ); + break; + } + if( !c->c_name ) + { + val = -1; + break; + } + } + } + if( -2 == val ) + ERR( "Undefined facility at line %d\n", line_number ); + else if( val != -1 ) + current_filter->facilities[ val ] = TRUE; + } + else if( strcmp( element_name, "priority" ) == 0 ) + { + for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ ) + { + aval = *attribute_values; + if( strcmp( aname, "value" ) == 0 ) + { + val = strtol( aval, NULL, 0 ); + if( val < 0 || val >= 8 ) + { + val = -1; + break; + } + } + else if( strcmp( aname, "name" ) == 0 ) + { + CODE *c; + for( c = prioritynames; c->c_name; c++ ) + if( strcmp( aval, c->c_name ) == 0 ) + { + val = LOG_PRI( c->c_val ); + break; + } + if( !c->c_name ) + { + val = -1; + break; + } + } + } + if( -2 == val ) + ERR( "Undefined priority at line %d\n", line_number ); + else if( val != -1 ) + current_filter->priorities[ val ] = TRUE; + } + + if( -1 == val ) + ERR( "Invalid value \"%s\" of attribute \"%s\" at line %d\n", aval, aname, line_number ); + } +} + +/****************************************************************************** + * xml_end_element + */ +static void xml_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + if( strcmp( element_name, "filter" ) == 0 ) + { + if( current_filter ) + { + /* append filter to the list */ + filters = g_list_append( filters, current_filter ); + current_filter = NULL; + } + } +} + +/****************************************************************************** + * xml_passthrough + * + * look for encoding name + */ +static void xml_passthrough (GMarkupParseContext *context, + const gchar *passthrough_text, + gsize text_len, + gpointer user_data, + GError **error) +{ + const gchar *startptr, *endptr; + + if( !prolog_expected ) + return; + + startptr = g_strstr_len( passthrough_text, text_len, "<?xml " ); + if( !startptr ) + return; + + startptr += 6; + text_len -= startptr - passthrough_text; + + endptr = g_strstr_len( startptr, text_len, "?>" ); + if( !endptr ) + goto parsed; + + text_len = endptr - startptr; + startptr = g_strstr_len( startptr, text_len, "encoding=\"" ); + if( !startptr ) + goto parsed; + + startptr += 10; + + endptr = strchr( startptr, '"' ); + if( !endptr ) + goto parsed; + + if( strncmp( startptr, "windows-", 8 ) == 0 ) + { + gchar *p; + + startptr += 8; + p = g_strndup( startptr, endptr - startptr ); + if( !p ) + goto parsed; + encoding = g_strdup_printf( "CP%s", p ); + g_free( p ); + } + else + encoding = g_strndup( startptr, endptr - startptr ); + +parsed: + prolog_expected = FALSE; +} + +/****************************************************************************** + * resolve_logpaths + * + * replace logpath_names structures + */ +static void resolve_logpaths() +{ + GList *paths = NULL; + GList *path_item; + struct logpath *logpath = NULL; + + for( path_item = logpaths; path_item; path_item = path_item->next ) + { + struct logpath_names *names = path_item->data; + GList *item; + + g_free( logpath ); + logpath = g_malloc( sizeof(struct logpath) ); + + /* find source */ + for( item = sources; item; item = item->next ) + { + struct source *s = item->data; + if( strcmp( s->name, names->source ) == 0 ) + break; + } + if( !item ) + { + ERR( "Undefined source \"%s\" in log path\n", names->source ); + continue; + } + logpath->source = item->data; + + /* find destination */ + for( item = destinations; item; item = item->next ) + { + struct destination *d = item->data; + if( strcmp( d->name, names->destination ) == 0 ) + break; + } + if( !item ) + { + ERR( "Undefined destination \"%s\" in log path\n", names->destination ); + continue; + } + logpath->destination = item->data; + + /* find filter */ + if( !names->filter ) + logpath->filter = NULL; + else + { + for( item = filters; item; item = item->next ) + { + struct filter *f = item->data; + if( strcmp( f->name, names->filter ) == 0 ) + break; + } + if( item ) + logpath->filter = item->data; + else + logpath->filter = NULL; + } + /* add item to paths */ + paths = g_list_append( paths, logpath ); + logpath = NULL; + } + + /* free list */ + for( path_item = logpaths; path_item; path_item = path_item->next ) + { + struct logpath_names *names = path_item->data; + g_free( names->source ); + g_free( names->destination ); + if( names->filter ) g_free( names->filter ); + g_free( names ); + } + g_list_free( logpaths ); + + /* set new list */ + logpaths = paths; +} + +/****************************************************************************** + * dump_configuration + */ +static void dump_configuration() +{ +# ifdef HAVE_DEBUG + + GList *item; + + TRACE( "Sources:\n" ); + for( item = sources; item; item = item->next ) + { + struct source *s = item->data; + TRACE( "\tname=%s\ttype=%s\tinterface=%d:%d:%d:%d\tport=%d\n", + s->name, + (s->type == ST_INTERNAL)? "internal" : ((s->type == ST_UDP)? "udp" : "undefined"), + s->udp.sin_addr.S_un.S_un_b.s_b1, s->udp.sin_addr.S_un.S_un_b.s_b2, + s->udp.sin_addr.S_un.S_un_b.s_b3, s->udp.sin_addr.S_un.S_un_b.s_b4, + ntohs( s->udp.sin_port ) ); + } + TRACE( "Destinations:\n" ); + for( item = destinations; item; item = item->next ) + { + struct destination *d = item->data; + TRACE( "\tname=%s\tfile=%s\n" + "\t\trotate=%s size=%d backlogs=%d ifempty=%s\n" + "\t\tolddir=%s compresscmd=%s\n", + d->name, d->file, + (d->rotate == RP_DAILY)? "daily" + : (d->rotate == RP_WEEKLY)? "weekly" + : (d->rotate == RP_MONTHLY)? "monthly" + : "undefined", + d->size, d->backlogs, d->ifempty? "yes" : "no", + d->olddir? d->olddir : "NULL", + d->compresscmd? d->compresscmd : "NULL" ); + } + TRACE( "Filters:\n" ); + for( item = filters; item; item = item->next ) + { + struct filter *f = item->data; + int i; + TRACE( "\tname=%s\n", f->name ); + TRACE( "\tfacilities:\n" ); + for( i = 0; i < LOG_NFACILITIES; i++ ) + if( f->facilities[i] ) + TRACE( "\t\t%s\n", get_facility_name( i ) ); + TRACE( "\tpriorities:\n" ); + for( i = 0; i < 8; i++ ) + if( f->priorities[i] ) + TRACE( "\t\t%s\n", get_priority_name( i ) ); + } + TRACE( "Log paths:\n" ); + for( item = logpaths; item; item = item->next ) + { + struct logpath *p = item->data; + TRACE( "\tsource=%s\tfilter=%s\tdestination=%s\n", + p->source->name, p->filter? p->filter->name : "NULL", p->destination->name ); + } + TRACE( "Purge directories:\n" ); + for( item = purger_dirs; item; item = item->next ) + { + struct purger_dir *p = item->data; + TRACE( "\tdirectory=%s\tkeep_days=%d\n", p->directory, p->keep_days ); + } + TRACE( "Options:\n" ); + TRACE( "\tuse_dns=%d\n", (int) use_dns ); + TRACE( "\tsource_encoding=%s\n", source_encoding? source_encoding : "NULL" ); + TRACE( "\tdestination_encoding=%s\n", destination_encoding? destination_encoding : "NULL" ); + TRACE( "\tmark_interval=%d\n", mark_interval ); + TRACE( "\tmark_message=%s\n", mark_message ); + TRACE( "\thold=%d\n", hold ); + TRACE( "\tlogdir=%s\n", logdir? logdir : "NULL" ); + +# endif /* HAVE_DEBUG */ +} + +/****************************************************************************** + * read_configuration + */ +gboolean read_configuration() +{ + gboolean ret = FALSE; + GMarkupParser parser = { + xml_start_element, + xml_end_element, + NULL, + xml_passthrough, + NULL + }; + gchar *pathname; + FILE *fd = NULL; + GMarkupParseContext *ctx = NULL; + char buffer[256]; + GError *error = NULL; + + TRACE_ENTER( "\n" ); + + if( '\\' == syslog_conf_dir[0] || '/' == syslog_conf_dir[0] || ':' == syslog_conf_dir[1] ) + /* absolute path */ + pathname = g_build_filename( syslog_conf_dir, "syslog.conf", NULL ); + else + /* relative path */ + pathname = g_build_filename( g_path_get_dirname( __argv[0] ), + syslog_conf_dir, "syslog.conf", NULL ); + fd = fopen( pathname, "r" ); + if( !fd ) + { + ERR( "Cannot open configuration file %s: %s\n", pathname, strerror(errno) ); + goto done; + } + + ctx = g_markup_parse_context_new( &parser, 0, NULL, NULL ); + if( !ctx ) + { + ERR( "Failed g_markup_parse_context_new\n" ); + goto done; + } + + while( fgets( buffer, sizeof(buffer), fd ) ) + { + gchar *encoded = NULL; + gchar *parser_input; + gboolean r; + + /* wrapper for Glib's XML parser: + determine encoding in xml_passthrough and convert data fed to the parser to UTF-8 */ + if( encoding ) + { + encoded = g_convert( buffer, -1, "UTF-8", encoding, NULL, NULL, &error ); + if( !encoded ) + goto done; + + parser_input = encoded; + } + else + parser_input = buffer; + + r = g_markup_parse_context_parse( ctx, parser_input, strlen( parser_input ), &error ); + if( encoded ) g_free( encoded ); + if( !r ) + goto done; + } + if( !feof( fd ) ) + { + ERR( "Cannot read configuration file %s: %s\n", pathname, strerror(errno) ); + goto done; + } + + resolve_logpaths(); + dump_configuration(); + if( !logdir ) + { + ERR( "logdir is not defined\n" ); + goto done; + } + ret = TRUE; + +done: + if( error ) + { + gchar *locale_msg = g_locale_from_utf8( error->message, -1, NULL, NULL, NULL ); + if( locale_msg ) + { + ERR( "%s\n", locale_msg ); + g_free( locale_msg ); + } + g_error_free( error ); + } + if( ctx ) g_markup_parse_context_free( ctx ); + if( fd ) fclose( fd ); + g_free( pathname ); + + TRACE_LEAVE( "done; ret=%d\n", (int) ret ); + return ret; +} diff --git a/daemon/fifo.c b/daemon/fifo.c new file mode 100644 index 0000000..2754885 --- /dev/null +++ b/daemon/fifo.c @@ -0,0 +1,105 @@ +/* + * fifo.c - syslogd implementation for windows, simple and fast queue + * + * Created by Alexander Yaworsky + * + * This replacement eliminates strange page faults which were + * with glib's 2.6.3 queues. + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <stdio.h> +#include <windows.h> +#include <glib.h> +#include <syslog.h> +#include <syslogd.h> + +/****************************************************************************** + * fifo_create + * + * Allocate and initialize fifo structure. Add an empty item to the fifo. + */ +struct fifo* fifo_create() +{ + struct fifo *ret = g_malloc( sizeof(struct fifo) ); + struct fifo_item *guard_item= g_malloc( sizeof(struct fifo_item) ); + ret->first = guard_item; + ret->last = guard_item; + guard_item->next = NULL; + guard_item->payload = NULL; + return ret; +} + +/****************************************************************************** + * fifo_destroy + * + * Delete all items and free fifo structure. + */ +void fifo_destroy( struct fifo* queue ) +{ + struct fifo_item *item; + + while( (item = queue->first) != NULL ) + { + queue->first = item->next; + g_free( item ); + } + g_free( queue ); +} + +/****************************************************************************** + * fifo_push + * + * Add item to queue. + */ +void fifo_push( struct fifo* queue, void* data ) +{ + struct fifo_item *item = g_malloc( sizeof(struct fifo_item) ); + item->next = NULL; + item->payload = data; + queue->last->next = item; + queue->last = item; +} + +/****************************************************************************** + * fifo_pop + * + * Extract item from queue. + * Consider four possible conditions: + * 1) next == NULL, payload == NULL: there is the only one empty item + * in the queue; leave it and return NULL + * 2) next == NULL, payload != NULL: leave item and return its payload; + * set item's payload NULL + * 3) next != NULL, payload == NULL: queue was empty when a new item was + * added; free this item and go to the next one + * 4) next != NULL, payload != NULL: free item and return its payload + */ +void* fifo_pop( struct fifo* queue ) +{ + for(;;) + { + struct fifo_item *item = queue->first; + struct fifo_item *next = InterlockedExchangePointer( &item->next, NULL ); + void *data = item->payload; + item->payload = NULL; + if( next ) + { + queue->first = next; + g_free( item ); + } + if( data ) + return data; + if( !next ) + return NULL; + } +} diff --git a/daemon/listener.c b/daemon/listener.c new file mode 100644 index 0000000..32c6297 --- /dev/null +++ b/daemon/listener.c @@ -0,0 +1,335 @@ +/* + * listener.c - syslogd implementation for windows, listener for UDP + * and "internal" sources + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <stdio.h> +#include <winsock2.h> + +#include <glib.h> + +#include <syslog.h> +#include <syslogd.h> + +static SOCKET *socket_array = NULL; +static int socket_count = 0; + +static struct source **source_references = NULL; + +static HANDLE *event_array = NULL; +static int event_count = 0; + +/* message data */ +static unsigned max_datagram_size = 1024; +static gchar *datagram_buffer = NULL; +static struct raw_message message; + +static CRITICAL_SECTION cs_internal_message; +static HANDLE internal_message_accepted = NULL; +static gchar internal_message_buffer[ 1024 ]; + +/****************************************************************************** + * init_listener + * + * create sockets and synchronization objects including ones for "internal" + * source + */ +gboolean init_listener() +{ + gboolean ret = FALSE; + unsigned n; + GList *item; + int i; + struct source *internal_src; + + TRACE_ENTER( "\n" ); + + /* create critical section and event for the access serialization to internal message buffer + */ + InitializeCriticalSection( &cs_internal_message ); + internal_message_accepted = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( !internal_message_accepted ) + { + ERR( "Cannot create event; error %lu\n", GetLastError() ); + goto done; + } + + /* allocate memory for sockets and events; + * the number of sockets is not greater than number of sources + */ + n = g_list_length( sources ); + socket_array = g_malloc( n * sizeof(SOCKET) ); + + /* number of source references is greater by one because of inclusion the event + * for "internal" source + * FIXME: how about multiple internal sources? + */ + source_references = g_malloc( (n + 1) * sizeof(struct source*) ); + + /* number of events is greater by two because of inclusion the event + * for "internal" source and the service_stop_event + */ + event_array = g_malloc( (n + 2) * sizeof(HANDLE) ); + + /* create sockets */ + for( item = sources; item; item = item->next ) + { + struct source *src = item->data; + SOCKET sock; + unsigned dgram_size; + int size; + + if( src->type == ST_INTERNAL ) + { + internal_src = src; + continue; + } + + if( src->type != ST_UDP ) + continue; + + sock = socket( AF_INET, SOCK_DGRAM, 0 ); + if( INVALID_SOCKET == sock ) + { + ERR( "socket() error %lu\n", WSAGetLastError() ); + goto done; + } + + if( bind( sock, (struct sockaddr*) &src->udp, sizeof(src->udp) ) ) + { + ERR( "bind() error %lu\n", WSAGetLastError() ); + closesocket( sock ); + goto done; + } + + size = sizeof(dgram_size); + if( getsockopt( sock, SOL_SOCKET, SO_MAX_MSG_SIZE, (char*) &dgram_size, &size ) ) + { + ERR( "getsockopt( SO_MAX_MSG_SIZE ) error %lu\n", WSAGetLastError() ); + closesocket( sock ); + goto done; + } + TRACE( "datagram size for %d.%d.%d.%d:%d is %u\n", + src->udp.sin_addr.S_un.S_un_b.s_b1, src->udp.sin_addr.S_un.S_un_b.s_b2, + src->udp.sin_addr.S_un.S_un_b.s_b3, src->udp.sin_addr.S_un.S_un_b.s_b4, + ntohs( src->udp.sin_port ), dgram_size ); + if( dgram_size > max_datagram_size ) + max_datagram_size = dgram_size; + + source_references[ socket_count ] = src; + socket_array[ socket_count++ ] = sock; + } + source_references[ socket_count ] = internal_src; + + /* create events; + * service_stop_event is added to the array + */ + while( event_count <= socket_count ) + { + HANDLE evt = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( !evt ) + { + ERR( "Cannot create event; error %lu\n", GetLastError() ); + goto done; + } + event_array[ event_count++ ] = evt; + } + event_array[ event_count++ ] = service_stop_event; + + /* bind events to sockets */ + for( i = 0; i < socket_count; i++ ) + { + if( WSAEventSelect( socket_array[ i ], event_array[ i ], FD_READ ) ) + { + ERR( "WSAEventSelect() error %lu\n", WSAGetLastError() ); + goto done; + } + } + + /* allocate datagram buffer */ + datagram_buffer = g_malloc( max_datagram_size ); + + ret = TRUE; + +done: + if( !ret ) + fini_listener(); + + TRACE_LEAVE( "done; socket_count=%d, event_count=%d, max_datagram_size=%d, ret=%d\n", + socket_count, event_count, max_datagram_size, (int) ret ); + return ret; +} + +/****************************************************************************** + * fini_listener + */ +void fini_listener() +{ + int i; + + TRACE_ENTER( "\n" ); + + for( i = 0; i < socket_count; i++ ) + closesocket( socket_array[ i ] ); + g_free( socket_array ); + socket_array = NULL; + socket_count = 0; + + g_free( source_references ); + source_references = NULL; + + /* note that the last event is the service_stop_event + * and should not be destroyed + */ + for( i = 0; i < event_count - 1; i++ ) + CloseHandle( event_array[ i ] ); + g_free( event_array ); + event_array = NULL; + event_count = 0; + + g_free( datagram_buffer ); + datagram_buffer = NULL; + + if( internal_message_accepted ) + { + CloseHandle( internal_message_accepted ); + internal_message_accepted = NULL; + } + DeleteCriticalSection( &cs_internal_message ); + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * listener + * + * wait for a message; generate mark message; + * allocates a new raw_message structure and assigns its pointer to *msg + */ +enum listener_status listener( struct raw_message** msg ) +{ + enum listener_status ret = LSNR_ERROR; + DWORD t, w; + int r; + int addrlen; + + TRACE_ENTER( "\n" ); + + for(;;) + { + if( !mark_interval ) + t = INFINITE; + else + t = mark_interval * 1000; + w = WaitForMultipleObjects( event_count, event_array, FALSE, t ); + if( WAIT_TIMEOUT == w ) + { + /* issue mark message */ + log_internal( LOG_NOTICE, "%s", mark_message ); + continue; + } + if( WAIT_FAILED == w ) + { + ERR( "Wait error %lu\n", GetLastError() ); + goto done; + } + if( w >= event_count ) + { + ERR( "Unknown wait error\n" ); + goto done; + } + if( w == event_count - 1 ) + { + /* shut down */ + ret = LSNR_SHUTDOWN; + goto done; + } + if( w == event_count - 2 ) + { + /* got "internal" message */ + message.source = source_references[ socket_count ]; + if( !message.source ) + { + /* internal source is not defined, cannot handle message */ + SetEvent( internal_message_accepted ); + continue; + } + message.msg = g_strdup( internal_message_buffer ); + SetEvent( internal_message_accepted ); + memset( &message.sender_addr, 0, sizeof(message.sender_addr) ); + goto alloc_msg; + } + /* got UDP message, read it */ + addrlen = sizeof(message.sender_addr); + r = recvfrom( socket_array[ w ], datagram_buffer, max_datagram_size, + 0, (struct sockaddr*) &message.sender_addr, &addrlen ); + if( r < 0 ) + { + ERR( "recvfrom() error %lu\n", WSAGetLastError() ); + goto done; + } + if( !r ) + continue; + + message.msg = g_strndup( datagram_buffer, r ); + message.source = source_references[ w ]; + + alloc_msg: + *msg = g_malloc( sizeof(struct raw_message) ); + memcpy( *msg, &message, sizeof(struct raw_message) ); + + ret = LSNR_GOT_MESSAGE; + goto done; + } + +done: + TRACE_LEAVE( "done; ret=%d\n", (int) ret ); + return ret; +} + +/****************************************************************************** + * log_internal + * + * generate internal log message + */ +void log_internal( int pri, char* fmt, ... ) +{ + va_list args; + SYSTEMTIME stm; + int len; + char *p; + + TRACE_ENTER( "\n" ); + EnterCriticalSection( &cs_internal_message ); + + GetLocalTime( &stm ); + len = sprintf( internal_message_buffer, "<%d>%s %2d %02d:%02d:%02d %s syslog: ", + LOG_SYSLOG | pri, + str_month[ stm.wMonth - 1 ], stm.wDay, stm.wHour, stm.wMinute, stm.wSecond, + local_hostname ); + va_start( args, fmt ); + vsnprintf( internal_message_buffer + len, sizeof(internal_message_buffer) - len, fmt, args ); + va_end( args ); + p = strchr( internal_message_buffer, '\n' ); + if( p ) + *p = 0; + p = strchr( internal_message_buffer, '\r' ); + if( p ) + *p = 0; + + SetEvent( event_array[ event_count - 2 ] ); + LeaveCriticalSection( &cs_internal_message ); + TRACE_LEAVE( "done\n" ); +} diff --git a/daemon/logrotate.c b/daemon/logrotate.c new file mode 100644 index 0000000..5b0932a --- /dev/null +++ b/daemon/logrotate.c @@ -0,0 +1,264 @@ +/* + * logrotate.c - syslogd implementation for windows, log rotation + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <windows.h> +#include <sys/stat.h> + +#include <glib.h> + +#include <syslog.h> +#include <syslogd.h> + +/****************************************************************************** + * expand_options + * + * Substitute $PATHNAME and $FILENAME + */ +static gchar* expand_options( const gchar* compressoptions, const gchar* pathname ) +{ + gchar *filename = g_path_get_basename( pathname ); + GString *str = g_string_new( compressoptions ); + gchar *substr_pathname; + gchar *substr_filename; + + TRACE_ENTER( "\n" ); + do { + substr_pathname = strstr( str->str, "$PATHNAME" ); + substr_filename = strstr( str->str, "$FILENAME" ); + + if( substr_pathname ) + { + int pos = substr_pathname - str->str; + g_string_erase( str, pos, 9 ); + g_string_insert( str, pos, pathname ); + } + + if( substr_filename ) + { + int pos = substr_filename - str->str; + g_string_erase( str, pos, 9 ); + g_string_insert( str, pos, filename ); + } + } while( substr_pathname || substr_filename ); + g_free( filename ); + TRACE_LEAVE( "return %s\n", str->str ); + return g_string_free( str, FALSE ); +} + +/****************************************************************************** + * compress_backlog + * + * Run external compression program. + * Return TRUE if all right. + */ +static gboolean compress_backlog( const gchar* compresscmd, const gchar* compressoptions, + const gchar* backlog ) +{ + gboolean ret = FALSE; + char command_pathname[ MAX_PATH ]; + LPTSTR command_filename; + gchar *options; + gchar *command_line; + STARTUPINFO si; + PROCESS_INFORMATION pi; + DWORD exit_code; + + TRACE_ENTER( "\n" ); + + if( !SearchPath( NULL, compresscmd, ".exe", + sizeof(command_pathname), command_pathname, &command_filename ) ) + { + ERR( "Command %s not found\n", compresscmd ); + TRACE_LEAVE( "error\n" ); + return FALSE; + } + + options = expand_options( compressoptions, backlog ); + command_line = g_strconcat( command_pathname, " ", options, NULL ); + + memset( &si, 0, sizeof(si ) ); + si.cb = sizeof(si); + TRACE_2( "command_line=%s\n", command_line ); + if( !CreateProcess( NULL, command_line, NULL, NULL, FALSE, + DETACHED_PROCESS, NULL, NULL, &si, &pi ) ) + ERR( "Cannot create process %s; error %lu\n", command_line, GetLastError() ); + else + { + CloseHandle( pi.hThread ); + TRACE_2( "waiting for %s\n", command_line ); + WaitForSingleObject( pi.hProcess, INFINITE ); + if( !GetExitCodeProcess( pi.hProcess, &exit_code ) ) + exit_code = 1; + CloseHandle( pi.hProcess ); + ret = 0 == exit_code; + } + + g_free( command_line ); + g_free( options ); + + TRACE_LEAVE( "ret=%d\n", ret ); + return ret; +} + +/****************************************************************************** + * do_rotate + * + * All criteria for rotation are met, just do it. + */ +static void do_rotate( const gchar* pathname, struct destination* destination ) +{ + int i; + gchar *dest_pathname; + gchar *backlog; + + TRACE_ENTER( "\n" ); + + /* construct destination pathname for backlogs */ + if( destination->olddir ) + { + gchar *filename = g_path_get_basename( pathname ); + + if( g_path_is_absolute( destination->olddir ) ) + dest_pathname = g_build_filename( destination->olddir, filename, NULL ); + else + { + gchar *prefix = g_path_get_dirname( __argv[0] ); + dest_pathname = g_build_filename( prefix, destination->olddir, filename, NULL ); + g_free( prefix ); + } + g_free( filename ); + } + else + dest_pathname = g_strdup( pathname ); + + /* remove earliest backlog */ + backlog = g_strdup_printf( "%s.%d", dest_pathname, destination->backlogs ); + DeleteFile( backlog ); + g_free( backlog ); + + /* rotate backlogs */ + for( i = destination->backlogs; i > 1; i-- ) + { + gchar *old_backlog = g_strdup_printf( "%s.%d", dest_pathname, i - 1 ); + gchar *new_backlog = g_strdup_printf( "%s.%d", dest_pathname, i ); + TRACE_2( "Move %s to %s\n", old_backlog, new_backlog ); + if( !MoveFile( old_backlog, new_backlog ) ) + { + /* most possible that old backlog file does not exist */ + TRACE_2( "Can't move %s to %s; error %lu\n", old_backlog, new_backlog, GetLastError() ); + } + g_free( old_backlog ); + g_free( new_backlog ); + } + + backlog = g_strconcat( dest_pathname, ".1", NULL ); + + /* move current log */ + TRACE_2( "Move %s to %s\n", pathname, backlog ); + if( !MoveFile( pathname, backlog ) ) + ERR( "Can't move %s to %s; error %lu\n", pathname, backlog, GetLastError() ); + + /* compress new backlog */ + if( destination->compresscmd ) + { + if( compress_backlog( destination->compresscmd, destination->compressoptions, backlog ) ) + /* remove original uncompressed file */ + DeleteFile( backlog ); + } + + g_free( backlog ); + g_free( dest_pathname ); + + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * rotate_logfile + */ +void rotate_logfile( const gchar* pathname, struct destination* destination ) +{ + struct stat fst; + gboolean rotated = FALSE; + time_t current_time; + struct tm *tm; + + TRACE_ENTER( "pathname=%s\n", pathname ); + + if( 0 == destination->backlogs + || (RP_UNDEFINED == destination->rotate && 0 == destination->size) ) + { + TRACE_LEAVE( "no conditions for rotation\n" ); + return; + } + + if( stat( pathname, &fst ) ) + { + /* most possible that file does not exist */ + TRACE_2( "stat(%s) error %s\n", pathname, strerror( errno ) ); + goto done; + } + + if( destination->size ) + { + if( fst.st_size > destination->size ) + { + do_rotate( pathname, destination ); + rotated = TRUE; + goto done; + } + TRACE_2( "checked size: file=%d, max=%d\n", fst.st_size, destination->size ); + } + + current_time = time(NULL); + tm = gmtime( ¤t_time ); + TRACE_2( "checking time: creation=%d, modification=%d\n", fst.st_ctime, fst.st_mtime ); + switch( destination->rotate ) + { + case RP_DAILY: + if( fst.st_mtime - (fst.st_ctime - fst.st_ctime % (24 * 3600)) > 24 * 3600 ) + break; + goto done; + case RP_WEEKLY: + if( 0 == tm->tm_wday && fst.st_mtime - fst.st_ctime > 24 * 3600 ) + break; + goto done; + case RP_MONTHLY: + if( 1 == tm->tm_mday && fst.st_mtime - fst.st_ctime > 24 * 3600 ) + break; + goto done; + default: + goto done; + } + + if( fst.st_size || destination->ifempty ) + { + do_rotate( pathname, destination ); + rotated = TRUE; + } + else + { + TRACE_2( "do not rotate empty file\n" ); + } + +done: + TRACE_LEAVE( "done; %s\n", rotated? "rotated" : "not rotated" ); +} diff --git a/daemon/main.c b/daemon/main.c new file mode 100644 index 0000000..eb77bb7 --- /dev/null +++ b/daemon/main.c @@ -0,0 +1,660 @@ +/* + * main.c - syslogd implementation for windows, main function + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <winsock2.h> + +#include <glib.h> + +#include <syslog.h> +#include <syslogd.h> + +HANDLE service_stop_event; + +char local_hostname[ MAX_COMPUTERNAME_LENGTH + 1 ]; + +int verbosity_level = 0; + +static char *service_name = "syslogd"; +static char *service_display_name = "syslogd"; +static char *service_stop_event_name = "syslogd-stop"; + +static OSVERSIONINFO vi; + +static SERVICE_STATUS_HANDLE hss; +static SERVICE_STATUS sstatus; + +/****************************************************************************** + * display message + */ +void display_message( FILE* fd, char* file, int line, const char* func, char* fmt, ... ) +{ + va_list args; + char formatstr[512]; + + snprintf( formatstr, sizeof(formatstr), "%08lX:%s:%d:%s: %s", + GetCurrentThreadId(), file, line, func, fmt ); + va_start( args, fmt ); + vfprintf( fd, formatstr, args ); + va_end( args ); + fflush( fd ); +} + +/****************************************************************************** + * shutdown_service + * + * set service stop event + * wait until event exists + */ +static void shutdown_service( gboolean quiet ) +{ + HANDLE he; + BOOL ret; + + for(;;) + { + he = OpenEvent( EVENT_MODIFY_STATE, FALSE, service_stop_event_name ); + if( !he ) + { + if( !quiet ) + ERR( "cannot open event; error %lu\n", GetLastError() ); + return; + } + TRACE( "setting stop event\n" ); + ret = SetEvent( he ); + CloseHandle( he ); + if( !ret ) + { + if( !quiet ) + { + ERR( "cannot set event; error %lu\n", GetLastError() ); + return; + } + } + quiet = TRUE; + Sleep(0); + } +} + +/****************************************************************************** + * service control handler + */ +static void WINAPI ServiceControlHandler( DWORD Control ) +{ + switch( Control ) + { + case SERVICE_CONTROL_STOP: + sstatus.dwCurrentState = SERVICE_STOP_PENDING; + sstatus.dwCheckPoint = 0; + SetEvent( service_stop_event ); + break; + case SERVICE_CONTROL_INTERROGATE: /* return status immediately */ + break; + } + SetServiceStatus( hss, &sstatus ); +} + +/****************************************************************************** + * service main + */ +static void WINAPI winnt_ServiceMain( DWORD Argc, LPTSTR* Argv ) +{ + hss = RegisterServiceCtrlHandler( PACKAGE_NAME, + (LPHANDLER_FUNCTION)ServiceControlHandler ); + if( !hss ) + return; + + service_stop_event = CreateEvent( NULL, TRUE, FALSE, NULL ); + if( !service_stop_event ) + return; + + sstatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + sstatus.dwCurrentState = SERVICE_RUNNING; + sstatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + sstatus.dwWin32ExitCode = NO_ERROR; + sstatus.dwCheckPoint = 0; + sstatus.dwWaitHint = 1000; + SetServiceStatus( hss, &sstatus ); + + syslogd_main(); + + sstatus.dwWin32ExitCode = 0; + sstatus.dwServiceSpecificExitCode = 0; + sstatus.dwCurrentState = SERVICE_STOPPED; + sstatus.dwCheckPoint = 0; + SetServiceStatus( hss, &sstatus ); + + CloseHandle( service_stop_event ); +} + +static int win9x_ServiceMain() +{ + service_stop_event = CreateEvent( NULL, TRUE, FALSE, service_stop_event_name ); + if( !service_stop_event ) + { + ERR( "Cannot create event %s; error %lu\n", service_stop_event_name, GetLastError() ); + return 1; + } + if( ERROR_ALREADY_EXISTS == GetLastError() ) + { + CloseHandle( service_stop_event ); + ERR( "Service is already running\n" ); + return 1; + } + + syslogd_main(); + + CloseHandle( service_stop_event ); + return 0; +} + +/****************************************************************************** + * open system Run key + */ +static BOOL open_run_key( PHKEY hk ) +{ + static char runkey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"; + + TRACE_ENTER( "\n" ); + if( RegOpenKey( HKEY_LOCAL_MACHINE, runkey, hk ) ) + { + ERR( "Cannot open registry key %s; error %lu\n", runkey, GetLastError() ); + return FALSE; + } + TRACE_LEAVE( "done\n" ); + return TRUE; +} + +/****************************************************************************** + * service installer + */ +static BOOL win9x_InstallService( char* command_line ) +{ + BOOL ret; + HKEY hk; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + TRACE_ENTER( "command_line=%s\n", command_line ); + ret = open_run_key( &hk ); + if( !ret ) + { + TRACE_LEAVE( "done\n" ); + return FALSE; + } + + if( RegSetValueEx( hk, service_name, 0, REG_SZ, command_line, strlen( command_line ) + 1 ) ) + { + ERR( "Cannot set registry value; error %lu\n", GetLastError() ); + ret = FALSE; + } + RegCloseKey( hk ); + if( !ret ) + return FALSE; + + memset( &si, 0, sizeof(si ) ); + si.cb = sizeof(si); + ret = CreateProcess( NULL, command_line, NULL, NULL, FALSE, DETACHED_PROCESS, + NULL, NULL, &si, &pi ); + if( !ret ) + { + ERR( "Cannot create process; error %lu\n", GetLastError() ); + return FALSE; + } + + CloseHandle( pi.hThread ); + CloseHandle( pi.hProcess ); + TRACE_LEAVE( "done\n" ); + return TRUE; +} + +static BOOL winnt_InstallService( char* command_line ) +{ + SC_HANDLE hscm, hsvc; + BOOL ret; + + TRACE_ENTER( "command_line=%s\n", command_line ); + hscm = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); + if( !hscm ) + { + ERR( "Cannot open service control manager; error %lu\n", GetLastError() ); + return FALSE; + } + hsvc = CreateService( hscm, service_name, service_display_name, + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, + command_line, NULL, NULL, NULL, NULL, NULL ); + if( !hsvc ) + { + ERR( "Cannot create service; error %lu\n", GetLastError() ); + ret = FALSE; + } + else + { + ret = StartService( hsvc, 0, NULL ); + if( !ret ) + ERR( "Cannot start service; error %lu\n", GetLastError() ); + CloseServiceHandle( hsvc ); + } + CloseServiceHandle( hscm ); + TRACE_LEAVE( "done\n" ); + return ret; +} + +static BOOL install_service( char* priority ) +{ + BOOL ret; + char command_line[ MAX_PATH + 64 ]; + + TRACE_ENTER( "\n" ); + if( __argv[0][1] == ':' ) + command_line[0] = 0; + else + { + TRACE( "argv[0] contains no absolute path\n" ); + GetCurrentDirectory( MAX_PATH, command_line ); + strcat( command_line, "\\" ); + } + strcat( command_line, __argv[0] ); + strcat( command_line, " --service" ); + if( priority ) + { + strcat( command_line, " --priority " ); + strcat( command_line, priority ); + } + + if( VER_PLATFORM_WIN32_NT == vi.dwPlatformId ) + ret = winnt_InstallService( command_line ); + else + ret = win9x_InstallService( command_line ); + + TRACE_LEAVE( "done; ret=%d\n", ret ); + return ret; +} + +/****************************************************************************** + * service uninstaller + */ +static void win9x_RemoveService() +{ + HKEY hk; + + TRACE_ENTER( "\n" ); + if( !open_run_key( &hk ) ) + return; + RegDeleteValue( hk, service_name ); + RegCloseKey( hk ); + TRACE_LEAVE( "done\n" ); +} + +static void winnt_RemoveService() +{ + SC_HANDLE hscm, hsvc; + int i; + + TRACE_ENTER( "\n" ); + hscm = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); + if( !hscm ) + { + ERR( "Cannot open service control manager; error %lu\n", GetLastError() ); + return; + } + + hsvc = OpenService( hscm, service_name, SERVICE_ALL_ACCESS ); + if( !hsvc ) + { + ERR( "Cannot open service %s; error %lu\n", service_name, GetLastError() ); + CloseServiceHandle( hscm ); + return; + } + + ControlService( hsvc, SERVICE_CONTROL_STOP, &sstatus ); + + for( i = 0; i < 10; i++ ) + { + Sleep( 100 ); + if( !QueryServiceStatus( hsvc, &sstatus ) ) + { + TRACE( "Cannot query service status; error %lu\n", GetLastError() ); + break; + } + if( SERVICE_STOPPED == sstatus.dwCurrentState ) + break; + } + if( !DeleteService( hsvc ) ) + TRACE( "Cannot delete service; error %lu\n", GetLastError() ); + + CloseServiceHandle( hsvc ); + CloseServiceHandle( hscm ); + TRACE_LEAVE( "done\n" ); +} + +static void remove_service() +{ + TRACE_ENTER( "\n" ); + if( VER_PLATFORM_WIN32_NT == vi.dwPlatformId ) + winnt_RemoveService(); + else + win9x_RemoveService(); + + /* always try to shutdown because it's possible that daemon + is running not as service under winNT */ + shutdown_service( TRUE ); + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * exception_handler + */ +LONG WINAPI exception_handler( PEXCEPTION_POINTERS ei ) +{ + DWORD i; + BYTE *addr; + + fprintf( stderr, + "*********************************\n" + "thread id:\t\t%lX\n" + "ExceptionCode:\t\t%lX\n" + "ExceptionFlags:\t\t%lX\n" + "ExceptionRecord:\t%p\n" + "ExceptionAddress:\t%p\n" + "NumberParameters:\t%lx\n" + "ExceptionInformation:\n", + GetCurrentThreadId(), + ei->ExceptionRecord->ExceptionCode, + ei->ExceptionRecord->ExceptionFlags, + ei->ExceptionRecord->ExceptionRecord, + ei->ExceptionRecord->ExceptionAddress, + ei->ExceptionRecord->NumberParameters ); + + for( i = 0; i < ei->ExceptionRecord->NumberParameters; i++ ) + fprintf( stderr, "\t%lX\n", ei->ExceptionRecord->ExceptionInformation[i] ); + + fprintf( stderr, + "ContextFlags=%lX\n" + "CS=%lX DS=%lX ES=%lX SS=%lX FS=%lX GS=%lX\n" + "EAX=%lX EBX=%lX ECX=%lX EDX=%lX ESI=%lX EDI=%lX\n" + "EBP=%lX ESP=%lX EIP=%lX EFLAGS=%lX\n" + "Stack Dump:\n", + ei->ContextRecord->ContextFlags, + ei->ContextRecord->SegCs, + ei->ContextRecord->SegDs, + ei->ContextRecord->SegEs, + ei->ContextRecord->SegSs, + ei->ContextRecord->SegFs, + ei->ContextRecord->SegGs, + ei->ContextRecord->Eax, + ei->ContextRecord->Ebx, + ei->ContextRecord->Ecx, + ei->ContextRecord->Edx, + ei->ContextRecord->Esi, + ei->ContextRecord->Edi, + ei->ContextRecord->Ebp, + ei->ContextRecord->Esp, + ei->ContextRecord->Eip, + ei->ContextRecord->EFlags ); + + addr = (LPBYTE) (ei->ContextRecord->Esp); + while( !IsBadReadPtr( addr, 16 ) ) + { + int skip = ((DWORD) addr) & 15; + BYTE *keep_addr = addr; + + fprintf( stderr, "%08lX", ((DWORD) addr) & ~15 ); + for( i = 0; i < skip; i++ ) + fprintf( stderr, " " ); + for( ; i < 8; i++ ) + fprintf( stderr, " %02X", *addr++ ); + if( i == 8 ) + fputc( '-', stderr ); + for( ; i < 16; i++ ) + fprintf( stderr, "%02X ", *addr++ ); + fputc( ' ', stderr ); + addr = keep_addr; + for( i = 0; i < skip; i++ ) + fputc( ' ', stderr ); + for( ; i < 16; i++ ) + { + BYTE b = *addr++; + + if( b < 32 ) b = ' '; + fputc( b, stderr ); + } + fputc( '\n', stderr ); + } + fprintf( stderr, "*********************************\n" ); + fflush( stderr ); + ExitProcess(2); +} + +/****************************************************************************** + * main + * + * Parse command line + */ +int main( int argc, char* argv[] ) +{ + static int help_flag = 0; + static int version_flag = 0; + static int install_flag = 0; + static int remove_flag = 0; + static int service_flag = 0; + static int shutdown_flag = 0; + char *instance_name = NULL; + char *priority = NULL; + int getopt_failure = 0; + WSADATA wsd; + DWORD size; + + SetUnhandledExceptionFilter( exception_handler ); + + if( WSAStartup( MAKEWORD( 2, 2 ), &wsd ) ) + { + ERR( "Cannot initialize winsock; error %lu\n", WSAGetLastError() ); + return 1; + } + + /* get local host name */ + size = sizeof(local_hostname); + if( !GetComputerName( local_hostname, &size ) ) + { + ERR( "Cannot get computer name; error %lu\n", GetLastError() ); + return 1; + } + TRACE( "local host name=%s\n", local_hostname ); + + /* get windows version */ + vi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if( ! GetVersionEx( &vi ) ) + { + ERR( "Cannot get windows version; error %lu\n", GetLastError() ); + return 1; + } + + for(;;) + { + static struct option long_options[] = + { + { "verbose", no_argument, NULL, 'v'}, + { "help", no_argument, &help_flag, 1 }, + { "version", no_argument, &version_flag, 1 }, + { "install", no_argument, &install_flag, 1 }, + { "remove", no_argument, &remove_flag, 1 }, + { "service", no_argument, &service_flag, 1 }, + { "shutdown", no_argument, &shutdown_flag,1 }, + { "instance", required_argument, NULL, 'I'}, + { "priority", required_argument, NULL, 'p'}, + { 0, 0, 0, 0 } + }; + int option_char; + int option_index; + + option_char = getopt_long( argc, argv, "vhirsI:p:", + long_options, &option_index ); + if( -1 == option_char ) + break; + + switch( option_char ) + { + case 0: + break; + + case 'v': + verbosity_level++; + break; + + case 'h': + help_flag = 1; + break; + + case 'i': + install_flag = 1; + break; + + case 'r': + remove_flag = 1; + break; + + case 'I': + instance_name = optarg; + break; + + case 'p': + { + DWORD pclass; + if( strcmpi( optarg, "normal" ) == 0 ) + { + priority = optarg; + pclass = NORMAL_PRIORITY_CLASS; + } + else if( strcmpi( optarg, "high" ) == 0 ) + { + priority = optarg; + pclass = ABOVE_NORMAL_PRIORITY_CLASS; + } + else if( strcmpi( optarg, "highest" ) == 0 ) + { + priority = optarg; + pclass = HIGH_PRIORITY_CLASS; + } + else + { + ERR( "Invalid priority %s; must be 'normal', 'high' or 'highest'\n", optarg ); + break; + } + SetPriorityClass( GetCurrentProcess(), pclass ); + break; + } + case '?': + /* getopt_long already printed an error message. */ + getopt_failure++; + break; + + default: + abort(); + } + } + if( getopt_failure ) + return 1; + + /* handle flags in order of priority */ + /* at first, check instance name */ + if( instance_name ) + { + service_name = g_strconcat( service_name, "_", instance_name, NULL ); + service_display_name = g_strconcat( service_display_name, "_", instance_name, NULL ); + service_stop_event_name = g_strconcat( service_stop_event_name, "_", instance_name, NULL ); + } + /* check service flag */ + if( service_flag ) + { + if( VER_PLATFORM_WIN32_NT == vi.dwPlatformId ) + { + /* run as service under windows NT */ + static SERVICE_TABLE_ENTRY service_table[] = { + { "", (LPSERVICE_MAIN_FUNCTION) winnt_ServiceMain }, + { NULL, NULL } + }; + + if( !StartServiceCtrlDispatcher( service_table ) ) + { + ERR( "Cannot start service control dispatcher; error %lu\n", GetLastError() ); + return 1; + } + return 0; + } + else + { + /* run as service under windows 9x */ + FreeConsole(); + return win9x_ServiceMain(); + } + } + + /* print version */ + if( version_flag ) + { + printf( "%s %s\n", PACKAGE_NAME, PACKAGE_VERSION ); + return 0; + } + + /* print help */ + if( help_flag ) + { + printf( "Usage: syslogd [OPTION]\n" ); + printf( "This is syslog daemon for windows.\n" ); + printf( "\n" ); + printf( "-v, --verbose\tbe verbose; each occurence of this parameter\n" + "\t\tincreases verbosity\n" ); + printf( "-i, --install\tinstall and start service\n" ); + printf( "-r, --remove\tstop and remove service\n" ); + printf( "-s, --shutdown\tsend shutdown signal to the daemon\n" ); + printf( "-I, --instance\tset instance name in the case of multiple daemons\n" ); + printf( "-p, --priority\tset priority class; value may be 'normal' (default),\n" + "\t\t\t'high', or 'highest'\n" ); + printf( "-h, --help\tdisplay this message\n" ); + printf( "--version\tdisplay version information\n" ); + return 0; + } + + /* install/remove/shutdown */ + if( remove_flag ) + { + if( install_flag || shutdown_flag ) + ERR( "Remove option has priority over install/shutdown\n" ); + remove_service(); + return 0; + } + if( install_flag ) + { + if( shutdown_flag ) + ERR( "Install option has priority over shutdown\n" ); + install_service( priority ); + return 0; + } + if( shutdown_flag ) + { + shutdown_service( FALSE ); + return 0; + } + + /* run as ordinary console application */ + return win9x_ServiceMain(); +} diff --git a/daemon/names.c b/daemon/names.c new file mode 100644 index 0000000..7c31d21 --- /dev/null +++ b/daemon/names.c @@ -0,0 +1,63 @@ +/* + * names.c - syslogd implementation for windows, syslog priority + * and facility names + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <stdio.h> +#define SYSLOG_NAMES +#include <syslog.h> + +char* get_priority_name( int pri ) +{ + static char *names[] = { + "emerg", + "alert", + "crit", + "error", + "warning", + "notice", + "info", + "debug" + }; + return names[ pri ]; +} + +char* get_facility_name( int fac ) +{ + static char *names[] = { + "kern", + "user", + "mail", + "daemon", + "auth", + "syslog", + "lpr", + "news", + "uucp", + "cron", + "authpriv", + "ftp", + "local0", + "local1", + "local2", + "local3", + "local4", + "local5", + "local6", + "local7" + }; + return names[ fac ]; +} diff --git a/daemon/pathnames.c b/daemon/pathnames.c new file mode 100644 index 0000000..932c854 --- /dev/null +++ b/daemon/pathnames.c @@ -0,0 +1,139 @@ +/* + * pathnames.c - syslogd implementation for windows, miscellaneous functions + * operating with file names and paths + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <windows.h> + +#include <glib.h> + +#include <syslog.h> +#include <syslogd.h> + +/****************************************************************************** + * make_absolute_log_pathname + * + * Make absolute pathname inside logdir. + */ +gchar* make_absolute_log_pathname( char* path_appendix ) +{ + gchar *path, *ret; + + if( logdir && g_path_is_absolute( logdir ) ) + path = g_strdup( logdir ); + else + { + gchar *dir = g_path_get_dirname( __argv[0] ); + path = g_build_filename( dir, logdir, NULL ); + g_free( dir ); + } + ret = g_build_filename( path, path_appendix, NULL ); + g_free( path ); + return ret; +} + +/****************************************************************************** + * create_directories + * + * Create all the directories in pathname. + */ +void create_directories( gchar* pathname ) +{ + gchar *p; + gchar saved; + + TRACE_ENTER( "%s\n", pathname ); + p = (gchar*) g_path_skip_root( pathname ); + if( !p ) + p = pathname; + for(;;) + { + while( *p != '\0' && *p != '/' && *p != '\\' ) + p++; + if( *p == '\0' ) + break; + saved = *p; + *p = '\0'; + CreateDirectory( pathname, NULL ); + *p++ = saved; + } + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * normalize_pathname + * + * Remove . and .. from pathname. + * Return NULL if pathname is invalid. + */ +gchar* normalize_pathname( const gchar* pathname ) +{ + gchar *ret = g_strdup( pathname ); + gchar *first_element, *current_element, *next_element; + + TRACE_ENTER( "%s\n", pathname ); + + first_element = (gchar*) g_path_skip_root( ret ); + if( !first_element ) + first_element = ret; + + for( current_element = first_element; *current_element != '\0'; ) + { + next_element = current_element; + while( *next_element != '\0' && *next_element != '/' && *next_element != '\\' ) + next_element++; + if( *next_element != '\0' ) + next_element++; /* skip separator */ + + if( current_element[0] != '.' ) + current_element = next_element; + else + { + if( current_element[1] == '/' + || current_element[1] == '\\' || current_element[1] == '\0' ) + /* /./ remove current element */ + memmove( current_element, next_element, strlen( next_element ) + 1 ); + else if( current_element[1] == '.' + && (current_element[2] == '/' + || current_element[2] == '\\' || current_element[2] == '\0') ) + { + /* /../ find and remove previous element */ + gchar *prev_element = current_element - 2; + if( prev_element < first_element ) + { + ERR( "Invalid pathname: %s\n", pathname ); + g_free( ret ); + ret = NULL; + break; + } + while( prev_element > first_element && *prev_element != '/' && *prev_element != '\\' ) + prev_element--; + if( prev_element > first_element ) + prev_element++; /* skip separator */ + memmove( prev_element, next_element, strlen( next_element ) + 1 ); + current_element = prev_element; + } + else + current_element = next_element; + } + } + TRACE_LEAVE( "ret=%s\n", ret? ret : "NULL" ); + return ret; +} diff --git a/daemon/purger.c b/daemon/purger.c new file mode 100644 index 0000000..43be168 --- /dev/null +++ b/daemon/purger.c @@ -0,0 +1,155 @@ +/* + * purger.c - syslogd implementation for windows, log dir purger + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <stdio.h> +#include <windows.h> + +#include <glib.h> + +#include <syslog.h> +#include <syslogd.h> + +static HANDLE purger_semaphore = NULL; + +/****************************************************************************** + * recursive_purge + * + * auxiliary function for purge_log_dirs + */ +static void recursive_purge( gchar* pathname, LPFILETIME min_time ) +{ + gchar *new_pathname; + HANDLE find_handle; + WIN32_FIND_DATA find_data; + + TRACE_ENTER( "%s\n", pathname ); + + new_pathname = g_build_filename( pathname, "*", NULL ); + find_handle = FindFirstFile( new_pathname, &find_data ); + if( INVALID_HANDLE_VALUE == find_handle ) + { + TRACE_2( "FindFirstFile(%s) error %lu\n", new_pathname, GetLastError() ); + g_free( new_pathname ); + TRACE_LEAVE( "it seems the path does not exist\n" ); + return; + } + g_free( new_pathname ); + do { + if( (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '\0') + || + (find_data.cFileName[0] == '.' + && find_data.cFileName[1] == '.' && find_data.cFileName[2] == '\0') ) + continue; + + new_pathname = g_build_filename( pathname, find_data.cFileName, NULL ); + if( find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + { + recursive_purge( new_pathname, min_time ); + if( RemoveDirectory( new_pathname ) ) + { + TRACE( "removed %s\n", new_pathname ); + } + else + { + TRACE( "failed to remove %s\n", new_pathname ); + } + } + else + { + if( CompareFileTime( min_time, &find_data.ftLastWriteTime ) > 0 ) + { + if( DeleteFile( new_pathname ) ) + { + TRACE( "deleted %s\n", new_pathname ); + } + else + { + TRACE( "failed to delete %s\n", new_pathname ); + } + } + } + g_free( new_pathname ); + } while( FindNextFile( find_handle, &find_data ) ); + FindClose( find_handle ); + + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * purge_log_dirs + */ +void purge_log_dirs() +{ + GList *item; + + TRACE_ENTER( "\n" ); + if( WaitForSingleObject( purger_semaphore, 0 ) != WAIT_OBJECT_0 ) + { + TRACE_LEAVE( "done; another thread is active\n" ); + return; + } + for( item = purger_dirs; item; item = item->next ) + { + struct purger_dir *p = item->data; + gchar *pathname = make_absolute_log_pathname( p->directory ); + FILETIME min_time; + long long min_time_64; + + GetSystemTimeAsFileTime( &min_time ); + min_time_64 = (((long long) min_time.dwHighDateTime) << 32) + (long long) min_time.dwLowDateTime; + min_time_64 -= ((long long) p->keep_days) * ((long long) (24 * 3600)) * (long long) 10000000; + min_time.dwHighDateTime = min_time_64 >> 32; + min_time.dwLowDateTime = min_time_64; + + recursive_purge( pathname, &min_time ); + g_free( pathname ); + } + ReleaseSemaphore( purger_semaphore, 1, NULL ); + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * init_purger + */ +gboolean init_purger() +{ + gboolean ret = FALSE; + + TRACE_ENTER( "\n" ); + + purger_semaphore = CreateSemaphore( NULL, 1, 1, NULL ); + if( !purger_semaphore ) + { + ERR( "Cannot create semaphore; error %lu\n", GetLastError() ); + goto done; + } + ret = TRUE; + +done: + TRACE_LEAVE( "done; ret=%d\n", ret ); + return ret; +} + +/****************************************************************************** + * fini_purger + */ +void fini_purger() +{ + TRACE_ENTER( "\n" ); + if( purger_semaphore ) CloseHandle( purger_semaphore ); + TRACE_LEAVE( "done\n" ); +} diff --git a/daemon/syslogd.c b/daemon/syslogd.c new file mode 100644 index 0000000..59ede6d --- /dev/null +++ b/daemon/syslogd.c @@ -0,0 +1,600 @@ +/* + * syslogd.c - syslogd implementation for windows + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <ctype.h> +#include <process.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <winsock2.h> + +#include <glib.h> + +#include <syslog.h> +#include <syslogd.h> + +static struct fifo *raw_message_queue = NULL; +static HANDLE fifo_semaphore = NULL; + +/* cache for source hostnames */ +struct hostname +{ + struct sockaddr_in addr; + gchar *host; + time_t top_age; /* zero prevents aging */ +}; +static GList *hostnames = NULL; + +#define HOSTNAME_LIFETIME 60 /* seconds */ +/* FIXME: is this value correct? maybe we should make it configurable? */ + +static GIConv conversion_descriptor = (GIConv) -1; + +char *str_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +/****************************************************************************** + * refrence_message + * + * increment reference count + */ +void reference_message( struct message* msg ) +{ + TRACE_ENTER( "message=%p\n", msg ); + InterlockedIncrement( &msg->refcount ); +} + +/****************************************************************************** + * release_message + * + * decrement reference count and destroy message structure if it becomes zero + */ +void release_message( struct message* msg ) +{ + TRACE_ENTER( "message=%p\n", msg ); + if( InterlockedDecrement( &msg->refcount ) ) + { + TRACE_LEAVE( "done; still referenced\n" ); + return; + } + g_free( msg->sender ); + g_free( msg->timestamp ); + g_free( msg->hostname ); + g_free( msg->program ); + g_free( msg->message ); + g_free( msg ); + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * convert_message_encoding + */ +static void convert_message_encoding( struct message* msg ) +{ + gchar *converted_msg; + + TRACE_ENTER( "message=%p\n", msg ); + + if( conversion_descriptor == (GIConv) -1 ) + { + TRACE_LEAVE( "nothing to do\n" ); + return; + } + + converted_msg = g_convert_with_iconv( msg->message, -1, + conversion_descriptor, NULL, NULL, NULL ); + if( !converted_msg ) + { + TRACE_LEAVE( "conversion error\n" ); + return; + } + + g_free( msg->message ); + msg->message = converted_msg; + + TRACE_LEAVE( "done; %s\n", msg->message ); +} + +/****************************************************************************** + * filter_message + * + * return: TRUE - accepted message, FALSE - rejected message + */ +static gboolean filter_message( struct message* msg, struct filter* filter ) +{ + gboolean ret = FALSE; + + TRACE_ENTER( "message=%p, filter=%s\n", msg, filter? filter->name : "NULL" ); + + if( !filter ) + goto passed; + + /* check facility */ + if( !filter->facilities[ msg->facility ] ) + goto done; + + /* check priority */ + if( !filter->priorities[ msg->priority ] ) + goto done; + +passed: + /* message is passed through filter */ + ret = TRUE; + +done: + TRACE_LEAVE( "done; ret=%d\n", ret ); + return ret; +} + +/****************************************************************************** + * mux_message + * + * filter and multiplex message to destinations; + * release message + */ +static void mux_message( struct message* msg ) +{ + GList *item; + + TRACE_ENTER( "message=%p\n", msg ); + + convert_message_encoding( msg ); + + for( item = logpaths; item; item = item->next ) + { + struct logpath *logpath = item->data; + + if( logpath->source != msg->source ) + continue; + + if( !filter_message( msg, logpath->filter ) ) + continue; + + write_message( msg, logpath->destination ); + } + + release_message( msg ); + + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * get_hostname + * + * convert addr to string and return it + */ +static gchar* get_hostname( struct sockaddr_in* addr ) +{ + gchar *ret; + time_t current_time; + GList *item; + struct hostname *new_hostname; + + TRACE_ENTER( "%d.%d.%d.%d\n", + addr->sin_addr.S_un.S_un_b.s_b1, addr->sin_addr.S_un.S_un_b.s_b2, + addr->sin_addr.S_un.S_un_b.s_b3, addr->sin_addr.S_un.S_un_b.s_b4 ); + + current_time = time(NULL); + + /* at first, try to find a cached entry */ + item = hostnames; + while( item ) + { + struct hostname *h = item->data; + if( h->top_age && h->top_age < current_time ) + { + GList *next_item = item->next; + + TRACE_2( "delete old entry %s\n", h->host ); + g_free( h->host ); + g_free( h ); + hostnames = g_list_delete_link( hostnames, item ); + item = next_item; + continue; + } + if( h->addr.sin_addr.S_un.S_addr == addr->sin_addr.S_un.S_addr ) + { + /* found in cache */ + ret = g_strdup( h->host ); + /* move entry to the beginning of the list */ + item->data = hostnames->data; + hostnames->data = h; + TRACE_LEAVE( "done; found cached entry: %s\n", ret ); + return ret; + } + item = item->next; + } + /* add new entry */ + new_hostname = g_malloc( sizeof(struct hostname) ); + memcpy( &new_hostname->addr, addr, sizeof(struct sockaddr_in) ); + if( use_dns ) + { + struct hostent *he = gethostbyaddr( (char*) &addr->sin_addr.S_un.S_addr, + sizeof(addr->sin_addr.S_un.S_addr), AF_INET ); + new_hostname->top_age = time(NULL) + HOSTNAME_LIFETIME; + if( !he ) + goto use_addr; + new_hostname->host = g_strdup( he->h_name ); + } + else + { + new_hostname->top_age = 0; +use_addr: + new_hostname->host = g_malloc( 16 ); + sprintf( new_hostname->host, "%d.%d.%d.%d", + addr->sin_addr.S_un.S_un_b.s_b1, addr->sin_addr.S_un.S_un_b.s_b2, + addr->sin_addr.S_un.S_un_b.s_b3, addr->sin_addr.S_un.S_un_b.s_b4 ); + } + hostnames = g_list_prepend( hostnames, new_hostname ); + ret = g_strdup( new_hostname->host ); + TRACE_LEAVE( "done; ret=%s\n", ret ); + return ret; +} + +/****************************************************************************** + * free_hostnames + */ +static void free_hostnames() +{ + GList *item; + for( item = hostnames; item; item = item->next ) + { + struct hostname *h = item->data; + g_free( h->host ); + g_free( h ); + } + g_list_free( hostnames ); + hostnames = NULL; +} + +/****************************************************************************** + * parse_PRI + * + * parse PRI part of message; + * set facility and priority; + * return pointer to the next char after PRI + */ +static gchar* parse_PRI( gchar* msg, int* facility, int* priority ) +{ + int i, pri; + + TRACE_ENTER( "\n" ); + + if( *msg != '<' ) + goto no_pri; + + pri = 0; + for( i = 1; i < 5; i++ ) + { + if( msg[ i ] == '>' ) + break; + if( !isdigit( msg[ i ] ) ) + goto no_pri; + pri = (pri * 10) + (msg[ i ] - '0'); + } + if( i == 5 ) + goto no_pri; + if( i == 1 ) + /* FIXME: is this right? or "<>" is an unidentifiable PRI? */ + goto no_pri; + + msg += i + 1; + + if( pri > LOG_NFACILITIES * 8 + 7 ) + { + TRACE_2( "Unidentifiable PRI %d\n", pri ); + *facility = LOG_USER; + *priority = LOG_NOTICE; + } + else + { + TRACE_2( "PRI=%d\n", pri ); + *facility = LOG_FAC( pri ); + *priority = LOG_PRI( pri ); + } + + TRACE_LEAVE( "done; facility=%d, priority=%d\n", *facility, *priority ); + return msg; + +no_pri: + *facility = LOG_USER; + *priority = LOG_NOTICE; + TRACE_LEAVE( "done; message contains no PRI\n" ); + return msg; +} + +/****************************************************************************** + * parse_timestamp + * + * parse TIMESTAMP part of message; + * allocate string; + * return pointer to the next char after TIMESTAMP + */ +static gchar* parse_timestamp( gchar* msg, gchar** timestamp ) +{ + int i; + SYSTEMTIME stm; + + TRACE_ENTER( "\n" ); + + for( i = 0; i < 12; i++ ) + if( strncasecmp( msg, str_month[ i ], 3 ) == 0 ) + break; + if( i == 12 ) + goto no_timestamp; + stm.wMonth = i + 1; + + if( msg[3] != ' ' ) + goto no_timestamp; + + if( msg[4] != ' ' ) + { + if( (!isdigit( msg[4] )) || (!isdigit( msg[5] )) ) + goto no_timestamp; + stm.wDay = (msg[4] - '0') * 10 + (msg[5] - '0'); + } + else + { + if( !isdigit( msg[5] ) ) + goto no_timestamp; + stm.wDay = msg[5] - '0'; + } + + if( msg[6] != ' ' ) + goto no_timestamp; + + if( (!isdigit( msg[7] )) || (!isdigit( msg[8] )) || msg[9] != ':' ) + goto no_timestamp; + stm.wHour = (msg[7] - '0') * 10 + (msg[8] - '0'); + + if( (!isdigit( msg[10] )) || (!isdigit( msg[11] )) || msg[12] != ':' ) + goto no_timestamp; + stm.wMinute = (msg[10] - '0') * 10 + (msg[11] - '0'); + + if( (!isdigit( msg[13] )) || (!isdigit( msg[14] )) || msg[15] != ' ' ) + goto no_timestamp; + stm.wSecond = (msg[13] - '0') * 10 + (msg[14] - '0'); + msg += 16; + goto done; + +no_timestamp: + TRACE_2( "no timestamp\n" ); + GetLocalTime( &stm ); + +done: + *timestamp = g_strdup_printf( "%s %2d %02d:%02d:%02d", + str_month[ stm.wMonth - 1 ], + stm.wDay, stm.wHour, stm.wMinute, stm.wSecond ); + TRACE_LEAVE( "done\n" ); + return msg; +} + +/****************************************************************************** + * parse_raw_message + * + * parse raw message and make a new message; + * destroy raw message + */ +static struct message* parse_raw_message( struct raw_message* raw_msg ) +{ + struct message* msg; + gchar *current_part, *next_part; + + TRACE_ENTER( "raw message=%p\n", raw_msg ); + + /* allocate and initialize message structure */ + msg = g_malloc( sizeof(struct message) ); + msg->refcount = 1; + msg->source = raw_msg->source; + + /* get sender's hostname */ + msg->sender = get_hostname( &raw_msg->sender_addr ); + + current_part = raw_msg->msg; + next_part = parse_PRI( current_part, &msg->facility, &msg->priority ); + + current_part = next_part; + next_part = parse_timestamp( current_part, &msg->timestamp ); + if( next_part == current_part ) + { + /* no valid timestamp */ + TRACE_2( "no valid timestamp: msg=%s\n", current_part ); + msg->hostname = g_strdup( msg->sender ); + msg->message = g_strdup( current_part ); + } + else + { + /* have valid timestamp, go ahead */ + current_part = next_part; + while( isalnum( *next_part ) || *next_part == '-' || *next_part == '.' ) + next_part++; + if( *next_part != ' ' ) + { + /* invalid hostname */ + msg->hostname = g_strdup( msg->sender ); + msg->message = g_strdup( current_part ); + TRACE_2( "invalid hostname; set sender (%s); msg=%s\n", msg->hostname, msg->message ); + } + else + { + msg->hostname = g_strndup( current_part, next_part - current_part ); + while( *next_part == ' ' && *next_part != 0 ) + next_part++; + msg->message = g_strdup( next_part ); + TRACE_2( "hostname=%s; msg=%s\n", msg->hostname, msg->message ); + } + } + + /* try to find program name */ + current_part = msg->message; + next_part = current_part; + while( *next_part != ' ' && *next_part != ':' && *next_part != '[' && *next_part != 0 ) + next_part++; + if( *next_part == ' ' || *next_part == 0 ) + msg->program = g_strdup(""); + else + msg->program = g_strndup( current_part, next_part - current_part ); + + /* destroy raw message */ + g_free( raw_msg->msg ); + g_free( raw_msg ); + + TRACE_LEAVE( "done; message=%p\n", msg ); + return msg; +} + +/****************************************************************************** + * message_processor + * + * main function; extract raw messages from queue and parse them + */ +static unsigned __stdcall message_processor( void* arg ) +{ + HANDLE wait_handles[2] = { fifo_semaphore, service_stop_event }; + + TRACE_ENTER( "\n" ); + for(;;) + { + DWORD w; + struct raw_message *raw_msg; + + w = WaitForMultipleObjects( 2, wait_handles, FALSE, INFINITE ); + if( WAIT_OBJECT_0 + 1 == w ) + /* shutdown */ + break; + if( w != WAIT_OBJECT_0 ) + { + ERR( "WaitForMultipleObjects() error %lu\n", GetLastError() ); + SetEvent( service_stop_event ); + break; + } + /* extract raw message from queue */ + raw_msg = fifo_pop( raw_message_queue ); + + TRACE_2( "got message %p from queue\n", raw_msg ); + + mux_message( parse_raw_message( raw_msg ) ); + } + TRACE_LEAVE( "done\n" ); + return 0; +} + +/****************************************************************************** + * main syslogd function + * + * global initialization; invoke listener + */ +void syslogd_main() +{ + HANDLE message_processor_thread = NULL; + unsigned tid; + + TRACE_ENTER( "\n" ); + + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); + + if( !read_configuration() ) + goto done; + + if( !init_purger() ) + goto done; + + if( !init_listener() ) + goto done; + + purge_log_dirs(); + + if( source_encoding && destination_encoding ) + { + conversion_descriptor = g_iconv_open( destination_encoding, source_encoding ); + if( conversion_descriptor == (GIConv) -1 ) + { + ERR( "Cannot convert messages from %s to %s\n", + source_encoding, destination_encoding ); + } + } + + /* create message queue and semaphore; + * avoid using Glib's asynchronous queues and threading support + * because synchronization capabilities are very limited; + */ + raw_message_queue = fifo_create(); + fifo_semaphore = CreateSemaphore( NULL, 0, LONG_MAX, NULL ); + if( !fifo_semaphore ) + { + ERR( "Cannot create semaphore; error %lu\n", GetLastError() ); + goto done; + } + + message_processor_thread = (HANDLE) _beginthreadex( NULL, 0, message_processor, NULL, 0, &tid ); + if( !message_processor_thread ) + { + ERR( "Cannot create thread; error %lu\n", GetLastError() ); + goto done; + } + + /* get messages from the listener */ + for(;;) + { + struct raw_message *raw_msg; + + switch( listener( &raw_msg ) ) + { + case LSNR_ERROR: + case LSNR_SHUTDOWN: + goto done; + + case LSNR_GOT_MESSAGE: + TRACE_2( "got message from %d.%d.%d.%d; source name %s; ptr=%p: %s\n", + raw_msg->sender_addr.sin_addr.S_un.S_un_b.s_b1, + raw_msg->sender_addr.sin_addr.S_un.S_un_b.s_b2, + raw_msg->sender_addr.sin_addr.S_un.S_un_b.s_b3, + raw_msg->sender_addr.sin_addr.S_un.S_un_b.s_b4, + raw_msg->source->name, raw_msg, raw_msg->msg ); + /* add raw message to queue */ + fifo_push( raw_message_queue, raw_msg ); + ReleaseSemaphore( fifo_semaphore, 1, NULL ); + break; + } + } + +done: + /* signal to all possibly running threads */ + SetEvent( service_stop_event ); + + if( message_processor_thread ) + { + /* wait for message processor shutdown */ + WaitForSingleObject( message_processor_thread, INFINITE ); + CloseHandle( message_processor_thread ); + } + + fini_writer(); + fini_purger(); + free_hostnames(); + + if( raw_message_queue ) fifo_destroy( raw_message_queue ); + if( conversion_descriptor != (GIConv) -1 ) + g_iconv_close( conversion_descriptor ); + + fini_listener(); + + if( fifo_semaphore ) CloseHandle( fifo_semaphore ); + + TRACE_LEAVE( "done\n" ); +} diff --git a/daemon/syslogd.h b/daemon/syslogd.h new file mode 100644 index 0000000..65dc042 --- /dev/null +++ b/daemon/syslogd.h @@ -0,0 +1,204 @@ +/* + * syslogd.h - syslogd implementation for windows, common definitions + * + * Created by Alexander Yaworsky + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +extern int verbosity_level; +extern void display_message( FILE* fd, char* file, int line, const char* func, char* fmt, ... ); + +#ifdef HAVE_DEBUG +# define DO_TRACE( verbosity, fmt... ) \ + do { \ + if( verbosity <= verbosity_level ) \ + display_message( stderr, __FILE__, __LINE__, __FUNCTION__, fmt ); \ + } while(0) +# define TRACE_2( fmt... ) DO_TRACE( 2, fmt ) +# define TRACE( fmt... ) DO_TRACE( 1, fmt ) +#else +# define TRACE_2( fmt... ) +# define TRACE( fmt... ) +#endif +#define TRACE_ENTER TRACE_2 +#define TRACE_LEAVE TRACE_2 +#define ERR( fmt... ) display_message( stderr, __FILE__, __LINE__, __FUNCTION__, fmt ) + +extern HANDLE service_stop_event; + +extern char local_hostname[]; + +extern char *str_month[]; + +extern void syslogd_main(); + +/* options and their default values */ +extern gboolean use_dns; +extern gchar *source_encoding; +extern gchar *destination_encoding; +extern int mark_interval; +extern gchar *mark_message; +extern int hold; +extern gchar *logdir; + +/* sources, destinations, filters and logpaths */ +enum source_type +{ + ST_UNDEFINED, + ST_INTERNAL, + ST_UDP +}; + +struct source +{ + gchar *name; + enum source_type type; + struct sockaddr_in udp; +}; + +enum rotation_period +{ + RP_UNDEFINED = 0, + RP_INVALID, + RP_DAILY, + RP_WEEKLY, + RP_MONTHLY +}; + +struct destination +{ + gchar *name; + gchar *file; + GList *file_writers; + CRITICAL_SECTION cs_file_writers; + enum rotation_period rotate; + int size; + int backlogs; + gboolean ifempty; + gchar *olddir; + gchar *compresscmd; + gchar *compressoptions; +}; + +struct filter +{ + gchar *name; + gboolean facilities[ LOG_NFACILITIES ]; + gboolean priorities[ 8 ]; +}; + +struct logpath +{ + struct source *source; + struct filter *filter; + struct destination *destination; +}; + +extern GList *sources; +extern GList *destinations; +extern GList *filters; +extern GList *logpaths; + +extern gboolean read_configuration(); + +/* queue */ +struct fifo_item +{ + struct fifo_item *next; /* queue is a single-linked list */ + void *payload; +}; + +struct fifo +{ + struct fifo_item *first; /* first pushed item */ + struct fifo_item *last; /* last pushed item */ +}; + +extern struct fifo* fifo_create(); +extern void fifo_destroy( struct fifo* queue ); +extern void fifo_push( struct fifo* queue, void* data ); +extern void* fifo_pop( struct fifo* queue ); + +/* listener */ +enum listener_status +{ + LSNR_ERROR, + LSNR_SHUTDOWN, + LSNR_GOT_MESSAGE +}; + +struct raw_message +{ + gchar *msg; + struct sockaddr_in sender_addr; + struct source *source; +}; + +extern gboolean init_listener(); +extern void fini_listener(); +extern enum listener_status listener( struct raw_message** msg ); +extern void log_internal( int pri, char* fmt, ... ); + +/* message */ +struct message +{ + LONG refcount; + struct source *source; + gchar *sender; + int facility; + int priority; + gchar *timestamp; + gchar *hostname; + gchar *program; + gchar *message; +}; + +extern void reference_message( struct message* msg ); +extern void release_message( struct message* msg ); + +/* writer */ +extern void fini_writer(); +extern void write_message( struct message* msg, struct destination* destination ); + +/* logrotate */ +extern void rotate_logfile( const gchar* pathname, struct destination* destination ); + +/* purger */ +struct purger_dir +{ + gchar *directory; + int keep_days; +}; + +extern GList *purger_dirs; + +extern gboolean init_purger(); +extern void fini_purger(); +extern void purge_log_dirs(); + +/* pathnames */ +extern gchar* make_absolute_log_pathname( char* path_appendix ); +extern void create_directories( gchar* pathname ); +extern gchar* normalize_pathname( const gchar* pathname ); + +/* workaround for syslog.h: included with SYSLOG_NAMES in names.c */ +typedef struct _code { + char *c_name; + int c_val; +} CODE; + +extern CODE prioritynames[]; +extern CODE facilitynames[]; + +extern char* get_priority_name( int pri ); +extern char* get_facility_name( int pri ); diff --git a/daemon/writer.c b/daemon/writer.c new file mode 100644 index 0000000..ea4f915 --- /dev/null +++ b/daemon/writer.c @@ -0,0 +1,498 @@ +/* + * writer.c - syslogd implementation for windows, message writer + * + * Created by Alexander Yaworsky + * + * Asynchronous i/o is not supported under win9x so we have to use + * a separate thread for each file. + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <ctype.h> +#include <errno.h> +#include <process.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <windows.h> + +#include <glib.h> + +#include <syslog.h> +#include <syslogd.h> + +#define WRITER_KEEPALIVE_TIME 10000 + +struct file_writer +{ + struct destination *destination; + gchar *file_name; + struct fifo *message_queue; + HANDLE fifo_semaphore; + HANDLE shutdown_event; /* manual-reset event */ + HANDLE fd; + struct message *first_msg, *second_msg, *current_msg; + int coalesce_count; + time_t max_hold; +}; + +/* forward declarations */ +static unsigned __stdcall writer_thread_proc( void* arg ); + +/****************************************************************************** + * compare_current_and_first_messages + * + * Auxiliary function for coalescer. + * If messages aren't identical, reset coalescer. + */ +static gboolean compare_current_and_first_messages( struct file_writer* writer ) +{ + TRACE_ENTER( "%p\n", writer ); + + if( strcmp( writer->current_msg->hostname, writer->first_msg->hostname ) + || strcmp( writer->current_msg->message, writer->first_msg->message ) ) + { + release_message( writer->first_msg ); + writer->first_msg = NULL; + TRACE_LEAVE( "%p done; messages aren't identical\n", writer ); + return FALSE; + } + + TRACE_LEAVE( "%p ok\n" ); + return TRUE; +} + +/****************************************************************************** + * init_coalescer + * + * Should be called when we've got the first unique message. + * Save message for subsequent comparsions and set max hold time. + */ +static void init_coalescer( struct file_writer* writer ) +{ + writer->first_msg = writer->current_msg; + reference_message( writer->first_msg ); + writer->max_hold = time(NULL) + hold; + writer->coalesce_count = 1; + TRACE_2( "%p max_hold=%ld\n", writer, writer->max_hold ); +} + +/****************************************************************************** + * coalesce + * + * coalesce messages; + * If there are more than two sequential identical messages then we + * should write first of them followed by message with the number of + * subsequent messages. + * + * The caller always must process current_msg unless it is set NULL + * when coalesced; + * return: TRUE if messages are coalesced; FALSE if flushing is required + */ +static gboolean coalesce( struct file_writer* writer ) +{ + time_t current_time; + + TRACE_ENTER( "%p\n", writer ); + + switch( writer->coalesce_count ) + { + case 0: + /* the first message */ + init_coalescer( writer ); + return TRUE; + + case 1: + /* the second message */ + TRACE_2( "%p second message\n", writer ); + if( !compare_current_and_first_messages( writer ) ) + return FALSE; + writer->second_msg = writer->current_msg; + writer->current_msg = NULL; + writer->coalesce_count = 2; + goto check_hold_time; + + case 2: + /* the third message */ + TRACE_2( "%p third message\n", writer ); + if( !compare_current_and_first_messages( writer ) ) + /* leave the second message; it will be written by flush_coalescer */ + return FALSE; + release_message( writer->second_msg ); + writer->second_msg = NULL; + release_message( writer->current_msg ); + writer->current_msg = NULL; + writer->coalesce_count = 3; + goto check_hold_time; + + default: + /* the fourth and subsequent messages */ + TRACE_2( "%p fourth+ message\n", writer ); + if( !compare_current_and_first_messages( writer ) ) + return FALSE; + release_message( writer->current_msg ); + writer->current_msg = NULL; + writer->coalesce_count++; + TRACE_2( "%p coalesce_count=%d\n", writer, writer->coalesce_count ); + goto check_hold_time; + } + +check_hold_time: + current_time = time(NULL); + TRACE_2( "%p current_time=%ld\n", writer, current_time ); + if( writer->max_hold < current_time ) + { + TRACE_LEAVE( "%p done; elapsed hold time\n", writer ); + return FALSE; + } + TRACE_LEAVE( "%p done\n", writer ); + return TRUE; +} + +/****************************************************************************** + * write_message_to_logfile + */ +static void write_message_to_logfile( struct file_writer* writer, struct message** msg ) +{ + gchar *buffer; + DWORD written; + + if( INVALID_HANDLE_VALUE == writer->fd ) + return; + TRACE_2( "%p: %s\n", writer, (*msg)->message ); + buffer = g_strconcat( (*msg)->timestamp, " ", (*msg)->hostname, " ", (*msg)->message, "\n", NULL ); + WriteFile( writer->fd, buffer, strlen( buffer ), &written, NULL ); + g_free( buffer ); + release_message( *msg ); + *msg = NULL; +} + +/****************************************************************************** + * flush_coalescer + */ +static void flush_coalescer( struct file_writer* writer ) +{ + if( writer->second_msg ) + { + write_message_to_logfile( writer, &writer->second_msg ); + TRACE_2( "%p written second message\n", writer ); + } + + if( writer->coalesce_count > 2 ) + { + SYSTEMTIME stm; + char buffer[ 1024 ]; + int size; + DWORD written; + + GetLocalTime( &stm ); + + /* make informational message */ + size = snprintf( buffer, sizeof(buffer), + "%s %2d %02d:%02d:%02d %s syslog: last message repeated %d times\n", + str_month[ stm.wMonth - 1 ], stm.wDay, stm.wHour, stm.wMinute, stm.wSecond, + local_hostname, + writer->coalesce_count - 1 ); + WriteFile( writer->fd, buffer, size, &written, NULL ); + TRACE_2( "%p made informational message\n", writer ); + } + + writer->coalesce_count = 0; + if( writer->first_msg ) + { + release_message( writer->first_msg ); + writer->first_msg = NULL; + } + if( writer->current_msg ) + { + /* we just got the first message and should initialize coalescer */ + init_coalescer( writer ); + write_message_to_logfile( writer, &writer->current_msg ); + TRACE_2( "%p written current message\n", writer ); + } +} + +/****************************************************************************** + * destroy_file_writer + */ +static void destroy_file_writer( struct file_writer* writer ) +{ + TRACE_ENTER( "%p\n", writer ); + if( writer->fifo_semaphore ) CloseHandle( writer->fifo_semaphore ); + if( writer->shutdown_event ) CloseHandle( writer->shutdown_event ); + fifo_destroy( writer->message_queue ); + g_free( writer->file_name ); + g_free( writer ); + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * create_file_writer + */ +static struct file_writer* create_file_writer( gchar* file_name ) +{ + struct file_writer *ret; + unsigned writer_thread_id; + HANDLE *writer_thread; + + TRACE_ENTER( "file_name=%s\n", file_name ); + ret = g_malloc0( sizeof(struct file_writer) ); + ret->file_name = g_strdup( file_name ); + ret->message_queue = fifo_create(); + ret->fifo_semaphore = CreateSemaphore( NULL, 0, LONG_MAX, NULL ); + if( !ret->fifo_semaphore ) + { + ERR( "Cannot create semaphore; error %lu\n", GetLastError() ); + goto error; + } + ret->shutdown_event = CreateEvent( NULL, TRUE, FALSE, NULL ); + if( !ret->shutdown_event ) + { + ERR( "Cannot create event; error %lu\n", GetLastError() ); + goto error; + } + writer_thread = (HANDLE) _beginthreadex( NULL, 0, writer_thread_proc, ret, + 0, &writer_thread_id ); + if( !writer_thread ) + { + ERR( "Cannot create thread; error %lu\n", GetLastError() ); + goto error; + } + CloseHandle( writer_thread ); + TRACE_LEAVE( "done; ret=%p\n", ret ); + return ret; + +error: + destroy_file_writer( ret ); + TRACE_LEAVE( "error\n" ); + return NULL; +} + +/****************************************************************************** + * detach_writer_from_destination + */ +static void detach_writer_from_destination( struct file_writer* writer ) +{ + TRACE_ENTER( "%p\n", writer ); + if( !writer->destination ) + { + TRACE_LEAVE( "done; already detached\n" ); + return; + } + EnterCriticalSection( &writer->destination->cs_file_writers ); + writer->destination->file_writers = g_list_remove( writer->destination->file_writers, writer ); + LeaveCriticalSection( &writer->destination->cs_file_writers ); + writer->destination = NULL; + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * writer_thread_proc + * + * Open file, extract messages from queue and write them to file. + * If queue is empty long enough, close file and destroy itself. + */ +static unsigned __stdcall writer_thread_proc( void* arg ) +{ + struct file_writer *writer = arg; + HANDLE wait_objects[2] = { writer->fifo_semaphore, writer->shutdown_event }; + gchar *pathname; + FILETIME systime; + + TRACE_ENTER( "writer=%p\n", writer ); + + pathname = make_absolute_log_pathname( writer->file_name ); + create_directories( pathname ); + + rotate_logfile( pathname, writer->destination ); + + writer->fd = CreateFile( pathname, GENERIC_WRITE, FILE_SHARE_READ, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + g_free( pathname ); + if( INVALID_HANDLE_VALUE == writer->fd ) + { + ERR( "CreateFile(%s) error %lu\n", pathname, GetLastError() ); + goto done; + } + SetFilePointer( writer->fd, 0, NULL, FILE_END ); + + /* there's a strange bug or feature in windows: if there was a file with the same + name in the directory, a new file will inherit its creation time; + because of this logs will be rotate each time; + here is a workaround: + */ + GetSystemTimeAsFileTime( &systime ); + if( !SetFileTime( writer->fd, &systime, &systime, &systime ) ) + ERR( "SetFileTime error %lu\n", GetLastError() ); + + for(;;) + { + DWORD w = WaitForMultipleObjects( 2, wait_objects, FALSE, WRITER_KEEPALIVE_TIME ); + if( WAIT_TIMEOUT == w ) + { + /* prepare to suicide; at this point a new message may be put into queue; + detach writer from destination and continue to write any pending messages */ + detach_writer_from_destination( writer ); + /* from now no new messages will be put into queue */ + SetEvent( writer->shutdown_event ); + } + + writer->current_msg = fifo_pop( writer->message_queue ); + if( !writer->current_msg ) + { + /* shutdown */ + goto done; + } + + if( coalesce( writer ) ) + { + if( writer->current_msg ) + { + write_message_to_logfile( writer, &writer->current_msg ); + TRACE_2( "%p written current message\n", writer ); + } + } + else + { + flush_coalescer( writer ); + } + } +done: + detach_writer_from_destination( writer ); + flush_coalescer( writer ); + if( writer->fd != INVALID_HANDLE_VALUE ) CloseHandle( writer->fd ); + destroy_file_writer( writer ); + + purge_log_dirs(); + + TRACE_LEAVE( "done\n" ); + return 0; +} + +/****************************************************************************** + * make_file_name + * + * expand filename pattern (message->file) + */ +static void make_file_name( char* pattern, struct message* message, char* buffer ) +{ + char *dest = buffer; + SYSTEMTIME stm; + + GetLocalTime( &stm ); + for(;;) + { + char c = *pattern++; + if( c != '%' ) + { + *dest++ = c; + if( '\0' == c ) + break; + continue; + } + c = *pattern++; + switch( c ) + { + case 'Y': dest += sprintf( dest, "%u", stm.wYear ); break; + case 'M': dest += sprintf( dest, "%02u", stm.wMonth ); break; + case 'm': dest += sprintf( dest, "%u", stm.wMonth ); break; + case 'D': dest += sprintf( dest, "%02u", stm.wDay ); break; + case 'd': dest += sprintf( dest, "%u", stm.wDay ); break; + case 'W': dest += sprintf( dest, "%u", stm.wDayOfWeek + 1 ); break; + case 'F': dest += sprintf( dest, "%s", get_facility_name( message->facility ) ); break; + case 'f': dest += sprintf( dest, "%d", message->facility ); break; + case 'L': dest += sprintf( dest, "%s", get_priority_name( message->priority ) ); break; + case 'l': dest += sprintf( dest, "%d", message->priority ); break; + case 'H': dest += sprintf( dest, "%s", message->hostname ); break; + case 'h': dest += sprintf( dest, "%s", message->sender ); break; + case 'P': dest += sprintf( dest, "%s", message->program ); break; + default: *dest++ = c; break; + } + } + strlwr( buffer ); +} + +/****************************************************************************** + * write_message + */ +void write_message( struct message* msg, struct destination* destination ) +{ + char file_name[ MAX_PATH ]; + GList *item; + struct file_writer *writer; + + TRACE_ENTER( "msg=%p, destination=%s\n", msg, destination->name ); + make_file_name( destination->file, msg, file_name ); + EnterCriticalSection( &destination->cs_file_writers ); + /* find existing writer */ + for( writer = NULL, item = destination->file_writers; item; item = item->next ) + if( strcmp( ((struct file_writer*) (item->data))->file_name, file_name ) == 0 ) + { + writer = item->data; + break; + } + if( !writer ) + { + /* create new writer */ + writer = create_file_writer( file_name ); + if( !writer ) + goto done; + /* add writer to destination */ + destination->file_writers = g_list_append( destination->file_writers, writer ); + writer->destination = destination; + } + /* put message into queue */ + reference_message( msg ); + fifo_push( writer->message_queue, msg ); + ReleaseSemaphore( writer->fifo_semaphore, 1, NULL ); + +done: + LeaveCriticalSection( &destination->cs_file_writers ); + TRACE_LEAVE( "done\n" ); +} + +/****************************************************************************** + * fini_writer + */ +void fini_writer() +{ + GList *dest_item; + + TRACE_ENTER( "\n" ); + /* setting shutdown event */ + for( dest_item = destinations; dest_item; dest_item = dest_item->next ) + { + struct destination *destination = dest_item->data; + GList *wr_item; + + EnterCriticalSection( &destination->cs_file_writers ); + for( wr_item = destination->file_writers; wr_item; wr_item = wr_item->next ) + { + struct file_writer *writer = wr_item->data; + SetEvent( writer->shutdown_event ); + } + LeaveCriticalSection( &destination->cs_file_writers ); + } + TRACE_2( "waiting for shutdown\n" ); + for(;;) + { + for( dest_item = destinations; dest_item; dest_item = dest_item->next ) + if( ((struct destination*) (dest_item->data))->file_writers ) + break; + if( !dest_item ) + break; + Sleep( 60 ); + } + TRACE_LEAVE( "done\n" ); +} |