/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 - * * * @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 characters * to wp. * Returned timestamp_len doesn't include terminating null. */ 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, safe_str_len, str, valist); } else { snprintf(wp+timestamp_len, safe_str_len, str); } /** write to syslog */ if (lf->lf_write_syslog) { switch(id) { case LOGFILE_ERROR: syslog(LOG_ERR, wp+timestamp_len); break; case LOGFILE_MESSAGE: syslog(LOG_NOTICE, wp+timestamp_len); break; default: break; } } /** remove double line feed */ if (wp[timestamp_len+str_len-2] == '\n') { wp[timestamp_len+str_len-2]=' '; } wp[timestamp_len+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 - * * * @param str_len - * * * @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 - * * * @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 - * * * @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 - * * * @param argc - * * * @param argv - * * * @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 ............(\"skygw_debug\")\n" "-b ............(\".log\")\n" "-c ............(\"skygw_trace\")\n" "-d ............(\".log\")\n" "-e ............(\"skygw_msg\")\n" "-f ............(\".log\")\n" "-g ............(\"skygw_err\")\n" "-i ............(\".log\")\n" "-j ............(\"/tmp\")\n" "-l .......(no default)\n" "-m ............(argv[0])\n" "-s .......(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; /**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; ifn_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 - * * * @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 - * * * @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 - * * * @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 - * * * @param writable - * * * @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 - * * * @param logfile_id - * * * @param logmanager - * * * @param store_shmem - * * * @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 - * * * @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 - * * * @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 * * * @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); }