root/usr/src/cmd/mdb/common/modules/genunix/zone.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2012, Joyent, Inc.  All rights reserved.
 * Copyright 2025 Oxide Computer Company
 */

#include <mdb/mdb_param.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_ks.h>

#include "zone.h"

#include <stddef.h>
#include <sys/zone.h>

#define ZONE_NAMELEN    20
#ifdef _LP64
#define ZONE_PATHLEN    32
#else
#define ZONE_PATHLEN    40
#endif

/*
 * Names corresponding to zone_status_t values in sys/zone.h
 */
char *zone_status_names[] = {
        "uninitialized",        /* ZONE_IS_UNINITIALIZED */
        "initialized",          /* ZONE_IS_INITIALIZED */
        "ready",                /* ZONE_IS_READY */
        "booting",              /* ZONE_IS_BOOTING */
        "running",              /* ZONE_IS_RUNNING */
        "shutting_down",        /* ZONE_IS_SHUTTING_DOWN */
        "empty",                /* ZONE_IS_EMPTY */
        "down",                 /* ZONE_IS_DOWN */
        "dying",                /* ZONE_IS_DYING */
        "dead"                  /* ZONE_IS_DEAD */
};

static int
zid_lookup_cb(uintptr_t addr, const zone_t *zone, void *arg)
{
        zoneid_t zid = *(uintptr_t *)arg;
        if (zone->zone_id == zid)
                mdb_printf("%p\n", addr);

        return (WALK_NEXT);
}

/*ARGSUSED*/
int
zid2zone(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        if (!(flags & DCMD_ADDRSPEC) || argc != 0)
                return (DCMD_USAGE);

        if (mdb_walk("zone", (mdb_walk_cb_t)zid_lookup_cb, &addr) == -1) {
                mdb_warn("failed to walk zone");
                return (DCMD_ERR);
        }

        return (DCMD_OK);
}

int
zoneprt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        zone_t zn;
        char name[ZONE_NAMELEN];
        char path[ZONE_PATHLEN];
        int len;
        uint_t vopt_given;
        uint_t ropt_given;

        if (argc > 2)
                return (DCMD_USAGE);

        if (!(flags & DCMD_ADDRSPEC)) {
                if (mdb_walk_dcmd("zone", "zone", argc, argv) == -1) {
                        mdb_warn("can't walk zones");
                        return (DCMD_ERR);
                }
                return (DCMD_OK);
        }

        /*
         * Get the optional -r (reference counts) and -v (verbose output)
         * arguments.
         */
        vopt_given = FALSE;
        ropt_given = FALSE;
        if (argc > 0 && mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE,
            &vopt_given, 'r', MDB_OPT_SETBITS, TRUE, &ropt_given, NULL) != argc)
                return (DCMD_USAGE);

        /*
         * -v can only be specified with -r.
         */
        if (vopt_given == TRUE && ropt_given == FALSE)
                return (DCMD_USAGE);

        /*
         * Print a table header, if necessary.
         */
        if (DCMD_HDRSPEC(flags)) {
                if (ropt_given == FALSE)
                        mdb_printf("%<u>%?s %6s %-13s %-20s %-s%</u>\n",
                            "ADDR", "ID", "STATUS", "NAME", "PATH");
                else
                        mdb_printf("%<u>%?s %6s %10s %10s %-20s%</u>\n",
                            "ADDR", "ID", "REFS", "CREFS", "NAME");
        }

        /*
         * Read the zone_t structure at the given address and read its name.
         */
        if (mdb_vread(&zn, sizeof (zone_t), addr) == -1) {
                mdb_warn("can't read zone_t structure at %p", addr);
                return (DCMD_ERR);
        }
        len = mdb_readstr(name, ZONE_NAMELEN, (uintptr_t)zn.zone_name);
        if (len > 0) {
                if (len == ZONE_NAMELEN)
                        (void) strcpy(&name[len - 4], "...");
        } else {
                (void) strcpy(name, "??");
        }

        if (ropt_given == FALSE) {
                char *statusp;

                /*
                 * Default display
                 * Fetch the zone's path and print the results.
                 */
                len = mdb_readstr(path, ZONE_PATHLEN,
                    (uintptr_t)zn.zone_rootpath);
                if (len > 0) {
                        if (len == ZONE_PATHLEN)
                                (void) strcpy(&path[len - 4], "...");
                } else {
                        (void) strcpy(path, "??");
                }
                if (zn.zone_status >= ZONE_IS_UNINITIALIZED && zn.zone_status <=
                    ZONE_IS_DEAD)
                        statusp = zone_status_names[zn.zone_status];
                else
                        statusp = "???";
                mdb_printf("%0?p %6d %-13s %-20s %s\n", addr, zn.zone_id,
                    statusp, name, path);
        } else {
                /*
                 * Display the zone's reference counts.
                 * Display the zone's subsystem-specific reference counts if
                 * the user specified the '-v' option.
                 */
                mdb_printf("%0?p %6d %10u %10u %-20s\n", addr, zn.zone_id,
                    zn.zone_ref, zn.zone_cred_ref, name);
                if (vopt_given == TRUE) {
                        GElf_Sym subsys_names_sym;
                        uintptr_t **zone_ref_subsys_names;
                        uint_t num_subsys;
                        uint_t n;

                        /*
                         * Read zone_ref_subsys_names from the kernel image.
                         */
                        if (mdb_lookup_by_name("zone_ref_subsys_names",
                            &subsys_names_sym) != 0) {
                                mdb_warn("can't find zone_ref_subsys_names");
                                return (DCMD_ERR);
                        }
                        if (subsys_names_sym.st_size != ZONE_REF_NUM_SUBSYS *
                            sizeof (char *)) {
                                mdb_warn("number of subsystems in target "
                                    "differs from what mdb expects (mismatched"
                                    " kernel versions?)");
                                if (subsys_names_sym.st_size <
                                    ZONE_REF_NUM_SUBSYS * sizeof (char *))
                                        num_subsys = subsys_names_sym.st_size /
                                            sizeof (char *);
                                else
                                        num_subsys = ZONE_REF_NUM_SUBSYS;
                        } else {
                                num_subsys = ZONE_REF_NUM_SUBSYS;
                        }
                        if ((zone_ref_subsys_names = mdb_alloc(
                            subsys_names_sym.st_size, UM_GC)) == NULL) {
                                mdb_warn("out of memory");
                                return (DCMD_ERR);
                        }
                        if (mdb_readvar(zone_ref_subsys_names,
                            "zone_ref_subsys_names") == -1) {
                                mdb_warn("can't find zone_ref_subsys_names");
                                return (DCMD_ERR);
                        }

                        /*
                         * Display each subsystem's reference count if it's
                         * nonzero.
                         */
                        mdb_inc_indent(7);
                        for (n = 0; n < num_subsys; ++n) {
                                char subsys_name[16];

                                /*
                                 * Skip subsystems lacking outstanding
                                 * references.
                                 */
                                if (zn.zone_subsys_ref[n] == 0)
                                        continue;

                                /*
                                 * Each subsystem's name must be read from
                                 * the target's image.
                                 */
                                if (mdb_readstr(subsys_name,
                                    sizeof (subsys_name),
                                    (uintptr_t)zone_ref_subsys_names[n]) ==
                                    -1) {
                                        mdb_warn("unable to read subsystem name"
                                            " from zone_ref_subsys_names[%u]",
                                            n);
                                        return (DCMD_ERR);
                                }
                                mdb_printf("%15s: %10u\n", subsys_name,
                                    zn.zone_subsys_ref[n]);
                        }
                        mdb_dec_indent(7);
                }
        }
        return (DCMD_OK);
}

int
zone_walk_init(mdb_walk_state_t *wsp)
{
        GElf_Sym sym;

        if (wsp->walk_addr == 0) {
                if (mdb_lookup_by_name("zone_active", &sym) == -1) {
                        mdb_warn("failed to find 'zone_active'");
                        return (WALK_ERR);
                }
                wsp->walk_addr = (uintptr_t)sym.st_value;
        }
        if (mdb_layered_walk("list", wsp) == -1) {
                mdb_warn("couldn't walk 'list'");
                return (WALK_ERR);
        }
        return (WALK_NEXT);
}

int
zone_walk_step(mdb_walk_state_t *wsp)
{
        return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer,
            wsp->walk_cbdata));
}

int
zsd_walk_init(mdb_walk_state_t *wsp)
{
        if (wsp->walk_addr == 0) {
                mdb_warn("global walk not supported\n");
                return (WALK_ERR);
        }
        wsp->walk_addr += offsetof(struct zone, zone_zsd);
        if (mdb_layered_walk("list", wsp) == -1) {
                mdb_warn("couldn't walk 'list'");
                return (WALK_ERR);
        }
        return (WALK_NEXT);
}

int
zsd_walk_step(mdb_walk_state_t *wsp)
{
        return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer,
            wsp->walk_cbdata));
}

/*
 * Helper structure used when walking ZSD entries via zsd().
 */
struct zsd_cb_data {
        uint_t          keygiven;       /* Was a key specified (are we */
                                        /* searching for a specific ZSD */
                                        /* entry)? */
        zone_key_t      key;            /* Key of ZSD for which we're looking */
        uint_t          found;          /* Was the specific ZSD entry found? */
        uint_t          voptgiven;      /* Display verbose information? */
};

/*
 * Helper function for zsd() that displays information from a single ZSD struct.
 * 'datap' must point to a valid zsd_cb_data struct.
 */
/* ARGSUSED */
static int
zsd_print(uintptr_t addrp, const void * datap, void * privatep)
{
        struct zsd_entry entry;
        struct zsd_cb_data *cbdp;

        if (mdb_vread(&entry, sizeof (entry), addrp) == -1) {
                mdb_warn("couldn't read zsd_entry at %p", addrp);
                return (WALK_ERR);
        }
        cbdp = (struct zsd_cb_data *)privatep;

        /*
         * Are we looking for a single entry specified by a key?  Then make sure
         * that the current ZSD's key is what we're looking for.
         */
        if (cbdp->keygiven == TRUE && cbdp->key != entry.zsd_key)
                return (WALK_NEXT);

        mdb_printf("%?x %0?p %8x\n", entry.zsd_key, entry.zsd_data,
            entry.zsd_flags);
        if (cbdp->voptgiven == TRUE)
                mdb_printf("    Create CB:   %a\n    Shutdown CB: %a\n"
                    "    Destroy CB:  %a\n", entry.zsd_create,
                    entry.zsd_shutdown, entry.zsd_destroy);
        if (cbdp->keygiven == TRUE) {
                cbdp->found = TRUE;
                return (WALK_DONE);
        }
        return (WALK_NEXT);
}

int
zsd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        zone_t zone;
        const mdb_arg_t *argp;
        int argcindex;
        struct zsd_cb_data cbd;
        char name[ZONE_NAMELEN];
        int len;

        /*
         * Walk all zones if necessary.
         */
        if (argc > 2)
                return (DCMD_USAGE);
        if ((flags & DCMD_ADDRSPEC) == 0) {
                if (mdb_walk_dcmd("zone", "zsd", argc, argv) == -1) {
                        mdb_warn("failed to walk zone\n");
                        return (DCMD_ERR);
                }
                return (DCMD_OK);
        }

        /*
         * Make sure a zone_t can be read from the specified address.
         */
        if (mdb_vread(&zone, sizeof (zone), addr) == -1) {
                mdb_warn("couldn't read zone_t at %p", (void *)addr);
                return (DCMD_ERR);
        }

        /*
         * Get the optional arguments (key or -v or both).  Note that
         * mdb_getopts() will not parse a key argument because it is not
         * preceded by an option letter.  We'll get around this by requiring
         * that all options precede the optional key argument.
         */
        cbd.keygiven = FALSE;
        cbd.voptgiven = FALSE;
        if (argc > 0 && (argcindex = mdb_getopts(argc, argv, 'v',
            MDB_OPT_SETBITS, TRUE, &cbd.voptgiven, NULL)) != argc) {
                /*
                 * No options may appear after the key.
                 */
                if (argcindex != argc - 1)
                        return (DCMD_USAGE);

                /*
                 * The missed argument should be a key.
                 */
                argp = &argv[argcindex];
                cbd.key = (zone_key_t)mdb_argtoull(argp);
                cbd.keygiven = TRUE;
                cbd.found = FALSE;
        }

        /*
         * Prepare to output the specified zone's ZSD information.
         */
        if (DCMD_HDRSPEC(flags))
                mdb_printf("%<u>%-20s %?s %?s %8s%</u>\n", "ZONE", "KEY",
                    "VALUE", "FLAGS");
        len = mdb_readstr(name, ZONE_NAMELEN, (uintptr_t)zone.zone_name);
        if (len > 0) {
                if (len == ZONE_NAMELEN)
                        (void) strcpy(&name[len - 4], "...");
        } else {
                (void) strcpy(name, "??");
        }
        mdb_printf("%-20s ", name);

        /*
         * Display the requested ZSD entries.
         */
        mdb_inc_indent(21);
        if (mdb_pwalk("zsd", zsd_print, &cbd, addr) != 0) {
                mdb_warn("failed to walk zsd\n");
                mdb_dec_indent(21);
                return (DCMD_ERR);
        }
        if (cbd.keygiven == TRUE && cbd.found == FALSE) {
                mdb_printf("no corresponding ZSD entry found\n");
                mdb_dec_indent(21);
                return (DCMD_ERR);
        }
        mdb_dec_indent(21);
        return (DCMD_OK);
}