root/sbin/geom/core/geom.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2004-2009 Pawel Jakub Dawidek <pjd@FreeBSD.org>
 * 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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/param.h>
#include <sys/linker.h>
#include <sys/module.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <libutil.h>
#include <inttypes.h>
#include <dlfcn.h>
#include <assert.h>
#include <libgeom.h>
#include <geom.h>
#include <libxo/xo.h>

#include "misc/subr.h"

#define GEOM_XO_VERSION "1"

#ifdef STATIC_GEOM_CLASSES
extern uint32_t gpart_version;
extern struct g_command gpart_class_commands[];
extern uint32_t glabel_version;
extern struct g_command glabel_class_commands[];
#endif

static char comm[MAXPATHLEN], *class_name = NULL, *gclass_name = NULL;
static uint32_t *version = NULL;
static int verbose = 0;
static struct g_command *class_commands = NULL;

#define GEOM_CLASS_CMDS         0x01
#define GEOM_STD_CMDS           0x02

#define GEOM_CLASS_WIDTH        10

static struct g_command *find_command(const char *cmdstr, int flags);
static void list_one_geom_by_provider(const char *provider_name);
static int std_available(const char *name);
static int std_list_available(void);
static int std_load_available(void);

static void std_help(struct gctl_req *req, unsigned flags);
static void std_list(struct gctl_req *req, unsigned flags);
static void std_status(struct gctl_req *req, unsigned flags);
static void std_load(struct gctl_req *req, unsigned flags);
static void std_unload(struct gctl_req *req, unsigned flags);

static struct g_command std_commands[] = {
        { "help", 0, std_help, G_NULL_OPTS, NULL },
        { "list", 0, std_list,
            {
                { 'a', "all", NULL, G_TYPE_BOOL },
                G_OPT_SENTINEL
            },
            "[-a] [name ...]"
        },
        { "status", 0, std_status,
            {
                { 'a', "all", NULL, G_TYPE_BOOL },
                { 'g', "geoms", NULL, G_TYPE_BOOL },
                { 's', "script", NULL, G_TYPE_BOOL },
                G_OPT_SENTINEL
            },
            "[-ags] [name ...]"
        },
        { "load", G_FLAG_VERBOSE | G_FLAG_LOADKLD, std_load, G_NULL_OPTS,
            NULL },
        { "unload", G_FLAG_VERBOSE, std_unload, G_NULL_OPTS, NULL },
        G_CMD_SENTINEL
};

static void
usage_command(struct g_command *cmd, const char *prefix)
{
        struct g_option *opt;
        unsigned i;

        if (cmd->gc_usage != NULL) {
                char *pos, *ptr, *sptr;

                sptr = ptr = strdup(cmd->gc_usage);
                while ((pos = strsep(&ptr, "\n")) != NULL) {
                        if (*pos == '\0')
                                continue;
                        fprintf(stderr, "%s %s %s %s\n", prefix, comm,
                            cmd->gc_name, pos);
                }
                free(sptr);
                return;
        }

        fprintf(stderr, "%s %s %s", prefix, comm, cmd->gc_name);
        if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0)
                fprintf(stderr, " [-v]");
        for (i = 0; ; i++) {
                opt = &cmd->gc_options[i];
                if (opt->go_name == NULL)
                        break;
                if (opt->go_val != NULL || G_OPT_TYPE(opt) == G_TYPE_BOOL)
                        fprintf(stderr, " [");
                else
                        fprintf(stderr, " ");
                fprintf(stderr, "-%c", opt->go_char);
                if (G_OPT_TYPE(opt) != G_TYPE_BOOL)
                        fprintf(stderr, " %s", opt->go_name);
                if (opt->go_val != NULL || G_OPT_TYPE(opt) == G_TYPE_BOOL)
                        fprintf(stderr, "]");
        }
        fprintf(stderr, "\n");
}

static void
usage(void)
{

        if (class_name == NULL) {
                fprintf(stderr, "usage: geom <class> <command> [options]\n");
                fprintf(stderr, "       geom -p <provider-name>\n");
                fprintf(stderr, "       geom -t\n");
                exit(EXIT_FAILURE);
        } else {
                struct g_command *cmd;
                const char *prefix;
                unsigned i;

                prefix = "usage:";
                if (class_commands != NULL) {
                        for (i = 0; ; i++) {
                                cmd = &class_commands[i];
                                if (cmd->gc_name == NULL)
                                        break;
                                usage_command(cmd, prefix);
                                prefix = "      ";
                        }
                }
                for (i = 0; ; i++) {
                        cmd = &std_commands[i];
                        if (cmd->gc_name == NULL)
                                break;
                        /*
                         * If class defines command, which has the same name as
                         * standard command, skip it, because it was already
                         * shown on usage().
                         */
                        if (find_command(cmd->gc_name, GEOM_CLASS_CMDS) != NULL)
                                continue;
                        usage_command(cmd, prefix);
                        prefix = "      ";
                }
                exit(EXIT_FAILURE);
        }
}

static void
load_module(void)
{
        char name1[64], name2[64];

        snprintf(name1, sizeof(name1), "g_%s", class_name);
        snprintf(name2, sizeof(name2), "geom_%s", class_name);
        if (modfind(name1) < 0) {
                /* Not present in kernel, try loading it. */
                if (kldload(name2) < 0 || modfind(name1) < 0) {
                        if (errno != EEXIST) {
                                xo_err(EXIT_FAILURE, "cannot load %s", name2);
                        }
                }
        }
}

static int
strlcatf(char *str, size_t size, const char *format, ...)
{
        size_t len;
        va_list ap;
        int ret;

        len = strlen(str);
        str += len;
        size -= len;

        va_start(ap, format);
        ret = vsnprintf(str, size, format, ap);
        va_end(ap);

        return (ret);
}

/*
 * Find given option in options available for given command.
 */
static struct g_option *
find_option(struct g_command *cmd, char ch)
{
        struct g_option *opt;
        unsigned i;

        for (i = 0; ; i++) {
                opt = &cmd->gc_options[i];
                if (opt->go_name == NULL)
                        return (NULL);
                if (opt->go_char == ch)
                        return (opt);
        }
        /* NOTREACHED */
        return (NULL);
}

/*
 * Add given option to gctl_req.
 */
static void
set_option(struct gctl_req *req, struct g_option *opt, const char *val)
{
        const char *optname;
        int64_t number;
        void *ptr;

        if (G_OPT_ISMULTI(opt)) {
                size_t optnamesize;

                if (G_OPT_NUM(opt) == UCHAR_MAX)
                        xo_errx(EXIT_FAILURE, "Too many -%c options.",
                            opt->go_char);

                /*
                 * Base option name length plus 3 bytes for option number
                 * (max. 255 options) plus 1 byte for terminating '\0'.
                 */
                optnamesize = strlen(opt->go_name) + 3 + 1;
                ptr = malloc(optnamesize);
                if (ptr == NULL)
                        xo_errx(EXIT_FAILURE, "No memory.");
                snprintf(ptr, optnamesize, "%s%u", opt->go_name, G_OPT_NUM(opt));
                G_OPT_NUMINC(opt);
                optname = ptr;
        } else {
                optname = opt->go_name;
        }

        if (G_OPT_TYPE(opt) == G_TYPE_NUMBER) {
                if (expand_number(val, &number) == -1) {
                        xo_err(EXIT_FAILURE, "Invalid value for '%c' argument",
                            opt->go_char);
                }
                ptr = malloc(sizeof(intmax_t));
                if (ptr == NULL)
                        xo_errx(EXIT_FAILURE, "No memory.");
                *(intmax_t *)ptr = number;
                opt->go_val = ptr;
                gctl_ro_param(req, optname, sizeof(intmax_t), opt->go_val);
        } else if (G_OPT_TYPE(opt) == G_TYPE_STRING) {
                gctl_ro_param(req, optname, -1, val);
        } else if (G_OPT_TYPE(opt) == G_TYPE_BOOL) {
                ptr = malloc(sizeof(int));
                if (ptr == NULL)
                        xo_errx(EXIT_FAILURE, "No memory.");
                *(int *)ptr = *val - '0';
                opt->go_val = ptr;
                gctl_ro_param(req, optname, sizeof(int), opt->go_val);
        } else {
                assert(!"Invalid type");
        }

        if (G_OPT_ISMULTI(opt))
                free(__DECONST(char *, optname));
}

/*
 * 1. Add given argument by caller.
 * 2. Add default values of not given arguments.
 * 3. Add the rest of arguments.
 */
static void
parse_arguments(struct g_command *cmd, struct gctl_req *req, int *argc,
    char ***argv)
{
        struct g_option *opt;
        char opts[64];
        unsigned i;
        int ch, vcount;

        *opts = '\0';
        if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0)
                strlcat(opts, "v", sizeof(opts));
        for (i = 0; ; i++) {
                opt = &cmd->gc_options[i];
                if (opt->go_name == NULL)
                        break;
                assert(G_OPT_TYPE(opt) != 0);
                assert((opt->go_type & ~(G_TYPE_MASK | G_TYPE_MULTI)) == 0);
                /* Multiple bool arguments makes no sense. */
                assert(G_OPT_TYPE(opt) != G_TYPE_BOOL ||
                    (opt->go_type & G_TYPE_MULTI) == 0);
                strlcatf(opts, sizeof(opts), "%c", opt->go_char);
                if (G_OPT_TYPE(opt) != G_TYPE_BOOL)
                        strlcat(opts, ":", sizeof(opts));
        }

        /*
         * Add specified arguments.
         */
        vcount = 0;
        while ((ch = getopt(*argc, *argv, opts)) != -1) {
                /* Standard (not passed to kernel) options. */
                if (ch == 'v' && (cmd->gc_flags & G_FLAG_VERBOSE) != 0)
                        verbose = 1;
                /* Options passed to kernel. */
                opt = find_option(cmd, ch);
                if (opt == NULL) {
                        if (ch == 'v' && (cmd->gc_flags & G_FLAG_VERBOSE) != 0) {
                                if (++vcount < 2)
                                        continue;
                                else
                                        xo_warnx("Option 'v' specified twice.");
                        }
                        usage();
                }
                if (!G_OPT_ISMULTI(opt) && G_OPT_ISDONE(opt)) {
                        xo_warnx("Option '%c' specified twice.", opt->go_char);
                        usage();
                }
                G_OPT_DONE(opt);

                if (G_OPT_TYPE(opt) == G_TYPE_BOOL)
                        set_option(req, opt, "1");
                else
                        set_option(req, opt, optarg);
        }
        *argc -= optind;
        *argv += optind;

        /*
         * Add not specified arguments, but with default values.
         */
        for (i = 0; ; i++) {
                opt = &cmd->gc_options[i];
                if (opt->go_name == NULL)
                        break;
                if (G_OPT_ISDONE(opt))
                        continue;

                if (G_OPT_TYPE(opt) == G_TYPE_BOOL) {
                        assert(opt->go_val == NULL);
                        set_option(req, opt, "0");
                } else {
                        if (opt->go_val == NULL) {
                                xo_warnx("Option '%c' not specified.",
                                    opt->go_char);
                                usage();
                        } else if (opt->go_val == G_VAL_OPTIONAL) {
                                /* add nothing. */
                        } else {
                                set_option(req, opt, opt->go_val);
                        }
                }
        }

        /*
         * Add rest of given arguments.
         */
        gctl_ro_param(req, "nargs", sizeof(int), argc);
        for (i = 0; i < (unsigned)*argc; i++) {
                char argname[16];

                snprintf(argname, sizeof(argname), "arg%u", i);
                gctl_ro_param(req, argname, -1, (*argv)[i]);
        }
}

/*
 * Find given command in commands available for given class.
 */
static struct g_command *
find_command(const char *cmdstr, int flags)
{
        struct g_command *cmd;
        unsigned i;

        /*
         * First try to find command defined by loaded library.
         */
        if ((flags & GEOM_CLASS_CMDS) != 0 && class_commands != NULL) {
                for (i = 0; ; i++) {
                        cmd = &class_commands[i];
                        if (cmd->gc_name == NULL)
                                break;
                        if (strcmp(cmd->gc_name, cmdstr) == 0)
                                return (cmd);
                }
        }
        /*
         * Now try to find in standard commands.
         */
        if ((flags & GEOM_STD_CMDS) != 0) {
                for (i = 0; ; i++) {
                        cmd = &std_commands[i];
                        if (cmd->gc_name == NULL)
                                break;
                        if (strcmp(cmd->gc_name, cmdstr) == 0)
                                return (cmd);
                }
        }
        return (NULL);
}

static unsigned
set_flags(struct g_command *cmd)
{
        unsigned flags = 0;

        if ((cmd->gc_flags & G_FLAG_VERBOSE) != 0 && verbose)
                flags |= G_FLAG_VERBOSE;

        return (flags);
}

/*
 * Run command.
 */
static void
run_command(int argc, char *argv[])
{
        struct g_command *cmd;
        struct gctl_req *req;
        const char *errstr;
        char buf[4096];

        /* First try to find a command defined by a class. */
        cmd = find_command(argv[0], GEOM_CLASS_CMDS);
        if (cmd == NULL) {
                /* Now, try to find a standard command. */
                cmd = find_command(argv[0], GEOM_STD_CMDS);
                if (cmd == NULL) {
                        xo_warnx("Unknown command: %s.", argv[0]);
                        usage();
                }
                if (!std_available(cmd->gc_name))
                        xo_errx(EXIT_FAILURE, "Command '%s' not available; "
                            "try 'load' first.", argv[0]);
        }
        if ((cmd->gc_flags & G_FLAG_LOADKLD) != 0)
                load_module();

        req = gctl_get_handle();
        gctl_ro_param(req, "class", -1, gclass_name);
        gctl_ro_param(req, "verb", -1, argv[0]);
        if (version != NULL)
                gctl_ro_param(req, "version", sizeof(*version), version);
        parse_arguments(cmd, req, &argc, &argv);

        buf[0] = '\0';
        if (cmd->gc_func != NULL) {
                unsigned flags;

                flags = set_flags(cmd);
                cmd->gc_func(req, flags);
                errstr = req->error;
        } else {
                gctl_add_param(req, "output", sizeof(buf), buf,
                    GCTL_PARAM_WR | GCTL_PARAM_ASCII);
                errstr = gctl_issue(req);
        }
        if (errstr != NULL && errstr[0] != '\0') {
                xo_warnx("%s", errstr);
                /* Suppress EXIT_FAILURE for warnings */
                if (strncmp(errstr, "warning: ", strlen("warning: ")) == 0)
                        req->nerror = 0;
                if (req->nerror != 0) {
                        gctl_free(req);
                        exit(EXIT_FAILURE);
                }
        }
        if (buf[0] != '\0')
                printf("%s", buf);
        gctl_free(req);
        if (verbose)
                printf("Done.\n");
        xo_finish();
        exit(EXIT_SUCCESS);
}

#ifndef STATIC_GEOM_CLASSES
static const char *
library_path(void)
{
        const char *path;

        path = getenv("GEOM_LIBRARY_PATH");
        if (path == NULL)
                path = GEOM_CLASS_DIR;
        return (path);
}

static void
load_library(void)
{
        char *curpath, path[MAXPATHLEN], *tofree, *totalpath;
        uint32_t *lib_version;
        void *dlh;
        int ret;

        ret = 0;
        tofree = totalpath = strdup(library_path());
        if (totalpath == NULL)
                xo_err(EXIT_FAILURE, "Not enough memory for library path");

        if (strchr(totalpath, ':') != NULL)
                curpath = strsep(&totalpath, ":");
        else
                curpath = totalpath;
        /* Traverse the paths to find one that contains the library we want. */
        while (curpath != NULL) {
                snprintf(path, sizeof(path), "%s/geom_%s.so", curpath,
                    class_name);
                ret = access(path, F_OK);
                if (ret == -1) {
                        if (errno == ENOENT) {
                                /*
                                 * If we cannot find library, try the next
                                 * path.
                                 */
                                curpath = strsep(&totalpath, ":");
                                continue;
                        }
                        xo_err(EXIT_FAILURE, "Cannot access library");
                }
                break;
        }
        free(tofree);
        /* No library was found, but standard commands can still be used */
        if (ret == -1)
                return;
        dlh = dlopen(path, RTLD_NOW);
        if (dlh == NULL)
                xo_errx(EXIT_FAILURE, "Cannot open library: %s.", dlerror());
        lib_version = dlsym(dlh, "lib_version");
        if (lib_version == NULL) {
                xo_warnx("Cannot find symbol %s: %s.", "lib_version",
                    dlerror());
                dlclose(dlh);
                exit(EXIT_FAILURE);
        }
        if (*lib_version != G_LIB_VERSION) {
                dlclose(dlh);
                xo_errx(EXIT_FAILURE, "%s and %s are not synchronized.",
                    getprogname(), path);
        }
        version = dlsym(dlh, "version");
        if (version == NULL) {
                xo_warnx("Cannot find symbol %s: %s.", "version", dlerror());
                dlclose(dlh);
                exit(EXIT_FAILURE);
        }
        class_commands = dlsym(dlh, "class_commands");
        if (class_commands == NULL) {
                xo_warnx("Cannot find symbol %s: %s.", "class_commands",
                    dlerror());
                dlclose(dlh);
                exit(EXIT_FAILURE);
        }
}
#endif  /* !STATIC_GEOM_CLASSES */

/*
 * Class name should be all capital letters.
 */
static void
set_class_name(void)
{
        char *s1, *s2;

        s1 = class_name;
        for (; *s1 != '\0'; s1++)
                *s1 = tolower(*s1);
        gclass_name = malloc(strlen(class_name) + 1);
        if (gclass_name == NULL)
                xo_errx(EXIT_FAILURE, "No memory");
        s1 = gclass_name;
        s2 = class_name;
        for (; *s2 != '\0'; s2++)
                *s1++ = toupper(*s2);
        *s1 = '\0';
}

static void
get_class(int *argc, char ***argv)
{

        snprintf(comm, sizeof(comm), "%s", basename((*argv)[0]));
        if (strcmp(comm, "geom") == 0) {
                if (*argc < 2)
                        usage();
                else if (*argc == 2) {
                        if (strcmp((*argv)[1], "-h") == 0 ||
                            strcmp((*argv)[1], "help") == 0) {
                                usage();
                        }
                }
                strlcatf(comm, sizeof(comm), " %s", (*argv)[1]);
                class_name = (*argv)[1];
                *argc -= 2;
                *argv += 2;
        } else if (*comm == 'g') {
                class_name = comm + 1;
                *argc -= 1;
                *argv += 1;
        } else {
                xo_errx(EXIT_FAILURE, "Invalid utility name.");
        }

#ifndef STATIC_GEOM_CLASSES
        load_library();
#else
        if (!strcasecmp(class_name, "part")) {
                version = &gpart_version;
                class_commands = gpart_class_commands;
        } else if (!strcasecmp(class_name, "label")) {
                version = &glabel_version;
                class_commands = glabel_class_commands;
        }
#endif /* !STATIC_GEOM_CLASSES */

        set_class_name();

        /* If we can't load or list, it's not a class. */
        if (!std_load_available() && !std_list_available())
                xo_errx(EXIT_FAILURE, "Invalid class name '%s'.", class_name);

        if (*argc < 1)
                usage();
}

static struct ggeom *
find_geom_by_provider(struct gmesh *mesh, const char *name)
{
        struct gclass *classp;
        struct ggeom *gp;
        struct gprovider *pp;

        LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
                LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
                        LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
                                if (strcmp(pp->lg_name, name) == 0)
                                        return (gp);
                        }
                }
        }

        return (NULL);
}

static int
compute_tree_width_geom(struct gmesh *mesh, struct ggeom *gp, int indent)
{
        struct gclass *classp2;
        struct ggeom *gp2;
        struct gconsumer *cp2;
        struct gprovider *pp;
        int max_width, width;

        max_width = width = indent + strlen(gp->lg_name);

        LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
                LIST_FOREACH(classp2, &mesh->lg_class, lg_class) {
                        LIST_FOREACH(gp2, &classp2->lg_geom, lg_geom) {
                                LIST_FOREACH(cp2,
                                    &gp2->lg_consumer, lg_consumer) {
                                        if (pp != cp2->lg_provider)
                                                continue;
                                        width = compute_tree_width_geom(mesh,
                                            gp2, indent + 2);
                                        if (width > max_width)
                                                max_width = width;
                                }
                        }
                }
        }

        return (max_width);
}

static int
compute_tree_width(struct gmesh *mesh)
{
        struct gclass *classp;
        struct ggeom *gp;
        int max_width, width;

        max_width = width = 0;

        LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
                LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
                        if (!LIST_EMPTY(&gp->lg_consumer))
                                continue;
                        width = compute_tree_width_geom(mesh, gp, 0);
                        if (width > max_width)
                                max_width = width;
                }
        }

        return (max_width);
}

static void
show_tree_geom(struct gmesh *mesh, struct ggeom *gp, int indent, int width)
{
        struct gclass *classp2;
        struct ggeom *gp2;
        struct gconsumer *cp2;
        struct gprovider *pp;

        if (LIST_EMPTY(&gp->lg_provider)) {
                printf("%*s%-*.*s %-*.*s\n", indent, "",
                    width - indent, width - indent, gp->lg_name,
                    GEOM_CLASS_WIDTH, GEOM_CLASS_WIDTH, gp->lg_class->lg_name);
                return;
        }

        LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
                printf("%*s%-*.*s %-*.*s %s\n", indent, "",
                    width - indent, width - indent, gp->lg_name,
                    GEOM_CLASS_WIDTH, GEOM_CLASS_WIDTH, gp->lg_class->lg_name,
                    pp->lg_name);

                LIST_FOREACH(classp2, &mesh->lg_class, lg_class) {
                        LIST_FOREACH(gp2, &classp2->lg_geom, lg_geom) {
                                LIST_FOREACH(cp2,
                                    &gp2->lg_consumer, lg_consumer) {
                                        if (pp != cp2->lg_provider)
                                                continue;
                                        show_tree_geom(mesh, gp2,
                                            indent + 2, width);
                                }
                        }
                }
        }
}

static void
show_tree(void)
{
        struct gmesh mesh;
        struct gclass *classp;
        struct ggeom *gp;
        int error, width;

        error = geom_gettree(&mesh);
        if (error != 0)
                xo_errc(EXIT_FAILURE, error, "Cannot get GEOM tree");

        width = compute_tree_width(&mesh);

        printf("%-*.*s %-*.*s %s\n",
            width, width, "Geom",
            GEOM_CLASS_WIDTH, GEOM_CLASS_WIDTH, "Class",
            "Provider");

        LIST_FOREACH(classp, &mesh.lg_class, lg_class) {
                LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
                        if (!LIST_EMPTY(&gp->lg_consumer))
                                continue;
                        show_tree_geom(&mesh, gp, 0, width);
                }
        }
}

int
main(int argc, char *argv[])
{
        char *provider_name;
        bool tflag;
        int ch;

        provider_name = NULL;
        tflag = false;

        argc = xo_parse_args(argc, argv);
        if (argc < 0)
                return (argc);

        if (strcmp(getprogname(), "geom") == 0) {
                while ((ch = getopt(argc, argv, "hp:t")) != -1) {
                        switch (ch) {
                        case 'p':
                                provider_name = strdup(optarg);
                                if (provider_name == NULL)
                                        xo_err(1, "strdup");
                                break;
                        case 't':
                                tflag = true;
                                break;
                        case 'h':
                        default:
                                usage();
                        }
                }

                /*
                 * Don't adjust argc and argv, it would break get_class().
                 */
        }
        xo_set_version(GEOM_XO_VERSION);

        if (tflag && provider_name != NULL) {
                xo_errx(EXIT_FAILURE,
                    "At most one of -P and -t may be specified.");
        }

        if (provider_name != NULL) {
                list_one_geom_by_provider(provider_name);
                xo_finish();
                return (0);
        }

        if (tflag) {
                show_tree();
                return (0);
        }

        get_class(&argc, &argv);
        run_command(argc, argv);
        /* NOTREACHED */

        exit(EXIT_FAILURE);
}

static struct gclass *
find_class(struct gmesh *mesh, const char *name)
{
        struct gclass *classp;

        LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
                if (strcmp(classp->lg_name, name) == 0)
                        return (classp);
        }
        return (NULL);
}

static struct ggeom *
find_geom(struct gclass *classp, const char *name)
{
        struct ggeom *gp;

        if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
                name += sizeof(_PATH_DEV) - 1;

        LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
                if (strcmp(gp->lg_name, name) == 0)
                        return (gp);
        }
        return (NULL);
}

static void
list_one_provider(struct gprovider *pp, const char *padding)
{
        struct gconfig *conf;
        char buf[5];

        xo_emit("{Lcw:Name}{:name}\n", pp->lg_name);
        humanize_number(buf, sizeof(buf), (int64_t)pp->lg_mediasize, "",
            HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
        xo_emit("{P:/%s}{Lcw:Mediasize}{:mediasize/%jd} ({N:/%s})\n",
            padding, (intmax_t)pp->lg_mediasize, buf);
        xo_emit("{P:/%s}{Lcw:Sectorsize}{:sectorsize/%u}\n",
            padding, pp->lg_sectorsize);
        if (pp->lg_stripesize > 0 || pp->lg_stripeoffset > 0) {
                xo_emit("{P:/%s}{Lcw:Stripesize}{:stripesize/%ju}\n",
                    padding, pp->lg_stripesize);
                xo_emit("{P:/%s}{Lcw:Stripeoffset}{:stripeoffset/%ju}\n",
                    padding, pp->lg_stripeoffset);
        }
        xo_emit("{P:/%s}{Lcw:Mode}{:mode}\n", padding, pp->lg_mode);
        LIST_FOREACH(conf, &pp->lg_config, lg_config) {
                if (strcmp(conf->lg_name, "attrib") != 0)
                        xo_emit("{P:/%s}{Lcwa:}{a:}\n", padding, conf->lg_name,
                            conf->lg_name, conf->lg_val ? conf->lg_val : "");
                else
                        xo_emit("{P:/%s}{Lcw:attrib}{l:attribute}\n", padding,
                            conf->lg_val ? conf->lg_val : "");
        }
}

static void
list_one_consumer(struct gconsumer *cp, const char *padding)
{
        struct gprovider *pp;
        struct gconfig *conf;

        pp = cp->lg_provider;
        if (pp == NULL)
                printf("[no provider]\n");
        else {
                char buf[5];

                xo_emit("{Lcw:Name}{:name}\n", pp->lg_name);
                humanize_number(buf, sizeof(buf), (int64_t)pp->lg_mediasize, "",
                    HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
                xo_emit("{P:/%s}{Lcw:Mediasize}{:mediasize/%jd} ({N:/%s})\n",
                    padding, (intmax_t)pp->lg_mediasize, buf);
                xo_emit("{P:/%s}{Lcw:Sectorsize}{:sectorsize/%u}\n",
                    padding, pp->lg_sectorsize);
                if (pp->lg_stripesize > 0 || pp->lg_stripeoffset > 0) {
                        xo_emit("{P:/%s}{Lcw:Stripesize}{:stripesize/%ju}\n",
                            padding, pp->lg_stripesize);
                        xo_emit("{P:/%s}{Lcw:Stripeoffset}{:stripeoffset/%ju}\n",
                            padding, pp->lg_stripeoffset);
                }
                xo_emit("{P:/%s}{Lcw:Mode}{:mode}\n", padding, pp->lg_mode);
        }
        LIST_FOREACH(conf, &cp->lg_config, lg_config) {
                xo_emit("{P:/%s}{Lcwa:}{a:}\n", padding, conf->lg_name,
                    conf->lg_name, conf->lg_val ? conf->lg_val : "");
        }
}

static void
list_one_geom(struct ggeom *gp)
{
        struct gprovider *pp;
        struct gconsumer *cp;
        struct gconfig *conf;
        unsigned n;

        xo_emit("{Lcw:Geom name}{:name}\n", gp->lg_name);
        LIST_FOREACH(conf, &gp->lg_config, lg_config) {
                xo_emit("{Lcwa:}{a:}\n", conf->lg_name, conf->lg_name,
                    conf->lg_val ? conf->lg_val : "");
        }
        if (!LIST_EMPTY(&gp->lg_provider)) {
                xo_open_list("providers");
                xo_emit("{Tc:Providers}\n");
                n = 1;
                LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
                        xo_emit("{T:/%u}. ", n++);
                        xo_open_instance("provider");
                        list_one_provider(pp, "   ");
                        xo_close_instance("provider");
                }
                xo_close_list("providers");
        }
        if (!LIST_EMPTY(&gp->lg_consumer)) {
                xo_open_list("consumers");
                xo_emit("{Tc:Consumers}\n");
                n = 1;
                LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) {
                        xo_emit("{T:/%u}. ", n++);
                        xo_open_instance("consumer");
                        list_one_consumer(cp, "   ");
                        xo_close_instance("consumer");
                }
                xo_close_list("consumers");
        }
        xo_emit("\n");
}

static void
list_one_geom_by_provider(const char *provider_name)
{
        struct gmesh mesh;
        struct ggeom *gp;
        int error;

        error = geom_gettree(&mesh);
        if (error != 0)
                xo_errc(EXIT_FAILURE, error, "Cannot get GEOM tree");

        gp = find_geom_by_provider(&mesh, provider_name);
        if (gp == NULL)
                xo_errx(EXIT_FAILURE, "Cannot find provider '%s'.", provider_name);

        xo_open_container(provider_name);
        xo_emit("{Lwc:Geom class}{:class}\n", gp->lg_class->lg_name);
        list_one_geom(gp);
        xo_close_container(provider_name);
}

static void
std_help(struct gctl_req *req __unused, unsigned flags __unused)
{

        usage();
}

static int
std_list_available(void)
{
        struct gmesh mesh;
        struct gclass *classp;
        int error;

        error = geom_gettree_geom(&mesh, gclass_name, "", 0);
        if (error != 0)
                xo_errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
        classp = find_class(&mesh, gclass_name);
        geom_deletetree(&mesh);
        if (classp != NULL)
                return (1);
        return (0);
}

static void
std_list(struct gctl_req *req, unsigned flags __unused)
{
        struct gmesh mesh;
        struct gclass *classp;
        struct ggeom *gp;
        const char *name;
        int all, error, i, nargs;

        nargs = gctl_get_int(req, "nargs");
        if (nargs == 1) {
                error = geom_gettree_geom(&mesh, gclass_name,
                    gctl_get_ascii(req, "arg0"), 1);
        } else
                error = geom_gettree(&mesh);
        if (error != 0)
                xo_errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
        classp = find_class(&mesh, gclass_name);
        if (classp == NULL) {
                geom_deletetree(&mesh);
                xo_errx(EXIT_FAILURE, "Class '%s' not found.", gclass_name);
        }
        all = gctl_get_int(req, "all");
        if (nargs > 0) {
                for (i = 0; i < nargs; i++) {
                        name = gctl_get_ascii(req, "arg%d", i);
                        gp = find_geom(classp, name);
                        if (gp == NULL) {
                                xo_errx(EXIT_FAILURE, "Class '%s' does not have "
                                    "an instance named '%s'.",
                                    gclass_name, name);
                        }
                        xo_open_container(gclass_name);
                        list_one_geom(gp);
                        xo_close_container(gclass_name);
                }
        } else {
                xo_open_list(gclass_name);
                LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
                        if (LIST_EMPTY(&gp->lg_provider) && !all)
                                continue;
                        xo_open_instance("geom");
                        list_one_geom(gp);
                        xo_close_instance("geom");
                }
                xo_close_list(gclass_name);
        }
        geom_deletetree(&mesh);
}

static int
std_status_available(void)
{

        /* 'status' command is available when 'list' command is. */
        return (std_list_available());
}

static void
status_update_len(struct ggeom *gp, int *name_len, int *status_len)
{
        struct gconfig *conf;
        int len;

        assert(gp != NULL);
        assert(name_len != NULL);
        assert(status_len != NULL);

        len = strlen(gp->lg_name);
        if (*name_len < len)
                *name_len = len;
        LIST_FOREACH(conf, &gp->lg_config, lg_config) {
                if (strcasecmp(conf->lg_name, "state") == 0) {
                        len = strlen(conf->lg_val);
                        if (*status_len < len)
                                *status_len = len;
                }
        }
}

static void
status_update_len_prs(struct ggeom *gp, int *name_len, int *status_len)
{
        struct gprovider *pp;
        struct gconfig *conf;
        int len, glen;

        assert(gp != NULL);
        assert(name_len != NULL);
        assert(status_len != NULL);

        glen = 0;
        LIST_FOREACH(conf, &gp->lg_config, lg_config) {
                if (strcasecmp(conf->lg_name, "state") == 0) {
                        glen = strlen(conf->lg_val);
                        break;
                }
        }
        LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
                len = strlen(pp->lg_name);
                if (*name_len < len)
                        *name_len = len;
                len = glen;
                LIST_FOREACH(conf, &pp->lg_config, lg_config) {
                        if (strcasecmp(conf->lg_name, "state") == 0) {
                                len = strlen(conf->lg_val);
                                break;
                        }
                }
                if (*status_len < len)
                        *status_len = len;
        }
}

static char *
status_one_consumer(struct gconsumer *cp, const char *value)
{
        struct gprovider *pp;
        struct gconfig *conf;
        char *ret;

        pp = cp->lg_provider;
        if (pp == NULL)
                return (NULL);
        ret = NULL;
        LIST_FOREACH(conf, &cp->lg_config, lg_config) {
                if (strcasecmp(conf->lg_name, value) == 0)
                        ret = conf->lg_val;
        }

        if (ret == NULL)
                return (NULL);
        return (ret);
}

static void
status_one_geom(struct ggeom *gp, int script, int name_len, int status_len)
{
        struct gconsumer *cp;
        struct gconfig *conf;
        char fmt[64];
        const char *name, *status, *cstate, *csyncr;
        int gotone, len;

        name = gp->lg_name;
        status = "N/A";
        LIST_FOREACH(conf, &gp->lg_config, lg_config) {
                if (strcasecmp(conf->lg_name, "state") == 0) {
                        status = conf->lg_val;
                        break;
                }
        }
        gotone = len = 0;
        xo_open_instance("status");
        LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) {
                if (cp->lg_provider == NULL)
                        continue;

                cstate = status_one_consumer(cp, "state");
                csyncr = status_one_consumer(cp, "synchronized");
                if (!gotone || script) {
                        if (!gotone) {
                                xo_emit("{t:name/%*s}  {t:status/%*s}  ",
                                    name_len, name, status_len, status);
                                xo_open_list("components");
                        } else {
                                /*
                                 * XXX: running the same xo_emit() as above or
                                 * variations of it will cause the XML/JSON to
                                 * produce extra "components" lists in script
                                 * mode
                                 */

                                snprintf(fmt, sizeof(fmt), "%*s  %*s  ",
                                    name_len, name, status_len, status);
                                xo_emit(fmt);
                        }
                }

                xo_open_instance("components");
                if (cstate != NULL && csyncr != NULL) {
                        xo_emit("{P:/%*s}{:component} ({:state}, {:synchronized})\n",
                            len, "", cp->lg_provider->lg_name, cstate, csyncr);
                } else if (cstate != NULL) {
                        xo_emit("{P:/%*s}{:component} ({:state})\n",
                            len, "", cp->lg_provider->lg_name, cstate);
                } else if (csyncr != NULL) {
                        xo_emit("{P:/%*s}{:component} ({:synchronized})\n",
                            len, "", cp->lg_provider->lg_name, csyncr);
                } else {
                        xo_emit("{P:/%*s}{:component}\n",
                            len, "", cp->lg_provider->lg_name);
                }
                xo_close_instance("components");
                gotone = 1;
                if (!len && !script)
                        len = name_len + status_len + 4;
        }
        if (!gotone) {
                xo_emit("{t:name/%*s}  {t:status/%*s}  N/A\n",
                    name_len, name, status_len, status);
        } else {
                xo_close_list("components");
        }
        xo_close_instance("status");
}

static void
status_one_geom_prs(struct ggeom *gp, int script, int name_len, int status_len)
{
        struct gprovider *pp;
        struct gconsumer *cp;
        struct gconfig *conf;
        const char *name, *status, *cstate, *csyncr;
        char fmt[64];
        int gotone, len;

        xo_open_instance("status");
        LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
                name = pp->lg_name;
                status = "N/A";
                LIST_FOREACH(conf, &gp->lg_config, lg_config) {
                        if (strcasecmp(conf->lg_name, "state") == 0) {
                                status = conf->lg_val;
                                break;
                        }
                }
                LIST_FOREACH(conf, &pp->lg_config, lg_config) {
                        if (strcasecmp(conf->lg_name, "state") == 0) {
                                status = conf->lg_val;
                                break;
                        }
                }
                gotone = len = 0;
                LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) {
                        if (cp->lg_provider == NULL)
                                continue;

                        cstate = status_one_consumer(cp, "state");
                        csyncr = status_one_consumer(cp, "synchronized");
                        if (!gotone || script) {
                                if (!gotone) {
                                        xo_emit("{t:name/%*s}  {t:status/%*s}  ",
                                            name_len, name, status_len, status);
                                        xo_open_list("components");
                                } else {
                                        /*
                                         * XXX: running the same xo_emit() as
                                         * above or variations of it will
                                         * cause the XML/JSON to produce
                                         * extra "components" lists in
                                         * script mode
                                         */

                                        snprintf(fmt, sizeof(fmt), "%*s  %*s  ",
                                            name_len, name, status_len, status);
                                        xo_emit(fmt);
                                }
                        }

                        xo_open_instance("component");
                        if (cstate != NULL && csyncr != NULL) {
                                xo_emit("{P:/%*s}{:component} ({:state}, {:synchronized})\n",
                                    len, "", cp->lg_provider->lg_name, cstate, csyncr);
                        } else if (cstate != NULL) {
                                xo_emit("{P:/%*s}{:component} ({:state})\n",
                                    len, "", cp->lg_provider->lg_name, cstate);
                        } else if (csyncr != NULL) {
                                xo_emit("{P:/%*s}{:component} ({:synchronized})\n",
                                    len, "", cp->lg_provider->lg_name, csyncr);
                        } else {
                                xo_emit("{P:/%*s}{:component}\n",
                                    len, "", cp->lg_provider->lg_name);
                        }
                        xo_close_instance("component");
                        gotone = 1;
                        if (!len && !script)
                                len = name_len + status_len + 4;
                }
                if (!gotone) {
                        xo_emit("{t:name/%*s}  {t:status/%*s}  N/A\n",
                            name_len, name, status_len, status);
                } else {
                        xo_close_list("components");
                }
        }
        xo_close_instance("status");
}

static void
std_status(struct gctl_req *req, unsigned flags __unused)
{
        struct gmesh mesh;
        struct gclass *classp;
        struct ggeom *gp;
        const char *name;
        int name_len, status_len;
        int all, error, geoms, i, n, nargs, script;

        error = geom_gettree(&mesh);
        if (error != 0)
                xo_errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
        classp = find_class(&mesh, gclass_name);
        if (classp == NULL)
                xo_errx(EXIT_FAILURE, "Class %s not found.", gclass_name);
        nargs = gctl_get_int(req, "nargs");
        all = gctl_get_int(req, "all");
        geoms = gctl_get_int(req, "geoms");
        script = gctl_get_int(req, "script");
        name_len = strlen("Name");
        status_len = strlen("Status");

        if (nargs > 0) {
                for (i = 0, n = 0; i < nargs; i++) {
                        name = gctl_get_ascii(req, "arg%d", i);
                        gp = find_geom(classp, name);
                        if (gp == NULL)
                                xo_errx(EXIT_FAILURE, "No such geom: %s.",
                                    name);
                        if (geoms) {
                                status_update_len(gp,
                                    &name_len, &status_len);
                        } else {
                                status_update_len_prs(gp,
                                    &name_len, &status_len);
                        }
                        n++;
                }
                if (n == 0)
                        goto end;
        } else {
                n = 0;
                LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
                        if (LIST_EMPTY(&gp->lg_provider) && !all)
                                continue;
                        if (geoms) {
                                status_update_len(gp,
                                    &name_len, &status_len);
                        } else {
                                status_update_len_prs(gp,
                                    &name_len, &status_len);
                        }
                        n++;
                }
                if (n == 0)
                        goto end;
        }
        if (!script) {
                xo_emit("{T:/%*s}  {T:/%*s}  {T:Components}\n",
                    name_len, "Name", status_len, "Status");
        }
        xo_open_list(gclass_name);
        if (nargs > 0) {
                for (i = 0; i < nargs; i++) {
                        name = gctl_get_ascii(req, "arg%d", i);
                        gp = find_geom(classp, name);
                        if (gp == NULL)
                                continue;
                        if (geoms) {
                                status_one_geom(gp, script, name_len,
                                    status_len);
                        } else {
                                status_one_geom_prs(gp, script, name_len,
                                    status_len);
                        }
                }
        } else {
                LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
                        if (LIST_EMPTY(&gp->lg_provider) && !all)
                                continue;
                        if (geoms) {
                                status_one_geom(gp, script, name_len,
                                    status_len);
                        } else {
                                status_one_geom_prs(gp, script, name_len,
                                    status_len);
                        }
                }
        }
        xo_close_list(gclass_name);
end:
        geom_deletetree(&mesh);
}

static int
std_load_available(void)
{
        char name[MAXPATHLEN], paths[MAXPATHLEN * 8], *p;
        struct stat sb;
        size_t len;

        snprintf(name, sizeof(name), "g_%s", class_name);
        /*
         * If already in kernel, "load" command is NOP.
         */
        if (modfind(name) >= 0)
                return (1);
        bzero(paths, sizeof(paths));
        len = sizeof(paths);
        if (sysctlbyname("kern.module_path", paths, &len, NULL, 0) < 0)
                xo_err(EXIT_FAILURE, "sysctl(kern.module_path)");
        for (p = strtok(paths, ";"); p != NULL; p = strtok(NULL, ";")) {
                snprintf(name, sizeof(name), "%s/geom_%s.ko", p, class_name);
                /*
                 * If geom_<name>.ko file exists, "load" command is available.
                 */
                if (stat(name, &sb) == 0)
                        return (1);
        }
        return (0);
}

static void
std_load(struct gctl_req *req __unused, unsigned flags)
{

        /*
         * Do nothing special here, because of G_FLAG_LOADKLD flag,
         * module is already loaded.
         */
        if ((flags & G_FLAG_VERBOSE) != 0)
                printf("Module available.\n");
}

static int
std_unload_available(void)
{
        char name[64];
        int id;

        snprintf(name, sizeof(name), "geom_%s", class_name);
        id = kldfind(name);
        if (id >= 0)
                return (1);
        return (0);
}

static void
std_unload(struct gctl_req *req, unsigned flags __unused)
{
        char name[64];
        int id;

        snprintf(name, sizeof(name), "geom_%s", class_name);
        id = kldfind(name);
        if (id < 0) {
                gctl_error(req, "Could not find module: %s.", strerror(errno));
                return;
        }
        if (kldunload(id) < 0) {
                gctl_error(req, "Could not unload module: %s.",
                    strerror(errno));
                return;
        }
}

static int
std_available(const char *name)
{

        if (strcmp(name, "help") == 0)
                return (1);
        else if (strcmp(name, "list") == 0)
                return (std_list_available());
        else if (strcmp(name, "status") == 0)
                return (std_status_available());
        else if (strcmp(name, "load") == 0)
                return (std_load_available());
        else if (strcmp(name, "unload") == 0)
                return (std_unload_available());
        else
                assert(!"Unknown standard command.");
        return (0);
}