/*-
* Copyright (c) 2007, 2008 Juan Romero Pardines.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Device properties */
typedef struct envsys_dvprops {
uint64_t refresh_timo;
/* more members could be added in the future */
} *dvprops_t;
/* A simple queue to manage all sensors */
static SIMPLEQ_HEAD(, envsys_sensor) sensors_list =
SIMPLEQ_HEAD_INITIALIZER(sensors_list);
/* A simple queue to manage statistics for all sensors */
static SIMPLEQ_HEAD(, envsys_sensor_stats) sensor_stats_list =
SIMPLEQ_HEAD_INITIALIZER(sensor_stats_list);
static int parse_dictionary(int);
static int add_sensors(prop_dictionary_t, prop_dictionary_t, const char *, const char *);
static int send_dictionary(FILE *);
static int find_sensors(prop_array_t, const char *, dvprops_t);
static void print_sensors(void);
static void print_sensors_json(void);
static int check_sensors(const char *);
static int usage(void);
static int sysmonfd; /* fd of /dev/sysmon */
int main(int argc, char **argv)
{
prop_dictionary_t dict;
int c, rval = 0;
char *endptr, *configfile = NULL;
FILE *cf;
if (prog_init && prog_init() == -1)
err(1, "init failed");
setprogname(argv[0]);
while ((c = getopt(argc, argv, "c:Dd:fIi:jklnrSs:Ttw:Wx")) != -1) {
switch (c) {
case 'c': /* configuration file */
configfile = optarg;
break;
case 'D': /* list registered devices */
flags |= ENVSYS_DFLAG;
break;
case 'd': /* show sensors of a specific device */
mydevname = optarg;
break;
case 'f': /* display temperature in Fahrenheit */
flags |= ENVSYS_FFLAG;
break;
case 'I': /* Skips invalid sensors */
flags |= ENVSYS_IFLAG;
break;
case 'i': /* wait time between intervals */
interval = (unsigned int)strtoul(optarg, &endptr, 10);
if (*endptr != '\0')
errx(EXIT_FAILURE, "bad interval '%s'", optarg);
break;
case 'j':
flags |= ENVSYS_JFLAG;
break;
case 'k': /* display temperature in Kelvin */
flags |= ENVSYS_KFLAG;
break;
case 'l': /* list sensors */
flags |= ENVSYS_LFLAG;
break;
case 'n': /* print value only */
flags |= ENVSYS_NFLAG;
break;
case 'r':
/*
* This flag is noop.. it's only here for
* compatibility with the old implementation.
*/
break;
case 'S':
flags |= ENVSYS_SFLAG;
break;
case 's': /* only show specified sensors */
sensors = optarg;
break;
case 'T': /* make statistics */
flags |= ENVSYS_TFLAG;
break;
case 't': /* produce a timestamp */
flags |= ENVSYS_tFLAG;
break;
case 'w': /* width value for the lines */
width = (unsigned int)strtoul(optarg, &endptr, 10);
if (*endptr != '\0')
errx(EXIT_FAILURE, "bad width '%s'", optarg);
break;
case 'x': /* print the dictionary in raw format */
flags |= ENVSYS_XFLAG;
break;
case 'W': /* No longer used, retained for compatibility */
break;
case '?':
default:
usage();
/* NOTREACHED */
}
}
/* Check if we want to make statistics */
if (flags & ENVSYS_TFLAG) {
if (!interval)
errx(EXIT_FAILURE,
"-T cannot be used without an interval (-i)");
else
statistics = true;
}
if (mydevname && sensors)
errx(EXIT_FAILURE, "-d flag cannot be used with -s");
/* Open the device in ro mode */
if ((sysmonfd = prog_open(_PATH_SYSMON, O_RDONLY)) == -1)
err(EXIT_FAILURE, "%s", _PATH_SYSMON);
/* Print dictionary in raw mode */
if (flags & ENVSYS_XFLAG) {
rval = prop_dictionary_recv_ioctl(sysmonfd,
ENVSYS_GETDICTIONARY,
&dict);
if (rval)
errx(EXIT_FAILURE, "%s", strerror(rval));
if (mydevname || sensors) {
prop_dictionary_t ndict;
ndict = prop_dictionary_create();
if (ndict == NULL)
err(EXIT_FAILURE, "prop_dictionary_create");
if (mydevname) {
if (add_sensors(ndict, dict, mydevname, NULL))
err(EXIT_FAILURE, "add_sensors");
}
if (sensors) {
char *sstring, *p, *last, *s;
char *dvstring = NULL; /* XXXGCC */
unsigned count = 0;
s = strdup(sensors);
if (s == NULL)
err(EXIT_FAILURE, "strdup");
for ((p = strtok_r(s, ",", &last)); p;
(p = strtok_r(NULL, ",", &last))) {
/* get device name */
dvstring = strtok(p, ":");
if (dvstring == NULL)
errx(EXIT_FAILURE, "missing device name");
/* get sensor description */
sstring = strtok(NULL, ":");
if (sstring == NULL)
errx(EXIT_FAILURE, "missing sensor description");
if (add_sensors(ndict, dict, dvstring, sstring))
err(EXIT_FAILURE, "add_sensors");
++count;
}
free(s);
/* in case we asked for a single sensor
* show only the sensor dictionary
*/
if (count == 1) {
prop_object_t obj, obj2;
if (argc > 0) {
for (; argc > 0; ++argv, --argc)
config_dict_extract(dict, *argv, true);
} else
config_dict_dump(dict);
/* Remove all properties set in dictionary */
} else if (flags & ENVSYS_SFLAG) {
/* Close the ro descriptor */
(void)prog_close(sysmonfd);
/* open the fd in rw mode */
if ((sysmonfd = prog_open(_PATH_SYSMON, O_RDWR)) == -1)
err(EXIT_FAILURE, "%s", _PATH_SYSMON);
dict = prop_dictionary_create();
if (!dict)
err(EXIT_FAILURE, "prop_dictionary_create");
rval = prop_dictionary_set_bool(dict,
"envsys-remove-props",
true);
if (!rval)
err(EXIT_FAILURE, "prop_dict_set_bool");
/* send the dictionary to the kernel now */
rval = prop_dictionary_send_ioctl(dict, sysmonfd,
ENVSYS_REMOVEPROPS);
if (rval)
warnx("%s", strerror(rval));
/* Set properties in dictionary */
} else if (configfile) {
/*
* Parse the configuration file.
*/
if ((cf = fopen(configfile, "r")) == NULL) {
syslog(LOG_ERR, "fopen failed: %s", strerror(errno));
errx(EXIT_FAILURE, "%s", strerror(errno));
}
rval = send_dictionary(cf);
(void)fclose(cf);
/* Show sensors with interval */
} else if (interval) {
for (;;) {
timestamp = time(NULL);
rval = parse_dictionary(sysmonfd);
if (rval)
break;
(void)fflush(stdout);
(void)sleep(interval);
}
/* Show sensors without interval */
} else {
timestamp = time(NULL);
rval = parse_dictionary(sysmonfd);
}
(void)prog_close(sysmonfd);
return rval ? EXIT_FAILURE : EXIT_SUCCESS;
}
static int
send_dictionary(FILE *cf)
{
prop_dictionary_t kdict, udict;
int error = 0;
/* Retrieve dictionary from kernel */
error = prop_dictionary_recv_ioctl(sysmonfd,
ENVSYS_GETDICTIONARY, &kdict);
if (error)
return error;
config_parse(cf, kdict);
/*
* Dictionary built by the parser from the configuration file.
*/
udict = config_dict_parsed();
/*
* Close the read only descriptor and open a new one read write.
*/
(void)prog_close(sysmonfd);
if ((sysmonfd = prog_open(_PATH_SYSMON, O_RDWR)) == -1) {
error = errno;
warn("%s", _PATH_SYSMON);
return error;
}
/*
* Send our sensor properties dictionary to the kernel then.
*/
error = prop_dictionary_send_ioctl(udict,
sysmonfd, ENVSYS_SETDICTIONARY);
if (error)
warnx("%s", strerror(error));
/*
* If we matched a sensor by its description return it, otherwise
* allocate a new one.
*/
SIMPLEQ_FOREACH(stats, &sensor_stats_list, entries)
if (strcmp(stats->desc, desc) == 0)
return stats;
stats = calloc(1, sizeof(*stats));
if (stats == NULL)
return NULL;
/* receive dictionary from kernel */
rval = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict);
if (rval)
return rval;
/* No drivers registered? */
if (prop_dictionary_count(dict) == 0) {
warnx("no drivers registered");
goto out;
}
if (mydevname) {
/* -d flag specified, print sensors only for this device */
obj = prop_dictionary_get(dict, mydevname);
if (prop_object_type(obj) != PROP_TYPE_ARRAY) {
warnx("unknown device `%s'", mydevname);
rval = EINVAL;
goto out;
}
rval = find_sensors(obj, mydevname, NULL);
if (rval)
goto out;
} else {
/* print sensors for all devices registered */
iter = prop_dictionary_iterator(dict);
if (iter == NULL) {
rval = EINVAL;
goto out;
}
/* iterate over the dictionary returned by the kernel */
while ((obj = prop_object_iterator_next(iter)) != NULL) {
array = prop_dictionary_get_keysym(dict, obj);
if (prop_object_type(array) != PROP_TYPE_ARRAY) {
warnx("no sensors found");
rval = EINVAL;
goto out;
}
iter = prop_array_iterator(array);
if (!iter)
return ENOMEM;
/* iterate over the array of dictionaries */
while ((obj = prop_object_iterator_next(iter)) != NULL) {
/* get the refresh-timeout property */
obj2 = prop_dictionary_get(obj, "device-properties");
if (obj2) {
if (!edp)
continue;
if (!prop_dictionary_get_uint64(obj2,
"refresh-timeout",
&edp->refresh_timo))
continue;
}
/* new sensor coming */
sensor = calloc(1, sizeof(*sensor));
if (sensor == NULL) {
prop_object_iterator_release(iter);
return ENOMEM;
}
/* copy device name */
(void)strlcpy(sensor->dvname, dvname, sizeof(sensor->dvname));
/* type string */
obj1 = prop_dictionary_get(obj, "type");
if (obj1) {
/* copy type */
(void)strlcpy(sensor->type,
prop_string_value(obj1),
sizeof(sensor->type));
} else {
free(sensor);
continue;
}
/* index string */
obj1 = prop_dictionary_get(obj, "index");
if (obj1) {
/* copy type */
(void)strlcpy(sensor->index,
prop_string_value(obj1),
sizeof(sensor->index));
} else {
free(sensor);
continue;
}
/* check sensor's state */
state = prop_dictionary_get(obj, "state");
/* mark sensors with invalid/unknown state */
if ((prop_string_equals_string(state, "invalid") ||
prop_string_equals_string(state, "unknown")))
sensor->invalid = true;
/* get current drive state string */
obj1 = prop_dictionary_get(obj, "drive-state");
if (obj1) {
(void)strlcpy(sensor->drvstate,
prop_string_value(obj1),
sizeof(sensor->drvstate));
}
/* get current battery capacity string */
obj1 = prop_dictionary_get(obj, "battery-capacity");
if (obj1) {
(void)strlcpy(sensor->battcap,
prop_string_value(obj1),
sizeof(sensor->battcap));
}
/* get current value */
obj1 = prop_dictionary_get(obj, "cur-value");
if (obj1)
sensor->cur_value = prop_number_signed_value(obj1);
/* get max value */
obj1 = prop_dictionary_get(obj, "max-value");
if (obj1)
sensor->max_value = prop_number_signed_value(obj1);
/* get min value */
obj1 = prop_dictionary_get(obj, "min-value");
if (obj1)
sensor->min_value = prop_number_signed_value(obj1);
/* get percentage flag */
obj1 = prop_dictionary_get(obj, "want-percentage");
if (obj1)
sensor->percentage = prop_bool_true(obj1);
/* get critical max value if available */
obj1 = prop_dictionary_get(obj, "critical-max");
if (obj1)
sensor->critmax_value = prop_number_signed_value(obj1);
/* get maximum capacity value if available */
obj1 = prop_dictionary_get(obj, "maximum-capacity");
if (obj1)
sensor->critmax_value = prop_number_signed_value(obj1);
/* get critical min value if available */
obj1 = prop_dictionary_get(obj, "critical-min");
if (obj1)
sensor->critmin_value = prop_number_signed_value(obj1);
/* get critical capacity value if available */
obj1 = prop_dictionary_get(obj, "critical-capacity");
if (obj1)
sensor->critmin_value = prop_number_signed_value(obj1);
/* get warning max value if available */
obj1 = prop_dictionary_get(obj, "warning-max");
if (obj1)
sensor->warnmax_value = prop_number_signed_value(obj1);
/* get high capacity value if available */
obj1 = prop_dictionary_get(obj, "high-capacity");
if (obj1)
sensor->warnmax_value = prop_number_signed_value(obj1);
/* get warning min value if available */
obj1 = prop_dictionary_get(obj, "warning-min");
if (obj1)
sensor->warnmin_value = prop_number_signed_value(obj1);
/* get warning capacity value if available */
obj1 = prop_dictionary_get(obj, "warning-capacity");
if (obj1)
sensor->warnmin_value = prop_number_signed_value(obj1);
/* print sensor names if -l was given */
if (flags & ENVSYS_LFLAG) {
if (width)
(void)printf("%*s\n", width,
prop_string_value(desc));
else
(void)printf("%s\n",
prop_string_value(desc));
}
/* Add the sensor into the list */
SIMPLEQ_INSERT_TAIL(&sensors_list, sensor, entries);
/* Collect statistics if flag enabled */
if (statistics) {
/* ignore sensors not relevant for statistics */
if ((strcmp(sensor->type, "Indicator") == 0) ||
(strcmp(sensor->type, "Battery charge") == 0) ||
(strcmp(sensor->type, "Drive") == 0))
continue;
/* ignore invalid data */
if (sensor->invalid)
continue;
/* find or allocate a new statistics sensor */
stats = find_stats_sensor(sensor->desc);
if (stats == NULL) {
free(sensor);
prop_object_iterator_release(iter);
return ENOMEM;
}
/* update data */
if (sensor->cur_value > stats->max)
stats->max = sensor->cur_value;
if (sensor->cur_value < stats->min)
stats->min = sensor->cur_value;
/*
* Parse device name and sensor description and find out
* if the sensor is valid.
*/
for ((p = strtok_r(s, ",", &last)); p;
(p = strtok_r(NULL, ",", &last))) {
/* get device name */
dvstring = strtok(p, ":");
if (dvstring == NULL) {
warnx("missing device name");
goto out;
}
/* get sensor description */
sstring = strtok(NULL, ":");
if (sstring == NULL) {
warnx("missing sensor description");
goto out;
}
SIMPLEQ_FOREACH(sensor, &sensors_list, entries) {
/* skip until we match device */
if (strcmp(dvstring, sensor->dvname))
continue;
if (strcmp(sstring, sensor->desc) == 0) {
sensor->visible = true;
sensor_found = true;
break;
}
}
if (sensor_found == false) {
warnx("unknown sensor `%s' for device `%s'",
sstring, dvstring);
goto out;
}
sensor_found = false;
}
/* check if all sensors were ok, and error out if not */
SIMPLEQ_FOREACH(sensor, &sensors_list, entries)
if (sensor->visible) {
free(s);
return 0;
}
warnx("no sensors selected to display");
out:
free(s);
return EINVAL;
}
/* When adding a new sensor type, be sure to address both
* print_sensors() and print_sensors_json()
*/
/* find the longest description */
SIMPLEQ_FOREACH(sensor, &sensors_list, entries)
if (strlen(sensor->desc) > maxlen)
maxlen = strlen(sensor->desc);
if (width)
maxlen = width;
/*
* Print a header at the bottom only once showing different
* members if the statistics flag is set or not.
*
* As bonus if -s is set, only print this header every 10 iterations
* to avoid redundancy... like vmstat(1).
*/
a = "Current";
units = "Unit";
if (statistics) {
b = "Max";
c = "Min";
d = "Avg";
} else {
b = "CritMax";
c = "WarnMax";
d = "WarnMin";
e = "CritMin";
}
if (tflag) {
if (ctime_r(×tamp, tbuf) != NULL)
(void)printf("%s\n",tbuf);
}
if (!nflag) {
if (!sensors || (!header_passes && sensors) ||
(header_passes == 10 && sensors)) {
if (statistics)
(void)printf("%s%*s %9s %8s %8s %8s %6s\n",
mydevname ? "" : " ", (int)maxlen,
"", a, b, c, d, units);
else
(void)printf("%s%*s %9s %8s %8s %8s %8s %5s\n",
mydevname ? "" : " ", (int)maxlen,
"", a, b, c, d, e, units);
if (sensors && header_passes == 10)
header_passes = 0;
}
if (sensors)
header_passes++;
/* print the sensors */
SIMPLEQ_FOREACH(sensor, &sensors_list, entries) {
/* skip sensors that were not marked as visible */
if (sensors && !sensor->visible)
continue;
/* skip invalid sensors if -I is set */
if ((flags & ENVSYS_IFLAG) && sensor->invalid)
continue;
/* print device name */
if (!nflag && !mydevname) {
if (tmpstr == NULL || strcmp(tmpstr, sensor->dvname))
printf("[%s]\n", sensor->dvname);
tmpstr = sensor->dvname;
}
/* find out the statistics sensor */
if (statistics) {
stats = find_stats_sensor(sensor->desc);
if (stats == NULL) {
/* No statistics for this sensor */
continue;
}
}
if (statistics) {
/* show statistics if flag set */
PRINTTEMP(stats->max);
PRINTTEMP(stats->min);
PRINTTEMP(stats->avg);
ilen += 2;
} else if (!nflag) {
PRINTTEMP(sensor->critmax_value);
PRINTTEMP(sensor->warnmax_value);
PRINTTEMP(sensor->warnmin_value);
PRINTTEMP(sensor->critmin_value);
}
if (!nflag)
(void)printf("%*s", (int)ilen - 3, stype);
#undef PRINTTEMP
/* print the sensors */
SIMPLEQ_FOREACH(sensor, &sensors_list, entries) {
/* completely skip sensors that were not marked as visible */
if (sensors && !sensor->visible)
continue;
/* completely skip invalid sensors if -I is set */
if ((flags & ENVSYS_IFLAG) && sensor->invalid)
continue;
/* find out the statistics sensor */
if (statistics) {
stats = find_stats_sensor(sensor->desc);
if (stats == NULL) {
/* No statistics for this sensor, completely skip */
continue;
}
}
if (tmpstr == NULL || strcmp(tmpstr, sensor->dvname)) {
if (tmpstr != NULL) {
mj_append_field(&devices, tmpstr, "array", &sensors_per_dev);
mj_delete(&sensors_per_dev);
}
if (statistics) {
/* show statistics if flag set */
mj_append_field(&this_sensor, "max", "integer",
(int64_t)stats->max);
mj_append_field(&this_sensor, "min", "integer",
(int64_t)stats->min);
mj_append_field(&this_sensor, "avg", "integer",
(int64_t)stats->avg);
}
if (statistics) {
/* show statistics if flag set */
PRINTFLOAT(stats->max / 10000.0, "max");
PRINTFLOAT(stats->min / 10000.0, "min");
PRINTFLOAT(stats->avg / 10000.0, "avg");
}
if (tmpstr != NULL) {
mj_append_field(&devices, tmpstr, "array", &sensors_per_dev);
mj_delete(&sensors_per_dev);
mj_append_field(&envstatj, "devices", "object", &devices);
if (tflag) {
mj_append_field(&envstatj, "timestamp", "integer", (int64_t)timestamp);
if (ctime_r(×tamp, tbuf) != NULL)
/* Pull off the newline */
mj_append_field(&envstatj, "human_timestamp", "string", tbuf, strlen(tbuf) - 1);
}
#ifdef __NOT
mj_asprint(&output_s, &envstatj, MJ_JSON_ENCODE);
printf("%s", output_s);
if (output_s != NULL)
free(output_s);
#endif
/* Work around lib/59230 for now. Way over allocate
* the output buffer. Summary of the problem:
* mj_string_size does not appear to allocate enough
* space if there are arrays present in the serialized
* superatom. It appears that for every array that is
* placed inside of another object (or probably array)
* the resulting buffer is a single character too
* small. So just do what mj_asprint() does, except
* make the size much larger than is needed.
*/