--- src/base/Makefile.am | 2 +- src/base/logtrace.c | 268 --------------------------------------------------- src/base/logtrace.cc | 253 ++++++++++++++++++++++++++++++++++++++++++++++++ src/base/logtrace.h | 88 ++++++++--------- 4 files changed, 298 insertions(+), 313 deletions(-) delete mode 100644 src/base/logtrace.c create mode 100644 src/base/logtrace.cc
diff --git a/src/base/Makefile.am b/src/base/Makefile.am index f278a8ffb..f1fb46abf 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -47,7 +47,7 @@ lib_libopensaf_core_la_SOURCES += \ src/base/hj_tmr.c \ src/base/hj_ubaid.c \ src/base/log_message.cc \ - src/base/logtrace.c \ + src/base/logtrace.cc \ src/base/mutex.cc \ src/base/ncs_main_pub.c \ src/base/ncs_sprr.c \ diff --git a/src/base/logtrace.c b/src/base/logtrace.c deleted file mode 100644 index fb145d9b8..000000000 --- a/src/base/logtrace.c +++ /dev/null @@ -1,268 +0,0 @@ -/* -*- OpenSAF -*- - * - * (C) Copyright 2008-2010 The OpenSAF Foundation - * Copyright Ericsson AB 2017 - All Rights Reserved. - * - * 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. This file and program are licensed - * under the GNU Lesser General Public License Version 2.1, February 1999. - * The complete license can be accessed from the following location: - * http://opensource.org/licenses/lgpl-license.php - * See the Copying file included with the OpenSAF distribution for full - * licensing terms. - * - * Author(s): Ericsson AB - * Wind River Systems - * - */ - -#include <stdio.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <stdarg.h> -#include <syslog.h> -#include <pthread.h> -#include <assert.h> -#include <stdlib.h> -#include <sys/time.h> -#include <time.h> -#include <string.h> -#include <sys/syscall.h> -#include <unistd.h> -#include <signal.h> -#include <errno.h> -#include <limits.h> -#include "osaf/configmake.h" - -#include "base/logtrace.h" - -static int trace_fd = -1; -static int category_mask; -static char *prefix_name[] = {"EM", "AL", "CR", "ER", "WA", "NO", "IN", - "DB", "TR", "T1", "T2", "T3", "T4", "T5", - "T6", "T7", "T8", ">>", "<<"}; - -static const char *ident; -static const char *pathname; -static int logmask; - -static pid_t gettid(void) { return syscall(SYS_gettid); } - -/** - * USR2 signal handler to enable/disable trace (toggle) - * @param sig - */ -static void sigusr2_handler(int sig) -{ - unsigned int trace_mask; - - if (category_mask == 0) - trace_mask = CATEGORY_ALL; - else - trace_mask = 0; - - trace_category_set(trace_mask); -} - -/** - * HUP signal handler to toggle info log level on/off - * @param sig - */ -static void sighup_handler(int sig) -{ - if ((logmask & LOG_MASK(LOG_INFO)) & LOG_MASK(LOG_INFO)) { - logmask = LOG_UPTO(LOG_NOTICE); - syslog(LOG_NOTICE, "logtrace: info level disabled"); - } else { - logmask = LOG_UPTO(LOG_INFO); - syslog(LOG_NOTICE, "logtrace: info level enabled"); - } - - setlogmask(logmask); -} - -void output_(const char *file, unsigned int line, int priority, int category, - const char *format, va_list ap) -{ - int i; - struct timeval tv; - char preamble[512]; - char log_string[1024]; - struct tm *tstamp_data, tm_info; - - assert(priority <= LOG_DEBUG && category < CAT_MAX); - - /* Create a nice syslog looking date string */ - gettimeofday(&tv, NULL); - tstamp_data = localtime_r(&tv.tv_sec, &tm_info); - osafassert(tstamp_data); - - strftime(log_string, sizeof(log_string), "%b %e %k:%M:%S", tstamp_data); - i = snprintf(preamble, sizeof(preamble), "%s.%06ld %s ", log_string, - tv.tv_usec, ident); - - snprintf(&preamble[i], sizeof(preamble) - i, "[%d:%d:%s:%04u] %s %s", - getpid(), gettid(), file, line, - prefix_name[priority + category], format); - i = vsnprintf(log_string, sizeof(log_string), preamble, ap); - - /* Check if the logtrace user had passed message length >= logtrace - * array limit of 1023. If so, prepare/add space for line feed and - * truncation character 'T'. - */ - if (i >= 1023) { - i = 1023; - log_string[i - 2] = 'T'; - log_string[i - 1] = '\n'; - log_string[i] = '\0'; // - } else { - /* Add line feed if not there already */ - if (log_string[i - 1] != '\n') { - log_string[i] = '\n'; - log_string[i + 1] = '\0'; - i++; - } - } - - /* If we got here without a file descriptor, trace was enabled in - * runtime, open the file */ - if (trace_fd == -1) { - trace_fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (trace_fd < 0) { - syslog(LOG_ERR, "logtrace: open failed, file=%s (%s)", - pathname, strerror(errno)); - return; - } - } - -write_retry: - i = write(trace_fd, log_string, i); - if (i == -1) { - if (errno == EAGAIN) - goto write_retry; - else - syslog(LOG_ERR, "logtrace: write failed, %s", - strerror(errno)); - } -} - -void _logtrace_log(const char *file, unsigned int line, int priority, - const char *format, ...) -{ - va_list ap; - va_list ap2; - - /* Uncondionally send to syslog */ - va_start(ap, format); - va_copy(ap2, ap); - - char *tmp_str = NULL; - int tmp_str_len = 0; - - if ((tmp_str_len = asprintf(&tmp_str, "%s %s", prefix_name[priority], - format)) < 0) { - vsyslog(priority, format, ap); - } else { - vsyslog(priority, tmp_str, ap); - free(tmp_str); - } - - /* Only output to file if configured to */ - if (!(category_mask & (1 << CAT_LOG))) - goto done; - - output_(file, line, priority, CAT_LOG, format, ap2); - -done: - va_end(ap); - va_end(ap2); -} - -bool is_trace_enabled_(unsigned int category) -{ - /* Filter on category */ - return (category_mask & (1 << category)) != 0; -} - -void _logtrace_trace(const char *file, unsigned int line, unsigned int category, - const char *format, ...) -{ - va_list ap; - - if (is_trace_enabled_(category) == false) - return; - - va_start(ap, format); - output_(file, line, LOG_DEBUG, category, format, ap); - va_end(ap); -} - -int logtrace_init(const char *_ident, const char *_pathname, unsigned int _mask) -{ - ident = _ident; - pathname = strdup(_pathname); - category_mask = _mask; - - tzset(); - - if (_mask != 0) { - trace_fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (trace_fd < 0) { - syslog(LOG_ERR, "logtrace: open failed, file=%s (%s)", - pathname, strerror(errno)); - return -1; - } - - syslog(LOG_INFO, - "logtrace: trace enabled to file %s, mask=0x%x", - pathname, category_mask); - } - - return 0; -} - -int logtrace_init_daemon(const char *_ident, const char *_pathname, - unsigned int _tracemask, int _logmask) -{ - if (signal(SIGUSR2, sigusr2_handler) == SIG_ERR) { - syslog(LOG_ERR, "logtrace: registering SIGUSR2 failed, (%s)", - strerror(errno)); - return -1; - } - - setlogmask(_logmask); - - if (signal(SIGHUP, sighup_handler) == SIG_ERR) { - syslog(LOG_ERR, "logtrace: registering SIGHUP failed, (%s)", - strerror(errno)); - return -1; - } - - logmask = _logmask; - - return logtrace_init(_ident, _pathname, _tracemask); -} - -int trace_category_set(unsigned int mask) -{ - category_mask = mask; - - if (category_mask == 0) { - if (trace_fd != -1) { - (void)close(trace_fd); - trace_fd = -1; - } - syslog(LOG_INFO, "logtrace: trace disabled"); - } else - syslog(LOG_INFO, - "logtrace: trace enabled to file %s, mask=0x%x", - pathname, category_mask); - - return 0; -} - -unsigned int trace_category_get(void) { return category_mask; } diff --git a/src/base/logtrace.cc b/src/base/logtrace.cc new file mode 100644 index 000000000..cff1dcf8e --- /dev/null +++ b/src/base/logtrace.cc @@ -0,0 +1,253 @@ +/* -*- OpenSAF -*- + * + * (C) Copyright 2008-2010 The OpenSAF Foundation + * Copyright Ericsson AB 2017 - All Rights Reserved. + * + * 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. This file and program are licensed + * under the GNU Lesser General Public License Version 2.1, February 1999. + * The complete license can be accessed from the following location: + * http://opensource.org/licenses/lgpl-license.php + * See the Copying file included with the OpenSAF distribution for full + * licensing terms. + * + * Author(s): Ericsson AB + * Wind River Systems + * + */ + +#include "base/logtrace.h" +#include <fcntl.h> +#include <limits.h> +#include <pthread.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include <cassert> +#include <cerrno> +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include "osaf/configmake.h" + +static int trace_fd = -1; +static int category_mask; +static const char *prefix_name[] = {"EM", "AL", "CR", "ER", "WA", "NO", "IN", + "DB", "TR", "T1", "T2", "T3", "T4", "T5", + "T6", "T7", "T8", ">>", "<<"}; + +static const char *ident; +static const char *pathname; +static int logmask; + +static pid_t gettid() { return syscall(SYS_gettid); } + +/** + * USR2 signal handler to enable/disable trace (toggle) + * @param sig + */ +static void sigusr2_handler(int sig) { + unsigned trace_mask; + + if (category_mask == 0) + trace_mask = CATEGORY_ALL; + else + trace_mask = 0; + + trace_category_set(trace_mask); +} + +/** + * HUP signal handler to toggle info log level on/off + * @param sig + */ +static void sighup_handler(int sig) { + if ((logmask & LOG_MASK(LOG_INFO)) & LOG_MASK(LOG_INFO)) { + logmask = LOG_UPTO(LOG_NOTICE); + syslog(LOG_NOTICE, "logtrace: info level disabled"); + } else { + logmask = LOG_UPTO(LOG_INFO); + syslog(LOG_NOTICE, "logtrace: info level enabled"); + } + + setlogmask(logmask); +} + +void logtrace_output(const char *file, unsigned line, int priority, + int category, const char *format, va_list ap) { + int i; + struct timeval tv; + char preamble[512]; + char log_string[1024]; + struct tm *tstamp_data, tm_info; + + assert(priority <= LOG_DEBUG && category < CAT_MAX); + + /* Create a nice syslog looking date string */ + gettimeofday(&tv, nullptr); + tstamp_data = localtime_r(&tv.tv_sec, &tm_info); + osafassert(tstamp_data); + + strftime(log_string, sizeof(log_string), "%b %e %k:%M:%S", tstamp_data); + i = snprintf(preamble, sizeof(preamble), "%s.%06ld %s ", log_string, + tv.tv_usec, ident); + + snprintf(&preamble[i], sizeof(preamble) - i, "[%d:%d:%s:%04u] %s %s", + getpid(), gettid(), file, line, prefix_name[priority + category], + format); + i = vsnprintf(log_string, sizeof(log_string), preamble, ap); + + /* Check if the logtrace user had passed message length >= logtrace + * array limit of 1023. If so, prepare/add space for line feed and + * truncation character 'T'. + */ + if (i >= 1023) { + i = 1023; + log_string[i - 2] = 'T'; + log_string[i - 1] = '\n'; + log_string[i] = '\0'; // + } else { + /* Add line feed if not there already */ + if (log_string[i - 1] != '\n') { + log_string[i] = '\n'; + log_string[i + 1] = '\0'; + i++; + } + } + + /* If we got here without a file descriptor, trace was enabled in + * runtime, open the file */ + if (trace_fd == -1) { + trace_fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (trace_fd < 0) { + syslog(LOG_ERR, "logtrace: open failed, file=%s (%s)", pathname, + strerror(errno)); + return; + } + } + +write_retry: + i = write(trace_fd, log_string, i); + if (i == -1) { + if (errno == EAGAIN) + goto write_retry; + else + syslog(LOG_ERR, "logtrace: write failed, %s", strerror(errno)); + } +} + +void logtrace_log(const char *file, unsigned line, int priority, + const char *format, ...) { + va_list ap; + va_list ap2; + + /* Uncondionally send to syslog */ + va_start(ap, format); + va_copy(ap2, ap); + + char *tmp_str = nullptr; + int tmp_str_len = 0; + + if ((tmp_str_len = + asprintf(&tmp_str, "%s %s", prefix_name[priority], format)) < 0) { + vsyslog(priority, format, ap); + } else { + vsyslog(priority, tmp_str, ap); + free(tmp_str); + } + + /* Only output to file if configured to */ + if (!(category_mask & (1 << CAT_LOG))) goto done; + + logtrace_output(file, line, priority, CAT_LOG, format, ap2); + +done: + va_end(ap); + va_end(ap2); +} + +bool is_logtrace_enabled(unsigned category) { + /* Filter on category */ + return (category_mask & (1 << category)) != 0; +} + +void logtrace_trace(const char *file, unsigned line, unsigned category, + const char *format, ...) { + va_list ap; + + if (is_logtrace_enabled(category) == false) return; + + va_start(ap, format); + logtrace_output(file, line, LOG_DEBUG, category, format, ap); + va_end(ap); +} + +int logtrace_init(const char *_ident, const char *_pathname, unsigned _mask) { + ident = _ident; + pathname = strdup(_pathname); + category_mask = _mask; + + tzset(); + + if (_mask != 0) { + trace_fd = open(pathname, O_WRONLY | O_APPEND | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (trace_fd < 0) { + syslog(LOG_ERR, "logtrace: open failed, file=%s (%s)", pathname, + strerror(errno)); + return -1; + } + + syslog(LOG_INFO, "logtrace: trace enabled to file %s, mask=0x%x", pathname, + category_mask); + } + + return 0; +} + +int logtrace_init_daemon(const char *_ident, const char *_pathname, + unsigned _tracemask, int _logmask) { + if (signal(SIGUSR2, sigusr2_handler) == SIG_ERR) { + syslog(LOG_ERR, "logtrace: registering SIGUSR2 failed, (%s)", + strerror(errno)); + return -1; + } + + setlogmask(_logmask); + + if (signal(SIGHUP, sighup_handler) == SIG_ERR) { + syslog(LOG_ERR, "logtrace: registering SIGHUP failed, (%s)", + strerror(errno)); + return -1; + } + + logmask = _logmask; + + return logtrace_init(_ident, _pathname, _tracemask); +} + +int trace_category_set(unsigned mask) { + category_mask = mask; + + if (category_mask == 0) { + if (trace_fd != -1) { + close(trace_fd); + trace_fd = -1; + } + syslog(LOG_INFO, "logtrace: trace disabled"); + } else + syslog(LOG_INFO, "logtrace: trace enabled to file %s, mask=0x%x", pathname, + category_mask); + + return 0; +} + +unsigned trace_category_get(void) { return category_mask; } diff --git a/src/base/logtrace.h b/src/base/logtrace.h index 458400945..f465f147b 100644 --- a/src/base/logtrace.h +++ b/src/base/logtrace.h @@ -75,7 +75,7 @@ enum logtrace_categories { * @return int - 0 if OK, -1 otherwise */ extern int logtrace_init(const char *ident, const char *pathname, - unsigned int mask); + unsigned mask); /** * logtrace_init_daemon - Initialize the logtrace system for daemons @@ -93,7 +93,7 @@ extern int logtrace_init(const char *ident, const char *pathname, * @return int - 0 if OK, -1 otherwise */ extern int logtrace_init_daemon(const char *ident, const char *pathname, - unsigned int tracemask, int logmask); + unsigned tracemask, int logmask); /** * trace_category_set - Set the mask used for trace filtering. @@ -111,92 +111,92 @@ extern int logtrace_init_daemon(const char *ident, const char *pathname, * * @return int - 0 if OK, -1 otherwise */ -extern int trace_category_set(unsigned int category_mask); +extern int trace_category_set(unsigned category_mask); /** * trace_category_get - Get the current mask used for trace filtering. * * @return int - The filtering mask value */ -extern unsigned int trace_category_get(void); +extern unsigned trace_category_get(void); /* internal functions, do not use directly */ -extern void _logtrace_log(const char *file, unsigned int line, int priority, - const char *format, ...) +extern void logtrace_log(const char *file, unsigned line, int priority, + const char *format, ...) __attribute__((format(printf, 4, 5))); -extern void _logtrace_trace(const char *file, unsigned int line, - unsigned int category, const char *format, ...) +extern void logtrace_trace(const char *file, unsigned line, unsigned category, + const char *format, ...) __attribute__((format(printf, 4, 5))); -extern bool is_trace_enabled_(unsigned int category); -extern void output_(const char *file, unsigned int line, int priority, - int category, const char *format, va_list ap); +extern bool is_logtrace_enabled(unsigned category); +extern void logtrace_output(const char *file, unsigned line, int priority, + int category, const char *format, va_list ap); /* LOG API. Use same levels as syslog */ #define LOG_EM(format, args...) \ - _logtrace_log(__FILE__, __LINE__, LOG_EMERG, (format), ##args) + logtrace_log(__FILE__, __LINE__, LOG_EMERG, (format), ##args) #define LOG_AL(format, args...) \ - _logtrace_log(__FILE__, __LINE__, LOG_ALERT, (format), ##args) + logtrace_log(__FILE__, __LINE__, LOG_ALERT, (format), ##args) #define LOG_CR(format, args...) \ - _logtrace_log(__FILE__, __LINE__, LOG_CRIT, (format), ##args) + logtrace_log(__FILE__, __LINE__, LOG_CRIT, (format), ##args) #define LOG_ER(format, args...) \ - _logtrace_log(__FILE__, __LINE__, LOG_ERR, (format), ##args) + logtrace_log(__FILE__, __LINE__, LOG_ERR, (format), ##args) #define LOG_WA(format, args...) \ - _logtrace_log(__FILE__, __LINE__, LOG_WARNING, (format), ##args) + logtrace_log(__FILE__, __LINE__, LOG_WARNING, (format), ##args) #define LOG_NO(format, args...) \ - _logtrace_log(__FILE__, __LINE__, LOG_NOTICE, (format), ##args) + logtrace_log(__FILE__, __LINE__, LOG_NOTICE, (format), ##args) #define LOG_IN(format, args...) \ - _logtrace_log(__FILE__, __LINE__, LOG_INFO, (format), ##args) + logtrace_log(__FILE__, __LINE__, LOG_INFO, (format), ##args) /* TRACE API. */ #define TRACE(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE, (format), ##args) #define TRACE_1(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE1, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE1, (format), ##args) #define TRACE_2(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE2, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE2, (format), ##args) #define TRACE_3(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE3, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE3, (format), ##args) #define TRACE_4(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE4, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE4, (format), ##args) #define TRACE_5(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE5, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE5, (format), ##args) #define TRACE_6(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE6, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE6, (format), ##args) #define TRACE_7(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE7, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE7, (format), ##args) #define TRACE_8(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE8, (format), ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE8, (format), ##args) #ifdef __cplusplus class Trace { public: Trace() {} ~Trace() { - if (!trace_leave_called && is_trace_enabled_(CAT_TRACE_LEAVE)) { + if (!trace_leave_called && is_logtrace_enabled(CAT_TRACE_LEAVE)) { va_list ap{}; - output_(file_, 0, LOG_DEBUG, CAT_TRACE_LEAVE, function_, ap); + logtrace_output(file_, 0, LOG_DEBUG, CAT_TRACE_LEAVE, function_, ap); } } - void trace(const char *file, const char *function, unsigned int line, - unsigned int category, const char *format, ...) { + void trace(const char *file, const char *function, unsigned line, + unsigned category, const char *format, ...) { va_list ap; - if (is_trace_enabled_(category)) { + if (is_logtrace_enabled(category)) { file_ = file; function_ = function; va_start(ap, format); - output_(file, line, LOG_DEBUG, category, format, ap); + logtrace_output(file, line, LOG_DEBUG, category, format, ap); va_end(ap); } } - void trace_leave(const char *file, unsigned int line, unsigned int category, + void trace_leave(const char *file, unsigned line, unsigned category, const char *format, ...) { va_list ap; - if (is_trace_enabled_(category)) { + if (is_logtrace_enabled(category)) { va_start(ap, format); trace_leave_called = true; - output_(file, line, LOG_DEBUG, category, format, ap); + logtrace_output(file, line, LOG_DEBUG, category, format, ap); va_end(ap); } } @@ -223,15 +223,15 @@ class Trace { __FUNCTION__, ##args) #else #define TRACE_ENTER() \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE_ENTER, "%s ", __FUNCTION__) -#define TRACE_ENTER2(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE_ENTER, "%s: " format, \ - __FUNCTION__, ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE_ENTER, "%s ", __FUNCTION__) +#define TRACE_ENTER2(format, args...) \ + logtrace_trace(__FILE__, __LINE__, CAT_TRACE_ENTER, "%s: " format, \ + __FUNCTION__, ##args) #define TRACE_LEAVE() \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE_LEAVE, "%s ", __FUNCTION__) -#define TRACE_LEAVE2(format, args...) \ - _logtrace_trace(__FILE__, __LINE__, CAT_TRACE_LEAVE, "%s: " format, \ - __FUNCTION__, ##args) + logtrace_trace(__FILE__, __LINE__, CAT_TRACE_LEAVE, "%s ", __FUNCTION__) +#define TRACE_LEAVE2(format, args...) \ + logtrace_trace(__FILE__, __LINE__, CAT_TRACE_LEAVE, "%s: " format, \ + __FUNCTION__, ##args) #endif #ifdef __cplusplus -- 2.13.3 ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot _______________________________________________ Opensaf-devel mailing list Opensaf-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/opensaf-devel