|
/* vim: set tw=78 ts=8 noet si: */ |
|
/*! |
|
* VRT WideSky Hub Demo Firmware System Logger |
|
* (C) 2017 VRT Systems |
|
*/ |
|
|
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
|
|
#include "hostname.h" |
|
#include "syslog.h" |
|
#include "fs.h" |
|
|
|
#include <stdlib.h> |
|
#include <sys/time.h> |
|
#include <time.h> |
|
#include <errno.h> |
|
#include <stdarg.h> |
|
|
|
#include <openthread/ip6.h> |
|
#include <openthread/udp.h> |
|
#include <openthread/thread.h> |
|
#include <openthread/openthread.h> |
|
|
|
#define SYSLOG_BUF_SZ (512) |
|
|
|
#define SYSLOG_CFG "syslog.cfg" |
|
#define LOGFILE_PRI "logfile.1" |
|
#define LOGFILE_SEC "logfile.2" |
|
|
|
#define SYSLOG_INIT_DELAY (5) |
|
#define SYSLOG_NETWORK_DELAY (10) |
|
#define SYSLOG_CHECK_DELAY (60) |
|
#define SYSLOG_MAX_BUFFER_USE_PERCENT (10) |
|
|
|
#define SYSLOG_PERSIST_SEVERITY (4) |
|
|
|
/*! |
|
* System logger re-sender file descriptor. |
|
*/ |
|
static int syslog_resend_fd; |
|
|
|
/*! |
|
* System logger timer. |
|
*/ |
|
static uint8_t syslog_timer; |
|
|
|
/*! |
|
* Configuration data |
|
*/ |
|
struct syslog_config_t { |
|
/*! Destination IP and port */ |
|
otSockAddr sockaddr; |
|
/*! TTL for outgoing messages */ |
|
uint8_t ttl; |
|
}; |
|
|
|
/*! |
|
* Configuration |
|
*/ |
|
static struct syslog_config_t syslog_config; |
|
|
|
/*! |
|
* Socket used for syslog traffic. |
|
*/ |
|
static otUdpSocket syslog_socket; |
|
|
|
/*! |
|
* Format a received message into a log buffer and return its size. |
|
*/ |
|
static int16_t syslog_format( |
|
otInstance* instance, |
|
char* buffer, uint16_t buf_sz, |
|
time_t timestamp, uint8_t facility, uint8_t severity, |
|
const char* app, const char* fmt, va_list fmt_args) { |
|
/* Sum of bytes written */ |
|
int16_t sum = 0; |
|
|
|
/* Converted timestamp */ |
|
struct tm tm; |
|
if (!gmtime_r(×tamp, &tm)) { |
|
/* Invalid timestamp arguments given */ |
|
return -errno; |
|
} |
|
|
|
#ifdef SYSLOG_TRACE |
|
#ifdef SYSLOG_TRACE_FORMAT |
|
iprintf("%s:%d Format message %s\r\n", |
|
__FILE__, __LINE__, fmt); |
|
#endif |
|
#endif |
|
|
|
/* Format the message header first. */ |
|
int fmt_sz = sniprintf(buffer, buf_sz, |
|
"<%d>1 %04d-%02d-%02dT%02d:%02d:%02dZ ", |
|
/* Priority value */ |
|
(facility << 3) | (severity & 0x07), |
|
/* Timestamp */ |
|
tm.tm_year + 1900, |
|
tm.tm_mon + 1, |
|
tm.tm_mday, |
|
tm.tm_hour, |
|
tm.tm_min, |
|
tm.tm_sec); |
|
|
|
#ifdef SYSLOG_TRACE |
|
#ifdef SYSLOG_TRACE_FORMAT |
|
iprintf("%s:%d Size: %d Buffer: %s\r\n", |
|
__FILE__, __LINE__, fmt_sz, buffer); |
|
#endif |
|
#endif |
|
|
|
buf_sz -= fmt_sz; |
|
buffer += fmt_sz; |
|
sum += fmt_sz; |
|
|
|
/* Append hostname */ |
|
hostname(instance, buffer, buf_sz); |
|
#ifdef SYSLOG_TRACE |
|
#ifdef SYSLOG_TRACE_FORMAT |
|
iprintf("%s:%d Size: %d Buffer: %s\r\n", |
|
__FILE__, __LINE__, HOSTNAME_LEN, buffer); |
|
#endif |
|
#endif |
|
buffer += HOSTNAME_LEN; |
|
buf_sz -= HOSTNAME_LEN; |
|
sum += HOSTNAME_LEN; |
|
|
|
/* Append remainder of header */ |
|
fmt_sz = sniprintf(buffer, buf_sz, |
|
" %s - - - ", app); |
|
#ifdef SYSLOG_TRACE |
|
#ifdef SYSLOG_TRACE_FORMAT |
|
iprintf("%s:%d Size: %d Buffer: %s\r\n", |
|
__FILE__, __LINE__, fmt_sz, buffer); |
|
#endif |
|
#endif |
|
|
|
buffer += fmt_sz; |
|
buf_sz -= fmt_sz; |
|
sum += fmt_sz; |
|
|
|
/* Append message */ |
|
fmt_sz = vsniprintf(buffer, buf_sz, fmt, fmt_args); |
|
#ifdef SYSLOG_TRACE |
|
#ifdef SYSLOG_TRACE_FORMAT |
|
iprintf("%s:%d Size: %d Buffer: %s\r\n", |
|
__FILE__, __LINE__, fmt_sz, buffer); |
|
#endif |
|
#endif |
|
sum += fmt_sz; |
|
|
|
/* We're done */ |
|
#ifdef SYSLOG_TRACE |
|
#ifdef SYSLOG_TRACE_FORMAT |
|
iprintf("%s:%d Total %u\r\n", |
|
__FILE__, __LINE__, sum); |
|
#endif |
|
#endif |
|
return sum; |
|
} |
|
|
|
|
|
/*! |
|
* Append a log message to a file for later transmission. |
|
*/ |
|
static int16_t syslog_write(const char* log_msg, uint16_t msg_len) { |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d write %s\r\n", __FILE__, __LINE__, log_msg); |
|
#endif |
|
|
|
int fd = open((syslog_resend_fd > 0) ? LOGFILE_SEC : LOGFILE_PRI, |
|
O_CREAT|O_APPEND|O_WRONLY); |
|
if (fd < 0) { |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Open fails with %s\r\n", |
|
__FILE__, __LINE__, strerror(errno)); |
|
#endif |
|
return -errno; |
|
} |
|
|
|
int sz = write(fd, &msg_len, sizeof(msg_len)); |
|
if (sz != sizeof(msg_len)) { |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Write length fails with %s\r\n", |
|
__FILE__, __LINE__, strerror(errno)); |
|
#endif |
|
|
|
close(fd); |
|
return -errno; |
|
} |
|
|
|
sz = write(fd, log_msg, msg_len); |
|
close(fd); |
|
|
|
if (sz != (int)msg_len) { |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Write message fails with %s\r\n", |
|
__FILE__, __LINE__, strerror(errno)); |
|
#endif |
|
|
|
return -errno; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
/*! |
|
* Submit a log message immediately. |
|
*/ |
|
static otError syslog_tx(otInstance* instance, |
|
const char* log_msg, uint16_t msg_len) { |
|
otMessageInfo msg_info; |
|
if (!syslog_socket.mHandler) { |
|
return OT_ERROR_INVALID_STATE; |
|
} |
|
|
|
otError err = OT_ERROR_NONE; |
|
memset(&msg_info, 0, sizeof(msg_info)); |
|
memcpy(&msg_info.mPeerAddr, &syslog_config.sockaddr.mAddress, |
|
sizeof(otIp6Address)); |
|
msg_info.mPeerPort = syslog_config.sockaddr.mPort; |
|
msg_info.mHopLimit = syslog_config.ttl; |
|
msg_info.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD; |
|
|
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d tx %s\r\n", __FILE__, __LINE__, log_msg); |
|
#endif |
|
otMessage* msg = otUdpNewMessage(instance, true); |
|
if (msg) { |
|
err = otMessageAppend(msg, log_msg, msg_len); |
|
} else { |
|
err = OT_ERROR_NO_BUFS; |
|
} |
|
|
|
if (err == OT_ERROR_NONE) { |
|
err = otUdpSend(&syslog_socket, msg, &msg_info); |
|
} |
|
|
|
if (msg && (err != OT_ERROR_NONE)) { |
|
otMessageFree(msg); |
|
} |
|
|
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d res %s\r\n", __FILE__, __LINE__, otThreadErrorToString(err)); |
|
#endif |
|
return err; |
|
} |
|
|
|
/*! |
|
* Read the system logger configuration. |
|
*/ |
|
static int syslog_cfg_read() { |
|
/* Try to read the syslog configuration */ |
|
int fd = open(SYSLOG_CFG, O_RDONLY); |
|
if (fd >= 0) { |
|
int res = read(fd, &syslog_config, |
|
sizeof(syslog_config)); |
|
if (res < (int)sizeof(syslog_config)) { |
|
res = -errno; |
|
} |
|
close(fd); |
|
return res; |
|
} else { |
|
return -errno; |
|
} |
|
} |
|
|
|
/*! |
|
* Close any existing socket |
|
*/ |
|
static void syslog_close() { |
|
if (syslog_socket.mHandler) { |
|
otUdpClose(&syslog_socket); |
|
memset(&syslog_socket, 0, sizeof(syslog_socket)); |
|
} |
|
} |
|
|
|
/*! |
|
* What to do on receipt of a UDP frame? Ignore it! |
|
*/ |
|
static void syslog_recv(void *aContext, otMessage *aMessage, |
|
const otMessageInfo *aMessageInfo) { |
|
(void)aContext; |
|
(void)aMessage; |
|
(void)aMessageInfo; |
|
} |
|
|
|
/*! |
|
* Open a new socket |
|
*/ |
|
static int syslog_open(otInstance* instance) { |
|
otError err = otUdpOpen(instance, &syslog_socket, |
|
&syslog_recv, NULL); |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d udpOpen = %d\r\n", __FILE__, __LINE__, err); |
|
#endif |
|
switch (err) { |
|
case OT_ERROR_INVALID_ARGS: |
|
return -EINVAL; |
|
case OT_ERROR_NONE: |
|
break; |
|
default: |
|
return -EIO; |
|
} |
|
|
|
err = otUdpConnect(&syslog_socket, &syslog_config.sockaddr); |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d udpConnect = %d\r\n", __FILE__, __LINE__, err); |
|
#endif |
|
switch (err) { |
|
case OT_ERROR_INVALID_ARGS: |
|
return -EINVAL; |
|
case OT_ERROR_NONE: |
|
break; |
|
default: |
|
return -EIO; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/*! |
|
* Initialise the system logger interface. |
|
*/ |
|
int syslog_init(otInstance* instance) { |
|
syslog_resend_fd = -1; |
|
syslog_timer = SYSLOG_INIT_DELAY; |
|
|
|
if (syslog_cfg_read() < 0) { |
|
return syslog_set_endpoint(instance, "ff02::1", 514, 1); |
|
} else { |
|
return syslog_open(instance); |
|
} |
|
} |
|
|
|
/*! |
|
* Set the endpoint for log messages. |
|
*/ |
|
int syslog_set_endpoint(otInstance* instance, const char* target_ip, |
|
uint16_t target_port, uint8_t ttl) { |
|
syslog_close(); |
|
|
|
otSockAddr sockaddr; |
|
memset(&sockaddr, 0, sizeof(sockaddr)); |
|
|
|
/* Parse the IP address */ |
|
otError err = otIp6AddressFromString(target_ip, &sockaddr.mAddress); |
|
if (err != OT_ERROR_NONE) { |
|
return -EINVAL; |
|
} |
|
|
|
/* Set the message target */ |
|
sockaddr.mPort = target_port; |
|
memcpy(&syslog_config.sockaddr, &sockaddr, sizeof(sockaddr)); |
|
syslog_config.ttl = ttl; |
|
|
|
int res = syslog_open(instance); |
|
if (!res) { |
|
int fd = open(SYSLOG_CFG, O_WRONLY); |
|
if (fd < 0) { |
|
return -errno; |
|
} |
|
res = write(fd, &syslog_config, sizeof(syslog_config)); |
|
if (res < (int)sizeof(syslog_config)) { |
|
res = -errno; |
|
} |
|
close(fd); |
|
} |
|
return res; |
|
} |
|
|
|
/*! |
|
* Process the stored log messages in our EEPROM logs. |
|
*/ |
|
void syslog_process(otInstance* instance) { |
|
if (syslog_timer) { |
|
syslog_timer--; |
|
return; |
|
} |
|
|
|
/* Check that we have a network */ |
|
switch (otThreadGetDeviceRole(instance)) { |
|
case OT_DEVICE_ROLE_CHILD: |
|
case OT_DEVICE_ROLE_ROUTER: |
|
case OT_DEVICE_ROLE_LEADER: |
|
break; |
|
default: |
|
/* No network */ |
|
syslog_timer = SYSLOG_NETWORK_DELAY; |
|
return; |
|
} |
|
|
|
if (syslog_resend_fd <= 0) { |
|
/* Check for written log messages to send */ |
|
syslog_resend_fd = open(LOGFILE_PRI, O_RDONLY); |
|
if (syslog_resend_fd < 0) { |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Unable to open log file: %s\r\n", |
|
__FILE__, __LINE__, strerror(errno)); |
|
#endif |
|
|
|
/* |
|
* Rename the secondary to the |
|
* primary, if it exists. Ignore if it doesn't. |
|
*/ |
|
fs_rename(LOGFILE_SEC, LOGFILE_PRI); |
|
|
|
/* Wait before retrying */ |
|
syslog_timer = SYSLOG_CHECK_DELAY; |
|
return; |
|
} |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Log file opened.\r\n", |
|
__FILE__, __LINE__); |
|
#endif |
|
|
|
} |
|
|
|
if (syslog_resend_fd) { |
|
/* |
|
* We have a log file open. Read the next message and |
|
* send it if we can. Is there buffer space? |
|
*/ |
|
otBufferInfo buffer_info; |
|
otMessageGetBufferInfo(instance, &buffer_info); |
|
uint32_t buffer_usage = buffer_info.mTotalBuffers |
|
- buffer_info.mFreeBuffers; |
|
buffer_usage *= 100; |
|
buffer_usage /= buffer_info.mTotalBuffers; |
|
|
|
if (buffer_usage > SYSLOG_MAX_BUFFER_USE_PERCENT) { |
|
/* Do nothing for now */ |
|
syslog_timer = SYSLOG_NETWORK_DELAY; |
|
return; |
|
} |
|
|
|
/* We have buffer space, and a network. */ |
|
int16_t msg_len; |
|
int res = read(syslog_resend_fd, &msg_len, sizeof(msg_len)); |
|
if ((res != sizeof(msg_len)) || (msg_len < 26) || (msg_len > 2048)) { |
|
/* Bad read… assume file is corrupt or end of file */ |
|
close(syslog_resend_fd); |
|
syslog_resend_fd = -1; |
|
fs_remove(LOGFILE_PRI); |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Log file closed.\r\n", |
|
__FILE__, __LINE__); |
|
#endif |
|
return; |
|
} |
|
|
|
/* Read the message to be sent */ |
|
char msg[msg_len+1]; |
|
res = read(syslog_resend_fd, msg, msg_len); |
|
msg[msg_len] = 0; |
|
if (res != msg_len) { |
|
/* Bad read… assume file is corrupt or end of file */ |
|
close(syslog_resend_fd); |
|
syslog_resend_fd = -1; |
|
fs_remove(LOGFILE_PRI); |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Log file closed.\r\n", |
|
__FILE__, __LINE__); |
|
#endif |
|
return; |
|
} |
|
|
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Read stored message: %s\r\n", |
|
__FILE__, __LINE__, msg); |
|
#endif |
|
|
|
/* We have read the message in… send it */ |
|
otError err = syslog_tx(instance, msg, msg_len); |
|
if (err != OT_ERROR_NONE) { |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d Send failed: %s\r\n", |
|
__FILE__, __LINE__, msg); |
|
#endif |
|
|
|
/* Failed. Re-wind to try again. */ |
|
lseek(syslog_resend_fd, |
|
-msg_len - sizeof(msg_len), SEEK_CUR); |
|
} |
|
} |
|
} |
|
|
|
/*! |
|
* Send a syslog message. |
|
*/ |
|
int syslog_send_va(otInstance* instance, |
|
uint8_t facility, uint8_t severity, |
|
const char* app, const char* fmt, va_list fmt_args) { |
|
|
|
/* Retrieve the current system time */ |
|
struct timeval tv; |
|
int res = gettimeofday(&tv, NULL); |
|
if (res < 0) { |
|
return res; |
|
} |
|
|
|
char buffer[SYSLOG_BUF_SZ]; |
|
int16_t sz = syslog_format(instance, buffer, sizeof(buffer), |
|
tv.tv_sec, facility, severity, app, fmt, fmt_args); |
|
if (sz < 0) { |
|
return sz; |
|
} |
|
|
|
/* Try to send the message */ |
|
#ifdef SYSLOG_TRACE |
|
iprintf("%s:%d msg %s\r\n", __FILE__, __LINE__, buffer); |
|
#endif |
|
otError err = syslog_tx(instance, buffer, sz); |
|
if ((err != OT_ERROR_NONE) && (severity <= SYSLOG_PERSIST_SEVERITY)) { |
|
/* Try to persist it for later */ |
|
res = syslog_write(buffer, sz); |
|
} |
|
|
|
return res; |
|
} |