root/usr.sbin/envstat/envstat.c
/* $NetBSD: envstat.c,v 1.105 2025/04/01 11:39:19 brad Exp $ */

/*-
 * 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.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: envstat.c,v 1.105 2025/04/01 11:39:19 brad Exp $");
#endif /* not lint */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <mj.h>
#include <paths.h>
#include <syslog.h>
#include <time.h>
#include <sys/envsys.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <prop/proplib.h>

#include "envstat.h"
#include "prog_ops.h"

#define ENVSYS_DFLAG    0x00000001      /* list registered devices */
#define ENVSYS_FFLAG    0x00000002      /* show temp in fahrenheit */
#define ENVSYS_LFLAG    0x00000004      /* list sensors */
#define ENVSYS_XFLAG    0x00000008      /* externalize dictionary */
#define ENVSYS_IFLAG    0x00000010      /* skip invalid sensors */
#define ENVSYS_SFLAG    0x00000020      /* remove all properties set */
#define ENVSYS_TFLAG    0x00000040      /* make statistics */
#define ENVSYS_NFLAG    0x00000080      /* print value only */
#define ENVSYS_KFLAG    0x00000100      /* show temp in kelvin */
#define ENVSYS_JFLAG    0x00000200      /* print the output in JSON */
#define ENVSYS_tFLAG    0x00000400      /* print a timestamp */

/* Sensors */
typedef struct envsys_sensor {
        SIMPLEQ_ENTRY(envsys_sensor) entries;
        int32_t cur_value;
        int32_t max_value;
        int32_t min_value;
        int32_t critmin_value;
        int32_t critmax_value;
        int32_t warnmin_value;
        int32_t warnmax_value;
        char    desc[ENVSYS_DESCLEN];
        char    type[ENVSYS_DESCLEN];
        char    index[ENVSYS_DESCLEN];
        char    drvstate[ENVSYS_DESCLEN];
        char    battcap[ENVSYS_DESCLEN];
        char    dvname[ENVSYS_DESCLEN];
        bool    invalid;
        bool    visible;
        bool    percentage;
} *sensor_t;

/* Sensor statistics */
typedef struct envsys_sensor_stats {
        SIMPLEQ_ENTRY(envsys_sensor_stats) entries;
        int32_t max;
        int32_t min;
        int32_t avg;
        char    desc[ENVSYS_DESCLEN];
} *sensor_stats_t;

/* 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 unsigned int     interval, flags, width;
static char             *mydevname, *sensors;
static bool             statistics;
static u_int            header_passes;
static time_t           timestamp;

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 */
                }
        }

        argc -= optind;
        argv += optind;

        if (argc > 0 && (flags & ENVSYS_XFLAG) == 0)
                usage();

        /* 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;

                                        obj = prop_dictionary_get(ndict, dvstring);
                                        obj2 = prop_array_get(obj, 0);
                                        prop_object_release(ndict);
                                        ndict = obj2;
                                }
                        }

                        prop_object_release(dict);
                        dict = ndict;
                }

                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));

        prop_object_release(udict);
        return error;
}

static sensor_stats_t
find_stats_sensor(const char *desc)
{
        sensor_stats_t stats;

        /*
         * 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;

        (void)strlcpy(stats->desc, desc, sizeof(stats->desc));
        stats->min = INT32_MAX;
        stats->max = INT32_MIN;
        SIMPLEQ_INSERT_TAIL(&sensor_stats_list, stats, entries);

        return stats;
}

static int
add_sensors(prop_dictionary_t ndict, prop_dictionary_t dict, const char *dev, const char *sensor)
{
        prop_object_iterator_t iter, iter2;
        prop_object_t obj, obj2, desc;
        prop_array_t array, narray;
        prop_dictionary_t sdict;
        const char *dnp;
        unsigned int capacity = 1;
        uint64_t dummy;
        bool found = false;

        if (prop_dictionary_count(dict) == 0)
                return 0;

        narray = prop_dictionary_get(ndict, dev);
        if (narray)
                found = true;
        else {
                narray = prop_array_create_with_capacity(capacity);
                if (!narray)
                        return -1;
                if (!prop_dictionary_set(ndict, dev, narray)) {
                        prop_object_release(narray);
                        return -1;
                }
        }

        iter = prop_dictionary_iterator(dict);
        if (iter == NULL)
                goto fail;
        while ((obj = prop_object_iterator_next(iter)) != NULL) {
                array = prop_dictionary_get_keysym(dict, obj);
                if (prop_object_type(array) != PROP_TYPE_ARRAY)
                        break;

                dnp = prop_dictionary_keysym_value(obj);
                if (strcmp(dev, dnp))
                        continue;
                found = true;

                iter2 = prop_array_iterator(array);
                while ((obj = prop_object_iterator_next(iter2)) != NULL) {
                        obj2 = prop_dictionary_get(obj, "device-properties");
                        if (obj2) {
                                if (!prop_dictionary_get_uint64(obj2,
                                    "refresh-timeout", &dummy))
                                        continue;
                        }

                        if (sensor) {
                                desc = prop_dictionary_get(obj, "description");
                                if (desc == NULL)
                                        continue;

                                if (!prop_string_equals_string(desc, sensor))
                                        continue;
                        }

                        if (!prop_array_ensure_capacity(narray, capacity))
                                goto fail;

                        sdict = prop_dictionary_copy(obj);
                        if (sdict == NULL)
                                goto fail;
                        prop_array_add(narray, sdict);
                        ++capacity;
                }
                prop_object_iterator_release(iter2);
        }
        prop_object_iterator_release(iter);

        /* drop key and array when device wasn't found */
        if (!found) {
                prop_dictionary_remove(ndict, dev);
                prop_object_release(narray);
        }

        return 0;

fail:
        prop_dictionary_remove(ndict, dev);
        prop_object_release(narray);
        return -1;
}

static int
parse_dictionary(int fd)
{
        sensor_t sensor = NULL;
        dvprops_t edp = NULL;
        prop_array_t array;
        prop_dictionary_t dict;
        prop_object_iterator_t iter;
        prop_object_t obj;
        const char *dnp = NULL;
        int rval = 0;

        /* 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;
                        }

                        edp = calloc(1, sizeof(*edp));
                        if (!edp) {
                                rval = ENOMEM;
                                goto out;
                        }

                        dnp = prop_dictionary_keysym_value(obj);
                        rval = find_sensors(array, dnp, edp);
                        if (rval)
                                goto out;

                        if (((flags & ENVSYS_LFLAG) == 0) &&
                            (flags & ENVSYS_DFLAG)) {
                                (void)printf("%s (checking events every ",
                                    dnp);
                                if (edp->refresh_timo == 1)
                                        (void)printf("second)\n");
                                else
                                        (void)printf("%d seconds)\n",
                                            (int)edp->refresh_timo);
                        }

                        free(edp);
                        edp = NULL;
                }
                prop_object_iterator_release(iter);
        }

        /* print sensors now */
        if (sensors)
                rval = check_sensors(sensors);
        if ((flags & ENVSYS_LFLAG) == 0 &&
            (flags & ENVSYS_DFLAG) == 0 &&
            (flags & ENVSYS_JFLAG) == 0)
                print_sensors();
        if ((flags & ENVSYS_LFLAG) == 0 &&
            (flags & ENVSYS_DFLAG) == 0 &&
            (flags & ENVSYS_JFLAG))
                print_sensors_json();
        if (interval && ((flags & ENVSYS_NFLAG) == 0 || sensors == NULL)) {
                (void)printf("\n");
                if (flags & ENVSYS_JFLAG)
                        (void)printf("#-------\n");
        }

out:
        while ((sensor = SIMPLEQ_FIRST(&sensors_list))) {
                SIMPLEQ_REMOVE_HEAD(&sensors_list, entries);
                free(sensor);
        }
        if (edp)
                free(edp);
        prop_object_release(dict);
        return rval;
}

static int
find_sensors(prop_array_t array, const char *dvname, dvprops_t edp)
{
        prop_object_iterator_t iter;
        prop_object_t obj, obj1, obj2;
        prop_string_t state, desc = NULL;
        sensor_t sensor = NULL;
        sensor_stats_t stats = NULL;

        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));

                /* description string */
                desc = prop_dictionary_get(obj, "description");
                if (desc) {
                        /* copy description */
                        (void)strlcpy(sensor->desc,
                            prop_string_value(desc),
                            sizeof(sensor->desc));
                } else {
                        free(sensor);
                        continue;
                }

                /* 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;

                        /* compute avg value */
                        stats->avg =
                            (sensor->cur_value + stats->max + stats->min) / 3;
                }
        }

        /* free memory */
        prop_object_iterator_release(iter);
        return 0;
}

static int
check_sensors(const char *str)
{
        sensor_t sensor = NULL;
        char *dvstring, *sstring, *p, *last, *s;
        bool sensor_found = false;

        if ((s = strdup(str)) == NULL)
                return errno;

        /*
         * 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()
 */

static void
print_sensors(void)
{
        sensor_t sensor;
        sensor_stats_t stats = NULL;
        size_t maxlen = 0, ilen;
        double temp = 0;
        const char *invalid = "N/A", *degrees, *tmpstr, *stype;
        const char *a, *b, *c, *d, *e, *units;
        const char *sep;
        int flen;
        bool nflag = (flags & ENVSYS_NFLAG) != 0;
        char tbuf[26];
        bool tflag = (flags & ENVSYS_tFLAG) != 0;

        tmpstr = stype = d = e = NULL;

        /* 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(&timestamp, 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++;

                sep = ":";
                flen = 10;
        } else {
                sep = "";
                flen = 1;
        }

        /* 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 (!nflag) {
                        /* print sensor description */
                        (void)printf("%s%*.*s", mydevname ? "" : "  ",
                            (int)maxlen,
                            (int)maxlen, sensor->desc);
                }

                /* print invalid string */
                if (sensor->invalid) {
                        (void)printf("%s%*s\n", sep, flen, invalid);
                        continue;
                }

                /*
                 * Indicator and Battery charge sensors.
                 */
                if ((strcmp(sensor->type, "Indicator") == 0) ||
                    (strcmp(sensor->type, "Battery charge") == 0)) {

                        (void)printf("%s%*s", sep, flen,
                             sensor->cur_value ? "TRUE" : "FALSE");

/* convert and print a temp value in degC, degF, or Kelvin */
#define PRINTTEMP(a)                                            \
do {                                                            \
        if (a) {                                                \
                temp = ((a) / 1000000.0);                       \
                if (flags & ENVSYS_FFLAG) {                     \
                        temp = temp * (9.0 / 5.0) - 459.67;     \
                        degrees = "degF";                       \
                } else if (flags & ENVSYS_KFLAG) {              \
                        degrees = "K";                          \
                } else {                                        \
                        temp = temp - 273.15;                   \
                        degrees = "degC";                       \
                }                                               \
                (void)printf("%*.3f", (int)ilen, temp); \
                ilen = 9;                                       \
        } else                                                  \
                ilen += 9;                                      \
} while (0)

                /* temperatures */
                } else if (strcmp(sensor->type, "Temperature") == 0) {

                        ilen = nflag ? 1 : 10;
                        degrees = "";
                        (void)printf("%s",sep);
                        PRINTTEMP(sensor->cur_value);
                        stype = degrees;

                        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

                /* fans */
                } else if (strcmp(sensor->type, "Fan") == 0) {
                        stype = "RPM";

                        (void)printf("%s%*u", sep, flen, sensor->cur_value);

                        ilen = 8;
                        if (statistics) {
                                /* show statistics if flag set */
                                (void)printf(" %8u %8u %8u",
                                    stats->max, stats->min, stats->avg);
                                ilen += 2;
                        } else if (!nflag) {
                                if (sensor->critmax_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->critmax_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->warnmax_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->warnmax_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->warnmin_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->warnmin_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->critmin_value) {
                                        (void)printf( " %*u", (int)ilen,
                                            sensor->critmin_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                        }

                        if (!nflag)
                                (void)printf(" %*s", (int)ilen - 3, stype);

                /* integers */
                } else if (strcmp(sensor->type, "Integer") == 0) {

                        stype = "none";

                        (void)printf("%s%*d", sep, flen, sensor->cur_value);

                        ilen = 8;

/* Print percentage of max_value */
#define PRINTPCT(a)                                                     \
do {                                                                    \
        if (sensor->max_value) {                                        \
                (void)printf(" %*.3f%%", (int)ilen,                     \
                        ((a) * 100.0) / sensor->max_value);             \
                ilen = 8;                                               \
        } else                                                          \
                ilen += 9;                                              \
} while ( /* CONSTCOND*/ 0 )

/* Print an integer sensor value */
#define PRINTINT(a)                                                     \
do {                                                                    \
        (void)printf(" %*u", (int)ilen, (a));                           \
        ilen = 8;                                                       \
} while ( /* CONSTCOND*/ 0 )

                        if (statistics) {
                                if (sensor->percentage) {
                                        PRINTPCT(stats->max);
                                        PRINTPCT(stats->min);
                                        PRINTPCT(stats->avg);
                                } else {
                                        PRINTINT(stats->max);
                                        PRINTINT(stats->min);
                                        PRINTINT(stats->avg);
                                }
                                ilen += 2;
                        } else if (!nflag) {
                                if (sensor->percentage) {
                                        PRINTPCT(sensor->critmax_value);
                                        PRINTPCT(sensor->warnmax_value);
                                        PRINTPCT(sensor->warnmin_value);
                                        PRINTPCT(sensor->critmin_value);
                                } else {
                                        PRINTINT(sensor->critmax_value);
                                        PRINTINT(sensor->warnmax_value);
                                        PRINTINT(sensor->warnmin_value);
                                        PRINTINT(sensor->critmin_value);
                                }
                        }

                        if (!nflag)
                                (void)printf("%*s", (int)ilen - 3, stype);

#undef PRINTINT
#undef PRINTPCT

                /* drives  */
                } else if (strcmp(sensor->type, "Drive") == 0) {

                        (void)printf("%s%*s", sep, flen, sensor->drvstate);

                /* Battery capacity */
                } else if (strcmp(sensor->type, "Battery capacity") == 0) {

                        (void)printf("%s%*s", sep, flen, sensor->battcap);

                /* Illuminance */
                } else if (strcmp(sensor->type, "Illuminance") == 0) {

                        stype = "lux";

                        (void)printf("%s%*u", sep, flen, sensor->cur_value);

                        ilen = 8;
                        if (statistics) {
                                /* show statistics if flag set */
                                (void)printf(" %8u %8u %8u",
                                    stats->max, stats->min, stats->avg);
                                ilen += 2;
                        } else if (!nflag) {
                                if (sensor->critmax_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->critmax_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->warnmax_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->warnmax_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->warnmin_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->warnmin_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->critmin_value) {
                                        (void)printf( " %*u", (int)ilen,
                                            sensor->critmin_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                        }

                        if (!nflag)
                                (void)printf(" %*s", (int)ilen - 3, stype);

                /* Pressure */
                } else if (strcmp(sensor->type, "pressure") == 0) {
                        stype = "hPa";

                        (void)printf("%s%*.3f", sep, flen,
                            sensor->cur_value / 10000.0);

                        ilen = 8;
                        if (statistics) {
                                /* show statistics if flag set */
                                (void)printf("  %.3f  %.3f  %.3f",
                                    stats->max / 10000.0, stats->min / 10000.0, stats->avg / 10000.0);
                                ilen += 2;
                        } else if (!nflag) {
                                if (sensor->critmax_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->critmax_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->warnmax_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->warnmax_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->warnmin_value) {
                                        (void)printf(" %*u", (int)ilen,
                                            sensor->warnmin_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                                if (sensor->critmin_value) {
                                        (void)printf( " %*u", (int)ilen,
                                            sensor->critmin_value);
                                        ilen = 8;
                                } else
                                        ilen += 9;

                        }

                        if (!nflag)
                                (void)printf(" %*s", (int)ilen - 3, stype);

                /* everything else */
                } else {
                        if (strcmp(sensor->type, "Voltage DC") == 0)
                                stype = "V";
                        else if (strcmp(sensor->type, "Voltage AC") == 0)
                                stype = "VAC";
                        else if (strcmp(sensor->type, "Ampere") == 0)
                                stype = "A";
                        else if (strcmp(sensor->type, "Watts") == 0)
                                stype = "W";
                        else if (strcmp(sensor->type, "Ohms") == 0)
                                stype = "Ohms";
                        else if (strcmp(sensor->type, "Watt hour") == 0)
                                stype = "Wh";
                        else if (strcmp(sensor->type, "Ampere hour") == 0)
                                stype = "Ah";
                        else if (strcmp(sensor->type, "relative Humidity") == 0)
                                stype = "%rH";
                        else
                                stype = "?";

                        (void)printf("%s%*.3f", sep, flen,
                            sensor->cur_value / 1000000.0);

                        ilen = 9;

/* Print percentage of max_value */
#define PRINTPCT(a)                                                     \
do {                                                                    \
        if ((a) && sensor->max_value) {                                 \
                (void)printf("%*.3f%%", (int)ilen,                      \
                        ((a) * 100.0) / sensor->max_value);             \
                ilen = 8;                                               \
        } else                                                          \
                ilen += 9;                                              \
} while ( /* CONSTCOND*/ 0 )

/* Print a generic sensor value */
#define PRINTVAL(a)                                                     \
do {                                                                    \
        if ((a)) {                                                      \
                (void)printf("%*.3f", (int)ilen, (a) / 1000000.0);      \
                ilen = 9;                                               \
        } else                                                          \
                ilen += 9;                                              \
} while ( /* CONSTCOND*/ 0 )

                        if (statistics) {
                                if (sensor->percentage) {
                                        PRINTPCT(stats->max);
                                        PRINTPCT(stats->min);
                                        PRINTPCT(stats->avg);
                                } else {
                                        PRINTVAL(stats->max);
                                        PRINTVAL(stats->min);
                                        PRINTVAL(stats->avg);
                                }
                                ilen += 2;
                        } else if (!nflag) {
                                if (sensor->percentage) {
                                        PRINTPCT(sensor->critmax_value);
                                        PRINTPCT(sensor->warnmax_value);
                                        PRINTPCT(sensor->warnmin_value);
                                        PRINTPCT(sensor->critmin_value);
                                } else {

                                        PRINTVAL(sensor->critmax_value);
                                        PRINTVAL(sensor->warnmax_value);
                                        PRINTVAL(sensor->warnmin_value);
                                        PRINTVAL(sensor->critmin_value);
                                }
                        }
#undef PRINTPCT
#undef PRINTVAL

                        if (!nflag) {
                                (void)printf(" %*s", (int)ilen - 4, stype);
                                if (sensor->percentage && sensor->max_value) {
                                        (void)printf(" (%5.2f%%)",
                                            (sensor->cur_value * 100.0) /
                                            sensor->max_value);
                                }
                        }
                }
                (void)printf("\n");
        }
}

static void
print_sensors_json(void)
{
        sensor_t sensor;
        sensor_stats_t stats = NULL;
        double temp = 0;

        const char *degrees, *tmpstr, *stype;
        const char *d, *e;
        mj_t envstatj;
        mj_t devices;
        mj_t sensors_per_dev;
        mj_t this_sensor;
        mj_t a_float;
        bool tflag = (flags & ENVSYS_tFLAG) != 0;
        char tbuf[26];
        char *output_s = NULL;
        size_t output_size = 0;

        tmpstr = stype = d = e = NULL;

        memset(&envstatj, 0x0, sizeof(envstatj));
        mj_create(&envstatj, "object");
        memset(&devices, 0x0, sizeof(devices));
        mj_create(&devices, "object");

        /* 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);
                        }

                        memset(&sensors_per_dev, 0x0, sizeof(sensors_per_dev));
                        mj_create(&sensors_per_dev, "array");

                        tmpstr = sensor->dvname;
                }

                /* Any condition that that causes the sensor to be
                 * completely skipped should be checked before we are
                 * at this point */

                memset(&this_sensor, 0x0, sizeof(this_sensor));
                mj_create(&this_sensor, "object");

                mj_append_field(&this_sensor, "index", "string",
                    sensor->index, strlen(sensor->index));
                mj_append_field(&this_sensor, "description", "string",
                    sensor->desc, strlen(sensor->desc));
                mj_append_field(&this_sensor, "type", "string",
                    sensor->type, strlen(sensor->type));
                mj_append_field(&this_sensor, "invalid", "integer",
                    (int64_t)sensor->invalid);

                /* The sensor is invalid, semi-skip it */
                if (sensor->invalid) {
                        goto around;
                }

                /*
                 * Indicator and Battery charge sensors.
                 */
                if ((strcmp(sensor->type, "Indicator") == 0) ||
                    (strcmp(sensor->type, "Battery charge") == 0)) {

                        mj_append_field(&this_sensor, "cur-value", "integer",
                            (int64_t)sensor->cur_value);

/* convert and print a temp value in degC, degF, or Kelvin */
#define PRINTTEMP(a,b)                                          \
do {                                                            \
    if (a) {                                                    \
        temp = ((a) / 1000000.0);                               \
        if (flags & ENVSYS_FFLAG) {                             \
                temp = temp * (9.0 / 5.0) - 459.67;             \
                degrees = "degF";                               \
        } else if (flags & ENVSYS_KFLAG) {                      \
                degrees = "K";                                  \
        } else {                                                \
                temp = temp - 273.15;                           \
                degrees = "degC";                               \
        }                                                       \
        memset(&a_float, 0x0, sizeof(a_float));                 \
        mj_create(&a_float, "number", temp);                    \
        mj_append_field(&this_sensor, b, "object", &a_float);   \
        mj_delete(&a_float);                                    \
    }                                                           \
} while (0)

                /* temperatures */
                } else if (strcmp(sensor->type, "Temperature") == 0) {

                        degrees = "";
                        PRINTTEMP(sensor->cur_value, "cur-value");
                        stype = degrees;

                        if (statistics) {
                                /* show statistics if flag set */
                                PRINTTEMP(stats->max, "max");
                                PRINTTEMP(stats->min, "min");
                                PRINTTEMP(stats->avg, "avg");
                        }
                        
                        PRINTTEMP(sensor->critmax_value, "critical-max");
                        PRINTTEMP(sensor->warnmax_value, "warning-max");
                        PRINTTEMP(sensor->warnmin_value, "warning-min");
                        PRINTTEMP(sensor->critmin_value, "critical-min");

                        mj_append_field(&this_sensor, "units", "string",
                            stype, strlen(stype));
#undef PRINTTEMP

                /* fans */
                } else if (strcmp(sensor->type, "Fan") == 0) {
                        stype = "RPM";

                        mj_append_field(&this_sensor, "cur-value", "integer",
                            (int64_t)sensor->cur_value);

                        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);
                        }
                        mj_append_field(&this_sensor, "critical-max", "integer",
                            (int64_t)sensor->critmax_value);
                        mj_append_field(&this_sensor, "warning-max", "integer",
                            (int64_t)sensor->warnmax_value);
                        mj_append_field(&this_sensor, "warning-min", "integer",
                            (int64_t)sensor->warnmin_value);
                        mj_append_field(&this_sensor, "critical-min", "integer",
                            (int64_t)sensor->critmin_value);

                        mj_append_field(&this_sensor, "units", "string",
                            stype, strlen(stype));

                /* integers */
                } else if (strcmp(sensor->type, "Integer") == 0) {

                        stype = "none";

                        mj_append_field(&this_sensor, "cur-value", "integer",
                            (int64_t)sensor->cur_value);

/* Print percentage of max_value */
#define PRINTPCT(a,b)                                                   \
do {                                                                    \
        if ((a) && sensor->max_value) {                                 \
                mj_append_field(&this_sensor, b, "integer",             \
                    (int64_t)((a) * 100.0) / sensor->max_value);        \
        }                                                               \
} while ( /* CONSTCOND*/ 0 )
                        if (statistics) {
                                if (sensor->percentage) {
                                        PRINTPCT(stats->max, "max");
                                        PRINTPCT(stats->min, "min");
                                        PRINTPCT(stats->avg, "avg");
                                } else {
                                        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 (sensor->percentage) {
                                PRINTPCT(sensor->critmax_value, "critical-max");
                                PRINTPCT(sensor->warnmax_value, "warning-max");
                                PRINTPCT(sensor->warnmin_value, "warning-min");
                                PRINTPCT(sensor->critmin_value, "critical-min");
                        } else {
                                mj_append_field(&this_sensor, "critmax_value", "integer",
                                    (int64_t)sensor->critmax_value);
                                mj_append_field(&this_sensor, "warning-max", "integer",
                                    (int64_t)sensor->warnmax_value);
                                mj_append_field(&this_sensor, "warning-min", "integer",
                                    (int64_t)sensor->warnmin_value);
                                mj_append_field(&this_sensor, "critical-min", "integer",
                                    (int64_t)sensor->critmin_value);
                        }

                        mj_append_field(&this_sensor, "units", "string",
                            stype, strlen(stype));
                /* drives  */
                } else if (strcmp(sensor->type, "Drive") == 0) {
                        mj_append_field(&this_sensor, "drvstate", "string",
                            sensor->drvstate, strlen(sensor->drvstate));
                /* Battery capacity */
                } else if (strcmp(sensor->type, "Battery capacity") == 0) {
                        mj_append_field(&this_sensor, "battcap", "string",
                            sensor->battcap, strlen(sensor->battcap));
                /* Illuminance */
                } else if (strcmp(sensor->type, "Illuminance") == 0) {

                        stype = "lux";

                        mj_append_field(&this_sensor, "cur-value", "integer",
                            (int64_t)sensor->cur_value);

                        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);
                        }

                        mj_append_field(&this_sensor, "critmax_value", "integer",
                            (int64_t)sensor->critmax_value);
                        mj_append_field(&this_sensor, "warning-max", "integer",
                            (int64_t)sensor->warnmax_value);
                        mj_append_field(&this_sensor, "warning-min", "integer",
                            (int64_t)sensor->warnmin_value);
                        mj_append_field(&this_sensor, "critical-min", "integer",
                            (int64_t)sensor->critmin_value);

                        mj_append_field(&this_sensor, "units", "string",
                            stype, strlen(stype));
                /* Pressure */
                } else if (strcmp(sensor->type, "pressure") == 0) {
#define PRINTFLOAT(a,b)                                         \
do {                                                            \
    if (a) {                                                    \
        memset(&a_float, 0x0, sizeof(a_float));                 \
        mj_create(&a_float, "number", a);                       \
        mj_append_field(&this_sensor, b, "object", &a_float);   \
        mj_delete(&a_float);                                    \
    }                                                           \
} while (0)
                        stype = "hPa";

                        PRINTFLOAT(sensor->cur_value / 10000.0, "cur-value");

                        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");
                        }

                        PRINTFLOAT(sensor->critmax_value, "critmax_value");
                        PRINTFLOAT(sensor->warnmax_value, "warning-max");
                        PRINTFLOAT(sensor->warnmin_value, "warning-min");
                        PRINTFLOAT(sensor->critmin_value, "critical-min");

                        mj_append_field(&this_sensor, "units", "string",
                            stype, strlen(stype));
                /* everything else */
                } else {
                        if (strcmp(sensor->type, "Voltage DC") == 0)
                                stype = "V";
                        else if (strcmp(sensor->type, "Voltage AC") == 0)
                                stype = "VAC";
                        else if (strcmp(sensor->type, "Ampere") == 0)
                                stype = "A";
                        else if (strcmp(sensor->type, "Watts") == 0)
                                stype = "W";
                        else if (strcmp(sensor->type, "Ohms") == 0)
                                stype = "Ohms";
                        else if (strcmp(sensor->type, "Watt hour") == 0)
                                stype = "Wh";
                        else if (strcmp(sensor->type, "Ampere hour") == 0)
                                stype = "Ah";
                        else if (strcmp(sensor->type, "relative Humidity") == 0)
                                stype = "%rH";
                        else
                                stype = "?";

                        PRINTFLOAT(sensor->cur_value / 1000000.0, "cur-value");

                        mj_append_field(&this_sensor, "units", "string",
                            stype, strlen(stype));
#undef PRINTFLOAT

/* Print a generic sensor value */
#define PRINTVAL(a,b)                                                   \
do {                                                                    \
        if ((a)) {                                                      \
                memset(&a_float, 0x0, sizeof(a_float));                 \
                mj_create(&a_float, "number", a / 1000000.0);           \
                mj_append_field(&this_sensor, b, "object", &a_float);   \
                mj_delete(&a_float);                                    \
        }                                                               \
} while ( /* CONSTCOND*/ 0 )

                        if (statistics) {
                                if (sensor->percentage) {
                                        PRINTPCT(stats->max, "max");
                                        PRINTPCT(stats->min, "min");
                                        PRINTPCT(stats->avg, "avg");
                                } else {
                                        PRINTVAL(stats->max, "max");
                                        PRINTVAL(stats->min, "min");
                                        PRINTVAL(stats->avg, "avg");
                                }
                        }
                        if (sensor->percentage) {
                                PRINTPCT(sensor->critmax_value, "critmax_value");
                                PRINTPCT(sensor->warnmax_value, "warning-max");
                                PRINTPCT(sensor->warnmin_value, "warning-min");
                                PRINTPCT(sensor->critmin_value, "critical-min");
                        } else {

                                PRINTVAL(sensor->critmax_value, "critmax_value");
                                PRINTVAL(sensor->warnmax_value, "warning-max");
                                PRINTVAL(sensor->warnmin_value, "warning-min");
                                PRINTVAL(sensor->critmin_value, "critical-min");
                        }
#undef PRINTPCT
#undef PRINTVAL

                        mj_append_field(&this_sensor, "units", "string",
                            stype, strlen(stype));
                        if (sensor->percentage && sensor->max_value) {
                                memset(&a_float, 0x0, sizeof(a_float));
                                mj_create(&a_float, "number",
                                    (sensor->cur_value * 100.0) / sensor->max_value);
                                mj_append_field(&this_sensor, "pct", "object",
                                    &a_float);
                                mj_delete(&a_float);
                        }
                }
around:
                mj_append(&sensors_per_dev, "object", &this_sensor);
                mj_delete(&this_sensor);
        }

        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(&timestamp, 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.
                 */

                output_size = mj_string_size(&envstatj) * 3;
                output_s = calloc(1, output_size);
                if (output_s != NULL) {
                        mj_snprint(output_s, output_size, &envstatj, MJ_JSON_ENCODE);
                        printf("%s", output_s);
                        free(output_s);
                }
        }

        mj_delete(&devices);
        mj_delete(&envstatj);
}

static int
usage(void)
{
        (void)fprintf(stderr, "Usage: %s [-DfIjklnrSTt] ", getprogname());
        (void)fprintf(stderr, "[-c file] [-d device] [-i interval] ");
        (void)fprintf(stderr, "[-s device:sensor,...] [-w width]\n");
        (void)fprintf(stderr, "       %s ", getprogname());
        (void)fprintf(stderr, "[-d device] ");
        (void)fprintf(stderr, "[-s device:sensor,...] ");
        (void)fprintf(stderr, "-x [property]\n");
        exit(EXIT_FAILURE);
        /* NOTREACHED */
}