Sleds/K2Client/platform_binding.cpp

978 lines
25 KiB
C++
Raw Normal View History

2025-03-13 21:28:38 +00:00
/*
Copyright (c) 2013, 2014 IOnU Security Inc. All rights reserved. Copyright (c) 2015, 2016 Sequence Logic, Inc.
Created August 2013 by Kendrick Webster
K2Client/platform_binding.c - implementation of platform_binding.h
(see platform_binding.h for module description)
*/
#include <sys/types.h>
#ifndef WIN32
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <sys/time.h>
#else
#include <Winsock2.h>
#include <ws2tcpip.h>
#pragma comment (lib, "Ws2_32.lib")
#define strdup _strdup
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>
#ifdef __MACH__
#include <mach/mach_time.h>
#else
#include <time.h>
#endif
#include "constants.h"
#include "util.h"
#include "K2IPC.h"
#include "K2Client.h"
#include "proxy.h"
#include "servers.h"
#include "platform_binding.h"
/* internal plumbing constants and macros */
#define TIMER_MICROSECONDS (1000000 / K2CLI_TIMER_TICKS_PER_SECOND)
#define TIMER_MILLISECONDS (TIMER_MICROSECONDS / 1000)
#define TICK_MINIMUM_MS (TIMER_MILLISECONDS * 2 / 3)
#define LOCALHOST_ADDRESS "127.0.0.1"
/* data accessed only by this module's internal thread */
static SOCKET sockfd = INVALID_SOCKET; /* UDP socket file descriptor */
static struct sockaddr_in servaddr; /* address of K2Daemon server */
static struct sockaddr_in recvaddr; /* address of received packet */
static struct sockaddr_in selfaddr; /* address that <sockfd> is bound to */
static uint8_t buf[K2IPC_MAX_PACKET_SIZE]; /* UDP packet buffer for Rx and Tx */
static char* status_select_offices_copy; /* thread-safe copy of status_select_offices */
static int ticksSkipped; /* count of timer ticks skipped due to insufficient clock time elapsed (glitch detector) */
/* ID of this module's internal thread */
static pthread_t thread_id;
/* inter-thread flags */
static volatile int running; /* TRUE if internal thread was started and not yet stopped */
static volatile int end_thread; /* TRUE if internal thread should exit */
static volatile int login; /* TRUE if a K2Daemon login should be initiated */
static volatile int poll_db; /* TRUE if K2Daemon should be signalled to poll the database */
static volatile int get_stats; /* TRUE if K2Daemon should be signalled to return CPU and memory statistics */
static volatile int have_stats; /* TRUE if stats were received in response to an earlier get_stats */
static volatile int stats_pending; /* TRUE if waiting for stats from daemon */
static volatile int duplicate_urn; /* TRUE if URN is already in use by another client */
/* inter-thread flags for testing */
static volatile int newsock; /* triggers socket close/re-open */
static volatile int no_logout; /* stop internal thread without sending a logout packet */
/*
mutex for serializing access to the module interface functions so that they can
safely be called by multi-threaded library clients
this also ensures that this module's thread state and flags are consistent with
calls to K2_Start(...) et. al.
*/
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_MUTEX pthread_mutex_lock(&mutex);
#define UNLOCK_MUTEX pthread_mutex_unlock(&mutex);
/*
params and callbacks from higher-layer code, registered via K2_Start(...),
modified only when thread is not running
*/
static char urn[128];
static char servers[896];
static k2_message_callback_t upcallMessage;
/*
data and signals from higher-layer code that may be modified while the
thread is running (mutex protected)
*/
static pthread_mutex_t signal_mutex = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_SIGNAL_MUTEX pthread_mutex_lock(&signal_mutex);
#define UNLOCK_SIGNAL_MUTEX pthread_mutex_unlock(&signal_mutex);
static char* volatile status_select_offices; /* from K2_StatusSelect(offices) */
static volatile int status_select_offices_changed;
/*
data to higher-layer code
*/
static pthread_mutex_t stats_mutex = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_STATS_MUTEX pthread_mutex_lock(&stats_mutex);
#define UNLOCK_STATS_MUTEX pthread_mutex_unlock(&stats_mutex);
static char* volatile daemon_stats;
/* this module's callbacks, registered with (called by) lower-layer code */
static void onMessage(const char* info);
static void onEvent(unsigned int event);
static void onDeviceStatus(const char* status);
static void onStats(const char* stats);
static void sendUDP(unsigned int len);
static int stopwatch(int reset);
/* ====================== Implementation functions ====================== */
/*
Functions are grouped into purpose-specific sections with comments
above each section.
Lower-level functions are generally towards the top.
*/
/* ------------------------------ Timing ----------------------------- */
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
#define log_skipped_ticks(...)
#else
static void log_skipped_ticks(void)
{
if (ticksSkipped)
{
log_warn("skipped %d timer ticks", ticksSkipped);
ticksSkipped = 0;
}
}
#endif
static int get_uptime_ms()
{
#ifdef __MACH__
const int64_t k = 1000 * 1000;
static mach_timebase_info_data_t s;
if (0 == s.denom)
{
mach_timebase_info(&s);
}
return (int)((mach_absolute_time() * s.numer) / (k * s.denom));
#else
#ifdef WIN32
static double k;
static BOOL i = FALSE;
if (!i)
{
i = TRUE;
LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
k = (double)f.QuadPart / 1000.0;
}
LARGE_INTEGER t;
QueryPerformanceCounter(&t);
return (int)((double)t.QuadPart / k);
#else
const int64_t k = 1000;
const int64_t M = k * k;
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (int)((t.tv_sec * k) + (t.tv_nsec / M));
#endif
#endif
}
/* ----------------------- Message upcalls to lib client callback ----------------------- */
static void upcall_message_1(const char* message)
{
if (NULL != upcallMessage)
{
upcallMessage(message);
}
log_verbose("message: %s", message);
}
/* all message upcalls funnel through this function and its helper (above) */
static void upcall_message(const char* type, const char* info)
{
int n = (int)(strlen(type) + strlen(info) + 1);
char* buf = (char *)malloc(n);
snprintf(buf, n, "%s%s", type, info);
upcall_message_1(buf);
free(buf);
}
/* for generic K2Client messsages */
void upcall_k2cli_message(const char* message)
{
upcall_message(K2_MESSAGE_PREFIX_INTERNAL, message);
}
/* ----------------------- Error message upcalls ----------------------- */
void upcall_error_string(const char* where, const char* error_string)
{
int n = (int)(strlen(K2_MESSAGE_2ND_PREFIX_FOR_ERRORS) + strlen(error_string) + strlen(where) + 3);
char* buf = (char *)malloc(n);
snprintf(buf, n, "%s%s: %s", K2_MESSAGE_2ND_PREFIX_FOR_ERRORS, where, error_string);
upcall_k2cli_message(buf);
free(buf);
}
#ifdef WIN32
/**
* Format a Windows error number. Caller MUST call 'LocalFree' on the returned pointer.
*/
static LPTSTR formatWinError(DWORD nErr)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
nErr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0, NULL);
return (LPTSTR)lpMsgBuf;
}
#endif
#ifdef WIN32
void upcall_error(const char* where, int errnum)
{
LPTSTR pErrMsg = formatWinError(errnum);
const char ERR_MSG[] = "Socket error:\n";
size_t nMsgLen = strlen(ERR_MSG) + strlen(pErrMsg) + 1;
char* buf = (char *)malloc(nMsgLen);
snprintf(buf, nMsgLen, "%s%s", ERR_MSG, pErrMsg);
upcall_error_string(where, buf);
free(buf);
LocalFree((LPVOID)pErrMsg);
}
#else
void upcall_error(const char* where, int errnum)
{
upcall_error_string(where, strerror(errnum));
}
#endif
#ifdef WIN32
void upcall_errno(const char* where)
{
upcall_error(where, WSAGetLastError());
}
#else
void upcall_errno(const char* where)
{
upcall_error(where, errno);
}
#endif
/* ----------------------- Network, socket, address boilerplate ----------------------- */
static void init_server_address(void)
{
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(LOCALHOST_ADDRESS);
servaddr.sin_port = htons(K2IPC_DEFAULT_UDP_PORT);
}
static void address_to_string(const struct sockaddr_in* a, char* buf, unsigned int buflen)
{
if (htonl(INADDR_ANY) == a->sin_addr.s_addr)
{
snprintf(buf, buflen, "INADDR_ANY");
}
else
{
#if 0 /*
If or when we decide to support IPV6, we need to use inet_ntop(), however,
it is not availavble under Windows XP, so for now we use inet_ntoa().
The code in this #if 0 block works on all platforms except Windows XP.
*/
#ifdef WIN32
if (!inet_ntop(a->sin_family, (PVOID)&(a->sin_addr), buf, buflen))
#else
if (!inet_ntop(a->sin_family, (const void *)&(a->sin_addr), buf, buflen))
#endif
{
snprintf(buf, buflen, "inet_ntop ERROR");
}
#endif /* 0 */
snprintf(buf, buflen, "%s", inet_ntoa(a->sin_addr));
}
}
static void show_socket_address(void)
{
char a[ADDRESS_STRING_LENGTH], b[ADDRESS_STRING_LENGTH + 64];
address_to_string(&selfaddr, a, sizeof(a));
snprintf(b, sizeof(b), "socket %d bound to %s port %d", sockfd, a, ntohs(selfaddr.sin_port));
upcall_k2cli_message(b);
}
static int get_socket_address(void)
{
socklen_t addr_len = sizeof(selfaddr);
memset(&selfaddr, 0, sizeof(selfaddr));
selfaddr.sin_family = AF_INET;
selfaddr.sin_addr.s_addr = htonl(INADDR_ANY);
selfaddr.sin_port = htons(0);
if (0 != getsockname(sockfd, (struct sockaddr *)&selfaddr, &addr_len))
{
upcall_errno("getsockname");
return 1;
}
show_socket_address();
return 0;
}
static int set_socket_timeout(void)
{
#ifdef WIN32
//DWORD tv = TIMER_MICROSECONDS / 1000; /* milliseconds */
// Windows, being lame again. It seems that you cannot set recieve/send timeouts to be
// less then 500ms. We want 100ms (or the like).
// Set the socket to non-blocking...
u_long mode = 1;
if (0 != ioctlsocket(sockfd, FIONBIO, &mode))
{
upcall_errno("ioctlsocket(FIONBIO)");
return 1;
}
#else
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = TIMER_MICROSECONDS;
//#endif
if (0 != setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv)))
{
upcall_errno("setsockopt(SO_RCVTIMEO)");
return 1;
}
#endif
return 0;
}
static int bind_socket(void)
{
memset(&selfaddr, 0, sizeof(selfaddr));
selfaddr.sin_family = AF_INET;
selfaddr.sin_addr.s_addr = htonl(INADDR_ANY);
selfaddr.sin_port = htons(0);
if (0 != bind(sockfd, (struct sockaddr *)&selfaddr, sizeof(selfaddr)))
{
upcall_errno("bind");
return 1;
}
return get_socket_address();
}
static int init_socket(void)
{
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == sockfd)
{
upcall_errno("socket");
return 1;
}
#ifdef __APPLE__
int set = 1;
// Prevent SIGPIPE if socket closed in K2Daemon (iOSX)
if (0 != setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)))
{
upcall_errno("setsockopt(SO_NOSIGPIPE)");
return 1;
}
#endif
if (set_socket_timeout())
{
return 1;
}
if (bind_socket())
{
return 1;
}
return 0;
}
static int receive_packet(void)
{
socklen_t recvaddr_len;
recvaddr_len = sizeof(recvaddr);
int retVal = 0;
#ifdef WIN32
// Again, lame... See comments in 'set_socket_timeout'
WaitForSingleObject((HANDLE)sockfd, TIMER_MICROSECONDS / 1000);
retVal = (int)recvfrom(sockfd, (char *)buf, sizeof(buf), 0, (struct sockaddr *)&recvaddr, &recvaddr_len);
#else
retVal = (int)recvfrom(sockfd, (char *)buf, sizeof(buf), 0, (struct sockaddr *)&recvaddr, &recvaddr_len);
#endif
return retVal;
}
/* callback from <proxy_start>, sends UDP packet to self */
static void sendToSelf(uint8_t* buf, size_t len)
{
selfaddr.sin_addr.s_addr = inet_addr(LOCALHOST_ADDRESS);
sendto(
sockfd,
#ifdef WIN32
(const char *)buf, (int) len,
#else
buf, len,
#endif
0, (struct sockaddr *)&selfaddr, sizeof(selfaddr));
}
/* callback from <select_server>, called with address of the server that was selected */
static void onServerSelected(const void* address, const char* host)
{
const struct sockaddr_in* addr = (const struct sockaddr_in*)address;
servaddr.sin_addr.s_addr = addr->sin_addr.s_addr;
servaddr.sin_port = addr->sin_port;
if (is_using_proxy)
{
proxy_start(&servaddr, host, urn, sendToSelf);
}
else
{
proxy_stop();
}
}
/* sets <servaddr> to the address of the next server to try to use */
static void next_server_address(void)
{
/*
As a default (in case host lookup fails, etc.) set the server address to loopback.
This will cause this module to receive a K2CLI_ERROR_NOT_CONNECTED after a delay of
a few seconds. Receiving that event will cause this module to pick a new server.
The delay is good. It prevents us from hammering the message upcall with error
messages when the network is down or the server list is bad.
*/
init_server_address();
/* randomly selects a server, tries direct UDP before proxy */
select_server(onServerSelected);
}
/* ----------------------- Main thread procedure loop ----------------------- */
static void timer_tick(void)
{
#define FINISH_LOGIN_TIMEOUT_TICKS 50
static int finish_login = 0;
static int n = 0;
char* p;
seed_random();
if (newsock)
{
newsock = FALSE;
#ifdef WIN32
closesocket(sockfd);
#else
close(sockfd);
#endif
init_socket();
}
if (login)
{
login = FALSE;
next_server_address();
seed_random();
finish_login = FINISH_LOGIN_TIMEOUT_TICKS;
}
if (finish_login)
{
if (!is_using_proxy || is_proxy_session_active)
{
finish_login = 0;
K2Cli_Login();
if (status_select_offices_copy && (0 < strlen(status_select_offices_copy)))
{
K2Cli_StatusSelect(status_select_offices_copy);
}
}
else if (0 == --finish_login)
{
log_warn("timed out waiting for proxy connect or init");
K2Cli_Login(); // lets state machine continue, it will trigger a re-connect to a different server
}
}
if (poll_db)
{
poll_db = FALSE;
K2Cli_Poll_DB();
}
if (status_select_offices_changed)
{
LOCK_SIGNAL_MUTEX
{
p = status_select_offices;
status_select_offices = NULL;
status_select_offices_changed = FALSE;
}
UNLOCK_SIGNAL_MUTEX
free(status_select_offices_copy);
status_select_offices_copy = p;
K2Cli_StatusSelect(status_select_offices_copy);
}
if (get_stats)
{
get_stats = FALSE;
K2Cli_QueryStats();
}
if (++n >= K2CLI_TIMER_TICKS_PER_SECOND) // once per second
{
n = 0;
log_skipped_ticks();
}
K2Cli_Timer();
}
static void* threadproc(void* arg)
{
int n, t0 = 0, t, fatal_error = FALSE;
#ifdef WIN32
/* Initialize the socket library */
WSADATA wsaData;
int nRes = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
if (nRes != 0)
{
upcall_error("WSAStartup(2.2)", nRes);
return NULL;
}
#endif
init_server_address();
if (0 != init_socket())
{
#ifdef WIN32
/* Clean up the socket library */
::WSACleanup();
#endif
return NULL;
}
seed_random_with_bytes(urn, strlen(urn));
seed_random();
set_servers(servers);
/* Start client IPC loop */
K2Cli_Initialize(buf, urn, onMessage, onEvent, onDeviceStatus, onStats, sendUDP, stopwatch);
login = TRUE;
timer_tick();
while (!(end_thread || fatal_error))
{
n = receive_packet();
if (n <= 0)
{
#ifdef WIN32
int nError = WSAGetLastError();
if (WSAEWOULDBLOCK == nError)
#else
if (EAGAIN == errno)
#endif
{
t = get_uptime_ms();
if ((t < t0) || (t >= (t0 + TICK_MINIMUM_MS)))
{
t0 = t;
timer_tick();
}
else
{
++ticksSkipped;
}
}
else
{
#ifdef WIN32
if (WSAECONNRESET == nError) {
closesocket(sockfd);
#else
if (ECONNRESET == errno) {
close(sockfd);
#endif
upcall_k2cli_message("connection reset");
init_socket();
} else {
#ifdef WIN32
upcall_error("recvfrom", nError);
#else
upcall_errno("recvfrom");
#endif
login = TRUE; // lets state machine continue, it will trigger a re-connect to a different server
//fatal_error = TRUE;
}
}
}
else if (!end_thread)
{
K2Cli_HandlePacket(n);
}
}
if (!no_logout)
{
K2Cli_Logout();
K2Cli_Timer();
}
proxy_stop();
clear_servers();
#ifdef WIN32
if (0 != closesocket(sockfd))
#else
if (0 != close(sockfd))
#endif
{
upcall_errno("close(sockfd)");
}
sockfd = INVALID_SOCKET;
#ifdef WIN32
/* Clean up the socket library */
::WSACleanup();
#endif
/*
Normally the <running> flag is updated by the module interface functions that
start or stop the thread. Fatal errors can also cause the thread to stop.
*/
if (fatal_error && !end_thread)
{
running = FALSE;
}
return NULL;
}
/* ------------------ Module interface function helpers ------------------ */
/* These functions run in the library client's thread with this module's mutex locked */
static void start_thread(void)
{
int error = pthread_create(&thread_id, NULL, threadproc, NULL);
if (error)
{
upcall_error("pthread_create", error);
}
else
{
running = TRUE;
}
}
static void stop_thread(void)
{
int error;
char buf[1] = {0};
end_thread = TRUE;
/* send UDP packet to self to awaken thread sooner than timer expiry */
selfaddr.sin_addr.s_addr = inet_addr(LOCALHOST_ADDRESS);
sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&selfaddr, sizeof(selfaddr));
error = pthread_join(thread_id, NULL);
if (error)
{
upcall_error("pthread_join", error);
}
running = end_thread = FALSE;
}
/* runs only when this module's thread is not running, modifies <urn> and <servers> */
static void start(const char* urn_, const char* servers_, k2_message_callback_t onMessage)
{
duplicate_urn = FALSE;
upcallMessage = onMessage;
if (safe_strcpy(urn, sizeof(urn), urn_))
{
upcall_error_string("K2_Start", "URN too long");
}
if (safe_strcpy(servers, sizeof(servers), servers_))
{
upcall_error_string("K2_Start", "Servers string too long");
}
start_thread();
}
/* ----------------------- Callbacks from K2Client.c ----------------------- */
static void onMessage(const char* info)
{
upcall_message(K2_MESSAGE_PREFIX_FROM_CLOUD, info);
}
static void onEvent(unsigned int event)
{
char a[ADDRESS_STRING_LENGTH];
char b[ADDRESS_STRING_LENGTH + 128];
const char* event_text = "(unknown event)";
switch (event)
{
#define X(name, text) case name: event_text = text; break;
K2CLI_EVENTS
#undef X
}
/* translate event to text for message upcall */
switch (event)
{
case K2CLI_ERROR_BAD_OPCODE:
case K2CLI_ERROR_BAD_MSG_SEQUENCE:
case K2CLI_ERROR_BAD_ACK_SEQUENCE:
case K2CLI_ERROR_WRONG_URN:
case K2CLI_ERROR_MISSING_NUL:
case K2CLI_INFO_SPURIOUS_PACKET:
address_to_string(&recvaddr, a, sizeof(a));
snprintf(b, sizeof(b), "%s (from %s port %d)", event_text, a, ntohs(recvaddr.sin_port));
upcall_k2cli_message(b);
break;
default:
upcall_k2cli_message(event_text);
break;
}
/* take special actions based on certain events */
switch (event)
{
case K2CLI_ERROR_NOT_CONNECTED:
if (!duplicate_urn)
{
login = TRUE; /* causes server to be randomly selected from list of servers */
}
break;
case K2CLI_ERROR_DUPLICATE_URN:
duplicate_urn = TRUE; /* avoids endless loop attempting server connect */
break;
}
if (stats_pending && (K2CLI_ERROR_NOT_CONNECTED <= event))
{
onStats(NULL);
}
}
static void onDeviceStatus(const char* status)
{
upcall_message(K2_MESSAGE_PREFIX_DEVICE_STATUS, status);
}
static void onStats(const char* stats)
{
char *p, *q;
if (stats)
{
q = strdup(stats);
}
else
{
q = NULL;
}
LOCK_STATS_MUTEX
{
p = daemon_stats;
daemon_stats = q;
}
UNLOCK_STATS_MUTEX
stats_pending = FALSE;
have_stats = TRUE;
free(p);
}
static void sendUDP(unsigned int len)
{
if (is_using_proxy)
{
proxy_send(buf, len);
}
else
{
sendto(
sockfd,
// Prevent SIGPIPE if socket closed in K2Daemon (Android and Linux)
#ifdef WIN32
(const char *)buf, len, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
#elif defined (__APPLE__)
buf, len, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
#else
buf, len, MSG_NOSIGNAL, (struct sockaddr *)&servaddr, sizeof(servaddr));
#endif
}
}
static int stopwatch(int reset)
{
static time_t t0;
time_t t = time(NULL);
if (reset || (0 == t0))
{
t0 = t;
}
return (int)(t - t0);
}
/* ----------------------- Module interface functions for testing (unpublicized) ----------------------- */
/* close old socket and open a new one (to get a new dynamic local port number) */
void K2_Newsock(void)
{
newsock = TRUE;
}
/* stop the client thread without loggging out (produces a heartbeat timeout) */
void K2_Stop_NoLogout(void)
{
no_logout = TRUE;
K2_Stop();
}
/* ----------------------- Module interface functions ----------------------- */
void K2_Start(const char* urn_, const char* servers_, k2_message_callback_t onMessage)
{
initialize_logging();
log_info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
log_info("K2_Start");
LOCK_MUTEX
{
if (running)
{
stop_thread();
}
start(urn_, servers_, onMessage);
}
UNLOCK_MUTEX
}
void K2_Stop(void)
{
log_info("K2_Stop");
LOCK_MUTEX
{
if (running)
{
stop_thread();
}
}
UNLOCK_MUTEX
log_info("################################################################################");
finalize_logging();
}
void K2_Restart(void)
{
initialize_logging();
log_info("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
log_info("K2_Restart");
LOCK_MUTEX
{
if (!running)
{
start_thread();
}
}
UNLOCK_MUTEX
}
void K2_StatusSelect(const char* offices)
{
char *p, *q;
if (offices && (0 < strlen(offices)))
{
q = strdup(offices);
}
else
{
q = NULL;
}
LOCK_MUTEX
{
LOCK_SIGNAL_MUTEX
{
p = status_select_offices;
status_select_offices = q;
status_select_offices_changed = TRUE;
}
UNLOCK_SIGNAL_MUTEX
}
UNLOCK_MUTEX
free(p);
}
void K2_Poll_DB(void)
{
poll_db = TRUE;
}
int K2_GetStats(char* buf, unsigned int bufsize)
{
int timeout = 700; /* message retry timeout is 6.3 seconds, if > 7 seconds, something is really wrong */
char* stats = NULL;
LOCK_MUTEX
{
have_stats = FALSE;
stats_pending = TRUE;
get_stats = TRUE;
while (running && !have_stats && timeout--)
{
#ifdef WIN32
Sleep(10);
#else
usleep(10000);
#endif
}
LOCK_STATS_MUTEX
{
stats = daemon_stats;
daemon_stats = NULL;
}
UNLOCK_STATS_MUTEX
}
UNLOCK_MUTEX
snprintf(buf, bufsize, "%s", stats ? stats : "");
free(stats);
return stats ? TRUE : FALSE;
}
void K2_SetLogVerbosityLevel(log_verbosity_level_t level)
{
switch (level)
{
case LOG_LEVEL_FATAL: set_log_level(LOG_FATAL); break;
case LOG_LEVEL_ERROR: set_log_level(LOG_ERROR); break;
case LOG_LEVEL_WARNING: set_log_level(LOG_WARNING); break;
case LOG_LEVEL_INFORMATIONAL: set_log_level(LOG_INFORMATIONAL); break;
case LOG_LEVEL_DEBUG: set_log_level(LOG_DEBUG); break;
case LOG_LEVEL_VERBOSE: set_log_level(LOG_VERBOSE); break;
default:
log_error("Log verbosity level %d is unknown, ignoring call to K2_SetLogVerbosityLevel", level);
break;
}
}