From 2df2ad18c9a24c729f678e42f5456fa11006967e Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Fri, 8 May 2026 12:23:45 -0400 Subject: [PATCH] scripts/c: add single-file C port of hbc_mini hbc_mini.c is a full port of scripts/hbc_mini.py requiring only zlib, pthreads, and a C11 compiler. Supports Linux, FreeBSD, NetBSD, and DragonFly BSD with platform-specific plugin backends: - cpu_monitor: /proc/stat (Linux) or kern.cp_time sysctl (BSD) - memory_monitor: /proc/meminfo (Linux), vm.stats.vm.* (FreeBSD), struct uvmexp (NetBSD) - network_monitor:/proc/net/dev (Linux) or getifaddrs()+if_data (BSD) - disk_monitor: df -P (all platforms) - ping_monitor: ping subprocess (all platforms) - nagios_runner: shell commands with perfdata parsing (all platforms) - os_info: uname() + /etc/os-release (Linux) or kern.osrelease (BSD) Build: cc -O2 -o hbc_mini hbc_mini.c -lz -lpthread -lm Co-Authored-By: Claude Sonnet 4.6 --- scripts/c/Makefile | 21 + scripts/c/hbc_mini | Bin 0 -> 53832 bytes scripts/c/hbc_mini.c | 1422 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1443 insertions(+) create mode 100644 scripts/c/Makefile create mode 100755 scripts/c/hbc_mini create mode 100644 scripts/c/hbc_mini.c diff --git a/scripts/c/Makefile b/scripts/c/Makefile new file mode 100644 index 0000000..8b4c81b --- /dev/null +++ b/scripts/c/Makefile @@ -0,0 +1,21 @@ +CC ?= cc +CFLAGS = -O2 -Wall -Wextra -std=c11 +LDFLAGS = -lz -lpthread -lm +TARGET = hbc_mini +SRC = hbc_mini.c + +# FreeBSD/NetBSD keep zlib in base; no extra flags needed. +# On some NetBSD installs pthreads may need -lpthread from pkgsrc. + +.PHONY: all clean debug + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +debug: $(SRC) + $(CC) -g -fsanitize=address,undefined -o $(TARGET)_dbg $< $(LDFLAGS) + +clean: + rm -f $(TARGET) $(TARGET)_dbg diff --git a/scripts/c/hbc_mini b/scripts/c/hbc_mini new file mode 100755 index 0000000000000000000000000000000000000000..70ad7299d4a55a74b79d21abbd2779729e6e91d3 GIT binary patch literal 53832 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 literal 0 HcmV?d00001 diff --git a/scripts/c/hbc_mini.c b/scripts/c/hbc_mini.c new file mode 100644 index 0000000..31f4601 --- /dev/null +++ b/scripts/c/hbc_mini.c @@ -0,0 +1,1422 @@ +/* hbc_mini.c — HeartBeat Client, single-file C port of hbc_mini.py + * + * Build: cc -O2 -o hbc_mini hbc_mini.c -lz -lpthread -lm + * Requires: zlib, pthreads, POSIX + * Supported: Linux, FreeBSD, NetBSD, DragonFly BSD + * + * Drop on any host and run: ./hbc_mini + * Config: ~/.hbc.json (same keys as Python version) + */ + +#ifdef __linux__ +# define _GNU_SOURCE +# define _POSIX_C_SOURCE 200809L +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +# include +# include +# include +# include +#endif +#if defined(__NetBSD__) +# include +#endif + +/* version updated by scripts/bumpminor.sh */ +#define HBC_VERSION "5.2.4" +#define DEFAULT_PORT 50003 +#define DEFAULT_INTERVAL 10 +#define MAX_HOSTS 8 +#define MAX_KV 64 +#define MAX_KEY 128 +#define MAX_VAL 4096 +#define PROTO_BUF_SIZE 65536 +#define MAX_PING_HOSTS 32 +#define MAX_NAGIOS_CMDS 32 +#define MAX_DISK_MOUNTS 32 +#define MAX_NET_SKIP 16 + +/* ============================================================ + * Logging + * ============================================================ */ + +static bool g_use_syslog = false; +static int g_log_level = 3; /* 0=debug 1=info 2=warn 3=error */ + +#define LL_DEBUG 0 +#define LL_INFO 1 +#define LL_WARN 2 +#define LL_ERROR 3 + +static void hbc_log(int level, const char *fmt, ...) { + if (level < g_log_level) return; + va_list ap; + va_start(ap, fmt); + if (g_use_syslog) { + int pri = LOG_ERR; + if (level == LL_DEBUG) pri = LOG_DEBUG; + else if (level == LL_INFO) pri = LOG_INFO; + else if (level == LL_WARN) pri = LOG_WARNING; + vsyslog(pri, fmt, ap); + } else { + const char *ln[] = {"DEBUG","INFO","WARN","ERROR"}; + time_t t = time(NULL); + struct tm tm; localtime_r(&t, &tm); + char ts[32]; strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", &tm); + fprintf(stderr, "%s hbc %s: ", ts, ln[level]); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + } + va_end(ap); +} + +#define LOGD(fmt, ...) hbc_log(LL_DEBUG, fmt, ##__VA_ARGS__) +#define LOGI(fmt, ...) hbc_log(LL_INFO, fmt, ##__VA_ARGS__) +#define LOGW(fmt, ...) hbc_log(LL_WARN, fmt, ##__VA_ARGS__) +#define LOGE(fmt, ...) hbc_log(LL_ERROR, fmt, ##__VA_ARGS__) + +/* ============================================================ + * Key-Value dict (protocol messages) + * ============================================================ */ + +typedef struct { char key[MAX_KEY]; char val[MAX_VAL]; } kv_t; +typedef struct { kv_t pairs[MAX_KV]; int count; } kvdict_t; + +static void kv_clear(kvdict_t *d) { d->count = 0; } + +static bool kv_set(kvdict_t *d, const char *key, const char *val) { + if (d->count >= MAX_KV) return false; + snprintf(d->pairs[d->count].key, MAX_KEY, "%s", key); + snprintf(d->pairs[d->count].val, MAX_VAL, "%s", val); + d->count++; + return true; +} + +static bool kv_set_int(kvdict_t *d, const char *key, long long v) { + char buf[32]; snprintf(buf, sizeof(buf), "%lld", v); + return kv_set(d, key, buf); +} + +static bool kv_set_ull(kvdict_t *d, const char *key, unsigned long long v) { + char buf[32]; snprintf(buf, sizeof(buf), "%llu", v); + return kv_set(d, key, buf); +} + +static bool kv_set_dbl(kvdict_t *d, const char *key, double v) { + char buf[32]; + if (isinf(v)) snprintf(buf, sizeof(buf), "inf"); + else snprintf(buf, sizeof(buf), "%.5f", v); + return kv_set(d, key, buf); +} + +static const char *kv_get(const kvdict_t *d, const char *key) { + for (int i = 0; i < d->count; i++) + if (strcmp(d->pairs[i].key, key) == 0) + return d->pairs[i].val; + return NULL; +} + +/* ============================================================ + * Protocol codec (mirrors hbd/common/proto.py) + * ============================================================ */ + +/* Build wire bytes: !MSG: + zlib-compressed key=val;key=val;... */ +static int proto_encode(const char *msg_id, const kvdict_t *d, + uint8_t *out, size_t cap) { + /* Build payload */ + char *payload = malloc(PROTO_BUF_SIZE); + if (!payload) return -1; + int plen = 0; + for (int i = 0; i < d->count; i++) { + if (i > 0 && plen < PROTO_BUF_SIZE - 1) payload[plen++] = ';'; + plen += snprintf(payload + plen, PROTO_BUF_SIZE - plen, + "%s=%s", d->pairs[i].key, d->pairs[i].val); + if (plen >= PROTO_BUF_SIZE) { plen = PROTO_BUF_SIZE - 1; break; } + } + /* Compress */ + uLongf clen = compressBound(plen); + uint8_t *cbuf = malloc(clen); + if (!cbuf) { free(payload); return -1; } + if (compress2(cbuf, &clen, (uint8_t *)payload, plen, 6) != Z_OK) { + free(payload); free(cbuf); return -1; + } + free(payload); + /* Header: !XXX: (5 bytes) */ + if ((size_t)(5 + clen) > cap) { free(cbuf); return -1; } + out[0] = '!'; + memcpy(out + 1, msg_id, 3); + out[4] = ':'; + memcpy(out + 5, cbuf, clen); + free(cbuf); + return 5 + (int)clen; +} + +/* Parse wire bytes into kvdict */ +static bool proto_decode(const uint8_t *data, int dlen, kvdict_t *out) { + kv_clear(out); + if (dlen < 5 || data[0] != '!') return false; + char id[4] = {0}; + memcpy(id, data + 1, 3); + kv_set(out, "ID", id); + uint8_t *payload = malloc(PROTO_BUF_SIZE); + if (!payload) return false; + uLongf plen = PROTO_BUF_SIZE - 1; + if (uncompress(payload, &plen, data + 5, dlen - 5) != Z_OK) { + free(payload); return false; + } + payload[plen] = '\0'; + char *p = (char *)payload; + while (*p) { + char *semi = strchr(p, ';'); + if (semi) *semi = '\0'; + char *eq = strchr(p, '='); + if (eq) { + *eq = '\0'; + char *kp = p; while (*kp == ' ') kp++; + char *vp = eq+1; while (*vp == ' ') vp++; + kv_set(out, kp, vp); + } + if (semi) p = semi + 1; else break; + } + free(payload); + return true; +} + +/* ============================================================ + * Minimal JSON parser (for config file) + * ============================================================ */ + +typedef enum { JT_NULL,JT_BOOL,JT_INT,JT_FLOAT,JT_STRING,JT_ARRAY,JT_OBJECT } jtype_t; + +typedef struct jval jval_t; +struct jval { + jtype_t type; + union { + bool b; + long long i; + double f; + char *s; + struct { char **keys; jval_t **vals; int count, cap; } obj; + struct { jval_t **items; int count, cap; } arr; + }; +}; + +static void jval_free(jval_t *v) { + if (!v) return; + if (v->type == JT_STRING) { free(v->s); } + else if (v->type == JT_OBJECT) { + for (int i = 0; i < v->obj.count; i++) { free(v->obj.keys[i]); jval_free(v->obj.vals[i]); } + free(v->obj.keys); free(v->obj.vals); + } else if (v->type == JT_ARRAY) { + for (int i = 0; i < v->arr.count; i++) jval_free(v->arr.items[i]); + free(v->arr.items); + } + free(v); +} + +static const char *jskip(const char *p) { + while (*p && isspace((unsigned char)*p)) p++; + return p; +} + +static const char *jparse_value(const char *p, jval_t **out); + +static const char *jparse_string(const char *p, char **out) { + if (*p != '"') return NULL; + p++; + char buf[4096]; int n = 0; + while (*p && *p != '"') { + if (*p == '\\') { + p++; + switch (*p) { + case '"': buf[n++]='"'; break; case '\\': buf[n++]='\\'; break; + case 'n': buf[n++]='\n'; break; case 'r': buf[n++]='\r'; break; + case 't': buf[n++]='\t'; break; default: buf[n++]=*p; break; + } + } else { buf[n++] = *p; } + p++; + if (n >= (int)sizeof(buf)-1) break; + } + buf[n] = '\0'; + if (*p == '"') p++; + *out = strdup(buf); + return p; +} + +static const char *jparse_object(const char *p, jval_t **out) { + if (*p != '{') return NULL; + p++; + jval_t *v = calloc(1, sizeof(jval_t)); v->type = JT_OBJECT; + p = jskip(p); + while (*p && *p != '}') { + p = jskip(p); + char *key = NULL; + p = jparse_string(p, &key); + if (!p) break; + p = jskip(p); if (*p == ':') p++; + p = jskip(p); + jval_t *val = NULL; + p = jparse_value(p, &val); + if (key && val) { + if (v->obj.count >= v->obj.cap) { + v->obj.cap = v->obj.cap ? v->obj.cap*2 : 4; + v->obj.keys = realloc(v->obj.keys, v->obj.cap * sizeof(char*)); + v->obj.vals = realloc(v->obj.vals, v->obj.cap * sizeof(jval_t*)); + } + v->obj.keys[v->obj.count] = key; + v->obj.vals[v->obj.count] = val; + v->obj.count++; + } else { free(key); jval_free(val); } + p = jskip(p); if (*p == ',') p++; + p = jskip(p); + } + if (*p == '}') p++; + *out = v; return p; +} + +static const char *jparse_array(const char *p, jval_t **out) { + if (*p != '[') return NULL; + p++; + jval_t *v = calloc(1, sizeof(jval_t)); v->type = JT_ARRAY; + p = jskip(p); + while (*p && *p != ']') { + jval_t *item = NULL; + p = jparse_value(jskip(p), &item); + if (item) { + if (v->arr.count >= v->arr.cap) { + v->arr.cap = v->arr.cap ? v->arr.cap*2 : 4; + v->arr.items = realloc(v->arr.items, v->arr.cap * sizeof(jval_t*)); + } + v->arr.items[v->arr.count++] = item; + } + p = jskip(p); if (*p == ',') p++; + } + if (*p == ']') p++; + *out = v; return p; +} + +static const char *jparse_value(const char *p, jval_t **out) { + p = jskip(p); + if (!*p) { *out = NULL; return p; } + if (*p == '{') return jparse_object(p, out); + if (*p == '[') return jparse_array(p, out); + if (*p == '"') { + char *s = NULL; p = jparse_string(p, &s); + jval_t *v = calloc(1, sizeof(jval_t)); v->type = JT_STRING; v->s = s; + *out = v; return p; + } + if (strncmp(p,"true",4)==0) { jval_t *v=calloc(1,sizeof(jval_t)); v->type=JT_BOOL; v->b=true; *out=v; return p+4; } + if (strncmp(p,"false",5)==0) { jval_t *v=calloc(1,sizeof(jval_t)); v->type=JT_BOOL; v->b=false; *out=v; return p+5; } + if (strncmp(p,"null",4)==0) { jval_t *v=calloc(1,sizeof(jval_t)); v->type=JT_NULL; *out=v; return p+4; } + if (isdigit((unsigned char)*p) || *p == '-') { + char *e1, *e2; + long long iv = strtoll(p, &e1, 10); + double fv = strtod(p, &e2); + if (e2 > e1) { + jval_t *v=calloc(1,sizeof(jval_t)); v->type=JT_FLOAT; v->f=fv; *out=v; return e2; + } else { + jval_t *v=calloc(1,sizeof(jval_t)); v->type=JT_INT; v->i=iv; *out=v; return e1; + } + } + *out = NULL; return p + 1; +} + +static jval_t *json_parse(const char *src) { + jval_t *v = NULL; jparse_value(src, &v); return v; +} + +static jval_t *jget(const jval_t *obj, const char *key) { + if (!obj || obj->type != JT_OBJECT) return NULL; + for (int i = 0; i < obj->obj.count; i++) + if (strcmp(obj->obj.keys[i], key) == 0) return obj->obj.vals[i]; + return NULL; +} + +static long long jint(const jval_t *v, long long def) { + if (!v) return def; + if (v->type==JT_INT) return v->i; + if (v->type==JT_FLOAT) return (long long)v->f; + if (v->type==JT_BOOL) return v->b ? 1 : 0; + return def; +} +static const char *jstr(const jval_t *v, const char *def) { + return (!v || v->type!=JT_STRING) ? def : v->s; +} + +/* ============================================================ + * Config + * ============================================================ */ + +typedef struct { + int hb_port, interval; + char owner[256]; + + char ping_hosts[MAX_PING_HOSTS][256]; + int ping_host_count, ping_interval, ping_count, ping_timeout; + + struct { char name[64]; char cmd[512]; } nagios[MAX_NAGIOS_CMDS]; + int nagios_count, nagios_interval, nagios_timeout; + + int cpu_interval, mem_interval, disk_interval, net_interval; + + char disk_mounts[MAX_DISK_MOUNTS][256]; + int disk_mount_count; + + char net_skip[MAX_NET_SKIP][64]; + int net_skip_count; +} config_t; + +static void config_defaults(config_t *c) { + memset(c, 0, sizeof(*c)); + c->hb_port = DEFAULT_PORT; + c->interval = DEFAULT_INTERVAL; + c->ping_interval = 60; c->ping_count = 3; c->ping_timeout = 5; + c->nagios_interval = 300; c->nagios_timeout = 30; + c->cpu_interval = 300; + c->mem_interval = 300; + c->disk_interval = 300; + c->net_interval = 300; + snprintf(c->net_skip[0], 64, "lo"); + c->net_skip_count = 1; +} + +static void config_load(config_t *cfg, const char *path) { + config_defaults(cfg); + char fpath[512]; + if (!path) { + const char *home = getenv("HOME"); + snprintf(fpath, sizeof(fpath), "%s/.hbc.json", home ? home : ""); + path = fpath; + } + FILE *f = fopen(path, "r"); + if (!f) return; + fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET); + char *buf = malloc(sz + 1); + if (!buf) { fclose(f); return; } + size_t _nr = fread(buf, 1, sz, f); buf[_nr] = '\0'; fclose(f); + jval_t *root = json_parse(buf); free(buf); + if (!root || root->type != JT_OBJECT) { LOGW("cannot parse %s", path); jval_free(root); return; } + + jval_t *v; + if ((v = jget(root, "hb_port"))) cfg->hb_port = jint(v, cfg->hb_port); + if ((v = jget(root, "interval"))) cfg->interval = jint(v, cfg->interval); + if ((v = jget(root, "owner"))) snprintf(cfg->owner, sizeof(cfg->owner), "%s", jstr(v, "")); + + jval_t *plugins = jget(root, "plugins"); + + /* ping_monitor */ + jval_t *pm = plugins ? jget(plugins,"ping_monitor") : jget(root,"ping_monitor"); + if (pm && pm->type == JT_OBJECT) { + if ((v=jget(pm,"interval"))) cfg->ping_interval = jint(v, cfg->ping_interval); + if ((v=jget(pm,"count"))) cfg->ping_count = jint(v, cfg->ping_count); + if ((v=jget(pm,"timeout"))) cfg->ping_timeout = jint(v, cfg->ping_timeout); + jval_t *h = jget(pm, "hosts"); + if (h && h->type == JT_ARRAY) { + for (int i = 0; i < h->arr.count && cfg->ping_host_count < MAX_PING_HOSTS; i++) { + const char *s = jstr(h->arr.items[i], NULL); + if (s) snprintf(cfg->ping_hosts[cfg->ping_host_count++], 256, "%s", s); + } + } else if (h && h->type == JT_OBJECT) { + for (int i = 0; i < h->obj.count && cfg->ping_host_count < MAX_PING_HOSTS; i++) + snprintf(cfg->ping_hosts[cfg->ping_host_count++], 256, "%s", h->obj.keys[i]); + } + } + + /* nagios_runner */ + jval_t *nr = plugins ? jget(plugins,"nagios_runner") : jget(root,"nagios_runner"); + if (nr && nr->type == JT_OBJECT) { + if ((v=jget(nr,"interval"))) cfg->nagios_interval = jint(v, cfg->nagios_interval); + if ((v=jget(nr,"timeout"))) cfg->nagios_timeout = jint(v, cfg->nagios_timeout); + jval_t *cmds = jget(nr, "commands"); + if (cmds && cmds->type == JT_ARRAY) { + for (int i = 0; i < cmds->arr.count && cfg->nagios_count < MAX_NAGIOS_CMDS; i++) { + jval_t *c = cmds->arr.items[i]; + if (!c || c->type != JT_OBJECT) continue; + jval_t *n = jget(c,"name"), *cmd = jget(c,"command"); + if (n && cmd) { + int idx = cfg->nagios_count++; + snprintf(cfg->nagios[idx].name, 64, "%s", jstr(n, "")); + snprintf(cfg->nagios[idx].cmd, 512, "%s", jstr(cmd, "")); + } + } + } + } + + /* cpu/memory/disk/network */ + jval_t *cpu = plugins ? jget(plugins,"cpu_monitor") : jget(root,"cpu_monitor"); + if (cpu && (v=jget(cpu,"interval"))) cfg->cpu_interval = jint(v, cfg->cpu_interval); + + jval_t *mem = plugins ? jget(plugins,"memory_monitor") : jget(root,"memory_monitor"); + if (mem && (v=jget(mem,"interval"))) cfg->mem_interval = jint(v, cfg->mem_interval); + + jval_t *disk = plugins ? jget(plugins,"disk_monitor") : jget(root,"disk_monitor"); + if (disk && disk->type == JT_OBJECT) { + if ((v=jget(disk,"interval"))) cfg->disk_interval = jint(v, cfg->disk_interval); + jval_t *m = jget(disk,"mounts"); + if (m && m->type == JT_ARRAY) + for (int i = 0; i < m->arr.count && cfg->disk_mount_count < MAX_DISK_MOUNTS; i++) { + const char *s = jstr(m->arr.items[i], NULL); + if (s) snprintf(cfg->disk_mounts[cfg->disk_mount_count++], 256, "%s", s); + } + } + + jval_t *net = plugins ? jget(plugins,"network_monitor") : jget(root,"network_monitor"); + if (net && net->type == JT_OBJECT) { + if ((v=jget(net,"interval"))) cfg->net_interval = jint(v, cfg->net_interval); + jval_t *sk = jget(net,"skip_interfaces"); + if (sk && sk->type == JT_ARRAY) { + cfg->net_skip_count = 0; + for (int i = 0; i < sk->arr.count && cfg->net_skip_count < MAX_NET_SKIP; i++) { + const char *s = jstr(sk->arr.items[i], NULL); + if (s) snprintf(cfg->net_skip[cfg->net_skip_count++], 64, "%s", s); + } + } + } + + LOGI("loaded config from %s", path); + jval_free(root); +} + +/* ============================================================ + * UDP connection + * ============================================================ */ + +typedef struct { + int conn_id; + char addr[INET6_ADDRSTRLEN]; + int port, af, sockfd; + char name[256]; + long long ack_count; + double last_send, rtt; + volatile bool request_info; + pthread_mutex_t mu; +} conn_t; + +static double now_ts(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return ts.tv_sec + ts.tv_nsec * 1e-9; +} + +static bool conn_open(conn_t *c) { + c->sockfd = socket(c->af, SOCK_DGRAM, 0); + if (c->sockfd < 0) { LOGE("socket: %s", strerror(errno)); return false; } + int fl = fcntl(c->sockfd, F_GETFL, 0); + fcntl(c->sockfd, F_SETFL, fl | O_NONBLOCK); + pthread_mutex_init(&c->mu, NULL); + return true; +} + +static void conn_close(conn_t *c) { + if (c->sockfd >= 0) { close(c->sockfd); c->sockfd = -1; } +} + +static int conn_send(conn_t *c, const char *msg_id, kvdict_t *d) { + kv_set(d, "name", c->name); + char buf[32]; + snprintf(buf, sizeof(buf), "%d", c->conn_id); kv_set(d, "id", buf); + snprintf(buf, sizeof(buf), "%.3f", now_ts()); kv_set(d, "time", buf); + + uint8_t wire[PROTO_BUF_SIZE]; + int wlen = proto_encode(msg_id, d, wire, sizeof(wire)); + if (wlen < 0) { LOGW("proto_encode failed"); return -1; } + + struct sockaddr_storage sa; socklen_t salen = 0; + memset(&sa, 0, sizeof(sa)); + if (c->af == AF_INET) { + struct sockaddr_in *s4 = (struct sockaddr_in *)&sa; + s4->sin_family = AF_INET; + s4->sin_port = htons(c->port); + inet_pton(AF_INET, c->addr, &s4->sin_addr); + salen = sizeof(*s4); + } else { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&sa; + s6->sin6_family = AF_INET6; + s6->sin6_port = htons(c->port); + inet_pton(AF_INET6, c->addr, &s6->sin6_addr); + salen = sizeof(*s6); + } + + pthread_mutex_lock(&c->mu); + c->last_send = now_ts(); + ssize_t sent = sendto(c->sockfd, wire, wlen, 0, (struct sockaddr *)&sa, salen); + pthread_mutex_unlock(&c->mu); + return (sent == wlen) ? 0 : -1; +} + +static void conn_recv(conn_t *c) { + uint8_t buf[PROTO_BUF_SIZE]; + struct sockaddr_storage sa; socklen_t sl = sizeof(sa); + ssize_t n = recvfrom(c->sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &sl); + if (n <= 0) return; + + kvdict_t msg; + if (!proto_decode(buf, (int)n, &msg)) return; + const char *id = kv_get(&msg, "ID"); + if (!id) return; + + if (strcmp(id, "ACK") == 0) { + c->rtt = (now_ts() - c->last_send) * 1000.0; + c->ack_count++; + const char *ru = kv_get(&msg, "request_update"); + if (ru && strcmp(ru,"1") == 0) c->request_info = true; + LOGD("ACK rtt=%.1fms", c->rtt); + } else if (strcmp(id, "CMD") == 0) { + const char *cmd = kv_get(&msg, "cmd"); + if (cmd) { + LOGI("CMD: %s", cmd); + char out[4096] = ""; + FILE *p = popen(cmd, "r"); + if (p) { size_t _r = fread(out, 1, sizeof(out)-1, p); (void)_r; pclose(p); } + kvdict_t rep; kv_clear(&rep); + kv_set(&rep, "service", "command"); + kv_set(&rep, "msg", out[0] ? out : "OK"); + conn_send(c, "HTB", &rep); + } + } else if (strcmp(id, "UPD") == 0) { + LOGI("UPD: update requested (not implemented in C version)"); + } +} + +/* ============================================================ + * Global state + * ============================================================ */ + +static volatile bool g_running = true; +static volatile bool g_restart = false; +static conn_t g_conns[MAX_HOSTS]; +static int g_nconns = 0; + +static void sig_handler(int sig) { + if (sig == SIGHUP) g_restart = true; + g_running = false; +} + +/* ============================================================ + * Plugin: os_info + * ============================================================ */ + +static void plugin_os_info(conn_t *c, const config_t *cfg) { + kvdict_t d; kv_clear(&d); + kv_set(&d, "plugin", "os_info"); + struct utsname u; + if (uname(&u) == 0) { + kv_set(&d, "system", u.sysname); + kv_set(&d, "node", u.nodename); + kv_set(&d, "release", u.release); + kv_set(&d, "machine", u.machine); + } + kv_set(&d, "hbc_version", HBC_VERSION); + kv_set(&d, "hbc_type", "mini-c"); + if (cfg->owner[0]) kv_set(&d, "owner", cfg->owner); + +#ifdef __linux__ + { + FILE *f = fopen("/etc/os-release", "r"); + if (!f) f = fopen("/etc/lsb-release", "r"); + if (f) { + char line[512]; + while (fgets(line, sizeof(line), f)) { + char *eq = strchr(line, '='); + if (!eq || line[0] == '#') continue; + *eq = '\0'; + char *val = eq + 1; + int vl = strlen(val); + while (vl > 0 && (val[vl-1]=='\n'||val[vl-1]=='\r'||val[vl-1]=='"'||val[vl-1]=='\'')) val[--vl]='\0'; + if (*val == '"' || *val == '\'') val++; + if (strcmp(line,"PRETTY_NAME")==0) kv_set(&d, "distro_pretty_name", val); + else if (strcmp(line,"NAME")==0) kv_set(&d, "distro_name", val); + else if (strcmp(line,"VERSION_ID")==0) kv_set(&d, "distro_version_id", val); + else if (strcmp(line,"ID")==0) kv_set(&d, "distro_id", val); + } + fclose(f); + } + } +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + { + char osver[128] = ""; size_t len = sizeof(osver); + sysctlbyname("kern.osrelease", osver, &len, NULL, 0); + if (osver[0]) kv_set(&d, "distro_version_id", osver); + } +#endif + conn_send(c, "PLG", &d); + LOGI("sent os_info"); +} + +/* ============================================================ + * Plugin: cpu_monitor + * Linux: /proc/stat (user nice sys idle iowait irq softirq steal) + * BSD: sysctl kern.cp_time (CP_USER CP_NICE CP_SYS CP_INTR CP_IDLE) + * ============================================================ */ + +typedef struct { + long long user, sys, idle, iowait, total; +} cpu_sample_t; + +#ifdef __linux__ + +static bool read_cpu_sample(cpu_sample_t *s) { + FILE *f = fopen("/proc/stat", "r"); + if (!f) return false; + char line[256]; bool ok = false; + while (fgets(line, sizeof(line), f)) { + if (strncmp(line, "cpu ", 4) != 0) continue; + long long u=0, ni=0, sy=0, id=0, iow=0, irq=0, sirq=0; + int n = sscanf(line+4, "%lld %lld %lld %lld %lld %lld %lld", + &u, &ni, &sy, &id, &iow, &irq, &sirq); + if (n < 4) break; + s->user = u + ni; + s->sys = sy + irq + sirq; + s->idle = id; + s->iowait = (n >= 5) ? iow : 0; + s->total = u + ni + sy + id + iow + irq + sirq; + ok = true; break; + } + fclose(f); return ok; +} + +static void read_cpu_extras(kvdict_t *d) { + FILE *fi = fopen("/proc/cpuinfo", "r"); + if (fi) { + char ln[256]; int cores = 0; + while (fgets(ln, sizeof(ln), fi)) if (strncmp(ln,"processor",9)==0) cores++; + fclose(fi); + if (cores > 0) kv_set_int(d, "cpu_core_count", cores); + } + fi = fopen("/proc/uptime", "r"); + if (fi) { + double up = 0; + if (fscanf(fi, "%lf", &up) == 1) kv_set_int(d, "uptime_seconds", (long long)up); + fclose(fi); + } +} + +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + +static bool read_cpu_sample(cpu_sample_t *s) { + /* kern.cp_time: CP_USER(0) CP_NICE(1) CP_SYS(2) CP_INTR(3) CP_IDLE(4) */ + unsigned long cp[5] = {0}; + size_t len = sizeof(cp); + if (sysctlbyname("kern.cp_time", cp, &len, NULL, 0) != 0) return false; + s->user = (long long)cp[0] + (long long)cp[1]; + s->sys = (long long)cp[2] + (long long)cp[3]; /* SYS + INTR */ + s->idle = (long long)cp[4]; + s->iowait = 0; + s->total = s->user + (long long)cp[2] + (long long)cp[3] + s->idle; + return true; +} + +static void read_cpu_extras(kvdict_t *d) { + int ncpu = 0; size_t len = sizeof(ncpu); + if (sysctlbyname("hw.ncpu", &ncpu, &len, NULL, 0) == 0 && ncpu > 0) + kv_set_int(d, "cpu_core_count", ncpu); + struct timeval bt; len = sizeof(bt); + if (sysctlbyname("kern.boottime", &bt, &len, NULL, 0) == 0) + kv_set_int(d, "uptime_seconds", (long long)(time(NULL) - bt.tv_sec)); +} + +#endif /* platform cpu */ + +static cpu_sample_t g_cpu_prev; +static bool g_cpu_have_prev = false; + +static void plugin_cpu_monitor(conn_t *c, const config_t *cfg) { + (void)cfg; + cpu_sample_t t1, t2; + if (!g_cpu_have_prev) { + if (!read_cpu_sample(&t1)) return; + sleep(1); + if (!read_cpu_sample(&t2)) return; + } else { + t1 = g_cpu_prev; + if (!read_cpu_sample(&t2)) return; + } + g_cpu_prev = t2; g_cpu_have_prev = true; + + long long idle_delta = (t2.idle + t2.iowait) - (t1.idle + t1.iowait); + long long total_delta = t2.total - t1.total; + if (total_delta == 0) return; + + kvdict_t d; kv_clear(&d); + kv_set(&d, "plugin", "cpu_monitor"); + kv_set_dbl(&d, "cpu_percent", round(100.0*(1.0-(double)idle_delta/total_delta)*10)/10.0); + kv_set_dbl(&d, "cpu_user", round(100.0*(t2.user - t1.user) / total_delta*10)/10.0); + kv_set_dbl(&d, "cpu_system", round(100.0*(t2.sys - t1.sys) / total_delta*10)/10.0); + kv_set_dbl(&d, "cpu_idle", round(100.0*(t2.idle - t1.idle) / total_delta*10)/10.0); + if (t2.iowait || t1.iowait) + kv_set_dbl(&d, "cpu_iowait", round(100.0*(t2.iowait - t1.iowait) / total_delta*10)/10.0); + double la[3]; + if (getloadavg(la, 3) == 3) { + kv_set_dbl(&d, "load_1min", round(la[0]*100)/100.0); + kv_set_dbl(&d, "load_5min", round(la[1]*100)/100.0); + kv_set_dbl(&d, "load_15min", round(la[2]*100)/100.0); + } + read_cpu_extras(&d); + kv_set_dbl(&d, "_timestamp", now_ts()); + conn_send(c, "PLG", &d); + LOGD("sent cpu_monitor"); +} + +/* ============================================================ + * Plugin: memory_monitor + * Linux: /proc/meminfo + * FreeBSD: sysctl vm.stats.vm.* + * NetBSD: sysctl vm.uvmexp (struct uvmexp) + * ============================================================ */ + +/* emit the common kvdict fields and send */ +static void mem_send(conn_t *c, + long long tot, long long used, long long av, long long fr, + long long act, long long ina, long long cac, long long buf, + long long stot, long long sused) { + kvdict_t d; kv_clear(&d); + kv_set(&d, "plugin", "memory_monitor"); + kv_set_ull(&d, "memory_total", (unsigned long long)tot); + kv_set_ull(&d, "memory_used", (unsigned long long)used); + kv_set_ull(&d, "memory_available", (unsigned long long)av); + kv_set_ull(&d, "memory_free", (unsigned long long)fr); + char pct[32]; + snprintf(pct, sizeof(pct), "%.1f", tot ? 100.0*used/tot : 0.0); + kv_set(&d, "memory_percent", pct); + if (buf) kv_set_ull(&d, "memory_buffers", (unsigned long long)buf); + if (cac) kv_set_ull(&d, "memory_cached", (unsigned long long)cac); + if (act) kv_set_ull(&d, "memory_active", (unsigned long long)act); + if (ina) kv_set_ull(&d, "memory_inactive", (unsigned long long)ina); + if (stot > 0) { + long long sfr = stot - sused; + kv_set_ull(&d, "swap_total", (unsigned long long)stot); + kv_set_ull(&d, "swap_used", (unsigned long long)sused); + kv_set_ull(&d, "swap_free", (unsigned long long)sfr); + snprintf(pct, sizeof(pct), "%.1f", 100.0*sused/stot); + kv_set(&d, "swap_percent", pct); + } + kv_set_dbl(&d, "_timestamp", now_ts()); + conn_send(c, "PLG", &d); + LOGD("sent memory_monitor"); +} + +#ifdef __linux__ + +static void plugin_memory_monitor(conn_t *c, const config_t *cfg) { + (void)cfg; + FILE *f = fopen("/proc/meminfo", "r"); + if (!f) return; + long long tot=0, fr=0, av=0, buf=0, cac=0, act=0, ina=0, stot=0, sfr=0; + char line[256]; + while (fgets(line, sizeof(line), f)) { + long long v; char key[64]; + if (sscanf(line, "%63s %lld", key, &v) != 2) continue; + int kl = strlen(key); if (key[kl-1]==':') key[kl-1]='\0'; + if (!strcmp(key,"MemTotal")) tot=v; + else if (!strcmp(key,"MemFree")) fr=v; + else if (!strcmp(key,"MemAvailable")) av=v; + else if (!strcmp(key,"Buffers")) buf=v; + else if (!strcmp(key,"Cached")) cac=v; + else if (!strcmp(key,"Active")) act=v; + else if (!strcmp(key,"Inactive")) ina=v; + else if (!strcmp(key,"SwapTotal")) stot=v; + else if (!strcmp(key,"SwapFree")) sfr=v; + } + fclose(f); + if (!tot) return; + if (!av) av = fr; + + /* ZFS ARC is reclaimable memory not counted in MemAvailable */ + long long arc_kb = 0; + FILE *arc = fopen("/proc/spl/kstat/zfs/arcstats", "r"); + if (arc) { + char ak[64]; long long av2; int at; + while (fscanf(arc, "%63s %d %lld\n", ak, &at, &av2) == 3) + if (!strcmp(ak,"size")) { arc_kb = av2/1024; break; } + fclose(arc); + } + av = (av + arc_kb < tot) ? av + arc_kb : tot; + long long used = tot - av; + /* values from /proc/meminfo are in kB */ + mem_send(c, tot*1024, used*1024, av*1024, fr*1024, + act*1024, ina*1024, cac*1024, buf*1024, + stot*1024, (stot-sfr)*1024); +} + +#elif defined(__FreeBSD__) || defined(__DragonFly__) + +static void plugin_memory_monitor(conn_t *c, const config_t *cfg) { + (void)cfg; + long ps = getpagesize(); + + unsigned long physmem = 0; size_t len = sizeof(physmem); + sysctlbyname("hw.physmem", &physmem, &len, NULL, 0); + if (!physmem) return; + + unsigned int v_free=0, v_inactive=0, v_active=0, v_cache=0; + len = sizeof(v_free); sysctlbyname("vm.stats.vm.v_free_count", &v_free, &len, NULL, 0); + len = sizeof(v_inactive); sysctlbyname("vm.stats.vm.v_inactive_count", &v_inactive, &len, NULL, 0); + len = sizeof(v_active); sysctlbyname("vm.stats.vm.v_active_count", &v_active, &len, NULL, 0); + len = sizeof(v_cache); sysctlbyname("vm.stats.vm.v_cache_count", &v_cache, &len, NULL, 0); + + long long tot = (long long)physmem; + long long fr = (long long)v_free * ps; + long long act = (long long)v_active * ps; + long long ina = (long long)v_inactive * ps; + long long cac = (long long)v_cache * ps; + long long av = fr + ina + cac; if (av > tot) av = tot; + long long used = tot - av; + mem_send(c, tot, used, av, fr, act, ina, cac, 0, 0, 0); +} + +#elif defined(__NetBSD__) + +static void plugin_memory_monitor(conn_t *c, const config_t *cfg) { + (void)cfg; + struct uvmexp uvm; + size_t len = sizeof(uvm); + int mib[2] = {CTL_VM, VM_UVMEXP}; + if (sysctl(mib, 2, &uvm, &len, NULL, 0) != 0) return; + + long long ps = uvm.pagesize; + long long tot = (long long)uvm.npages * ps; + long long fr = (long long)uvm.free * ps; + long long act = (long long)uvm.active * ps; + long long ina = (long long)uvm.inactive * ps; + long long av = fr + ina; if (av > tot) av = tot; + long long used = tot - av; + long long stot = (long long)uvm.swpages * ps; + long long sinuse = (long long)uvm.swpginuse * ps; + mem_send(c, tot, used, av, fr, act, ina, 0, 0, stot, sinuse); +} + +#endif /* platform memory */ + +/* ============================================================ + * Plugin: disk_monitor (df -P) + * ============================================================ */ + +static void plugin_disk_monitor(conn_t *c, const config_t *cfg) { + FILE *f = popen("df -P", "r"); + if (!f) return; + + char *json = malloc(MAX_VAL); if (!json) { pclose(f); return; } + int jlen = 0; bool first = true; + json[jlen++] = '{'; + + char line[512]; + if (!fgets(line, sizeof(line), f)) { pclose(f); free(json); return; } /* skip header */ + while (fgets(line, sizeof(line), f)) { + char fs[256], mount[256], pcts[16]; + long long tk, uk, ak; int pct; + if (sscanf(line, "%255s %lld %lld %lld %15s %255s", fs, &tk, &uk, &ak, pcts, mount) != 6) continue; + pct = atoi(pcts); + if (cfg->disk_mount_count > 0) { + bool found = false; + for (int i = 0; i < cfg->disk_mount_count; i++) + if (!strcmp(cfg->disk_mounts[i], mount)) { found=true; break; } + if (!found) continue; + } + /* JSON-escape the mount point */ + char esc[512]; int ei = 0; + for (int i = 0; mount[i] && ei < (int)sizeof(esc)-2; i++) { + if (mount[i]=='"'||mount[i]=='\\') esc[ei++]='\\'; + esc[ei++] = mount[i]; + } + esc[ei] = '\0'; + char entry[512]; + int el = snprintf(entry, sizeof(entry), + "%s\"%s\":{\"total\":%lld,\"used\":%lld,\"free\":%lld,\"percent\":%d}", + first ? "" : ",", esc, tk*1024, uk*1024, ak*1024, pct); + if (jlen + el < MAX_VAL - 2) { memcpy(json+jlen, entry, el); jlen+=el; first=false; } + } + pclose(f); + json[jlen++] = '}'; json[jlen] = '\0'; + if (first) { free(json); return; } + + kvdict_t d; kv_clear(&d); + kv_set(&d, "plugin", "disk_monitor"); + char *jval = malloc(MAX_VAL + 1); + if (jval) { snprintf(jval, MAX_VAL, "@%s", json); kv_set(&d, "partitions", jval); free(jval); } + kv_set_dbl(&d, "_timestamp", now_ts()); + conn_send(c, "PLG", &d); + free(json); + LOGD("sent disk_monitor"); +} + +/* ============================================================ + * Plugin: network_monitor + * Linux: /proc/net/dev + * BSD: getifaddrs() + struct if_data (AF_LINK) + * ============================================================ */ + +typedef struct { char iface[64]; unsigned long long rx, tx; } net_stat_t; +static net_stat_t g_net_prev[64]; +static int g_net_prev_n = 0; +static double g_net_prev_ts = 0; + +#ifdef __linux__ + +static int read_net_stats(net_stat_t *out, int max) { + FILE *f = fopen("/proc/net/dev", "r"); + if (!f) return 0; + char line[512]; int n = 0; + if (!fgets(line, sizeof(line), f)) { fclose(f); return 0; } /* header 1 */ + if (!fgets(line, sizeof(line), f)) { fclose(f); return 0; } /* header 2 */ + while (fgets(line, sizeof(line), f) && n < max) { + char *col = strchr(line, ':'); + if (!col) continue; + *col = '\0'; + char *iface = line; while (*iface==' ') iface++; + unsigned long long rx, tx, dummy; + if (sscanf(col+1, "%llu %llu %llu %llu %llu %llu %llu %llu %llu", + &rx,&dummy,&dummy,&dummy,&dummy,&dummy,&dummy,&dummy,&tx) == 9) { + snprintf(out[n].iface, 64, "%.*s", 63, iface); + out[n].rx = rx; out[n].tx = tx; n++; + } + } + fclose(f); return n; +} + +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + +static int read_net_stats(net_stat_t *out, int max) { + struct ifaddrs *ifap; + if (getifaddrs(&ifap) != 0) return 0; + int n = 0; + for (struct ifaddrs *ifa = ifap; ifa && n < max; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_LINK) continue; + struct if_data *ifd = (struct if_data *)ifa->ifa_data; + if (!ifd) continue; + snprintf(out[n].iface, 64, "%.*s", 63, ifa->ifa_name); + out[n].rx = (unsigned long long)ifd->ifi_ibytes; + out[n].tx = (unsigned long long)ifd->ifi_obytes; + n++; + } + freeifaddrs(ifap); + return n; +} + +#endif /* platform network */ + +static void plugin_network_monitor(conn_t *c, const config_t *cfg) { + net_stat_t curr[64]; + int cn = read_net_stats(curr, 64); + double now = now_ts(); + + if (g_net_prev_n == 0) { + memcpy(g_net_prev, curr, cn * sizeof(net_stat_t)); + g_net_prev_n = cn; g_net_prev_ts = now; + return; + } + double dt = now - g_net_prev_ts; + if (dt <= 0) return; + + char *json = malloc(MAX_VAL); if (!json) return; + int jlen = 0; bool first = true; + json[jlen++] = '{'; + + for (int i = 0; i < cn; i++) { + bool skip = false; + for (int s = 0; s < cfg->net_skip_count; s++) + if (!strcmp(cfg->net_skip[s], curr[i].iface)) { skip=true; break; } + if (skip) continue; + net_stat_t *prev = NULL; + for (int j = 0; j < g_net_prev_n; j++) + if (!strcmp(g_net_prev[j].iface, curr[i].iface)) { prev=&g_net_prev[j]; break; } + if (!prev) continue; + long long rd = (long long)(curr[i].rx - prev->rx); + long long td = (long long)(curr[i].tx - prev->tx); + char entry[256]; + int el = snprintf(entry, sizeof(entry), + "%s\"%s\":{\"bytes_recv\":%llu,\"bytes_sent\":%llu" + ",\"bytes_recv_delta\":%lld,\"bytes_sent_delta\":%lld}", + first?"":"," , curr[i].iface, curr[i].rx, curr[i].tx, rd, td); + if (jlen + el < MAX_VAL - 2) { memcpy(json+jlen, entry, el); jlen+=el; first=false; } + } + json[jlen++] = '}'; json[jlen] = '\0'; + memcpy(g_net_prev, curr, cn * sizeof(net_stat_t)); + g_net_prev_n = cn; g_net_prev_ts = now; + if (first) { free(json); return; } + + kvdict_t d; kv_clear(&d); + kv_set(&d, "plugin", "network_monitor"); + char *jval = malloc(MAX_VAL + 1); + if (jval) { snprintf(jval, MAX_VAL, "@%s", json); kv_set(&d, "interfaces", jval); free(jval); } + kv_set_dbl(&d, "_timestamp", now_ts()); + conn_send(c, "PLG", &d); + free(json); + LOGD("sent network_monitor"); +} + +/* ============================================================ + * Plugin: ping_monitor + * ============================================================ */ + +static bool do_ping(const char *host, int count, int timeout, + double *rmin, double *ravg, double *rmax, double *loss) { + *rmin = *ravg = *rmax = 1.0/0.0; /* inf */ + *loss = 100.0; + char cmd[512]; + snprintf(cmd, sizeof(cmd), "ping -c %d -W %d %s 2>/dev/null", count, timeout, host); + FILE *f = popen(cmd, "r"); + if (!f) return false; + char line[512]; bool got = false; + while (fgets(line, sizeof(line), f)) { + char *p = strstr(line, "% packet loss"); + if (p) { + while (p > line && (isdigit((unsigned char)*(p-1)) || *(p-1)=='.')) p--; + *loss = atof(p); + } + p = strstr(line, "min/avg/max"); + if (p) { + p = strchr(p, '='); + if (p) { sscanf(p+1, " %lf/%lf/%lf", rmin, ravg, rmax); got=true; } + } + } + pclose(f); return got; +} + +static void plugin_ping_monitor(conn_t *c, const config_t *cfg) { + if (!cfg->ping_host_count) return; + kvdict_t d; kv_clear(&d); + kv_set(&d, "plugin", "ping_monitor"); + for (int i = 0; i < cfg->ping_host_count; i++) { + double rmin, ravg, rmax, loss; + bool ok = do_ping(cfg->ping_hosts[i], cfg->ping_count, cfg->ping_timeout, + &rmin, &ravg, &rmax, &loss); + char key[300]; + snprintf(key, sizeof(key), "%s", cfg->ping_hosts[i]); + for (char *p = key; *p; p++) if (!isalnum((unsigned char)*p) && *p!='_') *p='_'; + char kb[512]; + if (ok) { + snprintf(kb,sizeof(kb),"%s_rtt_min",key); kv_set_dbl(&d,kb,rmin); + snprintf(kb,sizeof(kb),"%s_rtt_avg",key); kv_set_dbl(&d,kb,ravg); + snprintf(kb,sizeof(kb),"%s_rtt_max",key); kv_set_dbl(&d,kb,rmax); + snprintf(kb,sizeof(kb),"%s_loss",key); kv_set_dbl(&d,kb,loss); + } else { + snprintf(kb,sizeof(kb),"%s_rtt_min",key); kv_set(&d,kb,"inf"); + snprintf(kb,sizeof(kb),"%s_rtt_avg",key); kv_set(&d,kb,"inf"); + snprintf(kb,sizeof(kb),"%s_rtt_max",key); kv_set(&d,kb,"inf"); + snprintf(kb,sizeof(kb),"%s_loss",key); kv_set_dbl(&d,kb,100.0); + } + } + kv_set_dbl(&d, "_timestamp", now_ts()); + conn_send(c, "PLG", &d); + LOGD("sent ping_monitor"); +} + +/* ============================================================ + * Plugin: nagios_runner + * ============================================================ */ + +static const char *nagios_status_str(int rc) { + switch (rc) { case 0:return"OK"; case 1:return"WARNING"; case 2:return"CRITICAL"; default:return"UNKNOWN"; } +} + +static void parse_perfdata(const char *output, kvdict_t *d, const char *prefix) { + const char *pipe = strchr(output, '|'); + if (!pipe) return; + const char *p = pipe + 1; + while (*p) { + while (*p==' ') p++; + if (!*p) break; + char label[128]; int li = 0; + if (*p == '\'') { + p++; + while (*p && *p != '\'') label[li++] = *p++; + if (*p == '\'') p++; + } else { + while (*p && *p != '=') label[li++] = *p++; + } + label[li] = '\0'; + if (*p != '=') { while (*p && *p!=' ') p++; continue; } + p++; + char val[64]; int vi = 0; + while (*p && (isdigit((unsigned char)*p) || *p=='.' || *p=='-')) val[vi++] = *p++; + val[vi] = '\0'; + char uom[16]; int ui = 0; + while (*p && *p!=';' && *p!=' ') uom[ui++] = *p++; + uom[ui] = '\0'; + char key[256]; + snprintf(key, sizeof(key), "%s_%s", prefix, label); kv_set(d, key, val); + if (uom[0]) { snprintf(key,sizeof(key),"%s_%s_uom",prefix,label); kv_set(d,key,uom); } + for (int n=0; n<4 && *p==';'; n++) { p++; while (*p && *p!=';' && *p!=' ') p++; } + while (*p==' ') p++; + } +} + +static void plugin_nagios_runner(conn_t *c, const config_t *cfg) { + if (!cfg->nagios_count) return; + kvdict_t d; kv_clear(&d); + kv_set(&d, "plugin", "nagios_runner"); + for (int i = 0; i < cfg->nagios_count; i++) { + const char *name = cfg->nagios[i].name; + char output[4096] = ""; int rc = 3; + FILE *f = popen(cfg->nagios[i].cmd, "r"); + if (f) { + size_t _nr = fread(output, 1, sizeof(output)-1, f); (void)_nr; + int ret = pclose(f); + rc = WIFEXITED(ret) ? WEXITSTATUS(ret) : 3; + if (rc < 0 || rc > 3) rc = 3; + } + char *nl = strchr(output, '\n'); if (nl) *nl = '\0'; + char msg[512]; char *pipe = strchr(output,'|'); + if (pipe) snprintf(msg, sizeof(msg), "%.*s", (int)(pipe-output), output); + else snprintf(msg, sizeof(msg), "%s", output); + char key[128]; + snprintf(key,sizeof(key),"%s_status",name); kv_set(&d,key,nagios_status_str(rc)); + snprintf(key,sizeof(key),"%s_status_code",name); kv_set_int(&d,key,rc); + snprintf(key,sizeof(key),"%s_output",name); kv_set(&d,key,msg); + parse_perfdata(output, &d, name); + } + kv_set_dbl(&d, "_timestamp", now_ts()); + conn_send(c, "PLG", &d); + LOGD("sent nagios_runner"); +} + +/* ============================================================ + * Monitor thread: all periodic plugins in one thread + * ============================================================ */ + +typedef struct { conn_t *conn; const config_t *cfg; } thread_ctx_t; + +static void *monitor_thread(void *arg) { + thread_ctx_t *ctx = arg; + conn_t *conn = ctx->conn; + const config_t *cfg = ctx->cfg; + + time_t next_cpu = time(NULL); + time_t next_mem = time(NULL); + time_t next_disk = time(NULL); + time_t next_net = time(NULL); + time_t next_ping = time(NULL); + time_t next_nagios = time(NULL); + + /* Prime network baseline */ + plugin_network_monitor(conn, cfg); + + while (g_running) { + time_t now = time(NULL); + if (now >= next_cpu) { plugin_cpu_monitor(conn, cfg); next_cpu = now + cfg->cpu_interval; } + if (now >= next_mem) { plugin_memory_monitor(conn, cfg); next_mem = now + cfg->mem_interval; } + if (now >= next_disk) { plugin_disk_monitor(conn, cfg); next_disk = now + cfg->disk_interval; } + if (now >= next_net) { plugin_network_monitor(conn, cfg); next_net = now + cfg->net_interval; } + if (cfg->ping_host_count && now >= next_ping) { + plugin_ping_monitor(conn, cfg); next_ping = now + cfg->ping_interval; + } + if (cfg->nagios_count && now >= next_nagios) { + plugin_nagios_runner(conn, cfg); next_nagios = now + cfg->nagios_interval; + } + sleep(1); + } + return NULL; +} + +/* ============================================================ + * Daemonize + * ============================================================ */ + +static void daemonize(void) { + for (int n = 0; n < 2; n++) { + pid_t pid = fork(); + if (pid < 0) { perror("fork"); exit(1); } + if (pid > 0) exit(0); + if (n == 0) { setsid(); if (chdir("/")) {}; umask(0); } + } + int fd; + fd = open("/dev/zero", O_RDONLY); if (fd>=0) { dup2(fd,0); close(fd); } + fd = open("/dev/null", O_WRONLY); if (fd>=0) { dup2(fd,1); close(fd); } + fd = open("/dev/null", O_WRONLY); if (fd>=0) { dup2(fd,2); close(fd); } +} + +/* ============================================================ + * CLI + * ============================================================ */ + +static void usage(const char *prog) { + fprintf(stderr, + "Usage: %s [options] host [host...]\n" + " -b Send boot message\n" + " -c FILE Config file (JSON)\n" + " -m MSG Send one-shot message\n" + " -n NAME Override hostname\n" + " -d Daemonize\n" + " -v Verbose (info)\n" + " -x Debug\n" + " -h Help\n", prog); +} + +int main(int argc, char **argv) { + bool do_boot = false; + bool do_daemon = false; + const char *cfgpath = NULL; + const char *message = NULL; + const char *nameov = NULL; + + int opt; + while ((opt = getopt(argc, argv, "bc:m:n:dvxh")) != -1) { + switch (opt) { + case 'b': do_boot = true; break; + case 'c': cfgpath = optarg; break; + case 'm': message = optarg; break; + case 'n': nameov = optarg; break; + case 'd': do_daemon = true; break; + case 'v': g_log_level = LL_INFO; break; + case 'x': g_log_level = LL_DEBUG; break; + case 'h': usage(argv[0]); return 0; + default: usage(argv[0]); return 1; + } + } + if (optind >= argc) { fprintf(stderr,"error: host required\n"); usage(argv[0]); return 1; } + + const char *hosts[MAX_HOSTS]; int nhost = 0; + for (int i = optind; i < argc && nhost < MAX_HOSTS; i++) hosts[nhost++] = argv[i]; + + config_t cfg; + config_load(&cfg, cfgpath); + + if (do_daemon) { + daemonize(); + g_use_syslog = true; + openlog("hbc", LOG_PID, LOG_DAEMON); + } + + char iam[256]; + if (nameov) { + snprintf(iam, sizeof(iam), "%s", nameov); + } else { + gethostname(iam, sizeof(iam)); + char *dot = strchr(iam, '.'); if (dot) *dot = '\0'; + } + + int conn_id = 1; + for (int i = 0; i < nhost; i++) { + struct addrinfo hints = {0}, *res = NULL; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + char ps[16]; snprintf(ps, sizeof(ps), "%d", cfg.hb_port); + if (getaddrinfo(hosts[i], ps, &hints, &res) != 0) { + LOGE("cannot resolve %s", hosts[i]); continue; + } + for (struct addrinfo *ai = res; ai && g_nconns < MAX_HOSTS; ai = ai->ai_next) { + conn_t *c = &g_conns[g_nconns]; + memset(c, 0, sizeof(*c)); + c->conn_id = conn_id++; c->port = cfg.hb_port; + c->af = ai->ai_family; c->sockfd = -1; + snprintf(c->name, sizeof(c->name), "%s", iam); + void *addr = (ai->ai_family == AF_INET) + ? (void *)&((struct sockaddr_in *)ai->ai_addr)->sin_addr + : (void *)&((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; + inet_ntop(ai->ai_family, addr, c->addr, sizeof(c->addr)); + if (conn_open(c)) { g_nconns++; LOGI("connected to %s", c->addr); } + } + freeaddrinfo(res); + } + if (!g_nconns) { LOGE("no connections established"); return 1; } + + struct sigaction sa = {0}; + sa.sa_handler = sig_handler; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); + + conn_t *primary = &g_conns[0]; + LOGI("hbc_mini-c %s on %s -> %s port=%d interval=%ds", + HBC_VERSION, iam, hosts[0], cfg.hb_port, cfg.interval); + + /* Boot / one-shot message */ + bool send_shutdown = false; + if (do_boot || message) { + kvdict_t bm; kv_clear(&bm); + kv_set_int(&bm, "acks", 0); + if (do_boot) { kv_set_int(&bm,"boot",1); send_shutdown=true; } + if (message) { kv_set(&bm,"service","service"); kv_set(&bm,"msg",message); } + conn_send(primary, "HTB", &bm); + if (message && !do_daemon) { + usleep(300000); + for (int i=0;i= cfg.interval) { + for (int i = 0; i < g_nconns; i++) { + kvdict_t hb; kv_clear(&hb); + kv_set_int(&hb, "acks", g_conns[i].ack_count); + kv_set_dbl(&hb, "rtt", g_conns[i].rtt); + kv_set_int(&hb, "interval", cfg.interval); + conn_send(&g_conns[i], "HTB", &hb); + } + last_hb = now; + } + + fd_set rset; FD_ZERO(&rset); int maxfd = -1; + for (int i = 0; i < g_nconns; i++) { + if (g_conns[i].sockfd >= 0) { FD_SET(g_conns[i].sockfd,&rset); if(g_conns[i].sockfd>maxfd) maxfd=g_conns[i].sockfd; } + } + if (maxfd >= 0) { + struct timeval tv = {1, 0}; + if (select(maxfd+1, &rset, NULL, NULL, &tv) > 0) + for (int i = 0; i < g_nconns; i++) + if (g_conns[i].sockfd>=0 && FD_ISSET(g_conns[i].sockfd,&rset)) + conn_recv(&g_conns[i]); + } else { sleep(1); } + + if (primary->request_info) { + primary->request_info = false; + LOGI("refreshing info plugins on server request"); + plugin_os_info(primary, &cfg); + } + } + + pthread_cancel(tid); + pthread_join(tid, NULL); + + if (send_shutdown) { + kvdict_t sh; kv_clear(&sh); + kv_set_int(&sh, "shutdown", 1); + kv_set_int(&sh, "acks", primary->ack_count); + conn_send(primary, "HTB", &sh); + usleep(300000); + } + for (int i = 0; i < g_nconns; i++) conn_close(&g_conns[i]); + if (g_use_syslog) closelog(); + if (g_restart) { LOGI("restarting..."); execv(argv[0], argv); } + return 0; +}