sml_support.c
/*
* libsyncml - A syncml protocol implementation
* Copyright (C) 2005 Armin Bauer <armin.bauer@opensync.org>
* Copyright (C) 2007-2008 Michael Bell <michael.bell@opensync.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "syncml.h"
#include "syncml_internals.h"
#include "sml_support.h"
#include "sml_error_internals.h"
#include "config.h"
#include <glib.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <libxml/parser.h>
GPrivate* current_tabs = NULL;
GPrivate* thread_id = NULL;
#define G_ERRORCHECK_MUTEXES
static const char* smlCheckDebugDirectory(const char *env)
{
const char *dirname = g_getenv(env);
if (!dirname)
return NULL;
if (!g_file_test(dirname, G_FILE_TEST_EXISTS)) {
if (g_mkdir_with_parents(dirname, 0700) != 0) {
g_warning("The debug directory %s cannot be created.", dirname);
return NULL;
}
}
if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
g_warning("The debug directory %s is not a directory.", dirname);
return NULL;
}
return dirname;
}
void smlLog(const char *logname, const char *data, unsigned int size)
{
#if defined ENABLE_TRACE
const char *trace;
if (!(trace = smlCheckDebugDirectory("SYNCML_LOG")))
return;
int i = 0;
char *logfile = NULL;
for (;;i = i + 1) {
char *logfile_tmp = g_strdup_printf("%s/%s", trace, logname);
logfile = g_strdup_printf(logfile_tmp, i);
smlSafeCFree(&logfile_tmp);
if (!g_file_test(logfile, G_FILE_TEST_EXISTS))
break;
smlSafeCFree(&logfile);
}
GError *error = NULL;
GIOChannel *chan = g_io_channel_new_file(logfile, "w", &error);
if (!chan) {
printf("unable to open %s for writing: %s\n", logfile, error->message);
return;
}
gsize writen;
g_io_channel_set_encoding(chan, NULL, NULL);
if (g_io_channel_write_chars(chan, data, size, &writen, NULL) != G_IO_STATUS_NORMAL) {
printf("unable to write trace to %s\n", logfile);
} else
g_io_channel_flush(chan, NULL);
g_io_channel_shutdown(chan, TRUE, NULL);
g_io_channel_unref(chan);
#endif
}
void smlTrace(SmlTraceType type, const char *message, ...)
{
#if defined ENABLE_TRACE
const char *trace;
if (!(trace = smlCheckDebugDirectory("SYNCML_TRACE")))
return;
if (!g_thread_supported ()) g_thread_init (NULL);
int tabs = 0;
if (!current_tabs)
current_tabs = g_private_new (NULL);
else
tabs = GPOINTER_TO_INT(g_private_get(current_tabs));
unsigned long int id;
pid_t pid;
const char *endline;
#ifdef _WIN32
#ifdef PTW32_VERSION
id = (unsigned long int)pthread_self().p;
#else
if (!thread_id)
thread_id = g_private_new (NULL);
id = GPOINTER_TO_INT(thread_id);
#endif
pid = _getpid();
endline = "\r\n";
#else
id = (unsigned long int)pthread_self();
pid = getpid();
endline = "\n";
#endif
char *logfile = g_strdup_printf("%s/Thread%lu-%d.log", trace, id, pid);
// create message
va_list arglist;
char *buffer = NULL;
va_start(arglist, message);
buffer = g_strdup_vprintf(message, arglist);
va_end(arglist);
// filter message if necessary
const char *filter = g_getenv("SYNCML_TRACE_FILTER");
if (filter)
{
gchar** filters = g_strsplit(filter, ",", 0);
gint i;
SmlBool matched = FALSE;
for (i=0; filters[i] != NULL && !matched ; i++)
{
if (strstr(buffer, filters[i]))
matched = TRUE;
}
g_strfreev(filters);
if (!matched)
{
smlSafeCFree(&buffer);
return;
}
}
GString *tabstr = g_string_new("");
long i = 0;
for (i = 0; i < tabs; i++) {
tabstr = g_string_append(tabstr, "\t");
}
GTimeVal curtime;
g_get_current_time(&curtime);
char *logmessage = NULL;
switch (type) {
case TRACE_ENTRY:
logmessage = g_strdup_printf("[%li.%06li]\t%s>>>>>>> %s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
tabs++;
break;
case TRACE_INTERNAL:
logmessage = g_strdup_printf("[%li.%06li]\t%s%s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
break;
case TRACE_ERROR:
logmessage = g_strdup_printf("[%li.%06li]\t%sERROR: %s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
break;
case TRACE_EXIT:
logmessage = g_strdup_printf("[%li.%06li]%s<<<<<<< %s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
tabs--;
if (tabs < 0)
tabs = 0;
break;
case TRACE_EXIT_ERROR:
logmessage = g_strdup_printf("[%li.%06li]%s<--- ERROR --- %s%s", curtime.tv_sec, curtime.tv_usec, tabstr->str, buffer, endline);
tabs--;
if (tabs < 0)
tabs = 0;
break;
}
smlSafeCFree(&buffer);
g_private_set(current_tabs, GINT_TO_POINTER(tabs));
g_string_free(tabstr, TRUE);
GError *error = NULL;
GIOChannel *chan = g_io_channel_new_file(logfile, "a", &error);
if (!chan) {
printf("unable to open %s for writing: %s%s", logfile, error->message, endline);
smlSafeCFree(&logfile);
smlSafeCFree(&logmessage);
return;
}
gsize writen;
g_io_channel_set_encoding(chan, NULL, NULL);
if (g_io_channel_write_chars(chan, logmessage, strlen(logmessage), &writen, NULL) != G_IO_STATUS_NORMAL) {
printf("unable to write trace to %s%s", logfile, endline);
} else
g_io_channel_flush(chan, NULL);
g_io_channel_shutdown(chan, TRUE, NULL);
g_io_channel_unref(chan);
smlSafeCFree(&logmessage);
smlSafeCFree(&logfile);
#endif
}
char *smlPrintBinary(const char *data, int len)
{
int t;
GString *str = g_string_new("");
for (t = 0; t < len; t++) {
if (data[t] >= ' ' && data[t] <= 'z')
g_string_append_c(str, data[t]);
else
g_string_append_printf(str, " %02x ", (unsigned char) data[t]);
}
return g_string_free(str, FALSE);
}
char *smlPrintHex(const char *data, int len)
{
int t;
GString *str = g_string_new("");
for (t = 0; t < len; t++) {
g_string_append_printf(str, " %02x", (unsigned char) data[t]);
if (data[t] >= ' ' && data[t] <= 'z')
g_string_append_printf(str, "(%c)", data[t]);
g_string_append_c(str, ' ');
}
return g_string_free(str, FALSE);
}
/* End of debug API */
char *smlRandStr(int maxlength, SmlBool exact)
{
const char *randchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ1234567890";
int length;
char *retchar;
int i = 0;
if (exact)
length = maxlength;
else
length = g_random_int_range(1, maxlength + 1);
retchar = malloc(length * sizeof(char) + 1);
retchar[0] = 0;
for (i = 0; i < length; i++) {
retchar[i] = randchars[g_random_int_range(0, strlen(randchars))];
retchar[i + 1] = 0;
}
return retchar;
}
void *smlTryMalloc0(long n_bytes, SmlError **error)
{
CHECK_ERROR_REF
void *result = g_try_malloc(n_bytes);
if (!result) {
/* set error */
char *msg = NULL;
if (n_bytes > 0)
msg = g_strdup_printf("No memory left (needed %ld).", n_bytes);
else
msg = g_strdup_printf("Malloc of zero bytes requested.");
/* publish error */
if (error == NULL)
g_error("%s", msg);
else
smlErrorSet(error, SML_ERROR_INTERNAL_NO_MEMORY, "%s", msg);
return NULL;
}
memset(result, 0, n_bytes);
return result;
}
const char *smlGetVersion(void)
{
return "$Id$";
}
SmlThread *smlThreadNew(GMainContext *context, SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, context, error);
CHECK_ERROR_REF
/* Never ever run on the default context because a library
* thread should never influence the behavior of an user
* application and should never be influence by an user
* application.
*
* This is highly critical especially if the user application
* user the default context to loop on smlDsSessionDispatch
* and smlManagerDispatch.
*/
smlAssert(context);
SmlThread *thread = smlTryMalloc0(sizeof(SmlThread), error);
if (!thread)
goto error;
if (!g_thread_supported ()) g_thread_init (NULL);
if (!g_thread_supported ())
{
smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
"Threads are not supported.");
goto error;
}
thread->started = FALSE;
thread->started_mutex = g_mutex_new();
thread->started_cond = g_cond_new();
thread->context = context;
g_main_context_ref(thread->context);
thread->loop = g_main_loop_new(thread->context, FALSE);
smlTrace(TRACE_EXIT, "%s: %p", __func__, thread);
return thread;
error:
smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
return NULL;
}
void smlThreadFree(SmlThread *thread)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, thread);
smlAssert(thread);
if (thread->started_mutex)
g_mutex_free(thread->started_mutex);
if (thread->started_cond)
g_cond_free(thread->started_cond);
if (thread->loop)
g_main_loop_unref(thread->loop);
if (thread->context)
g_main_context_unref(thread->context);
smlSafeFree((gpointer *)&thread);
smlTrace(TRACE_EXIT, "%s", __func__);
}
static gpointer smlThreadStartCallback(gpointer data)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
SmlThread *thread = data;
smlTrace(TRACE_INTERNAL, "%s: +++++++++ This is the worker thread of thread %p for context %p +++++++++", __func__, thread, thread->context);
smlTrace(TRACE_INTERNAL, "%s: locking", __func__);
g_mutex_lock(thread->started_mutex);
thread->started = TRUE;
smlTrace(TRACE_INTERNAL, "%s: sending condition", __func__);
g_cond_signal(thread->started_cond);
smlTrace(TRACE_INTERNAL, "%s: unlocking", __func__);
g_mutex_unlock(thread->started_mutex);
/* check main context ownership */
if (!g_main_context_acquire(thread->context)) {
smlAssertMsg(FALSE, "This thread is not the owner of the GMainContext.");
} else {
smlTrace(TRACE_INTERNAL, "%s: Thread is owner of the GMainContext.", __func__);
}
g_main_loop_run(thread->loop);
/* release context ownership / decrement reference counter */
g_main_context_release(thread->context);
smlTrace(TRACE_EXIT, "%s", __func__);
return FALSE;
}
static gboolean smlThreadStopCallback(gpointer data)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
SmlThread *thread = data;
smlTrace(TRACE_INTERNAL, "%s: +++++++++ Quitting worker thread +++++++++", __func__);
g_main_loop_quit(thread->loop);
smlTrace(TRACE_EXIT, "%s", __func__);
return FALSE;
}
void smlThreadStart(SmlThread *thread)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, thread);
smlAssert(thread);
//Start the thread
smlTrace(TRACE_INTERNAL, "%s: locking", __func__);
g_mutex_lock(thread->started_mutex);
smlTrace(TRACE_INTERNAL, "%s: creating thread", __func__);
thread->thread = g_thread_create (smlThreadStartCallback, thread, TRUE, NULL);
smlAssert(thread->thread);
smlTrace(TRACE_INTERNAL, "%s: waiting for start", __func__);
while(!thread->started) {
smlTrace(TRACE_INTERNAL, "%s: checking condition", __func__);
g_cond_wait(thread->started_cond, thread->started_mutex);
}
smlTrace(TRACE_INTERNAL, "%s: condition received", __func__);
g_mutex_unlock(thread->started_mutex);
smlTrace(TRACE_EXIT, "%s", __func__);
}
void smlThreadStop(SmlThread *thread)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, thread);
smlAssert(thread);
GSource *source = g_idle_source_new();
g_source_set_callback(source, smlThreadStopCallback, thread, NULL);
g_source_attach(source, thread->context);
g_thread_join(thread->thread);
thread->thread = NULL;
g_source_unref(source);
smlTrace(TRACE_EXIT, "%s", __func__);
}
typedef struct SmlThreadFunctionContext {
GMutex *mutex;
GCond *cond;
SmlThreadCallFunctionType func;
gpointer data;
SmlBool result;
SmlError **error;
} SmlThreadFunctionContext;
gboolean smlThreadCallFunctionCallback(gpointer data)
{
smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
smlAssert(data);
SmlThreadFunctionContext *ctx = data;
ctx->result = ctx->func(ctx->data, ctx->error);
g_mutex_lock(ctx->mutex);
g_cond_signal(ctx->cond);
g_mutex_unlock(ctx->mutex);
/* This function should only run once. */
smlTrace(TRACE_EXIT, "%s", __func__);
return FALSE;
}
SmlBool smlThreadCallFunction(
SmlThread *thread,
SmlThreadCallFunctionType func,
gpointer data,
SmlError **error)
{
smlTrace(TRACE_ENTRY, "%s(%p => %p, %p, %p, %p)", __func__, thread, thread?thread->context:NULL, func, data, error);
CHECK_ERROR_REF
smlAssert(func);
/* initialize function context */
smlTrace(TRACE_INTERNAL, "%s: preparing context", __func__);
SmlThreadFunctionContext *ctx;
ctx = smlTryMalloc0(sizeof(SmlThreadFunctionContext), error);
if (!ctx)
goto error;
ctx->mutex = g_mutex_new();
if (!ctx->mutex) {
smlErrorSet(error, SML_ERROR_GENERIC, "Cannot create new mutex.");
goto error;
}
ctx->cond = g_cond_new();
if (!ctx->cond) {
smlErrorSet(error, SML_ERROR_GENERIC, "Cannot create new condition.");
goto error;
}
ctx->func = func;
ctx->data = data;
ctx->error = error;
/* prepare glib source */
smlTrace(TRACE_INTERNAL, "%s: preparing source", __func__);
GSource *source = g_idle_source_new();
g_source_set_callback(source, smlThreadCallFunctionCallback, ctx, NULL);
/* call function "synchronously" */
g_mutex_lock(ctx->mutex);
smlTrace(TRACE_INTERNAL, "%s: attach source", __func__);
g_source_attach(source, thread->context);
smlTrace(TRACE_INTERNAL, "%s: wait for condition", __func__);
g_cond_wait(ctx->cond, ctx->mutex);
smlTrace(TRACE_INTERNAL, "%s: get condition", __func__);
g_mutex_unlock(ctx->mutex);
/* cleanup */
smlTrace(TRACE_INTERNAL, "%s: cleanup", __func__);
SmlBool result = ctx->result;
g_source_unref(source);
g_mutex_free(ctx->mutex);
g_cond_free(ctx->cond);
smlSafeFree((gpointer *) &ctx);
/* return result */
smlTrace(TRACE_EXIT, "%s - %i", __func__, result);
return result;
error:
if (ctx->cond)
g_cond_free(ctx->cond);
if (ctx->mutex)
g_mutex_free(ctx->mutex);
if (ctx)
smlSafeFree((gpointer *) &ctx);
smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
return FALSE;
}
void smlSafeFree(gpointer *address)
{
smlAssert(address);
smlAssert(*address);
g_free(*address);
*address = NULL;
}
void smlSafeCFree(char **address)
{
smlSafeFree((gpointer *)address);
}
const char *smlGetLibraryVersion()
{
return PACKAGE_VERSION;
}
const char *smlGetLibrarySoName()
{
return PACKAGE_SONAME;
}
smlErrorPrint
const char * smlErrorPrint(SmlError **error)
Returns the message of the error.
Definition: sml_error.c:299
smlPrintHex
char * smlPrintHex(const char *data, int len)
Used for printing binary data in just hex.
Definition: sml_support.c:271
SmlThreadFunctionContext
Definition: sml_support.c:521
smlThreadCallFunction
SmlBool smlThreadCallFunction(SmlThread *thread, SmlThreadCallFunctionType func, gpointer data, SmlError **error)
Definition: sml_support.c:561
smlRandStr
char * smlRandStr(int maxlength, SmlBool exact)
Creates a random string.
Definition: sml_support.c:302
smlPrintBinary
char * smlPrintBinary(const char *data, int len)
Used for printing binary data.
Definition: sml_support.c:252
smlGetVersion
const char * smlGetVersion(void)
Gets the version of libsyncml as string.
Definition: sml_support.c:364
SmlThreadFunctionContext
struct SmlThreadFunctionContext SmlThreadFunctionContext
smlTrace
void smlTrace(SmlTraceType type, const char *message,...)
Used for tracing the application.
Definition: sml_support.c:120
smlThreadCallFunctionCallback
gboolean smlThreadCallFunctionCallback(gpointer data)
Definition: sml_support.c:534
smlTryMalloc0
void * smlTryMalloc0(long n_bytes, SmlError **error)
Safely mallocs.
Definition: sml_support.c:335
SmlThread
Definition: sml_support.h:52
smlErrorSet
void smlErrorSet(SmlError **error, SmlErrorType type, const char *format,...)
Sets the error.
Definition: sml_error.c:355
SmlError
Represent an error.
Definition: sml_error_internals.h:29