#include <sys/fm/protocol.h>
#include <uuid/uuid.h>
#include <alloca.h>
#include <fmd_alloc.h>
#include <fmd_module.h>
#include <fmd_error.h>
#include <fmd_conf.h>
#include <fmd_case.h>
#include <fmd_string.h>
#include <fmd_subr.h>
#include <fmd_protocol.h>
#include <fmd_event.h>
#include <fmd_eventq.h>
#include <fmd_dispq.h>
#include <fmd_buf.h>
#include <fmd_log.h>
#include <fmd_asru.h>
#include <fmd_fmri.h>
#include <fmd_xprt.h>
#include <fmd.h>
static const char *const _fmd_case_snames[] = {
"UNSOLVED",
"SOLVED",
"CLOSE_WAIT",
"CLOSED",
"REPAIRED",
"RESOLVED"
};
static fmd_case_impl_t *fmd_case_tryhold(fmd_case_impl_t *);
fmd_case_hash_t *
fmd_case_hash_create(void)
{
fmd_case_hash_t *chp = fmd_alloc(sizeof (fmd_case_hash_t), FMD_SLEEP);
(void) pthread_rwlock_init(&chp->ch_lock, NULL);
chp->ch_hashlen = fmd.d_str_buckets;
chp->ch_hash = fmd_zalloc(sizeof (void *) * chp->ch_hashlen, FMD_SLEEP);
chp->ch_code_hash = fmd_zalloc(sizeof (void *) * chp->ch_hashlen,
FMD_SLEEP);
chp->ch_count = 0;
return (chp);
}
void
fmd_case_hash_destroy(fmd_case_hash_t *chp)
{
fmd_free(chp->ch_hash, sizeof (void *) * chp->ch_hashlen);
fmd_free(chp->ch_code_hash, sizeof (void *) * chp->ch_hashlen);
fmd_free(chp, sizeof (fmd_case_hash_t));
}
void
fmd_case_hash_apply(fmd_case_hash_t *chp,
void (*func)(fmd_case_t *, void *), void *arg)
{
fmd_case_impl_t *cp, **cps, **cpp;
uint_t cpc, i;
(void) pthread_rwlock_rdlock(&chp->ch_lock);
cps = cpp = fmd_alloc(chp->ch_count * sizeof (fmd_case_t *), FMD_SLEEP);
cpc = chp->ch_count;
for (i = 0; i < chp->ch_hashlen; i++) {
for (cp = chp->ch_hash[i]; cp != NULL; cp = cp->ci_next)
*cpp++ = fmd_case_tryhold(cp);
}
ASSERT(cpp == cps + cpc);
(void) pthread_rwlock_unlock(&chp->ch_lock);
for (i = 0; i < cpc; i++) {
if (cps[i] != NULL) {
func((fmd_case_t *)cps[i], arg);
fmd_case_rele((fmd_case_t *)cps[i]);
}
}
fmd_free(cps, cpc * sizeof (fmd_case_t *));
}
static void
fmd_case_code_hash_insert(fmd_case_hash_t *chp, fmd_case_impl_t *cip)
{
uint_t h = fmd_strhash(cip->ci_code) % chp->ch_hashlen;
cip->ci_code_next = chp->ch_code_hash[h];
chp->ch_code_hash[h] = cip;
}
static void
fmd_case_code_hash_delete(fmd_case_hash_t *chp, fmd_case_impl_t *cip)
{
fmd_case_impl_t **pp, *cp;
if (cip->ci_code) {
uint_t h = fmd_strhash(cip->ci_code) % chp->ch_hashlen;
pp = &chp->ch_code_hash[h];
for (cp = *pp; cp != NULL; cp = cp->ci_code_next) {
if (cp != cip)
pp = &cp->ci_code_next;
else
break;
}
if (cp != NULL) {
*pp = cp->ci_code_next;
cp->ci_code_next = NULL;
}
}
}
static const char *
fmd_case_mkcode(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_case_susp_t *cis;
fmd_case_hash_t *chp = fmd.d_cases;
char **keys, **keyp;
const char *s;
ASSERT(MUTEX_HELD(&cip->ci_lock));
ASSERT(cip->ci_state >= FMD_CASE_SOLVED);
fmd_case_code_hash_delete(chp, cip);
fmd_free(cip->ci_code, cip->ci_codelen);
cip->ci_codelen = cip->ci_mod->mod_codelen;
cip->ci_code = fmd_zalloc(cip->ci_codelen, FMD_SLEEP);
keys = keyp = alloca(sizeof (char *) * (cip->ci_nsuspects + 1));
for (cis = cip->ci_suspects; cis != NULL; cis = cis->cis_next) {
if (nvlist_lookup_string(cis->cis_nvl, FM_CLASS, keyp) == 0)
keyp++;
}
*keyp = NULL;
if (cip->ci_nsuspects == 0 || fmd_module_dc_key2code(
cip->ci_mod, keys, cip->ci_code, cip->ci_codelen) != 0) {
(void) fmd_conf_getprop(fmd.d_conf, "nodiagcode", &s);
fmd_free(cip->ci_code, cip->ci_codelen);
cip->ci_codelen = strlen(s) + 1;
cip->ci_code = fmd_zalloc(cip->ci_codelen, FMD_SLEEP);
(void) strcpy(cip->ci_code, s);
}
fmd_case_code_hash_insert(chp, cip);
return (cip->ci_code);
}
typedef struct {
int *fcl_countp;
int fcl_maxcount;
uint8_t *fcl_ba;
nvlist_t **fcl_nva;
int *fcl_msgp;
} fmd_case_lst_t;
static void
fmd_case_set_lst(fmd_asru_link_t *alp, void *arg)
{
fmd_case_lst_t *entryp = (fmd_case_lst_t *)arg;
boolean_t b;
int state;
if (*entryp->fcl_countp >= entryp->fcl_maxcount)
return;
if (nvlist_lookup_boolean_value(alp->al_event, FM_SUSPECT_MESSAGE,
&b) == 0 && b == B_FALSE)
*entryp->fcl_msgp = B_FALSE;
entryp->fcl_ba[*entryp->fcl_countp] = 0;
state = fmd_asru_al_getstate(alp);
if (state & FMD_ASRU_DEGRADED)
entryp->fcl_ba[*entryp->fcl_countp] |= FM_SUSPECT_DEGRADED;
if (state & FMD_ASRU_UNUSABLE)
entryp->fcl_ba[*entryp->fcl_countp] |= FM_SUSPECT_UNUSABLE;
if (state & FMD_ASRU_FAULTY)
entryp->fcl_ba[*entryp->fcl_countp] |= FM_SUSPECT_FAULTY;
if (!(state & FMD_ASRU_PRESENT))
entryp->fcl_ba[*entryp->fcl_countp] |= FM_SUSPECT_NOT_PRESENT;
if (alp->al_reason == FMD_ASRU_REPAIRED)
entryp->fcl_ba[*entryp->fcl_countp] |= FM_SUSPECT_REPAIRED;
else if (alp->al_reason == FMD_ASRU_REPLACED)
entryp->fcl_ba[*entryp->fcl_countp] |= FM_SUSPECT_REPLACED;
else if (alp->al_reason == FMD_ASRU_ACQUITTED)
entryp->fcl_ba[*entryp->fcl_countp] |= FM_SUSPECT_ACQUITTED;
entryp->fcl_nva[*entryp->fcl_countp] = alp->al_event;
(*entryp->fcl_countp)++;
}
static void
fmd_case_faulty(fmd_asru_link_t *alp, void *arg)
{
int *faultyp = (int *)arg;
*faultyp |= (alp->al_flags & FMD_ASRU_FAULTY);
}
static void
fmd_case_usable(fmd_asru_link_t *alp, void *arg)
{
int *usablep = (int *)arg;
*usablep |= !(fmd_asru_al_getstate(alp) & FMD_ASRU_UNUSABLE);
}
static void
fmd_case_not_faulty(fmd_asru_link_t *alp, void *arg)
{
int *not_faultyp = (int *)arg;
*not_faultyp |= !(alp->al_flags & FMD_ASRU_FAULTY);
}
static void
fmd_case_unusable_and_present(fmd_asru_link_t *alp, void *arg)
{
int *rvalp = (int *)arg;
int state;
nvlist_t *asru;
if ((alp->al_flags & FMD_ASRU_PROXY) &&
!(alp->al_flags & FMD_ASRU_PROXY_WITH_ASRU)) {
*rvalp |= B_TRUE;
return;
}
state = fmd_asru_al_getstate(alp);
if (nvlist_lookup_nvlist(alp->al_event, FM_FAULT_ASRU, &asru) != 0)
return;
*rvalp |= ((state & FMD_ASRU_UNUSABLE) && (state & FMD_ASRU_PRESENT));
}
nvlist_t *
fmd_case_mkevent(fmd_case_t *cp, const char *class)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
nvlist_t **nva, *nvl;
uint8_t *ba;
int msg = B_TRUE;
const char *code;
fmd_case_lst_t fcl;
int count = 0;
(void) pthread_mutex_lock(&cip->ci_lock);
ASSERT(cip->ci_state >= FMD_CASE_SOLVED);
nva = alloca(sizeof (nvlist_t *) * cip->ci_nsuspects);
ba = alloca(sizeof (uint8_t) * cip->ci_nsuspects);
fcl.fcl_countp = &count;
fcl.fcl_maxcount = cip->ci_nsuspects;
fcl.fcl_msgp = &msg;
fcl.fcl_ba = ba;
fcl.fcl_nva = nva;
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_case_set_lst, &fcl);
if (cip->ci_code == NULL)
(void) fmd_case_mkcode(cp);
if (strcmp(class, FM_LIST_REPAIRED_CLASS) == 0)
(void) fmd_conf_getprop(fmd.d_conf, "repaircode", &code);
else if (strcmp(class, FM_LIST_RESOLVED_CLASS) == 0)
(void) fmd_conf_getprop(fmd.d_conf, "resolvecode", &code);
else if (strcmp(class, FM_LIST_UPDATED_CLASS) == 0)
(void) fmd_conf_getprop(fmd.d_conf, "updatecode", &code);
else
code = cip->ci_code;
if (msg == B_FALSE)
cip->ci_flags |= FMD_CF_INVISIBLE;
nvl = fmd_protocol_list(class, cip->ci_diag_de == NULL ?
cip->ci_mod->mod_fmri : cip->ci_diag_de, cip->ci_uuid, code, count,
nva, ba, msg, &cip->ci_tv, cip->ci_injected);
(void) pthread_mutex_unlock(&cip->ci_lock);
return (nvl);
}
static int fmd_case_match_on_faulty_overlap = 1;
static int fmd_case_match_on_acquit_overlap = 1;
static int fmd_case_auto_acquit_isolated = 1;
static int fmd_case_auto_acquit_non_acquitted = 1;
static int fmd_case_too_recent = 10;
static boolean_t
fmd_case_compare_elem(nvlist_t *nvl, nvlist_t *xnvl, const char *elem)
{
nvlist_t *new_rsrc;
nvlist_t *rsrc;
char *new_name = NULL;
char *name = NULL;
ssize_t new_namelen;
ssize_t namelen;
int fmri_present = 1;
int new_fmri_present = 1;
int match = B_FALSE;
fmd_topo_t *ftp = fmd_topo_hold();
if (nvlist_lookup_nvlist(xnvl, elem, &rsrc) != 0)
fmri_present = 0;
else {
if ((namelen = fmd_fmri_nvl2str(rsrc, NULL, 0)) == -1)
goto done;
name = fmd_alloc(namelen + 1, FMD_SLEEP);
if (fmd_fmri_nvl2str(rsrc, name, namelen + 1) == -1)
goto done;
}
if (nvlist_lookup_nvlist(nvl, elem, &new_rsrc) != 0)
new_fmri_present = 0;
else {
if ((new_namelen = fmd_fmri_nvl2str(new_rsrc, NULL, 0)) == -1)
goto done;
new_name = fmd_alloc(new_namelen + 1, FMD_SLEEP);
if (fmd_fmri_nvl2str(new_rsrc, new_name, new_namelen + 1) == -1)
goto done;
}
match = (fmri_present == new_fmri_present &&
(fmri_present == 0 ||
topo_fmri_strcmp(ftp->ft_hdl, name, new_name)));
done:
if (name != NULL)
fmd_free(name, namelen + 1);
if (new_name != NULL)
fmd_free(new_name, new_namelen + 1);
fmd_topo_rele(ftp);
return (match);
}
static int
fmd_case_match_suspect(nvlist_t *nvl1, nvlist_t *nvl2)
{
char *class, *new_class;
if (!fmd_case_compare_elem(nvl1, nvl2, FM_FAULT_ASRU))
return (0);
if (!fmd_case_compare_elem(nvl1, nvl2, FM_FAULT_RESOURCE))
return (0);
if (!fmd_case_compare_elem(nvl1, nvl2, FM_FAULT_FRU))
return (0);
(void) nvlist_lookup_string(nvl2, FM_CLASS, &class);
(void) nvlist_lookup_string(nvl1, FM_CLASS, &new_class);
return (strcmp(class, new_class) == 0);
}
typedef struct {
int *fcms_countp;
int fcms_maxcount;
fmd_case_impl_t *fcms_cip;
uint8_t *fcms_new_susp_state;
uint8_t *fcms_old_susp_state;
uint8_t *fcms_old_match_state;
} fcms_t;
#define SUSPECT_STATE_FAULTY 0x1
#define SUSPECT_STATE_ISOLATED 0x2
#define SUSPECT_STATE_REMOVED 0x4
#define SUSPECT_STATE_ACQUITED 0x8
#define SUSPECT_STATE_REPAIRED 0x10
#define SUSPECT_STATE_REPLACED 0x20
#define SUSPECT_STATE_NO_MATCH 0x1
static void
fmd_case_match_suspects(fmd_asru_link_t *alp, void *arg)
{
fcms_t *fcmsp = (fcms_t *)arg;
fmd_case_impl_t *cip = fcmsp->fcms_cip;
fmd_case_susp_t *cis;
int i = 0;
int state = fmd_asru_al_getstate(alp);
if (*fcmsp->fcms_countp >= fcmsp->fcms_maxcount)
return;
if (!(state & FMD_ASRU_PRESENT) || (!(state & FMD_ASRU_FAULTY) &&
alp->al_reason == FMD_ASRU_REMOVED))
fcmsp->fcms_old_susp_state[*fcmsp->fcms_countp] =
SUSPECT_STATE_REMOVED;
else if ((state & FMD_ASRU_UNUSABLE) && (state & FMD_ASRU_FAULTY))
fcmsp->fcms_old_susp_state[*fcmsp->fcms_countp] =
SUSPECT_STATE_ISOLATED;
else if (state & FMD_ASRU_FAULTY)
fcmsp->fcms_old_susp_state[*fcmsp->fcms_countp] =
SUSPECT_STATE_FAULTY;
else if (alp->al_reason == FMD_ASRU_REPLACED)
fcmsp->fcms_old_susp_state[*fcmsp->fcms_countp] =
SUSPECT_STATE_REPLACED;
else if (alp->al_reason == FMD_ASRU_ACQUITTED)
fcmsp->fcms_old_susp_state[*fcmsp->fcms_countp] =
SUSPECT_STATE_ACQUITED;
else
fcmsp->fcms_old_susp_state[*fcmsp->fcms_countp] =
SUSPECT_STATE_REPAIRED;
for (cis = cip->ci_suspects; cis != NULL; cis = cis->cis_next, i++)
if (fmd_case_match_suspect(cis->cis_nvl, alp->al_event) == 1)
break;
if (cis != NULL)
fcmsp->fcms_new_susp_state[i] =
fcmsp->fcms_old_susp_state[*fcmsp->fcms_countp];
else
fcmsp->fcms_old_match_state[*fcmsp->fcms_countp] |=
SUSPECT_STATE_NO_MATCH;
(*fcmsp->fcms_countp)++;
}
typedef struct {
int *fca_do_update;
fmd_case_impl_t *fca_cip;
} fca_t;
static void
fmd_case_fault_acquitted_matching(fmd_asru_link_t *alp, void *arg)
{
fca_t *fcap = (fca_t *)arg;
fmd_case_impl_t *cip = fcap->fca_cip;
fmd_case_susp_t *cis;
int state = fmd_asru_al_getstate(alp);
if (!(state & FMD_ASRU_FAULTY) &&
alp->al_reason == FMD_ASRU_ACQUITTED) {
for (cis = cip->ci_suspects; cis != NULL; cis = cis->cis_next)
if (fmd_case_match_suspect(cis->cis_nvl,
alp->al_event) == 1)
break;
if (cis != NULL) {
(void) fmd_asru_setflags(alp, FMD_ASRU_FAULTY);
*fcap->fca_do_update = 1;
}
}
}
static void
fmd_case_fault_all_matching(fmd_asru_link_t *alp, void *arg)
{
fca_t *fcap = (fca_t *)arg;
fmd_case_impl_t *cip = fcap->fca_cip;
fmd_case_susp_t *cis;
int state = fmd_asru_al_getstate(alp);
if (!(state & FMD_ASRU_FAULTY)) {
for (cis = cip->ci_suspects; cis != NULL; cis = cis->cis_next)
if (fmd_case_match_suspect(cis->cis_nvl,
alp->al_event) == 1)
break;
if (cis != NULL) {
(void) fmd_asru_setflags(alp, FMD_ASRU_FAULTY);
*fcap->fca_do_update = 1;
}
}
}
static void
fmd_case_acquit_no_match(fmd_asru_link_t *alp, void *arg)
{
fca_t *fcap = (fca_t *)arg;
fmd_case_impl_t *cip = fcap->fca_cip;
fmd_case_susp_t *cis;
int state = fmd_asru_al_getstate(alp);
if (state & FMD_ASRU_FAULTY) {
for (cis = cip->ci_suspects; cis != NULL; cis = cis->cis_next)
if (fmd_case_match_suspect(cis->cis_nvl,
alp->al_event) == 1)
break;
if (cis == NULL) {
(void) fmd_asru_clrflags(alp, FMD_ASRU_FAULTY,
FMD_ASRU_ACQUITTED);
*fcap->fca_do_update = 1;
}
}
}
static void
fmd_case_acquit_isolated(fmd_asru_link_t *alp, void *arg)
{
int *do_update = (int *)arg;
int state = fmd_asru_al_getstate(alp);
if ((state & FMD_ASRU_PRESENT) && (state & FMD_ASRU_UNUSABLE) &&
(state & FMD_ASRU_FAULTY)) {
(void) fmd_asru_clrflags(alp, FMD_ASRU_FAULTY,
FMD_ASRU_ACQUITTED);
*do_update = 1;
}
}
static void
fmd_case_acquit_suspect(fmd_asru_link_t *alp, void *arg)
{
nvlist_t *nvl = (nvlist_t *)arg;
int state = fmd_asru_al_getstate(alp);
if ((state & FMD_ASRU_FAULTY) &&
fmd_case_match_suspect(nvl, alp->al_event) == 1)
(void) fmd_asru_clrflags(alp, FMD_ASRU_FAULTY,
FMD_ASRU_ACQUITTED);
}
typedef struct {
fmd_case_impl_t *fccd_cip;
uint8_t *fccd_new_susp_state;
uint8_t *fccd_new_match_state;
int *fccd_discard_new;
int *fccd_adjust_new;
} fccd_t;
static void
fmd_case_check_for_dups(fmd_case_t *old_cp, void *arg)
{
fccd_t *fccdp = (fccd_t *)arg;
fmd_case_impl_t *new_cip = fccdp->fccd_cip;
fmd_case_impl_t *old_cip = (fmd_case_impl_t *)old_cp;
int i, count = 0, do_update = 0, got_isolated_overlap = 0;
int got_faulty_overlap = 0;
int got_acquit_overlap = 0;
boolean_t too_recent;
uint64_t most_recent = 0;
fcms_t fcms;
fca_t fca;
uint8_t *new_susp_state;
uint8_t *old_susp_state;
uint8_t *old_match_state;
new_susp_state = alloca(new_cip->ci_nsuspects * sizeof (uint8_t));
for (i = 0; i < new_cip->ci_nsuspects; i++)
new_susp_state[i] = 0;
old_susp_state = alloca(old_cip->ci_nsuspects * sizeof (uint8_t));
for (i = 0; i < old_cip->ci_nsuspects; i++)
old_susp_state[i] = 0;
old_match_state = alloca(old_cip->ci_nsuspects * sizeof (uint8_t));
for (i = 0; i < old_cip->ci_nsuspects; i++)
old_match_state[i] = 0;
fcms.fcms_countp = &count;
fcms.fcms_maxcount = old_cip->ci_nsuspects;
fcms.fcms_cip = new_cip;
fcms.fcms_new_susp_state = new_susp_state;
fcms.fcms_old_susp_state = old_susp_state;
fcms.fcms_old_match_state = old_match_state;
fmd_asru_hash_apply_by_case(fmd.d_asrus, (fmd_case_t *)old_cip,
fmd_case_match_suspects, &fcms);
for (i = 0; i < new_cip->ci_nsuspects; i++)
if (new_susp_state[i] == SUSPECT_STATE_FAULTY)
got_faulty_overlap = 1;
if (got_faulty_overlap && fmd_case_match_on_faulty_overlap)
goto got_match;
for (i = 0; i < new_cip->ci_nsuspects; i++)
if (new_susp_state[i] == SUSPECT_STATE_ACQUITED)
got_acquit_overlap = 1;
for (i = 0; i < old_cip->ci_nsuspects; i++)
if (old_susp_state[i] == SUSPECT_STATE_FAULTY)
got_acquit_overlap = 0;
if (got_acquit_overlap && fmd_case_match_on_acquit_overlap)
goto got_match;
for (i = 0; i < new_cip->ci_nsuspects; i++)
if (new_susp_state[i] == 0)
return;
for (i = 0; i < old_cip->ci_nsuspects; i++)
if (old_match_state[i] == SUSPECT_STATE_NO_MATCH &&
(old_susp_state[i] == SUSPECT_STATE_FAULTY ||
old_susp_state[i] == SUSPECT_STATE_ACQUITED ||
old_susp_state[i] == SUSPECT_STATE_REPAIRED))
return;
got_match:
if (old_cip->ci_state >= FMD_CASE_REPAIRED) {
if (fmd_case_auto_acquit_non_acquitted) {
*fccdp->fccd_adjust_new = 1;
for (i = 0; i < new_cip->ci_nsuspects; i++) {
fccdp->fccd_new_susp_state[i] |=
new_susp_state[i];
if (new_susp_state[i] == 0)
fccdp->fccd_new_susp_state[i] =
SUSPECT_STATE_NO_MATCH;
}
}
return;
}
*fccdp->fccd_discard_new = 1;
fca.fca_cip = new_cip;
fca.fca_do_update = &do_update;
fmd_asru_hash_apply_by_case(fmd.d_asrus, old_cp,
fmd_asru_most_recent, &most_recent);
too_recent = (new_cip->ci_tv.tv_sec - most_recent <
fmd_case_too_recent);
if (got_faulty_overlap) {
fmd_asru_hash_apply_by_case(fmd.d_asrus, old_cp,
fmd_case_acquit_no_match, &fca);
if (fmd_case_auto_acquit_isolated && !too_recent)
fmd_asru_hash_apply_by_case(fmd.d_asrus, old_cp,
fmd_case_acquit_isolated, &do_update);
} else if (got_acquit_overlap) {
if (fmd_case_auto_acquit_isolated && !too_recent) {
fmd_asru_hash_apply_by_case(fmd.d_asrus, old_cp,
fmd_case_fault_acquitted_matching, &fca);
fmd_asru_hash_apply_by_case(fmd.d_asrus, old_cp,
fmd_case_acquit_isolated, &do_update);
}
} else if (fmd_case_auto_acquit_isolated) {
for (i = 0; i < new_cip->ci_nsuspects; i++)
if (new_susp_state[i] == SUSPECT_STATE_ISOLATED)
got_isolated_overlap = 1;
if (!got_isolated_overlap)
fmd_asru_hash_apply_by_case(fmd.d_asrus, old_cp,
fmd_case_fault_all_matching, &fca);
fmd_asru_hash_apply_by_case(fmd.d_asrus, old_cp,
fmd_case_acquit_no_match, &fca);
}
if (do_update)
fmd_case_update(old_cp);
}
static int
fmd_case_convict(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_asru_hash_t *ahp = fmd.d_asrus;
int discard_new = 0, i;
fmd_case_susp_t *cis;
fmd_asru_link_t *alp;
uint8_t *new_susp_state;
uint8_t *new_match_state;
int adjust_new = 0;
fccd_t fccd;
fmd_case_impl_t *ncp, **cps, **cpp;
uint_t cpc;
fmd_case_hash_t *chp;
new_susp_state = alloca(cip->ci_nsuspects * sizeof (uint8_t));
for (i = 0; i < cip->ci_nsuspects; i++)
new_susp_state[i] = 0;
new_match_state = alloca(cip->ci_nsuspects * sizeof (uint8_t));
for (i = 0; i < cip->ci_nsuspects; i++)
new_match_state[i] = 0;
fccd.fccd_cip = cip;
fccd.fccd_adjust_new = &adjust_new;
fccd.fccd_new_susp_state = new_susp_state;
fccd.fccd_new_match_state = new_match_state;
fccd.fccd_discard_new = &discard_new;
chp = fmd.d_cases;
(void) pthread_rwlock_rdlock(&chp->ch_lock);
cps = cpp = fmd_alloc(chp->ch_count * sizeof (fmd_case_t *), FMD_SLEEP);
cpc = chp->ch_count;
for (i = 0; i < chp->ch_hashlen; i++)
for (ncp = chp->ch_hash[i]; ncp != NULL; ncp = ncp->ci_next)
*cpp++ = fmd_case_tryhold(ncp);
ASSERT(cpp == cps + cpc);
(void) pthread_rwlock_unlock(&chp->ch_lock);
for (i = 0; i < cpc; i++) {
if (cps[i] != NULL) {
if (cps[i] != (fmd_case_impl_t *)cp)
fmd_case_check_for_dups((fmd_case_t *)cps[i],
&fccd);
fmd_case_rele((fmd_case_t *)cps[i]);
}
}
fmd_free(cps, cpc * sizeof (fmd_case_t *));
(void) pthread_mutex_lock(&cip->ci_lock);
if (cip->ci_code == NULL)
(void) fmd_case_mkcode(cp);
else if (cip->ci_precanned)
fmd_case_code_hash_insert(fmd.d_cases, cip);
if (discard_new) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return (1);
}
for (cis = cip->ci_suspects; cis != NULL; cis = cis->cis_next) {
if ((alp = fmd_asru_hash_create_entry(ahp,
cp, cis->cis_nvl)) == NULL) {
fmd_error(EFMD_CASE_EVENT, "cannot convict suspect in "
"%s: %s\n", cip->ci_uuid, fmd_strerror(errno));
continue;
}
alp->al_flags |= FMD_ASRU_PRESENT;
alp->al_asru->asru_flags |= FMD_ASRU_PRESENT;
(void) fmd_asru_clrflags(alp, FMD_ASRU_UNUSABLE, 0);
(void) fmd_asru_setflags(alp, FMD_ASRU_FAULTY);
}
if (adjust_new) {
int some_suspect = 0, some_not_suspect = 0;
for (i = 0; i < cip->ci_nsuspects; i++) {
if ((new_susp_state[i] & SUSPECT_STATE_REPLACED) ||
(new_susp_state[i] & SUSPECT_STATE_REPAIRED) ||
(new_susp_state[i] & SUSPECT_STATE_REMOVED) ||
(new_match_state[i] & SUSPECT_STATE_NO_MATCH))
some_not_suspect = 1;
else
some_suspect = 1;
}
if (some_suspect && some_not_suspect) {
for (cis = cip->ci_suspects, i = 0; cis != NULL;
cis = cis->cis_next, i++)
if ((new_susp_state[i] &
SUSPECT_STATE_REPLACED) ||
(new_susp_state[i] &
SUSPECT_STATE_REPAIRED) ||
(new_susp_state[i] &
SUSPECT_STATE_REMOVED) ||
(new_match_state[i] &
SUSPECT_STATE_NO_MATCH))
fmd_asru_hash_apply_by_case(fmd.d_asrus,
cp, fmd_case_acquit_suspect,
cis->cis_nvl);
}
}
(void) pthread_mutex_unlock(&cip->ci_lock);
return (0);
}
void
fmd_case_publish(fmd_case_t *cp, uint_t state)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_event_t *e;
nvlist_t *nvl;
char *class;
if (state == FMD_CASE_CURRENT)
state = cip->ci_state;
switch (state) {
case FMD_CASE_SOLVED:
(void) pthread_mutex_lock(&cip->ci_lock);
if (cip->ci_precanned == 0 && cip->ci_xprt == NULL &&
cip->ci_code != NULL) {
(void) pthread_mutex_unlock(&cip->ci_lock);
break;
}
if (cip->ci_tv_valid == 0) {
fmd_time_gettimeofday(&cip->ci_tv);
cip->ci_tv_valid = 1;
}
(void) pthread_mutex_unlock(&cip->ci_lock);
if (fmd_case_convict(cp) == 1) {
cip->ci_flags &= ~FMD_CF_SOLVED;
fmd_case_transition(cp, FMD_CASE_CLOSE_WAIT, 0);
break;
}
if (cip->ci_xprt != NULL) {
int count = 0;
fmd_asru_set_on_proxy_t fasp;
fmd_xprt_impl_t *xip = (fmd_xprt_impl_t *)cip->ci_xprt;
fasp.fasp_countp = &count;
fasp.fasp_maxcount = cip->ci_nsuspects;
fasp.fasp_proxy_asru = cip->ci_proxy_asru;
fasp.fasp_proxy_external = xip->xi_flags &
FMD_XPRT_EXTERNAL;
fasp.fasp_proxy_rdonly = ((xip->xi_flags &
FMD_XPRT_RDWR) == FMD_XPRT_RDONLY);
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_asru_set_on_proxy, &fasp);
}
nvl = fmd_case_mkevent(cp, FM_LIST_SUSPECT_CLASS);
(void) nvlist_lookup_string(nvl, FM_CLASS, &class);
e = fmd_event_create(FMD_EVT_PROTOCOL, FMD_HRT_NOW, nvl, class);
(void) pthread_rwlock_rdlock(&fmd.d_log_lock);
fmd_log_append(fmd.d_fltlog, e, cp);
(void) pthread_rwlock_unlock(&fmd.d_log_lock);
fmd_dispq_dispatch(fmd.d_disp, e, class);
(void) pthread_mutex_lock(&cip->ci_mod->mod_stats_lock);
cip->ci_mod->mod_stats->ms_casesolved.fmds_value.ui64++;
(void) pthread_mutex_unlock(&cip->ci_mod->mod_stats_lock);
break;
case FMD_CASE_CLOSE_WAIT:
fmd_case_hold(cp);
e = fmd_event_create(FMD_EVT_CLOSE, FMD_HRT_NOW, NULL, cp);
fmd_eventq_insert_at_head(cip->ci_mod->mod_queue, e);
(void) pthread_mutex_lock(&cip->ci_mod->mod_stats_lock);
cip->ci_mod->mod_stats->ms_caseclosed.fmds_value.ui64++;
(void) pthread_mutex_unlock(&cip->ci_mod->mod_stats_lock);
break;
case FMD_CASE_CLOSED:
nvl = fmd_case_mkevent(cp, FM_LIST_ISOLATED_CLASS);
(void) nvlist_lookup_string(nvl, FM_CLASS, &class);
e = fmd_event_create(FMD_EVT_PROTOCOL, FMD_HRT_NOW, nvl, class);
fmd_dispq_dispatch(fmd.d_disp, e, class);
break;
case FMD_CASE_REPAIRED:
nvl = fmd_case_mkevent(cp, FM_LIST_REPAIRED_CLASS);
(void) nvlist_lookup_string(nvl, FM_CLASS, &class);
e = fmd_event_create(FMD_EVT_PROTOCOL, FMD_HRT_NOW, nvl, class);
(void) pthread_rwlock_rdlock(&fmd.d_log_lock);
fmd_log_append(fmd.d_fltlog, e, cp);
(void) pthread_rwlock_unlock(&fmd.d_log_lock);
fmd_dispq_dispatch(fmd.d_disp, e, class);
break;
case FMD_CASE_RESOLVED:
nvl = fmd_case_mkevent(cp, FM_LIST_RESOLVED_CLASS);
(void) nvlist_lookup_string(nvl, FM_CLASS, &class);
e = fmd_event_create(FMD_EVT_PROTOCOL, FMD_HRT_NOW, nvl, class);
(void) pthread_rwlock_rdlock(&fmd.d_log_lock);
fmd_log_append(fmd.d_fltlog, e, cp);
(void) pthread_rwlock_unlock(&fmd.d_log_lock);
fmd_dispq_dispatch(fmd.d_disp, e, class);
break;
}
}
fmd_case_t *
fmd_case_hash_lookup(fmd_case_hash_t *chp, const char *uuid)
{
fmd_case_impl_t *cip;
uint_t h;
(void) pthread_rwlock_rdlock(&chp->ch_lock);
h = fmd_strhash(uuid) % chp->ch_hashlen;
for (cip = chp->ch_hash[h]; cip != NULL; cip = cip->ci_next) {
if (strcmp(cip->ci_uuid, uuid) == 0)
break;
}
if (cip != NULL)
cip = fmd_case_tryhold(cip);
if (cip == NULL)
(void) fmd_set_errno(EFMD_CASE_INVAL);
(void) pthread_rwlock_unlock(&chp->ch_lock);
return ((fmd_case_t *)cip);
}
static fmd_case_impl_t *
fmd_case_hash_insert(fmd_case_hash_t *chp, fmd_case_impl_t *cip)
{
fmd_case_impl_t *eip;
uint_t h;
(void) pthread_rwlock_wrlock(&chp->ch_lock);
h = fmd_strhash(cip->ci_uuid) % chp->ch_hashlen;
for (eip = chp->ch_hash[h]; eip != NULL; eip = eip->ci_next) {
if (strcmp(cip->ci_uuid, eip->ci_uuid) == 0 &&
fmd_case_tryhold(eip) != NULL) {
(void) pthread_rwlock_unlock(&chp->ch_lock);
return (eip);
}
}
cip->ci_next = chp->ch_hash[h];
chp->ch_hash[h] = cip;
chp->ch_count++;
ASSERT(chp->ch_count != 0);
(void) pthread_rwlock_unlock(&chp->ch_lock);
return (cip);
}
static void
fmd_case_hash_delete(fmd_case_hash_t *chp, fmd_case_impl_t *cip)
{
fmd_case_impl_t *cp, **pp;
uint_t h;
ASSERT(MUTEX_HELD(&cip->ci_lock));
cip->ci_flags |= FMD_CF_DELETING;
(void) pthread_mutex_unlock(&cip->ci_lock);
(void) pthread_rwlock_wrlock(&chp->ch_lock);
h = fmd_strhash(cip->ci_uuid) % chp->ch_hashlen;
pp = &chp->ch_hash[h];
for (cp = *pp; cp != NULL; cp = cp->ci_next) {
if (cp != cip)
pp = &cp->ci_next;
else
break;
}
if (cp == NULL) {
fmd_panic("case %p (%s) not found on hash chain %u\n",
(void *)cip, cip->ci_uuid, h);
}
*pp = cp->ci_next;
cp->ci_next = NULL;
fmd_case_code_hash_delete(chp, cip);
ASSERT(chp->ch_count != 0);
chp->ch_count--;
(void) pthread_rwlock_unlock(&chp->ch_lock);
(void) pthread_mutex_lock(&cip->ci_lock);
ASSERT(cip->ci_flags & FMD_CF_DELETING);
}
fmd_case_t *
fmd_case_create(fmd_module_t *mp, const char *uuidstr, void *data)
{
fmd_case_impl_t *cip = fmd_zalloc(sizeof (fmd_case_impl_t), FMD_SLEEP);
fmd_case_impl_t *eip = NULL;
uuid_t uuid;
(void) pthread_mutex_init(&cip->ci_lock, NULL);
fmd_buf_hash_create(&cip->ci_bufs);
fmd_module_hold(mp);
cip->ci_mod = mp;
cip->ci_refs = 1;
cip->ci_state = FMD_CASE_UNSOLVED;
cip->ci_flags = FMD_CF_DIRTY;
cip->ci_data = data;
(void) fmd_conf_getprop(fmd.d_conf, "uuidlen", &cip->ci_uuidlen);
cip->ci_uuid = fmd_zalloc(cip->ci_uuidlen + 1, FMD_SLEEP);
if (uuidstr == NULL) {
do {
if (eip != NULL)
fmd_case_rele((fmd_case_t *)eip);
uuid_generate(uuid);
uuid_unparse(uuid, cip->ci_uuid);
} while ((eip = fmd_case_hash_insert(fmd.d_cases, cip)) != cip);
} else {
(void) strncpy(cip->ci_uuid, uuidstr, cip->ci_uuidlen + 1);
if (fmd_case_hash_insert(fmd.d_cases, cip) != cip) {
fmd_free(cip->ci_uuid, cip->ci_uuidlen + 1);
(void) fmd_buf_hash_destroy(&cip->ci_bufs);
fmd_module_rele(mp);
pthread_mutex_destroy(&cip->ci_lock);
fmd_free(cip, sizeof (*cip));
return (NULL);
}
}
ASSERT(fmd_module_locked(mp));
fmd_list_append(&mp->mod_cases, cip);
fmd_module_setcdirty(mp);
(void) pthread_mutex_lock(&cip->ci_mod->mod_stats_lock);
cip->ci_mod->mod_stats->ms_caseopen.fmds_value.ui64++;
(void) pthread_mutex_unlock(&cip->ci_mod->mod_stats_lock);
return ((fmd_case_t *)cip);
}
static void
fmd_case_destroy_suspects(fmd_case_impl_t *cip)
{
fmd_case_susp_t *cis, *ncis;
ASSERT(MUTEX_HELD(&cip->ci_lock));
if (cip->ci_proxy_asru)
fmd_free(cip->ci_proxy_asru, sizeof (uint8_t) *
cip->ci_nsuspects);
nvlist_free(cip->ci_diag_de);
if (cip->ci_diag_asru)
fmd_free(cip->ci_diag_asru, sizeof (uint8_t) *
cip->ci_nsuspects);
for (cis = cip->ci_suspects; cis != NULL; cis = ncis) {
ncis = cis->cis_next;
nvlist_free(cis->cis_nvl);
fmd_free(cis, sizeof (fmd_case_susp_t));
}
cip->ci_suspects = NULL;
cip->ci_nsuspects = 0;
}
fmd_case_t *
fmd_case_recreate(fmd_module_t *mp, fmd_xprt_t *xp,
uint_t state, const char *uuid, const char *code)
{
fmd_case_impl_t *cip = fmd_zalloc(sizeof (fmd_case_impl_t), FMD_SLEEP);
fmd_case_impl_t *eip;
(void) pthread_mutex_init(&cip->ci_lock, NULL);
fmd_buf_hash_create(&cip->ci_bufs);
fmd_module_hold(mp);
cip->ci_mod = mp;
cip->ci_xprt = xp;
cip->ci_refs = 1;
cip->ci_state = state;
cip->ci_uuid = fmd_strdup(uuid, FMD_SLEEP);
cip->ci_uuidlen = strlen(cip->ci_uuid);
cip->ci_code = fmd_strdup(code, FMD_SLEEP);
cip->ci_codelen = cip->ci_code ? strlen(cip->ci_code) + 1 : 0;
if (state > FMD_CASE_CLOSE_WAIT)
cip->ci_flags |= FMD_CF_SOLVED;
if ((eip = fmd_case_hash_insert(fmd.d_cases, cip)) != cip) {
(void) pthread_mutex_lock(&cip->ci_lock);
cip->ci_refs--;
fmd_case_destroy((fmd_case_t *)cip, B_FALSE);
cip = eip;
(void) pthread_mutex_lock(&cip->ci_lock);
if (mp == fmd.d_rmod) {
if (cip->ci_state < FMD_CASE_CLOSED) {
if (code != NULL && cip->ci_code == NULL) {
cip->ci_code = fmd_strdup(code,
FMD_SLEEP);
cip->ci_codelen = cip->ci_code ?
strlen(cip->ci_code) + 1 : 0;
fmd_case_code_hash_insert(fmd.d_cases,
cip);
}
}
if ((cip->ci_state == FMD_CASE_REPAIRED ||
cip->ci_state == FMD_CASE_RESOLVED) &&
state == FMD_CASE_CLOSED)
cip->ci_state = FMD_CASE_CLOSED;
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_case_rele((fmd_case_t *)cip);
return ((fmd_case_t *)cip);
}
if (cip->ci_mod != fmd.d_rmod || xp != NULL) {
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_case_rele((fmd_case_t *)cip);
return (NULL);
}
fmd_module_lock(cip->ci_mod);
fmd_list_delete(&cip->ci_mod->mod_cases, cip);
fmd_module_unlock(cip->ci_mod);
fmd_module_rele(cip->ci_mod);
cip->ci_mod = mp;
fmd_module_hold(mp);
if (state == FMD_CASE_UNSOLVED) {
fmd_asru_hash_delete_case(fmd.d_asrus,
(fmd_case_t *)cip);
fmd_case_destroy_suspects(cip);
fmd_case_code_hash_delete(fmd.d_cases, cip);
fmd_free(cip->ci_code, cip->ci_codelen);
cip->ci_code = NULL;
cip->ci_codelen = 0;
cip->ci_tv_valid = 0;
}
cip->ci_state = state;
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_case_rele((fmd_case_t *)cip);
} else {
if (cip->ci_code)
fmd_case_code_hash_insert(fmd.d_cases, cip);
}
ASSERT(fmd_module_locked(mp));
fmd_list_append(&mp->mod_cases, cip);
(void) pthread_mutex_lock(&cip->ci_mod->mod_stats_lock);
cip->ci_mod->mod_stats->ms_caseopen.fmds_value.ui64++;
(void) pthread_mutex_unlock(&cip->ci_mod->mod_stats_lock);
return ((fmd_case_t *)cip);
}
void
fmd_case_destroy(fmd_case_t *cp, int visible)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_case_item_t *cit, *ncit;
ASSERT(MUTEX_HELD(&cip->ci_lock));
ASSERT(cip->ci_refs == 0);
if (visible) {
TRACE((FMD_DBG_CASE, "deleting case %s", cip->ci_uuid));
fmd_case_hash_delete(fmd.d_cases, cip);
}
for (cit = cip->ci_items; cit != NULL; cit = ncit) {
ncit = cit->cit_next;
fmd_event_rele(cit->cit_event);
fmd_free(cit, sizeof (fmd_case_item_t));
}
fmd_case_destroy_suspects(cip);
if (cip->ci_principal != NULL)
fmd_event_rele(cip->ci_principal);
fmd_free(cip->ci_uuid, cip->ci_uuidlen + 1);
fmd_free(cip->ci_code, cip->ci_codelen);
(void) fmd_buf_hash_destroy(&cip->ci_bufs);
fmd_module_rele(cip->ci_mod);
fmd_free(cip, sizeof (fmd_case_impl_t));
}
void
fmd_case_hold(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
(void) pthread_mutex_lock(&cip->ci_lock);
fmd_case_hold_locked(cp);
(void) pthread_mutex_unlock(&cip->ci_lock);
}
void
fmd_case_hold_locked(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
ASSERT(MUTEX_HELD(&cip->ci_lock));
if (cip->ci_flags & FMD_CF_DELETING)
fmd_panic("attempt to hold a deleting case %p (%s)\n",
(void *)cip, cip->ci_uuid);
cip->ci_refs++;
ASSERT(cip->ci_refs != 0);
}
static fmd_case_impl_t *
fmd_case_tryhold(fmd_case_impl_t *cip)
{
(void) pthread_mutex_lock(&cip->ci_lock);
if (cip->ci_flags & FMD_CF_DELETING) {
(void) pthread_mutex_unlock(&cip->ci_lock);
cip = NULL;
} else {
fmd_case_hold_locked((fmd_case_t *)cip);
(void) pthread_mutex_unlock(&cip->ci_lock);
}
return (cip);
}
void
fmd_case_rele(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
(void) pthread_mutex_lock(&cip->ci_lock);
ASSERT(cip->ci_refs != 0);
if (--cip->ci_refs == 0)
fmd_case_destroy((fmd_case_t *)cip, B_TRUE);
else
(void) pthread_mutex_unlock(&cip->ci_lock);
}
void
fmd_case_rele_locked(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
ASSERT(MUTEX_HELD(&cip->ci_lock));
--cip->ci_refs;
ASSERT(cip->ci_refs != 0);
}
int
fmd_case_insert_principal(void *cp, fmd_event_t *ep)
{
fmd_case_impl_t *cip = cp;
fmd_case_item_t *cit;
fmd_event_t *oep;
uint_t state;
int new;
fmd_event_hold(ep);
(void) pthread_mutex_lock(&cip->ci_lock);
if (cip->ci_flags & FMD_CF_SOLVED)
state = FMD_EVS_DIAGNOSED;
else
state = FMD_EVS_ACCEPTED;
oep = cip->ci_principal;
cip->ci_principal = ep;
for (cit = cip->ci_items; cit != NULL; cit = cit->cit_next) {
if (cit->cit_event == ep)
break;
}
cip->ci_flags |= FMD_CF_DIRTY;
new = cit == NULL && ep != oep;
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_module_setcdirty(cip->ci_mod);
fmd_event_transition(ep, state);
if (oep != NULL)
fmd_event_rele(oep);
return (new);
}
int
fmd_case_insert_event(void *cp, fmd_event_t *ep)
{
fmd_case_impl_t *cip = cp;
fmd_case_item_t *cit;
uint_t state;
int new;
boolean_t injected;
(void) pthread_mutex_lock(&cip->ci_lock);
if (cip->ci_flags & FMD_CF_SOLVED)
state = FMD_EVS_DIAGNOSED;
else
state = FMD_EVS_ACCEPTED;
for (cit = cip->ci_items; cit != NULL; cit = cit->cit_next) {
if (cit->cit_event == ep)
break;
}
new = cit == NULL && ep != cip->ci_principal;
if (cit != NULL || (cip->ci_flags & FMD_CF_SOLVED)) {
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_event_transition(ep, state);
return (new);
}
cit = fmd_alloc(sizeof (fmd_case_item_t), FMD_SLEEP);
fmd_event_hold(ep);
if (nvlist_lookup_boolean_value(((fmd_event_impl_t *)ep)->ev_nvl,
"__injected", &injected) == 0 && injected)
fmd_case_set_injected(cp);
cit->cit_next = cip->ci_items;
cit->cit_event = ep;
cip->ci_items = cit;
cip->ci_nitems++;
cip->ci_flags |= FMD_CF_DIRTY;
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_module_setcdirty(cip->ci_mod);
fmd_event_transition(ep, state);
return (new);
}
void
fmd_case_insert_suspect(fmd_case_t *cp, nvlist_t *nvl)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_case_susp_t *cis = fmd_alloc(sizeof (fmd_case_susp_t), FMD_SLEEP);
(void) pthread_mutex_lock(&cip->ci_lock);
ASSERT(cip->ci_state < FMD_CASE_CLOSE_WAIT);
cip->ci_flags |= FMD_CF_DIRTY;
cis->cis_next = cip->ci_suspects;
cis->cis_nvl = nvl;
cip->ci_suspects = cis;
cip->ci_nsuspects++;
(void) pthread_mutex_unlock(&cip->ci_lock);
if (cip->ci_xprt == NULL)
fmd_module_setcdirty(cip->ci_mod);
}
void
fmd_case_recreate_suspect(fmd_case_t *cp, nvlist_t *nvl)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_case_susp_t *cis = fmd_alloc(sizeof (fmd_case_susp_t), FMD_SLEEP);
boolean_t b;
(void) pthread_mutex_lock(&cip->ci_lock);
cis->cis_next = cip->ci_suspects;
cis->cis_nvl = nvl;
if (nvlist_lookup_boolean_value(nvl,
FM_SUSPECT_MESSAGE, &b) == 0 && b == B_FALSE)
cip->ci_flags |= FMD_CF_INVISIBLE;
cip->ci_suspects = cis;
cip->ci_nsuspects++;
(void) pthread_mutex_unlock(&cip->ci_lock);
}
void
fmd_case_reset_suspects(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
(void) pthread_mutex_lock(&cip->ci_lock);
ASSERT(cip->ci_state < FMD_CASE_SOLVED);
fmd_case_destroy_suspects(cip);
cip->ci_flags |= FMD_CF_DIRTY;
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_module_setcdirty(cip->ci_mod);
}
static void
fmd_case_unusable(fmd_asru_link_t *alp, void *arg)
{
(void) fmd_asru_setflags(alp, FMD_ASRU_UNUSABLE);
}
void
fmd_case_transition(fmd_case_t *cp, uint_t state, uint_t flags)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_case_item_t *cit;
fmd_event_t *e;
int resolved = 0;
int any_unusable_and_present = 0;
ASSERT(state <= FMD_CASE_RESOLVED);
(void) pthread_mutex_lock(&cip->ci_lock);
if (!(cip->ci_flags & FMD_CF_SOLVED) && !(flags & FMD_CF_SOLVED))
flags &= ~(FMD_CF_ISOLATED | FMD_CF_REPAIRED | FMD_CF_RESOLVED);
cip->ci_flags |= flags;
if (cip->ci_state >= state) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return;
}
TRACE((FMD_DBG_CASE, "case %s %s->%s", cip->ci_uuid,
_fmd_case_snames[cip->ci_state], _fmd_case_snames[state]));
cip->ci_state = state;
cip->ci_flags |= FMD_CF_DIRTY;
if (cip->ci_xprt == NULL && cip->ci_mod != fmd.d_rmod)
fmd_module_setcdirty(cip->ci_mod);
switch (state) {
case FMD_CASE_SOLVED:
for (cit = cip->ci_items; cit != NULL; cit = cit->cit_next)
fmd_event_transition(cit->cit_event, FMD_EVS_DIAGNOSED);
if (cip->ci_principal != NULL) {
fmd_event_transition(cip->ci_principal,
FMD_EVS_DIAGNOSED);
}
break;
case FMD_CASE_CLOSE_WAIT:
if ((cip->ci_flags & (FMD_CF_SOLVED | FMD_CF_ISOLATED |
FMD_CF_REPAIRED)) == (FMD_CF_SOLVED | FMD_CF_ISOLATED))
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_case_unusable, NULL);
if (fmd_case_orphaned(cp)) {
if (cip->ci_flags & FMD_CF_REPAIRED) {
state = cip->ci_state = FMD_CASE_REPAIRED;
TRACE((FMD_DBG_CASE, "case %s %s->%s",
cip->ci_uuid,
_fmd_case_snames[FMD_CASE_CLOSE_WAIT],
_fmd_case_snames[FMD_CASE_REPAIRED]));
goto do_repair;
} else {
state = cip->ci_state = FMD_CASE_CLOSED;
TRACE((FMD_DBG_CASE, "case %s %s->%s",
cip->ci_uuid,
_fmd_case_snames[FMD_CASE_CLOSE_WAIT],
_fmd_case_snames[FMD_CASE_CLOSED]));
}
}
break;
case FMD_CASE_REPAIRED:
do_repair:
ASSERT(cip->ci_xprt != NULL || fmd_case_orphaned(cp));
if (flags & FMD_CF_RESOLVED) {
if (cip->ci_xprt != NULL)
fmd_list_delete(&cip->ci_mod->mod_cases, cip);
} else {
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_case_unusable_and_present,
&any_unusable_and_present);
if (any_unusable_and_present)
break;
if (cip->ci_xprt != NULL) {
fmd_xprt_uuresolved(cip->ci_xprt, cip->ci_uuid);
break;
}
}
cip->ci_state = FMD_CASE_RESOLVED;
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_case_publish(cp, state);
TRACE((FMD_DBG_CASE, "case %s %s->%s", cip->ci_uuid,
_fmd_case_snames[FMD_CASE_REPAIRED],
_fmd_case_snames[FMD_CASE_RESOLVED]));
state = FMD_CASE_RESOLVED;
resolved = 1;
(void) pthread_mutex_lock(&cip->ci_lock);
break;
case FMD_CASE_RESOLVED:
if (cip->ci_xprt != NULL) {
fmd_list_delete(&cip->ci_mod->mod_cases, cip);
resolved = 1;
break;
}
ASSERT(fmd_case_orphaned(cp));
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_case_unusable_and_present, &any_unusable_and_present);
if (any_unusable_and_present) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return;
}
resolved = 1;
break;
}
(void) pthread_mutex_unlock(&cip->ci_lock);
if (cip->ci_mod->mod_flags & FMD_MOD_INIT)
fmd_case_publish(cp, state);
else {
fmd_case_hold(cp);
e = fmd_event_create(FMD_EVT_PUBLISH, FMD_HRT_NOW, NULL, cp);
fmd_eventq_insert_at_head(cip->ci_mod->mod_queue, e);
}
if (resolved) {
if (cip->ci_xprt != NULL) {
(void) pthread_mutex_lock(&cip->ci_lock);
fmd_asru_hash_delete_case(fmd.d_asrus, cp);
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_case_rele(cp);
} else {
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_asru_log_resolved, NULL);
(void) pthread_mutex_lock(&cip->ci_lock);
cip->ci_flags |= FMD_CF_RES_CMPL;
(void) pthread_mutex_unlock(&cip->ci_lock);
}
}
}
void
fmd_case_discard_resolved(fmd_case_t *cp, void *arg)
{
int check_if_aged = *(int *)arg;
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
(void) pthread_mutex_lock(&cip->ci_lock);
if (!(cip->ci_flags & FMD_CF_RES_CMPL)) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return;
}
if (check_if_aged) {
int aged = 1;
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_asru_check_if_aged, &aged);
if (!aged) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return;
}
}
fmd_module_lock(cip->ci_mod);
fmd_list_delete(&cip->ci_mod->mod_cases, cip);
fmd_module_unlock(cip->ci_mod);
fmd_asru_hash_delete_case(fmd.d_asrus, cp);
cip->ci_flags &= ~FMD_CF_RES_CMPL;
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_case_rele(cp);
}
void
fmd_case_transition_update(fmd_case_t *cp, uint_t state, uint_t flags)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
int usable = 0;
ASSERT(state >= FMD_CASE_SOLVED);
(void) pthread_mutex_lock(&cip->ci_lock);
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_case_usable, &usable);
(void) pthread_mutex_unlock(&cip->ci_lock);
if (!usable) {
state = MAX(state, FMD_CASE_CLOSE_WAIT);
flags |= FMD_CF_ISOLATED;
}
fmd_case_transition(cp, state, flags);
}
void
fmd_case_setdirty(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
(void) pthread_mutex_lock(&cip->ci_lock);
cip->ci_flags |= FMD_CF_DIRTY;
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_module_setcdirty(cip->ci_mod);
}
void
fmd_case_clrdirty(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
(void) pthread_mutex_lock(&cip->ci_lock);
cip->ci_flags &= ~FMD_CF_DIRTY;
(void) pthread_mutex_unlock(&cip->ci_lock);
}
void
fmd_case_commit(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_case_item_t *cit;
(void) pthread_mutex_lock(&cip->ci_lock);
if (cip->ci_flags & FMD_CF_DIRTY) {
for (cit = cip->ci_items; cit != NULL; cit = cit->cit_next)
fmd_event_commit(cit->cit_event);
if (cip->ci_principal != NULL)
fmd_event_commit(cip->ci_principal);
fmd_buf_hash_commit(&cip->ci_bufs);
cip->ci_flags &= ~FMD_CF_DIRTY;
}
(void) pthread_mutex_unlock(&cip->ci_lock);
}
void
fmd_case_xprt_updated(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
nvlist_t **nva;
uint8_t *ba;
int msg = B_TRUE;
int count = 0;
fmd_case_lst_t fcl;
ASSERT(cip->ci_xprt != NULL);
(void) pthread_mutex_lock(&cip->ci_lock);
ba = alloca(sizeof (uint8_t) * cip->ci_nsuspects);
nva = alloca(sizeof (nvlist_t *) * cip->ci_nsuspects);
fcl.fcl_countp = &count;
fcl.fcl_maxcount = cip->ci_nsuspects;
fcl.fcl_msgp = &msg;
fcl.fcl_ba = ba;
fcl.fcl_nva = nva;
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_case_set_lst, &fcl);
(void) pthread_mutex_unlock(&cip->ci_lock);
fmd_xprt_updated(cip->ci_xprt, cip->ci_uuid, ba, cip->ci_proxy_asru,
count);
}
void
fmd_case_update_status(fmd_case_t *cp, uint8_t *statusp, uint8_t *proxy_asrup,
uint8_t *diag_asrup)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
int count = 0;
fmd_asru_update_status_t faus;
faus.faus_countp = &count;
faus.faus_maxcount = cip->ci_nsuspects;
faus.faus_ba = statusp;
faus.faus_proxy_asru = proxy_asrup;
faus.faus_diag_asru = diag_asrup;
faus.faus_is_proxy = (cip->ci_xprt != NULL);
(void) pthread_mutex_lock(&cip->ci_lock);
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_asru_update_status,
&faus);
(void) pthread_mutex_unlock(&cip->ci_lock);
}
void
fmd_case_update_containees(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
(void) pthread_mutex_lock(&cip->ci_lock);
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_asru_update_containees, NULL);
(void) pthread_mutex_unlock(&cip->ci_lock);
}
void
fmd_case_close_status(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
int count = 0;
fmd_asru_close_status_t facs;
facs.facs_countp = &count;
facs.facs_maxcount = cip->ci_nsuspects;
(void) pthread_mutex_lock(&cip->ci_lock);
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_asru_close_status,
&facs);
(void) pthread_mutex_unlock(&cip->ci_lock);
}
void
fmd_case_update(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
uint_t cstate;
int faulty = 0;
(void) pthread_mutex_lock(&cip->ci_lock);
cstate = cip->ci_state;
if (cip->ci_state < FMD_CASE_SOLVED) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return;
}
if (cip->ci_flags & FMD_CF_REPAIRED) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return;
}
TRACE((FMD_DBG_CASE, "case update %s", cip->ci_uuid));
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_case_faulty, &faulty);
(void) pthread_mutex_unlock(&cip->ci_lock);
if (faulty) {
nvlist_t *nvl;
fmd_event_t *e;
char *class;
TRACE((FMD_DBG_CASE, "sending list.updated %s", cip->ci_uuid));
nvl = fmd_case_mkevent(cp, FM_LIST_UPDATED_CLASS);
(void) nvlist_lookup_string(nvl, FM_CLASS, &class);
e = fmd_event_create(FMD_EVT_PROTOCOL, FMD_HRT_NOW, nvl, class);
(void) pthread_rwlock_rdlock(&fmd.d_log_lock);
fmd_log_append(fmd.d_fltlog, e, cp);
(void) pthread_rwlock_unlock(&fmd.d_log_lock);
fmd_dispq_dispatch(fmd.d_disp, e, class);
return;
}
if (cstate == FMD_CASE_CLOSED)
fmd_case_transition(cp, FMD_CASE_REPAIRED, FMD_CF_REPAIRED);
else
fmd_case_transition(cp, FMD_CASE_CLOSE_WAIT, FMD_CF_REPAIRED);
}
void
fmd_case_delete(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_modstat_t *msp;
size_t buftotal;
TRACE((FMD_DBG_CASE, "case delete %s", cip->ci_uuid));
ASSERT(fmd_module_locked(cip->ci_mod));
fmd_list_delete(&cip->ci_mod->mod_cases, cip);
buftotal = fmd_buf_hash_destroy(&cip->ci_bufs);
(void) pthread_mutex_lock(&cip->ci_mod->mod_stats_lock);
msp = cip->ci_mod->mod_stats;
ASSERT(msp->ms_caseopen.fmds_value.ui64 != 0);
msp->ms_caseopen.fmds_value.ui64--;
ASSERT(msp->ms_buftotal.fmds_value.ui64 >= buftotal);
msp->ms_buftotal.fmds_value.ui64 -= buftotal;
(void) pthread_mutex_unlock(&cip->ci_mod->mod_stats_lock);
if (cip->ci_xprt == NULL)
fmd_module_setcdirty(cip->ci_mod);
fmd_module_rele(cip->ci_mod);
cip->ci_mod = fmd.d_rmod;
fmd_module_hold(cip->ci_mod);
if (cip->ci_flags & FMD_CF_SOLVED) {
fmd_module_lock(cip->ci_mod);
fmd_list_append(&cip->ci_mod->mod_cases, cip);
fmd_module_unlock(cip->ci_mod);
fmd_case_hold(cp);
}
if (cip->ci_flags & FMD_CF_REPAIRED)
fmd_case_transition(cp, FMD_CASE_REPAIRED, 0);
else if (cip->ci_flags & FMD_CF_ISOLATED) {
fmd_case_transition(cp, FMD_CASE_CLOSED, 0);
if (cip->ci_xprt != NULL)
fmd_xprt_uuclose(cip->ci_xprt, cip->ci_uuid);
}
fmd_case_rele(cp);
}
void
fmd_case_discard(fmd_case_t *cp, boolean_t delete_from_asru_cache)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
(void) pthread_mutex_lock(&cip->ci_mod->mod_stats_lock);
cip->ci_mod->mod_stats->ms_caseopen.fmds_value.ui64--;
(void) pthread_mutex_unlock(&cip->ci_mod->mod_stats_lock);
ASSERT(fmd_module_locked(cip->ci_mod));
fmd_list_delete(&cip->ci_mod->mod_cases, cip);
if (delete_from_asru_cache) {
(void) pthread_mutex_lock(&cip->ci_lock);
fmd_asru_hash_delete_case(fmd.d_asrus, cp);
(void) pthread_mutex_unlock(&cip->ci_lock);
}
fmd_case_rele(cp);
}
int
fmd_case_repair(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
uint_t cstate;
fmd_asru_rep_arg_t fara;
(void) pthread_mutex_lock(&cip->ci_lock);
cstate = cip->ci_state;
if (cstate < FMD_CASE_SOLVED) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return (fmd_set_errno(EFMD_CASE_STATE));
}
if (cip->ci_flags & FMD_CF_REPAIRED) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return (0);
}
TRACE((FMD_DBG_CASE, "case repair %s", cip->ci_uuid));
fara.fara_reason = FMD_ASRU_REPAIRED;
fara.fara_bywhat = FARA_BY_CASE;
fara.fara_rval = NULL;
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_asru_repaired, &fara);
(void) pthread_mutex_unlock(&cip->ci_lock);
if (cip->ci_xprt != NULL) {
fmd_case_xprt_updated(cp);
return (0);
}
if (cstate == FMD_CASE_CLOSED)
fmd_case_transition(cp, FMD_CASE_REPAIRED, FMD_CF_REPAIRED);
else
fmd_case_transition(cp, FMD_CASE_CLOSE_WAIT, FMD_CF_REPAIRED);
return (0);
}
int
fmd_case_acquit(fmd_case_t *cp)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
uint_t cstate;
fmd_asru_rep_arg_t fara;
(void) pthread_mutex_lock(&cip->ci_lock);
cstate = cip->ci_state;
if (cstate < FMD_CASE_SOLVED) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return (fmd_set_errno(EFMD_CASE_STATE));
}
if (cip->ci_flags & FMD_CF_REPAIRED) {
(void) pthread_mutex_unlock(&cip->ci_lock);
return (0);
}
TRACE((FMD_DBG_CASE, "case acquit %s", cip->ci_uuid));
fara.fara_reason = FMD_ASRU_ACQUITTED;
fara.fara_bywhat = FARA_BY_CASE;
fara.fara_rval = NULL;
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_asru_repaired, &fara);
(void) pthread_mutex_unlock(&cip->ci_lock);
if (cip->ci_xprt != NULL) {
fmd_case_xprt_updated(cp);
return (0);
}
if (cstate == FMD_CASE_CLOSED)
fmd_case_transition(cp, FMD_CASE_REPAIRED, FMD_CF_REPAIRED);
else
fmd_case_transition(cp, FMD_CASE_CLOSE_WAIT, FMD_CF_REPAIRED);
return (0);
}
int
fmd_case_contains(fmd_case_t *cp, fmd_event_t *ep)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
fmd_case_item_t *cit;
uint_t state;
int rv = 0;
(void) pthread_mutex_lock(&cip->ci_lock);
if (cip->ci_state >= FMD_CASE_SOLVED)
state = FMD_EVS_DIAGNOSED;
else
state = FMD_EVS_ACCEPTED;
for (cit = cip->ci_items; cit != NULL; cit = cit->cit_next) {
if ((rv = fmd_event_equal(ep, cit->cit_event)) != 0)
break;
}
if (rv == 0 && cip->ci_principal != NULL)
rv = fmd_event_equal(ep, cip->ci_principal);
(void) pthread_mutex_unlock(&cip->ci_lock);
if (rv != 0)
fmd_event_transition(ep, state);
return (rv);
}
int
fmd_case_orphaned(fmd_case_t *cp)
{
return (((fmd_case_impl_t *)cp)->ci_mod == fmd.d_rmod);
}
void
fmd_case_settime(fmd_case_t *cp, time_t tv_sec, suseconds_t tv_usec)
{
((fmd_case_impl_t *)cp)->ci_tv.tv_sec = tv_sec;
((fmd_case_impl_t *)cp)->ci_tv.tv_usec = tv_usec;
((fmd_case_impl_t *)cp)->ci_tv_valid = 1;
}
void
fmd_case_set_injected(fmd_case_t *cp)
{
((fmd_case_impl_t *)cp)->ci_injected = 1;
}
void
fmd_case_set_de_fmri(fmd_case_t *cp, nvlist_t *nvl)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
nvlist_free(cip->ci_diag_de);
cip->ci_diag_de = nvl;
}
void
fmd_case_setcode(fmd_case_t *cp, char *code)
{
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
cip->ci_code = fmd_strdup(code, FMD_SLEEP);
cip->ci_codelen = cip->ci_code ? strlen(cip->ci_code) + 1 : 0;
}
static void
fmd_case_repair_replay_case(fmd_case_t *cp, void *arg)
{
int not_faulty = 0;
int faulty = 0;
nvlist_t *nvl;
fmd_event_t *e;
char *class;
int any_unusable_and_present = 0;
fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
if (cip->ci_state < FMD_CASE_SOLVED || cip->ci_xprt != NULL)
return;
if (cip->ci_state == FMD_CASE_RESOLVED) {
cip->ci_flags |= FMD_CF_RES_CMPL;
return;
}
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_case_faulty, &faulty);
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp, fmd_case_not_faulty,
¬_faulty);
if (cip->ci_state >= FMD_CASE_REPAIRED && !faulty) {
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_case_unusable_and_present, &any_unusable_and_present);
if (!any_unusable_and_present) {
cip->ci_state = FMD_CASE_RESOLVED;
TRACE((FMD_DBG_CASE, "replay sending list.repaired %s",
cip->ci_uuid));
nvl = fmd_case_mkevent(cp, FM_LIST_REPAIRED_CLASS);
(void) nvlist_lookup_string(nvl, FM_CLASS, &class);
e = fmd_event_create(FMD_EVT_PROTOCOL, FMD_HRT_NOW, nvl,
class);
fmd_dispq_dispatch(fmd.d_disp, e, class);
TRACE((FMD_DBG_CASE, "replay sending list.resolved %s",
cip->ci_uuid));
fmd_case_publish(cp, FMD_CASE_RESOLVED);
fmd_asru_hash_apply_by_case(fmd.d_asrus, cp,
fmd_asru_log_resolved, NULL);
cip->ci_flags |= FMD_CF_RES_CMPL;
} else {
TRACE((FMD_DBG_CASE, "replay sending list.repaired %s",
cip->ci_uuid));
nvl = fmd_case_mkevent(cp, FM_LIST_REPAIRED_CLASS);
(void) nvlist_lookup_string(nvl, FM_CLASS, &class);
e = fmd_event_create(FMD_EVT_PROTOCOL, FMD_HRT_NOW, nvl,
class);
fmd_dispq_dispatch(fmd.d_disp, e, class);
}
} else if (faulty && not_faulty) {
TRACE((FMD_DBG_CASE, "replay sending list.updated %s",
cip->ci_uuid));
nvl = fmd_case_mkevent(cp, FM_LIST_UPDATED_CLASS);
(void) nvlist_lookup_string(nvl, FM_CLASS, &class);
e = fmd_event_create(FMD_EVT_PROTOCOL, FMD_HRT_NOW, nvl, class);
fmd_dispq_dispatch(fmd.d_disp, e, class);
}
}
void
fmd_case_repair_replay()
{
fmd_case_hash_apply(fmd.d_cases, fmd_case_repair_replay_case, NULL);
}