MaxScale/log_manager/log_manager.cc
2015-05-12 16:50:37 +01:00

3174 lines
89 KiB
C++

/*
* This file is distributed as part of the MariaDB Corporation MaxScale. 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 MariaDB Corporation Ab 2013-2014
*/
#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>
#include "atomic.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;
#if defined(SS_DEBUG)
static int write_index;
static int block_start_index;
static int prevval;
static simple_mutex_t msg_mutex;
#endif
static int highprec = 0;
static int do_syslog = 1;
static int do_maxscalelog = 1;
static int use_stdout = 0;
/**
* 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;
/**
* Thread-specific struct variable for storing current session id and currently
* enabled log files for the session.
*/
__thread log_info_t tls_log_info = {0, 0};
/**
* Global counter for each log file type. It indicates for how many sessions
* each log type is currently enabled.
*/
ssize_t log_ses_count[LOGFILE_LAST] = {0};
/**
* BUFSIZ comes from the system. It equals with block size or
* its multiplication.
*/
#define MAX_LOGSTRLEN BUFSIZ
/**
* Path to directory in which all files are stored to shared memory
* by the OS.
*/
const char* shm_pathname_prefix = "/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;
static bool flushall_flag;
static bool flushall_started_flag;
static bool flushall_done_flag;
/** 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;
blockbuf_state_t bb_state; /**State of the block buffer*/
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;
size_t lf_buf_size;
bool lf_flushflag;
bool lf_rotateflag;
int lf_spinlock; /**< lf_flushflag & lf_rotateflag */
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 void logfile_rotate(logfile_t* lf);
static bool logfile_create(logfile_t* lf);
static bool logfile_open_file(filewriter_t* fw, logfile_t* lf);
static char* form_full_file_name(strpart_t* parts, logfile_t* lf, int seqnoidx);
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,
bool rotate,
size_t len,
const char* str,
va_list valist);
static blockbuf_t* blockbuf_init(logfile_id_t id);
static void blockbuf_node_done(void* bb_data);
static char* blockbuf_get_writepos(
blockbuf_t** p_bb,
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 check_file_and_path(
char* filename,
bool* writable,
bool do_log);
static bool file_is_symlink(char* filename);
static int skygw_log_disable_raw(logfile_id_t id, bool emergency); /*< no locking */
static int find_last_seqno(strpart_t* parts, int seqno, int seqnoidx);
void flushall_logfiles(bool flush);
bool thr_flushall_check();
#include "../core/atomic.c"
const char* get_suffix_default(void)
{
return ".log";
}
const char* get_debug_prefix_default(void)
{
return "debug";
}
const char* get_debug_suffix_default(void)
{
return get_suffix_default();
}
const char* get_trace_prefix_default(void)
{
return "trace";
}
const char* get_trace_suffix_default(void)
{
return get_suffix_default();
}
const char* get_msg_prefix_default(void)
{
return "messages";
}
const char* get_msg_suffix_default(void)
{
return get_suffix_default();
}
const char* get_err_prefix_default(void)
{
return "error";
}
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 (lm == NULL)
{
err = 1;
goto return_succp;
}
#if defined(SS_DEBUG)
lm->lm_chk_top = CHK_NUM_LOGMANAGER;
lm->lm_chk_tail = CHK_NUM_LOGMANAGER;
write_index = 0;
block_start_index = 0;
prevval = -1;
simple_mutex_init(&msg_mutex, "Message mutex");
#endif
lm->lm_clientmes = skygw_message_init();
lm->lm_logmes = skygw_message_init();
if (lm->lm_clientmes == NULL ||
lm->lm_logmes == NULL)
{
err = 1;
goto return_succp;
}
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;
if(!do_syslog)
{
free(syslog_id_str);
syslog_id_str = NULL;
}
/** Initialize configuration including log file naming info */
if (!fnames_conf_init(fn, argc, argv))
{
err = 1;
goto return_succp;
}
/** Initialize logfiles */
if(!logfiles_init(lm))
{
err = 1;
goto return_succp;
}
/**
* Set global variable
*/
lm_enabled_logfiles_bitmask = lm->lm_enabled_logfiles;
/**
* Initialize filewriter data and open the log file
* for each log file type.
*/
if (!filewriter_init(lm, fw, lm->lm_clientmes, lm->lm_logmes))
{
err = 1;
goto return_succp;
}
/** Initialize and start filewriter thread */
fw->fwr_thread = skygw_thread_init("filewriter thr",
thr_filewriter_fun,
(void *)fw);
if (fw->fwr_thread == NULL)
{
err = 1;
goto return_succp;
}
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, "*\n* Error : Initializing log manager failed.\n*\n");
}
return succp;
}
/**
* Initializes log managing routines in MariaDB Corporation MaxScale.
*
* Parameters:
* @param argc number of arguments in argv array
*
* @param argv arguments array
*
* @return true if succeed, otherwise false
*
*/
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;
}
/**
* Release resources of log manager.
*
* Lock must have been acquired before calling
* this function.
*/
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;
}
/**
* This function is provided for atexit() system function.
*/
void skygw_logmanager_exit(void)
{
skygw_logmanager_done();
}
/**
* End execution of log manager
*
* 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;
}
/**
* Finds write position from block buffer for log string and writes there.
*
* Parameters:
*
* @param id logfile object identifier
* @param flush indicates whether log string must be written to disk
* immediately
* @param use_valist does write involve formatting of the string and use of
* valist argument
* @param spread_down if true, log string is spread to all logs having
* larger id.
* @param rotate if set, closes currently open log file and opens a
* new one
* @param str_len length of formatted string
* @param str string to be written to log
* @param valist variable-length argument list for formatting the string
*
* @return 0 if succeed, -1 otherwise
*
*/
static int logmanager_write_log(
logfile_id_t id,
bool flush,
bool use_valist,
bool spread_down,
bool rotate,
size_t str_len,
const char* str,
va_list valist)
{
logfile_t* lf;
char* wp;
int err = 0;
blockbuf_t* bb;
blockbuf_t* bb_c;
size_t timestamp_len;
int i;
CHK_LOGMANAGER(lm);
if (id < LOGFILE_FIRST || id > LOGFILE_LAST) {
const char* errstr = "Invalid logfile id argument.";
/**
* invalid id, since we don't have logfile yet.
*/
err = logmanager_write_log(LOGFILE_ERROR,
true,
false,
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, operation is either flush or rotate.
*/
if (str == NULL)
{
if (flush)
{
logfile_flush(lf); /*< wakes up file writer */
}
else if (rotate)
{
logfile_rotate(lf); /*< wakes up file writer */
}
}
else
{
/** Length of string that will be written, limited by bufsize */
size_t safe_str_len;
/** Length of session id */
size_t sesid_str_len;
size_t cmplen = 0;
/**
* 2 braces, 2 spaces and terminating char
* If session id is stored to tls_log_info structure, allocate
* room for session id too.
*/
if (id == LOGFILE_TRACE && tls_log_info.li_sesid != 0)
{
sesid_str_len = 5*sizeof(char)+get_decimal_len(tls_log_info.li_sesid);
}
else
{
sesid_str_len = 0;
}
if(highprec)
timestamp_len = get_timestamp_len_hp();
else
timestamp_len = get_timestamp_len();
cmplen = sesid_str_len > 0 ? sesid_str_len - sizeof(char) : 0;
/** Find out how much can be safely written with current block size */
if (timestamp_len-sizeof(char)+cmplen+str_len > lf->lf_buf_size)
{
safe_str_len = lf->lf_buf_size;
}
else
{
safe_str_len = timestamp_len-sizeof(char)+cmplen+str_len;
}
/**
* Seek write position and register to block buffer.
* Then print formatted string to write position.
*/
#if defined (SS_LOG_DEBUG)
{
char *copy,*tok;
int tokval;
simple_mutex_lock(&msg_mutex,true);
copy = strdup(str);
tok = strtok(copy,"|");
tok = strtok(NULL,"|");
if(strstr(str,"message|") && tok)
{
tokval = atoi(tok);
if(prevval > 0)
{
ss_dassert(tokval == (prevval + 1));
}
prevval = tokval;
}
free(copy);
simple_mutex_unlock(&msg_mutex);
}
#endif
/** Book space for log string from buffer */
if(do_maxscalelog)
{
wp = blockbuf_get_writepos(&bb,
id,
safe_str_len,
flush);
}
else
{
wp = (char*)malloc(sizeof(char)*(timestamp_len-sizeof(char)+cmplen+str_len + 1));
}
#if defined (SS_LOG_DEBUG)
{
sprintf(wp,"[msg:%d]",atomic_add(&write_index,1));
safe_str_len -= strlen(wp);
wp += strlen(wp);
}
#endif
/**
* Write timestamp with at most <timestamp_len> characters
* to wp.
* Returned timestamp_len doesn't include terminating null.
*/
if(highprec)
timestamp_len = snprint_timestamp_hp(wp, timestamp_len);
else
timestamp_len = snprint_timestamp(wp, timestamp_len);
if (sesid_str_len != 0)
{
/**
* Write session id
*/
snprintf(wp+timestamp_len,
sesid_str_len,
"[%lu] ",
tls_log_info.li_sesid);
sesid_str_len -= 1; /*< don't calculate terminating char anymore */
}
/**
* Write next string to overwrite terminating null character
* of the timestamp string.
*/
if (use_valist) {
vsnprintf(wp+timestamp_len+sesid_str_len,
safe_str_len-timestamp_len-sesid_str_len,
str,
valist);
} else {
snprintf(wp+timestamp_len+sesid_str_len,
safe_str_len-timestamp_len-sesid_str_len,
"%s",
str);
}
/** write to syslog */
if (lf->lf_write_syslog)
{
switch(id) {
case LOGFILE_ERROR:
syslog(LOG_ERR, "%s", wp+timestamp_len);
break;
case LOGFILE_MESSAGE:
syslog(LOG_NOTICE, "%s", wp+timestamp_len);
break;
default:
break;
}
}
/** remove double line feed */
if (wp[safe_str_len-2] == '\n')
{
wp[safe_str_len-2]=' ';
}
wp[safe_str_len-1] = '\n';
if(do_maxscalelog)
{
blockbuf_unregister(bb);
}
else
{
free(wp);
}
/**
* 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, "%s", 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_state == BB_FULL */
blockbuf_unregister(bb_c);
}
} /* if (spread_down) */
} /* if (str == NULL) */
return_err:
return err;
}
/**
* Register writer to a block buffer. When reference counter is non-zero the
* flusher thread doesn't write the block to disk.
*
* @param bb block buffer
*/
static void blockbuf_register(
blockbuf_t* bb)
{
CHK_BLOCKBUF(bb);
ss_dassert(bb->bb_refcount >= 0);
atomic_add(&bb->bb_refcount, 1);
}
/**
* Unregister writer from block buffer. If the buffer got filled up and there
* are no other registered writers anymore, notify the flusher thread.
*
* @param bb block buffer
*/
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_state == BB_FULL) {
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_state == BB_FULL || 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_state = BB_FULL;
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 if(bb->bb_state == BB_CLEARED){
/**
*Move the cleared buffer to the end of the list if it is the first one in the list
*/
simple_mutex_unlock(&bb->bb_mutex);
simple_mutex_lock(&bb_list->mlist_mutex, true);
if(node == bb_list->mlist_first)
{
if(bb_list->mlist_nodecount > 1 &&
node != bb_list->mlist_last){
bb_list->mlist_last->mlnode_next = bb_list->mlist_first;
bb_list->mlist_first = bb_list->mlist_first->mlnode_next;
bb_list->mlist_last->mlnode_next->mlnode_next = NULL;
bb_list->mlist_last = bb_list->mlist_last->mlnode_next;
}
ss_dassert(node == bb_list->mlist_last);
simple_mutex_unlock(&bb_list->mlist_mutex);
simple_mutex_lock(&bb->bb_mutex, true);
bb->bb_state = BB_READY;
simple_mutex_unlock(&bb->bb_mutex);
simple_mutex_lock(&bb_list->mlist_mutex, true);
node = bb_list->mlist_first;
}
else
{
if(node->mlnode_next){
node = node->mlnode_next;
}else{
node = bb_list->mlist_first;
}
continue;
}
}else if (bb->bb_state == BB_READY){
/**
* 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_state == BB_FULL || 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_state = (flush == true ? BB_FULL : bb->bb_state);
/** Unlock buffer */
simple_mutex_unlock(&bb->bb_mutex);
return pos;
}
static void blockbuf_node_done(
void* bb_data)
{
blockbuf_t* bb = (blockbuf_t *)bb_data;
simple_mutex_done(&bb->bb_mutex);
}
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;
#if defined(SS_LOG_DEBUG)
sprintf(bb->bb_buf,"[block:%d]",atomic_add(&block_start_index,1));
bb->bb_buf_used += strlen(bb->bb_buf);
bb->bb_buf_left -= strlen(bb->bb_buf);
#endif
CHK_BLOCKBUF(bb);
return bb;
}
int skygw_log_enable(
logfile_id_t id)
{
bool err = 0;
if (!logmanager_register(true))
{
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) /*< no locking */
{
int rc;
rc = skygw_log_disable_raw(id, false);
return rc;
}
static int skygw_log_disable_raw(
logfile_id_t id,
bool emergency) /*< no locking */
{
bool err = 0;
if (!logmanager_register(true))
{
err = -1;
goto return_err;
}
CHK_LOGMANAGER(lm);
if (emergency || 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) {
const char* errstr = "Invalid logfile id argument.";
/**
* invalid id, since we don't have logfile yet.
*/
err = logmanager_write_log(LOGFILE_ERROR,
true,
false,
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(use_stdout == 0)
{
if (val) {
logstr = strdup("---\tLogging to file is enabled\t--");
} else {
logstr = strdup("---\tLogging to file is disabled\t--");
}
oldval = lf->lf_enabled;
lf->lf_enabled = val;
err = logmanager_write_log(id,
true,
false,
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,
const char* str,
...)
{
int i,err = 0;
va_list valist;
size_t len;
if (!logmanager_register(true))
{
err = -1;
goto return_err;
}
CHK_LOGMANAGER(lm);
/**
* Find out the length of log string (to be formatted str).
*/
va_start(valist, str);
len = sizeof(char) * vsnprintf(NULL, 0, str, valist);
va_end(valist);
/**
* Add one for line feed.
*/
len += sizeof(char);
/**
* Write log string to buffer and add to file write list.
*/
for(i = LOGFILE_FIRST;i<=LOGFILE_LAST;i <<=1)
{
/**
* If particular log is disabled in general and it is not enabled for
* the current session, check the next log.
*/
if (!LOG_IS_ENABLED(i) || (i & id) == 0)
{
continue;
}
va_start(valist, str);
err = logmanager_write_log((logfile_id_t)i, true, true, true, false, len, str, valist);
va_end(valist);
if (err != 0) {
fprintf(stderr, "skygw_log_write_flush failed.\n");
break;
}
}
logmanager_unregister();
return_err:
return err;
}
int skygw_log_write(
logfile_id_t id,
const char* str,
...)
{
int i,err = 0;
va_list valist;
size_t len;
if (!logmanager_register(true))
{
err = -1;
goto return_err;
}
CHK_LOGMANAGER(lm);
/**
* If particular log is disabled in general and it is not enabled for
* the current session, then unregister and return.
*/
/**
* 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.
*/
for(i = LOGFILE_FIRST;i<=LOGFILE_LAST;i <<=1)
{
/**
* If particular log is disabled in general and it is not enabled for
* the current session, check the next log.
*/
if (!LOG_IS_ENABLED(i) || (i & id) == 0)
{
continue;
}
va_start(valist, str);
err = logmanager_write_log((logfile_id_t)i, false, true, true, false, len, str, valist);
va_end(valist);
if (err != 0) {
fprintf(stderr, "skygw_log_write failed.\n");
break;
}
}
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, 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;
}
/**
* Replace current logfile with new file with increased sequence number on
* its name.
*/
int skygw_log_rotate(
logfile_id_t id)
{
int err = 0;
logfile_t* lf;
va_list valist; /**< Dummy, must be present but it is not processed */
if (!logmanager_register(false))
{
ss_dfprintf(stderr,
"Can't register to logmanager, rotating failed\n");
goto return_err;
}
CHK_LOGMANAGER(lm);
lf = &lm->lm_logfile[id];
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Log rotation is called for %s.",
lf->lf_full_file_name)));
err = logmanager_write_log(id, false, false, false, true, 0, NULL, valist);
if (err != 0)
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Log file rotation failed for file %s.",
lf->lf_full_file_name)));
fprintf(stderr, "skygw_log_rotate failed.\n");
goto return_unregister;
}
return_unregister:
LOGIF(LM, (skygw_log_write_flush(
LOGFILE_MESSAGE,
"File %s use for log writing..",
lf->lf_full_file_name)));
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"
"-o .......(write logs to stdout)\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:o")) != -1)
{
switch (opt) {
case 'o':
use_stdout = 1;
break;
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(do_syslog)
{
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 */
if(do_syslog)
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");*/
#if defined(NOT_USED)
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);
#endif
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 Log manager pointer
*
* @return succp true if succeed, otherwise false.
*
*
* @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;
/** Open syslog immediately. Print pid of loggind process. */
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, "*\n* Error : Initializing log files 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);
}
/**
* Set rotate flag for a log file and wake up the writer thread which then
* performs the actual rotation task.
*
* @param lf logfile pointer
*/
static void logfile_rotate(
logfile_t* lf)
{
CHK_LOGFILE(lf);
acquire_lock(&lf->lf_spinlock);
lf->lf_rotateflag = true;
release_lock(&lf->lf_spinlock);
skygw_message_send(lf->lf_logmes);
}
/**
* Forms complete path name for logfile and tests that the file doesn't conflict
* with any existing file and it is writable.
*
* @param lf logfile pointer
*
* @return true if succeed, false if failed
*
* @note Log file openings are not TOCTOU-safe. It is not likely that
* multiple copies of same files are opened in parallel but it is possible by
* using log manager in parallel with multiple processes and by configuring
* log manager to use same directories among those processes.
*/
static bool logfile_create(
logfile_t* lf)
{
bool namecreatefail;
bool nameconflicts;
bool store_shmem;
bool writable;
bool succp;
strpart_t spart[3]; /*< string parts of which the file is composed of */
/**
* sparts is an array but next pointers are used to walk through
* the list of string parts.
*/
spart[0].sp_next = &spart[1];
spart[1].sp_next = &spart[2];
spart[2].sp_next = NULL;
spart[1].sp_string = lf->lf_name_prefix;
spart[2].sp_string = lf->lf_name_suffix;
store_shmem = lf->lf_store_shmem;
do {
namecreatefail = false;
nameconflicts = false;
spart[0].sp_string = lf->lf_filepath;
/**
* Create name for log file. Seqno is added between prefix &
* suffix (index == 2)
*/
lf->lf_full_file_name =
form_full_file_name(spart, lf, 2);
if (store_shmem)
{
spart[0].sp_string = lf->lf_linkpath;
/**
* Create name for link file
*/
lf->lf_full_link_name = form_full_file_name(spart,lf,2);
}
/**
* At least one of the files couldn't be created. Increase
* sequence number and retry until succeeds.
*/
if (lf->lf_full_file_name == NULL ||
(store_shmem && lf->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 (check_file_and_path(lf->lf_full_file_name, &writable, true))
{
/** Found similarly named file which isn't writable */
if (!writable || file_is_symlink(lf->lf_full_file_name))
{
nameconflicts = true;
goto file_create_fail;
}
}
else
{
/**
* Opening the file failed for some other reason than
* existing non-writable file. Shut down.
*/
if (!writable)
{
succp = false;
goto return_succp;
}
}
if (store_shmem)
{
if (check_file_and_path(lf->lf_full_link_name, &writable, true))
{
/** Found similarly named link which isn't writable */
if (!writable)
{
nameconflicts = true;
}
}
else
{
/**
* Opening the file failed for some other reason than
* existing non-writable file. Shut down.
*/
if (!writable)
{
succp = false;
goto return_succp;
}
}
}
file_create_fail:
if (namecreatefail || nameconflicts)
{
lf->lf_name_seqno += 1;
if (lf->lf_full_file_name != NULL)
{
free(lf->lf_full_file_name);
lf->lf_full_file_name = NULL;
}
if (lf->lf_full_link_name != NULL)
{
free(lf->lf_full_link_name);
lf->lf_full_link_name = NULL;
}
}
} while (namecreatefail || nameconflicts);
succp = true;
return_succp:
return succp;
}
/**
* Opens a log file and writes header to the beginning of it. File name, FILE*,
* and file descriptor are stored to skygw_file_t struct which is stored in
* filewriter strcuture passed as parameter.
*
* @param fw filewriter pointer
* @param lf logfile pointer
*
* @return true if succeed; the resulting skygw_file_t is written in filewriter,
* false if failed.
*
*/
static bool logfile_open_file(
filewriter_t* fw,
logfile_t* lf)
{
bool succp;
char* start_msg_str;
int err;
if(use_stdout)
{
fw->fwr_file[lf->lf_id] = skygw_file_alloc (
lf->lf_full_file_name);
fw->fwr_file[lf->lf_id]->sf_file = stdout;
}
else if (lf->lf_store_shmem)
{
/** Create symlink pointing to log file */
fw->fwr_file[lf->lf_id] = skygw_file_init(
lf->lf_full_file_name,
lf->lf_full_link_name);
}
else
{
/** Create normal disk-resident log file */
fw->fwr_file[lf->lf_id] = skygw_file_init(
lf->lf_full_file_name,
NULL);
}
if (fw->fwr_file[lf->lf_id] == NULL)
{
fprintf(stderr,
"Error : opening logfile %s failed.\n",
lf->lf_full_file_name);
succp = false;
goto return_succp;
}
if(use_stdout == 0)
{
if (lf->lf_enabled)
{
start_msg_str = strdup("---\tLogging is enabled.\n");
}
else
{
start_msg_str = strdup("---\tLogging is disabled.\n");
}
err = skygw_file_write(fw->fwr_file[lf->lf_id],
(void *)start_msg_str,
strlen(start_msg_str),
true);
if (err != 0)
{
fprintf(stderr,
"Error : writing to file %s failed due to %d, %s. "
"Exiting MaxScale.\n",
lf->lf_full_file_name,
err,
strerror(err));
succp = false;
goto return_succp;
}
free(start_msg_str);
}
succp = true;
return_succp:
return succp;
}
/**
* @node Combine all name parts from left to right.
*
* Parameters:
* @param parts
*
* @param seqno 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 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.
*
*/
static char* form_full_file_name(
strpart_t* parts,
logfile_t* lf,
int seqnoidx)
{
int i;
int seqno;
size_t s;
size_t fnlen;
char* filename = NULL;
char* seqnostr = NULL;
strpart_t* p;
if (lf->lf_name_seqno != -1)
{
int file_sn;
int link_sn = 0;
char* tmp = parts[0].sp_string;
file_sn = find_last_seqno(parts, lf->lf_name_seqno, seqnoidx);
if (lf->lf_linkpath != NULL)
{
tmp = parts[0].sp_string;
parts[0].sp_string = lf->lf_linkpath;
link_sn = find_last_seqno(parts, lf->lf_name_seqno, seqnoidx);
parts[0].sp_string = tmp;
}
lf->lf_name_seqno = MAX(file_sn, link_sn);
seqno = lf->lf_name_seqno;
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;
seqno = lf->lf_name_seqno;
}
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);
if (seqnostr != NULL)
{
snprintf(seqnostr, s+1, "%d", seqno);
}
for (i=0, p=parts; p->sp_string != NULL; i++, p=p->sp_next)
{
if (seqnostr != NULL && i == seqnoidx)
{
strcat(filename, seqnostr); /*< add sequence number */
}
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 path and file exist in the local file system and if they do,
* check if they are accessible and writable.
*
* Parameters:
* @param filename file to be checked
*
* @param writable flag indicating whether file was found writable or not
* if writable is NULL, check is skipped.
*
* @return true & writable if file exists and it is writable,
* true & not writable if file exists but it can't be written,
* false & writable if file doesn't exist but directory could be written, and
* false & not writable if directory can't be written.
*
* @details Note, that a 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 check_file_and_path(
char* filename,
bool* writable,
bool do_log)
{
bool exists;
if (filename == NULL)
{
exists = false;
if (writable)
{
*writable = false;
}
}
else
{
if(access(filename,F_OK) == 0)
{
exists = true;
if(access(filename,W_OK) == 0)
{
if(writable)
{
*writable = true;
}
}
else
{
if (do_log && file_is_symlink(filename))
{
fprintf(stderr,
"*\n* Error : Can't access "
"file pointed to by %s due "
"to %s.\n",
filename,
strerror(errno));
}
else if (do_log)
{
fprintf(stderr,
"*\n* Error : Can't access %s due "
"to %s.\n",
filename,
strerror(errno));
}
if(writable)
{
*writable = false;
}
}
}
else
{
exists = false;
if(writable)
{
*writable = true;
}
}
}
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 log file
* @param logfile_id identifier for log file
* @param logmanager log manager pointer
* @param store_shmem flag to indicate whether log is physically written to shmem
* @param write_syslog flag to indicate whether log is also written to syslog
*
* @return true if succeed, false otherwise
*/
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;
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_rotateflag= 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;
/**
* 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)
{
char* c;
pid_t pid = getpid();
int len = strlen(shm_pathname_prefix)+
+ strlen("maxscale.") +
get_decimal_len((size_t)pid) + 1;
c = (char *)calloc(len, sizeof(char));
if (c == NULL)
{
succp = false;
goto return_with_succp;
}
sprintf(c, "%smaxscale.%d", shm_pathname_prefix, pid);
logfile->lf_filepath = c;
if (mkdir(c, S_IRWXU | S_IRWXG) != 0 &&
errno != EEXIST)
{
succp = false;
goto return_with_succp;
}
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);
if (!(succp = logfile_create(logfile)))
{
goto return_with_succp;
}
/**
* 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"),
blockbuf_node_done,
MAXNBLOCKBUFS) == NULL)
{
ss_dfprintf(stderr,
"*\n* Error : Initializing buffers for log files "
"failed.");
logfile_free_memory(logfile);
goto return_with_succp;
}
#if defined(SS_DEBUG)
if (store_shmem && !use_stdout)
{
fprintf(stderr, "%s\t: %s->%s\n",
STRLOGNAME(logfile_id),
logfile->lf_full_link_name,
logfile->lf_full_file_name);
}
else if(!use_stdout)
{
fprintf(stderr, "%s\t: %s\n",
STRLOGNAME(logfile_id),
logfile->lf_full_file_name);
}
#endif
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);
/** fallthrough */
case INIT:
/** Test if list is initialized before freeing it */
if (lf->lf_blockbuf_list.mlist_versno != 0)
{
mlist_done(&lf->lf_blockbuf_list);
}
logfile_free_memory(lf);
lf->lf_state = DONE;
/** fallthrough */
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 data and open the log file for each log file type.
*
* @param logmanager Log manager struct
* @param fw File writer struct
* @param clientmes Messaging from file writer to log manager
* @param logmes Messaging from loggers to file writer thread
*
* @return true if succeed, false if failed
*
*/
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;
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 (!(succp = logfile_open_file(fw, lf)))
{
fprintf(stderr,
"Error : opening log file %s failed. Exiting "
"MaxScale\n",
lf->lf_full_file_name);
goto return_succp;
}
} /*< for */
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;
if(use_stdout)
skygw_file_free(fw->fwr_file[id]);
else
skygw_file_close(fw->fwr_file[id], true);
}
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_state == 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;
blockbuf_state_t flush_blockbuf; /**< flush single block buffer. */
bool flush_logfile; /**< flush logfile */
bool do_flushall = false;
bool rotate_logfile; /*< close current and open new file */
size_t vn1;
size_t vn2;
thr = (skygw_thread_t *)data;
fwr = (filewriter_t *)skygw_thread_get_data(thr);
flushall_logfiles(false);
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);
if(skygw_thread_must_exit(thr)){
flushall_logfiles(true);
}
/** 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.
*/
do_flushall = thr_flushall_check();
file = fwr->fwr_file[i];
lf = &lm->lm_logfile[(logfile_id_t)i];
/**
* read and reset logfile's flush- and rotateflag
*/
acquire_lock(&lf->lf_spinlock);
flush_logfile = lf->lf_flushflag;
rotate_logfile = lf->lf_rotateflag;
lf->lf_flushflag = false;
lf->lf_rotateflag = false;
release_lock(&lf->lf_spinlock);
/**
* Log rotation :
* Close current, and open a new file for the log.
*/
if (rotate_logfile)
{
bool succp;
lf->lf_name_seqno += 1; /*< new sequence number */
if (!(succp = logfile_create(lf)))
{
lf->lf_name_seqno -= 1; /*< restore */
}
else if ((succp = logfile_open_file(fwr, lf)))
{
if(use_stdout)
skygw_file_free (file);
else
skygw_file_close(file, false); /*< close old file */
}
if (!succp)
{
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"Error : Log rotation failed. "
"Creating replacement file %s "
"failed. Continuing "
"logging to existing file.",
lf->lf_full_file_name)));
}
continue;
}
/**
* 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)
{
int err = 0;
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_state;
if (bb->bb_buf_used != 0 &&
(flush_blockbuf == BB_FULL ||
flush_logfile ||
do_flushall))
{
/**
* 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);
}
err = skygw_file_write(
file,
(void *)bb->bb_buf,
bb->bb_buf_used,
(flush_logfile ||
do_flushall));
if (err)
{
fprintf(stderr,
"Error : Write to %s log "
": %s failed due to %d, "
"%s. Disabling the log.",
STRLOGNAME((logfile_id_t)i),
lf->lf_full_file_name,
err,
strerror(err));
/** Force log off */
skygw_log_disable_raw((logfile_id_t)i, true);
}
/**
* 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_state = BB_CLEARED;
#if defined(SS_LOG_DEBUG)
sprintf(bb->bb_buf,"[block:%d]",atomic_add(&block_start_index,1));
bb->bb_buf_used += strlen(bb->bb_buf);
bb->bb_buf_left -= strlen(bb->bb_buf);
#endif
}
/** 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_started_flag){
flushall_started_flag = false;
flushall_done_flag = true;
i = LOGFILE_FIRST;
goto retry_flush_on_exit;
}
if (!thr_flushall_check() && skygw_thread_must_exit(thr))
{
flushall_logfiles(true);
i = LOGFILE_FIRST;
goto retry_flush_on_exit;
}
}/* for */
if(flushall_done_flag){
flushall_done_flag = false;
flushall_logfiles(false);
skygw_message_send(fwr->fwr_clientmes);
}
} /* 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);
}
/**
* Find the file with biggest sequence number from given directory and return
* the sequence number.
*
* @param parts string parts of which the file name is composed of
* @param seqno the sequence number to start with, if seqno is -1 just return
*
* @return the biggest sequence number used
*/
static int find_last_seqno(
strpart_t* parts,
int seqno,
int seqnoidx)
{
strpart_t* p;
char* snstr;
int snstrlen;
if (seqno == -1)
{
return seqno;
}
snstrlen = UINTLEN(INT_MAX);
snstr = (char *)calloc(1, snstrlen);
p = parts;
while (true)
{
int i;
char filename[NAME_MAX] = {0};
/** Form name with next seqno */
snprintf(snstr, snstrlen, "%d", seqno+1);
for (i=0, p=parts; p->sp_string != NULL; i++, p=p->sp_next)
{
if (snstr != NULL && i == seqnoidx)
{
strncat(filename, snstr, NAME_MAX - 1); /*< add sequence number */
}
strncat(filename, p->sp_string, NAME_MAX - 1);
if (p->sp_next == NULL)
{
break;
}
}
if (check_file_and_path(filename, NULL, false))
{
seqno++;
}
else
{
break;
}
}
free(snstr);
return seqno;
}
bool thr_flushall_check()
{
bool rval = false;
simple_mutex_lock(&lm->lm_mutex,true);
rval = flushall_flag;
if(rval && !flushall_started_flag && !flushall_done_flag){
flushall_started_flag = true;
}
simple_mutex_unlock(&lm->lm_mutex);
return rval;
}
void flushall_logfiles(bool flush)
{
simple_mutex_lock(&lm->lm_mutex,true);
flushall_flag = flush;
simple_mutex_unlock(&lm->lm_mutex);
}
/**
* Flush all log files synchronously
*/
void skygw_log_sync_all(void)
{
if(!use_stdout)skygw_log_write(LOGFILE_TRACE,"Starting log flushing to disk.");
flushall_logfiles(true);
skygw_message_send(lm->lm_logmes);
skygw_message_wait(lm->lm_clientmes);
}
/**
* Toggle high precision logging
* @param val 0 for disabled, 1 for enabled
*/
void skygw_set_highp(int val)
{
highprec = val;
}
/**
* Toggle syslog logging
* @param val 0 for disabled, 1 for enabled
*/
void logmanager_enable_syslog(int val)
{
do_syslog = val;
}
/**
* Toggle syslog logging
* @param val 0 for disabled, 1 for enabled
*/
void logmanager_enable_maxscalelog(int val)
{
do_maxscalelog = val;
}