 d700754baa
			
		
	
	d700754baa
	
	
	
		
			
			Bug #372, http://bugs.skysql.com/show_bug.cgi?id=372 Do not exceed the buffer capacity in log writing. Now longer strings are cut to fit to the buffer.
		
			
				
	
	
		
			2449 lines
		
	
	
		
			75 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2449 lines
		
	
	
		
			75 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * This file is distributed as part of the SkySQL Gateway.  It is free
 | |
|  * software: you can redistribute it and/or modify it under the terms of the
 | |
|  * GNU General Public License as published by the Free Software Foundation,
 | |
|  * version 2.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful, but WITHOUT
 | |
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | |
|  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | |
|  * details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License along with
 | |
|  * this program; if not, write to the Free Software Foundation, Inc., 51
 | |
|  * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | |
|  *
 | |
|  * Copyright SkySQL Ab 2013
 | |
|  */
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <fcntl.h>
 | |
| 
 | |
| #include <unistd.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <stdarg.h>
 | |
| #include <errno.h>
 | |
| #include <syslog.h>
 | |
| 
 | |
| #include <skygw_debug.h>
 | |
| #include <skygw_types.h>
 | |
| #include <skygw_utils.h>
 | |
| #include <log_manager.h>
 | |
| 
 | |
| #define MAX_PREFIXLEN 250
 | |
| #define MAX_SUFFIXLEN 250
 | |
| #define MAX_PATHLEN   512
 | |
| #define MAXNBLOCKBUFS 10
 | |
| 
 | |
| /** for procname */
 | |
| #if !defined(_GNU_SOURCE)
 | |
| # define _GNU_SOURCE
 | |
| #endif
 | |
| 
 | |
| extern char *program_invocation_name;
 | |
| extern char *program_invocation_short_name;
 | |
| 
 | |
| /**
 | |
|  * Variable holding the enabled logfiles information.
 | |
|  * Used from log users to check enabled logs prior calling
 | |
|  * actual library calls such as skygw_log_write.
 | |
|  */
 | |
| int lm_enabled_logfiles_bitmask = 0;
 | |
| 
 | |
| /**
 | |
|  * BUFSIZ comes from the system. It equals with block size or
 | |
|  * its multiplication.
 | |
|  */
 | |
| #define MAX_LOGSTRLEN BUFSIZ
 | |
| 
 | |
| #if defined(SS_PROF)
 | |
| /**
 | |
|  * These counters may be inaccurate but give some idea of how
 | |
|  * things are going.
 | |
|  */
 | |
| 
 | |
| #endif
 | |
| /**
 | |
|  * Path to directory in which all files are stored to shared memory
 | |
|  * by the OS.
 | |
|  */
 | |
| const char* shm_pathname = "/dev/shm";
 | |
| 
 | |
| /** Logfile ids from call argument '-s' */
 | |
| char* shmem_id_str     = NULL;
 | |
| /** Errors are written to syslog too by default */
 | |
| char* syslog_id_str    = strdup("LOGFILE_ERROR");
 | |
| char* syslog_ident_str = NULL;
 | |
| 
 | |
| /**
 | |
|  * Global log manager pointer and lock variable.
 | |
|  * lmlock protects logmanager access.
 | |
|  */
 | |
| static int lmlock;
 | |
| static logmanager_t* lm;
 | |
| 
 | |
| 
 | |
| /** Writer thread structure */
 | |
| struct filewriter_st {
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t        fwr_chk_top;
 | |
| #endif
 | |
|         flat_obj_state_t   fwr_state;
 | |
|         logmanager_t*      fwr_logmgr;
 | |
|         /** Physical files */
 | |
|         skygw_file_t*      fwr_file[LOGFILE_LAST+1];
 | |
|         /** fwr_logmes is for messages from log clients */
 | |
|         skygw_message_t*   fwr_logmes;
 | |
|         /** fwr_clientmes is for messages to log clients */
 | |
|         skygw_message_t*   fwr_clientmes;
 | |
|         skygw_thread_t*    fwr_thread;
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t        fwr_chk_tail;
 | |
| #endif
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Log client's string is copied to block-sized log buffer, which is passed
 | |
|  * to file writer thread.
 | |
|  */
 | |
| typedef struct blockbuf_st {
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t    bb_chk_top;
 | |
| #endif
 | |
|         logfile_id_t   bb_fileid;
 | |
|         bool           bb_isfull;   /**< closed for disk write */
 | |
|         simple_mutex_t bb_mutex;    /**< bb_buf_used, bb_isfull */
 | |
|         int            bb_refcount; /**< protected by list mutex. #of clients */
 | |
| //        int            bb_blankcount; /**< # of blanks used btw strings */
 | |
|         size_t         bb_buf_size;
 | |
|         size_t         bb_buf_left;
 | |
|         size_t         bb_buf_used;
 | |
|         char           bb_buf[MAX_LOGSTRLEN];
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t    bb_chk_tail;
 | |
| #endif
 | |
| } blockbuf_t;
 | |
| 
 | |
| /**
 | |
|  * logfile object corresponds to physical file(s) where
 | |
|  * certain log is written.
 | |
|  */
 | |
| struct logfile_st {
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t      lf_chk_top;
 | |
| #endif
 | |
|         flat_obj_state_t lf_state;
 | |
|         bool             lf_init_started;
 | |
|         bool             lf_enabled;
 | |
|         bool             lf_store_shmem;
 | |
|         bool             lf_write_syslog;
 | |
|         logmanager_t*    lf_lmgr;
 | |
|         /** fwr_logmes is for messages from log clients */
 | |
|         skygw_message_t* lf_logmes;
 | |
|         logfile_id_t     lf_id;
 | |
|         char*            lf_filepath; /**< path to file used for logging */
 | |
|         char*            lf_linkpath; /**< path to symlink file.  */
 | |
|         char*            lf_name_prefix;
 | |
|         char*            lf_name_suffix;
 | |
|         int              lf_name_seqno;
 | |
|         char*            lf_full_file_name; /**< complete log file name */
 | |
|         char*            lf_full_link_name; /**< complete symlink name */
 | |
|         int              lf_nfiles_max;
 | |
|         size_t           lf_file_size;
 | |
|         /** list of block-sized log buffers */
 | |
|         mlist_t          lf_blockbuf_list;
 | |
|         int              lf_buf_size;
 | |
|         bool             lf_flushflag;
 | |
|         int              lf_spinlock; /**< lf_flushflag */
 | |
|         int              lf_npending_writes;
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t      lf_chk_tail;
 | |
| #endif
 | |
| };
 | |
| 
 | |
| 
 | |
| struct fnames_conf_st {
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t      fn_chk_top;
 | |
| #endif
 | |
|         flat_obj_state_t fn_state;
 | |
|         char*            fn_debug_prefix;
 | |
|         char*            fn_debug_suffix;
 | |
|         char*            fn_trace_prefix;
 | |
|         char*            fn_trace_suffix;
 | |
|         char*            fn_msg_prefix;
 | |
|         char*            fn_msg_suffix;
 | |
|         char*            fn_err_prefix;
 | |
|         char*            fn_err_suffix;
 | |
|         char*            fn_logpath;
 | |
| #if defined(SS_DEBUG)  
 | |
|         skygw_chk_t      fn_chk_tail;
 | |
| #endif
 | |
| };
 | |
| 
 | |
| struct logmanager_st {
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t      lm_chk_top;
 | |
| #endif
 | |
|         bool             lm_enabled;
 | |
|         int              lm_enabled_logfiles;
 | |
|         simple_mutex_t   lm_mutex;
 | |
|         size_t           lm_nlinks;
 | |
|         /** fwr_logmes is for messages from log clients */
 | |
|         skygw_message_t* lm_logmes;
 | |
|         /** fwr_clientmes is for messages to log clients */
 | |
|         skygw_message_t* lm_clientmes;
 | |
|         fnames_conf_t    lm_fnames_conf;
 | |
|         logfile_t        lm_logfile[LOGFILE_LAST+1];
 | |
|         filewriter_t     lm_filewriter;
 | |
| #if defined(SS_DEBUG)
 | |
|         skygw_chk_t      lm_chk_tail;
 | |
| #endif
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Type definition for string part. It is used in forming the log file name
 | |
|  * from string parts provided by the client of log manager, as arguments.
 | |
|  */
 | |
| typedef struct strpart_st strpart_t;
 | |
| 
 | |
| struct strpart_st {
 | |
|         char*      sp_string;
 | |
|         strpart_t* sp_next;
 | |
| };
 | |
| 
 | |
| 
 | |
| /** Static function declarations */
 | |
| static bool logfiles_init(logmanager_t* lmgr);
 | |
| static bool logfile_init(
 | |
|         logfile_t*     logfile,
 | |
|         logfile_id_t   logfile_id,
 | |
|         logmanager_t*  logmanager,
 | |
|         bool           store_shmem,
 | |
|         bool           write_syslog);
 | |
| static void logfile_done(logfile_t* logfile);
 | |
| static void logfile_free_memory(logfile_t* lf);
 | |
| static void logfile_flush(logfile_t* lf);
 | |
| static bool filewriter_init(
 | |
|         logmanager_t*    logmanager,
 | |
|         filewriter_t*    fw,
 | |
|         skygw_message_t* clientmes,
 | |
|         skygw_message_t* logmes);
 | |
| static void   filewriter_done(filewriter_t* filewriter);
 | |
| static bool   fnames_conf_init(fnames_conf_t* fn, int argc, char* argv[]);
 | |
| static void   fnames_conf_done(fnames_conf_t* fn);
 | |
| static void   fnames_conf_free_memory(fnames_conf_t* fn);
 | |
| static char*  fname_conf_get_prefix(fnames_conf_t* fn, logfile_id_t id);
 | |
| static char*  fname_conf_get_suffix(fnames_conf_t* fn, logfile_id_t id);
 | |
| static void*  thr_filewriter_fun(void* data);
 | |
| static logfile_t* logmanager_get_logfile(logmanager_t* lm, logfile_id_t id);
 | |
| static bool logmanager_register(bool writep);
 | |
| static void logmanager_unregister(void);
 | |
| static bool logmanager_init_nomutex(int argc, char* argv[]);
 | |
| static void logmanager_done_nomutex(void);
 | |
| static int  logmanager_write_log(
 | |
|         logfile_id_t id,
 | |
|         bool         flush,
 | |
|         bool         use_valist,
 | |
|         bool         spread_down,
 | |
|         size_t       len,
 | |
|         char*        str,
 | |
|         va_list      valist);
 | |
| 
 | |
| static blockbuf_t* blockbuf_init(logfile_id_t id);
 | |
| static char*       blockbuf_get_writepos(
 | |
| #if 0
 | |
|         int**        refcount,
 | |
| #else
 | |
|         blockbuf_t** p_bb,
 | |
| #endif
 | |
|         logfile_id_t id,
 | |
|         size_t       str_len,
 | |
|         bool         flush);
 | |
| 
 | |
| static void  blockbuf_register(blockbuf_t* bb);
 | |
| static void  blockbuf_unregister(blockbuf_t* bb);
 | |
| static bool  logfile_set_enabled(logfile_id_t id, bool val);
 | |
| static char* add_slash(char* str);
 | |
| static bool  file_exists_and_is_writable(char* filename, bool* writable);
 | |
| static bool  file_is_symlink(char* filename);
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| const char* get_suffix_default(void)
 | |
| {
 | |
|         return ".log";        
 | |
| }
 | |
| 
 | |
| const char* get_debug_prefix_default(void)
 | |
| {
 | |
|         return "skygw_debug";
 | |
| }
 | |
| 
 | |
| const char* get_debug_suffix_default(void)
 | |
| {
 | |
|         return get_suffix_default();
 | |
| }
 | |
| 
 | |
| const char* get_trace_prefix_default(void)
 | |
| {
 | |
|         return "skygw_trace";
 | |
| }
 | |
| 
 | |
| const char* get_trace_suffix_default(void)
 | |
| {
 | |
|         return get_suffix_default();
 | |
| }
 | |
| 
 | |
| const char* get_msg_prefix_default(void)
 | |
| {
 | |
|         return "skygw_msg";
 | |
| }
 | |
| 
 | |
| const char* get_msg_suffix_default(void)
 | |
| {
 | |
|         return get_suffix_default();
 | |
| }
 | |
| 
 | |
| const char* get_err_prefix_default(void)
 | |
| {
 | |
|         return "skygw_err";
 | |
| }
 | |
| 
 | |
| const char* get_err_suffix_default(void)
 | |
| {
 | |
|         return get_suffix_default();
 | |
| }
 | |
| 
 | |
| const char* get_logpath_default(void)
 | |
| {
 | |
|         return "/tmp";
 | |
| }
 | |
| 
 | |
| static bool logmanager_init_nomutex(
 | |
|         int    argc,
 | |
|         char*  argv[])
 | |
| {
 | |
|         fnames_conf_t* fn;
 | |
|         filewriter_t*  fw;
 | |
|         int            err;
 | |
|         bool           succp = false;
 | |
| 
 | |
|         lm = (logmanager_t *)calloc(1, sizeof(logmanager_t));
 | |
| #if defined(SS_DEBUG)
 | |
|         lm->lm_chk_top   = CHK_NUM_LOGMANAGER;
 | |
|         lm->lm_chk_tail  = CHK_NUM_LOGMANAGER;
 | |
| #endif
 | |
|         lm->lm_clientmes = skygw_message_init();
 | |
|         lm->lm_logmes    = skygw_message_init();
 | |
|         lm->lm_enabled_logfiles |= LOGFILE_ERROR;
 | |
|         lm->lm_enabled_logfiles |= LOGFILE_MESSAGE;
 | |
| #if defined(SS_DEBUG)
 | |
|         lm->lm_enabled_logfiles |= LOGFILE_TRACE;
 | |
|         lm->lm_enabled_logfiles |= LOGFILE_DEBUG;
 | |
| #endif
 | |
|         fn = &lm->lm_fnames_conf;
 | |
|         fw = &lm->lm_filewriter;
 | |
|         fn->fn_state  = UNINIT;
 | |
|         fw->fwr_state = UNINIT;
 | |
| 
 | |
|         /**
 | |
|          * Set global variable
 | |
|          */
 | |
|         lm_enabled_logfiles_bitmask = lm->lm_enabled_logfiles;
 | |
|         
 | |
|         /** Initialize configuration including log file naming info */
 | |
|         if (!fnames_conf_init(fn, argc, argv)) {
 | |
|             goto return_succp;
 | |
|         }
 | |
| 
 | |
|         /** Initialize logfiles */
 | |
|         if(!logfiles_init(lm)) {
 | |
|             goto return_succp;
 | |
|         }
 | |
|         
 | |
|         /** Initialize filewriter data and open the (first) log file(s)
 | |
|          * for each log file type. */
 | |
|         if (!filewriter_init(lm, fw, lm->lm_clientmes, lm->lm_logmes)) {
 | |
|             goto return_succp;
 | |
|         }
 | |
|         
 | |
|         /** Initialize and start filewriter thread */
 | |
|         fw->fwr_thread = skygw_thread_init("filewriter thr",
 | |
|                                            thr_filewriter_fun,
 | |
|                                            (void *)fw);
 | |
|    
 | |
|         if ((err = skygw_thread_start(fw->fwr_thread)) != 0) {
 | |
|             goto return_succp;
 | |
|         }
 | |
|         /** Wait message from filewriter_thr */
 | |
|         skygw_message_wait(fw->fwr_clientmes);
 | |
| 
 | |
|         succp = true;
 | |
|         lm->lm_enabled = true;
 | |
|         
 | |
| return_succp:
 | |
|         if (err != 0) {
 | |
|             /** This releases memory of all created objects */
 | |
|             logmanager_done_nomutex();
 | |
|             fprintf(stderr, "* Initializing logmanager failed.\n");
 | |
|         }
 | |
|         return succp;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node Initializes log managing routines in SkySQL Gateway.
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param p_ctx - in, give
 | |
|  *          pointer to memory location where logmanager stores private write
 | |
|  * buffer.
 | |
|  *
 | |
|  * @param argc - in, use
 | |
|  *          number of arguments in argv array
 | |
|  *
 | |
|  * @param argv - in, use
 | |
|  *          arguments array
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details (write detailed description here)
 | |
|  *
 | |
|  */
 | |
| bool skygw_logmanager_init(
 | |
|         int    argc,
 | |
|         char*  argv[])
 | |
| {
 | |
|         bool           succp = false;
 | |
|                 
 | |
|         acquire_lock(&lmlock);
 | |
| 
 | |
|         if (lm != NULL) {
 | |
|             succp = true;
 | |
|             goto return_succp;
 | |
|         }
 | |
|         
 | |
|         succp = logmanager_init_nomutex(argc, argv);
 | |
|         
 | |
| return_succp:
 | |
|         release_lock(&lmlock);
 | |
|         
 | |
|         return succp;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void logmanager_done_nomutex(void)
 | |
| {
 | |
|         int           i;
 | |
|         logfile_t*    lf;
 | |
|         filewriter_t* fwr;
 | |
| 
 | |
|         fwr = &lm->lm_filewriter;
 | |
| 
 | |
|         if (fwr->fwr_state == RUN) {
 | |
|             CHK_FILEWRITER(fwr);
 | |
|             /** Inform filewriter thread and wait until it has stopped. */
 | |
|             skygw_thread_set_exitflag(fwr->fwr_thread,
 | |
|                                       fwr->fwr_logmes,
 | |
|                                       fwr->fwr_clientmes);
 | |
|         
 | |
|             /** Free thread memory */
 | |
|             skygw_thread_done(fwr->fwr_thread);
 | |
|         }
 | |
|         
 | |
|         /** Free filewriter memory. */
 | |
|         filewriter_done(fwr);
 | |
|         
 | |
|         for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i++) {
 | |
|             lf = logmanager_get_logfile(lm, (logfile_id_t)i);            
 | |
|             /** Release logfile memory */
 | |
|             logfile_done(lf);
 | |
|         }
 | |
| 
 | |
|         if (syslog_id_str)
 | |
|         {
 | |
|                 closelog();
 | |
|         }
 | |
|         /** Release messages and finally logmanager memory */
 | |
|         fnames_conf_done(&lm->lm_fnames_conf);
 | |
|         skygw_message_done(lm->lm_clientmes);
 | |
|         skygw_message_done(lm->lm_logmes);
 | |
| 
 | |
|         /** Set global pointer NULL to prevent access to freed data. */
 | |
|         free(lm);
 | |
|         lm = NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node This function is provided for atexit() system function. 
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param void - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return void
 | |
|  *
 | |
|  * 
 | |
|  * @details (write detailed description here)
 | |
|  *
 | |
|  */
 | |
| void skygw_logmanager_exit(void)
 | |
| {
 | |
|         skygw_logmanager_done();
 | |
| }
 | |
| 
 | |
| /** 
 | |
|  * @node End execution of log manager
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param p_ctx - in, take
 | |
|  *           pointer to memory location including context pointer. Context will
 | |
|  *           be freed in this function.
 | |
|  *
 | |
|  * @param logmanager - in, use
 | |
|  *          pointer to logmanager.
 | |
|  *
 | |
|  * @return void
 | |
|  *
 | |
|  * 
 | |
|  * @details Stops file writing thread, releases filewriter, and logfiles.
 | |
|  *
 | |
|  */
 | |
| void skygw_logmanager_done(void)
 | |
| {
 | |
|         acquire_lock(&lmlock);
 | |
| 
 | |
|         if (lm == NULL) {
 | |
|             release_lock(&lmlock);
 | |
|             return;
 | |
|         }
 | |
|         CHK_LOGMANAGER(lm);
 | |
|         /** Mark logmanager unavailable */
 | |
|         lm->lm_enabled = false;
 | |
|         
 | |
|         /** Wait until all users have left or someone shuts down
 | |
|          * logmanager between lock release and acquire.
 | |
|          */
 | |
|         while(lm != NULL && lm->lm_nlinks != 0) {
 | |
|             release_lock(&lmlock);
 | |
|             pthread_yield();
 | |
|             acquire_lock(&lmlock);
 | |
|         }
 | |
| 
 | |
|         /** Logmanager was already shut down. Return successfully. */
 | |
|         if (lm == NULL) {
 | |
|             goto return_void;
 | |
|         }
 | |
|         ss_dassert(lm->lm_nlinks == 0);
 | |
|         logmanager_done_nomutex();
 | |
| 
 | |
| return_void:
 | |
|         release_lock(&lmlock);
 | |
| }
 | |
| 
 | |
| static logfile_t* logmanager_get_logfile(
 | |
|         logmanager_t* lmgr,
 | |
|         logfile_id_t  id)
 | |
| {
 | |
|         logfile_t* lf;
 | |
|         CHK_LOGMANAGER(lmgr);
 | |
|         ss_dassert(id >= LOGFILE_FIRST && id <= LOGFILE_LAST);
 | |
|         lf = &lmgr->lm_logfile[id];
 | |
| 
 | |
|         if (lf->lf_state == RUN) {
 | |
|             CHK_LOGFILE(lf);
 | |
|         }
 | |
|         return lf;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node Finds write position from block buffer for log string and writes there.  
 | |
|  *
 | |
|  * Parameters:
 | |
|  *
 | |
|  * @param id - in, use
 | |
|  *          logfile object identifier
 | |
|  *
 | |
|  * @param flush - in, use
 | |
|  *          indicates whether log string must be written to disk immediately
 | |
|  *
 | |
|  * @param use_valist - in, use
 | |
|  *          does write involve formatting of the string and use of valist argument
 | |
|  *
 | |
|  * @param spread_down - in, use
 | |
|  *          if true, log string is spread to all logs having larger id.
 | |
|  *
 | |
|  * @param str_len - in, use
 | |
|  *          length of formatted string
 | |
|  *
 | |
|  * @param str - in, use
 | |
|  *          string to be written to log 
 | |
|  *
 | |
|  * @param valist - in, use
 | |
|  *          variable-length argument list for formatting the string
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details (write detailed description here)
 | |
|  *
 | |
|  */
 | |
| static int logmanager_write_log(
 | |
|         logfile_id_t  id,
 | |
|         bool          flush,
 | |
|         bool          use_valist,
 | |
|         bool          spread_down,
 | |
|         size_t        str_len,
 | |
|         char*         str,
 | |
|         va_list       valist)
 | |
| {
 | |
|         logfile_t*   lf;
 | |
|         char*        wp;
 | |
|         int          err = 0;
 | |
|         blockbuf_t*  bb;
 | |
|         blockbuf_t*  bb_c;
 | |
|         int          timestamp_len;
 | |
|         int          i;
 | |
| 
 | |
|         CHK_LOGMANAGER(lm);
 | |
|         
 | |
|         if (id < LOGFILE_FIRST || id > LOGFILE_LAST) {
 | |
|                 char* errstr = "Invalid logfile id argument.";
 | |
|                 /**
 | |
|                  * invalid id, since we don't have logfile yet.
 | |
|                  */
 | |
|                 err = logmanager_write_log(LOGFILE_ERROR,
 | |
|                                            true,
 | |
|                                            false,
 | |
|                                            false,
 | |
|                                            strlen(errstr)+1,
 | |
|                                            errstr,
 | |
|                                            valist);
 | |
|                 if (err != 0) {
 | |
|                         fprintf(stderr,
 | |
|                                 "Writing to logfile %s failed.\n",
 | |
|                                 STRLOGID(LOGFILE_ERROR));
 | |
|                 }
 | |
|                 err = -1;
 | |
|                 ss_dassert(false);            
 | |
|                 goto return_err;
 | |
|         }
 | |
|         lf = &lm->lm_logfile[id];
 | |
|         CHK_LOGFILE(lf);
 | |
| 
 | |
|         /**
 | |
|          * When string pointer is NULL, case is skygw_log_flush and no
 | |
|          * writing is involved. With flush && str != NULL case is
 | |
|          * skygw_log_write_flush.
 | |
|          */
 | |
|         if (str == NULL) {
 | |
|                 ss_dassert(flush);
 | |
|                 logfile_flush(lf); /**< here we wake up file writer */ 
 | |
|         } else {
 | |
|                 /** Length of string that will be written, limited by bufsize */
 | |
|                 int safe_str_len; 
 | |
| 
 | |
|                 timestamp_len = get_timestamp_len();
 | |
|                 safe_str_len = MIN(timestamp_len-1+str_len, lf->lf_buf_size);
 | |
|                 /**
 | |
|                  * Seek write position and register to block buffer.
 | |
|                  * Then print formatted string to write position.
 | |
|                  */
 | |
|                 wp = blockbuf_get_writepos(&bb,
 | |
|                                            id,
 | |
|                                            safe_str_len,
 | |
|                                            flush);
 | |
|                 /**
 | |
|                  * Write timestamp with at most <timestamp_len> characters
 | |
|                  * to wp
 | |
|                  */
 | |
|                 timestamp_len = snprint_timestamp(wp, timestamp_len);
 | |
|                 /**
 | |
|                  * Write next string to overwrite terminating null character
 | |
|                  * of the timestamp string.
 | |
|                  */
 | |
|                 if (use_valist) {
 | |
|                         vsnprintf(wp+timestamp_len-1, safe_str_len, str, valist);
 | |
|                 } else {
 | |
|                         snprintf(wp+timestamp_len-1, safe_str_len, str);
 | |
|                 }
 | |
|                 
 | |
|                 /** write to syslog */
 | |
|                 if (lf->lf_write_syslog)
 | |
|                 {
 | |
|                         switch(id) {
 | |
|                         case LOGFILE_ERROR:
 | |
|                                 syslog(LOG_ERR, wp+timestamp_len-1);
 | |
|                                 break;
 | |
|                                 
 | |
|                         case LOGFILE_MESSAGE:
 | |
|                                 syslog(LOG_NOTICE, wp+timestamp_len-1);
 | |
|                                 break;
 | |
|                                 
 | |
|                         default:
 | |
|                                 break;
 | |
|                         }
 | |
|                 }
 | |
|                 
 | |
|                 /** remove double line feed */
 | |
|                 if (wp[timestamp_len-1+str_len-2] == '\n') {
 | |
|                         wp[timestamp_len-1+str_len-2]=' ';
 | |
|                 }
 | |
|                 wp[timestamp_len-1+str_len-1]='\n';
 | |
|                 blockbuf_unregister(bb);
 | |
| 
 | |
|                 /**
 | |
|                  * disable because cross-blockbuffer locking either causes deadlock
 | |
|                  * or run out of memory blocks.
 | |
|                  */
 | |
|                 if (spread_down && false) {
 | |
|                         /**
 | |
|                          * Write to target log. If spread_down == true, then
 | |
|                          * write also to all logs with greater logfile id.
 | |
|                          * LOGFILE_ERROR   = 1,
 | |
|                          * LOGFILE_MESSAGE = 2,
 | |
|                          * LOGFILE_TRACE   = 4,
 | |
|                          * LOGFILE_DEBUG   = 8
 | |
|                          *
 | |
|                          * So everything written to error log will appear in
 | |
|                          * message, trace and debuglog. Messages will be
 | |
|                          * written in trace and debug log.
 | |
|                          */
 | |
|                         for (i=(id<<1); i<=LOGFILE_LAST; i<<=1)
 | |
|                         {
 | |
|                                 /** pointer to write buffer of larger-id log */
 | |
|                                 char* wp_c;
 | |
|                             
 | |
|                                 /**< Check if particular log is enabled */
 | |
|                                 if (!(lm->lm_enabled_logfiles & i))
 | |
|                                 {
 | |
|                                         continue;
 | |
|                                 }
 | |
|                                 /**
 | |
|                                  * Seek write position and register to block
 | |
|                                  * buffer. Then print formatted string to
 | |
|                                  * write position.
 | |
|                                  */
 | |
|                                 wp_c = blockbuf_get_writepos(
 | |
|                                         &bb_c,
 | |
|                                         (logfile_id_t)i,
 | |
|                                         timestamp_len-1+str_len,
 | |
|                                         flush);
 | |
|                                 /**
 | |
|                                  * Copy original string from block buffer to
 | |
|                                  * other logs' block buffers.
 | |
|                                  */
 | |
|                                 snprintf(wp_c, timestamp_len+str_len, wp);
 | |
|                             
 | |
|                                 /** remove double line feed */
 | |
|                                 if (wp_c[timestamp_len-1+str_len-2] == '\n')
 | |
|                                 {
 | |
|                                         wp_c[timestamp_len-1+str_len-2]=' ';
 | |
|                                 }
 | |
|                                 wp_c[timestamp_len-1+str_len-1]='\n';
 | |
|                             
 | |
|                                 /** lock-free unregistration, includes flush if
 | |
|                                  * bb_isfull */
 | |
|                                 blockbuf_unregister(bb_c);
 | |
|                         }
 | |
|                 } /* if (spread_down) */
 | |
|         }
 | |
|         
 | |
| return_err:
 | |
|         return err;
 | |
| }
 | |
| 
 | |
| static void blockbuf_register(
 | |
|         blockbuf_t* bb)
 | |
| {
 | |
|         CHK_BLOCKBUF(bb);
 | |
|         ss_dassert(bb->bb_refcount >= 0);
 | |
|         atomic_add(&bb->bb_refcount, 1);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void blockbuf_unregister(
 | |
|         blockbuf_t* bb)
 | |
| {
 | |
|         logfile_t* lf;
 | |
|         
 | |
|         CHK_BLOCKBUF(bb);
 | |
|         ss_dassert(bb->bb_refcount >= 1);
 | |
|         lf = &lm->lm_logfile[bb->bb_fileid];
 | |
|         CHK_LOGFILE(lf);
 | |
|         /**
 | |
|          * if this is the last client in a full buffer, send write request.
 | |
|          */
 | |
|         if (atomic_add(&bb->bb_refcount, -1) == 1 && bb->bb_isfull) {
 | |
|             skygw_message_send(lf->lf_logmes);
 | |
|         }
 | |
|         ss_dassert(bb->bb_refcount >= 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node (write brief function description here) 
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param id - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param str_len - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details List mutex now protects both the list and the contents of it.
 | |
|  * TODO : It should be so that adding and removing nodes of the list is protected
 | |
|  * by the list mutex. Buffer modifications should be protected by buffer
 | |
|  * mutex.
 | |
|  *
 | |
|  */
 | |
| static char* blockbuf_get_writepos(
 | |
|         blockbuf_t** p_bb,
 | |
|         logfile_id_t id,
 | |
|         size_t       str_len,
 | |
|         bool         flush)
 | |
| {
 | |
|         logfile_t*     lf;
 | |
|         mlist_t*       bb_list;
 | |
|         char*          pos = NULL;
 | |
|         mlist_node_t*  node;
 | |
|         blockbuf_t*    bb;
 | |
|         ss_debug(bool  succp;)
 | |
| 
 | |
|             
 | |
|         CHK_LOGMANAGER(lm);
 | |
|         lf = &lm->lm_logfile[id];
 | |
|         CHK_LOGFILE(lf);
 | |
|         bb_list = &lf->lf_blockbuf_list;
 | |
| 
 | |
|         /** Lock list */
 | |
|         simple_mutex_lock(&bb_list->mlist_mutex, true);
 | |
|         CHK_MLIST(bb_list);
 | |
| 
 | |
|         if (bb_list->mlist_nodecount > 0) {
 | |
|             /**
 | |
|              * At least block buffer exists on the list. 
 | |
|              */
 | |
|             node = bb_list->mlist_first;
 | |
|             
 | |
|             /** Loop over blockbuf list to find write position */
 | |
|             while (true) {
 | |
|                 CHK_MLIST_NODE(node);
 | |
| 
 | |
|                 /** Unlock list */
 | |
|                 simple_mutex_unlock(&bb_list->mlist_mutex);
 | |
|                 
 | |
|                 bb = (blockbuf_t *)node->mlnode_data;
 | |
|                 CHK_BLOCKBUF(bb);
 | |
| 
 | |
|                 /** Lock buffer */
 | |
|                 simple_mutex_lock(&bb->bb_mutex, true);
 | |
|                 
 | |
|                 if (bb->bb_isfull || bb->bb_buf_left < str_len) {
 | |
|                     /**
 | |
|                      * This block buffer is too full.
 | |
|                      * Send flush request to file writer thread. This causes
 | |
|                      * flushing all buffers, and (eventually) frees buffer space.
 | |
|                      */
 | |
|                     blockbuf_register(bb);
 | |
|                     bb->bb_isfull = true;
 | |
|                     blockbuf_unregister(bb);
 | |
| 
 | |
|                     /** Unlock buffer */
 | |
|                     simple_mutex_unlock(&bb->bb_mutex);
 | |
| 
 | |
|                     /** Lock list */
 | |
|                     simple_mutex_lock(&bb_list->mlist_mutex, true);
 | |
|                     
 | |
|                     /**
 | |
|                      * If next node exists move forward. Else check if there is
 | |
|                      * space for a new block buffer on the list.
 | |
|                      */
 | |
|                     if (node != bb_list->mlist_last) {
 | |
|                         node = node->mlnode_next;
 | |
|                         continue;
 | |
|                     }
 | |
|                     /**
 | |
|                      * All buffers on the list are full.
 | |
|                      */
 | |
|                     if (bb_list->mlist_nodecount <
 | |
|                         bb_list->mlist_nodecount_max)
 | |
|                     {
 | |
|                         /**
 | |
|                          * New node is created
 | |
|                          */
 | |
|                         bb = blockbuf_init(id);
 | |
|                         CHK_BLOCKBUF(bb);
 | |
| 
 | |
|                         /**
 | |
|                          * Increase version to odd to mark list update active
 | |
|                          * update.
 | |
|                          */
 | |
|                         bb_list->mlist_versno += 1;
 | |
|                         ss_dassert(bb_list->mlist_versno%2 == 1);
 | |
|                         
 | |
|                         ss_debug(succp =)
 | |
|                             mlist_add_data_nomutex(bb_list, bb);
 | |
|                         ss_dassert(succp);
 | |
| 
 | |
|                         /**
 | |
|                          * Increase version to even to mark completion of update.
 | |
|                          */
 | |
|                         bb_list->mlist_versno += 1;
 | |
|                         ss_dassert(bb_list->mlist_versno%2 == 0);
 | |
|                     } else {
 | |
|                         /**
 | |
|                          * List and buffers are full.
 | |
|                          * Reset to the beginning of the list, and wait until
 | |
|                          * there is a block buffer with enough space.
 | |
|                          */
 | |
|                         simple_mutex_unlock(&bb_list->mlist_mutex);
 | |
|                         simple_mutex_lock(&bb_list->mlist_mutex, true);
 | |
| 
 | |
|                         node = bb_list->mlist_first;
 | |
|                         continue;
 | |
|                     }
 | |
|                 } else {
 | |
|                     /**
 | |
|                      * There is space for new log string.
 | |
|                      */
 | |
|                     break;
 | |
|                 }
 | |
|             } /** while (true) */
 | |
|         } else {
 | |
|             /**
 | |
|              * Create the first block buffer to logfile's blockbuf list.
 | |
|              */
 | |
|             bb = blockbuf_init(id);
 | |
|             CHK_BLOCKBUF(bb);
 | |
| 
 | |
|             /** Lock buffer */
 | |
|             simple_mutex_lock(&bb->bb_mutex, true);
 | |
|             /**
 | |
|              * Increase version to odd to mark list update active update.
 | |
|              */
 | |
|             bb_list->mlist_versno += 1;
 | |
|             ss_dassert(bb_list->mlist_versno%2 == 1);
 | |
| 
 | |
|             ss_debug(succp =)mlist_add_data_nomutex(bb_list, bb);
 | |
|             ss_dassert(succp);
 | |
| 
 | |
|             /**
 | |
|              * Increase version to even to mark completion of update.
 | |
|              */
 | |
|             bb_list->mlist_versno += 1;
 | |
|             ss_dassert(bb_list->mlist_versno%2 == 0);
 | |
|             
 | |
|             /** Unlock list */
 | |
|             simple_mutex_unlock(&bb_list->mlist_mutex);
 | |
|         } /* if (bb_list->mlist_nodecount > 0) */
 | |
|         
 | |
|         ss_dassert(pos == NULL);
 | |
|         ss_dassert(!(bb->bb_isfull || bb->bb_buf_left < str_len));
 | |
|         ss_dassert(bb_list->mlist_nodecount <= bb_list->mlist_nodecount_max);
 | |
| 
 | |
|         /**
 | |
|          * Registration to blockbuf adds reference for the write operation.
 | |
|          * It indicates that client has allocated space from the buffer,
 | |
|          * but not written yet. As long as refcount > 0 buffer can't be
 | |
|          * written to disk.
 | |
|          */
 | |
|         blockbuf_register(bb);
 | |
|         *p_bb = bb;
 | |
|         /**
 | |
|          * At this point list mutex is held and bb points to a node with
 | |
|          * enough space available. pos is not yet set.
 | |
|          */
 | |
|         pos = &bb->bb_buf[bb->bb_buf_used];
 | |
|         bb->bb_buf_used += str_len;
 | |
|         bb->bb_buf_left -= str_len;
 | |
|         
 | |
|         ss_dassert(pos >= &bb->bb_buf[0] &&
 | |
|                    pos <= &bb->bb_buf[MAX_LOGSTRLEN-str_len]);
 | |
|         
 | |
|         /** read checkmark */
 | |
|         /** TODO: add buffer overflow checkmark
 | |
|         chk_val = (int)bb->bb_buf[bb->bb_buf_used-count_len];
 | |
|         ss_dassert(chk_val == bb->bb_strcount);
 | |
|         */
 | |
|         
 | |
|         /** TODO : write next checkmark
 | |
|         bb->bb_strcount += 1;
 | |
|         memcpy(&bb->bb_buf[bb->bb_buf_used], &bb->bb_strcount, count_len);
 | |
|         bb->bb_buf_used += count_len;
 | |
|         bb->bb_buf_left -= count_len;
 | |
|         */
 | |
|         
 | |
|         /**
 | |
|          * If flush flag is set, set buffer full. As a consequence, no-one
 | |
|          * can write to it before it is flushed to disk.
 | |
|          */
 | |
|         bb->bb_isfull = (flush == true ? true : bb->bb_isfull);
 | |
|         
 | |
|         /** Unlock buffer */
 | |
|         simple_mutex_unlock(&bb->bb_mutex);
 | |
|         return pos;
 | |
| }
 | |
|             
 | |
| 
 | |
| 
 | |
| 
 | |
| static blockbuf_t* blockbuf_init(
 | |
|         logfile_id_t id)
 | |
| {
 | |
|         blockbuf_t* bb;
 | |
| 
 | |
|         bb = (blockbuf_t *)calloc(1, sizeof(blockbuf_t));
 | |
|         bb->bb_fileid = id;
 | |
| #if defined(SS_DEBUG)
 | |
|         bb->bb_chk_top = CHK_NUM_BLOCKBUF;
 | |
|         bb->bb_chk_tail = CHK_NUM_BLOCKBUF;
 | |
| #endif
 | |
|         simple_mutex_init(&bb->bb_mutex, "Blockbuf mutex");
 | |
|         bb->bb_buf_left = MAX_LOGSTRLEN;
 | |
|         bb->bb_buf_size = MAX_LOGSTRLEN;
 | |
| 
 | |
|         CHK_BLOCKBUF(bb);
 | |
|         return bb;
 | |
| }
 | |
| 
 | |
| 
 | |
| int skygw_log_enable(
 | |
|         logfile_id_t id)
 | |
| {
 | |
|         bool err = 0;
 | |
| 
 | |
|         if (!logmanager_register(true)) {
 | |
|             //fprintf(stderr, "ERROR: Can't register to logmanager\n");
 | |
|             err = -1;
 | |
|             goto return_err;
 | |
|         }
 | |
|         CHK_LOGMANAGER(lm);
 | |
| 
 | |
|         if (logfile_set_enabled(id, true)) {
 | |
|                 lm->lm_enabled_logfiles |= id;
 | |
|                 /**
 | |
|                  * Set global variable
 | |
|                  */
 | |
|                 lm_enabled_logfiles_bitmask = lm->lm_enabled_logfiles;
 | |
|         }
 | |
|         
 | |
|         logmanager_unregister();
 | |
| return_err:
 | |
|         return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| int skygw_log_disable(
 | |
|         logfile_id_t id)
 | |
| {
 | |
|         bool err = 0;
 | |
| 
 | |
|         if (!logmanager_register(true)) {
 | |
|             //fprintf(stderr, "ERROR: Can't register to logmanager\n");
 | |
|             err = -1;
 | |
|             goto return_err;
 | |
|         }
 | |
|         CHK_LOGMANAGER(lm);
 | |
| 
 | |
|         if (logfile_set_enabled(id, false)) {
 | |
|             lm->lm_enabled_logfiles &= ~id;
 | |
|             /**
 | |
|              * Set global variable
 | |
|              */
 | |
|             lm_enabled_logfiles_bitmask = lm->lm_enabled_logfiles;
 | |
|         }
 | |
| 
 | |
|         logmanager_unregister();
 | |
| return_err:
 | |
|         return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool logfile_set_enabled(
 | |
|         logfile_id_t id,
 | |
|         bool         val)
 | |
| {
 | |
|         char*        logstr;
 | |
|         va_list      notused;
 | |
|         bool         oldval;
 | |
|         bool         succp = false;
 | |
|         int          err = 0;
 | |
|         logfile_t*   lf;
 | |
|         
 | |
|         CHK_LOGMANAGER(lm);
 | |
|         
 | |
|         if (id < LOGFILE_FIRST || id > LOGFILE_LAST) {
 | |
|             char* errstr = "Invalid logfile id argument.";
 | |
|             /**
 | |
|              * invalid id, since we don't have logfile yet.
 | |
|              */
 | |
|             err = logmanager_write_log(LOGFILE_ERROR,
 | |
|                                        true,
 | |
|                                        false,
 | |
|                                        false,
 | |
|                                        strlen(errstr)+1,
 | |
|                                        errstr,
 | |
|                                        notused);
 | |
|             if (err != 0) {
 | |
|                 fprintf(stderr,
 | |
|                         "* Writing to logfile %s failed.\n",
 | |
|                         STRLOGID(LOGFILE_ERROR));
 | |
|             }
 | |
|             ss_dassert(false);            
 | |
|             goto return_succp;
 | |
|         }
 | |
|         lf = &lm->lm_logfile[id];
 | |
|         CHK_LOGFILE(lf);
 | |
| 
 | |
|         if (val) {
 | |
|             logstr = strdup("---\tLogging is enabled\t--");
 | |
|         } else {
 | |
|             logstr = strdup("---\tLogging is disabled\t--");
 | |
|         }
 | |
|         oldval = lf->lf_enabled;
 | |
|         lf->lf_enabled = val;
 | |
|         err = logmanager_write_log(id,
 | |
|                                    true,
 | |
|                                    false,
 | |
|                                    false,
 | |
|                                    strlen(logstr)+1,
 | |
|                                    logstr,
 | |
|                                    notused);
 | |
|         free(logstr);
 | |
|         
 | |
|         if (err != 0) {
 | |
|             lf->lf_enabled = oldval;
 | |
|             fprintf(stderr,
 | |
|                     "logfile_set_enabled failed. Writing notification to logfile %s "
 | |
|                     "failed.\n ",
 | |
|                     STRLOGID(id));
 | |
|             goto return_succp;
 | |
|         }
 | |
|         succp = true;
 | |
| return_succp:
 | |
|         return succp;
 | |
| }
 | |
| 
 | |
| 
 | |
| int skygw_log_write_flush(
 | |
|         logfile_id_t  id,
 | |
|         char*         str,
 | |
|         ...)
 | |
| {
 | |
|         int     err = 0;
 | |
|         va_list valist;
 | |
|         size_t  len;
 | |
| 
 | |
|         if (!logmanager_register(true)) {
 | |
|             //fprintf(stderr, "ERROR: Can't register to logmanager\n");
 | |
|             err = -1;
 | |
|             goto return_err;
 | |
|         }
 | |
|         CHK_LOGMANAGER(lm);
 | |
| 
 | |
|         /**
 | |
|          * If particular log is disabled only unregister and return.
 | |
|          */
 | |
|         if (!(lm->lm_enabled_logfiles & id)) {
 | |
|             err = 1;
 | |
|             goto return_unregister;
 | |
|         }
 | |
|         /**
 | |
|          * Find out the length of log string (to be formatted str).
 | |
|          */
 | |
|         va_start(valist, str);
 | |
|         len = vsnprintf(NULL, 0, str, valist);
 | |
|         va_end(valist);
 | |
|         /**
 | |
|          * Add one for line feed.
 | |
|          */
 | |
|         len += 1;
 | |
|         /**
 | |
|          * Write log string to buffer and add to file write list.
 | |
|          */
 | |
|         va_start(valist, str);
 | |
|         err = logmanager_write_log(id, true, true, true, len, str, valist);
 | |
|         va_end(valist);
 | |
| 
 | |
|         if (err != 0) {
 | |
|             fprintf(stderr, "skygw_log_write_flush failed.\n");
 | |
|             goto return_unregister;
 | |
|         }
 | |
| 
 | |
| return_unregister:
 | |
|         logmanager_unregister();
 | |
| return_err:
 | |
|         return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| int skygw_log_write(
 | |
|         logfile_id_t  id,
 | |
|         char*         str,
 | |
|         ...)
 | |
| {
 | |
|         int     err = 0;
 | |
|         va_list valist;
 | |
|         size_t  len;
 | |
|         
 | |
|         if (!logmanager_register(true)) {
 | |
|             //fprintf(stderr, "ERROR: Can't register to logmanager\n");
 | |
|             err = -1;
 | |
|             goto return_err;
 | |
|         }
 | |
|         CHK_LOGMANAGER(lm);
 | |
| 
 | |
|         /**
 | |
|          * If particular log is disabled only unregister and return.
 | |
|          */
 | |
|         if (!(lm->lm_enabled_logfiles & id)) {
 | |
|                 err = 1;
 | |
|                 goto return_unregister;
 | |
|         }
 | |
|         /**
 | |
|          * Find out the length of log string (to be formatted str).
 | |
|          */
 | |
|         va_start(valist, str);
 | |
|         len = vsnprintf(NULL, 0, str, valist);
 | |
|         va_end(valist);
 | |
|         /**
 | |
|          * Add one for line feed.
 | |
|          */
 | |
|         len += 1;
 | |
|         /**
 | |
|          * Write log string to buffer and add to file write list.
 | |
|          */
 | |
|         va_start(valist, str);
 | |
|         err = logmanager_write_log(id, false, true, true, len, str, valist);
 | |
|         va_end(valist);
 | |
| 
 | |
|         if (err != 0) {
 | |
|             fprintf(stderr, "skygw_log_write failed.\n");
 | |
|             goto return_unregister;
 | |
|         }
 | |
| 
 | |
| return_unregister:
 | |
|         logmanager_unregister();
 | |
| return_err:
 | |
|         return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| int skygw_log_flush(
 | |
|         logfile_id_t  id)
 | |
| {
 | |
|         int err = 0;
 | |
|         va_list valist; /**< Dummy, must be present but it is not processed */
 | |
|         
 | |
|         if (!logmanager_register(false)) {
 | |
|             ss_dfprintf(stderr,
 | |
|                         "Can't register to logmanager, nothing to flush\n");
 | |
|             goto return_err;
 | |
|         }
 | |
|         CHK_LOGMANAGER(lm);
 | |
|         err = logmanager_write_log(id, true, false, false, 0, NULL, valist);
 | |
| 
 | |
|         if (err != 0) {
 | |
|             fprintf(stderr, "skygw_log_flush failed.\n");
 | |
|             goto return_unregister;
 | |
|         }
 | |
| 
 | |
| return_unregister:
 | |
|         logmanager_unregister();
 | |
| return_err:
 | |
|         return err;
 | |
| }
 | |
| 
 | |
| /** 
 | |
|  * @node Register as a logging client to logmanager. 
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param lmgr - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details Link count modify is protected by mutex.
 | |
|  *
 | |
|  */
 | |
| static bool logmanager_register(
 | |
|         bool writep)
 | |
| {
 | |
|         bool succp = true;
 | |
| 
 | |
|         acquire_lock(&lmlock);
 | |
| 
 | |
|         if (lm == NULL || !lm->lm_enabled) {
 | |
|             /**
 | |
|              * Flush succeeds if logmanager is shut or shutting down.
 | |
|              * returning false so that flusher doesn't access logmanager
 | |
|              * and its members which would probabaly lead to NULL pointer
 | |
|              * reference.
 | |
|              */
 | |
|             if (!writep) {
 | |
|                 succp = false;
 | |
|                 goto return_succp;
 | |
|             }
 | |
| 
 | |
|             ss_dassert(lm == NULL || (lm != NULL && !lm->lm_enabled));
 | |
| 
 | |
|             /**
 | |
|              * Wait until logmanager shut down has completed.
 | |
|              * logmanager is enabled if someone already restarted
 | |
|              * it between latest lock release, and acquire.
 | |
|              */
 | |
|             while (lm != NULL && !lm->lm_enabled) {
 | |
|                 release_lock(&lmlock);
 | |
|                 pthread_yield();
 | |
|                 acquire_lock(&lmlock);
 | |
|             }
 | |
|             
 | |
|             if (lm == NULL) {
 | |
|                 succp = logmanager_init_nomutex(0, NULL);
 | |
|             }
 | |
|         }
 | |
|         /** if logmanager existed or was succesfully restarted, increase link */
 | |
|         if (succp) {
 | |
|             lm->lm_nlinks += 1;
 | |
|         }
 | |
|         
 | |
|     return_succp:
 | |
|         release_lock(&lmlock);        
 | |
|         return succp;
 | |
| }
 | |
| 
 | |
| /** 
 | |
|  * @node Unregister from logmanager. 
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param lmgr - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details Link count modify is protected by mutex.
 | |
|  *
 | |
|  */
 | |
| static void logmanager_unregister(void)
 | |
| {
 | |
|         acquire_lock(&lmlock);
 | |
| 
 | |
|         lm->lm_nlinks -= 1;
 | |
|         ss_dassert(lm->lm_nlinks >= 0);
 | |
| 
 | |
|         release_lock(&lmlock);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node Initialize log file naming parameters from call arguments
 | |
|  * or from default functions in cases where arguments are not provided.
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param fn - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param argc - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param argv - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return pointer to object which is either in RUN or DONE state.
 | |
|  *
 | |
|  * 
 | |
|  * @details Note that input parameter lenghts are checked here.
 | |
|  *
 | |
|  */
 | |
| static bool fnames_conf_init(
 | |
|         fnames_conf_t* fn,
 | |
|         int            argc,
 | |
|         char*          argv[])
 | |
| {
 | |
|         int            opt;
 | |
|         bool           succp = false;
 | |
|         const char*    argstr =
 | |
|                 "-h - help\n"
 | |
|                 "-a <debug prefix>   ............(\"skygw_debug\")\n"
 | |
|                 "-b <debug suffix>   ............(\".log\")\n"
 | |
|                 "-c <trace prefix>   ............(\"skygw_trace\")\n"
 | |
|                 "-d <trace suffix>   ............(\".log\")\n"
 | |
|                 "-e <message prefix> ............(\"skygw_msg\")\n"
 | |
|                 "-f <message suffix> ............(\".log\")\n"
 | |
|                 "-g <error prefix>   ............(\"skygw_err\")\n"
 | |
|                 "-i <error suffix>   ............(\".log\")\n"
 | |
|                 "-j <log path>       ............(\"/tmp\")\n"
 | |
|                 "-l <syslog log file ids> .......(no default)\n"
 | |
|                 "-m <syslog ident>   ............(argv[0])\n"
 | |
|                 "-s <shmem log file ids>  .......(no default)\n";
 | |
| 
 | |
|         /**
 | |
|          * When init_started is set, clean must be done for it.
 | |
|          */
 | |
|         fn->fn_state = INIT;
 | |
| #if defined(SS_DEBUG)
 | |
|         fn->fn_chk_top  = CHK_NUM_FNAMES;
 | |
|         fn->fn_chk_tail = CHK_NUM_FNAMES;
 | |
| #endif
 | |
|         optind = 1; /**<! reset getopt index */
 | |
|         while ((opt = getopt(argc, argv, "+a:b:c:d:e:f:g:h:i:j:l:m:s:")) != -1)
 | |
|         {
 | |
|                 switch (opt) {
 | |
|                 case 'a':
 | |
|                         fn->fn_debug_prefix = strndup(optarg, MAX_PREFIXLEN);
 | |
|                         break;
 | |
| 
 | |
|                 case 'b':
 | |
|                         fn->fn_debug_suffix = strndup(optarg, MAX_SUFFIXLEN);
 | |
|                         break;
 | |
|                 case 'c':
 | |
|                         fn->fn_trace_prefix = strndup(optarg, MAX_PREFIXLEN);
 | |
|                         break;
 | |
| 
 | |
|                 case 'd':
 | |
|                         fn->fn_trace_suffix = strndup(optarg, MAX_SUFFIXLEN);
 | |
|                         break;
 | |
| 
 | |
|                 case 'e':
 | |
|                         fn->fn_msg_prefix = strndup(optarg, MAX_PREFIXLEN);
 | |
|                         break;
 | |
| 
 | |
|                 case 'f':
 | |
|                         fn->fn_msg_suffix = strndup(optarg, MAX_SUFFIXLEN);
 | |
|                         break;
 | |
| 
 | |
|                 case 'g':
 | |
|                         fn->fn_err_prefix = strndup(optarg, MAX_PREFIXLEN);
 | |
|                         break;
 | |
|                     
 | |
|                 case 'i':
 | |
|                         fn->fn_err_suffix = strndup(optarg, MAX_SUFFIXLEN);
 | |
|                         break;
 | |
| 
 | |
|                 case 'j':
 | |
|                         fn->fn_logpath = strndup(optarg, MAX_PATHLEN);
 | |
|                         break;
 | |
| 
 | |
|                 case 'l':
 | |
|                         /** record list of log file ids for syslogged */
 | |
|                         if (syslog_id_str != NULL)
 | |
|                         {
 | |
|                                 free (syslog_id_str);
 | |
|                         }
 | |
|                         syslog_id_str = optarg;
 | |
|                         break;
 | |
| 
 | |
|                 case 'm':
 | |
|                         /**
 | |
|                          * Identity string for syslog printing, needs '-l'
 | |
|                          * to be effective.
 | |
|                          */
 | |
|                         syslog_ident_str = optarg;
 | |
|                         break;
 | |
|                         
 | |
|                 case 's':
 | |
|                         /** record list of log file ids for later use */
 | |
|                         shmem_id_str = optarg;
 | |
|                         break;
 | |
|                 case 'h':
 | |
|                 default:
 | |
|                         fprintf(stderr,
 | |
|                                 "\nSupported arguments are (default)\n%s\n",
 | |
|                                 argstr);
 | |
|                         goto return_conf_init;
 | |
|                 } /** switch (opt) */
 | |
|         }
 | |
|         /** If log file name is not specified in call arguments, use default. */
 | |
|         fn->fn_debug_prefix = (fn->fn_debug_prefix == NULL) ?
 | |
|                 strdup(get_debug_prefix_default()) : fn->fn_debug_prefix;
 | |
|         fn->fn_debug_suffix = (fn->fn_debug_suffix == NULL) ?
 | |
|                 strdup(get_debug_suffix_default()) : fn->fn_debug_suffix;
 | |
|         fn->fn_trace_prefix = (fn->fn_trace_prefix == NULL) ?
 | |
|                 strdup(get_trace_prefix_default()) : fn->fn_trace_prefix;
 | |
|         fn->fn_trace_suffix = (fn->fn_trace_suffix == NULL) ?
 | |
|                 strdup(get_trace_suffix_default()) : fn->fn_trace_suffix;
 | |
|         fn->fn_msg_prefix   = (fn->fn_msg_prefix == NULL) ?
 | |
|                 strdup(get_msg_prefix_default()) : fn->fn_msg_prefix;
 | |
|         fn->fn_msg_suffix   = (fn->fn_msg_suffix == NULL) ?
 | |
|                 strdup(get_msg_suffix_default()) : fn->fn_msg_suffix;
 | |
|         fn->fn_err_prefix   = (fn->fn_err_prefix == NULL) ?
 | |
|                 strdup(get_err_prefix_default()) : fn->fn_err_prefix;
 | |
|         fn->fn_err_suffix   = (fn->fn_err_suffix == NULL) ?
 | |
|                 strdup(get_err_suffix_default()) : fn->fn_err_suffix;
 | |
|         fn->fn_logpath      = (fn->fn_logpath == NULL) ?
 | |
|                 strdup(get_logpath_default()) : fn->fn_logpath;
 | |
| 
 | |
|         /** Set identity string for syslog if it is not set in config.*/
 | |
|         syslog_ident_str =
 | |
|             (syslog_ident_str == NULL ?
 | |
|              (argv == NULL ? strdup(program_invocation_short_name) :  
 | |
|               strdup(*argv)) :
 | |
|              syslog_ident_str);
 | |
|         
 | |
|         /* ss_dfprintf(stderr, "\n\n\tCommand line : ");
 | |
|            for (i=0; i<argc; i++) {
 | |
|            ss_dfprintf(stderr, "%s ", argv[i]);
 | |
|            }
 | |
|            ss_dfprintf(stderr, "\n");*/
 | |
| 
 | |
|         fprintf(stderr,
 | |
|                 "Error log     :\t%s/%s1%s\n"
 | |
|                 "Message log   :\t%s/%s1%s\n"
 | |
|                 "Trace log     :\t%s/%s1%s\n"
 | |
|                 "Debug log     :\t%s/%s1%s\n\n",
 | |
|                 fn->fn_logpath,
 | |
|                 fn->fn_err_prefix,
 | |
|                 fn->fn_err_suffix,
 | |
|                 fn->fn_logpath,
 | |
|                 fn->fn_msg_prefix,
 | |
|                 fn->fn_msg_suffix,
 | |
|                 fn->fn_logpath,
 | |
|                 fn->fn_trace_prefix,
 | |
|                 fn->fn_trace_suffix,
 | |
|                 fn->fn_logpath,
 | |
|                 fn->fn_debug_prefix,
 | |
|                 fn->fn_debug_suffix);
 | |
|         
 | |
|         succp = true;
 | |
|         fn->fn_state = RUN;
 | |
|         CHK_FNAMES_CONF(fn);
 | |
|         
 | |
| return_conf_init:
 | |
|         if (!succp) {
 | |
|                 fnames_conf_done(fn);
 | |
|         }
 | |
|         ss_dassert(fn->fn_state == RUN || fn->fn_state == DONE);
 | |
|         return succp;
 | |
| }
 | |
| 
 | |
| 
 | |
| static char* fname_conf_get_prefix(
 | |
|         fnames_conf_t* fn,
 | |
|         logfile_id_t   id)
 | |
| {
 | |
|         CHK_FNAMES_CONF(fn);
 | |
|         ss_dassert(id >= LOGFILE_FIRST && id <= LOGFILE_LAST);
 | |
| 
 | |
|         switch (id) {
 | |
|         case LOGFILE_DEBUG:
 | |
|                 return strdup(fn->fn_debug_prefix);
 | |
|                 break;
 | |
|                 
 | |
|             case LOGFILE_TRACE:
 | |
|                 return strdup(fn->fn_trace_prefix);
 | |
|                 break;
 | |
| 
 | |
|             case LOGFILE_MESSAGE:
 | |
|                 return strdup(fn->fn_msg_prefix);
 | |
|                 break;
 | |
| 
 | |
|             case LOGFILE_ERROR:
 | |
|                 return strdup(fn->fn_err_prefix);
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 return NULL;
 | |
|         }
 | |
| }
 | |
| 
 | |
| static char* fname_conf_get_suffix(
 | |
|         fnames_conf_t* fn,
 | |
|         logfile_id_t   id)
 | |
| {
 | |
|         CHK_FNAMES_CONF(fn);
 | |
|         ss_dassert(id >= LOGFILE_FIRST && id <= LOGFILE_LAST);
 | |
| 
 | |
|         switch (id) {
 | |
|             case LOGFILE_DEBUG:
 | |
|                 return strdup(fn->fn_debug_suffix);
 | |
|                 break;
 | |
| 
 | |
|         case LOGFILE_TRACE:
 | |
|                 return strdup(fn->fn_trace_suffix);
 | |
|                 break;
 | |
| 
 | |
|             case LOGFILE_MESSAGE:
 | |
|                 return strdup(fn->fn_msg_suffix);
 | |
|                 break;
 | |
| 
 | |
|             case LOGFILE_ERROR:
 | |
|                 return strdup(fn->fn_err_suffix);
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 return NULL;
 | |
|         }
 | |
| }
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node Calls logfile initializer for each logfile.
 | |
|  * 
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param lm - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details If logfile is supposed to be located to shared memory
 | |
|  * it is specified here. In the case of shared memory file, a soft
 | |
|  * link is created to log directory.
 | |
|  *
 | |
|  * Motivation is performance. LOGFILE_DEBUG, for example, has so much data
 | |
|  * that writing it to disk slows execution down remarkably.
 | |
|  *
 | |
|  */
 | |
| static bool logfiles_init(
 | |
|         logmanager_t* lm)
 | |
| {
 | |
|         bool  succp = true;
 | |
|         int   lid   = LOGFILE_FIRST;
 | |
|         int   i     = 0;
 | |
|         bool  store_shmem;
 | |
|         bool  write_syslog;
 | |
| 
 | |
|         if (syslog_id_str != NULL)
 | |
|         {
 | |
|                 openlog(syslog_ident_str, LOG_PID | LOG_NDELAY, LOG_USER);
 | |
|         }
 | |
|         /**
 | |
|          * Initialize log files, pass softlink flag if necessary.
 | |
|          */
 | |
|         while (lid<=LOGFILE_LAST && succp) {
 | |
|                 /**
 | |
|                  * Check if the file is stored in shared memory. If so,
 | |
|                  * a symbolic link will be created to log directory.
 | |
|                  */
 | |
|                 if (shmem_id_str != NULL &&
 | |
|                     strcasestr(shmem_id_str,
 | |
|                                STRLOGID(logfile_id_t(lid))) != NULL)
 | |
|                 {
 | |
|                         store_shmem = true;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                         store_shmem = false;
 | |
|                 }
 | |
|                 /**
 | |
|                  * Check if file is also written to syslog.
 | |
|                  */
 | |
|                 if (syslog_id_str != NULL &&
 | |
|                     strcasestr(syslog_id_str,
 | |
|                                STRLOGID(logfile_id_t(lid))) != NULL)
 | |
|                 {
 | |
|                         write_syslog = true;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                         write_syslog = false;
 | |
|                 }
 | |
|                 succp = logfile_init(&lm->lm_logfile[lid],
 | |
|                                      (logfile_id_t)lid,
 | |
|                                      lm,
 | |
|                                      store_shmem,
 | |
|                                      write_syslog);
 | |
|                
 | |
|                 if (!succp) {
 | |
|                         fprintf(stderr, "Initializing logfiles failed\n");
 | |
|                         break;
 | |
|                 }
 | |
|                 lid <<= 1;
 | |
|                 i += 1;
 | |
|         }
 | |
|         return succp;
 | |
| }
 | |
| 
 | |
| static void logfile_flush(
 | |
|         logfile_t* lf)
 | |
| {
 | |
|         CHK_LOGFILE(lf);
 | |
|         acquire_lock(&lf->lf_spinlock);
 | |
|         lf->lf_flushflag = true;
 | |
|         release_lock(&lf->lf_spinlock);
 | |
|         skygw_message_send(lf->lf_logmes);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node Combine all name parts from left to right. 
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param parts - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param seqno - in, use
 | |
|  *          specifies the the sequence number which will be added as a part
 | |
|  *          of full file name.
 | |
|  *          seqno == -1 indicates that sequence number won't be used.
 | |
|  *
 | |
|  * @param seqnoidx - in, use
 | |
|  *          Specifies the seqno position in the 'array' of name parts.
 | |
|  *          If seqno == -1 seqnoidx will be set -1 as well.
 | |
|  *
 | |
|  * @return Pointer to filename, of NULL if failed.
 | |
|  *
 | |
|  * 
 | |
|  * @details (write detailed description here)
 | |
|  *
 | |
|  */
 | |
| static char* form_full_file_name(
 | |
|         strpart_t* parts,
 | |
|         int        seqno,
 | |
|         int        seqnoidx)
 | |
| {
 | |
|         int    i;
 | |
|         size_t s;
 | |
|         size_t fnlen;
 | |
|         char*  filename = NULL;
 | |
|         char*  seqnostr = NULL;
 | |
|         strpart_t* p;
 | |
| 
 | |
|         if (seqno != -1)
 | |
|         {
 | |
|                 s = UINTLEN(seqno);
 | |
|                 seqnostr = (char *)malloc((int)s+1);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|                 /**
 | |
|                  * These magic numbers are needed to indicate this and
 | |
|                  * in subroutines that sequence number is not used.
 | |
|                  */
 | |
|                 s = 0;
 | |
|                 seqnoidx = -1;
 | |
|         }
 | |
|         
 | |
|         if (parts == NULL || parts->sp_string == NULL) {
 | |
|                 goto return_filename;
 | |
|         }
 | |
|         /**
 | |
|          * Length of path, file name, separating slash, sequence number and
 | |
|          * terminating character.
 | |
|          */
 | |
|         fnlen = sizeof('/') + s + sizeof('\0');
 | |
|         p = parts;
 | |
|         /** Calculate the combined length of all parts put together */
 | |
|         while (p->sp_string != NULL)
 | |
|         {
 | |
|                 fnlen += strnlen(p->sp_string, NAME_MAX);
 | |
|                 
 | |
|                 if (p->sp_next == NULL)
 | |
|                 {
 | |
|                         break;
 | |
|                 }
 | |
|                 p = p->sp_next;
 | |
|         }
 | |
|                 
 | |
|         if (fnlen > NAME_MAX) {
 | |
|                 fprintf(stderr, "Error : Too long file name= %d.\n", (int)fnlen);
 | |
|                 goto return_filename;
 | |
|         }
 | |
| 
 | |
|         filename = (char*)calloc(1, fnlen);
 | |
|         snprintf(seqnostr, s+1, "%d", seqno);
 | |
| 
 | |
|         for (i=0, p=parts; p->sp_string != NULL; i++, p=p->sp_next)
 | |
|         {
 | |
|                 if (i == seqnoidx)
 | |
|                 {
 | |
|                         strcat(filename, seqnostr);
 | |
|                 }
 | |
|                 strcat(filename, p->sp_string);
 | |
| 
 | |
|                 if (p->sp_next == NULL)
 | |
|                 {
 | |
|                         break;
 | |
|                 }
 | |
|         }
 | |
|         
 | |
| return_filename:
 | |
|         if (seqnostr != NULL) free(seqnostr);
 | |
|         return filename;
 | |
| }
 | |
| 
 | |
| /** 
 | |
|  * @node Allocate new buffer where argument string with a slash is copied.
 | |
|  * Original string buffer is freed.
 | |
|  * added. 
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param str - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details (write detailed description here)
 | |
|  *
 | |
|  */
 | |
| static char* add_slash(
 | |
|         char* str)
 | |
| {
 | |
|         char*  p = str;
 | |
|         size_t plen = strlen(p);
 | |
|         
 | |
|         /** Add slash if missing */
 | |
|         if (p[plen-1] != '/')
 | |
|         {
 | |
|                 str = (char *)malloc(plen+2);
 | |
|                 snprintf(str, plen+2, "%s/", p);
 | |
|                 free(p);
 | |
|         }
 | |
|         return str;
 | |
| }
 | |
| 
 | |
| /** 
 | |
|  * @node Check if the file exists in the local file system and if it does,
 | |
|  * whether it is writable. 
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param filename - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param writable - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details Note, that an space character is written to the end of file.
 | |
|  * TODO: recall what was the reason for not succeeding with simply
 | |
|  * calling access, and fstat. vraa 26.11.13
 | |
|  *
 | |
|  */
 | |
| static bool file_exists_and_is_writable(
 | |
|         char* filename,
 | |
|         bool* writable)
 | |
| {
 | |
|         int  fd;
 | |
|         bool exists = true;
 | |
| 
 | |
|         if (filename == NULL)
 | |
|         {
 | |
|                 exists = false;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|                 fd = open(filename, O_CREAT|O_EXCL, S_IRWXU);
 | |
| 
 | |
|                 /** file exist */
 | |
|                 if (fd == -1)
 | |
|                 {
 | |
|                         /** Open file and write a byte for test */
 | |
|                         fd = open(filename, O_CREAT|O_RDWR, S_IRWXU|S_IRWXG);
 | |
|                         
 | |
|                         if (fd != -1)
 | |
|                         {
 | |
|                                 char c = ' ';
 | |
|                                 if (write(fd, &c, 1) == 1)
 | |
|                                 {                                        
 | |
|                                         *writable = true;
 | |
|                                 }                          
 | |
|                                 close(fd);
 | |
|                         }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                         close(fd);
 | |
|                         unlink(filename);
 | |
|                         exists = false;
 | |
|                 }
 | |
|         }
 | |
|         return exists;
 | |
| }
 | |
| 
 | |
| static bool file_is_symlink(
 | |
|         char* filename)
 | |
| {
 | |
|         int  rc;
 | |
|         bool succp = false;
 | |
|         struct stat b;
 | |
| 
 | |
|         if (filename != NULL)
 | |
|         {
 | |
|                 rc = lstat(filename, &b);
 | |
| 
 | |
|                 if (rc != -1 && S_ISLNK(b.st_mode)) {
 | |
|                         succp = true;
 | |
|                 }
 | |
|         }
 | |
|         return succp;
 | |
| }
 | |
|         
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node Initialize logfile structure. Form log file name, and optionally
 | |
|  * link name. Create block buffer for logfile.
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param logfile - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param logfile_id - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param logmanager - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param store_shmem - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return true if succeed, false otherwise
 | |
|  *
 | |
|  * 
 | |
|  * @details (write detailed description here)
 | |
|  *
 | |
|  */
 | |
| static bool logfile_init(
 | |
|         logfile_t*     logfile,
 | |
|         logfile_id_t   logfile_id,
 | |
|         logmanager_t*  logmanager,
 | |
|         bool           store_shmem,
 | |
|         bool           write_syslog)
 | |
| {
 | |
|         bool           succp = false;
 | |
|         fnames_conf_t* fn = &logmanager->lm_fnames_conf;
 | |
|         /** string parts of which the file is composed of */
 | |
|         strpart_t      strparts[3];
 | |
|         bool           namecreatefail;
 | |
|         bool           nameconflicts;
 | |
|         bool           writable;
 | |
| 
 | |
|         logfile->lf_state = INIT;
 | |
| #if defined(SS_DEBUG)
 | |
|         logfile->lf_chk_top = CHK_NUM_LOGFILE;
 | |
|         logfile->lf_chk_tail = CHK_NUM_LOGFILE;
 | |
| #endif
 | |
|         logfile->lf_logmes = logmanager->lm_logmes;
 | |
|         logfile->lf_id = logfile_id;
 | |
|         logfile->lf_name_prefix = fname_conf_get_prefix(fn, logfile_id);
 | |
|         logfile->lf_name_suffix = fname_conf_get_suffix(fn, logfile_id);
 | |
|         logfile->lf_npending_writes = 0;
 | |
|         logfile->lf_name_seqno = 1;
 | |
|         logfile->lf_lmgr = logmanager;
 | |
|         logfile->lf_flushflag = false;
 | |
|         logfile->lf_spinlock = 0;
 | |
|         logfile->lf_store_shmem = store_shmem;
 | |
|         logfile->lf_write_syslog = write_syslog;
 | |
|         logfile->lf_buf_size = MAX_LOGSTRLEN;
 | |
|         logfile->lf_enabled = logmanager->lm_enabled_logfiles & logfile_id;
 | |
|         /**
 | |
|          * strparts is an array but next pointers are used to walk through
 | |
|          * the list of string parts.
 | |
|          */
 | |
|         strparts[0].sp_next = &strparts[1];
 | |
|         strparts[1].sp_next = &strparts[2];
 | |
|         strparts[2].sp_next = NULL;
 | |
|         
 | |
|         strparts[1].sp_string = logfile->lf_name_prefix;
 | |
|         strparts[2].sp_string = logfile->lf_name_suffix;
 | |
|         /**
 | |
|          * If file is stored in shared memory in /dev/shm, a link
 | |
|          * pointing to shm file is created and located to the file
 | |
|          * directory.
 | |
|          */
 | |
|         if (store_shmem) {
 | |
|                 logfile->lf_filepath = strdup(shm_pathname);
 | |
|                 logfile->lf_linkpath = strdup(fn->fn_logpath);
 | |
|                 logfile->lf_linkpath = add_slash(logfile->lf_linkpath);
 | |
|         } else {
 | |
|                 logfile->lf_filepath = strdup(fn->fn_logpath);
 | |
|         }
 | |
|         logfile->lf_filepath = add_slash(logfile->lf_filepath);
 | |
|                         
 | |
|         do {
 | |
|                 namecreatefail = false;
 | |
|                 nameconflicts  = false;
 | |
|                 
 | |
|                 strparts[0].sp_string = logfile->lf_filepath;
 | |
|                 /**
 | |
|                  * Create name for log file. Seqno is added between prefix &
 | |
|                  * suffix (index == 2)
 | |
|                  */
 | |
|                 logfile->lf_full_file_name =
 | |
|                         form_full_file_name(strparts, logfile->lf_name_seqno, 2);
 | |
|                 
 | |
|                 if (store_shmem) {
 | |
|                         strparts[0].sp_string = logfile->lf_linkpath;
 | |
|                         /**
 | |
|                          * Create name for link file
 | |
|                          */
 | |
|                         logfile->lf_full_link_name =
 | |
|                                 form_full_file_name(strparts,
 | |
|                                                     logfile->lf_name_seqno,
 | |
|                                                     2);        
 | |
|                 }
 | |
|                 /**
 | |
|                  * At least one of the files couldn't be created. Increase
 | |
|                  * sequence number and retry until succeeds.
 | |
|                  */
 | |
|                 if (logfile->lf_full_file_name == NULL ||
 | |
|                     (store_shmem && logfile->lf_full_link_name == NULL))
 | |
|                 {
 | |
|                         namecreatefail = true;
 | |
|                         goto file_create_fail;
 | |
|                 }
 | |
| 
 | |
|                 /**
 | |
|                  * If file exists but is different type, create fails and
 | |
|                  * new, increased sequence number is added to file name.
 | |
|                  */
 | |
|                 if (file_exists_and_is_writable(logfile->lf_full_file_name,
 | |
|                                                 &writable))
 | |
|                 {
 | |
|                         if (!writable ||
 | |
|                             file_is_symlink(logfile->lf_full_file_name))
 | |
|                         {
 | |
|                                 nameconflicts = true;
 | |
|                                 goto file_create_fail;
 | |
|                         }
 | |
|                 }
 | |
|                 
 | |
|                 if (store_shmem)
 | |
|                 {
 | |
|                         writable = false;
 | |
| 
 | |
|                         if (file_exists_and_is_writable(
 | |
|                                     logfile->lf_full_link_name,
 | |
|                                     &writable))
 | |
|                         {
 | |
|                                 if (!writable ||
 | |
|                                     !file_is_symlink(logfile->lf_full_link_name))
 | |
|                                 {
 | |
|                                         nameconflicts = true;
 | |
|                                         goto file_create_fail;
 | |
|                                 }
 | |
|                         }
 | |
|                 }
 | |
|         file_create_fail:
 | |
|                 if (namecreatefail || nameconflicts)
 | |
|                 {
 | |
|                         logfile->lf_name_seqno += 1;
 | |
| 
 | |
|                         if (logfile->lf_full_file_name != NULL)
 | |
|                         {
 | |
|                                 free(logfile->lf_full_file_name);
 | |
|                                 logfile->lf_full_file_name = NULL;
 | |
|                         }
 | |
|                         if (logfile->lf_full_link_name != NULL)
 | |
|                         {
 | |
|                                 free(logfile->lf_full_link_name);
 | |
|                                 logfile->lf_full_link_name = NULL;
 | |
|                         }
 | |
| 
 | |
|                 }
 | |
|         } while (namecreatefail || nameconflicts);
 | |
|         /**
 | |
|          * Create a block buffer list for log file. Clients' writes go to buffers
 | |
|          * from where separate log flusher thread writes them to disk.
 | |
|          */
 | |
|         if (mlist_init(&logfile->lf_blockbuf_list,
 | |
|                        NULL,
 | |
|                        strdup("logfile block buffer list"),
 | |
|                        NULL,
 | |
|                        MAXNBLOCKBUFS) == NULL)
 | |
|         {
 | |
|                 ss_dfprintf(stderr,
 | |
|                             "Initializing logfile blockbuf list "
 | |
|                             "failed\n");
 | |
|                 logfile_free_memory(logfile);
 | |
|                 goto return_with_succp;
 | |
|         }
 | |
|         succp = true;
 | |
|         logfile->lf_state = RUN;
 | |
|         CHK_LOGFILE(logfile);
 | |
|                 
 | |
| return_with_succp:
 | |
|         if (!succp) {
 | |
|                 logfile_done(logfile);
 | |
|         }
 | |
|         ss_dassert(logfile->lf_state == RUN ||
 | |
|                    logfile->lf_state == DONE);
 | |
|         return succp;
 | |
| }
 | |
|         
 | |
| /** 
 | |
|  * @node Flush logfile and free memory allocated for it.  
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param lf - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return void
 | |
|  *
 | |
|  * 
 | |
|  * @details Operation is not protected. it is assumed that no one tries
 | |
|  * to call logfile functions when logfile_done is called.
 | |
|  *
 | |
|  * It is also assumed that filewriter doesn't exist anymore.
 | |
|  *
 | |
|  * Logfile access could be protected by using exit flag and rwlock to protect
 | |
|  * flag read/write. Lock would be held during log write operation by clients.
 | |
|  *
 | |
|  */
 | |
| static void logfile_done(
 | |
|         logfile_t* lf)
 | |
| {
 | |
|         switch(lf->lf_state) {
 | |
|             case RUN:
 | |
|                 CHK_LOGFILE(lf);
 | |
|                 ss_dassert(lf->lf_npending_writes == 0);
 | |
|             case INIT:
 | |
|                 mlist_done(&lf->lf_blockbuf_list);
 | |
|                 logfile_free_memory(lf);
 | |
|                 lf->lf_state = DONE;
 | |
|             case DONE:
 | |
|             case UNINIT:
 | |
|             default:
 | |
|                 break;
 | |
|         }
 | |
| }
 | |
| 
 | |
| static void logfile_free_memory(
 | |
|         logfile_t* lf)
 | |
| {
 | |
|         if (lf->lf_filepath != NULL)       free(lf->lf_filepath);
 | |
|         if (lf->lf_linkpath != NULL)       free(lf->lf_linkpath);
 | |
|         if (lf->lf_name_prefix != NULL)    free(lf->lf_name_prefix);
 | |
|         if (lf->lf_name_suffix != NULL)    free(lf->lf_name_suffix);
 | |
|         if (lf->lf_full_link_name != NULL) free(lf->lf_full_link_name);
 | |
|         if (lf->lf_full_file_name != NULL) free(lf->lf_full_file_name);
 | |
| }
 | |
| 
 | |
| /** 
 | |
|  * @node Initialize filewriter struct to a given address
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param fw - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details (write detailed description here)
 | |
|  *
 | |
|  */
 | |
| static bool filewriter_init(
 | |
|         logmanager_t*    logmanager,
 | |
|         filewriter_t*    fw,
 | |
|         skygw_message_t* clientmes,
 | |
|         skygw_message_t* logmes)
 | |
| {
 | |
|         bool         succp = false;
 | |
|         logfile_t*   lf;
 | |
|         logfile_id_t id;
 | |
|         int          i;
 | |
|         char*        start_msg_str;
 | |
|         
 | |
|         CHK_LOGMANAGER(logmanager);
 | |
| 
 | |
|         fw->fwr_state = INIT;
 | |
| #if defined(SS_DEBUG)
 | |
|         fw->fwr_chk_top = CHK_NUM_FILEWRITER;
 | |
|         fw->fwr_chk_tail = CHK_NUM_FILEWRITER;
 | |
| #endif
 | |
|         fw->fwr_logmgr = logmanager;
 | |
|         /** Message from filewriter to clients */
 | |
|         fw->fwr_logmes = logmes;
 | |
|         /** Message from clients to filewriter */
 | |
|         fw->fwr_clientmes = clientmes;
 | |
| 
 | |
|         if (fw->fwr_logmes == NULL || fw->fwr_clientmes == NULL) {
 | |
|                 goto return_succp;
 | |
|         }
 | |
|         for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1) {
 | |
|                 id = (logfile_id_t)i;
 | |
|                 lf = logmanager_get_logfile(logmanager, id);
 | |
| 
 | |
|                 if (lf->lf_store_shmem)
 | |
|                 {
 | |
|                         /** Create symlink pointing to log file */
 | |
|                         fw->fwr_file[id] = skygw_file_init(lf->lf_full_file_name,
 | |
|                                                            lf->lf_full_link_name);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                         /** Create normal disk-resident log file */
 | |
|                         fw->fwr_file[id] = skygw_file_init(lf->lf_full_file_name,
 | |
|                                                            NULL);
 | |
|                 }
 | |
|             
 | |
|                 if (fw->fwr_file[id] == NULL) {
 | |
|                         goto return_succp;
 | |
|                 }
 | |
|                 if (lf->lf_enabled) {
 | |
|                         start_msg_str = strdup("---\tLogging is enabled.\n");
 | |
|                 } else {
 | |
|                         start_msg_str = strdup("---\tLogging is disabled.\n");
 | |
|                 }
 | |
|                 skygw_file_write(fw->fwr_file[id],
 | |
|                                  (void *)start_msg_str,
 | |
|                                  strlen(start_msg_str),
 | |
|                                  true);
 | |
|                 free(start_msg_str);
 | |
|         }
 | |
|         fw->fwr_state = RUN;
 | |
|         CHK_FILEWRITER(fw);
 | |
|         succp = true;
 | |
| return_succp:
 | |
|         if (!succp) {
 | |
|                 filewriter_done(fw);
 | |
|         }
 | |
|         ss_dassert(fw->fwr_state == RUN || fw->fwr_state == DONE);
 | |
|         return succp;
 | |
| }
 | |
| 
 | |
| static void filewriter_done(
 | |
|     filewriter_t* fw)
 | |
| {
 | |
|         int           i;
 | |
|         logfile_id_t  id;
 | |
| 
 | |
|         switch(fw->fwr_state) {
 | |
|             case RUN:
 | |
|                 CHK_FILEWRITER(fw);
 | |
|             case INIT:
 | |
|                 fw->fwr_logmes = NULL;
 | |
|                 fw->fwr_clientmes = NULL;            
 | |
|                 for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i++) {
 | |
|                     id = (logfile_id_t)i;
 | |
|                     skygw_file_done(fw->fwr_file[id]);
 | |
|                 }
 | |
|                 fw->fwr_state = DONE;
 | |
|             case DONE:
 | |
|             case UNINIT:
 | |
|             default:
 | |
|                 break;
 | |
|         }
 | |
| }
 | |
| 
 | |
| 
 | |
| /** 
 | |
|  * @node Writes block buffers of logfiles to physical log files on disk.
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param data - thread context, skygw_thread_t
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return 
 | |
|  *
 | |
|  * 
 | |
|  * @details Waits until receives wake-up message. Scans through block buffer
 | |
|  * lists of each logfile object.
 | |
|  *
 | |
|  * Block buffer is written to log file if
 | |
|  * 1. bb_isfull == true,
 | |
|  * 2. logfile object's lf_flushflag == true, or
 | |
|  * 3. skygw_thread_must_exit returns true.
 | |
|  * 
 | |
|  * Log file is flushed (fsync'd) in cases #2 and #3.
 | |
|  *
 | |
|  * Concurrency control : block buffer is accessed by file writer (this) and
 | |
|  * log clients. File writer reads and sets each logfile object's flushflag
 | |
|  * with spinlock. Another protected section is when block buffer's metadata is
 | |
|  * read, and optionally the write operation.
 | |
|  *
 | |
|  * When block buffer is marked full, logfile's flushflag is set, other
 | |
|  * log clients don't try to access buffer(s). There may, however, be
 | |
|  * active writes, on the block in parallel with file writer operations.
 | |
|  * Each active write corresponds to bb_refcounter values.
 | |
|  * On the other hand, file writer doesn't process the block buffer before
 | |
|  * its refcount is zero.
 | |
|  *
 | |
|  * Every log file obj. has its own block buffer (linked) list.
 | |
|  * List is accessed by log clients, which add nodes if necessary, and
 | |
|  * by file writer which traverses the list and accesses block buffers
 | |
|  * included in list nodes.
 | |
|  * List modifications are protected with version numbers.
 | |
|  * Before modification, version is increased by one to be odd. After the
 | |
|  * completion, it is increased again to even. List can be read only when
 | |
|  * version is even and read is consistent only if version hasn't changed
 | |
|  * during the read.
 | |
|  */
 | |
| static void* thr_filewriter_fun(
 | |
|         void* data)
 | |
| {
 | |
|         skygw_thread_t* thr;
 | |
|         filewriter_t*   fwr;
 | |
|         skygw_file_t*   file;
 | |
|         logfile_t*      lf;
 | |
|             
 | |
|         mlist_t*      bb_list;
 | |
|         blockbuf_t*   bb;
 | |
|         mlist_node_t* node;
 | |
|         int           i;
 | |
|         bool          flush_blockbuf;   /**< flush single block buffer. */
 | |
|         bool          flush_logfile;    /**< flush logfile */
 | |
|         bool          flushall_logfiles;/**< flush all logfiles */
 | |
|         size_t        vn1;
 | |
|         size_t        vn2;
 | |
| 
 | |
|         thr = (skygw_thread_t *)data;
 | |
|         fwr = (filewriter_t *)skygw_thread_get_data(thr);
 | |
|         CHK_FILEWRITER(fwr);
 | |
|         ss_debug(skygw_thread_set_state(thr, THR_RUNNING));
 | |
| 
 | |
|         /** Inform log manager about the state. */
 | |
|         skygw_message_send(fwr->fwr_clientmes);
 | |
| 
 | |
|         while(!skygw_thread_must_exit(thr)) {
 | |
|                 /**
 | |
|                  * Wait until new log arrival message appears.
 | |
|                  * Reset message to avoid redundant calls.
 | |
|                  */
 | |
|                 skygw_message_wait(fwr->fwr_logmes);
 | |
| 
 | |
|                 flushall_logfiles = skygw_thread_must_exit(thr);
 | |
|             
 | |
|                 /** Process all logfiles which have buffered writes. */
 | |
|                 for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1) {
 | |
|                 retry_flush_on_exit:
 | |
|                         /**
 | |
|                          * Get file pointer of current logfile.
 | |
|                          */
 | |
|                         file = fwr->fwr_file[i];
 | |
|                         lf = &lm->lm_logfile[(logfile_id_t)i];
 | |
| 
 | |
|                         /**
 | |
|                          * read and reset logfile's flushflag
 | |
|                          */
 | |
|                         acquire_lock(&lf->lf_spinlock);
 | |
|                         flush_logfile = lf->lf_flushflag;
 | |
|                         lf->lf_flushflag = false;
 | |
|                         release_lock(&lf->lf_spinlock);
 | |
|                 
 | |
|                         /**
 | |
|                          * get logfile's block buffer list
 | |
|                          */
 | |
|                         bb_list = &lf->lf_blockbuf_list;
 | |
| #if defined(SS_DEBUG)
 | |
|                         simple_mutex_lock(&bb_list->mlist_mutex, true);
 | |
|                         CHK_MLIST(bb_list);
 | |
|                         simple_mutex_unlock(&bb_list->mlist_mutex);
 | |
| #endif
 | |
|                         node = bb_list->mlist_first;
 | |
|                 
 | |
|                         while (node != NULL) {
 | |
|                                 CHK_MLIST_NODE(node);
 | |
|                                 bb = (blockbuf_t *)node->mlnode_data;
 | |
|                                 CHK_BLOCKBUF(bb);
 | |
| 
 | |
|                                 /** Lock block buffer */
 | |
|                                 simple_mutex_lock(&bb->bb_mutex, true);
 | |
| 
 | |
|                                 flush_blockbuf = bb->bb_isfull;
 | |
|                     
 | |
|                                 if (bb->bb_buf_used != 0 &&
 | |
|                                     (flush_blockbuf ||
 | |
|                                      flush_logfile ||
 | |
|                                      flushall_logfiles))
 | |
|                                 {
 | |
|                                         /**
 | |
|                                          * buffer is at least half-full
 | |
|                                          * -> write to disk
 | |
|                                          */
 | |
|                                         while(bb->bb_refcount > 0) {
 | |
|                                                 simple_mutex_unlock(
 | |
|                                                         &bb->bb_mutex);
 | |
|                                                 simple_mutex_lock(
 | |
|                                                         &bb->bb_mutex,
 | |
|                                                         true);
 | |
|                                         }
 | |
| 
 | |
|                                         skygw_file_write(file,
 | |
|                                                          (void *)bb->bb_buf,
 | |
|                                                          bb->bb_buf_used,
 | |
|                                                          (flush_logfile ||
 | |
|                                                           flushall_logfiles));
 | |
|                                         /**
 | |
|                                          * Reset buffer's counters and mark
 | |
|                                          * not full.
 | |
|                                          */
 | |
|                                         bb->bb_buf_left = bb->bb_buf_size;
 | |
|                                         bb->bb_buf_used = 0;
 | |
|                                         memset(bb->bb_buf, 0, bb->bb_buf_size);
 | |
|                                         bb->bb_isfull = false;
 | |
|                                 }
 | |
|                                 /** Release lock to block buffer */
 | |
|                                 simple_mutex_unlock(&bb->bb_mutex);
 | |
|                     
 | |
|                                 /** Consistent lock-free read on the list */
 | |
|                                 do {
 | |
|                                         while ((vn1 = bb_list->mlist_versno)%2
 | |
|                                                != 0);
 | |
|                                         node = node->mlnode_next;
 | |
|                                         vn2 = bb_list->mlist_versno;
 | |
|                                 } while (vn1 != vn2);
 | |
|                     
 | |
|                         } /* while (node != NULL) */
 | |
| 
 | |
|                         /**
 | |
|                          * Writer's exit flag was set after checking it.
 | |
|                          * Loop is restarted to ensure that all logfiles are
 | |
|                          * flushed.
 | |
|                          */
 | |
|                         if (!flushall_logfiles && skygw_thread_must_exit(thr))
 | |
|                         {
 | |
|                                 flushall_logfiles = true;
 | |
|                                 i = LOGFILE_FIRST;
 | |
|                                 goto retry_flush_on_exit;
 | |
|                         }
 | |
|                 } /* for */
 | |
|         } /* while (!skygw_thread_must_exit) */
 | |
|         
 | |
|         ss_debug(skygw_thread_set_state(thr, THR_STOPPED));
 | |
|         /** Inform log manager that file writer thread has stopped. */
 | |
|         skygw_message_send(fwr->fwr_clientmes);
 | |
|         return NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void fnames_conf_done(
 | |
|         fnames_conf_t* fn)
 | |
| {
 | |
|         switch (fn->fn_state) {
 | |
|             case RUN:
 | |
|                 CHK_FNAMES_CONF(fn);
 | |
|             case INIT:
 | |
|                 fnames_conf_free_memory(fn);
 | |
|                 fn->fn_state = DONE;
 | |
|             case DONE:
 | |
|             case UNINIT:
 | |
|             default:
 | |
|                 break;
 | |
|         }
 | |
| }
 | |
| 
 | |
| 
 | |
| static void fnames_conf_free_memory(
 | |
|         fnames_conf_t* fn)
 | |
| {
 | |
|         if (fn->fn_debug_prefix != NULL) free(fn->fn_debug_prefix);
 | |
|         if (fn->fn_debug_suffix!= NULL)  free(fn->fn_debug_suffix);
 | |
|         if (fn->fn_trace_prefix != NULL) free(fn->fn_trace_prefix);
 | |
|         if (fn->fn_trace_suffix!= NULL)  free(fn->fn_trace_suffix);
 | |
|         if (fn->fn_msg_prefix != NULL)   free(fn->fn_msg_prefix);
 | |
|         if (fn->fn_msg_suffix != NULL)   free(fn->fn_msg_suffix);
 | |
|         if (fn->fn_err_prefix != NULL)   free(fn->fn_err_prefix);
 | |
|         if (fn->fn_err_suffix != NULL)   free(fn->fn_err_suffix);
 | |
|         if (fn->fn_logpath != NULL)      free(fn->fn_logpath);
 | |
| }
 |