
log_manager.cc: Log manager handles cases where there are mismatch in user privileges. Mark log files enabled in the global lm_enabled_logfiles_bitmask after initialization so that it reflects reality in error cases. In general, take into account the possibility that any phase in initialization may fail and read return values. Replaced file_exists_and_is_writable to check_file_and_path which has a slightly different logic and which detects if file open fails for a few different reasons. Improved logging (in stderr) in general in error cases. gateway.c: Also check home directory accessibility in case when it is provided as a command-line argument. Added function check_dir_access to provide that function. Read return value of skysql_logmanager_init and exit (nicely) if it failed. skygw_utils.cc: initialize mlist with version number 2, which indicates that object is initialized (different than zero) and that there are no active updates on the object (version%2==0).
2675 lines
78 KiB
C++
2675 lines
78 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>
|
|
|
|
#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
|
|
/**
|
|
* 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_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;
|
|
|
|
|
|
/** 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;
|
|
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,
|
|
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* nameconflict,
|
|
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;
|
|
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();
|
|
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;
|
|
|
|
/** Initialize configuration including log file naming info */
|
|
if (!fnames_conf_init(fn, argc, argv)) {
|
|
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 (first) log file(s)
|
|
* 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 ((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;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @node Initializes log managing routines in MariaDB Corporation MaxScale.
|
|
*
|
|
* Parameters:
|
|
* @param p_ctx - in, give
|
|
* pointer to memory location where logmanager stores private write
|
|
* buffer.
|
|
*
|
|
* @param argc - in, use
|
|
* number of arguments in argv array
|
|
*
|
|
* @param argv - in, use
|
|
* arguments array
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
bool skygw_logmanager_init(
|
|
int argc,
|
|
char* argv[])
|
|
{
|
|
bool succp = false;
|
|
|
|
acquire_lock(&lmlock);
|
|
|
|
if (lm != NULL) {
|
|
succp = true;
|
|
goto return_succp;
|
|
}
|
|
|
|
succp = logmanager_init_nomutex(argc, argv);
|
|
|
|
return_succp:
|
|
release_lock(&lmlock);
|
|
|
|
return succp;
|
|
}
|
|
|
|
|
|
static void logmanager_done_nomutex(void)
|
|
{
|
|
int i;
|
|
logfile_t* lf;
|
|
filewriter_t* fwr;
|
|
|
|
fwr = &lm->lm_filewriter;
|
|
|
|
if (fwr->fwr_state == RUN) {
|
|
CHK_FILEWRITER(fwr);
|
|
/** Inform filewriter thread and wait until it has stopped. */
|
|
skygw_thread_set_exitflag(fwr->fwr_thread,
|
|
fwr->fwr_logmes,
|
|
fwr->fwr_clientmes);
|
|
|
|
/** Free thread memory */
|
|
skygw_thread_done(fwr->fwr_thread);
|
|
}
|
|
|
|
/** Free filewriter memory. */
|
|
filewriter_done(fwr);
|
|
|
|
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i++) {
|
|
lf = logmanager_get_logfile(lm, (logfile_id_t)i);
|
|
/** Release logfile memory */
|
|
logfile_done(lf);
|
|
}
|
|
|
|
if (syslog_id_str)
|
|
{
|
|
closelog();
|
|
}
|
|
/** Release messages and finally logmanager memory */
|
|
fnames_conf_done(&lm->lm_fnames_conf);
|
|
skygw_message_done(lm->lm_clientmes);
|
|
skygw_message_done(lm->lm_logmes);
|
|
|
|
/** Set global pointer NULL to prevent access to freed data. */
|
|
free(lm);
|
|
lm = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* @node This function is provided for atexit() system function.
|
|
*
|
|
* Parameters:
|
|
* @param void - <usage>
|
|
* <description>
|
|
*
|
|
* @return void
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
void skygw_logmanager_exit(void)
|
|
{
|
|
skygw_logmanager_done();
|
|
}
|
|
|
|
/**
|
|
* @node End execution of log manager
|
|
*
|
|
* Parameters:
|
|
* @param p_ctx - in, take
|
|
* pointer to memory location including context pointer. Context will
|
|
* be freed in this function.
|
|
*
|
|
* @param logmanager - in, use
|
|
* pointer to logmanager.
|
|
*
|
|
* @return void
|
|
*
|
|
*
|
|
* @details Stops file writing thread, releases filewriter, and logfiles.
|
|
*
|
|
*/
|
|
void skygw_logmanager_done(void)
|
|
{
|
|
acquire_lock(&lmlock);
|
|
|
|
if (lm == NULL) {
|
|
release_lock(&lmlock);
|
|
return;
|
|
}
|
|
CHK_LOGMANAGER(lm);
|
|
/** Mark logmanager unavailable */
|
|
lm->lm_enabled = false;
|
|
|
|
/** Wait until all users have left or someone shuts down
|
|
* logmanager between lock release and acquire.
|
|
*/
|
|
while(lm != NULL && lm->lm_nlinks != 0) {
|
|
release_lock(&lmlock);
|
|
pthread_yield();
|
|
acquire_lock(&lmlock);
|
|
}
|
|
|
|
/** Logmanager was already shut down. Return successfully. */
|
|
if (lm == NULL) {
|
|
goto return_void;
|
|
}
|
|
ss_dassert(lm->lm_nlinks == 0);
|
|
logmanager_done_nomutex();
|
|
|
|
return_void:
|
|
release_lock(&lmlock);
|
|
}
|
|
|
|
static logfile_t* logmanager_get_logfile(
|
|
logmanager_t* lmgr,
|
|
logfile_id_t id)
|
|
{
|
|
logfile_t* lf;
|
|
CHK_LOGMANAGER(lmgr);
|
|
ss_dassert(id >= LOGFILE_FIRST && id <= LOGFILE_LAST);
|
|
lf = &lmgr->lm_logfile[id];
|
|
|
|
if (lf->lf_state == RUN) {
|
|
CHK_LOGFILE(lf);
|
|
}
|
|
return lf;
|
|
}
|
|
|
|
|
|
/**
|
|
* @node Finds write position from block buffer for log string and writes there.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* @param id - in, use
|
|
* logfile object identifier
|
|
*
|
|
* @param flush - in, use
|
|
* indicates whether log string must be written to disk immediately
|
|
*
|
|
* @param use_valist - in, use
|
|
* does write involve formatting of the string and use of valist argument
|
|
*
|
|
* @param spread_down - in, use
|
|
* if true, log string is spread to all logs having larger id.
|
|
*
|
|
* @param str_len - in, use
|
|
* length of formatted string
|
|
*
|
|
* @param str - in, use
|
|
* string to be written to log
|
|
*
|
|
* @param valist - in, use
|
|
* variable-length argument list for formatting the string
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
static int logmanager_write_log(
|
|
logfile_id_t id,
|
|
bool flush,
|
|
bool use_valist,
|
|
bool spread_down,
|
|
size_t str_len,
|
|
const 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) {
|
|
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,
|
|
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();
|
|
|
|
/** Findout how much can be safely written with current block size */
|
|
if (timestamp_len-1+str_len > lf->lf_buf_size)
|
|
{
|
|
safe_str_len = lf->lf_buf_size;
|
|
}
|
|
else
|
|
{
|
|
safe_str_len = timestamp_len-1+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
|
|
|
|
wp = blockbuf_get_writepos(&bb,
|
|
id,
|
|
safe_str_len,
|
|
flush);
|
|
|
|
|
|
#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.
|
|
*/
|
|
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-timestamp_len, str, valist);
|
|
} else {
|
|
snprintf(wp+timestamp_len, safe_str_len-timestamp_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';
|
|
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, "%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) */
|
|
}
|
|
|
|
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_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 full buffer to the end of the list
|
|
*/
|
|
|
|
simple_mutex_unlock(&bb->bb_mutex);
|
|
simple_mutex_lock(&bb_list->mlist_mutex, true);
|
|
|
|
if(node->mlnode_next){
|
|
bb_list->mlist_first = node->mlnode_next;
|
|
bb_list->mlist_last->mlnode_next = node;
|
|
node->mlnode_next = NULL;
|
|
bb_list->mlist_last = node;
|
|
node = bb_list->mlist_first;
|
|
}
|
|
|
|
bb->bb_state = BB_READY;
|
|
|
|
}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)) {
|
|
//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) {
|
|
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,
|
|
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,
|
|
const 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,
|
|
const char* str,
|
|
...)
|
|
{
|
|
int err = 0;
|
|
va_list valist;
|
|
size_t len;
|
|
|
|
if (!logmanager_register(true)) {
|
|
//fprintf(stderr, "ERROR: Can't register to logmanager\n");
|
|
err = -1;
|
|
goto return_err;
|
|
}
|
|
CHK_LOGMANAGER(lm);
|
|
|
|
/**
|
|
* If particular log is disabled only unregister and return.
|
|
*/
|
|
if (!(lm->lm_enabled_logfiles & id)) {
|
|
err = 1;
|
|
goto return_unregister;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Find out the length of log string (to be formatted str).
|
|
*/
|
|
va_start(valist, str);
|
|
len = vsnprintf(NULL, 0, str, valist);
|
|
va_end(valist);
|
|
/**
|
|
* Add one for line feed.
|
|
*/
|
|
len += 1;
|
|
/**
|
|
* Write log string to buffer and add to file write list.
|
|
*/
|
|
|
|
va_start(valist, str);
|
|
err = logmanager_write_log(id, false, true, true, len, str, valist);
|
|
va_end(valist);
|
|
|
|
if (err != 0) {
|
|
fprintf(stderr, "skygw_log_write failed.\n");
|
|
goto return_unregister;
|
|
}
|
|
|
|
return_unregister:
|
|
logmanager_unregister();
|
|
return_err:
|
|
return err;
|
|
}
|
|
|
|
|
|
int skygw_log_flush(
|
|
logfile_id_t id)
|
|
{
|
|
int err = 0;
|
|
va_list valist; /**< Dummy, must be present but it is not processed */
|
|
|
|
if (!logmanager_register(false)) {
|
|
ss_dfprintf(stderr,
|
|
"Can't register to logmanager, nothing to flush\n");
|
|
goto return_err;
|
|
}
|
|
CHK_LOGMANAGER(lm);
|
|
err = logmanager_write_log(id, true, false, false, 0, NULL, valist);
|
|
|
|
if (err != 0) {
|
|
fprintf(stderr, "skygw_log_flush failed.\n");
|
|
goto return_unregister;
|
|
}
|
|
|
|
return_unregister:
|
|
logmanager_unregister();
|
|
return_err:
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* @node Register as a logging client to logmanager.
|
|
*
|
|
* Parameters:
|
|
* @param lmgr - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details Link count modify is protected by mutex.
|
|
*
|
|
*/
|
|
static bool logmanager_register(
|
|
bool writep)
|
|
{
|
|
bool succp = true;
|
|
|
|
acquire_lock(&lmlock);
|
|
|
|
if (lm == NULL || !lm->lm_enabled) {
|
|
/**
|
|
* Flush succeeds if logmanager is shut or shutting down.
|
|
* returning false so that flusher doesn't access logmanager
|
|
* and its members which would probabaly lead to NULL pointer
|
|
* reference.
|
|
*/
|
|
if (!writep) {
|
|
succp = false;
|
|
goto return_succp;
|
|
}
|
|
|
|
ss_dassert(lm == NULL || (lm != NULL && !lm->lm_enabled));
|
|
|
|
/**
|
|
* Wait until logmanager shut down has completed.
|
|
* logmanager is enabled if someone already restarted
|
|
* it between latest lock release, and acquire.
|
|
*/
|
|
while (lm != NULL && !lm->lm_enabled) {
|
|
release_lock(&lmlock);
|
|
pthread_yield();
|
|
acquire_lock(&lmlock);
|
|
}
|
|
|
|
if (lm == NULL) {
|
|
succp = logmanager_init_nomutex(0, NULL);
|
|
}
|
|
}
|
|
/** if logmanager existed or was succesfully restarted, increase link */
|
|
if (succp) {
|
|
lm->lm_nlinks += 1;
|
|
}
|
|
|
|
return_succp:
|
|
release_lock(&lmlock);
|
|
return succp;
|
|
}
|
|
|
|
/**
|
|
* @node Unregister from logmanager.
|
|
*
|
|
* Parameters:
|
|
* @param lmgr - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details Link count modify is protected by mutex.
|
|
*
|
|
*/
|
|
static void logmanager_unregister(void)
|
|
{
|
|
acquire_lock(&lmlock);
|
|
|
|
lm->lm_nlinks -= 1;
|
|
ss_dassert(lm->lm_nlinks >= 0);
|
|
|
|
release_lock(&lmlock);
|
|
}
|
|
|
|
|
|
/**
|
|
* @node Initialize log file naming parameters from call arguments
|
|
* or from default functions in cases where arguments are not provided.
|
|
*
|
|
* Parameters:
|
|
* @param fn - <usage>
|
|
* <description>
|
|
*
|
|
* @param argc - <usage>
|
|
* <description>
|
|
*
|
|
* @param argv - <usage>
|
|
* <description>
|
|
*
|
|
* @return pointer to object which is either in RUN or DONE state.
|
|
*
|
|
*
|
|
* @details Note that input parameter lenghts are checked here.
|
|
*
|
|
*/
|
|
static bool fnames_conf_init(
|
|
fnames_conf_t* fn,
|
|
int argc,
|
|
char* argv[])
|
|
{
|
|
int opt;
|
|
bool succp = false;
|
|
const char* argstr =
|
|
"-h - help\n"
|
|
"-a <debug prefix> ............(\"skygw_debug\")\n"
|
|
"-b <debug suffix> ............(\".log\")\n"
|
|
"-c <trace prefix> ............(\"skygw_trace\")\n"
|
|
"-d <trace suffix> ............(\".log\")\n"
|
|
"-e <message prefix> ............(\"skygw_msg\")\n"
|
|
"-f <message suffix> ............(\".log\")\n"
|
|
"-g <error prefix> ............(\"skygw_err\")\n"
|
|
"-i <error suffix> ............(\".log\")\n"
|
|
"-j <log path> ............(\"/tmp\")\n"
|
|
"-l <syslog log file ids> .......(no default)\n"
|
|
"-m <syslog ident> ............(argv[0])\n"
|
|
"-s <shmem log file ids> .......(no default)\n";
|
|
|
|
/**
|
|
* When init_started is set, clean must be done for it.
|
|
*/
|
|
fn->fn_state = INIT;
|
|
#if defined(SS_DEBUG)
|
|
fn->fn_chk_top = CHK_NUM_FNAMES;
|
|
fn->fn_chk_tail = CHK_NUM_FNAMES;
|
|
#endif
|
|
optind = 1; /**<! reset getopt index */
|
|
while ((opt = getopt(argc, argv, "+a:b:c:d:e:f:g:h:i:j:l:m:s:")) != -1)
|
|
{
|
|
switch (opt) {
|
|
case 'a':
|
|
fn->fn_debug_prefix = strndup(optarg, MAX_PREFIXLEN);
|
|
break;
|
|
|
|
case 'b':
|
|
fn->fn_debug_suffix = strndup(optarg, MAX_SUFFIXLEN);
|
|
break;
|
|
case 'c':
|
|
fn->fn_trace_prefix = strndup(optarg, MAX_PREFIXLEN);
|
|
break;
|
|
|
|
case 'd':
|
|
fn->fn_trace_suffix = strndup(optarg, MAX_SUFFIXLEN);
|
|
break;
|
|
|
|
case 'e':
|
|
fn->fn_msg_prefix = strndup(optarg, MAX_PREFIXLEN);
|
|
break;
|
|
|
|
case 'f':
|
|
fn->fn_msg_suffix = strndup(optarg, MAX_SUFFIXLEN);
|
|
break;
|
|
|
|
case 'g':
|
|
fn->fn_err_prefix = strndup(optarg, MAX_PREFIXLEN);
|
|
break;
|
|
|
|
case 'i':
|
|
fn->fn_err_suffix = strndup(optarg, MAX_SUFFIXLEN);
|
|
break;
|
|
|
|
case 'j':
|
|
fn->fn_logpath = strndup(optarg, MAX_PATHLEN);
|
|
break;
|
|
|
|
case 'l':
|
|
/** record list of log file ids for syslogged */
|
|
if (syslog_id_str != NULL)
|
|
{
|
|
free (syslog_id_str);
|
|
}
|
|
syslog_id_str = optarg;
|
|
break;
|
|
|
|
case 'm':
|
|
/**
|
|
* Identity string for syslog printing, needs '-l'
|
|
* to be effective.
|
|
*/
|
|
syslog_ident_str = optarg;
|
|
break;
|
|
|
|
case 's':
|
|
/** record list of log file ids for later use */
|
|
shmem_id_str = optarg;
|
|
break;
|
|
case 'h':
|
|
default:
|
|
fprintf(stderr,
|
|
"\nSupported arguments are (default)\n%s\n",
|
|
argstr);
|
|
goto return_conf_init;
|
|
} /** switch (opt) */
|
|
}
|
|
/** If log file name is not specified in call arguments, use default. */
|
|
fn->fn_debug_prefix = (fn->fn_debug_prefix == NULL) ?
|
|
strdup(get_debug_prefix_default()) : fn->fn_debug_prefix;
|
|
fn->fn_debug_suffix = (fn->fn_debug_suffix == NULL) ?
|
|
strdup(get_debug_suffix_default()) : fn->fn_debug_suffix;
|
|
fn->fn_trace_prefix = (fn->fn_trace_prefix == NULL) ?
|
|
strdup(get_trace_prefix_default()) : fn->fn_trace_prefix;
|
|
fn->fn_trace_suffix = (fn->fn_trace_suffix == NULL) ?
|
|
strdup(get_trace_suffix_default()) : fn->fn_trace_suffix;
|
|
fn->fn_msg_prefix = (fn->fn_msg_prefix == NULL) ?
|
|
strdup(get_msg_prefix_default()) : fn->fn_msg_prefix;
|
|
fn->fn_msg_suffix = (fn->fn_msg_suffix == NULL) ?
|
|
strdup(get_msg_suffix_default()) : fn->fn_msg_suffix;
|
|
fn->fn_err_prefix = (fn->fn_err_prefix == NULL) ?
|
|
strdup(get_err_prefix_default()) : fn->fn_err_prefix;
|
|
fn->fn_err_suffix = (fn->fn_err_suffix == NULL) ?
|
|
strdup(get_err_suffix_default()) : fn->fn_err_suffix;
|
|
fn->fn_logpath = (fn->fn_logpath == NULL) ?
|
|
strdup(get_logpath_default()) : fn->fn_logpath;
|
|
|
|
/** Set identity string for syslog if it is not set in config.*/
|
|
syslog_ident_str =
|
|
(syslog_ident_str == NULL ?
|
|
(argv == NULL ? strdup(program_invocation_short_name) :
|
|
strdup(*argv)) :
|
|
syslog_ident_str);
|
|
|
|
/* ss_dfprintf(stderr, "\n\n\tCommand line : ");
|
|
for (i=0; i<argc; i++) {
|
|
ss_dfprintf(stderr, "%s ", argv[i]);
|
|
}
|
|
ss_dfprintf(stderr, "\n");*/
|
|
#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 - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details If logfile is supposed to be located to shared memory
|
|
* it is specified here. In the case of shared memory file, a soft
|
|
* link is created to log directory.
|
|
*
|
|
* Motivation is performance. LOGFILE_DEBUG, for example, has so much data
|
|
* that writing it to disk slows execution down remarkably.
|
|
*
|
|
*/
|
|
static bool logfiles_init(
|
|
logmanager_t* lm)
|
|
{
|
|
bool succp = true;
|
|
int lid = LOGFILE_FIRST;
|
|
int i = 0;
|
|
bool store_shmem;
|
|
bool write_syslog;
|
|
|
|
if (syslog_id_str != NULL)
|
|
{
|
|
openlog(syslog_ident_str, LOG_PID | LOG_NDELAY, LOG_USER);
|
|
}
|
|
/**
|
|
* Initialize log files, pass softlink flag if necessary.
|
|
*/
|
|
while (lid<=LOGFILE_LAST && succp) {
|
|
/**
|
|
* Check if the file is stored in shared memory. If so,
|
|
* a symbolic link will be created to log directory.
|
|
*/
|
|
if (shmem_id_str != NULL &&
|
|
strcasestr(shmem_id_str,
|
|
STRLOGID(logfile_id_t(lid))) != NULL)
|
|
{
|
|
store_shmem = true;
|
|
}
|
|
else
|
|
{
|
|
store_shmem = false;
|
|
}
|
|
/**
|
|
* Check if file is also written to syslog.
|
|
*/
|
|
if (syslog_id_str != NULL &&
|
|
strcasestr(syslog_id_str,
|
|
STRLOGID(logfile_id_t(lid))) != NULL)
|
|
{
|
|
write_syslog = true;
|
|
}
|
|
else
|
|
{
|
|
write_syslog = false;
|
|
}
|
|
succp = logfile_init(&lm->lm_logfile[lid],
|
|
(logfile_id_t)lid,
|
|
lm,
|
|
store_shmem,
|
|
write_syslog);
|
|
|
|
if (!succp) {
|
|
fprintf(stderr, "*\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);
|
|
}
|
|
|
|
|
|
/**
|
|
* @node Combine all name parts from left to right.
|
|
*
|
|
* Parameters:
|
|
* @param parts - <usage>
|
|
* <description>
|
|
*
|
|
* @param seqno - in, use
|
|
* specifies the the sequence number which will be added as a part
|
|
* of full file name.
|
|
* seqno == -1 indicates that sequence number won't be used.
|
|
*
|
|
* @param seqnoidx - in, use
|
|
* Specifies the seqno position in the 'array' of name parts.
|
|
* If seqno == -1 seqnoidx will be set -1 as well.
|
|
*
|
|
* @return Pointer to filename, of NULL if failed.
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
static char* form_full_file_name(
|
|
strpart_t* parts,
|
|
int seqno,
|
|
int seqnoidx)
|
|
{
|
|
int i;
|
|
size_t s;
|
|
size_t fnlen;
|
|
char* filename = NULL;
|
|
char* seqnostr = NULL;
|
|
strpart_t* p;
|
|
|
|
if (seqno != -1)
|
|
{
|
|
s = UINTLEN(seqno);
|
|
seqnostr = (char *)malloc((int)s+1);
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* These magic numbers are needed to indicate this and
|
|
* in subroutines that sequence number is not used.
|
|
*/
|
|
s = 0;
|
|
seqnoidx = -1;
|
|
}
|
|
|
|
if (parts == NULL || parts->sp_string == NULL) {
|
|
goto return_filename;
|
|
}
|
|
/**
|
|
* Length of path, file name, separating slash, sequence number and
|
|
* terminating character.
|
|
*/
|
|
fnlen = sizeof('/') + s + sizeof('\0');
|
|
p = parts;
|
|
/** Calculate the combined length of all parts put together */
|
|
while (p->sp_string != NULL)
|
|
{
|
|
fnlen += strnlen(p->sp_string, NAME_MAX);
|
|
|
|
if (p->sp_next == NULL)
|
|
{
|
|
break;
|
|
}
|
|
p = p->sp_next;
|
|
}
|
|
|
|
if (fnlen > NAME_MAX) {
|
|
fprintf(stderr, "Error : Too long file name= %d.\n", (int)fnlen);
|
|
goto return_filename;
|
|
}
|
|
|
|
filename = (char*)calloc(1, fnlen);
|
|
snprintf(seqnostr, s+1, "%d", seqno);
|
|
|
|
for (i=0, p=parts; p->sp_string != NULL; i++, p=p->sp_next)
|
|
{
|
|
if (i == seqnoidx)
|
|
{
|
|
strcat(filename, seqnostr);
|
|
}
|
|
strcat(filename, p->sp_string);
|
|
|
|
if (p->sp_next == NULL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return_filename:
|
|
if (seqnostr != NULL) free(seqnostr);
|
|
return filename;
|
|
}
|
|
|
|
/**
|
|
* @node Allocate new buffer where argument string with a slash is copied.
|
|
* Original string buffer is freed.
|
|
* added.
|
|
*
|
|
* Parameters:
|
|
* @param str - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
static char* add_slash(
|
|
char* str)
|
|
{
|
|
char* p = str;
|
|
size_t plen = strlen(p);
|
|
|
|
/** Add slash if missing */
|
|
if (p[plen-1] != '/')
|
|
{
|
|
str = (char *)malloc(plen+2);
|
|
snprintf(str, plen+2, "%s/", p);
|
|
free(p);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
|
|
/**
|
|
* @node Check if the path and file exist in the local file system and if they do,
|
|
* check if they are accessible and writable.
|
|
*
|
|
* Parameters:
|
|
* @param filename - <usage>
|
|
* <description>
|
|
*
|
|
* @param writable - <usage>
|
|
* <description>
|
|
*
|
|
* @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)
|
|
{
|
|
int fd;
|
|
bool exists;
|
|
|
|
if (filename == NULL)
|
|
{
|
|
exists = false;
|
|
*writable = false;
|
|
}
|
|
else
|
|
{
|
|
fd = open(filename, O_CREAT|O_EXCL, S_IRWXU);
|
|
|
|
if (fd == -1)
|
|
{
|
|
/** File exists, check permission to read/write */
|
|
if (errno == EEXIST)
|
|
{
|
|
/** Open file and write a byte for test */
|
|
fd = open(filename, O_CREAT|O_RDWR, S_IRWXU|S_IRWXG);
|
|
|
|
if (fd == -1)
|
|
{
|
|
fprintf(stderr,
|
|
"*\n* Error : Can't access %s due "
|
|
"to %s.\n",
|
|
filename,
|
|
strerror(errno));
|
|
*writable = false;
|
|
}
|
|
else
|
|
{
|
|
char c = ' ';
|
|
if (write(fd, &c, 1) == 1)
|
|
{
|
|
*writable = true;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,
|
|
"*\n* Error : Can't write to "
|
|
"%s due to %s.\n",
|
|
filename,
|
|
strerror(errno));
|
|
*writable = false;
|
|
}
|
|
close(fd);
|
|
}
|
|
exists = true;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,
|
|
"*\n* Error : Can't access %s due to %s.\n",
|
|
filename,
|
|
strerror(errno));
|
|
exists = false;
|
|
*writable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
close(fd);
|
|
unlink(filename);
|
|
exists = false;
|
|
*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 - <usage>
|
|
* <description>
|
|
*
|
|
* @param logfile_id - <usage>
|
|
* <description>
|
|
*
|
|
* @param logmanager - <usage>
|
|
* <description>
|
|
*
|
|
* @param store_shmem - <usage>
|
|
* <description>
|
|
*
|
|
* @return true if succeed, false otherwise
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
static bool logfile_init(
|
|
logfile_t* logfile,
|
|
logfile_id_t logfile_id,
|
|
logmanager_t* logmanager,
|
|
bool store_shmem,
|
|
bool write_syslog)
|
|
{
|
|
bool succp = false;
|
|
fnames_conf_t* fn = &logmanager->lm_fnames_conf;
|
|
/** string parts of which the file is composed of */
|
|
strpart_t strparts[3];
|
|
bool namecreatefail;
|
|
bool nameconflicts;
|
|
bool writable;
|
|
|
|
logfile->lf_state = INIT;
|
|
#if defined(SS_DEBUG)
|
|
logfile->lf_chk_top = CHK_NUM_LOGFILE;
|
|
logfile->lf_chk_tail = CHK_NUM_LOGFILE;
|
|
#endif
|
|
logfile->lf_logmes = logmanager->lm_logmes;
|
|
logfile->lf_id = logfile_id;
|
|
logfile->lf_name_prefix = fname_conf_get_prefix(fn, logfile_id);
|
|
logfile->lf_name_suffix = fname_conf_get_suffix(fn, logfile_id);
|
|
logfile->lf_npending_writes = 0;
|
|
logfile->lf_name_seqno = 1;
|
|
logfile->lf_lmgr = logmanager;
|
|
logfile->lf_flushflag = false;
|
|
logfile->lf_spinlock = 0;
|
|
logfile->lf_store_shmem = store_shmem;
|
|
logfile->lf_write_syslog = write_syslog;
|
|
logfile->lf_buf_size = MAX_LOGSTRLEN;
|
|
logfile->lf_enabled = logmanager->lm_enabled_logfiles & logfile_id;
|
|
/**
|
|
* strparts is an array but next pointers are used to walk through
|
|
* the list of string parts.
|
|
*/
|
|
strparts[0].sp_next = &strparts[1];
|
|
strparts[1].sp_next = &strparts[2];
|
|
strparts[2].sp_next = NULL;
|
|
|
|
strparts[1].sp_string = logfile->lf_name_prefix;
|
|
strparts[2].sp_string = logfile->lf_name_suffix;
|
|
/**
|
|
* If file is stored in shared memory in /dev/shm, a link
|
|
* pointing to shm file is created and located to the file
|
|
* directory.
|
|
*/
|
|
if (store_shmem)
|
|
{
|
|
char* c;
|
|
pid_t pid = getpid();
|
|
int len = strlen(shm_pathname_prefix)+
|
|
get_decimal_len((size_t)pid) + 1;
|
|
|
|
c = (char *)calloc(len, sizeof(char));
|
|
|
|
if (c == NULL)
|
|
{
|
|
succp = false;
|
|
goto file_create_fail;
|
|
}
|
|
sprintf(c, "%s%d", shm_pathname_prefix, pid);
|
|
logfile->lf_filepath = c;
|
|
|
|
if (mkdir(c, S_IRWXU | S_IRWXG) != 0 &&
|
|
errno != EEXIST)
|
|
{
|
|
succp = false;
|
|
goto file_create_fail;
|
|
}
|
|
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 (check_file_and_path(
|
|
logfile->lf_full_file_name,
|
|
&writable))
|
|
{
|
|
/** Found similarly named file which isn't writable */
|
|
if (!writable ||
|
|
file_is_symlink(logfile->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_with_succp;
|
|
}
|
|
}
|
|
|
|
if (store_shmem)
|
|
{
|
|
if (check_file_and_path(
|
|
logfile->lf_full_file_name,
|
|
&writable))
|
|
{
|
|
/** Found similarly named file which isn't writable */
|
|
if (!writable ||
|
|
file_is_symlink(logfile->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_with_succp;
|
|
}
|
|
}
|
|
}
|
|
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"),
|
|
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 (store_shmem)
|
|
{
|
|
fprintf(stderr, "%s\t: %s->%s\n",
|
|
STRLOGNAME(logfile_id),
|
|
logfile->lf_full_link_name,
|
|
logfile->lf_full_file_name);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s\t: %s\n",
|
|
STRLOGNAME(logfile_id),
|
|
logfile->lf_full_file_name);
|
|
}
|
|
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 struct to a given address
|
|
*
|
|
* Parameters:
|
|
* @param fw - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
static bool filewriter_init(
|
|
logmanager_t* logmanager,
|
|
filewriter_t* fw,
|
|
skygw_message_t* clientmes,
|
|
skygw_message_t* logmes)
|
|
{
|
|
bool succp = false;
|
|
logfile_t* lf;
|
|
logfile_id_t id;
|
|
int i;
|
|
char* start_msg_str;
|
|
|
|
CHK_LOGMANAGER(logmanager);
|
|
|
|
fw->fwr_state = INIT;
|
|
#if defined(SS_DEBUG)
|
|
fw->fwr_chk_top = CHK_NUM_FILEWRITER;
|
|
fw->fwr_chk_tail = CHK_NUM_FILEWRITER;
|
|
#endif
|
|
fw->fwr_logmgr = logmanager;
|
|
/** Message from filewriter to clients */
|
|
fw->fwr_logmes = logmes;
|
|
/** Message from clients to filewriter */
|
|
fw->fwr_clientmes = clientmes;
|
|
|
|
if (fw->fwr_logmes == NULL || fw->fwr_clientmes == NULL) {
|
|
goto return_succp;
|
|
}
|
|
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1) {
|
|
id = (logfile_id_t)i;
|
|
lf = logmanager_get_logfile(logmanager, id);
|
|
|
|
if (lf->lf_store_shmem)
|
|
{
|
|
/** Create symlink pointing to log file */
|
|
fw->fwr_file[id] = skygw_file_init(lf->lf_full_file_name,
|
|
lf->lf_full_link_name);
|
|
}
|
|
else
|
|
{
|
|
/** Create normal disk-resident log file */
|
|
fw->fwr_file[id] = skygw_file_init(lf->lf_full_file_name,
|
|
NULL);
|
|
}
|
|
|
|
if (fw->fwr_file[id] == NULL) {
|
|
goto return_succp;
|
|
}
|
|
if (lf->lf_enabled) {
|
|
start_msg_str = strdup("---\tLogging is enabled.\n");
|
|
} else {
|
|
start_msg_str = strdup("---\tLogging is disabled.\n");
|
|
}
|
|
skygw_file_write(fw->fwr_file[id],
|
|
(void *)start_msg_str,
|
|
strlen(start_msg_str),
|
|
true);
|
|
free(start_msg_str);
|
|
}
|
|
fw->fwr_state = RUN;
|
|
CHK_FILEWRITER(fw);
|
|
succp = true;
|
|
return_succp:
|
|
if (!succp) {
|
|
filewriter_done(fw);
|
|
}
|
|
ss_dassert(fw->fwr_state == RUN || fw->fwr_state == DONE);
|
|
return succp;
|
|
}
|
|
|
|
static void filewriter_done(
|
|
filewriter_t* fw)
|
|
{
|
|
int i;
|
|
logfile_id_t id;
|
|
|
|
switch(fw->fwr_state) {
|
|
case RUN:
|
|
CHK_FILEWRITER(fw);
|
|
case INIT:
|
|
fw->fwr_logmes = NULL;
|
|
fw->fwr_clientmes = NULL;
|
|
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i++) {
|
|
id = (logfile_id_t)i;
|
|
skygw_file_done(fw->fwr_file[id]);
|
|
}
|
|
fw->fwr_state = DONE;
|
|
case DONE:
|
|
case UNINIT:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @node Writes block buffers of logfiles to physical log files on disk.
|
|
*
|
|
* Parameters:
|
|
* @param data - thread context, skygw_thread_t
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details Waits until receives wake-up message. Scans through block buffer
|
|
* lists of each logfile object.
|
|
*
|
|
* Block buffer is written to log file if
|
|
* 1. bb_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 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_state;
|
|
|
|
if (bb->bb_buf_used != 0 &&
|
|
(flush_blockbuf == BB_FULL ||
|
|
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_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_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);
|
|
}
|