root/usr/src/cmd/fm/fmadm/common/faulty.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 2020 Joyent, Inc.
 */

#include <sys/types.h>
#include <fmadm.h>
#include <errno.h>
#include <limits.h>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <fm/fmd_log.h>
#include <sys/fm/protocol.h>
#include <fm/libtopo.h>
#include <fm/fmd_adm.h>
#include <fm/fmd_msg.h>
#include <dlfcn.h>
#include <sys/systeminfo.h>
#include <sys/utsname.h>
#include <libintl.h>
#include <locale.h>
#include <sys/smbios.h>
#include <libdevinfo.h>
#include <stdlib.h>
#include <stddef.h>

/*
 * Fault records are added to catalog by calling add_fault_record_to_catalog()
 * records are stored in order of importance to the system.
 * If -g flag is set or not_suppressed is not set and the class fru, fault,
 * type are the same then details are merged into an existing record, with uuid
 * records are stored in time order.
 * For each record information is extracted from nvlist and merged into linked
 * list each is checked for identical records for which percentage certainty are
 * added together.
 * print_catalog() is called to print out catalog and release external resources
 *
 *                         /---------------\
 *      status_rec_list -> |               | -|
 *                         \---------------/
 *                                \/
 *                         /---------------\    /-------\    /-------\
 *      status_fru_list    | status_record | -> | uurec | -> | uurec | -|
 *            \/           |               | |- |       | <- |       |
 *      /-------------\    |               |    \-------/    \-------/
 *      |             | -> |               |       \/           \/
 *      \-------------/    |               |    /-------\    /-------\
 *            \/           |               | -> | asru  | -> | asru  |
 *            ---          |               |    |       | <- |       |
 *                         |               |    \-------/    \-------/
 *      status_asru_list   |  class        |
 *            \/           |  resource     |    /-------\    /-------\
 *      /-------------\    |  fru          | -> | list  | -> | list  |
 *      |             | -> |  serial       |    |       | <- |       |
 *      \-------------/    |               |    \-------/    \-------/
 *            \/           \---------------/
 *            ---               \/    /\
 *                         /---------------\
 *                         | status_record |
 *                         \---------------/
 *
 * Fmadm faulty takes a number of options which affect the format of the
 * output displayed. By default, the display reports the FRU and ASRU along
 * with other information on per-case basis as in the example below.
 *
 * --------------- ------------------------------------  -------------- -------
 * TIME            EVENT-ID                              MSG-ID         SEVERITY
 * --------------- ------------------------------------  -------------- -------
 * Sep 21 10:01:36 d482f935-5c8f-e9ab-9f25-d0aaafec1e6c  AMD-8000-2F    Major
 *
 * Fault class  : fault.memory.dimm_sb
 * Affects      : mem:///motherboard=0/chip=0/memory-controller=0/dimm=0/rank=0
 *                  faulted but still in service
 * FRU          : "CPU 0 DIMM 0" (hc://.../memory-controller=0/dimm=0)
 *                  faulty
 *
 * Description  : The number of errors associated with this memory module has
 *              exceeded acceptable levels.  Refer to
 *              http://illumos.org/msg/AMD-8000-2F for more information.
 *
 * Response     : Pages of memory associated with this memory module are being
 *              removed from service as errors are reported.
 *
 * Impact       : Total system memory capacity will be reduced as pages are
 *              retired.
 *
 * Action       : Schedule a repair procedure to replace the affected memory
 *              module.  Use fmdump -v -u <EVENT_ID> to identify the module.
 *
 * The -v flag is similar, but adds some additonal information such as the
 * resource. The -s flag is also similar but just gives the top line summary.
 * All these options (ie without the -f or -r flags) use the print_catalog()
 * function to do the display.
 *
 * The -f flag changes the output so that it appears sorted on a per-fru basis.
 * The output is somewhat cut down compared to the default output. If -f is
 * used, then print_fru() is used to print the output.
 *
 * -----------------------------------------------------------------------------
 * "SLOT 2" (hc://.../hostbridge=3/pciexrc=3/pciexbus=4/pciexdev=0) faulty
 * 5ca4aeb3-36...f6be-c2e8166dc484 2 suspects in this FRU total certainty 100%
 *
 * Description  : A problem was detected for a PCI device.
 *              Refer to http://illumos.org/msg/PCI-8000-7J
 *              for more information.
 *
 * Response     : One or more device instances may be disabled
 *
 * Impact       : Possible loss of services provided by the device instances
 *              associated with this fault
 *
 * Action       : Schedule a repair procedure to replace the affected device.
 *              Use fmdump -v -u <EVENT_ID> to identify the device or contact
 *              Sun for support.
 *
 * The -r flag changes the output so that it appears sorted on a per-asru basis.
 * The output is very much cut down compared to the default output, just giving
 * the asru fmri and state. Here print_asru() is used to print the output.
 *
 * mem:///motherboard=0/chip=0/memory-controller=0/dimm=0/rank=0        degraded
 *
 * For all fmadm faulty options, the sequence of events is
 *
 * 1) Walk through all the cases in the system using fmd_adm_case_iter() and
 * for each case call dfault_rec(). This will call add_fault_record_to_catalog()
 * This will extract the data from the nvlist and call catalog_new_record() to
 * save the data away in various linked lists in the catalogue.
 *
 * 2) Once this is done, the data can be supplemented by using
 * fmd_adm_rsrc_iter(). However this is now only necessary for the -i option.
 *
 * 3) Finally print_catalog(), print_fru() or print_asru() are called as
 * appropriate to display the information from the catalogue sorted in the
 * requested way.
 *
 */

typedef struct name_list {
        struct name_list *next;
        struct name_list *prev;
        char *name;
        uint8_t pct;
        uint8_t max_pct;
        ushort_t count;
        int status;
        char *label;
} name_list_t;

typedef struct ari_list {
        char *ari_uuid;
        struct ari_list *next;
} ari_list_t;

typedef struct uurec {
        struct uurec *next;
        struct uurec *prev;
        char *uuid;
        ari_list_t *ari_uuid_list;
        name_list_t *asru;
        uint64_t sec;
        nvlist_t *event;
} uurec_t;

typedef struct uurec_select {
        struct uurec_select *next;
        char *uuid;
} uurec_select_t;

typedef struct host_id {
        char *chassis;
        char *server;
        char *platform;
        char *domain;
        char *product_sn;
} hostid_t;

typedef struct host_id_list {
        hostid_t hostid;
        struct host_id_list *next;
} host_id_list_t;

typedef struct status_record {
        hostid_t *host;
        int nrecs;
        uurec_t *uurec;
        char *severity;                 /* in C locale */
        char *msgid;
        name_list_t *class;
        name_list_t *resource;
        name_list_t *asru;
        name_list_t *fru;
        name_list_t *serial;
        uint8_t not_suppressed;
        uint8_t injected;
} status_record_t;

typedef struct sr_list {
        struct sr_list *next;
        struct sr_list *prev;
        struct status_record *status_record;
} sr_list_t;

typedef struct resource_list {
        struct resource_list *next;
        struct resource_list *prev;
        sr_list_t *status_rec_list;
        char *resource;
        uint8_t not_suppressed;
        uint8_t injected;
        uint8_t max_pct;
} resource_list_t;

sr_list_t *status_rec_list;
resource_list_t *status_fru_list;
resource_list_t *status_asru_list;

static int max_display;
static int max_fault = 0;
static topo_hdl_t *topo_handle;
static host_id_list_t *host_list;
static int n_server;
static int opt_g;
static fmd_msg_hdl_t *fmadm_msghdl = NULL; /* handle for libfmd_msg calls */

static char *
format_date(char *buf, size_t len, uint64_t sec)
{
        if (sec > LONG_MAX) {
                (void) fprintf(stderr,
                    "record time is too large for 32-bit utility\n");
                (void) snprintf(buf, len, "0x%llx", sec);
        } else {
                time_t tod = (time_t)sec;
                time_t now = time(NULL);
                if (tod > now+60 ||
                    tod < now - 6L*30L*24L*60L*60L) { /* 6 months ago */
                        (void) strftime(buf, len, "%b %d %Y    ",
                            localtime(&tod));
                } else {
                        (void) strftime(buf, len, "%b %d %T", localtime(&tod));
                }
        }

        return (buf);
}

static hostid_t *
find_hostid_in_list(char *platform, char *chassis, char *server, char *domain,
    char *product_sn)
{
        hostid_t *rt = NULL;
        host_id_list_t *hostp;

        if (platform == NULL)
                platform = "-";
        if (server == NULL)
                server = "-";
        hostp = host_list;
        while (hostp) {
                if (hostp->hostid.platform &&
                    strcmp(hostp->hostid.platform, platform) == 0 &&
                    hostp->hostid.server &&
                    strcmp(hostp->hostid.server, server) == 0 &&
                    (chassis == NULL || hostp->hostid.chassis == NULL ||
                    strcmp(chassis, hostp->hostid.chassis) == 0) &&
                    (product_sn == NULL || hostp->hostid.product_sn == NULL ||
                    strcmp(product_sn, hostp->hostid.product_sn) == 0) &&
                    (domain == NULL || hostp->hostid.domain == NULL ||
                    strcmp(domain, hostp->hostid.domain) == 0)) {
                        rt = &hostp->hostid;
                        break;
                }
                hostp = hostp->next;
        }
        if (rt == NULL) {
                hostp = malloc(sizeof (host_id_list_t));
                hostp->hostid.platform = strdup(platform);
                hostp->hostid.product_sn =
                    product_sn ? strdup(product_sn) : NULL;
                hostp->hostid.server = strdup(server);
                hostp->hostid.chassis = chassis ? strdup(chassis) : NULL;
                hostp->hostid.domain = domain ? strdup(domain) : NULL;
                hostp->next = host_list;
                host_list = hostp;
                rt = &hostp->hostid;
                n_server++;
        }
        return (rt);
}

static hostid_t *
find_hostid(nvlist_t *nvl)
{
        char *platform = NULL, *chassis = NULL, *server = NULL, *domain = NULL;
        char *product_sn = NULL;
        nvlist_t *auth, *fmri;
        hostid_t *rt = NULL;

        if (nvlist_lookup_nvlist(nvl, FM_SUSPECT_DE, &fmri) == 0 &&
            nvlist_lookup_nvlist(fmri, FM_FMRI_AUTHORITY, &auth) == 0) {
                (void) nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT,
                    &platform);
                (void) nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT_SN,
                    &product_sn);
                (void) nvlist_lookup_string(auth, FM_FMRI_AUTH_SERVER, &server);
                (void) nvlist_lookup_string(auth, FM_FMRI_AUTH_CHASSIS,
                    &chassis);
                (void) nvlist_lookup_string(auth, FM_FMRI_AUTH_DOMAIN, &domain);
                rt = find_hostid_in_list(platform, chassis, server,
                    domain, product_sn);
        }
        return (rt);
}

static char *
get_nvl2str_topo(nvlist_t *nvl)
{
        char *name = NULL;
        char *tname;
        int err;
        char *scheme = NULL;
        char *mod_name = NULL;
        char buf[128];

        if (topo_handle == NULL)
                topo_handle = topo_open(TOPO_VERSION, 0, &err);
        if (topo_fmri_nvl2str(topo_handle, nvl, &tname, &err) == 0) {
                name = strdup(tname);
                topo_hdl_strfree(topo_handle, tname);
        } else {
                (void) nvlist_lookup_string(nvl, FM_FMRI_SCHEME, &scheme);
                (void) nvlist_lookup_string(nvl, FM_FMRI_MOD_NAME, &mod_name);
                if (scheme && strcmp(scheme, FM_FMRI_SCHEME_FMD) == 0 &&
                    mod_name) {
                        (void) snprintf(buf, sizeof (buf), "%s:///module/%s",
                            scheme, mod_name);
                        name = strdup(buf);
                }
        }
        return (name);
}

static int
set_priority(char *s)
{
        int rt = 0;

        if (s) {
                if (strcmp(s, "Minor") == 0)
                        rt = 1;
                else if (strcmp(s, "Major") == 0)
                        rt = 10;
                else if (strcmp(s, "Critical") == 0)
                        rt = 100;
        }
        return (rt);
}

static int
cmp_priority(char *s1, char *s2, uint64_t t1, uint64_t t2, uint8_t p1,
    uint8_t p2)
{
        int r1, r2;
        int rt;

        r1 = set_priority(s1);
        r2 = set_priority(s2);
        rt = r1 - r2;
        if (rt == 0) {
                if (t1 > t2)
                        rt = 1;
                else if (t1 < t2)
                        rt = -1;
                else
                        rt = p1 - p2;
        }
        return (rt);
}

/*
 * merge two lists into one, by comparing enties in new and moving into list if
 * name is not there or free off memory for names which are already there
 * add_pct indicates if pct is the sum or highest pct
 */
static name_list_t *
merge_name_list(name_list_t **list, name_list_t *new, int add_pct)
{
        name_list_t *lp, *np, *sp, *rt = NULL;
        int max_pct;

        rt = *list;
        np = new;
        while (np) {
                lp = *list;
                while (lp) {
                        if (strcmp(lp->name, np->name) == 0)
                                break;
                        lp = lp->next;
                        if (lp == *list)
                                lp = NULL;
                }
                if (np->next == new)
                        sp = NULL;
                else
                        sp = np->next;
                if (lp) {
                        lp->status |= (np->status & FM_SUSPECT_FAULTY);
                        if (add_pct) {
                                lp->pct += np->pct;
                                lp->count += np->count;
                        } else if (np->pct > lp->pct) {
                                lp->pct = np->pct;
                        }
                        max_pct = np->max_pct;
                        if (np->label)
                                free(np->label);
                        free(np->name);
                        free(np);
                        np = NULL;
                        if (max_pct > lp->max_pct) {
                                lp->max_pct = max_pct;
                                if (lp->max_pct > lp->prev->max_pct &&
                                    lp != *list) {
                                        lp->prev->next = lp->next;
                                        lp->next->prev = lp->prev;
                                        np = lp;
                                }
                        }
                }
                if (np) {
                        lp = *list;
                        if (lp) {
                                if (np->max_pct > lp->max_pct) {
                                        np->next = lp;
                                        np->prev = lp->prev;
                                        lp->prev->next = np;
                                        lp->prev = np;
                                        *list = np;
                                        rt = np;
                                } else {
                                        lp = lp->next;
                                        while (lp != *list &&
                                            np->max_pct < lp->max_pct) {
                                                lp = lp->next;
                                        }
                                        np->next = lp;
                                        np->prev = lp->prev;
                                        lp->prev->next = np;
                                        lp->prev = np;
                                }
                        } else {
                                *list = np;
                                np->next = np;
                                np->prev = np;
                                rt = np;
                        }
                }
                np = sp;
        }
        return (rt);
}

static name_list_t *
alloc_name_list(char *name, uint8_t pct)
{
        name_list_t *nlp;

        nlp = malloc(sizeof (*nlp));
        nlp->name = strdup(name);
        nlp->pct = pct;
        nlp->max_pct = pct;
        nlp->count = 1;
        nlp->next = nlp;
        nlp->prev = nlp;
        nlp->status = 0;
        nlp->label = NULL;
        return (nlp);
}

static status_record_t *
new_record_init(uurec_t *uurec_p, char *msgid, name_list_t *class,
    name_list_t *fru, name_list_t *asru, name_list_t *resource,
    name_list_t *serial, boolean_t not_suppressed,
    hostid_t *hostid, boolean_t injected)
{
        status_record_t *status_rec_p;

        status_rec_p = (status_record_t *)malloc(sizeof (status_record_t));
        status_rec_p->nrecs = 1;
        status_rec_p->host = hostid;
        status_rec_p->uurec = uurec_p;
        uurec_p->next = NULL;
        uurec_p->prev = NULL;
        uurec_p->asru = asru;
        if ((status_rec_p->severity = fmd_msg_getitem_id(fmadm_msghdl, NULL,
            msgid, FMD_MSG_ITEM_SEVERITY)) == NULL)
                status_rec_p->severity = strdup("unknown");
        status_rec_p->class = class;
        status_rec_p->fru = fru;
        status_rec_p->asru = asru;
        status_rec_p->resource = resource;
        status_rec_p->serial = serial;
        status_rec_p->msgid = strdup(msgid);
        status_rec_p->not_suppressed = not_suppressed;
        status_rec_p->injected = injected;
        return (status_rec_p);
}

/*
 * add record to given list maintaining order higher priority first.
 */
static void
add_rec_list(status_record_t *status_rec_p, sr_list_t **list_pp)
{
        sr_list_t *tp, *np, *sp;
        int order;
        uint64_t sec;

        np = malloc(sizeof (sr_list_t));
        np->status_record = status_rec_p;
        sec = status_rec_p->uurec->sec;
        if ((sp = *list_pp) == NULL) {
                *list_pp = np;
                np->next = np;
                np->prev = np;
        } else {
                /* insert new record in front of lower priority */
                tp = sp;
                order = cmp_priority(status_rec_p->severity,
                    sp->status_record->severity, sec,
                    tp->status_record->uurec->sec, 0, 0);
                if (order > 0) {
                        *list_pp = np;
                } else {
                        tp = sp->next;
                        while (tp != sp &&
                            cmp_priority(status_rec_p->severity,
                            tp->status_record->severity, sec,
                            tp->status_record->uurec->sec, 0, 0)) {
                                tp = tp->next;
                        }
                }
                np->next = tp;
                np->prev = tp->prev;
                tp->prev->next = np;
                tp->prev = np;
        }
}

static void
add_resource(status_record_t *status_rec_p, resource_list_t **rp,
    resource_list_t *np)
{
        int order;
        uint64_t sec;
        resource_list_t *sp, *tp;
        status_record_t *srp;
        char *severity = status_rec_p->severity;

        add_rec_list(status_rec_p, &np->status_rec_list);
        if ((sp = *rp) == NULL) {
                np->next = np;
                np->prev = np;
                *rp = np;
        } else {
                /*
                 * insert new record in front of lower priority
                 */
                tp = sp->next;
                srp = sp->status_rec_list->status_record;
                sec = status_rec_p->uurec->sec;
                order = cmp_priority(severity, srp->severity, sec,
                    srp->uurec->sec, np->max_pct, sp->max_pct);
                if (order > 0) {
                        *rp = np;
                } else {
                        srp = tp->status_rec_list->status_record;
                        while (tp != sp &&
                            cmp_priority(severity, srp->severity, sec,
                            srp->uurec->sec, np->max_pct, sp->max_pct) < 0) {
                                tp = tp->next;
                                srp = tp->status_rec_list->status_record;
                        }
                }
                np->next = tp;
                np->prev = tp->prev;
                tp->prev->next = np;
                tp->prev = np;
        }
}

static void
add_resource_list(status_record_t *status_rec_p, name_list_t *fp,
    resource_list_t **rpp)
{
        int order;
        resource_list_t *np, *end;
        status_record_t *srp;

        np = *rpp;
        end = np;
        while (np) {
                if (strcmp(fp->name, np->resource) == 0) {
                        np->not_suppressed |= status_rec_p->not_suppressed;
                        np->injected |= status_rec_p->injected;
                        srp = np->status_rec_list->status_record;
                        order = cmp_priority(status_rec_p->severity,
                            srp->severity, status_rec_p->uurec->sec,
                            srp->uurec->sec, fp->max_pct, np->max_pct);
                        if (order > 0 && np != end) {
                                /*
                                 * remove from list and add again using
                                 * new priority
                                 */
                                np->prev->next = np->next;
                                np->next->prev = np->prev;
                                add_resource(status_rec_p,
                                    rpp, np);
                        } else {
                                add_rec_list(status_rec_p,
                                    &np->status_rec_list);
                        }
                        break;
                }
                np = np->next;
                if (np == end) {
                        np = NULL;
                        break;
                }
        }
        if (np == NULL) {
                np = malloc(sizeof (resource_list_t));
                np->resource = fp->name;
                np->not_suppressed = status_rec_p->not_suppressed;
                np->injected = status_rec_p->injected;
                np->status_rec_list = NULL;
                np->max_pct = fp->max_pct;
                add_resource(status_rec_p, rpp, np);
        }
}

static void
add_list(status_record_t *status_rec_p, name_list_t *listp,
    resource_list_t **glistp)
{
        name_list_t *fp, *end;

        fp = listp;
        end = fp;
        while (fp) {
                add_resource_list(status_rec_p, fp, glistp);
                fp = fp->next;
                if (fp == end)
                        break;
        }
}

/*
 * add record to rec, fru and asru lists.
 */
static void
catalog_new_record(uurec_t *uurec_p, char *msgid, name_list_t *class,
    name_list_t *fru, name_list_t *asru, name_list_t *resource,
    name_list_t *serial, boolean_t not_suppressed,
    hostid_t *hostid, boolean_t injected, boolean_t dummy_fru)
{
        status_record_t *status_rec_p;

        status_rec_p = new_record_init(uurec_p, msgid, class, fru, asru,
            resource, serial, not_suppressed, hostid, injected);
        add_rec_list(status_rec_p, &status_rec_list);
        if (status_rec_p->fru && !dummy_fru)
                add_list(status_rec_p, status_rec_p->fru, &status_fru_list);
        if (status_rec_p->asru)
                add_list(status_rec_p, status_rec_p->asru, &status_asru_list);
}

static void
get_serial_no(nvlist_t *nvl, name_list_t **serial_p, uint8_t pct)
{
        char *name;
        char *serial = NULL;
        char **lserial = NULL;
        uint64_t serint;
        name_list_t *nlp;
        int j;
        uint_t nelem;
        char buf[64];

        if (nvlist_lookup_string(nvl, FM_FMRI_SCHEME, &name) == 0) {
                if (strcmp(name, FM_FMRI_SCHEME_CPU) == 0) {
                        if (nvlist_lookup_uint64(nvl, FM_FMRI_CPU_SERIAL_ID,
                            &serint) == 0) {
                                (void) snprintf(buf, sizeof (buf), "%llX",
                                    serint);
                                nlp = alloc_name_list(buf, pct);
                                (void) merge_name_list(serial_p, nlp, 1);
                        }
                } else if (strcmp(name, FM_FMRI_SCHEME_MEM) == 0) {
                        if (nvlist_lookup_string_array(nvl,
                            FM_FMRI_MEM_SERIAL_ID, &lserial, &nelem) == 0) {
                                nlp = alloc_name_list(lserial[0], pct);
                                for (j = 1; j < nelem; j++) {
                                        name_list_t *n1lp;
                                        n1lp = alloc_name_list(lserial[j], pct);
                                        (void) merge_name_list(&nlp, n1lp, 1);
                                }
                                (void) merge_name_list(serial_p, nlp, 1);
                        }
                } else if (strcmp(name, FM_FMRI_SCHEME_HC) == 0) {
                        if (nvlist_lookup_string(nvl, FM_FMRI_HC_SERIAL_ID,
                            &serial) == 0) {
                                nlp = alloc_name_list(serial, pct);
                                (void) merge_name_list(serial_p, nlp, 1);
                        }
                }
        }
}

static void
extract_record_info(nvlist_t *nvl, name_list_t **class_p,
    name_list_t **fru_p, name_list_t **serial_p, name_list_t **resource_p,
    name_list_t **asru_p, boolean_t *dummy_fru, uint8_t status)
{
        nvlist_t *lfru, *lasru, *rsrc;
        name_list_t *nlp;
        char *name;
        uint8_t lpct = 0;
        char *lclass = NULL;
        char *label;

        (void) nvlist_lookup_uint8(nvl, FM_FAULT_CERTAINTY, &lpct);
        if (nvlist_lookup_string(nvl, FM_CLASS, &lclass) == 0) {
                nlp = alloc_name_list(lclass, lpct);
                (void) merge_name_list(class_p, nlp, 1);
        }
        if (nvlist_lookup_nvlist(nvl, FM_FAULT_FRU, &lfru) == 0) {
                name = get_nvl2str_topo(lfru);
                if (name != NULL) {
                        nlp = alloc_name_list(name, lpct);
                        nlp->status = status & ~(FM_SUSPECT_UNUSABLE |
                            FM_SUSPECT_DEGRADED);
                        free(name);
                        if (nvlist_lookup_string(nvl, FM_FAULT_LOCATION,
                            &label) == 0)
                                nlp->label = strdup(label);
                        (void) merge_name_list(fru_p, nlp, 1);
                }
                get_serial_no(lfru, serial_p, lpct);
        } else if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &rsrc) != 0) {
                /*
                 * No FRU or resource. But we want to display the repair status
                 * somehow, so create a dummy FRU field.
                 */
                *dummy_fru = 1;
                nlp = alloc_name_list(dgettext("FMD", "None"), lpct);
                nlp->status = status & ~(FM_SUSPECT_UNUSABLE |
                    FM_SUSPECT_DEGRADED);
                (void) merge_name_list(fru_p, nlp, 1);
        }
        if (nvlist_lookup_nvlist(nvl, FM_FAULT_ASRU, &lasru) == 0) {
                name = get_nvl2str_topo(lasru);
                if (name != NULL) {
                        nlp = alloc_name_list(name, lpct);
                        nlp->status = status & ~(FM_SUSPECT_NOT_PRESENT |
                            FM_SUSPECT_REPAIRED | FM_SUSPECT_REPLACED |
                            FM_SUSPECT_ACQUITTED);
                        free(name);
                        (void) merge_name_list(asru_p, nlp, 1);
                }
                get_serial_no(lasru, serial_p, lpct);
        }
        if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &rsrc) == 0) {
                name = get_nvl2str_topo(rsrc);
                if (name != NULL) {
                        nlp = alloc_name_list(name, lpct);
                        nlp->status = status;
                        free(name);
                        if (nvlist_lookup_string(nvl, FM_FAULT_LOCATION,
                            &label) == 0)
                                nlp->label = strdup(label);
                        (void) merge_name_list(resource_p, nlp, 1);
                }
        }
}

static void
add_fault_record_to_catalog(nvlist_t *nvl, uint64_t sec, char *uuid)
{
        char *msgid = "-";
        uint_t i, size = 0;
        name_list_t *class = NULL, *resource = NULL;
        name_list_t *asru = NULL, *fru = NULL, *serial = NULL;
        nvlist_t **nva;
        uint8_t *ba;
        uurec_t *uurec_p;
        hostid_t *host;
        boolean_t not_suppressed = 1;
        boolean_t any_present = 0;
        boolean_t injected = 0;
        boolean_t dummy_fru = 0;

        (void) nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &msgid);
        (void) nvlist_lookup_uint32(nvl, FM_SUSPECT_FAULT_SZ, &size);
        (void) nvlist_lookup_boolean_value(nvl, FM_SUSPECT_MESSAGE,
            &not_suppressed);
        (void) nvlist_lookup_boolean_value(nvl, FM_SUSPECT_INJECTED, &injected);

        if (size != 0) {
                (void) nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST,
                    &nva, &size);
                (void) nvlist_lookup_uint8_array(nvl, FM_SUSPECT_FAULT_STATUS,
                    &ba, &size);
                for (i = 0; i < size; i++) {
                        extract_record_info(nva[i], &class, &fru, &serial,
                            &resource, &asru, &dummy_fru, ba[i]);
                        if (!(ba[i] & FM_SUSPECT_NOT_PRESENT) &&
                            (ba[i] & FM_SUSPECT_FAULTY))
                                any_present = 1;
                }
                /*
                 * also suppress if no resources present
                 */
                if (any_present == 0)
                        not_suppressed = 0;
        }

        uurec_p = (uurec_t *)malloc(sizeof (uurec_t));
        uurec_p->uuid = strdup(uuid);
        uurec_p->sec = sec;
        uurec_p->ari_uuid_list = NULL;
        uurec_p->event = NULL;
        (void) nvlist_dup(nvl, &uurec_p->event, 0);
        host = find_hostid(nvl);
        catalog_new_record(uurec_p, msgid, class, fru, asru,
            resource, serial, not_suppressed, host, injected, dummy_fru);
}

static void
update_asru_state_in_catalog(const char *uuid, const char *ari_uuid)
{
        sr_list_t *srp;
        uurec_t *uurp;
        ari_list_t *ari_list;

        srp = status_rec_list;
        if (srp) {
                for (;;) {
                        uurp = srp->status_record->uurec;
                        while (uurp) {
                                if (strcmp(uuid, uurp->uuid) == 0) {
                                        ari_list = (ari_list_t *)
                                            malloc(sizeof (ari_list_t));
                                        ari_list->ari_uuid = strdup(ari_uuid);
                                        ari_list->next = uurp->ari_uuid_list;
                                        uurp->ari_uuid_list = ari_list;
                                        return;
                                }
                                uurp = uurp->next;
                        }
                        if (srp->next == status_rec_list)
                                break;
                        srp = srp->next;
                }
        }
}

static void
print_line(char *label, char *buf)
{
        char *cp, *ep, *wp;
        char c;
        int i;
        int lsz;
        char *padding;

        lsz = strlen(label);
        padding = malloc(lsz + 1);
        for (i = 0; i < lsz; i++)
                padding[i] = ' ';
        padding[i] = 0;
        cp = buf;
        ep = buf;
        c = *ep;
        (void) printf("\n");
        while (c) {
                i = lsz;
                wp = NULL;
                while ((c = *ep) != '\0' && (wp == NULL || i < 80)) {
                        if (c == ' ')
                                wp = ep;
                        else if (c == '\n') {
                                i = 0;
                                *ep = 0;
                                do {
                                        ep++;
                                } while ((c = *ep) != '\0' && c == ' ');
                                break;
                        }
                        ep++;
                        i++;
                }
                if (i >= 80 && wp) {
                        *wp = 0;
                        ep = wp + 1;
                        c = *ep;
                }
                (void) printf("%s%s\n", label, cp);
                cp = ep;
                label = padding;
        }
        free(padding);
}

static void
print_dict_info_line(nvlist_t *e, fmd_msg_item_t what, const char *linehdr)
{
        char *cp = fmd_msg_getitem_nv(fmadm_msghdl, NULL, e, what);

        if (cp) {
                print_line(dgettext("FMD", linehdr), cp);
                free(cp);
        }
}

static void
print_dict_info(nvlist_t *nvl)
{
        print_dict_info_line(nvl, FMD_MSG_ITEM_DESC, "Description : ");
        print_dict_info_line(nvl, FMD_MSG_ITEM_RESPONSE, "Response    : ");
        print_dict_info_line(nvl, FMD_MSG_ITEM_IMPACT, "Impact      : ");
        print_dict_info_line(nvl, FMD_MSG_ITEM_ACTION, "Action      : ");
}

static void
print_name(name_list_t *list, char *padding, int *np, int pct, int full)
{
        char *name;

        name = list->name;
        if (list->label) {
                (void) printf("%s \"%s\" (%s)", padding, list->label, name);
                *np += 1;
        } else {
                (void) printf("%s %s", padding, name);
                *np += 1;
        }
        if (list->pct && pct > 0 && pct < 100) {
                if (list->count > 1) {
                        if (full) {
                                (void) printf(" %d @ %s %d%%\n", list->count,
                                    dgettext("FMD", "max"),
                                    list->max_pct);
                        } else {
                                (void) printf(" %s %d%%\n",
                                    dgettext("FMD", "max"),
                                    list->max_pct);
                        }
                } else {
                        (void) printf(" %d%%\n", list->pct);
                }
        } else {
                (void) printf("\n");
        }
}

static void
print_asru_status(int status, char *label)
{
        char *msg = NULL;

        switch (status) {
        case 0:
                msg = dgettext("FMD", "ok and in service");
                break;
        case FM_SUSPECT_DEGRADED:
                msg = dgettext("FMD", "service degraded, "
                    "but associated components no longer faulty");
                break;
        case FM_SUSPECT_FAULTY | FM_SUSPECT_DEGRADED:
                msg = dgettext("FMD", "faulted but still "
                    "providing degraded service");
                break;
        case FM_SUSPECT_FAULTY:
                msg = dgettext("FMD", "faulted but still in service");
                break;
        case FM_SUSPECT_UNUSABLE:
                msg = dgettext("FMD", "out of service, "
                    "but associated components no longer faulty");
                break;
        case FM_SUSPECT_FAULTY | FM_SUSPECT_UNUSABLE:
                msg = dgettext("FMD", "faulted and taken out of service");
                break;
        default:
                break;
        }
        if (msg) {
                (void) printf("%s     %s\n", label, msg);
        }
}

static void
print_fru_status(int status, char *label)
{
        char *msg = NULL;

        if (status & FM_SUSPECT_NOT_PRESENT)
                msg = dgettext("FMD", "not present");
        else if (status & FM_SUSPECT_FAULTY)
                msg = dgettext("FMD", "faulty");
        else if (status & FM_SUSPECT_REPLACED)
                msg = dgettext("FMD", "replaced");
        else if (status & FM_SUSPECT_REPAIRED)
                msg = dgettext("FMD", "repair attempted");
        else if (status & FM_SUSPECT_ACQUITTED)
                msg = dgettext("FMD", "acquitted");
        else
                msg = dgettext("FMD", "removed");
        (void) printf("%s     %s\n", label, msg);
}

static void
print_rsrc_status(int status, char *label)
{
        char *msg = "";

        if (status & FM_SUSPECT_NOT_PRESENT)
                msg = dgettext("FMD", "not present");
        else if (status & FM_SUSPECT_FAULTY) {
                if (status & FM_SUSPECT_DEGRADED)
                        msg = dgettext("FMD",
                            "faulted but still providing degraded service");
                else if (status & FM_SUSPECT_UNUSABLE)
                        msg = dgettext("FMD",
                            "faulted and taken out of service");
                else
                        msg = dgettext("FMD", "faulted but still in service");
        } else if (status & FM_SUSPECT_REPLACED)
                msg = dgettext("FMD", "replaced");
        else if (status & FM_SUSPECT_REPAIRED)
                msg = dgettext("FMD", "repair attempted");
        else if (status & FM_SUSPECT_ACQUITTED)
                msg = dgettext("FMD", "acquitted");
        else
                msg = dgettext("FMD", "removed");
        (void) printf("%s     %s\n", label, msg);
}

static void
print_name_list(name_list_t *list, char *label,
    int limit, int pct, void (func1)(int, char *), int full)
{
        char *name;
        char *padding;
        int i, j, l, n;
        name_list_t *end = list;

        l = strlen(label);
        padding = malloc(l + 1);
        for (i = 0; i < l; i++)
                padding[i] = ' ';
        padding[l] = 0;
        (void) printf("%s", label);
        name = list->name;
        if (list->label)
                (void) printf(" \"%s\" (%s)", list->label, name);
        else
                (void) printf(" %s", name);
        if (list->pct && pct > 0 && pct < 100) {
                if (list->count > 1) {
                        if (full) {
                                (void) printf(" %d @ %s %d%%\n", list->count,
                                    dgettext("FMD", "max"), list->max_pct);
                        } else {
                                (void) printf(" %s %d%%\n",
                                    dgettext("FMD", "max"), list->max_pct);
                        }
                } else {
                        (void) printf(" %d%%\n", list->pct);
                }
        } else {
                (void) printf("\n");
        }
        if (func1)
                func1(list->status, padding);
        n = 1;
        j = 0;
        while ((list = list->next) != end) {
                if (limit == 0 || n < limit) {
                        print_name(list, padding, &n, pct, full);
                        if (func1)
                                func1(list->status, padding);
                } else
                        j++;
        }
        if (j == 1) {
                print_name(list->prev, padding, &n, pct, full);
        } else if (j > 1) {
                (void) printf("%s... %d %s\n", padding, j,
                    dgettext("FMD", "more entries suppressed,"
                    " use -v option for full list"));
        }
        free(padding);
}

static int
asru_same_status(name_list_t *list)
{
        name_list_t *end = list;
        int status = list->status;

        while ((list = list->next) != end) {
                if (status == -1) {
                        status = list->status;
                        continue;
                }
                if (list->status != -1 && status != list->status) {
                        status = -1;
                        break;
                }
        }
        return (status);
}

static int
serial_in_fru(name_list_t *fru, name_list_t *serial)
{
        name_list_t *sp = serial;
        name_list_t *fp;
        int nserial = 0;
        int found = 0;
        char buf[128];

        while (sp) {
                fp = fru;
                nserial++;
                (void) snprintf(buf, sizeof (buf), "serial=%s", sp->name);
                buf[sizeof (buf) - 1] = 0;
                while (fp) {
                        if (strstr(fp->name, buf) != NULL) {
                                found++;
                                break;
                        }
                        fp = fp->next;
                        if (fp == fru)
                                break;
                }
                sp = sp->next;
                if (sp == serial)
                        break;
        }
        return (found == nserial ? 1 : 0);
}

static void
print_sup_record(status_record_t *srp, int opt_i, int full)
{
        char buf[32];
        uurec_t *uurp = srp->uurec;
        int n, j, k, max;
        int status;
        ari_list_t *ari_list;

        n = 0;
        max = max_fault;
        if (max < 0) {
                max = 0;
        }
        j = max / 2;
        max -= j;
        k = srp->nrecs - max;
        while ((uurp = uurp->next) != NULL) {
                if (full || n < j || n >= k || max_fault == 0 ||
                    srp->nrecs == max_fault+1) {
                        if (opt_i) {
                                ari_list = uurp->ari_uuid_list;
                                while (ari_list) {
                                        (void) printf("%-15s %s\n",
                                            format_date(buf, sizeof (buf),
                                            uurp->sec), ari_list->ari_uuid);
                                        ari_list = ari_list->next;
                                }
                        } else {
                                (void) printf("%-15s %s\n",
                                    format_date(buf, sizeof (buf), uurp->sec),
                                    uurp->uuid);
                        }
                } else if (n == j)
                        (void) printf("... %d %s\n", srp->nrecs - max_fault,
                            dgettext("FMD", "more entries suppressed"));
                n++;
        }
        (void) printf("\n");
        if (srp->host) {
                (void) printf("%s %s", dgettext("FMD", "Host        :"),
                    srp->host->server);
                if (srp->host->domain)
                        (void) printf("\t%s %s", dgettext("FMD",
                            "Domain      :"), srp->host->domain);
                (void) printf("\n%s %s", dgettext("FMD", "Platform    :"),
                    srp->host->platform);
                (void) printf("\t%s %s", dgettext("FMD", "Chassis_id  :"),
                    srp->host->chassis ? srp->host->chassis : "");
                (void) printf("\n%s %s\n\n", dgettext("FMD", "Product_sn  :"),
                    srp->host->product_sn ? srp->host->product_sn : "");
        }
        if (srp->class)
                print_name_list(srp->class,
                    dgettext("FMD", "Fault class :"), 0, srp->class->pct,
                    NULL, full);
        if (srp->asru) {
                status = asru_same_status(srp->asru);
                if (status != -1) {
                        print_name_list(srp->asru,
                            dgettext("FMD", "Affects     :"),
                            full ? 0 : max_display, 0, NULL, full);
                        print_asru_status(status, "             ");
                } else
                        print_name_list(srp->asru,
                            dgettext("FMD", "Affects     :"),
                            full ? 0 : max_display, 0, print_asru_status, full);
        }
        if (full || srp->fru == NULL || srp->asru == NULL) {
                if (srp->resource) {
                        status = asru_same_status(srp->resource);
                        if (status != -1) {
                                print_name_list(srp->resource,
                                    dgettext("FMD", "Problem in  :"),
                                    full ? 0 : max_display, 0, NULL, full);
                                print_rsrc_status(status, "             ");
                        } else
                                print_name_list(srp->resource,
                                    dgettext("FMD", "Problem in  :"),
                                    full ? 0 : max_display, 0,
                                    print_rsrc_status, full);
                }
        }
        if (srp->fru) {
                status = asru_same_status(srp->fru);
                if (status != -1) {
                        print_name_list(srp->fru, dgettext("FMD",
                            "FRU         :"), 0,
                            srp->fru->pct == 100 ? 100 : srp->fru->max_pct,
                            NULL, full);
                        print_fru_status(status, "             ");
                } else
                        print_name_list(srp->fru, dgettext("FMD",
                            "FRU         :"), 0,
                            srp->fru->pct == 100 ? 100 : srp->fru->max_pct,
                            print_fru_status, full);
        }
        if (srp->serial && !serial_in_fru(srp->fru, srp->serial) &&
            !serial_in_fru(srp->asru, srp->serial)) {
                print_name_list(srp->serial, dgettext("FMD", "Serial ID.  :"),
                    0, 0, NULL, full);
        }
        print_dict_info(srp->uurec->event);
        (void) printf("\n");
}

static void
print_status_record(status_record_t *srp, int summary, int opt_i, int full)
{
        char buf[32];
        uurec_t *uurp = srp->uurec;
        static int header = 0;
        char *head;
        ari_list_t *ari_list;

        if (!summary || !header) {
                if (opt_i) {
                        head = "--------------- "
                            "------------------------------------  "
                            "-------------- ---------\n"
                            "TIME            CACHE-ID"
                            "                              MSG-ID"
                            "         SEVERITY\n--------------- "
                            "------------------------------------ "
                            " -------------- ---------";
                } else {
                        head = "--------------- "
                            "------------------------------------  "
                            "-------------- ---------\n"
                            "TIME            EVENT-ID"
                            "                              MSG-ID"
                            "         SEVERITY\n--------------- "
                            "------------------------------------ "
                            " -------------- ---------";
                }
                (void) printf("%s\n", dgettext("FMD", head));
                header = 1;
        }
        if (opt_i) {
                ari_list = uurp->ari_uuid_list;
                while (ari_list) {
                        (void) printf("%-15s %-37s %-14s %-9s %s\n",
                            format_date(buf, sizeof (buf), uurp->sec),
                            ari_list->ari_uuid, srp->msgid, srp->severity,
                            srp->injected ? dgettext("FMD", "injected") : "");
                        ari_list = ari_list->next;
                }
        } else {
                (void) printf("%-15s %-37s %-14s %-9s %s\n",
                    format_date(buf, sizeof (buf), uurp->sec),
                    uurp->uuid, srp->msgid, srp->severity,
                    srp->injected ? dgettext("FMD", "injected") : "");
        }

        if (!summary)
                print_sup_record(srp, opt_i, full);
}

static void
print_catalog(int summary, int opt_a, int full, int opt_i, int page_feed)
{
        status_record_t *srp;
        sr_list_t *slp;

        slp = status_rec_list;
        if (slp) {
                for (;;) {
                        srp = slp->status_record;
                        if (opt_a || srp->not_suppressed) {
                                if (page_feed)
                                        (void) printf("\f\n");
                                print_status_record(srp, summary, opt_i, full);
                        }
                        if (slp->next == status_rec_list)
                                break;
                        slp = slp->next;
                }
        }
}

static name_list_t *
find_fru(status_record_t *srp, char *resource)
{
        name_list_t *rt = NULL;
        name_list_t *fru = srp->fru;

        while (fru) {
                if (strcmp(resource, fru->name) == 0) {
                        rt = fru;
                        break;
                }
                fru = fru->next;
                if (fru == srp->fru)
                        break;
        }
        return (rt);
}

static void
print_fru_line(name_list_t *fru, char *uuid)
{
        if (fru->pct == 100) {
                (void) printf("%s %d %s %d%%\n", uuid, fru->count,
                    dgettext("FMD", "suspects in this FRU total certainty"),
                    100);
        } else {
                (void) printf("%s %d %s %d%%\n", uuid, fru->count,
                    dgettext("FMD", "suspects in this FRU max certainty"),
                    fru->max_pct);
        }
}

static void
print_fru(int summary, int opt_a, int opt_i, int page_feed)
{
        resource_list_t *tp = status_fru_list;
        status_record_t *srp;
        sr_list_t *slp, *end;
        uurec_t *uurp;
        name_list_t *fru;
        int status;
        ari_list_t *ari_list;

        while (tp) {
                if (opt_a || tp->not_suppressed) {
                        if (page_feed)
                                (void) printf("\f\n");
                        if (!summary)
                                (void) printf("-----------------------------"
                                    "---------------------------------------"
                                    "----------\n");
                        slp = tp->status_rec_list;
                        end = slp;
                        do {
                                srp = slp->status_record;
                                if (!srp->not_suppressed) {
                                        slp = slp->next;
                                        continue;
                                }
                                fru = find_fru(srp, tp->resource);
                                if (fru) {
                                        if (fru->label)
                                                (void) printf("\"%s\" (%s) ",
                                                    fru->label, fru->name);
                                        else
                                                (void) printf("%s ",
                                                    fru->name);
                                        break;
                                }
                                slp = slp->next;
                        } while (slp != end);

                        slp = tp->status_rec_list;
                        end = slp;
                        status = 0;
                        do {
                                srp = slp->status_record;
                                if (!srp->not_suppressed) {
                                        slp = slp->next;
                                        continue;
                                }
                                fru = srp->fru;
                                while (fru) {
                                        if (strcmp(tp->resource,
                                            fru->name) == 0)
                                                status |= fru->status;
                                        fru = fru->next;
                                        if (fru == srp->fru)
                                                break;
                                }
                                slp = slp->next;
                        } while (slp != end);
                        if (status & FM_SUSPECT_NOT_PRESENT)
                                (void) printf(dgettext("FMD", "not present"));
                        else if (status & FM_SUSPECT_FAULTY)
                                (void) printf(dgettext("FMD", "faulty"));
                        else if (status & FM_SUSPECT_REPLACED)
                                (void) printf(dgettext("FMD", "replaced"));
                        else if (status & FM_SUSPECT_REPAIRED)
                                (void) printf(dgettext("FMD",
                                    "repair attempted"));
                        else if (status & FM_SUSPECT_ACQUITTED)
                                (void) printf(dgettext("FMD", "acquitted"));
                        else
                                (void) printf(dgettext("FMD", "removed"));

                        if (tp->injected)
                                (void) printf(dgettext("FMD", " injected\n"));
                        else
                                (void) printf(dgettext("FMD", "\n"));

                        slp = tp->status_rec_list;
                        end = slp;
                        do {
                                srp = slp->status_record;
                                if (!srp->not_suppressed) {
                                        slp = slp->next;
                                        continue;
                                }
                                uurp = srp->uurec;
                                fru = find_fru(srp, tp->resource);
                                if (fru) {
                                        if (opt_i) {
                                                ari_list = uurp->ari_uuid_list;
                                                while (ari_list) {
                                                        print_fru_line(fru,
                                                            ari_list->ari_uuid);
                                                        ari_list =
                                                            ari_list->next;
                                                }
                                        } else {
                                                print_fru_line(fru, uurp->uuid);
                                        }
                                }
                                slp = slp->next;
                        } while (slp != end);
                        if (!summary) {
                                slp = tp->status_rec_list;
                                end = slp;
                                do {
                                        srp = slp->status_record;
                                        if (!srp->not_suppressed) {
                                                slp = slp->next;
                                                continue;
                                        }
                                        if (srp->serial &&
                                            !serial_in_fru(srp->fru,
                                            srp->serial)) {
                                                print_name_list(srp->serial,
                                                    dgettext("FMD",
                                                    "Serial ID.  :"),
                                                    0, 0, NULL, 1);
                                                break;
                                        }
                                        slp = slp->next;
                                } while (slp != end);
                        }
                }
                tp = tp->next;
                if (tp == status_fru_list)
                        break;
        }
}

static void
print_asru(int opt_a)
{
        resource_list_t *tp = status_asru_list;
        status_record_t *srp;
        sr_list_t *slp, *end;
        char *msg;
        int status;
        name_list_t *asru;

        while (tp) {
                if (opt_a || tp->not_suppressed) {
                        status = 0;
                        slp = tp->status_rec_list;
                        end = slp;
                        do {
                                srp = slp->status_record;
                                if (!srp->not_suppressed) {
                                        slp = slp->next;
                                        continue;
                                }
                                asru = srp->asru;
                                while (asru) {
                                        if (strcmp(tp->resource,
                                            asru->name) == 0)
                                                status |= asru->status;
                                        asru = asru->next;
                                        if (asru == srp->asru)
                                                break;
                                }
                                slp = slp->next;
                        } while (slp != end);
                        switch (status) {
                        case 0:
                                msg = dgettext("FMD", "ok");
                                break;
                        case FM_SUSPECT_DEGRADED:
                                msg = dgettext("FMD", "degraded");
                                break;
                        case FM_SUSPECT_FAULTY | FM_SUSPECT_DEGRADED:
                                msg = dgettext("FMD", "degraded");
                                break;
                        case FM_SUSPECT_FAULTY:
                                msg = dgettext("FMD", "degraded");
                                break;
                        case FM_SUSPECT_UNUSABLE:
                                msg = dgettext("FMD", "unknown");
                                break;
                        case FM_SUSPECT_FAULTY | FM_SUSPECT_UNUSABLE:
                                msg = dgettext("FMD", "faulted");
                                break;
                        default:
                                msg = "";
                                break;
                        }
                        (void) printf("%-69s %s", tp->resource, msg);
                        if (tp->injected)
                                (void) printf(dgettext("FMD", " injected\n"));
                        else
                                (void) printf(dgettext("FMD", "\n"));
                }
                tp = tp->next;
                if (tp == status_asru_list)
                        break;
        }
}

static int
uuid_in_list(char *uuid, uurec_select_t *uurecp)
{
        while (uurecp) {
                if (strcmp(uuid, uurecp->uuid) == 0)
                        return (1);
                uurecp = uurecp->next;
        }
        return (0);
}

static int
dfault_rec(const fmd_adm_caseinfo_t *acp, void *arg)
{
        int64_t *diag_time;
        uint_t nelem;
        int rt = 0;
        char *uuid = "-";
        uurec_select_t *uurecp = (uurec_select_t *)arg;

        if (nvlist_lookup_int64_array(acp->aci_event, FM_SUSPECT_DIAG_TIME,
            &diag_time, &nelem) == 0 && nelem >= 2) {
                (void) nvlist_lookup_string(acp->aci_event, FM_SUSPECT_UUID,
                    &uuid);
                if (uurecp == NULL || uuid_in_list(uuid, uurecp))
                        add_fault_record_to_catalog(acp->aci_event, *diag_time,
                            uuid);
        } else {
                rt = -1;
        }
        return (rt);
}

/*ARGSUSED*/
static int
dstatus_rec(const fmd_adm_rsrcinfo_t *ari, void *unused)
{
        update_asru_state_in_catalog(ari->ari_case, ari->ari_uuid);
        return (0);
}

static int
get_cases_from_fmd(fmd_adm_t *adm, uurec_select_t *uurecp, int opt_i)
{
        int rt = FMADM_EXIT_SUCCESS;

        /*
         * These calls may fail with Protocol error if message payload is
         * too big
         */
        if (fmd_adm_case_iter(adm, NULL, dfault_rec, uurecp) != 0)
                die("failed to get case list from fmd");
        if (opt_i && fmd_adm_rsrc_iter(adm, 1, dstatus_rec, NULL) != 0)
                die("failed to get case status from fmd");
        return (rt);
}

/*
 * fmadm faulty command
 *
 *      -a              show hidden fault records
 *      -f              show faulty fru's
 *      -g              force grouping of similar faults on the same fru
 *      -n              number of fault records to display
 *      -p              pipe output through pager
 *      -r              show faulty asru's
 *      -s              print summary of first fault
 *      -u              print listed uuid's only
 *      -v              full output
 */

int
cmd_faulty(fmd_adm_t *adm, int argc, char *argv[])
{
        int opt_a = 0, opt_v = 0, opt_p = 0, opt_s = 0, opt_r = 0, opt_f = 0;
        int opt_i = 0;
        char *pager;
        FILE *fp;
        int rt, c, stat;
        uurec_select_t *tp;
        uurec_select_t *uurecp = NULL;

        while ((c = getopt(argc, argv, "afgin:prsu:v")) != EOF) {
                switch (c) {
                case 'a':
                        opt_a++;
                        break;
                case 'f':
                        opt_f++;
                        break;
                case 'g':
                        opt_g++;
                        break;
                case 'i':
                        opt_i++;
                        break;
                case 'n':
                        max_fault = atoi(optarg);
                        break;
                case 'p':
                        opt_p++;
                        break;
                case 'r':
                        opt_r++;
                        break;
                case 's':
                        opt_s++;
                        break;
                case 'u':
                        tp = (uurec_select_t *)malloc(sizeof (uurec_select_t));
                        tp->uuid = optarg;
                        tp->next = uurecp;
                        uurecp = tp;
                        opt_a = 1;
                        break;
                case 'v':
                        opt_v++;
                        break;
                default:
                        return (FMADM_EXIT_USAGE);
                }
        }
        if (optind < argc)
                return (FMADM_EXIT_USAGE);

        if ((fmadm_msghdl = fmd_msg_init(NULL, FMD_MSG_VERSION)) == NULL)
                return (FMADM_EXIT_ERROR);
        rt = get_cases_from_fmd(adm, uurecp, opt_i);
        if (opt_p) {
                if ((pager = getenv("PAGER")) == NULL)
                        pager = "/usr/bin/more";
                fp = popen(pager, "w");
                if (fp == NULL) {
                        rt = FMADM_EXIT_ERROR;
                        opt_p = 0;
                } else {
                        (void) dup2(fileno(fp), 1);
                        setbuf(stdout, NULL);
                        (void) fclose(fp);
                }
        }
        max_display = max_fault;
        if (opt_f)
                print_fru(opt_s, opt_a, opt_i, opt_p && !opt_s);
        if (opt_r)
                print_asru(opt_a);
        if (opt_f == 0 && opt_r == 0)
                print_catalog(opt_s, opt_a, opt_v, opt_i, opt_p && !opt_s);
        fmd_msg_fini(fmadm_msghdl);
        if (topo_handle)
                topo_close(topo_handle);
        if (opt_p) {
                (void) fclose(stdout);
                (void) wait(&stat);
        }
        return (rt);
}

int
cmd_flush(fmd_adm_t *adm, int argc, char *argv[])
{
        int i, status = FMADM_EXIT_SUCCESS;

        if (argc < 2 || (i = getopt(argc, argv, "")) != EOF)
                return (FMADM_EXIT_USAGE);

        for (i = 1; i < argc; i++) {
                if (fmd_adm_rsrc_flush(adm, argv[i]) != 0) {
                        warn("failed to flush %s", argv[i]);
                        status = FMADM_EXIT_ERROR;
                } else
                        note("flushed resource history for %s\n", argv[i]);
        }

        return (status);
}

int
cmd_repair(fmd_adm_t *adm, int argc, char *argv[])
{
        int err;

        if (getopt(argc, argv, "") != EOF)
                return (FMADM_EXIT_USAGE);

        if (argc - optind != 1)
                return (FMADM_EXIT_USAGE);

        /*
         * argument could be a uuid, an fmri (asru, fru or resource)
         * or a label. Try uuid first, If that fails try the others.
         */
        err = fmd_adm_case_repair(adm, argv[optind]);
        if (err != 0)
                err = fmd_adm_rsrc_repaired(adm, argv[optind]);

        if (err != 0)
                die("failed to record repair to %s", argv[optind]);

        note("recorded repair to %s\n", argv[optind]);
        return (FMADM_EXIT_SUCCESS);
}

int
cmd_repaired(fmd_adm_t *adm, int argc, char *argv[])
{
        int err;

        if (getopt(argc, argv, "") != EOF)
                return (FMADM_EXIT_USAGE);

        if (argc - optind != 1)
                return (FMADM_EXIT_USAGE);

        /*
         * argument could be an fmri (asru, fru or resource) or a label.
         */
        err = fmd_adm_rsrc_repaired(adm, argv[optind]);
        if (err != 0)
                die("failed to record repair to %s", argv[optind]);

        note("recorded repair to of %s\n", argv[optind]);
        return (FMADM_EXIT_SUCCESS);
}

int
cmd_replaced(fmd_adm_t *adm, int argc, char *argv[])
{
        int err;

        if (getopt(argc, argv, "") != EOF)
                return (FMADM_EXIT_USAGE);

        if (argc - optind != 1)
                return (FMADM_EXIT_USAGE);

        /*
         * argument could be an fmri (asru, fru or resource) or a label.
         */
        err = fmd_adm_rsrc_replaced(adm, argv[optind]);
        if (err != 0)
                die("failed to record replacement of %s", argv[optind]);

        note("recorded replacement of %s\n", argv[optind]);
        return (FMADM_EXIT_SUCCESS);
}

int
cmd_acquit(fmd_adm_t *adm, int argc, char *argv[])
{
        int err;

        if (getopt(argc, argv, "") != EOF)
                return (FMADM_EXIT_USAGE);

        if (argc - optind != 1 && argc - optind != 2)
                return (FMADM_EXIT_USAGE);

        /*
         * argument could be a uuid, an fmri (asru, fru or resource)
         * or a label. Or it could be a uuid and an fmri or label.
         */
        if (argc - optind == 2) {
                err = fmd_adm_rsrc_acquit(adm, argv[optind], argv[optind + 1]);
                if (err != 0)
                        err = fmd_adm_rsrc_acquit(adm, argv[optind + 1],
                            argv[optind]);
        } else {
                err = fmd_adm_case_acquit(adm, argv[optind]);
                if (err != 0)
                        err = fmd_adm_rsrc_acquit(adm, argv[optind], "");
        }

        if (err != 0)
                die("failed to record acquital of %s", argv[optind]);

        note("recorded acquital of %s\n", argv[optind]);
        return (FMADM_EXIT_SUCCESS);
}