#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: config.c,v 1.15 2024/10/06 19:08:34 rillig Exp $");
#endif
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <err.h>
#include <errno.h>
#include <sys/queue.h>
#include <prop/proplib.h>
#include "envstat.h"
static SLIST_HEAD(, sensor_block) sensor_block_list =
SLIST_HEAD_INITIALIZER(&sensor_block_list);
static SLIST_HEAD(, device_block) device_block_list =
SLIST_HEAD_INITIALIZER(&device_block_list);
enum {
VALUE_ERR,
PROP_ERR,
SENSOR_ERR,
DEV_ERR
};
static prop_dictionary_t cfdict, sensordict, refreshdict;
__dead static void config_errmsg(int, const char *, const char *);
static void
config_errmsg(int lvl, const char *key, const char *key2)
{
(void)printf("envstat: ");
switch (lvl) {
case VALUE_ERR:
(void)printf("invalid value for '%s' in `%s'\n",
key, key2);
break;
case PROP_ERR:
(void)printf("the '%s' property is not allowed "
"in `%s'\n", key, key2);
break;
case SENSOR_ERR:
(void)printf("'%s' is not a valid sensor in the "
"`%s' device\n", key, key2);
break;
case DEV_ERR:
(void)printf("device `%s' doesn't exist\n", key);
break;
}
(void)printf("envstat: please fix the configuration file!\n");
exit(EXIT_FAILURE);
}
void
config_dict_add_prop(const char *key, char *value)
{
if (!key || !value)
return;
if (!sensordict) {
sensordict = prop_dictionary_create();
if (!sensordict)
err(EXIT_FAILURE, "sensordict");
}
if (!prop_dictionary_set_string(sensordict, key, value))
err(EXIT_FAILURE, "prop_dict_set_string");
}
void
config_dict_mark(void)
{
struct sensor_block *sb;
sb = calloc(1, sizeof(*sb));
if (!sb)
err(EXIT_FAILURE, "!sb");
sb->dict = prop_dictionary_create();
if (!sb->dict)
err(EXIT_FAILURE, "!sb->dict");
sb->dict = prop_dictionary_copy(sensordict);
SLIST_INSERT_HEAD(&sensor_block_list, sb, sb_head);
config_dict_destroy(sensordict);
}
void
config_dict_dump(prop_dictionary_t d)
{
char *buf;
buf = prop_dictionary_externalize(d);
(void)printf("%s", buf);
free(buf);
}
static void
display_object(prop_object_t obj, bool nflag)
{
char *xml;
prop_object_t next_obj;
prop_object_iterator_t iter;
if (obj == NULL)
exit(EXIT_FAILURE);
switch (prop_object_type(obj)) {
case PROP_TYPE_BOOL:
printf("%s\n", prop_bool_true(obj) ? "true" : "false");
break;
case PROP_TYPE_NUMBER:
printf("%" PRId64 "\n", prop_number_signed_value(obj));
break;
case PROP_TYPE_STRING:
printf("%s\n", prop_string_value(obj));
break;
case PROP_TYPE_DICTIONARY:
xml = prop_dictionary_externalize(obj);
printf("%s", xml);
free(xml);
break;
case PROP_TYPE_ARRAY:
iter = prop_array_iterator(obj);
if (!nflag)
printf("Array:\n");
while ((next_obj = prop_object_iterator_next(iter)) != NULL)
display_object(next_obj, nflag);
break;
default:
errx(EXIT_FAILURE, "Unhandled type %d", prop_object_type(obj));
}
}
void
config_dict_extract(prop_dictionary_t dict, const char *prop, bool nflag)
{
char *s, *p, *cur, *ep = NULL;
prop_object_t obj;
unsigned long ind;
obj = dict;
cur = NULL;
s = strdup(prop);
p = strtok_r(s, "/", &ep);
while (p) {
cur = p;
p = strtok_r(NULL, "/", &ep);
switch (prop_object_type(obj)) {
case PROP_TYPE_DICTIONARY:
obj = prop_dictionary_get(obj, cur);
if (obj == NULL)
exit(EXIT_FAILURE);
break;
case PROP_TYPE_ARRAY:
ind = strtoul(cur, NULL, 0);
obj = prop_array_get(obj, ind);
if (obj == NULL)
exit(EXIT_FAILURE);
break;
default:
errx(EXIT_FAILURE, "Select neither dict nor array with"
" `%s'", cur);
}
}
if (obj != NULL && cur != NULL)
display_object(obj, nflag);
free(s);
}
prop_dictionary_t
config_dict_parsed(void)
{
return cfdict;
}
void
config_dict_adddev_prop(const char *key, const char *value, int line)
{
prop_dictionary_t d = NULL;
uint64_t timo;
size_t len;
char *endptr, *strval;
bool minutes, hours;
minutes = hours = false;
if (strchr(value, 's')) {
} else if (strchr(value, 'm')) {
minutes = true;
} else if (strchr(value, 'h')) {
hours = true;
} else
goto bad;
len = strlen(value);
strval = calloc(len, sizeof(*value));
if (!strval)
err(EXIT_FAILURE, "calloc");
(void)strlcpy(strval, value, len);
timo = strtoul(strval, &endptr, 10);
if (*endptr != '\0') {
free(strval);
goto bad;
}
free(strval);
refreshdict = prop_dictionary_create();
if (!refreshdict)
err(EXIT_FAILURE, "prop_dict_create refresh");
d = prop_dictionary_create();
if (!d)
err(EXIT_FAILURE, "prop_dict_create refresh 1");
if (minutes)
timo *= 60;
else if (hours) {
if (timo > 999)
goto bad;
timo *= 60 * 60;
} else {
if (timo < 1)
goto bad;
}
if (!prop_dictionary_set_uint64(d, key, timo))
err(EXIT_FAILURE, "%s", key);
if (!prop_dictionary_set(refreshdict, "device-properties", d))
err(EXIT_FAILURE, "device-properties %s", key);
prop_object_release(d);
return;
bad:
(void)printf("envstat: invalid value for the '%s' "
"property at line %d\n", key, line);
(void)printf("envstat: please fix the configuration file!\n");
if (d)
prop_object_release(d);
exit(EXIT_FAILURE);
}
void
config_dict_destroy(prop_dictionary_t d)
{
prop_object_iterator_t iter;
prop_object_t obj;
iter = prop_dictionary_iterator(d);
if (!iter)
err(EXIT_FAILURE, "!iter");
while ((obj = prop_object_iterator_next(iter)) != NULL) {
prop_dictionary_remove(d,
prop_dictionary_keysym_value(obj));
prop_object_iterator_reset(iter);
}
prop_object_iterator_release(iter);
}
void
config_devblock_add(const char *key, prop_dictionary_t kdict)
{
struct device_block *db;
struct sensor_block *sb;
prop_array_t array;
prop_object_iterator_t iter;
prop_dictionary_t sdict;
prop_object_t obj;
prop_string_t lindex;
const char *sensor;
bool sensor_found = false;
if (!key)
err(EXIT_FAILURE, "devblock !key");
array = prop_dictionary_get(kdict, key);
if (!array)
config_errmsg(DEV_ERR, key, NULL);
SLIST_FOREACH(sb, &sensor_block_list, sb_head) {
lindex = prop_dictionary_get(sb->dict, "index");
sensor = prop_string_value(lindex);
iter = prop_array_iterator(array);
if (!iter)
err(EXIT_FAILURE, "prop_array_iterator devblock");
while ((sdict = prop_object_iterator_next(iter)) != NULL) {
obj = prop_dictionary_get(sdict, "index");
if (prop_string_equals(lindex, obj)) {
sensor_found = true;
break;
}
}
if (!sensor_found) {
prop_object_iterator_release(iter);
config_errmsg(SENSOR_ERR, sensor, key);
}
config_devblock_check_sensorprops(sdict, sb->dict, sensor);
prop_object_iterator_release(iter);
}
db = calloc(1, sizeof(*db));
if (!db)
err(EXIT_FAILURE, "calloc db");
db->array = prop_array_create();
if (!db->array)
err(EXIT_FAILURE, "prop_array_create devblock");
SLIST_FOREACH(sb, &sensor_block_list, sb_head)
if (!prop_array_add(db->array, sb->dict))
err(EXIT_FAILURE, "prop_array_add");
if (refreshdict) {
if (!prop_array_add(db->array, refreshdict))
err(EXIT_FAILURE, "prop_array_add refreshdict");
prop_object_release(refreshdict);
}
db->dev_key = strdup(key);
SLIST_INSERT_HEAD(&device_block_list, db, db_head);
while (!SLIST_EMPTY(&sensor_block_list)) {
sb = SLIST_FIRST(&sensor_block_list);
SLIST_REMOVE_HEAD(&sensor_block_list, sb_head);
prop_object_release(sb->dict);
free(sb);
}
if (!cfdict) {
cfdict = prop_dictionary_create();
if (!cfdict)
err(EXIT_FAILURE, "prop_dictionary_create cfdict");
}
if (!prop_dictionary_set(cfdict, key, db->array))
err(EXIT_FAILURE, "prop_dictionary_set db->array");
refreshdict = NULL;
}
prop_dictionary_t
config_devblock_getdict(const char *dvname, const char *sensor_key)
{
struct device_block *db;
prop_object_iterator_t iter;
prop_object_t obj, obj2;
if (!dvname || !sensor_key)
return NULL;
SLIST_FOREACH(db, &device_block_list, db_head)
if (strcmp(db->dev_key, dvname) == 0)
break;
if (!db)
return NULL;
iter = prop_array_iterator(db->array);
if (!iter)
return NULL;
while ((obj = prop_object_iterator_next(iter)) != NULL) {
obj2 = prop_dictionary_get(obj, "index");
if (prop_string_equals_string(obj2, sensor_key))
break;
}
prop_object_iterator_release(iter);
return obj;
}
void
config_devblock_check_sensorprops(prop_dictionary_t ksdict,
prop_dictionary_t csdict,
const char *sensor)
{
prop_object_t obj, obj2, obj3;
const char *strval;
char *endptr;
double val;
obj = prop_dictionary_get(csdict, "rfact");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "allow-rfact");
if (prop_bool_true(obj2)) {
strval = prop_string_value(obj);
val = strtod(strval, &endptr);
if (*endptr != '\0')
config_errmsg(VALUE_ERR, "rfact", sensor);
if (!prop_dictionary_set_uint32(csdict, "rfact", val))
err(EXIT_FAILURE, "dict_set rfact");
} else
config_errmsg(PROP_ERR, "rfact", sensor);
}
obj = prop_dictionary_get(csdict, "critical-capacity");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "want-percentage");
obj3 = prop_dictionary_get(ksdict, "monitoring-supported");
if (prop_bool_true(obj2) && prop_bool_true(obj3)) {
strval = prop_string_value(obj);
val = strtod(strval, &endptr);
if ((*endptr != '\0') || (val < 0 || val > 100))
config_errmsg(VALUE_ERR,
"critical-capacity",
sensor);
obj = prop_dictionary_get(ksdict, "max-value");
val = (val / 100) * prop_number_signed_value(obj);
if (!prop_dictionary_set_uint32(csdict,
"critical-capacity",
val))
err(EXIT_FAILURE, "dict_set critcap");
} else
config_errmsg(PROP_ERR, "critical-capacity", sensor);
}
obj = prop_dictionary_get(csdict, "warning-capacity");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "want-percentage");
obj3 = prop_dictionary_get(ksdict, "monitoring-supported");
if (prop_bool_true(obj2) && prop_bool_true(obj3)) {
strval = prop_string_value(obj);
val = strtod(strval, &endptr);
if ((*endptr != '\0') || (val < 0 || val > 100))
config_errmsg(VALUE_ERR,
"warning-capacity",
sensor);
obj = prop_dictionary_get(ksdict, "max-value");
val = (val / 100) * prop_number_signed_value(obj);
if (!prop_dictionary_set_uint32(csdict,
"warning-capacity",
val))
err(EXIT_FAILURE, "dict_set warncap");
} else
config_errmsg(PROP_ERR, "warning-capacity", sensor);
}
obj = prop_dictionary_get(csdict, "high-capacity");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "want-percentage");
obj3 = prop_dictionary_get(ksdict, "monitoring-supported");
if (prop_bool_true(obj2) && prop_bool_true(obj3)) {
strval = prop_string_value(obj);
val = strtod(strval, &endptr);
if ((*endptr != '\0') || (val < 0 || val > 100))
config_errmsg(VALUE_ERR,
"high-capacity",
sensor);
obj = prop_dictionary_get(ksdict, "max-value");
val = (val / 100) * prop_number_signed_value(obj);
if (!prop_dictionary_set_uint32(csdict,
"high-capacity",
val))
err(EXIT_FAILURE, "dict_set highcap");
} else
config_errmsg(PROP_ERR, "high-capacity", sensor);
}
obj = prop_dictionary_get(csdict, "maximum-capacity");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "want-percentage");
obj3 = prop_dictionary_get(ksdict, "monitoring-supported");
if (prop_bool_true(obj2) && prop_bool_true(obj3)) {
strval = prop_string_value(obj);
val = strtod(strval, &endptr);
if ((*endptr != '\0') || (val < 0 || val > 100))
config_errmsg(VALUE_ERR,
"maximum-capacity",
sensor);
obj = prop_dictionary_get(ksdict, "max-value");
val = (val / 100) * prop_number_signed_value(obj);
if (!prop_dictionary_set_uint32(csdict,
"maximum-capacity",
val))
err(EXIT_FAILURE, "dict_set maxcap");
} else
config_errmsg(PROP_ERR, "maximum-capacity", sensor);
}
obj = prop_dictionary_get(csdict, "critical-max");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "monitoring-supported");
if (!prop_bool_true(obj2))
config_errmsg(PROP_ERR, "critical-max", sensor);
strval = prop_string_value(obj);
obj = convert_val_to_pnumber(ksdict, "critical-max",
sensor, strval);
if (!prop_dictionary_set(csdict, "critical-max", obj))
err(EXIT_FAILURE, "prop_dict_set cmax");
}
obj = prop_dictionary_get(csdict, "critical-min");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "monitoring-supported");
if (!prop_bool_true(obj2))
config_errmsg(PROP_ERR, "critical-min", sensor);
strval = prop_string_value(obj);
obj = convert_val_to_pnumber(ksdict, "critical-min",
sensor, strval);
if (!prop_dictionary_set(csdict, "critical-min", obj))
err(EXIT_FAILURE, "prop_dict_set cmin");
}
obj = prop_dictionary_get(csdict, "warning-max");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "monitoring-supported");
if (!prop_bool_true(obj2))
config_errmsg(PROP_ERR, "warning-max", sensor);
strval = prop_string_value(obj);
obj = convert_val_to_pnumber(ksdict, "warning-max",
sensor, strval);
if (!prop_dictionary_set(csdict, "warning-max", obj))
err(EXIT_FAILURE, "prop_dict_set wmax");
}
obj = prop_dictionary_get(csdict, "warning-min");
if (obj) {
obj2 = prop_dictionary_get(ksdict, "monitoring-supported");
if (!prop_bool_true(obj2))
config_errmsg(PROP_ERR, "warning-min", sensor);
strval = prop_string_value(obj);
obj = convert_val_to_pnumber(ksdict, "warning-min",
sensor, strval);
if (!prop_dictionary_set(csdict, "warning-min", obj))
err(EXIT_FAILURE, "prop_dict_set wmin");
}
}
prop_number_t
convert_val_to_pnumber(prop_dictionary_t kdict, const char *prop,
const char *sensor, const char *value)
{
prop_object_t obj;
prop_number_t num;
double val, max, min;
char *strval, *endptr;
bool celsius;
size_t len;
val = max = min = 0;
obj = prop_dictionary_get(kdict, "type");
if (prop_string_equals_string(obj, "Battery capacity"))
config_errmsg(PROP_ERR, prop, sensor);
if (prop_string_equals_string(obj, "Temperature")) {
if (strchr(value, 'C'))
celsius = true;
else {
if (!strchr(value, 'F'))
config_errmsg(VALUE_ERR, prop, sensor);
celsius = false;
}
len = strlen(value);
strval = calloc(len, sizeof(*value));
if (!strval)
err(EXIT_FAILURE, "calloc");
(void)strlcpy(strval, value, len);
val = strtod(strval, &endptr);
if (*endptr != '\0') {
free(strval);
config_errmsg(VALUE_ERR, prop, sensor);
}
if (!celsius)
val = (val - 32.0) * (5.0 / 9.0);
val = val * 1000000 + 273150000;
num = prop_number_create_unsigned(val);
free(strval);
} else if (prop_string_equals_string(obj, "Fan") ||
prop_string_equals_string(obj, "Integer")) {
val = strtod(value, &endptr);
if (*endptr != '\0')
config_errmsg(VALUE_ERR, prop, sensor);
num = prop_number_create_unsigned(val);
} else {
obj = prop_dictionary_get(kdict, "max-value");
if (obj)
max = prop_number_signed_value(obj);
obj = prop_dictionary_get(kdict, "min-value");
if (obj)
min = prop_number_signed_value(obj);
val = strtod(value, &endptr);
if (*endptr != '\0')
config_errmsg(VALUE_ERR, prop, sensor);
val *= 1000000.0;
if (max && val > max)
config_errmsg(VALUE_ERR, prop, sensor);
if (min && val < min)
config_errmsg(VALUE_ERR, prop, sensor);
num = prop_number_create_signed(val);
}
return num;
}