# # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # SUBDIRS = common include ../Makefile.subdirs # # 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 2015 RackTop Systems. # .KEEP_STATE: .SUFFIXES: include $(SRC)/cmd/Makefile.cmd MODCLASS = plugins # # Set PROG and OBJS based on the values of MODULE and SRCS. We expect that # these macros to be defined by the Makefile that is including this file. # PROG = $(MODULE:%=%.so) YOBJS = $(YSRCS:%.y=%.o) OBJS = $(YOBJS) $(SRCS:%.c=%.o) CONF = $(MODULE:%=%.conf) # # We may have sources from directories other than the current, but # we build all objects in the current directory (not necessarily in the # directory of its source). # Hammerhead: Use GNU Make $(shell ...) instead of dmake :sh modifier LINKOBJS = $(shell echo $(OBJS) | sed 's!\.\./[a-z_/]*/!!g') # # A module may set DMOD and DMOD_SRCS if it has a mdb proc module. # DMOD, if set, must match PROG above (for mdb autoloading) so it will # be built in a subdirectory. # ROOTDMOD = $(DMOD:%.so=$(ROOT)/usr/lib/mdb/proc/%.so) DMODPROG = $(DMOD:%=dmod/%) DMOD_OBJS = $(DMOD_SRCS:%.c=%.o) # # Set ROOTPROG and ROOTCONF based on the values of MODULE, CLASS, and PLATFORMS # We expect these macros to be defined by the Makefile that is including us. # common_ROOTPROG = $(ROOT)/usr/lib/fm/fmd/plugins/$(PROG) arch_ROOTPROG = $(ROOT)/usr/platform/$(ARCH)/lib/fm/fmd/plugins/$(PROG) plat_ROOTPROG = $(PLATFORMS:%=$(ROOT)/usr/platform/%/lib/fm/fmd/plugins/$(PROG)) ROOTPROG = $($(CLASS)_ROOTPROG) common_ROOTCONF = $(ROOT)/usr/lib/fm/fmd/plugins/$(CONF) arch_ROOTCONF = $(ROOT)/usr/platform/$(ARCH)/lib/fm/fmd/plugins/$(CONF) plat_ROOTCONF = $(PLATFORMS:%=$(ROOT)/usr/platform/%/lib/fm/fmd/plugins/$(CONF)) ROOTCONF = $($(CLASS)_ROOTCONF) APIMAP = $(SRC)/cmd/fm/fmd/common/fmd_api.map FMRIMAP = $(SRC)/cmd/fm/fmd/common/fmd_fmri.map CFLAGS += $(CTF_FLAGS) $(CCVERBOSE) $(CC_PICFLAGS) CFLAGS64 += $(CTF_FLAGS_64) $(CCVERBOSE) $(CC_PICFLAGS) CFLAGS += $(GSHARED) CPPFLAGS += -D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT # Hammerhead: FMD plugins are dlopen'd at runtime - disable strict symbol # checking for GNU ld (undefined symbols resolved by fmd at load time) ZDEFS = LDFLAGS += $(ZDEFS) $(ZTEXT) $(ZIGNORE) MAPFILE-DMOD = $(SRC)/cmd/mdb/common/modules/conf/mapfile-extern $(PROG) : LDFLAGS += -Wl,-M$(APIMAP) -Wl,-M$(FMRIMAP) $(PROG) : LDLIBS += -lnvpair -lc all: $(PROG) $(DMODPROG) .NOTPARALLEL: .PARALLEL: $(OBJS) $(DMOD_OBJS) $(PROG): $(OBJS) $(APIMAP) $(LINK.c) $(LINKOBJS) -o $@ $(LDLIBS) $(CTFMERGE) -L VERSION -o $@ $(LINKOBJS) $(POST_PROCESS_SO) $(DMODPROG): $(DMOD_OBJS) $(MAPFILE-DMOD) -@mkdir -p $(@D) $(LINK.c) $(DMOD_OBJS) $(MAPFILE-DMOD:%=-Wl,-M%) -o $@ $(LDLIBS) -lc $(POST_PROCESS) %.o: %.c $(COMPILE.c) $< -o $(@F) $(CTFCONVERT) $(CTFCVTFLAGS) $(@F) clean: $(RM) $(OBJS) $(DMOD_OBJS) $(CLEANFILES) $(LINKOBJS) clobber: clean $(RM) $(PROG) $(DMODPROG) install_h: # Hammerhead: .SECONDEXPANSION required for $$(@D) to resolve target directory .SECONDEXPANSION: $(ROOTPROG): $$(@D) $(PROG) $(RM) $@; $(INS) -s -m 0555 -f $(@D) $(PROG) $(ROOTCONF): $$(@D) $(CONF) $(RM) $@; $(INS) -s -m 0644 -f $(@D) $(CONF) $(ROOTDMOD): $$(@D) $(DMODPROG) $(RM) $@; $(INS) -s -m 0555 -f $(@D) $(DMODPROG) install: $(ROOTPROG) $(ROOTCONF) $(ROOTDMOD) include $(SRC)/cmd/fm/modules/Makefile.rootdirs # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" # # Define the transitive set of rules to create a common module's install dir # within the proto area. This is used by all Makefile. files. # $(ROOT)/usr/lib/fm: $(INS.dir) $(ROOT)/usr/lib/fm/fmd: $(ROOT)/usr/lib/fm $(INS.dir) $(ROOT)/usr/lib/fm/fmd/%: $(ROOT)/usr/lib/fm/fmd $(INS.dir) # # Define the transitive set of rules to create a platform module's install dir # within the proto area. This is used by all Makefile. files. # $(ROOT)/usr/platform/%/lib/fm: $(INS.dir) $(ROOT)/usr/platform/%/lib/fm/fmd: $(ROOT)/usr/platform/%/lib/fm $(INS.dir) $(ROOT)/usr/platform/%/lib/fm/fmd/$(MODCLASS): $(ROOT)/usr/platform/%/lib/fm/fmd $(INS.dir) # # 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. # SUBDIRS = cpumem-retire \ disk-lights \ disk-monitor \ disk-transport \ eversholt \ ext-event-transport \ fabric-xlate \ fdd-msg \ io-retire \ ip-transport \ sensor-transport \ ses-log-transport \ sw-diag-response \ sp-monitor \ syslog-msgs \ zfs-diagnosis \ zfs-retire include ../../Makefile.subdirs # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2022 Garrett D'Amore # MODULE = cpumem-retire CLASS = common SRCS = \ cma_main.c \ cma_cpu.c \ cma_cpu_arch.c \ cma_page.c \ cma_page_arch.c include ../../Makefile.plugin CMA_VERSION = "1.1" INCDIRS = . CPPFLAGS += $(INCDIRS:%=-I%) -DCMA_VERSION='$(CMA_VERSION)' LDFLAGS += -R/usr/lib/fm LDLIBS += -L$(ROOTLIB)/fm -lfmd_agent /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _CMA_H #define _CMA_H #include #include #ifdef __cplusplus extern "C" { #endif #define CMA_RA_SUCCESS 0 #define CMA_RA_FAILURE 1 #ifdef opl #define FM_FMRI_HC_CPUIDS "hc-xscf-cpuids" #endif #ifdef i386 extern boolean_t cma_is_native; #endif typedef struct cma_page { struct cma_page *pg_next; /* List of page retirements for retry */ nvlist_t *pg_rsrc; /* Resource for this page */ nvlist_t *pg_asru; /* ASRU for this page */ uint64_t pg_addr; /* Address of this page */ char *pg_uuid; /* UUID for this page's case */ uint_t pg_nretries; /* Number of retries so far for page */ } cma_page_t; #ifdef sun4v typedef struct cma_cpu { struct cma_cpu *cpu_next; /* List of cpus */ nvlist_t *cpu_fmri; /* FMRI for this cpu entry */ int cpuid; /* physical id of this cpu */ char *cpu_uuid; /* UUID for this cpu's case */ uint_t cpu_nretries; /* Number of retries so far for cpu */ } cma_cpu_t; #endif /* sun4v */ typedef struct cma { struct timespec cma_cpu_delay; /* CPU offline retry interval */ uint_t cma_cpu_tries; /* Number of CPU offline retries */ uint_t cma_cpu_dooffline; /* Whether to offline CPUs */ uint_t cma_cpu_forcedoffline; /* Whether to do forced CPU offline */ uint_t cma_cpu_doonline; /* Whether to online CPUs */ uint_t cma_cpu_doblacklist; /* Whether to blacklist CPUs */ uint_t cma_cpu_dounblacklist; /* Whether to unblacklist CPUs */ cma_page_t *cma_pages; /* List of page retirements for retry */ hrtime_t cma_page_curdelay; /* Current retry sleep interval */ hrtime_t cma_page_mindelay; /* Minimum retry sleep interval */ hrtime_t cma_page_maxdelay; /* Maximum retry sleep interval */ id_t cma_page_timerid; /* fmd timer ID for retry sleep */ uint_t cma_page_doretire; /* Whether to retire pages */ uint_t cma_page_dounretire; /* Whether to unretire pages */ #ifdef sun4v cma_cpu_t *cma_cpus; /* List of cpus */ hrtime_t cma_cpu_curdelay; /* Current retry sleep interval */ hrtime_t cma_cpu_mindelay; /* Minimum retry sleep interval */ hrtime_t cma_cpu_maxdelay; /* Maximum retry sleep interval */ id_t cma_cpu_timerid; /* LDOM cpu timer */ #endif /* sun4v */ } cma_t; typedef struct cma_stats { fmd_stat_t cpu_flts; /* Successful offlines */ fmd_stat_t cpu_repairs; /* Successful onlines */ fmd_stat_t cpu_fails; /* Failed offlines/onlines */ fmd_stat_t cpu_blfails; /* Failed blacklists */ fmd_stat_t cpu_supp; /* Suppressed offlines/onlines */ fmd_stat_t cpu_blsupp; /* Suppressed blacklists */ fmd_stat_t page_flts; /* Successful page retires */ fmd_stat_t page_repairs; /* Successful page unretires */ fmd_stat_t page_fails; /* Failed page retires/unretires */ fmd_stat_t page_supp; /* Suppressed retires/unretires */ fmd_stat_t page_nonent; /* Retires for non-present pages */ fmd_stat_t bad_flts; /* Malformed faults */ fmd_stat_t nop_flts; /* Inapplicable faults */ fmd_stat_t auto_flts; /* Auto-close faults */ } cma_stats_t; extern cma_stats_t cma_stats; extern cma_t cma; extern int cma_cpu_cpu_retire(fmd_hdl_t *, nvlist_t *, nvlist_t *, const char *, boolean_t); extern int cma_cpu_hc_retire(fmd_hdl_t *, nvlist_t *, nvlist_t *, const char *, boolean_t); extern int cma_page_retire(fmd_hdl_t *, nvlist_t *, nvlist_t *, const char *, boolean_t); extern void cma_page_retry(fmd_hdl_t *); extern void cma_page_fini(fmd_hdl_t *); extern int cma_set_errno(int); extern int cma_cache_way_retire(fmd_hdl_t *, nvlist_t *, nvlist_t *, const char *, boolean_t); /* * Platforms may have their own implementations of these functions */ extern int cma_cpu_blacklist(fmd_hdl_t *, nvlist_t *, nvlist_t *, boolean_t); extern int cma_cpu_statechange(fmd_hdl_t *, nvlist_t *, const char *, int, boolean_t); extern int cma_fmri_page_service_state(fmd_hdl_t *, nvlist_t *); extern int cma_fmri_page_retire(fmd_hdl_t *, nvlist_t *); extern int cma_fmri_page_unretire(fmd_hdl_t *, nvlist_t *); #ifdef sun4v extern void cma_cpu_start_retry(fmd_hdl_t *, nvlist_t *, const char *, boolean_t); extern void cma_cpu_fini(fmd_hdl_t *); extern void cma_cpu_retry(fmd_hdl_t *); #endif /* sun4v */ extern const char *p_online_state_fmt(int); #ifdef __cplusplus } #endif #endif /* _CMA_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include static int cpu_statechange(fmd_hdl_t *, nvlist_t *, nvlist_t *, const char *, uint32_t, boolean_t); #ifndef opl /* * Perform retire/unretire by invoking the topo methods registered in the * hc-scheme resource. * * If the fault is found to be diagnosed under the old topology, the resource * will not exist in the current topology, then we fall back to legacy retire * (using the "cpu" scheme ASRU). */ static boolean_t old_topo_fault(nvlist_t *nvl) { nvlist_t *rsrc; #ifdef i386 nvlist_t **hcl; uint_t nhcl = 0; char *name; #endif if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &rsrc) != 0) return (B_TRUE); #ifdef i386 /* * x86 has moved from "motherboard/chip/cpu" topo to * "motherboard/chip/core/strand" */ if (nvlist_lookup_nvlist_array(rsrc, FM_FMRI_HC_LIST, &hcl, &nhcl) == 0 && nhcl == 3 && nvlist_lookup_string(hcl[0], FM_FMRI_HC_NAME, &name) == 0 && strcmp(name, "motherboard") == 0 && nvlist_lookup_string(hcl[1], FM_FMRI_HC_NAME, &name) == 0 && strcmp(name, "chip") == 0 && nvlist_lookup_string(hcl[2], FM_FMRI_HC_NAME, &name) == 0 && strcmp(name, "cpu") == 0) return (B_TRUE); #endif return (B_FALSE); } /* ARGSUSED */ int cma_cpu_hc_retire(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, const char *uuid, boolean_t repair) { int i, err; int rc = CMA_RA_SUCCESS; nvlist_t *rsrc; /* * For the cached faults which were diagnosed under the old * topology, we fall back to retire by using cpu-scheme ASRUs. * Under xVM Dom0, since logic cpuid in "cpu" scheme ASRU makes no * sense, the fault should be ignored. */ if (old_topo_fault(nvl)) { #ifdef i386 if (! cma_is_native) return (CMA_RA_FAILURE); #endif return (cma_cpu_cpu_retire(hdl, nvl, asru, uuid, repair)); } /* * Lookup the resource and call its topo methods to do retire/unretire */ if ((! repair && ! cma.cma_cpu_dooffline) || (repair && ! cma.cma_cpu_doonline)) { fmd_hdl_debug(hdl, "suppressed %s of CPU\n", repair ? "unretire" : "retire"); cma_stats.cpu_supp.fmds_value.ui64++; } else { err = FMD_AGENT_RETIRE_FAIL; if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &rsrc) == 0) { if (repair) { err = fmd_nvl_fmri_unretire(hdl, rsrc); } else { for (i = 0; i < cma.cma_cpu_tries; i++) { err = fmd_nvl_fmri_retire(hdl, rsrc); if (err == FMD_AGENT_RETIRE_DONE) break; (void) nanosleep(&cma.cma_cpu_delay, NULL); } } } if (err == FMD_AGENT_RETIRE_DONE) { if (repair) cma_stats.cpu_repairs.fmds_value.ui64++; else cma_stats.cpu_flts.fmds_value.ui64++; } else { rc = CMA_RA_FAILURE; cma_stats.bad_flts.fmds_value.ui64++; #ifdef sun4v /* libldom requests are processed asynchronously */ cma_cpu_start_retry(hdl, nvl, uuid, repair); #endif } } if ((! repair && ! cma.cma_cpu_doblacklist) || (repair && ! cma.cma_cpu_dounblacklist)) { fmd_hdl_debug(hdl, "suppressed %s of CPU\n", repair ? "unblacklist" : "blacklist"); cma_stats.cpu_blsupp.fmds_value.ui64++; } else { if (cma_cpu_blacklist(hdl, nvl, asru, repair) < 0) cma_stats.cpu_blfails.fmds_value.ui64++; } return (rc); } #else /* opl */ /* ARGSUSED 4 */ int cma_cpu_hc_retire(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, const char *uuid, boolean_t repair) { uint_t cpuid; uint_t i, nprs; nvlist_t **hc_prs = NULL, *hc_spec_nvl; /* OPL has ASRU in "hc" scheme */ if (nvlist_lookup_nvlist(asru, FM_FMRI_HC_SPECIFIC, &hc_spec_nvl) != 0) { cma_stats.bad_flts.fmds_value.ui64++; fmd_hdl_debug(hdl, "cma_cpu_hc_retire lookup hc_spec_nvl failed\n"); return (CMA_RA_FAILURE); } if (nvlist_lookup_nvlist_array(hc_spec_nvl, FM_FMRI_HC_CPUIDS, &hc_prs, &nprs) != 0) { cma_stats.bad_flts.fmds_value.ui64++; fmd_hdl_debug(hdl, "cma_cpu_hc_retire lookup cpuid array failed\n"); return (CMA_RA_FAILURE); } for (i = 0; i < nprs; i++) { if (nvlist_lookup_uint32(hc_prs[i], FM_FMRI_CPU_ID, &cpuid) != 0) { cma_stats.bad_flts.fmds_value.ui64++; return (CMA_RA_FAILURE); } if (cpu_statechange(hdl, nvl, hc_prs[i], uuid, cpuid, repair) != CMA_RA_SUCCESS) { cma_stats.bad_flts.fmds_value.ui64++; return (CMA_RA_FAILURE); } } return (CMA_RA_SUCCESS); } #endif /* opl */ /* * The rest of this file uses ASRUs to do retire, this is now not the * preferable way, but it's still needed for some circumstances when * retire via topo methods can't work, ie. * * 1) There are legacy platforms which don't have full topology. * 2) The resources in the FMD cached faults may not be set or exist in the * up-to-dated topology. */ /* ARGSUSED */ static int cpu_online(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, const char *uuid, uint32_t cpuid) { int err = CMA_RA_SUCCESS; if (cma.cma_cpu_doonline) { err = cma_cpu_statechange(hdl, asru, uuid, P_ONLINE, B_TRUE); } else { fmd_hdl_debug(hdl, "suppressed online of CPU %u\n", cpuid); cma_stats.cpu_supp.fmds_value.ui64++; } /* OPL performs the blacklist in the service processor */ #ifndef opl if (cma.cma_cpu_dounblacklist) { if (cma_cpu_blacklist(hdl, nvl, asru, B_TRUE) < 0) cma_stats.cpu_blfails.fmds_value.ui64++; } else { fmd_hdl_debug(hdl, "suppressed unblacklist of CPU %u\n", cpuid); cma_stats.cpu_blsupp.fmds_value.ui64++; } #endif /* opl */ return (err); } /* ARGSUSED */ static int cpu_offline(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, const char *uuid, uint32_t cpuid) { int err = CMA_RA_FAILURE; if (cma.cma_cpu_dooffline) { int cpustate = P_FAULTED; if (cma.cma_cpu_forcedoffline) cpustate |= P_FORCED; err = cma_cpu_statechange(hdl, asru, uuid, cpustate, B_FALSE); } else { fmd_hdl_debug(hdl, "suppressed offline of CPU %u\n", cpuid); cma_stats.cpu_supp.fmds_value.ui64++; } /* OPL performs the blacklist in the service processor */ #ifndef opl if (cma.cma_cpu_doblacklist) { if (cma_cpu_blacklist(hdl, nvl, asru, B_FALSE) < 0) cma_stats.cpu_blfails.fmds_value.ui64++; } else { fmd_hdl_debug(hdl, "suppressed blacklist of CPU %u\n", cpuid); cma_stats.cpu_blsupp.fmds_value.ui64++; } #endif /* opl */ return (err); } static int cpu_statechange(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, const char *uuid, uint32_t cpuid, boolean_t repair) { if (repair) return (cpu_online(hdl, nvl, asru, uuid, cpuid)); else return (cpu_offline(hdl, nvl, asru, uuid, cpuid)); } const char * p_online_state_fmt(int state) { state &= ~P_FORCED; switch (state) { case P_OFFLINE: return (PS_OFFLINE); case P_ONLINE: return (PS_ONLINE); case P_FAULTED: return (PS_FAULTED); case P_POWEROFF: return (PS_POWEROFF); case P_NOINTR: return (PS_NOINTR); case P_SPARE: return (PS_SPARE); case P_DISABLED: return (PS_DISABLED); default: return ("unknown"); } } int cma_cpu_cpu_retire(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, const char *uuid, boolean_t repair) { uint_t cpuid; if (nvlist_lookup_uint32(asru, FM_FMRI_CPU_ID, &cpuid) != 0) { fmd_hdl_debug(hdl, "cpu fault missing '%s'\n", FM_FMRI_CPU_ID); cma_stats.bad_flts.fmds_value.ui64++; return (CMA_RA_FAILURE); } return (cpu_statechange(hdl, nvl, asru, uuid, cpuid, repair)); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include int cma_cpu_blacklist(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, boolean_t repair) { bl_req_t blr; nvlist_t *fmri; char *fmribuf; size_t fmrisz; int fd, rc, err; char *class; /* * Some platforms have special unums for the E$ DIMMs. If we're dealing * with a platform that has these unums, one will have been added to the * fault as the resource. We'll use that for the blacklisting. If we * can't find a resource, we'll fall back to the ASRU. */ if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &fmri) != 0) fmri = asru; if ((nvlist_lookup_string(nvl, FM_CLASS, &class) != 0) || (class == NULL) || (*class == '\0')) { fmd_hdl_debug(hdl, "failed to get the fault class name\n"); errno = EINVAL; return (-1); } if ((fd = open("/dev/bl", O_RDONLY)) < 0) return (-1); /* errno is set for us */ if ((errno = nvlist_size(fmri, &fmrisz, NV_ENCODE_NATIVE)) != 0 || (fmribuf = fmd_hdl_alloc(hdl, fmrisz, FMD_SLEEP)) == NULL) { (void) close(fd); return (-1); /* errno is set for us */ } if ((errno = nvlist_pack(fmri, &fmribuf, &fmrisz, NV_ENCODE_NATIVE, 0)) != 0) { fmd_hdl_free(hdl, fmribuf, fmrisz); (void) close(fd); return (-1); /* errno is set for us */ } blr.bl_fmri = fmribuf; blr.bl_fmrisz = fmrisz; blr.bl_class = class; rc = ioctl(fd, repair ? BLIOC_DELETE : BLIOC_INSERT, &blr); err = errno; fmd_hdl_free(hdl, fmribuf, fmrisz); (void) close(fd); if (rc < 0 && err != ENOTSUP) { errno = err; return (-1); } return (0); } /* ARGSUSED */ int cma_cpu_statechange(fmd_hdl_t *hdl, nvlist_t *asru, const char *uuid, int cpustate, boolean_t repair) { int i; uint_t cpuid; if (nvlist_lookup_uint32(asru, FM_FMRI_CPU_ID, &cpuid) != 0) { fmd_hdl_debug(hdl, "missing '%s'\n", FM_FMRI_CPU_ID); cma_stats.bad_flts.fmds_value.ui64++; return (CMA_RA_FAILURE); } for (i = 0; i < cma.cma_cpu_tries; i++, (void) nanosleep(&cma.cma_cpu_delay, NULL)) { int oldstate; if ((oldstate = p_online(cpuid, cpustate)) != -1) { fmd_hdl_debug(hdl, "changed cpu %u state from \"%s\" " "to \"%s\"\n", cpuid, p_online_state_fmt(oldstate), p_online_state_fmt(cpustate)); if (repair) cma_stats.cpu_repairs.fmds_value.ui64++; else cma_stats.cpu_flts.fmds_value.ui64++; return (CMA_RA_SUCCESS); } } fmd_hdl_debug(hdl, "failed to changed cpu %u state to \"%s\": %s\n", cpuid, p_online_state_fmt(cpustate), strerror(errno)); cma_stats.cpu_fails.fmds_value.ui64++; return (CMA_RA_FAILURE); } /* * 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. */ #include #include #include #include #include #include #include #include #include #include #ifdef sun4v #include static fmd_hdl_t *init_hdl; ldom_hdl_t *cma_lhp; #endif #ifdef i386 boolean_t cma_is_native; #endif extern const char *fmd_fmri_get_platform(); cma_t cma; cma_stats_t cma_stats = { { "cpu_flts", FMD_TYPE_UINT64, "cpu faults resolved" }, { "cpu_repairs", FMD_TYPE_UINT64, "cpu faults repaired" }, { "cpu_fails", FMD_TYPE_UINT64, "cpu faults unresolveable" }, { "cpu_blfails", FMD_TYPE_UINT64, "failed cpu blacklists" }, { "cpu_supp", FMD_TYPE_UINT64, "cpu offlines suppressed" }, { "cpu_blsupp", FMD_TYPE_UINT64, "cpu blacklists suppressed" }, { "page_flts", FMD_TYPE_UINT64, "page faults resolved" }, { "page_repairs", FMD_TYPE_UINT64, "page faults repaired" }, { "page_fails", FMD_TYPE_UINT64, "page faults unresolveable" }, { "page_supp", FMD_TYPE_UINT64, "page retires suppressed" }, { "page_nonent", FMD_TYPE_UINT64, "retires for non-existent fmris" }, { "bad_flts", FMD_TYPE_UINT64, "invalid fault events received" }, { "nop_flts", FMD_TYPE_UINT64, "inapplicable fault events received" }, { "auto_flts", FMD_TYPE_UINT64, "auto-close faults received" } }; typedef struct cma_subscriber { const char *subr_class; const char *subr_sname; uint_t subr_svers; int (*subr_func)(fmd_hdl_t *, nvlist_t *, nvlist_t *, const char *, boolean_t); } cma_subscriber_t; static const cma_subscriber_t cma_subrs[] = { #if defined(i386) /* * On x86, the ASRUs are expected to be in hc scheme. When * cpumem-retire wants to retire a cpu or mem page, it calls the * methods registered in the topo node to do that. The topo * enumerator, which necessarily knows all the config info that * we'd ever need in deciding what/how to retire etc. This takes * away much of that complexity from the agent into the entity * that knows all config/topo information. */ { "fault.memory.page", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.memory.page_sb", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.memory.page_ck", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.memory.page_ue", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.memory.generic-x86.page_ce", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.memory.generic-x86.page_ue", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.memory.intel.page_ce", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.memory.intel.page_ue", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.memory.dimm", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.dimm_sb", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.dimm_ck", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.dimm_ue", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.generic-x86.dimm_ce", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.generic-x86.dimm_ue", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.intel.dimm_ce", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.intel.dimm_ue", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.intel.fbd.*", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.dimm_testfail", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.bank", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.datapath", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.intel.quickpath.mem_scrubbing", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_page_retire }, { "fault.cpu.intel.quickpath.*", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.generic-x86.mc", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.intel.dma", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.intel.dma", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, /* * The ASRU for cpu faults are in cpu scheme on native and in hc * scheme on xpv. So each cpu fault class needs to be listed twice. */ /* * The following faults do NOT retire a cpu thread, * and therefore must be intercepted before * the default "fault.cpu.*" dispatch to cma_cpu_hc_retire. */ { "fault.cpu.amd.dramchannel", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.amd.dramchannel", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.generic-x86.bus_interconnect_memory", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.generic-x86.bus_interconnect_memory", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.generic-x86.bus_interconnect_io", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.generic-x86.bus_interconnect_io", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.generic-x86.bus_interconnect", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.generic-x86.bus_interconnect", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.intel.bus_interconnect_memory", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.intel.bus_interconnect_memory", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.intel.bus_interconnect_io", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.intel.bus_interconnect_io", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.intel.bus_interconnect", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.intel.bus_interconnect", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.intel.nb.*", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.intel.nb.*", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.intel.dma", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.intel.dma", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.cpu.*", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cpu_hc_retire }, #elif defined(sun4v) /* * The following are PI sun4v faults */ { "fault.memory.memlink", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.memlink-uc", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.memlink-failover", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.dimm-ue-imminent", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.dram-ue-imminent", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.dimm-page-retires-excessive", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.page", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, cma_page_retire }, { "fault.memory.dimm", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm_sb", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm_ck", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm_ue", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm-page-retires-excessive", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm-ue-imminent", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dram-ue-imminent", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.bank", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.datapath", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.datapath", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.link-c", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.link-u", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.link-f", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.link-c", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.link-u", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.link-f", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, /* * The following ultraSPARC-T1/T2 faults do NOT retire a cpu thread, * and therefore must be intercepted before * the default "fault.cpu.*" dispatch to cma_cpu_hc_retire. */ { "fault.cpu.*.l2cachedata", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*.l2cachetag", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*.l2cachectl", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*.l2data-c", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*.l2data-u", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*.mau", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*.lfu-u", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*.lfu-f", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.*.lfu-p", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.ultraSPARC-T1.freg", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.ultraSPARC-T1.l2cachedata", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.ultraSPARC-T1.l2cachetag", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.ultraSPARC-T1.l2cachectl", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.ultraSPARC-T1.mau", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, { "fault.cpu.ultraSPARC-T2plus.chip", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.cpu.*", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.cpu.*", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cpu_hc_retire }, #elif defined(opl) { "fault.memory.page", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, cma_page_retire }, { "fault.memory.dimm", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm-page-retires-excessive", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm-ue-imminent", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dram-ue-imminent", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.bank", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.cpu.SPARC64-VI.*", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cpu_cpu_retire }, { "fault.cpu.SPARC64-VII.*", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cpu_cpu_retire }, { "fault.chassis.SPARC-Enterprise.cpu.SPARC64-VI.core.se", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.chassis.SPARC-Enterprise.cpu.SPARC64-VI.core.se-offlinereq", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.chassis.SPARC-Enterprise.cpu.SPARC64-VI.core.ce", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.chassis.SPARC-Enterprise.cpu.SPARC64-VI.core.ce-offlinereq", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.chassis.SPARC-Enterprise.cpu.SPARC64-VII.core.se", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.chassis.SPARC-Enterprise.cpu.SPARC64-VII.core.se-offlinereq", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.chassis.SPARC-Enterprise.cpu.SPARC64-VII.core.ce", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, { "fault.chassis.SPARC-Enterprise.cpu.SPARC64-VII.core.ce-offlinereq", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, cma_cpu_hc_retire }, #else /* * For platforms excluding i386, sun4v and opl. */ { "fault.memory.page", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, cma_page_retire }, { "fault.memory.page_sb", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, cma_page_retire }, { "fault.memory.page_ck", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, cma_page_retire }, { "fault.memory.page_ue", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, cma_page_retire }, { "fault.memory.dimm", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm_sb", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm_ck", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm_ue", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm-page-retires-excessive", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm-ue-imminent", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dram-ue-imminent", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.dimm_testfail", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.bank", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.datapath", FM_FMRI_SCHEME_MEM, FM_MEM_SCHEME_VERSION, NULL }, { "fault.memory.datapath", FM_FMRI_SCHEME_HC, FM_HC_SCHEME_VERSION, NULL }, { "fault.memory.datapath", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, NULL }, /* * The following faults do NOT retire a cpu thread, * and therefore must be intercepted before * the default "fault.cpu.*" dispatch to cma_cpu_cpu_retire. */ { "fault.cpu.ultraSPARC-IVplus.l2cachedata-line", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cache_way_retire }, { "fault.cpu.ultraSPARC-IVplus.l3cachedata-line", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cache_way_retire }, { "fault.cpu.ultraSPARC-IVplus.l2cachetag-line", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cache_way_retire }, { "fault.cpu.ultraSPARC-IVplus.l3cachetag-line", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cache_way_retire }, /* * Default "fault.cpu.*" for "cpu" scheme ASRU dispatch. */ { "fault.cpu.*", FM_FMRI_SCHEME_CPU, FM_CPU_SCHEME_VERSION, cma_cpu_cpu_retire }, #endif { NULL, NULL, 0, NULL } }; static const cma_subscriber_t * nvl2subr(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t **asrup) { const cma_subscriber_t *sp; nvlist_t *asru; char *scheme; uint8_t version; boolean_t retire; if (nvlist_lookup_boolean_value(nvl, FM_SUSPECT_RETIRE, &retire) == 0 && retire == 0) { fmd_hdl_debug(hdl, "cma_recv: retire suppressed"); return (NULL); } if (nvlist_lookup_nvlist(nvl, FM_FAULT_ASRU, &asru) != 0 || nvlist_lookup_string(asru, FM_FMRI_SCHEME, &scheme) != 0 || nvlist_lookup_uint8(asru, FM_VERSION, &version) != 0) { cma_stats.bad_flts.fmds_value.ui64++; return (NULL); } for (sp = cma_subrs; sp->subr_class != NULL; sp++) { if (fmd_nvl_class_match(hdl, nvl, sp->subr_class) && strcmp(scheme, sp->subr_sname) == 0 && version <= sp->subr_svers) { *asrup = asru; return (sp); } } cma_stats.nop_flts.fmds_value.ui64++; return (NULL); } static void cma_recv_list(fmd_hdl_t *hdl, nvlist_t *nvl, const char *class) { char *uuid = NULL; nvlist_t **nva, **save_nva; uint_t nvc = 0, save_nvc; uint_t keepopen; int err = 0; nvlist_t *asru = NULL; uint32_t index; err |= nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid); err |= nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST, &nva, &nvc); if (err != 0) { cma_stats.bad_flts.fmds_value.ui64++; return; } save_nvc = keepopen = nvc; save_nva = nva; while (nvc-- != 0 && (strcmp(class, FM_LIST_SUSPECT_CLASS) != 0 || !fmd_case_uuclosed(hdl, uuid))) { nvlist_t *nvl = *nva++; const cma_subscriber_t *subr; int has_fault; if ((subr = nvl2subr(hdl, nvl, &asru)) == NULL) continue; /* * A handler returns CMA_RA_SUCCESS to indicate that * from this suspects point-of-view the case may be * closed, CMA_RA_FAILURE otherwise. * A handler must not close the case itself. */ if (subr->subr_func != NULL) { has_fault = fmd_nvl_fmri_has_fault(hdl, asru, FMD_HAS_FAULT_ASRU, NULL); if (strcmp(class, FM_LIST_SUSPECT_CLASS) == 0) { if (has_fault == 1) err = subr->subr_func(hdl, nvl, asru, uuid, 0); } else { if (has_fault == 0) err = subr->subr_func(hdl, nvl, asru, uuid, 1); } if (err == CMA_RA_SUCCESS) keepopen--; } } /* * Run though again to catch any new faults in list.updated. */ while (save_nvc-- != 0 && (strcmp(class, FM_LIST_UPDATED_CLASS) == 0)) { nvlist_t *nvl = *save_nva++; const cma_subscriber_t *subr; int has_fault; if ((subr = nvl2subr(hdl, nvl, &asru)) == NULL) continue; if (subr->subr_func != NULL) { has_fault = fmd_nvl_fmri_has_fault(hdl, asru, FMD_HAS_FAULT_ASRU, NULL); if (has_fault == 1) err = subr->subr_func(hdl, nvl, asru, uuid, 0); } } /* * Do not close the case if we are handling cache faults. */ if (asru != NULL) { if (nvlist_lookup_uint32(asru, FM_FMRI_CPU_CACHE_INDEX, &index) != 0) { if (!keepopen && strcmp(class, FM_LIST_SUSPECT_CLASS) == 0) { fmd_case_uuclose(hdl, uuid); } } } if (!keepopen && strcmp(class, FM_LIST_REPAIRED_CLASS) == 0) fmd_case_uuresolved(hdl, uuid); } static void cma_recv_one(fmd_hdl_t *hdl, nvlist_t *nvl) { const cma_subscriber_t *subr; nvlist_t *asru; if ((subr = nvl2subr(hdl, nvl, &asru)) == NULL) return; if (subr->subr_func != NULL) { if (fmd_nvl_fmri_has_fault(hdl, asru, FMD_HAS_FAULT_ASRU, NULL) == 1) (void) subr->subr_func(hdl, nvl, asru, NULL, 0); } } /*ARGSUSED*/ static void cma_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { fmd_hdl_debug(hdl, "received %s\n", class); if (strcmp(class, FM_LIST_RESOLVED_CLASS) == 0) return; if (strcmp(class, FM_LIST_SUSPECT_CLASS) == 0 || strcmp(class, FM_LIST_REPAIRED_CLASS) == 0 || strcmp(class, FM_LIST_UPDATED_CLASS) == 0) cma_recv_list(hdl, nvl, class); else cma_recv_one(hdl, nvl); } /*ARGSUSED*/ static void cma_timeout(fmd_hdl_t *hdl, id_t id, void *arg) { if (id == cma.cma_page_timerid) cma_page_retry(hdl); #ifdef sun4v /* * cpu offline/online needs to be retried on sun4v because * ldom request can be asynchronous. */ else if (id == cma.cma_cpu_timerid) cma_cpu_retry(hdl); #endif } #ifdef sun4v static void * cma_init_alloc(size_t size) { return (fmd_hdl_alloc(init_hdl, size, FMD_SLEEP)); } static void cma_init_free(void *addr, size_t size) { fmd_hdl_free(init_hdl, addr, size); } #endif static const fmd_hdl_ops_t fmd_ops = { cma_recv, /* fmdo_recv */ cma_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ }; static const fmd_prop_t fmd_props[] = { { "cpu_tries", FMD_TYPE_UINT32, "10" }, { "cpu_delay", FMD_TYPE_TIME, "1sec" }, #ifdef sun4v { "cpu_ret_mindelay", FMD_TYPE_TIME, "5sec" }, { "cpu_ret_maxdelay", FMD_TYPE_TIME, "5min" }, #endif /* sun4v */ { "cpu_offline_enable", FMD_TYPE_BOOL, "true" }, { "cpu_online_enable", FMD_TYPE_BOOL, "true" }, { "cpu_forced_offline", FMD_TYPE_BOOL, "true" }, #ifdef opl { "cpu_blacklist_enable", FMD_TYPE_BOOL, "false" }, { "cpu_unblacklist_enable", FMD_TYPE_BOOL, "false" }, #else { "cpu_blacklist_enable", FMD_TYPE_BOOL, "true" }, { "cpu_unblacklist_enable", FMD_TYPE_BOOL, "true" }, #endif /* opl */ { "page_ret_mindelay", FMD_TYPE_TIME, "1sec" }, { "page_ret_maxdelay", FMD_TYPE_TIME, "5min" }, { "page_retire_enable", FMD_TYPE_BOOL, "true" }, { "page_unretire_enable", FMD_TYPE_BOOL, "true" }, { NULL, 0, NULL } }; static const fmd_hdl_info_t fmd_info = { "CPU/Memory Retire Agent", CMA_VERSION, &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { hrtime_t nsec; #ifdef i386 char buf[BUFSIZ]; /* * Abort the cpumem-retire module if not running on amd64/i86pc. */ if (sysinfo(SI_PLATFORM, buf, sizeof (buf)) == -1) return; if (strncmp(buf, "amd64", sizeof (buf)) == 0 || strncmp(buf, "i86pc", sizeof (buf)) == 0) { cma_is_native = B_TRUE; } else { return; } #endif /* i386 */ if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; /* invalid data in configuration file */ fmd_hdl_subscribe(hdl, "fault.cpu.*"); fmd_hdl_subscribe(hdl, "fault.memory.*"); #ifdef opl fmd_hdl_subscribe(hdl, "fault.chassis.SPARC-Enterprise.cpu.*"); #endif (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (cma_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&cma_stats); cma.cma_cpu_tries = fmd_prop_get_int32(hdl, "cpu_tries"); nsec = fmd_prop_get_int64(hdl, "cpu_delay"); cma.cma_cpu_delay.tv_sec = nsec / NANOSEC; cma.cma_cpu_delay.tv_nsec = nsec % NANOSEC; cma.cma_page_mindelay = fmd_prop_get_int64(hdl, "page_ret_mindelay"); cma.cma_page_maxdelay = fmd_prop_get_int64(hdl, "page_ret_maxdelay"); #ifdef sun4v cma.cma_cpu_mindelay = fmd_prop_get_int64(hdl, "cpu_ret_mindelay"); cma.cma_cpu_maxdelay = fmd_prop_get_int64(hdl, "cpu_ret_maxdelay"); #endif cma.cma_cpu_dooffline = fmd_prop_get_int32(hdl, "cpu_offline_enable"); cma.cma_cpu_forcedoffline = fmd_prop_get_int32(hdl, "cpu_forced_offline"); cma.cma_cpu_doonline = fmd_prop_get_int32(hdl, "cpu_online_enable"); cma.cma_cpu_doblacklist = fmd_prop_get_int32(hdl, "cpu_blacklist_enable"); cma.cma_cpu_dounblacklist = fmd_prop_get_int32(hdl, "cpu_unblacklist_enable"); cma.cma_page_doretire = fmd_prop_get_int32(hdl, "page_retire_enable"); cma.cma_page_dounretire = fmd_prop_get_int32(hdl, "page_unretire_enable"); if (cma.cma_page_maxdelay < cma.cma_page_mindelay) fmd_hdl_abort(hdl, "page retirement delays conflict\n"); #ifdef sun4v init_hdl = hdl; cma_lhp = ldom_init(cma_init_alloc, cma_init_free); #endif } void _fmd_fini(fmd_hdl_t *hdl) { #ifdef sun4v ldom_fini(cma_lhp); cma_cpu_fini(hdl); #endif cma_page_fini(hdl); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Page retirement can be an extended process due to the fact that a retirement * may not be possible when the original request is made. The kernel will * repeatedly attempt to retire a given page, but will not let us know when the * page has been retired. We therefore have to poll to see if the retirement * has been completed. This poll is implemented with a bounded exponential * backoff to reduce the burden which we impose upon the system. * * To reduce the burden on fmd in the face of retirement storms, we schedule * all retries as a group. In the simplest case, we attempt to retire a single * page. When forced to retry, we initially schedule a retry at a configurable * interval t. If the retry fails, we schedule another at 2 * t, and so on, * until t reaches the maximum interval (also configurable). Future retries * for that page will occur with t equal to the maximum interval value. We * will never give up on a retirement. * * With multiple retirements, the situation gets slightly more complicated. As * indicated above, we schedule retries as a group. We don't want to deny new * pages their short retry intervals, so we'll (re)set the retry interval to the * value appropriate for the newest page. */ #include #include #include #include #include #include #include #include #include #include static void cma_page_free(fmd_hdl_t *hdl, cma_page_t *page) { nvlist_free(page->pg_asru); nvlist_free(page->pg_rsrc); fmd_hdl_free(hdl, page, sizeof (cma_page_t)); } /* * Retire the specified ASRU, referring to a memory page by PA or by DIMM * offset (i.e. the encoded coordinates internal bank, row, and column). * In the initial FMA implementation, fault.memory.page exported an ASRU * with an explicit physical address, which is valid at the initial time of * diagnosis but may not be later following DR, DIMM removal, or interleave * changes. On SPARC, this issue was solved by exporting the DIMM offset * and pushing the entire FMRI to the platform memory controller through * /dev/fm so it can derive the current PA from the DIMM and offset. * On x86, we also encode DIMM and offset in hc-specific, which is then used * by the x64 memory controller driver. * At some point these three approaches need to be rationalized: all platforms * should use the same scheme, either with decoding in the kernel or decoding * in userland (i.e. with a libtopo method to compute and update the PA). */ /*ARGSUSED*/ int cma_page_retire(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *asru, const char *uuid, boolean_t repair) { cma_page_t *page; uint64_t pageaddr; const char *action = repair ? "unretire" : "retire"; int rc; nvlist_t *rsrc = NULL, *asrucp = NULL, *hcsp; (void) nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &rsrc); if (nvlist_dup(asru, &asrucp, 0) != 0) { fmd_hdl_debug(hdl, "page retire nvlist dup failed\n"); return (CMA_RA_FAILURE); } /* It should already be expanded, but we'll do it again anyway */ if (fmd_nvl_fmri_expand(hdl, asrucp) < 0) { fmd_hdl_debug(hdl, "failed to expand page asru\n"); cma_stats.bad_flts.fmds_value.ui64++; nvlist_free(asrucp); return (CMA_RA_FAILURE); } if (!repair && !fmd_nvl_fmri_present(hdl, asrucp)) { fmd_hdl_debug(hdl, "page retire overtaken by events\n"); cma_stats.page_nonent.fmds_value.ui64++; nvlist_free(asrucp); return (CMA_RA_SUCCESS); } /* Figure out physaddr from resource or asru */ if (rsrc == NULL || nvlist_lookup_nvlist(rsrc, FM_FMRI_HC_SPECIFIC, &hcsp) != 0 || (nvlist_lookup_uint64(hcsp, "asru-" FM_FMRI_HC_SPECIFIC_PHYSADDR, &pageaddr) != 0 && nvlist_lookup_uint64(hcsp, FM_FMRI_HC_SPECIFIC_PHYSADDR, &pageaddr) != 0)) { if (nvlist_lookup_uint64(asrucp, FM_FMRI_MEM_PHYSADDR, &pageaddr) != 0) { fmd_hdl_debug(hdl, "mem fault missing 'physaddr'\n"); cma_stats.bad_flts.fmds_value.ui64++; nvlist_free(asrucp); return (CMA_RA_FAILURE); } } if (repair) { if (!cma.cma_page_dounretire) { fmd_hdl_debug(hdl, "suppressed unretire of page %llx\n", (u_longlong_t)pageaddr); cma_stats.page_supp.fmds_value.ui64++; nvlist_free(asrucp); return (CMA_RA_SUCCESS); } /* If unretire via topo fails, we fall back to legacy way */ if (rsrc == NULL || (rc = fmd_nvl_fmri_unretire(hdl, rsrc)) < 0) rc = cma_fmri_page_unretire(hdl, asrucp); } else { if (!cma.cma_page_doretire) { fmd_hdl_debug(hdl, "suppressed retire of page %llx\n", (u_longlong_t)pageaddr); cma_stats.page_supp.fmds_value.ui64++; nvlist_free(asrucp); return (CMA_RA_FAILURE); } /* If retire via topo fails, we fall back to legacy way */ if (rsrc == NULL || (rc = fmd_nvl_fmri_retire(hdl, rsrc)) < 0) rc = cma_fmri_page_retire(hdl, asrucp); } if (rc == FMD_AGENT_RETIRE_DONE) { fmd_hdl_debug(hdl, "%sd page 0x%llx\n", action, (u_longlong_t)pageaddr); if (repair) cma_stats.page_repairs.fmds_value.ui64++; else cma_stats.page_flts.fmds_value.ui64++; nvlist_free(asrucp); return (CMA_RA_SUCCESS); } else if (repair || rc != FMD_AGENT_RETIRE_ASYNC) { fmd_hdl_debug(hdl, "%s of page 0x%llx failed, will not " "retry: %s\n", action, (u_longlong_t)pageaddr, strerror(errno)); cma_stats.page_fails.fmds_value.ui64++; nvlist_free(asrucp); return (CMA_RA_FAILURE); } /* * The page didn't immediately retire. We'll need to periodically * check to see if it has been retired. */ fmd_hdl_debug(hdl, "page didn't retire - sleeping\n"); page = fmd_hdl_zalloc(hdl, sizeof (cma_page_t), FMD_SLEEP); page->pg_addr = pageaddr; if (rsrc != NULL) (void) nvlist_dup(rsrc, &page->pg_rsrc, 0); page->pg_asru = asrucp; if (uuid != NULL) page->pg_uuid = fmd_hdl_strdup(hdl, uuid, FMD_SLEEP); page->pg_next = cma.cma_pages; cma.cma_pages = page; if (cma.cma_page_timerid != 0) fmd_timer_remove(hdl, cma.cma_page_timerid); cma.cma_page_curdelay = cma.cma_page_mindelay; cma.cma_page_timerid = fmd_timer_install(hdl, NULL, NULL, cma.cma_page_curdelay); /* Don't free asrucp here. This FMRI will be needed for retry. */ return (CMA_RA_FAILURE); } static int page_retry(fmd_hdl_t *hdl, cma_page_t *page) { int rc; if (page->pg_asru != NULL && !fmd_nvl_fmri_present(hdl, page->pg_asru)) { fmd_hdl_debug(hdl, "page retire overtaken by events"); cma_stats.page_nonent.fmds_value.ui64++; if (page->pg_uuid != NULL) fmd_case_uuclose(hdl, page->pg_uuid); return (1); /* no longer a page to retire */ } if (page->pg_rsrc == NULL || (rc = fmd_nvl_fmri_service_state(hdl, page->pg_rsrc)) < 0) rc = cma_fmri_page_service_state(hdl, page->pg_asru); if (rc == FMD_SERVICE_STATE_UNUSABLE) { fmd_hdl_debug(hdl, "retired page 0x%llx on retry %u\n", page->pg_addr, page->pg_nretries); cma_stats.page_flts.fmds_value.ui64++; if (page->pg_uuid != NULL) fmd_case_uuclose(hdl, page->pg_uuid); return (1); /* page retired */ } if (rc == FMD_SERVICE_STATE_ISOLATE_PENDING) { fmd_hdl_debug(hdl, "scheduling another retry for 0x%llx\n", page->pg_addr); return (0); /* schedule another retry */ } else { fmd_hdl_debug(hdl, "failed to retry page 0x%llx " "retirement: %s\n", page->pg_addr, strerror(errno)); cma_stats.page_fails.fmds_value.ui64++; return (1); /* give up */ } } void cma_page_retry(fmd_hdl_t *hdl) { cma_page_t **pagep; cma.cma_page_timerid = 0; fmd_hdl_debug(hdl, "page_retry: timer fired\n"); pagep = &cma.cma_pages; while (*pagep != NULL) { cma_page_t *page = *pagep; if (page_retry(hdl, page)) { /* * Successful retry or we're giving up - remove from * the list */ *pagep = page->pg_next; if (page->pg_uuid != NULL) fmd_hdl_strfree(hdl, page->pg_uuid); cma_page_free(hdl, page); } else { page->pg_nretries++; pagep = &page->pg_next; } } if (cma.cma_pages == NULL) return; /* no more retirements */ /* * We still have retirements that haven't completed. Back the delay * off, and schedule a retry. */ cma.cma_page_curdelay = MIN(cma.cma_page_curdelay * 2, cma.cma_page_maxdelay); fmd_hdl_debug(hdl, "scheduled page retirement retry for %llu secs\n", (u_longlong_t)(cma.cma_page_curdelay / NANOSEC)); cma.cma_page_timerid = fmd_timer_install(hdl, NULL, NULL, cma.cma_page_curdelay); } void cma_page_fini(fmd_hdl_t *hdl) { cma_page_t *page; while ((page = cma.cma_pages) != NULL) { cma.cma_pages = page->pg_next; if (page->pg_uuid != NULL) fmd_hdl_strfree(hdl, page->pg_uuid); cma_page_free(hdl, page); } } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include /* ARGSUSED */ int cma_fmri_page_service_state(fmd_hdl_t *hdl, nvlist_t *nvl) { fmd_agent_hdl_t *fa_hdl; int rc; if ((fa_hdl = fmd_agent_open(FMD_AGENT_VERSION)) != NULL) { rc = fmd_agent_page_isretired(fa_hdl, nvl); if (rc == FMD_AGENT_RETIRE_DONE) rc = FMD_SERVICE_STATE_UNUSABLE; else if (rc == FMD_AGENT_RETIRE_FAIL) rc = FMD_SERVICE_STATE_OK; else if (rc == FMD_AGENT_RETIRE_ASYNC) rc = FMD_SERVICE_STATE_ISOLATE_PENDING; fmd_agent_close(fa_hdl); return (rc); } return (FMD_SERVICE_STATE_UNKNOWN); } /* ARGSUSED */ int cma_fmri_page_retire(fmd_hdl_t *hdl, nvlist_t *nvl) { fmd_agent_hdl_t *fa_hdl; int rc; if ((fa_hdl = fmd_agent_open(FMD_AGENT_VERSION)) != NULL) { rc = fmd_agent_page_retire(fa_hdl, nvl); fmd_agent_close(fa_hdl); return (rc); } return (FMD_AGENT_RETIRE_FAIL); } /* ARGSUSED */ int cma_fmri_page_unretire(fmd_hdl_t *hdl, nvlist_t *nvl) { fmd_agent_hdl_t *fa_hdl; int rc; if ((fa_hdl = fmd_agent_open(FMD_AGENT_VERSION)) != NULL) { rc = fmd_agent_page_unretire(fa_hdl, nvl); fmd_agent_close(fa_hdl); return (rc); } return (FMD_AGENT_RETIRE_FAIL); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" # # cpumem-retire # setprop cpu_tries 10 setprop cpu_delay 1sec # setprop page_ret_mindelay 1sec setprop page_ret_maxdelay 5min # setprop cpu_offline_enable true setprop cpu_forced_offline true setprop cpu_blacklist_enable true setprop page_retire_enable true # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright (c) 2013, Joyent, Inc. All rights reserved. # MODULE = disk-lights CLASS = common SRCS = disk_lights.c include ../../Makefile.plugin LINTFLAGS += -L$(ROOT)/usr/lib/fm LDLIBS += -ltopo LDFLAGS += -L$(ROOT)/usr/lib/fm -R/usr/lib/fm # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright (c) 2013, Joyent, Inc. All rights reserved. # subscribe fault.io.disk.* subscribe fault.io.scsi.* /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ /* * Disk Lights Agent (FMA) * * This Fault Management Daemon (fmd) module periodically scans the topology * tree, enumerates all disks with associated fault indicators, and then * synchronises the fault status of resources in the FMA Resource Cache with * the indicators. In short: it turns the fault light on for befallen disks. * * Presently, we recognise associated fault indicators for disks by looking * for the following structure in the topology tree: * * /bay=N * | * +---- /disk=0 <---------------- our Disk * | * +---- /bay=N?indicator=fail <---- the Fault Light * \---- /bay=N?indicator=ident * * That is: a DISK node will have a parent BAY; that BAY will itself have * child Facility nodes, one of which will be called "fail". If any of the * above does not hold, we simply do nothing for this disk. */ #include #include #include #include #include #include #include #include typedef struct disk_lights { fmd_hdl_t *dl_fmd; uint64_t dl_poll_interval; uint64_t dl_coalesce_interval; id_t dl_timer; boolean_t dl_triggered; } disk_lights_t; static void disklights_topo(fmd_hdl_t *, topo_hdl_t *); static void disklights_recv(fmd_hdl_t *, fmd_event_t *, nvlist_t *, const char *); static void disklights_timeout(fmd_hdl_t *, id_t, void *); static const fmd_hdl_ops_t fmd_ops = { disklights_recv, /* fmdo_recv */ disklights_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ disklights_topo, /* fmdo_topo */ }; /* * POLL_INTERVAL is the period after which we perform an unsolicited poll * to ensure we remain in sync with reality. */ #define DL_PROP_POLL_INTERVAL "poll-interval" /* * COALESCE_INTERVAL is how long we wait after we are trigged by either a * topology change or a relevant list.* event, in order to allow a series * of events to coalesce. */ #define DL_PROP_COALESCE_INTERVAL "coalesce-interval" static const fmd_prop_t fmd_props[] = { { DL_PROP_POLL_INTERVAL, FMD_TYPE_TIME, "5min" }, { DL_PROP_COALESCE_INTERVAL, FMD_TYPE_TIME, "3s" }, { NULL, 0, NULL } }; static const fmd_hdl_info_t fmd_info = { "Disk Lights Agent", "1.0", &fmd_ops, fmd_props }; /* * Fetch the Facility Node properties (name, type) from the FMRI * for this node, or return -1 if we can't. */ static int get_facility_props(topo_hdl_t *hdl, tnode_t *node, char **facname, char **factype) { int e, ret = -1; nvlist_t *fmri = NULL, *fnvl; char *nn = NULL, *tt = NULL; if (topo_node_resource(node, &fmri, &e) != 0) goto out; if (nvlist_lookup_nvlist(fmri, FM_FMRI_FACILITY, &fnvl) != 0) goto out; if (nvlist_lookup_string(fnvl, FM_FMRI_FACILITY_NAME, &nn) != 0) goto out; if (nvlist_lookup_string(fnvl, FM_FMRI_FACILITY_TYPE, &tt) != 0) goto out; *facname = topo_hdl_strdup(hdl, nn); *factype = topo_hdl_strdup(hdl, tt); ret = 0; out: nvlist_free(fmri); return (ret); } typedef struct dl_fault_walk_inner { char *fwi_name; uint32_t fwi_mode; } dl_fault_walk_inner_t; static int dl_fault_walk_inner(topo_hdl_t *thp, tnode_t *node, void *arg) { dl_fault_walk_inner_t *fwi = arg; char *facname = NULL, *factype = NULL; int err; /* * We're only interested in BAY children that are valid Facility Nodes. */ if (topo_node_flags(node) != TOPO_NODE_FACILITY || get_facility_props(thp, node, &facname, &factype) != 0) { goto out; } if (strcmp(fwi->fwi_name, facname) != 0) goto out; /* * Attempt to set the LED mode appropriately. If this fails, give up * and move on. */ (void) topo_prop_set_uint32(node, TOPO_PGROUP_FACILITY, TOPO_LED_MODE, TOPO_PROP_MUTABLE, fwi->fwi_mode, &err); out: topo_hdl_strfree(thp, facname); topo_hdl_strfree(thp, factype); return (TOPO_WALK_NEXT); } static int dl_fault_walk_outer(topo_hdl_t *thp, tnode_t *node, void *arg) { disk_lights_t *dl = arg; dl_fault_walk_inner_t fwi; tnode_t *pnode; int err, has_fault; nvlist_t *fmri = NULL; bzero(&fwi, sizeof (fwi)); /* * We are only looking for DISK nodes in the topology that have a parent * BAY. */ if (strcmp(DISK, topo_node_name(node)) != 0 || (pnode = topo_node_parent(node)) == NULL || strcmp(BAY, topo_node_name(pnode)) != 0) { return (TOPO_WALK_NEXT); } /* * Check to see if the Resource this FMRI describes is Faulty: */ if (topo_node_resource(node, &fmri, &err) != 0) return (TOPO_WALK_NEXT); has_fault = fmd_nvl_fmri_has_fault(dl->dl_fmd, fmri, FMD_HAS_FAULT_RESOURCE, NULL); nvlist_free(fmri); /* * Walk the children of this BAY and flush out our fault status if * we find an appropriate indicator node. */ fwi.fwi_name = "fail"; fwi.fwi_mode = has_fault ? TOPO_LED_STATE_ON : TOPO_LED_STATE_OFF; (void) topo_node_child_walk(thp, pnode, dl_fault_walk_inner, &fwi, &err); return (TOPO_WALK_NEXT); } /* * Walk all of the topology nodes looking for DISKs that match the structure * described in the overview. Once we find them, check their fault status * and update their fault indiciator accordingly. */ static void dl_examine_topo(disk_lights_t *dl) { int err; topo_hdl_t *thp = NULL; topo_walk_t *twp = NULL; thp = fmd_hdl_topo_hold(dl->dl_fmd, TOPO_VERSION); if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, dl_fault_walk_outer, dl, &err)) == NULL) { fmd_hdl_error(dl->dl_fmd, "failed to get topology: %s\n", topo_strerror(err)); goto out; } if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) { fmd_hdl_error(dl->dl_fmd, "failed to walk topology: %s\n", topo_strerror(err)); goto out; } out: if (twp != NULL) topo_walk_fini(twp); if (thp != NULL) fmd_hdl_topo_rele(dl->dl_fmd, thp); } static void dl_trigger_enum(disk_lights_t *dl) { /* * If we're already on the short-poll coalesce timer, then return * immediately. */ if (dl->dl_triggered == B_TRUE) return; dl->dl_triggered = B_TRUE; /* * Replace existing poll timer with coalesce timer: */ if (dl->dl_timer != 0) fmd_timer_remove(dl->dl_fmd, dl->dl_timer); dl->dl_timer = fmd_timer_install(dl->dl_fmd, NULL, NULL, dl->dl_coalesce_interval); } /*ARGSUSED*/ static void disklights_timeout(fmd_hdl_t *hdl, id_t id, void *data) { disk_lights_t *dl = fmd_hdl_getspecific(hdl); dl->dl_triggered = B_FALSE; dl_examine_topo(dl); /* * Install the long-interval timer for the next poll. */ dl->dl_timer = fmd_timer_install(hdl, NULL, NULL, dl->dl_poll_interval); } /*ARGSUSED*/ static void disklights_topo(fmd_hdl_t *hdl, topo_hdl_t *thp) { disk_lights_t *dl = fmd_hdl_getspecific(hdl); dl_trigger_enum(dl); } /*ARGSUSED*/ static void disklights_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { disk_lights_t *dl = fmd_hdl_getspecific(hdl); dl_trigger_enum(dl); } void _fmd_init(fmd_hdl_t *hdl) { disk_lights_t *dl; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; dl = fmd_hdl_zalloc(hdl, sizeof (*dl), FMD_SLEEP); fmd_hdl_setspecific(hdl, dl); /* * Load Configuration: */ dl->dl_fmd = hdl; dl->dl_poll_interval = fmd_prop_get_int64(hdl, DL_PROP_POLL_INTERVAL); dl->dl_coalesce_interval = fmd_prop_get_int64(hdl, DL_PROP_COALESCE_INTERVAL); /* * Schedule the initial enumeration: */ dl_trigger_enum(dl); } void _fmd_fini(fmd_hdl_t *hdl) { disk_lights_t *dl = fmd_hdl_getspecific(hdl); fmd_hdl_free(hdl, dl, sizeof (*dl)); } # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. MODULE = disk-monitor CLASS = common SRCS = disk_monitor.c diskmon_conf.c topo_gather.c \ hotplug_mgr.c dm_platform.c schg_mgr.c util.c include ../../Makefile.plugin LINTFLAGS += -I. -I$(SRC)/lib/fm/topo/modules/common/disk -L$(ROOT)/usr/lib/fm CFLAGS += -I. -I$(SRC)/lib/fm/topo/modules/common/disk LDLIBS += -lsysevent -lnvpair -lsmbios -lcfgadm -ltopo -luutil -lipmi LDFLAGS += -L$(ROOT)/usr/lib/fm -R/usr/lib/fm CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" # # fmd configuration file for the disk-monitor.so disk monitor. # subscribe fault.io.disk.* dictionary DISK /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. */ /* * Disk Monitor */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "disk_monitor.h" #include "hotplug_mgr.h" #include "schg_mgr.h" #include "topo_gather.h" #include "dm_platform.h" #define THIS_FMD_MODULE_NAME "disk-monitor" static enum disk_init_state { INIT_STATE_NONE = 0, STATE_CHANGE_MGR_INITTED = 2, HOTPLUG_MGR_INITTED = 4 } g_init_state = INIT_STATE_NONE; typedef enum { LT_SUSPECT, LT_REPAIRED } fm_list_type_t; /* * Global verbosity flag -- controls chattiness of debug messages and * warnings. Its value is determined by the fmd property "log-level" * settable in the DE's .conf file. */ log_class_t g_verbose = 0; cfgdata_t *config_data = NULL; fmd_hdl_t *g_fm_hdl = NULL; static const fmd_prop_t fmd_props[]; static void diskmon_teardown_all(void) { cleanup_hotplug_manager(); cleanup_state_change_manager(config_data); config_fini(); } static int count_disks(diskmon_t *disklistp) { int i = 0; while (disklistp != NULL) { i++; disklistp = disklistp->next; } return (i); } static int diskmon_init(void) { /* * Block the generation of state change events (generated by the * hotplug manager thread) here; they will be unblocked after the * state change manager thread is ready to accept state changes * (shortly after it starts). */ block_state_change_events(); if (dm_platform_init() != 0) goto cleanup; if (init_hotplug_manager() != 0) goto cleanup; else g_init_state |= HOTPLUG_MGR_INITTED; if (init_state_change_manager(config_data) != 0) goto cleanup; else g_init_state |= STATE_CHANGE_MGR_INITTED; return (E_SUCCESS); cleanup: unblock_state_change_events(); /* * The cleanup order here does matter, due to dependencies between the * managers. */ if (g_init_state & HOTPLUG_MGR_INITTED) cleanup_hotplug_manager(); if (g_init_state & STATE_CHANGE_MGR_INITTED) cleanup_state_change_manager(config_data); dm_platform_fini(); return (E_ERROR); } static void dm_fault_execute_actions(fmd_hdl_t *hdl, diskmon_t *diskp, nvlist_t *nvl) { const char *action_prop = NULL; const char *action_string; /* * The predictive failure action is the activation of the fault * indicator. */ if (fmd_nvl_class_match(hdl, nvl, DISK_ERROR_CLASS "." FM_FAULT_DISK_OVERTEMP)) action_prop = DISK_PROP_OTEMPACTION; if (fmd_nvl_class_match(hdl, nvl, DISK_ERROR_CLASS "." FM_FAULT_DISK_TESTFAIL)) action_prop = DISK_PROP_STFAILACTION; if (fmd_nvl_class_match(hdl, nvl, DISK_ERROR_CLASS "." FM_FAULT_SSM_WEAROUT)) action_prop = DISK_PROP_SSMWEAROUTACTION; dm_fault_indicator_set(diskp, INDICATOR_ON); if (action_prop != NULL && (action_string = dm_prop_lookup(diskp->props, action_prop)) != NULL) { if (dm_platform_indicator_execute(action_string) != 0) { log_warn("Fault action `%s' did not successfully " "complete.\n", action_string); } } } static void diskmon_agent_repair(fmd_hdl_t *hdl, nvlist_t *nvl, int repair) { char *uuid = NULL; nvlist_t **nva; uint_t nvc; diskmon_t *diskp; nvlist_t *fmri; nvlist_t *fltnvl; int err = 0; err |= nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid); err |= nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST, &nva, &nvc); if (err != 0) return; while (nvc-- != 0) { fltnvl = *nva++; if (nvlist_lookup_nvlist(fltnvl, FM_FAULT_RESOURCE, &fmri) != 0) continue; if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL) continue; log_msg(MM_MAIN, "Disk %s repaired!\n", diskp->location); dm_fault_indicator_set(diskp, INDICATOR_OFF); dm_state_change(diskp, HPS_REPAIRED); } if (repair) fmd_case_uuresolved(hdl, uuid); } static void diskmon_agent_suspect(fmd_hdl_t *hdl, nvlist_t *nvl) { char *uuid = NULL; nvlist_t **nva; uint_t nvc; diskmon_t *diskp; nvlist_t *fmri; nvlist_t *fltnvl; int err = 0; err |= nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid); err |= nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST, &nva, &nvc); if (err != 0) return; while (nvc-- != 0 && !fmd_case_uuclosed(hdl, uuid)) { fltnvl = *nva++; if (nvlist_lookup_nvlist(fltnvl, FM_FAULT_RESOURCE, &fmri) != 0) continue; if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL) continue; /* Execute the actions associated with this fault */ dm_fault_execute_actions(hdl, diskp, fltnvl); /* * Send a state change event to the state change manager */ dm_state_change(diskp, HPS_FAULTED); } if (!fmd_case_uuclosed(hdl, uuid)) { /* Case is closed */ fmd_case_uuclose(hdl, uuid); } } /*ARGSUSED*/ static void diskmon_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { diskmon_t *diskp; nvlist_t *fmri; if (g_verbose & MM_MAIN) nvlist_print(stderr, nvl); /* * Act on the fault suspect list or repaired list (embedded agent * action). */ if (fmd_nvl_class_match(hdl, nvl, FM_LIST_REPAIRED_CLASS)) { diskmon_agent_repair(hdl, nvl, 1); return; } else if (fmd_nvl_class_match(hdl, nvl, FM_LIST_UPDATED_CLASS)) { diskmon_agent_repair(hdl, nvl, 0); return; } else if (fmd_nvl_class_match(hdl, nvl, FM_LIST_SUSPECT_CLASS)) { diskmon_agent_suspect(hdl, nvl); return; } else if (fmd_nvl_class_match(hdl, nvl, FM_LIST_RESOLVED_CLASS)) { return; } /* * If we get any replayed faults, set the diskmon's faulted * flag for the appropriate fault, then change the diskmon's state * to faulted. */ if (fmd_nvl_class_match(hdl, nvl, DISK_ERROR_CLASS ".*")) { if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &fmri) != 0) return; if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL) return; /* Execute the actions associated with this fault */ dm_fault_execute_actions(hdl, diskp, nvl); /* * If the fault wasn't generated by this module, send a * state change event to the state change manager */ dm_state_change(diskp, HPS_FAULTED); return; } } static const fmd_hdl_ops_t fmd_ops = { diskmon_recv, /* fmdo_recv */ NULL, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ }; static const fmd_prop_t fmd_props[] = { { GLOBAL_PROP_LOG_LEVEL, FMD_TYPE_UINT32, "0" }, { NULL, 0, NULL } }; static const fmd_hdl_info_t fmd_info = { "Disk Monitor", DISK_MONITOR_MODULE_VERSION, &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { fmd_case_t *cp; int disk_count; g_fm_hdl = hdl; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) { return; } if (config_init()) { log_err("Could not initialize configuration!\n"); fmd_hdl_unregister(hdl); return; } if (config_get(hdl, fmd_props)) { config_fini(); log_err("Could not retrieve configuration from libtopo!\n"); fmd_hdl_unregister(hdl); return; } /* * If there are no disks to monitor, bail out */ if ((disk_count = count_disks(config_data->disk_list)) == 0) { config_fini(); fmd_hdl_unregister(hdl); return; } if (diskmon_init() == E_ERROR) { config_fini(); fmd_hdl_unregister(hdl); return; } log_msg(MM_MAIN, "Monitoring %d disks.\n", disk_count); /* * Iterate over all active cases. * Since we automatically solve all cases, these cases must have * had the fault added, but the DE must have been interrupted * before they were solved. */ for (cp = fmd_case_next(hdl, NULL); cp != NULL; cp = fmd_case_next(hdl, cp)) { if (!fmd_case_solved(hdl, cp)) fmd_case_solve(hdl, cp); } } /*ARGSUSED*/ void _fmd_fini(fmd_hdl_t *hdl) { diskmon_teardown_all(); g_fm_hdl = NULL; } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DISK_MONITOR_H #define _DISK_MONITOR_H /* * Externs for disk monitor */ #include #ifdef __cplusplus extern "C" { #endif #define DISK_MONITOR_MODULE_VERSION "1.0" #define E_SUCCESS 0 #define E_ERROR 1 extern cfgdata_t *config_data; extern fmd_hdl_t *g_fm_hdl; #ifdef __cplusplus } #endif #endif /* _DISK_MONITOR_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Disk & Indicator Monitor configuration file support routines */ #include #include #include #include #include #include #include #include #include #include "disk_monitor.h" #include "util.h" #include "topo_gather.h" extern log_class_t g_verbose; const char * hotplug_state_string(hotplug_state_t state) { switch (state & ~HPS_FAULTED) { default: case HPS_UNKNOWN: return ("Unknown"); case HPS_ABSENT: return ("Absent"); case HPS_PRESENT: return ("Present"); case HPS_CONFIGURED: return ("Configured"); case HPS_UNCONFIGURED: return ("Unconfigured"); } } void conf_error_msg(conf_err_t err, char *buf, int buflen, void *arg) { switch (err) { case E_MULTIPLE_IND_LISTS_DEFINED: (void) snprintf(buf, buflen, "Multiple Indicator lists " "defined"); break; case E_MULTIPLE_INDRULE_LISTS_DEFINED: (void) snprintf(buf, buflen, "Multiple Indicator rule lists " "defined"); break; case E_INVALID_STATE_CHANGE: (void) snprintf(buf, buflen, "Invalid state change"); break; case E_IND_MULTIPLY_DEFINED: (void) snprintf(buf, buflen, "Multiple Indicator definitions (name & state) detected"); break; case E_IND_ACTION_REDUNDANT: (void) snprintf(buf, buflen, "Redundant Indicator actions " "specified"); break; case E_IND_ACTION_CONFLICT: (void) snprintf(buf, buflen, "Indicator action conflict (+/- " "same Indicator) found"); break; case E_IND_MISSING_FAULT_ON: (void) snprintf(buf, buflen, "Missing declaration of `+" INDICATOR_FAULT_IDENTIFIER "'"); break; case E_IND_MISSING_FAULT_OFF: (void) snprintf(buf, buflen, "Missing declaration of `-" INDICATOR_FAULT_IDENTIFIER "'"); break; case E_INDRULE_REFERENCES_NONEXISTENT_IND_ACTION: (void) snprintf(buf, buflen, "`%c%s': Undefined Indicator in " BAY_IND_ACTION " property", (((ind_action_t *)arg)->ind_state == INDICATOR_ON) ? '+' : '-', ((ind_action_t *)arg)->ind_name); break; case E_DUPLICATE_STATE_TRANSITION: (void) snprintf(buf, buflen, "Duplicate state transition " "(%s -> %s)", hotplug_state_string(((state_transition_t *)arg)->begin), hotplug_state_string(((state_transition_t *)arg)->end)); break; default: (void) snprintf(buf, buflen, "Unknown error"); break; } } static int string_to_integer(const char *prop, int *value) { long val; errno = 0; val = strtol(prop, NULL, 0); if (val == 0 && errno != 0) return (-1); else if (val > INT_MAX || val < INT_MIN) { errno = ERANGE; return (-1); } if (value != NULL) *value = (int)val; return (0); } const char * dm_prop_lookup(nvlist_t *props, const char *prop_name) { char *str; if (nvlist_lookup_string(props, prop_name, &str) == 0) return ((const char *)str); else return (NULL); } int dm_prop_lookup_int(nvlist_t *props, const char *prop_name, int *value) { const char *prop = dm_prop_lookup(props, prop_name); if (prop == NULL) return (-1); return (string_to_integer(prop, value)); } nvlist_t * namevalpr_to_nvlist(namevalpr_t *nvprp) { nvlist_t *nvlp = NULL; if (nvlist_alloc(&nvlp, NV_UNIQUE_NAME, 0) != 0) { return (NULL); } if (nvlist_add_string(nvlp, nvprp->name, nvprp->value) != 0) { nvlist_free(nvlp); return (NULL); } return (nvlp); } indicator_t * new_indicator(ind_state_t lstate, char *namep, char *actionp) { indicator_t *newindicator = (indicator_t *)dmalloc(sizeof (indicator_t)); newindicator->ind_state = lstate; newindicator->ind_name = namep ? dstrdup(namep) : NULL; newindicator->ind_instr_spec = actionp ? dstrdup(actionp) : NULL; newindicator->next = NULL; return (newindicator); } void link_indicator(indicator_t **first, indicator_t *to_add) { indicator_t *travptr; dm_assert(first != NULL); if (*first == NULL) *first = to_add; else { travptr = *first; while (travptr->next != NULL) { travptr = travptr->next; } travptr->next = to_add; } } void ind_free(indicator_t *indp) { indicator_t *nextp; while (indp != NULL) { nextp = indp->next; if (indp->ind_name) dstrfree(indp->ind_name); if (indp->ind_instr_spec) dstrfree(indp->ind_instr_spec); dfree(indp, sizeof (indicator_t)); indp = nextp; } } ind_action_t * new_indaction(ind_state_t state, char *namep) { ind_action_t *lap = (ind_action_t *)dmalloc(sizeof (ind_action_t)); lap->ind_state = state; lap->ind_name = namep ? dstrdup(namep) : NULL; lap->next = NULL; return (lap); } void link_indaction(ind_action_t **first, ind_action_t *to_add) { ind_action_t *travptr; dm_assert(first != NULL); if (*first == NULL) *first = to_add; else { travptr = *first; while (travptr->next != NULL) { travptr = travptr->next; } travptr->next = to_add; } } void indaction_free(ind_action_t *lap) { ind_action_t *nextp; /* Free the whole list */ while (lap != NULL) { nextp = lap->next; if (lap->ind_name) dstrfree(lap->ind_name); dfree(lap, sizeof (ind_action_t)); lap = nextp; } } indrule_t * new_indrule(state_transition_t *st, ind_action_t *actionp) { indrule_t *lrp = (indrule_t *)dmalloc(sizeof (indrule_t)); if (st != NULL) lrp->strans = *st; lrp->action_list = actionp; lrp->next = NULL; return (lrp); } void link_indrule(indrule_t **first, indrule_t *to_add) { indrule_t *travptr; dm_assert(first != NULL); if (*first == NULL) *first = to_add; else { travptr = *first; while (travptr->next != NULL) { travptr = travptr->next; } travptr->next = to_add; } } void indrule_free(indrule_t *lrp) { indrule_t *nextp; /* Free the whole list */ while (lrp != NULL) { nextp = lrp->next; if (lrp->action_list) indaction_free(lrp->action_list); dfree(lrp, sizeof (indrule_t)); lrp = nextp; } } dm_fru_t * new_dmfru(char *manu, char *modl, char *firmrev, char *serno, uint64_t capa) { dm_fru_t *frup = (dm_fru_t *)dzmalloc(sizeof (dm_fru_t)); bcopy(manu, frup->manuf, MIN(sizeof (frup->manuf), strlen(manu) + 1)); bcopy(modl, frup->model, MIN(sizeof (frup->model), strlen(modl) + 1)); bcopy(firmrev, frup->rev, MIN(sizeof (frup->rev), strlen(firmrev) + 1)); bcopy(serno, frup->serial, MIN(sizeof (frup->serial), strlen(serno) + 1)); frup->size_in_bytes = capa; return (frup); } void dmfru_free(dm_fru_t *frup) { dfree(frup, sizeof (dm_fru_t)); } diskmon_t * new_diskmon(nvlist_t *app_props, indicator_t *indp, indrule_t *indrp, nvlist_t *nvlp) { diskmon_t *dmp = (diskmon_t *)dmalloc(sizeof (diskmon_t)); if (nvlp != NULL) dmp->props = nvlp; else (void) nvlist_alloc(&dmp->props, NV_UNIQUE_NAME, 0); if (app_props) dmp->app_props = app_props; else (void) nvlist_alloc(&dmp->app_props, NV_UNIQUE_NAME, 0); dmp->ind_list = indp; dmp->indrule_list = indrp; dm_assert(pthread_mutex_init(&dmp->manager_mutex, NULL) == 0); dmp->state = HPS_UNKNOWN; dmp->initial_configuration = B_TRUE; dm_assert(pthread_mutex_init(&dmp->fault_indicator_mutex, NULL) == 0); dmp->fault_indicator_state = INDICATOR_UNKNOWN; dmp->configured_yet = B_FALSE; dmp->state_change_count = 0; dm_assert(pthread_mutex_init(&dmp->fru_mutex, NULL) == 0); dmp->frup = NULL; dmp->next = NULL; return (dmp); } void diskmon_free(diskmon_t *dmp) { diskmon_t *nextp; /* Free the whole list */ while (dmp != NULL) { nextp = dmp->next; nvlist_free(dmp->props); if (dmp->location) dstrfree(dmp->location); if (dmp->ind_list) ind_free(dmp->ind_list); if (dmp->indrule_list) indrule_free(dmp->indrule_list); nvlist_free(dmp->app_props); if (dmp->frup) dmfru_free(dmp->frup); dfree(dmp, sizeof (diskmon_t)); dmp = nextp; } } static cfgdata_t * new_cfgdata(namevalpr_t *nvp, diskmon_t *dmp) { cfgdata_t *cdp = (cfgdata_t *)dzmalloc(sizeof (cfgdata_t)); if (nvp != NULL) cdp->props = namevalpr_to_nvlist(nvp); else if (nvlist_alloc(&cdp->props, NV_UNIQUE_NAME, 0) != 0) { return (NULL); } if (dmp != NULL) cdp->disk_list = dmp; return (cdp); } static void cfgdata_add_namevalpr(cfgdata_t *cfgp, namevalpr_t *nvp) { if (cfgp->props == NULL) { (void) nvlist_alloc(&cfgp->props, NV_UNIQUE_NAME, 0); } (void) nvlist_add_string(cfgp->props, nvp->name, nvp->value); } void cfgdata_add_diskmon(cfgdata_t *cfgp, diskmon_t *dmp) { if (cfgp->disk_list == NULL) { cfgp->disk_list = dmp; } else { diskmon_t *disklist = cfgp->disk_list; while (disklist->next != NULL) disklist = disklist->next; disklist->next = dmp; } } static void cfgdata_free(cfgdata_t *cdp) { nvlist_free(cdp->props); diskmon_free(cdp->disk_list); dfree(cdp, sizeof (cfgdata_t)); } conf_err_t check_indactions(ind_action_t *indrp) { char *buf; conf_err_t rv = E_NO_ERROR; nvlist_t *nvp = NULL; int len; (void) nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0); /* * Check indicator actions for conflicts */ while (indrp != NULL && rv == E_NO_ERROR) { len = strlen(indrp->ind_name) + 2; buf = dmalloc(len); (void) snprintf(buf, len, "%c%s", indrp->ind_state == INDICATOR_ON ? '+' : '-', indrp->ind_name); switch (nvlist_lookup_boolean(nvp, buf)) { case ENOENT: (void) nvlist_add_boolean(nvp, buf); break; case 0: rv = E_IND_ACTION_REDUNDANT; break; default: break; } /* Look for the opposite action. If found, that's an error */ (void) snprintf(buf, len, "%c%s", indrp->ind_state == INDICATOR_ON ? '-' : '+', indrp->ind_name); switch (nvlist_lookup_boolean(nvp, buf)) { case ENOENT: break; case 0: rv = E_IND_ACTION_CONFLICT; break; default: break; } dfree(buf, len); indrp = indrp->next; } nvlist_free(nvp); return (rv); } conf_err_t check_inds(indicator_t *indp) { char *buf; conf_err_t rv = E_NO_ERROR; nvlist_t *nvp = NULL; int len; boolean_t fault_on = B_FALSE, fault_off = B_FALSE; (void) nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0); /* * Check inds for multiple definitions (same identifier or same action) */ while (indp != NULL && rv == E_NO_ERROR) { len = strlen(indp->ind_name) + 2; buf = dmalloc(len); (void) snprintf(buf, len, "%c%s", indp->ind_state == INDICATOR_ON ? '+' : '-', indp->ind_name); /* Keep track of the +/-FAULT for checking later */ if (strcasecmp(buf, "+" INDICATOR_FAULT_IDENTIFIER) == 0) fault_on = B_TRUE; else if (strcasecmp(buf, "-" INDICATOR_FAULT_IDENTIFIER) == 0) fault_off = B_TRUE; switch (nvlist_lookup_boolean(nvp, buf)) { case ENOENT: (void) nvlist_add_boolean(nvp, buf); break; case 0: rv = E_IND_MULTIPLY_DEFINED; break; default: break; } dfree(buf, len); indp = indp->next; } /* * Make sure we have a -FAULT and +FAULT */ if (!fault_on) rv = E_IND_MISSING_FAULT_ON; else if (!fault_off) rv = E_IND_MISSING_FAULT_OFF; nvlist_free(nvp); return (rv); } conf_err_t check_indrules(indrule_t *indrp, state_transition_t **offender) { char buf[32]; conf_err_t rv = E_NO_ERROR; nvlist_t *nvp = NULL; /* * Ensure that no two rules have the same state transitions. */ (void) nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0); while (indrp != NULL && rv == E_NO_ERROR) { (void) snprintf(buf, sizeof (buf), "%d-%d", (int)indrp->strans.begin, (int)indrp->strans.end); switch (nvlist_lookup_boolean(nvp, buf)) { case 0: *offender = &indrp->strans; rv = E_DUPLICATE_STATE_TRANSITION; break; case ENOENT: (void) nvlist_add_boolean(nvp, buf); break; default: break; } indrp = indrp->next; } nvlist_free(nvp); return (rv); } conf_err_t check_consistent_ind_indrules(indicator_t *indp, indrule_t *indrp, ind_action_t **offender) { char *buf; conf_err_t rv = E_NO_ERROR; nvlist_t *nvp = NULL; ind_action_t *alp; int len; /* * Ensure that every indicator action referenced in each ruleset * exists in the indicator list given. */ (void) nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0); while (indp != NULL) { len = strlen(indp->ind_name) + 2; buf = dmalloc(len); (void) snprintf(buf, len, "%c%s", indp->ind_state == INDICATOR_ON ? '+' : '-', indp->ind_name); (void) nvlist_add_boolean(nvp, buf); dfree(buf, len); indp = indp->next; } while (indrp != NULL && rv == E_NO_ERROR) { alp = indrp->action_list; while (alp != NULL && rv == E_NO_ERROR) { len = strlen(alp->ind_name) + 2; buf = dmalloc(len); (void) snprintf(buf, len, "%c%s", alp->ind_state == INDICATOR_ON ? '+' : '-', alp->ind_name); switch (nvlist_lookup_boolean(nvp, buf)) { case 0: /* Normal case */ break; case ENOENT: *offender = alp; rv = E_INDRULE_REFERENCES_NONEXISTENT_IND_ACTION; break; default: break; } dfree(buf, len); alp = alp->next; } indrp = indrp->next; } nvlist_free(nvp); return (rv); } conf_err_t check_state_transition(hotplug_state_t s1, hotplug_state_t s2) { /* * The following are valid transitions: * * HPS_ABSENT -> HPS_PRESENT * HPS_ABSENT -> HPS_CONFIGURED * HPS_PRESENT -> HPS_CONFIGURED * HPS_PRESENT -> HPS_ABSENT * HPS_CONFIGURED -> HPS_UNCONFIGURED * HPS_CONFIGURED -> HPS_ABSENT * HPS_UNCONFIGURED -> HPS_ABSENT * HPS_UNCONFIGURED -> HPS_CONFIGURED * */ if (s1 == HPS_ABSENT && s2 != HPS_PRESENT && s2 != HPS_CONFIGURED) return (E_INVALID_STATE_CHANGE); else if (s1 == HPS_PRESENT && (s2 != HPS_CONFIGURED && s2 != HPS_ABSENT)) return (E_INVALID_STATE_CHANGE); else if (s1 == HPS_CONFIGURED && (s2 != HPS_UNCONFIGURED && s2 != HPS_ABSENT)) return (E_INVALID_STATE_CHANGE); else if (s1 == HPS_UNCONFIGURED && (s2 != HPS_ABSENT && s2 != HPS_CONFIGURED)) return (E_INVALID_STATE_CHANGE); else return (E_NO_ERROR); } static void print_inds(indicator_t *indp, FILE *fp, char *prefix) { char plusminus; (void) fprintf(fp, "%sindicators {\n", prefix); while (indp != NULL) { plusminus = (indp->ind_state == INDICATOR_ON) ? '+' : '-'; (void) fprintf(fp, "%s\t%c%s = \"%s\"\n", prefix, plusminus, indp->ind_name, indp->ind_instr_spec); indp = indp->next; } (void) fprintf(fp, "%s}\n", prefix); } static void print_indrules(indrule_t *lrp, FILE *fp, char *prefix) { char plusminus; ind_action_t *lap; (void) fprintf(fp, "%sindicator_rules {\n", prefix); while (lrp != NULL) { (void) fprintf(fp, "%s\t%12s -> %12s\t{ ", prefix, hotplug_state_string(lrp->strans.begin), hotplug_state_string(lrp->strans.end)); lap = lrp->action_list; while (lap != NULL) { plusminus = (lap->ind_state == INDICATOR_ON) ? '+' : '-'; (void) fprintf(fp, "%c%s", plusminus, lap->ind_name); lap = lap->next; if (lap != NULL) (void) fprintf(fp, ", "); } (void) fprintf(fp, " }\n"); lrp = lrp->next; } (void) fprintf(fp, "%s}\n", prefix); } static void print_props(nvlist_t *nvlp, FILE *fp, char *prefix) { nvpair_t *nvp = nvlist_next_nvpair(nvlp, NULL); char *name, *str; while (nvp != NULL) { dm_assert(nvpair_type(nvp) == DATA_TYPE_STRING); name = nvpair_name(nvp); (void) nvlist_lookup_string(nvlp, name, &str); (void) fprintf(fp, "%s%s = \"%s\"\n", prefix, name, str); nvp = nvlist_next_nvpair(nvlp, nvp); } } static void print_ap(nvlist_t *dpp, FILE *fp, char *prefix) { int len = strlen(prefix) + 2; char *buf = dmalloc(len); (void) snprintf(buf, len, "%s\t", prefix); (void) fprintf(fp, "%sap_props {\n", prefix); print_props(dpp, fp, buf); (void) fprintf(fp, "%s}\n", prefix); dfree(buf, len); } static void print_disks(diskmon_t *dmp, FILE *fp, char *prefix) { int len = strlen(prefix) + 2; char *buf = dmalloc(len); (void) snprintf(buf, len, "%s\t", prefix); while (dmp != NULL) { (void) fprintf(fp, "%sdisk \"%s\" {\n", prefix, dmp->location); if (dmp->props) { print_props(dmp->props, fp, buf); } if (dmp->app_props) { print_ap(dmp->app_props, fp, buf); } (void) fprintf(fp, "%s\n", prefix); print_inds(dmp->ind_list, fp, buf); (void) fprintf(fp, "%s\n", prefix); print_indrules(dmp->indrule_list, fp, buf); (void) fprintf(fp, "%s}\n", prefix); if (dmp->next != NULL) (void) fprintf(fp, "%s\n", prefix); dmp = dmp->next; } dfree(buf, len); } static void print_cfgdata(cfgdata_t *cfgp, FILE *fp, char *prefix) { /* First, print the properties, then the disks */ print_props(cfgp->props, fp, prefix); (void) fprintf(fp, "%s\n", prefix); print_disks(cfgp->disk_list, fp, prefix); } int config_init(void) { if (init_configuration_from_topo() == 0) { config_data = new_cfgdata(NULL, NULL); return (0); } return (-1); } int config_get(fmd_hdl_t *hdl, const fmd_prop_t *fmd_props) { int err, i = 0; char *str = NULL; namevalpr_t nvp; uint64_t u64; boolean_t intfound = B_FALSE, strfound = B_FALSE; #define INT64_BUF_LEN 128 char buf[INT64_BUF_LEN]; u64 = fmd_prop_get_int32(hdl, GLOBAL_PROP_LOG_LEVEL); g_verbose = (int)u64; err = update_configuration_from_topo(hdl, NULL); /* Pull in the properties from the DE configuration file */ while (fmd_props[i].fmdp_name != NULL) { nvp.name = (char *)fmd_props[i].fmdp_name; switch (fmd_props[i].fmdp_type) { case FMD_TYPE_UINT32: case FMD_TYPE_INT32: intfound = B_TRUE; u64 = fmd_prop_get_int32(hdl, fmd_props[i].fmdp_name); break; case FMD_TYPE_UINT64: case FMD_TYPE_INT64: intfound = B_TRUE; u64 = fmd_prop_get_int64(hdl, fmd_props[i].fmdp_name); break; case FMD_TYPE_STRING: strfound = B_TRUE; str = fmd_prop_get_string(hdl, fmd_props[i].fmdp_name); break; } if (intfound) { (void) snprintf(buf, INT64_BUF_LEN, "0x%llx", u64); nvp.value = buf; intfound = B_FALSE; } else if (strfound) { nvp.value = str; } log_msg(MM_CONF, "Adding property `%s' with value `%s'\n", nvp.name, nvp.value); cfgdata_add_namevalpr(config_data, &nvp); if (strfound) { strfound = B_FALSE; fmd_prop_free_string(hdl, str); } i++; } if ((g_verbose & (MM_CONF|MM_OTHER)) == (MM_CONF|MM_OTHER)) print_cfgdata(config_data, stderr, ""); return (err); } void config_fini(void) { fini_configuration_from_topo(); cfgdata_free(config_data); config_data = NULL; } nvlist_t * dm_global_proplist(void) { return (config_data->props); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. */ #ifndef _DISKMOND_CONF_H #define _DISKMOND_CONF_H /* * Configuration File data */ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include "dm_types.h" #include "util.h" #ifndef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #endif #ifndef MAX #define MAX(x, y) ((x) > (y) ? (x) : (y)) #endif #define DEVICES_PREFIX "/devices" #define GLOBAL_PROP_LOG_LEVEL "log-level" /* Property names (and values) for the disk configuration file entity */ #define DISK_PROP_DEVPATH "dev-path" #define DISK_PROP_LOGNAME "logical-path" #define DISK_PROP_FRUACTION "fru-update-action" #define DISK_PROP_OTEMPACTION "overtemp-action" #define DISK_PROP_STFAILACTION "selftest-fail-action" #define DISK_PROP_SSMWEAROUTACTION "ssm-wearout-action" /* Properties for the "ap" subentity */ #define DISK_AP_PROP_APID "path" #define DEVPATH_MINOR_SEPARATOR ':' #define DEFAULT_FAULT_POLLING_INTERVAL 3600 /* seconds */ #define INDICATOR_FAULT_IDENTIFIER "FAULT" typedef enum conf_err_e { E_NO_ERROR = 0, E_MULTIPLE_IND_LISTS_DEFINED, E_MULTIPLE_INDRULE_LISTS_DEFINED, E_INVALID_STATE_CHANGE, E_IND_MULTIPLY_DEFINED, E_IND_ACTION_REDUNDANT, E_IND_ACTION_CONFLICT, E_IND_MISSING_FAULT_ON, E_IND_MISSING_FAULT_OFF, E_INDRULE_REFERENCES_NONEXISTENT_IND_ACTION, E_DUPLICATE_STATE_TRANSITION } conf_err_t; typedef enum { INDICATOR_UNKNOWN, INDICATOR_ON, INDICATOR_OFF } ind_state_t; typedef enum { TS_NOT_RUNNING, TS_RUNNING, TS_EXIT_REQUESTED, TS_EXITED } thread_state_t; typedef struct ind_action { ind_state_t ind_state; char *ind_name; struct ind_action *next; } ind_action_t; typedef struct state_transition { hotplug_state_t begin; hotplug_state_t end; } state_transition_t; typedef struct indrule { state_transition_t strans; ind_action_t *action_list; struct indrule *next; } indrule_t; typedef struct indicator { ind_state_t ind_state; char *ind_name; char *ind_instr_spec; struct indicator *next; } indicator_t; typedef struct diskmon { /* * Static configuration data */ nvlist_t *props; char *location; /* descriptive location */ nvlist_t *app_props; indicator_t *ind_list; indrule_t *indrule_list; /* * Dynamic data */ hotplug_state_t state; /* * Only one manager can be manipulating the * state in the diskmon at one time (either the * state-change manager or the fault-polling manager) */ pthread_mutex_t manager_mutex; /* * Set to true only during initialization, and * cleared the next time a fru update needs to * occur, this flag enabled an optimization of * NOT calling libtopo for a configuration update * when the DE starts up. This allows a HUGE * savings (since only a single snapshot-- the * initial snapshot) is used as the source of * the FRU information. */ boolean_t initial_configuration; /* For the state-change manager: */ /* * Current state of the fault indicator. */ pthread_mutex_t fault_indicator_mutex; ind_state_t fault_indicator_state; /* * Set to TRUE when a disk transitions to the CONFIGURED state * and remains TRUE until the disk is physically removed. */ boolean_t configured_yet; /* * The number of disk hotplug state transitions since the disk * was inserted. */ uint_t state_change_count; /* Disk FRU (model, manufacturer, etc) information */ pthread_mutex_t fru_mutex; dm_fru_t *frup; struct diskmon *next; } diskmon_t; typedef struct cfgdata { nvlist_t *props; diskmon_t *disk_list; } cfgdata_t; typedef struct namevalpr { char *name; char *value; } namevalpr_t; extern indicator_t *new_indicator(ind_state_t lstate, char *namep, char *actionp); extern void link_indicator(indicator_t **first, indicator_t *to_add); extern void ind_free(indicator_t *indp); extern ind_action_t *new_indaction(ind_state_t state, char *namep); extern void link_indaction(ind_action_t **first, ind_action_t *to_add); extern void indaction_free(ind_action_t *lap); extern indrule_t *new_indrule(state_transition_t *st, ind_action_t *actionp); extern void link_indrule(indrule_t **first, indrule_t *to_add); extern void indrule_free(indrule_t *lrp); extern diskmon_t *new_diskmon(nvlist_t *app_props, indicator_t *indp, indrule_t *indrp, nvlist_t *nvlp); extern void diskmon_free(diskmon_t *dmp); extern dm_fru_t *new_dmfru(char *manu, char *modl, char *firmrev, char *serno, uint64_t capa); extern void dmfru_free(dm_fru_t *frup); extern nvlist_t *namevalpr_to_nvlist(namevalpr_t *nvprp); extern conf_err_t check_state_transition(hotplug_state_t s1, hotplug_state_t s2); extern conf_err_t check_inds(indicator_t *indp); extern conf_err_t check_indactions(ind_action_t *indap); extern conf_err_t check_indrules(indrule_t *indrp, state_transition_t **offender); extern conf_err_t check_consistent_ind_indrules(indicator_t *indp, indrule_t *indrp, ind_action_t **offender); extern void cfgdata_add_diskmon(cfgdata_t *cfgp, diskmon_t *dmp); extern void conf_error_msg(conf_err_t err, char *buf, int buflen, void *arg); extern const char *dm_prop_lookup(nvlist_t *props, const char *prop_name); extern int dm_prop_lookup_int(nvlist_t *props, const char *prop_name, int *value); extern int config_init(void); extern int config_get(fmd_hdl_t *hdl, const fmd_prop_t *fmd_props); extern void config_fini(void); extern const char *hotplug_state_string(hotplug_state_t state); extern nvlist_t *dm_global_proplist(void); #ifdef __cplusplus } #endif #endif /* _DISKMOND_CONF_H */ /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include "diskmon_conf.h" #include "dm_platform.h" #include "util.h" /* For the purposes of disk capacity, a B is 1000x, not 1024x */ #define ONE_KILOBYTE 1000.0 #define ONE_MEGABYTE (ONE_KILOBYTE * 1000) #define ONE_GIGABYTE (ONE_MEGABYTE * 1000) #define ONE_TERABYTE (ONE_GIGABYTE * 1000) #define ONE_PETABYTE (ONE_TERABYTE * 1000) static ipmi_handle_t *g_ipmi_hdl; typedef enum { IPMI_CACHE_SENSOR, IPMI_CACHE_FRU } ipmi_cache_type_t; typedef struct ipmi_cache_entry { ipmi_cache_type_t ic_type; uu_list_node_t ic_node; union { ipmi_set_sensor_reading_t ic_sensor; ipmi_sunoem_fru_t ic_fru; } ic_data; } ipmi_cache_entry_t; static pthread_mutex_t g_ipmi_mtx = PTHREAD_MUTEX_INITIALIZER; static uu_list_pool_t *g_ipmi_cache_pool; static uu_list_t *g_ipmi_cache; /* * The textual strings that are used in the actions may be one of the * following forms: * * [1] `fru gid= hdd=' * [2] `sensor id= assert= deassert=' * * The generic parser will take a string and spit out the first token * (e.g. `fru' or `sensor') and an nvlist that contains the key-value * pairs in the rest of the string. The assumption is that there are * no embedded spaces or tabs in the keys or values. */ static boolean_t isnumber(const char *str) { boolean_t hex = B_FALSE; int digits = 0; if (strncasecmp(str, "0x", 2) == 0) { hex = B_TRUE; str += 2; } else if (*str == '-' || *str == '+') { str++; } while (*str != 0) { if ((hex && !isxdigit(*str)) || (!hex && !isdigit(*str))) { return (B_FALSE); } str++; digits++; } return ((digits == 0) ? B_FALSE : B_TRUE); } static void tolowerString(char *str) { while (*str != 0) { *str = tolower(*str); str++; } } static boolean_t parse_action_string(const char *actionString, char **cmdp, nvlist_t **propsp) { char *action; char *tok, *lasts, *eq; int actionlen; boolean_t rv = B_TRUE; if (nvlist_alloc(propsp, NV_UNIQUE_NAME, 0) != 0) return (B_FALSE); actionlen = strlen(actionString) + 1; action = dstrdup(actionString); *cmdp = NULL; if ((tok = strtok_r(action, " \t", &lasts)) != NULL) { *cmdp = dstrdup(tok); while (rv && (tok = strtok_r(NULL, " \t", &lasts)) != NULL) { /* Look for a name=val construct */ if ((eq = strchr(tok, '=')) != NULL && eq[1] != 0) { *eq = 0; eq++; /* * Convert token to lowercase to preserve * case-insensitivity, because nvlist doesn't * do case-insensitive lookups */ tolowerString(tok); if (isnumber(eq)) { /* Integer property */ if (nvlist_add_uint64(*propsp, tok, strtoull(eq, NULL, 0)) != 0) rv = B_FALSE; } else { /* String property */ if (nvlist_add_string(*propsp, tok, eq) != 0) rv = B_FALSE; } } else if (eq == NULL) { /* Boolean property */ if (nvlist_add_boolean(*propsp, tok) != 0) rv = B_FALSE; } else /* Parse error (`X=' is invalid) */ rv = B_FALSE; } } else rv = B_FALSE; dfree(action, actionlen); if (!rv) { if (*cmdp) { dstrfree(*cmdp); *cmdp = NULL; } nvlist_free(*propsp); *propsp = NULL; } return (rv); } static int platform_update_fru(nvlist_t *props, dm_fru_t *frup) { uint64_t gid, hdd; ipmi_sunoem_fru_t fru; char *buf; ipmi_cache_entry_t *entry; if (nvlist_lookup_uint64(props, "gid", &gid) != 0 || nvlist_lookup_uint64(props, "hdd", &hdd) != 0) { return (-1); } fru.isf_type = (uint8_t)gid; fru.isf_id = (uint8_t)hdd; buf = (char *)dzmalloc(sizeof (fru.isf_data.disk.isf_capacity) + 1); (void) memcpy(fru.isf_data.disk.isf_manufacturer, frup->manuf, MIN(sizeof (fru.isf_data.disk.isf_manufacturer), sizeof (frup->manuf))); (void) memcpy(fru.isf_data.disk.isf_model, frup->model, MIN(sizeof (fru.isf_data.disk.isf_model), sizeof (frup->model))); (void) memcpy(fru.isf_data.disk.isf_serial, frup->serial, MIN(sizeof (fru.isf_data.disk.isf_serial), sizeof (frup->serial))); (void) memcpy(fru.isf_data.disk.isf_version, frup->rev, MIN(sizeof (fru.isf_data.disk.isf_version), sizeof (frup->rev))); /* * Print the size of the disk to a temporary buffer whose size is * 1 more than the size of the buffer in the ipmi request data * structure, so we can get the full 8 characters (instead of 7 + NUL) */ (void) snprintf(buf, sizeof (fru.isf_data.disk.isf_capacity) + 1, "%.1f%s", frup->size_in_bytes >= ONE_PETABYTE ? (frup->size_in_bytes / ONE_PETABYTE) : (frup->size_in_bytes >= ONE_TERABYTE ? (frup->size_in_bytes / ONE_TERABYTE) : (frup->size_in_bytes >= ONE_GIGABYTE ? (frup->size_in_bytes / ONE_GIGABYTE) : (frup->size_in_bytes >= ONE_MEGABYTE ? (frup->size_in_bytes / ONE_MEGABYTE) : (frup->size_in_bytes / ONE_KILOBYTE)))), frup->size_in_bytes >= ONE_PETABYTE ? "PB" : (frup->size_in_bytes >= ONE_TERABYTE ? "TB" : (frup->size_in_bytes >= ONE_GIGABYTE ? "GB" : (frup->size_in_bytes >= ONE_MEGABYTE ? "MB" : "KB")))); (void) memcpy(fru.isf_data.disk.isf_capacity, buf, sizeof (fru.isf_data.disk.isf_capacity)); dfree(buf, sizeof (fru.isf_data.disk.isf_capacity) + 1); if (ipmi_sunoem_update_fru(g_ipmi_hdl, &fru) != 0) return (-1); /* find a cache entry or create one if necessary */ for (entry = uu_list_first(g_ipmi_cache); entry != NULL; entry = uu_list_next(g_ipmi_cache, entry)) { if (entry->ic_type == IPMI_CACHE_FRU && entry->ic_data.ic_fru.isf_type == gid && entry->ic_data.ic_fru.isf_id == hdd) break; } if (entry == NULL) { entry = dzmalloc(sizeof (ipmi_cache_entry_t)); entry->ic_type = IPMI_CACHE_FRU; (void) uu_list_insert_before(g_ipmi_cache, NULL, entry); } (void) memcpy(&entry->ic_data.ic_fru, &fru, sizeof (fru)); return (0); } static int platform_set_sensor(nvlist_t *props) { uint64_t assertmask = 0, deassertmask = 0, sid; boolean_t am_present, dam_present; ipmi_set_sensor_reading_t sr, *sp; ipmi_cache_entry_t *entry; int ret; /* We need at least 2 properties: `sid' and (`amask' || `dmask'): */ am_present = nvlist_lookup_uint64(props, "amask", &assertmask) == 0; dam_present = nvlist_lookup_uint64(props, "dmask", &deassertmask) == 0; if (nvlist_lookup_uint64(props, "sid", &sid) != 0 || (!am_present && !dam_present)) { return (-1); } if (sid > UINT8_MAX) { log_warn("IPMI Plugin: Invalid sensor id `0x%llx'.\n", (longlong_t)sid); return (-1); } else if (assertmask > UINT16_MAX) { log_warn("IPMI Plugin: Invalid assertion mask `0x%llx'.\n", (longlong_t)assertmask); return (-1); } else if (assertmask > UINT16_MAX) { log_warn("IPMI Plugin: Invalid deassertion mask `0x%llx'.\n", (longlong_t)deassertmask); return (-1); } (void) memset(&sr, '\0', sizeof (sr)); sr.iss_id = (uint8_t)sid; if (am_present) { sr.iss_assert_op = IPMI_SENSOR_OP_SET; sr.iss_assert_state = (uint16_t)assertmask; } if (dam_present) { sr.iss_deassrt_op = IPMI_SENSOR_OP_SET; sr.iss_deassert_state = (uint16_t)deassertmask; } ret = ipmi_set_sensor_reading(g_ipmi_hdl, &sr); /* find a cache entry or create one if necessary */ for (entry = uu_list_first(g_ipmi_cache); entry != NULL; entry = uu_list_next(g_ipmi_cache, entry)) { if (entry->ic_type == IPMI_CACHE_SENSOR && entry->ic_data.ic_sensor.iss_id == (uint8_t)sid) break; } if (entry == NULL) { entry = dzmalloc(sizeof (ipmi_cache_entry_t)); entry->ic_type = IPMI_CACHE_SENSOR; (void) uu_list_insert_before(g_ipmi_cache, NULL, entry); entry->ic_data.ic_sensor.iss_id = (uint8_t)sid; entry->ic_data.ic_sensor.iss_assert_op = IPMI_SENSOR_OP_SET; entry->ic_data.ic_sensor.iss_deassrt_op = IPMI_SENSOR_OP_SET; } sp = &entry->ic_data.ic_sensor; if (am_present) { sp->iss_assert_state |= assertmask; sp->iss_deassert_state &= ~assertmask; } if (dam_present) { sp->iss_deassert_state |= deassertmask; sp->iss_assert_state &= ~deassertmask; } return (ret); } #define PROTOCOL_SEPARATOR ':' static char * extract_protocol(const char *action) { char *s = strchr(action, PROTOCOL_SEPARATOR); char *proto = NULL; int len; int i = 0; /* The protocol is the string before the separator, but in lower-case */ if (s) { len = (uintptr_t)s - (uintptr_t)action; proto = (char *)dmalloc(len + 1); while (i < len) { proto[i] = tolower(action[i]); i++; } proto[len] = 0; } return (proto); } static char * extract_action(const char *action) { /* The action is the string after the separator */ char *s = strchr(action, PROTOCOL_SEPARATOR); return (s ? (s + 1) : NULL); } static int do_action(const char *action, dm_fru_t *fru) { nvlist_t *props; char *cmd; int rv = -1; char *protocol = extract_protocol(action); char *actionp = extract_action(action); if (strcmp(protocol, "ipmi") != 0) { log_err("unknown protocol '%s'\n", protocol); dstrfree(protocol); return (-1); } dstrfree(protocol); (void) pthread_mutex_lock(&g_ipmi_mtx); if (parse_action_string(actionp, &cmd, &props)) { if (strcmp(cmd, "fru") == 0) { rv = platform_update_fru(props, fru); } else if (strcmp(cmd, "state") == 0) { rv = platform_set_sensor(props); } else { log_err("unknown platform action '%s'\n", cmd); } dstrfree(cmd); nvlist_free(props); } (void) pthread_mutex_unlock(&g_ipmi_mtx); return (rv); } int dm_platform_update_fru(const char *action, dm_fru_t *fru) { return (do_action(action, fru)); } int dm_platform_indicator_execute(const char *action) { return (do_action(action, NULL)); } int dm_platform_resync(void) { ipmi_cache_entry_t *entry; int rv = 0; (void) pthread_mutex_lock(&g_ipmi_mtx); /* * Called when the SP is reset, as the sensor/FRU state is not * maintained across reboots. Note that we must update the FRU * information first, as certain sensor states prevent this from * working. */ for (entry = uu_list_first(g_ipmi_cache); entry != NULL; entry = uu_list_next(g_ipmi_cache, entry)) { if (entry->ic_type == IPMI_CACHE_FRU) rv |= ipmi_sunoem_update_fru(g_ipmi_hdl, &entry->ic_data.ic_fru); } for (entry = uu_list_first(g_ipmi_cache); entry != NULL; entry = uu_list_next(g_ipmi_cache, entry)) { if (entry->ic_type == IPMI_CACHE_SENSOR) rv |= ipmi_set_sensor_reading(g_ipmi_hdl, &entry->ic_data.ic_sensor); } (void) pthread_mutex_unlock(&g_ipmi_mtx); return (rv); } int dm_platform_init(void) { int err; char *msg; if ((g_ipmi_hdl = ipmi_open(&err, &msg, IPMI_TRANSPORT_BMC, NULL)) == NULL) { log_warn("Failed to load libipmi: %s\n", msg); return (-1); } if ((g_ipmi_cache_pool = uu_list_pool_create( "ipmi_cache", sizeof (ipmi_cache_entry_t), offsetof(ipmi_cache_entry_t, ic_node), NULL, 0)) == NULL) return (-1); if ((g_ipmi_cache = uu_list_create(g_ipmi_cache_pool, NULL, 0)) == NULL) return (-1); return (0); } void dm_platform_fini(void) { if (g_ipmi_hdl) ipmi_close(g_ipmi_hdl); if (g_ipmi_cache) { ipmi_cache_entry_t *entry; while ((entry = uu_list_first(g_ipmi_cache)) != NULL) { uu_list_remove(g_ipmi_cache, entry); dfree(entry, sizeof (*entry)); } uu_list_destroy(g_ipmi_cache); } if (g_ipmi_cache_pool) uu_list_pool_destroy(g_ipmi_cache_pool); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DM_PLATFORM_H #define _DM_PLATFORM_H #include "dm_types.h" #ifdef __cplusplus extern "C" { #endif int dm_platform_update_fru(const char *action, dm_fru_t *fru); int dm_platform_indicator_execute(const char *action); int dm_platform_resync(void); int dm_platform_init(void); void dm_platform_fini(void); #ifdef __cplusplus } #endif #endif /* _DM_PLATFORM_H */ /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DM_TYPES_H #define _DM_TYPES_H /* * Common types for the disk monitor */ #ifdef __cplusplus extern "C" { #endif #include #define NSEC_PER_POLL_POSITION 250000000UL /* 0.25 of a second */ typedef enum { HPS_UNKNOWN = 0x0000, HPS_ABSENT = 0x0001, HPS_PRESENT = 0x0002, HPS_UNCONFIGURED = 0x0003, HPS_CONFIGURED = 0x0004, HPS_FAULTED = 0x1000, /* This state can be OR'ed in */ HPS_REPAIRED = 0x2000 } hotplug_state_t; typedef struct { /* * Each field is the size of the corresponding field in the scsi * inquiry structure + 1 byte for the terminating NUL. */ char manuf[9]; /* 8 characters */ char model[33]; /* 32 characters to fo ATA ident$ */ char rev[9]; /* 8 characters */ /* * SCSI Serial number is 12 bytes from the main INQUIRY * page, but it may be longer in the Unit Serial Number * VPD page, so save space for up to 20 bytes of it (ATA * serial numbers may be up to 20-bytes long). */ char serial[21]; uint64_t size_in_bytes; } dm_fru_t; #ifdef __cplusplus } #endif #endif /* _DM_TYPES_H */ /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include /* for the EC's for DEVFS */ #include #include #include #include #include #include #include #include #include #include #include #include "disk_monitor.h" #include "hotplug_mgr.h" #include "schg_mgr.h" #include "dm_platform.h" typedef struct sysevent_event { sysevent_t *evp; } sysevent_event_t; /* Lock guarantees the ordering of the incoming sysevents */ static pthread_t g_sysev_tid; static pthread_mutex_t g_event_handler_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t g_event_handler_cond = PTHREAD_COND_INITIALIZER; static qu_t *g_sysev_queue = NULL; static thread_state_t g_sysev_thread_state = TS_NOT_RUNNING; /* * The sysevent handle is bound to the main sysevent handler * (event_handler), for each of the hotplug sysevents. */ static sysevent_handle_t *sysevent_handle = NULL; static void free_sysevent_event(void *p); static int nsleep(int seconds) { struct timespec tspec; tspec.tv_sec = seconds; tspec.tv_nsec = 0; return (nanosleep(&tspec, NULL)); } static int config_list_ext_poll(int num, char * const *path, cfga_list_data_t **list_array, int *nlist, int flag) { boolean_t done = B_FALSE; boolean_t timedout = B_FALSE; boolean_t interrupted = B_FALSE; int timeout = 0; int e; #define TIMEOUT_MAX 60 do { switch ((e = config_list_ext(num, path, list_array, nlist, NULL, NULL, NULL, flag))) { case CFGA_OK: return (CFGA_OK); case CFGA_BUSY: case CFGA_SYSTEM_BUSY: if (timeout++ >= TIMEOUT_MAX) timedout = B_TRUE; else { if (nsleep(1) < 0) interrupted = (errno == EINTR); } break; default: done = B_TRUE; break; } } while (!done && !timedout && !interrupted); return (e); } /* * Given a physical attachment point with a dynamic component * (as in the case of SCSI APs), ensure the 'controller' * portion of the dynamic component matches the physical portion. * Argument 'adjusted' must point to a buffer of at least * MAXPATHLEN bytes. */ void adjust_dynamic_ap(const char *apid, char *adjusted) { cfga_list_data_t *list_array = NULL; int nlist; char *ap_path[1]; char phys[MAXPATHLEN]; char dev_phys[MAXPATHLEN]; char *dyn; int c, t, d; dm_assert((strlen(apid) + 8 /* strlen("/devices") */) < MAXPATHLEN); /* In the case of any error, return the unadjusted APID */ (void) strcpy(adjusted, apid); /* if AP is not dynamic or not a disk node, no need to adjust it */ dyn = strstr(apid, "::"); if ((dyn == NULL) || (dyn == apid) || (sscanf(dyn, "::dsk/c%dt%dd%d", &c, &t, &d) != 3)) return; /* * Copy the AP_ID and terminate it at the '::' that we know * for a fact it contains. Pre-pend '/devices' for the sake * of cfgadm_scsi, and get the cfgadm data for the controller. */ (void) strcpy(phys, apid); *strstr(phys, "::") = '\0'; (void) snprintf(dev_phys, MAXPATHLEN, "/devices%s", phys); ap_path[0] = dev_phys; if (config_list_ext_poll(1, ap_path, &list_array, &nlist, 0) != CFGA_OK) return; dm_assert(nlist == 1); if (sscanf(list_array[0].ap_log_id, "c%d", &c) == 1) (void) snprintf(adjusted, MAXPATHLEN, "%s::dsk/c%dt%dd%d", phys, c, t, d); free(list_array); } static int disk_ap_is_scsi(const char *ap_path) { return (strstr(ap_path, ":scsi:") != NULL); } /* * Looks up the attachment point's state and returns it in one of * the hotplug states that the state change manager understands. */ hotplug_state_t disk_ap_state_to_hotplug_state(diskmon_t *diskp) { hotplug_state_t state = HPS_UNKNOWN; cfga_list_data_t *list_array = NULL; int rv, nlist; char *app = (char *)dm_prop_lookup(diskp->app_props, DISK_AP_PROP_APID); char adj_app[MAXPATHLEN]; char *ap_path[1]; char *devices_app; int len; boolean_t list_valid = B_FALSE; dm_assert(app != NULL); adjust_dynamic_ap(app, adj_app); ap_path[0] = adj_app; devices_app = NULL; rv = config_list_ext_poll(1, ap_path, &list_array, &nlist, CFGA_FLAG_LIST_ALL); if (rv != CFGA_OK) { /* * The SATA and SCSI libcfgadm plugins add a * /devices to the phys id; to use it, we must * prepend this string before the call. */ len = 8 /* strlen("/devices") */ + strlen(adj_app) + 1; devices_app = dmalloc(len); (void) snprintf(devices_app, len, "/devices%s", adj_app); ap_path[0] = devices_app; rv = config_list_ext_poll(1, ap_path, &list_array, &nlist, CFGA_FLAG_LIST_ALL); } /* * cfgadm_scsi will return an error for an absent target, * so treat an error as "absent"; otherwise, make sure * cfgadm_xxx has returned a list of 1 item */ if (rv == CFGA_OK) { dm_assert(nlist == 1); list_valid = B_TRUE; } else if (disk_ap_is_scsi(ap_path[0])) state = HPS_ABSENT; if (devices_app != NULL) dfree(devices_app, len); if (list_valid) { /* * The following truth table defines how each state is * computed: * * +----------------------------------------------+ * | | o_state | r_state | condition | * | +---------+---------+-----------| * | Absent |Don'tCare|Disc/Empt| Don'tCare | * | Present |Unconfgrd|Connected| unknown | * | Configured |Configred|Connected| Don'tCare | * | Unconfigured |Unconfgrd|Connected| OK | * +--------------+---------+---------+-----------+ */ if (list_array[0].ap_r_state == CFGA_STAT_EMPTY || list_array[0].ap_r_state == CFGA_STAT_DISCONNECTED) state = HPS_ABSENT; else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED && list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED && list_array[0].ap_cond == CFGA_COND_UNKNOWN) state = HPS_PRESENT; else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED && list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED && list_array[0].ap_cond != CFGA_COND_UNKNOWN) state = HPS_UNCONFIGURED; else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED && list_array[0].ap_o_state == CFGA_STAT_CONFIGURED) state = HPS_CONFIGURED; free(list_array); } return (state); } /* * Examine the sysevent passed in and returns the hotplug state that * the sysevent states (or implies, in the case of attachment point * events). */ static hotplug_state_t disk_sysev_to_state(diskmon_t *diskp, sysevent_t *evp) { const char *class_name, *subclass; hotplug_state_t state = HPS_UNKNOWN; sysevent_value_t se_val; /* * The state mapping is as follows: * * Sysevent State * -------------------------------------------------------- * EC_DEVFS/ESC_DEVFS_DEVI_ADD Configured * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE Unconfigured * EC_DR/ESC_DR_AP_STATE_CHANGE *[Absent/Present] * * (The EC_DR event requires a probe of the attachment point * to determine the AP's state if there is no usable HINT) * */ class_name = sysevent_get_class_name(evp); subclass = sysevent_get_subclass_name(evp); if (strcmp(class_name, EC_DEVFS) == 0) { if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0) { state = HPS_CONFIGURED; } else if (strcmp(subclass, ESC_DEVFS_DEVI_REMOVE) == 0) { state = HPS_UNCONFIGURED; } } else if (strcmp(class_name, EC_DR) == 0 && ((strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) || (strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0))) { if (sysevent_lookup_attr(evp, DR_HINT, SE_DATA_TYPE_STRING, &se_val) == 0 && se_val.value.sv_string != NULL) { if (strcmp(se_val.value.sv_string, DR_HINT_INSERT) == 0) { state = HPS_PRESENT; } else if (strcmp(se_val.value.sv_string, DR_HINT_REMOVE) == 0) { state = HPS_ABSENT; } } /* * If the state could not be determined by the hint * (or there was no hint), ask the AP directly. * SCSI HBAs may send an insertion sysevent * *after* configuring the target node, so double- * check HPS_PRESENT */ if ((state == HPS_UNKNOWN) || (state = HPS_PRESENT)) state = disk_ap_state_to_hotplug_state(diskp); } return (state); } static void disk_split_ap_path_sata(const char *ap_path, char *device, int *target) { char *p; int n; /* * /devices/rootnode/.../device:target */ (void) strncpy(device, ap_path, MAXPATHLEN); p = strrchr(device, ':'); dm_assert(p != NULL); n = sscanf(p, ":%d", target); dm_assert(n == 1); *p = '\0'; } static void disk_split_ap_path_scsi(const char *ap_path, char *device, int *target) { char *p; int n; /* * /devices/rootnode/.../device:scsi::dsk/cXtXdX */ (void) strncpy(device, ap_path, MAXPATHLEN); p = strrchr(device, ':'); dm_assert(p != NULL); n = sscanf(p, ":dsk/c%*dt%dd%*d", target); dm_assert(n == 1); *strchr(device, ':') = '\0'; } static void disk_split_ap_path(const char *ap_path, char *device, int *target) { /* * The AP path comes in two forms; for SATA devices, * is is of the form: * /devices/rootnode/.../device:portnum * and for SCSI devices, it is of the form: * /devices/rootnode/.../device:scsi::dsk/cXtXdX */ if (disk_ap_is_scsi(ap_path)) disk_split_ap_path_scsi(ap_path, device, target); else disk_split_ap_path_sata(ap_path, device, target); } static void disk_split_device_path(const char *dev_path, char *device, int *target) { char *t, *p, *e; /* * The disk device path is of the form: * /rootnode/.../device/target@tgtid,tgtlun */ (void) strncpy(device, dev_path, MAXPATHLEN); e = t = strrchr(device, '/'); dm_assert(t != NULL); t = strchr(t, '@'); dm_assert(t != NULL); t += 1; if ((p = strchr(t, ',')) != NULL) *p = '\0'; *target = strtol(t, 0, 16); *e = '\0'; } /* * Returns the diskmon that corresponds to the physical disk path * passed in. */ static diskmon_t * disk_match_by_device_path(diskmon_t *disklistp, const char *dev_path) { char dev_device[MAXPATHLEN]; int dev_target; char ap_device[MAXPATHLEN]; int ap_target; dm_assert(disklistp != NULL); dm_assert(dev_path != NULL); if (strncmp(dev_path, DEVICES_PREFIX, 8) == 0) dev_path += 8; /* pare dev_path into device and target components */ disk_split_device_path(dev_path, (char *)&dev_device, &dev_target); /* * The AP path specified in the configuration properties is * the path to an attachment point minor node whose port number is * equal to the target number on the disk "major" node sent by the * sysevent. To match them, we need to extract the target id and * construct an AP string to compare to the AP path in the diskmon. */ while (disklistp != NULL) { char *app = (char *)dm_prop_lookup(disklistp->app_props, DISK_AP_PROP_APID); dm_assert(app != NULL); /* Not necessary to adjust the APID here */ if (strncmp(app, DEVICES_PREFIX, 8) == 0) app += 8; disk_split_ap_path(app, (char *)&ap_device, &ap_target); if ((strcmp(dev_device, ap_device) == 0) && (dev_target == ap_target)) return (disklistp); disklistp = disklistp->next; } return (NULL); } static diskmon_t * disk_match_by_ap_id(diskmon_t *disklistp, const char *ap_id) { const char *disk_ap_id; dm_assert(disklistp != NULL); dm_assert(ap_id != NULL); /* Match only the device-tree portion of the name */ if (strncmp(ap_id, DEVICES_PREFIX, 8 /* strlen("/devices") */) == 0) ap_id += 8; while (disklistp != NULL) { disk_ap_id = dm_prop_lookup(disklistp->app_props, DISK_AP_PROP_APID); dm_assert(disk_ap_id != NULL); if (strcmp(disk_ap_id, ap_id) == 0) return (disklistp); disklistp = disklistp->next; } return (NULL); } static diskmon_t * disk_match_by_target_id(diskmon_t *disklistp, const char *target_path) { const char *disk_ap_id; char match_device[MAXPATHLEN]; int match_target; char ap_device[MAXPATHLEN]; int ap_target; /* Match only the device-tree portion of the name */ if (strncmp(target_path, DEVICES_PREFIX, 8) == 0) target_path += 8; disk_split_ap_path(target_path, (char *)&match_device, &match_target); while (disklistp != NULL) { disk_ap_id = dm_prop_lookup(disklistp->app_props, DISK_AP_PROP_APID); dm_assert(disk_ap_id != NULL); disk_split_ap_path(disk_ap_id, (char *)&ap_device, &ap_target); if ((match_target == ap_target) && (strcmp(match_device, ap_device) == 0)) return (disklistp); disklistp = disklistp->next; } return (NULL); } static diskmon_t * match_sysevent_to_disk(diskmon_t *disklistp, sysevent_t *evp) { diskmon_t *dmp = NULL; sysevent_value_t se_val; char *class_name = sysevent_get_class_name(evp); char *subclass = sysevent_get_subclass_name(evp); se_val.value.sv_string = NULL; if (strcmp(class_name, EC_DEVFS) == 0) { /* EC_DEVFS-class events have a `DEVFS_PATHNAME' property */ if (sysevent_lookup_attr(evp, DEVFS_PATHNAME, SE_DATA_TYPE_STRING, &se_val) == 0 && se_val.value.sv_string != NULL) { dmp = disk_match_by_device_path(disklistp, se_val.value.sv_string); } } else if (strcmp(class_name, EC_DR) == 0 && strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) { /* EC_DR-class events have a `DR_AP_ID' property */ if (sysevent_lookup_attr(evp, DR_AP_ID, SE_DATA_TYPE_STRING, &se_val) == 0 && se_val.value.sv_string != NULL) { dmp = disk_match_by_ap_id(disklistp, se_val.value.sv_string); } } else if (strcmp(class_name, EC_DR) == 0 && strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0) { /* get DR_TARGET_ID */ if (sysevent_lookup_attr(evp, DR_TARGET_ID, SE_DATA_TYPE_STRING, &se_val) == 0 && se_val.value.sv_string != NULL) { dmp = disk_match_by_target_id(disklistp, se_val.value.sv_string); } } if (se_val.value.sv_string) log_msg(MM_HPMGR, "match_sysevent_to_disk: device/ap: %s\n", se_val.value.sv_string); return (dmp); } /* * The disk hotplug monitor (DHPM) listens for disk hotplug events and calls the * state-change functionality when a disk's state changes. The DHPM listens for * hotplug events via sysevent subscriptions to the following sysevent * classes/subclasses: { EC_DEVFS/ESC_DEVFS_BRANCH_ADD, * EC_DEVFS/ESC_DEVFS_BRANCH_REMOVE, EC_DEVFS/ESC_DEVFS_DEVI_ADD, * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE, EC_DR/ESC_DR_AP_STATE_CHANGE }. Once the * event is received, the device path sent as part of the event is matched * to one of the disks described by the configuration data structures. */ static void dm_process_sysevent(sysevent_t *dupev) { char *class_name; char *pub; char *subclass = sysevent_get_subclass_name(dupev); diskmon_t *diskp; class_name = sysevent_get_class_name(dupev); log_msg(MM_HPMGR, "****EVENT: %s %s (by %s)\n", class_name, subclass, ((pub = sysevent_get_pub_name(dupev)) != NULL) ? pub : "UNKNOWN"); if (pub) free(pub); if (strcmp(class_name, EC_PLATFORM) == 0 && strcmp(subclass, ESC_PLATFORM_SP_RESET) == 0) { if (dm_platform_resync() != 0) log_warn("failed to resync SP platform\n"); sysevent_free(dupev); return; } /* * We will handle this event if the event's target matches one of the * disks we're monitoring */ if ((diskp = match_sysevent_to_disk(config_data->disk_list, dupev)) != NULL) { dm_state_change(diskp, disk_sysev_to_state(diskp, dupev)); } sysevent_free(dupev); } static void dm_fmd_sysevent_thread(void *queuep) { qu_t *qp = (qu_t *)queuep; sysevent_event_t *sevevp; /* Signal the thread spawner that we're running */ dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0); if (g_sysev_thread_state != TS_EXIT_REQUESTED) g_sysev_thread_state = TS_RUNNING; (void) pthread_cond_broadcast(&g_event_handler_cond); dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0); while (g_sysev_thread_state != TS_EXIT_REQUESTED) { if ((sevevp = (sysevent_event_t *)queue_remove(qp)) == NULL) continue; dm_process_sysevent(sevevp->evp); free_sysevent_event(sevevp); } /* Signal the thread spawner that we've exited */ dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0); g_sysev_thread_state = TS_EXITED; (void) pthread_cond_broadcast(&g_event_handler_cond); dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0); log_msg(MM_HPMGR, "FMD sysevent handler thread exiting..."); } static sysevent_event_t * new_sysevent_event(sysevent_t *ev) { /* * Cannot use dmalloc for this because the thread isn't a FMD-created * thread! */ sysevent_event_t *sevevp = malloc(sizeof (sysevent_event_t)); sevevp->evp = ev; return (sevevp); } static void free_sysevent_event(void *p) { /* the sysevent_event was allocated with malloc(): */ free(p); } static void event_handler(sysevent_t *ev) { /* The duplicated sysevent will be freed in the child thread */ sysevent_t *dupev = sysevent_dup(ev); /* * Add this sysevent to the work queue of our FMA thread so we can * handle the sysevent and use the FMA API (e.g. for memory * allocation, etc.) in the sysevent handler. */ queue_add(g_sysev_queue, new_sysevent_event(dupev)); } static void fini_sysevents(void) { sysevent_unsubscribe_event(sysevent_handle, EC_ALL); } static int init_sysevents(void) { int rv = 0; const char *devfs_subclasses[] = { ESC_DEVFS_DEVI_ADD, ESC_DEVFS_DEVI_REMOVE }; const char *dr_subclasses[] = { ESC_DR_AP_STATE_CHANGE, ESC_DR_TARGET_STATE_CHANGE }; const char *platform_subclasses[] = { ESC_PLATFORM_SP_RESET }; if ((sysevent_handle = sysevent_bind_handle(event_handler)) == NULL) { rv = errno; log_err("Could not initialize the hotplug manager (" "sysevent_bind_handle failure"); } if (sysevent_subscribe_event(sysevent_handle, EC_DEVFS, devfs_subclasses, sizeof (devfs_subclasses)/sizeof (devfs_subclasses[0])) != 0) { log_err("Could not initialize the hotplug manager " "sysevent_subscribe_event(event class = EC_DEVFS) " "failure"); rv = -1; } else if (sysevent_subscribe_event(sysevent_handle, EC_DR, dr_subclasses, sizeof (dr_subclasses)/sizeof (dr_subclasses[0])) != 0) { log_err("Could not initialize the hotplug manager " "sysevent_subscribe_event(event class = EC_DR) " "failure"); /* Unsubscribe from all sysevents in the event of a failure */ fini_sysevents(); rv = -1; } else if (sysevent_subscribe_event(sysevent_handle, EC_PLATFORM, platform_subclasses, sizeof (platform_subclasses)/sizeof (platform_subclasses[0])) != 0) { log_err("Could not initialize the hotplug manager " "sysevent_subscribe_event(event class = EC_PLATFORM) " "failure"); /* Unsubscribe from all sysevents in the event of a failure */ fini_sysevents(); rv = -1; } return (rv); } /*ARGSUSED*/ static void stdfree(void *p, size_t sz) { free(p); } /* * Assumptions: Each disk's current state was determined and stored in * its diskmon_t. */ hotplug_mgr_init_err_t init_hotplug_manager() { /* Create the queue to which we'll add sysevents */ g_sysev_queue = new_queue(B_TRUE, malloc, stdfree, free_sysevent_event); /* * Grab the event handler lock before spawning the thread so we can * wait for the thread to transition to the running state. */ dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0); /* Create the sysevent handling thread */ g_sysev_tid = fmd_thr_create(g_fm_hdl, dm_fmd_sysevent_thread, g_sysev_queue); /* Wait for the thread's acknowledgement */ while (g_sysev_thread_state != TS_RUNNING) (void) pthread_cond_wait(&g_event_handler_cond, &g_event_handler_lock); dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0); if (init_sysevents() != 0) { log_warn_e("Error initializing sysevents"); return (HPM_ERR_SYSEVENT_INIT); } return (0); } void cleanup_hotplug_manager() { /* Unsubscribe from the sysevents */ fini_sysevents(); /* * Wait for the thread to exit before we can destroy * the event queue. */ dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0); g_sysev_thread_state = TS_EXIT_REQUESTED; queue_add(g_sysev_queue, NULL); while (g_sysev_thread_state != TS_EXITED) (void) pthread_cond_wait(&g_event_handler_cond, &g_event_handler_lock); dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0); (void) pthread_join(g_sysev_tid, NULL); fmd_thr_destroy(g_fm_hdl, g_sysev_tid); /* Finally, destroy the event queue and reset the thread state */ queue_free(&g_sysev_queue); g_sysev_thread_state = TS_NOT_RUNNING; } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _HOTPLUG_MGR_H #define _HOTPLUG_MGR_H /* * Hotplug Manager declarations */ #ifdef __cplusplus extern "C" { #endif /* These errors are OR'able */ typedef enum { HPM_ERR_POLLTHR_CREATION_FAILURES = 1, HPM_ERR_SYSEVENT_INIT = 2 } hotplug_mgr_init_err_t; extern hotplug_state_t disk_ap_state_to_hotplug_state(diskmon_t *diskp); extern hotplug_mgr_init_err_t init_hotplug_manager(void); extern void cleanup_hotplug_manager(void); extern void adjust_dynamic_ap(const char *, char *); #ifdef __cplusplus } #endif #endif /* _HOTPLUG_MGR_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include "disk_monitor.h" #include "schg_mgr.h" #include "hotplug_mgr.h" #include "topo_gather.h" #include "dm_platform.h" /* State-change event processing thread data */ static pthread_t g_schg_tid; static thread_state_t g_schgt_state = TS_NOT_RUNNING; static pthread_mutex_t g_schgt_state_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t g_schgt_state_cvar = PTHREAD_COND_INITIALIZER; static pthread_mutex_t g_schgt_add_mutex = PTHREAD_MUTEX_INITIALIZER; static qu_t *g_schg_queue = NULL; static void dm_state_change_nolock(diskmon_t *diskp, hotplug_state_t newstate); /* * Each disk state change is described by an instance of the following * structure (which includes the disk object and the new state) */ typedef struct disk_statechg { diskmon_t *diskp; hotplug_state_t newstate; } disk_statechg_t; static disk_statechg_t * new_statechange(diskmon_t *diskp, hotplug_state_t state) { disk_statechg_t *dscp = (disk_statechg_t *)dmalloc(sizeof (disk_statechg_t)); /* * The states are additive -- we don't need to preserve * the current faulted state in the newstate: */ dscp->diskp = diskp; dscp->newstate = state; return (dscp); } static void free_statechange(void *dscp) { dfree(dscp, sizeof (disk_statechg_t)); } static void add_to_statechange_queue(diskmon_t *diskp, hotplug_state_t newstate) { queue_add(g_schg_queue, new_statechange(diskp, newstate)); } static const char * lookup_action_string(indicator_t *ind_listp, ind_state_t state, char *name) { const char *str = NULL; while (ind_listp != NULL) { if (state == ind_listp->ind_state && strcasecmp(ind_listp->ind_name, name) == 0) { str = ind_listp->ind_instr_spec; break; } ind_listp = ind_listp->next; } return (str); } void dm_fault_indicator_set(diskmon_t *diskp, ind_state_t istate) { const char *astring; dm_assert(pthread_mutex_lock(&diskp->fault_indicator_mutex) == 0); /* * No need to execute redundant indicator actions */ if (istate == INDICATOR_UNKNOWN || diskp->fault_indicator_state == istate) { dm_assert(pthread_mutex_unlock(&diskp->fault_indicator_mutex) == 0); return; } astring = lookup_action_string(diskp->ind_list, istate, INDICATOR_FAULT_IDENTIFIER); if (astring != NULL) { log_msg(MM_SCHGMGR, "Executing action `%s'\n", astring); if (dm_platform_indicator_execute(astring) != 0) { log_warn("[Disk in %s] Action `%s' did not complete " "successfully.\n", diskp->location, astring); } else { diskp->fault_indicator_state = istate; log_msg(MM_SCHGMGR, "Action `%s' executed " "successfully\n", astring); } } dm_assert(pthread_mutex_unlock(&diskp->fault_indicator_mutex) == 0); } static void schg_execute_state_change_action(diskmon_t *diskp, hotplug_state_t oldstate, hotplug_state_t newstate) { indrule_t *rulelist; ind_action_t *actions; const char *astring; log_msg(MM_SCHGMGR, "[Disk in %s] State change action: %s -> %s\n", diskp->location, hotplug_state_string(oldstate), hotplug_state_string(newstate)); /* * Find the list of actions that correspond to this state change. * If the old state is UNKNOWN, then we'll match to first action * whose transition state is the new state. */ rulelist = diskp->indrule_list; while (rulelist != NULL) { if ((oldstate == HPS_UNKNOWN || rulelist->strans.begin == oldstate) && rulelist->strans.end == newstate) break; rulelist = rulelist->next; } if (rulelist != NULL) { /* Now we have a set of actions to perform: */ actions = rulelist->action_list; while (actions != NULL) { astring = lookup_action_string(diskp->ind_list, actions->ind_state, actions->ind_name); dm_assert(astring != NULL); log_msg(MM_SCHGMGR, "Executing action `%s'\n", astring); if (dm_platform_indicator_execute(astring) != 0) { log_warn("[Disk in %s][State transition from " "%s to %s] Action `%s' did not complete " "successfully.\n", diskp->location, hotplug_state_string(oldstate), hotplug_state_string(newstate), astring); } else log_msg(MM_SCHGMGR, "Action `%s' executed successfully\n", astring); actions = actions->next; } } } static void schg_send_fru_update(diskmon_t *diskp, dm_fru_t *frup) { const char *action = dm_prop_lookup(diskp->props, DISK_PROP_FRUACTION); if (action == NULL) { log_msg(MM_SCHGMGR|MM_NOTE, "No FRU update action for disk " "in %s\n", diskp->location); return; } if (dm_platform_update_fru(action, frup) != 0) { log_warn("Error updating FRU information for disk in %s.\n", diskp->location); } } static void schg_update_fru_info(diskmon_t *diskp) { if (diskp->initial_configuration || update_configuration_from_topo(g_fm_hdl, diskp) == TOPO_SUCCESS) { diskp->initial_configuration = B_FALSE; dm_assert(pthread_mutex_lock(&diskp->fru_mutex) == 0); if (diskp->frup != NULL) schg_send_fru_update(diskp, diskp->frup); else log_warn("frup unexpectedly went away: not updating " "FRU information for disk %s!\n", diskp->location); dm_assert(pthread_mutex_unlock(&diskp->fru_mutex) == 0); } else { log_warn_e("Error retrieving FRU information " "for disk in %s", diskp->location); } } void block_state_change_events(void) { dm_assert(pthread_mutex_lock(&g_schgt_add_mutex) == 0); } void unblock_state_change_events(void) { dm_assert(pthread_mutex_unlock(&g_schgt_add_mutex) == 0); } static void disk_state_change_first_time(diskmon_t *diskp) { hotplug_state_t firststate; /* * Grab the current state of the attachment point to initialize the * initial disk state. Create a disk state change with this new * state so it will be processed in the loop below. If we can't get * the initial state for some reason, then we'll just end up doing it * later when we get a state change from the hotplug monitor or the * fault monitor. */ firststate = disk_ap_state_to_hotplug_state(diskp); if (firststate != HPS_UNKNOWN) dm_state_change_nolock(diskp, firststate); /* * The fault indicators will be updated when faults are replayed * based on the state of the disk as faulty in the fmd resource cache. * A FAULTED state change will come from the _recv function when the * fault component event is replayed. */ } static void disk_state_change_thread(void *vdisklistp) { diskmon_t *disklistp = (diskmon_t *)vdisklistp; diskmon_t *diskp; disk_statechg_t *dscp; hotplug_state_t nextstate; const char *pth; /* * Perform startup activities to initialize the state of the * indicators for each disk. */ diskp = disklistp; while (diskp != NULL) { disk_state_change_first_time(diskp); diskp = diskp->next; } unblock_state_change_events(); dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0); if (g_schgt_state != TS_EXIT_REQUESTED) { g_schgt_state = TS_RUNNING; dm_assert(pthread_cond_broadcast(&g_schgt_state_cvar) == 0); } dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0); while (g_schgt_state != TS_EXIT_REQUESTED) { if ((dscp = (disk_statechg_t *)queue_remove(g_schg_queue)) == NULL) { dm_assert(g_schgt_state == TS_EXIT_REQUESTED); continue; } diskp = dscp->diskp; /* * If the new state is the faulted state, add that state to * the disk's current state. */ if (dscp->newstate == HPS_FAULTED) { /* * If the disk wasn't previously in the faulted state, * execute the generic fault action. Even if we're * in the faulted state, accept additional faults. */ nextstate = DISK_STATE(diskp->state) | HPS_FAULTED; } else if (dscp->newstate == HPS_REPAIRED) { nextstate = DISK_STATE(diskp->state); } else if (dscp->newstate == HPS_ABSENT) { /* * If the new state is ABSENT, forget any faults */ nextstate = HPS_ABSENT; } else nextstate = dscp->newstate | DISK_FAULTED(diskp->state); /* * When a new disk is inserted and reaches the CONFIGURED state, * the following actions must be done in the following order: * * (1) Execute the configuration-specified action on the * state change. * (2) Retreive the FRU information from the disk and execute * the FRU-update action specified, * (3) Initialize the fault monitor state associated with * the new drive. * * Once the disk is no longer "new" (a disk is "new" when it * has not yet reached the CONFIGURED state), subsequent * transitions away and back to CONFIGURED (as long as the * disk is not physically removed) will result in the * execution of the predefined action ONLY. * */ if (dscp->newstate != HPS_FAULTED && DISK_STATE(nextstate) != HPS_UNKNOWN && dscp->newstate != HPS_REPAIRED) { schg_execute_state_change_action(diskp, DISK_STATE(diskp->state), DISK_STATE(nextstate)); } if (!diskp->configured_yet && DISK_STATE(nextstate) == HPS_CONFIGURED) { schg_update_fru_info(diskp); /* * If this state transition is lagging the true * state of the system (e.g. if the true state of * the disk is UNCONFIGURED, there's another * state change somewhere later in the queue), then * it's possible for the disk path property to not * exist. */ if (dm_prop_lookup(diskp->props, DISK_PROP_DEVPATH) == NULL) { log_msg(MM_SCHGMGR, "Processed stale state change " "for disk %s\n", diskp->location); } else { diskp->configured_yet = B_TRUE; } } dm_assert(pthread_mutex_lock(&diskp->manager_mutex) == 0); /* * Make the new state visible to all observers */ diskp->state = nextstate; /* * Now, update the diskmon if the disk is now absent -- it's * essential to do this after the state is set (above) so that * state observers in other threads don't try to access the * data structures that we're freeing here. */ if (diskp->configured_yet && DISK_STATE(nextstate) == HPS_ABSENT) { /* * When the disk is removed, the fault monitor state is * useless, so discard it. */ dm_assert(DISK_STATE(nextstate) != HPS_CONFIGURED); diskp->configured_yet = B_FALSE; } dm_assert(pthread_mutex_unlock(&diskp->manager_mutex) == 0); pth = dm_prop_lookup(diskp->props, DISK_PROP_DEVPATH); log_msg(MM_SCHGMGR, "[State change #%d][%s]: Disk path = %s\n", diskp->state_change_count, diskp->location, pth == NULL ? "Unknown" : pth); log_msg(MM_SCHGMGR, "[State change #%d][%s]: New state = %s%s\n", diskp->state_change_count, diskp->location, hotplug_state_string(diskp->state), DISK_FAULTED(diskp->state) ? "+FAULTED" : ""); atomic_inc_uint(&diskp->state_change_count); /* The caller is responsible for freeing the state change: */ free_statechange(dscp); } dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0); g_schgt_state = TS_EXITED; dm_assert(pthread_cond_broadcast(&g_schgt_state_cvar) == 0); dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0); log_msg(MM_SCHGMGR, "State change thread exiting...\n"); } static void dm_state_change_nolock(diskmon_t *diskp, hotplug_state_t newstate) { /* Enqueue a new state change for the state-change thread */ add_to_statechange_queue(diskp, newstate); } void dm_state_change(diskmon_t *diskp, hotplug_state_t newstate) { dm_assert(pthread_mutex_lock(&g_schgt_add_mutex) == 0); dm_state_change_nolock(diskp, newstate); dm_assert(pthread_mutex_unlock(&g_schgt_add_mutex) == 0); } int init_state_change_manager(cfgdata_t *cfgdatap) { /* new_queue() is guaranteed to succeed */ g_schg_queue = new_queue(B_TRUE, dmalloc, dfree, free_statechange); dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0); g_schg_tid = fmd_thr_create(g_fm_hdl, disk_state_change_thread, cfgdatap->disk_list); /* * Now, wait for the thread to enter the TS_RUNNING state. This * is important because we want the state-change thread to pull the * initial state of the disks on startup (without the wait, we could * have the hotplug event handler race and deliver a state change * before the state-change thread initialized the initial disk state). */ while (g_schgt_state != TS_RUNNING) { (void) pthread_cond_wait(&g_schgt_state_cvar, &g_schgt_state_mutex); } dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0); return (0); } /*ARGSUSED*/ void cleanup_state_change_manager(cfgdata_t *cfgdatap) { if (g_schgt_state != TS_RUNNING) return; g_schgt_state = TS_EXIT_REQUESTED; queue_add(g_schg_queue, NULL); dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0); while (g_schgt_state != TS_EXITED) dm_assert(pthread_cond_wait(&g_schgt_state_cvar, &g_schgt_state_mutex) == 0); dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0); (void) pthread_join(g_schg_tid, NULL); fmd_thr_destroy(g_fm_hdl, g_schg_tid); queue_free(&g_schg_queue); g_schgt_state = TS_NOT_RUNNING; } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SCHG_MGR_H #define _SCHG_MGR_H /* * State Change Manager declarations */ #ifdef __cplusplus extern "C" { #endif #include "dm_types.h" #define DISK_STATE(d) ((d) & (~HPS_FAULTED)) #define DISK_FAULTED(d) ((d) & HPS_FAULTED) extern int init_state_change_manager(cfgdata_t *cfgdatap); extern void cleanup_state_change_manager(cfgdata_t *cfgdatap); extern void dm_state_change(diskmon_t *diskp, hotplug_state_t newstate); extern void dm_fault_indicator_set(diskmon_t *diskp, ind_state_t istate); extern void block_state_change_events(void); extern void unblock_state_change_events(void); #ifdef __cplusplus } #endif #endif /* _SCHG_MGR_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Gathers properties exported by libtopo and uses them to construct diskmon * data structures, which hold the configuration information for the * DE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "disk.h" #include "disk_monitor.h" #include "hotplug_mgr.h" #include "topo_gather.h" #define TOPO_PGROUP_IO "io" /* duplicated from did_props.h */ #define MAX_CONF_MSG_LEN 256 static nvlist_t *g_topo2diskmon = NULL; /* * The following function template is required for nvlists that were * create with no flags (so there can be multiple identical name or name-value * pairs). The function defined below returns the first match for the name * provided. */ #define NONUNIQUE_NVLIST_FN(suffix, type, atype) \ static int \ nonunique_nvlist_lookup_##suffix(nvlist_t *nvlp, const char *n, atype *rpp) \ { \ nvpair_t *nvp = NULL; \ while ((nvp = nvlist_next_nvpair(nvlp, nvp)) != NULL) { \ if (nvpair_type(nvp) != type) \ continue; \ if (strcmp(nvpair_name(nvp), n) == 0) \ return (nvpair_value_##suffix(nvp, rpp)); \ } \ return (ENOENT); \ } NONUNIQUE_NVLIST_FN(string, DATA_TYPE_STRING, char *) static diskmon_t * dm_fmristring_to_diskmon(char *str) { diskmon_t *p = NULL; uint64_t u64val; char ch; char *lastsl = strrchr(str, '/'); ch = *lastsl; *lastsl = 0; if (nvlist_lookup_uint64(g_topo2diskmon, str, &u64val) == 0) { p = (diskmon_t *)(uintptr_t)u64val; } *lastsl = ch; return (p); } diskmon_t * dm_fmri_to_diskmon(fmd_hdl_t *hdl, nvlist_t *fmri) { topo_hdl_t *thdl; nvlist_t *dupfmri; diskmon_t *diskp; char *buf; int err; if (nvlist_dup(fmri, &dupfmri, 0) != 0) return (NULL); (void) nvlist_remove(dupfmri, FM_FMRI_HC_REVISION, DATA_TYPE_STRING); (void) nvlist_remove(dupfmri, FM_FMRI_HC_SERIAL_ID, DATA_TYPE_STRING); (void) nvlist_remove(dupfmri, FM_FMRI_HC_PART, DATA_TYPE_STRING); thdl = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if (topo_fmri_nvl2str(thdl, dupfmri, &buf, &err) != 0) { fmd_hdl_topo_rele(hdl, thdl); nvlist_free(dupfmri); return (NULL); } fmd_hdl_topo_rele(hdl, thdl); diskp = dm_fmristring_to_diskmon(buf); nvlist_free(dupfmri); topo_hdl_strfree(thdl, buf); return (diskp); } static nvlist_t * find_disk_monitor_private_pgroup(tnode_t *node) { int err; nvlist_t *list_of_lists, *nvlp, *dupnvlp; nvlist_t *disk_monitor_pgrp = NULL; nvpair_t *nvp = NULL; char *pgroup_name; /* * topo_prop_get_all() returns an nvlist that contains other * nvlists (some of which are property groups). Since the private * property group we need will be among the list of property * groups returned (hopefully), we need to walk the list of nvlists * in the topo node's properties to find the property groups, then * check inside each embedded nvlist to see if it's the pgroup we're * looking for. */ if ((list_of_lists = topo_prop_getprops(node, &err)) != NULL) { /* * Go through the list of nvlists, looking for the * property group we need. */ while ((nvp = nvlist_next_nvpair(list_of_lists, nvp)) != NULL) { if (nvpair_type(nvp) != DATA_TYPE_NVLIST || strcmp(nvpair_name(nvp), TOPO_PROP_GROUP) != 0 || nvpair_value_nvlist(nvp, &nvlp) != 0) continue; dm_assert(nvlp != NULL); pgroup_name = NULL; if (nonunique_nvlist_lookup_string(nvlp, TOPO_PROP_GROUP_NAME, &pgroup_name) != 0 || strcmp(pgroup_name, DISK_MONITOR_PROPERTIES) != 0) continue; else { /* * Duplicate the nvlist so that when the * master nvlist is freed (below), we will * still refer to allocated memory. */ if (nvlist_dup(nvlp, &dupnvlp, 0) == 0) disk_monitor_pgrp = dupnvlp; else disk_monitor_pgrp = NULL; break; } } nvlist_free(list_of_lists); } return (disk_monitor_pgrp); } /* * Look up the FMRI corresponding to the node in the global * hash table and return the pointer stored (if any). Save the * FMRI string in *str if str is non-NULL. */ static void * fmri2ptr(topo_hdl_t *thp, tnode_t *node, char **str, int *err) { nvlist_t *fmri = NULL; char *cstr = NULL; uint64_t u64val; void *p = NULL; if (topo_node_resource(node, &fmri, err) != 0) return (NULL); if (topo_fmri_nvl2str(thp, fmri, &cstr, err) != 0) { nvlist_free(fmri); return (NULL); } if (nvlist_lookup_uint64(g_topo2diskmon, cstr, &u64val) == 0) { p = (void *)(uintptr_t)u64val; } nvlist_free(fmri); if (str != NULL) *str = dstrdup(cstr); topo_hdl_strfree(thp, cstr); return (p); } typedef struct walk_diskmon { diskmon_t *target; char *pfmri; } walk_diskmon_t; static int topo_add_disk(topo_hdl_t *thp, tnode_t *node, walk_diskmon_t *wdp) { diskmon_t *target_diskp = wdp->target; char *devpath = NULL; char *capacity = NULL; char *firmrev = NULL; char *serial = NULL; char *manuf = NULL; char *model = NULL; char *label; uint64_t ptr = 0; int err; dm_fru_t *frup; diskmon_t *diskp; if (wdp->pfmri == NULL) { log_msg(MM_TOPO, "No diskmon for parent of node %p.\n", node); return (0); } if (nvlist_lookup_uint64(g_topo2diskmon, wdp->pfmri, &ptr) != 0) { log_msg(MM_TOPO, "No diskmon for %s: parent of node %p.\n", wdp->pfmri, node); dstrfree(wdp->pfmri); /* Skip this disk: */ return (0); } dstrfree(wdp->pfmri); wdp->pfmri = NULL; diskp = (diskmon_t *)(uintptr_t)ptr; /* If we were called upon to update a particular disk, do it */ if (target_diskp != NULL && diskp != target_diskp) { return (0); } /* * Update the diskmon's location field with the disk's label */ if (diskp->location) dstrfree(diskp->location); if (topo_node_label(node, &label, &err) == 0) { diskp->location = dstrdup(label); topo_hdl_strfree(thp, label); } else diskp->location = dstrdup("unknown location"); /* * Check for a device path property (if the disk is configured, * it will be present) and add it to the diskmon's properties) */ if (topo_prop_get_string(node, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH, &devpath, &err) == 0) { char devp[PATH_MAX]; /* * Consumers of the DISK_PROP_DEVPATH property expect a raw * minor device node */ (void) snprintf(devp, PATH_MAX, "%s:q,raw", devpath); (void) nvlist_add_string(diskp->props, DISK_PROP_DEVPATH, devp); topo_hdl_strfree(thp, devpath); } /* * Add the logical disk node, if it exists */ if (topo_prop_get_string(node, TOPO_PGROUP_STORAGE, TOPO_STORAGE_LOGICAL_DISK_NAME, &devpath, &err) == 0) { (void) nvlist_add_string(diskp->props, DISK_PROP_LOGNAME, devpath); topo_hdl_strfree(thp, devpath); } /* * Add the FRU information (if present in the node) to the diskmon's * fru data structure: */ (void) topo_prop_get_string(node, TOPO_PGROUP_STORAGE, TOPO_STORAGE_MODEL, &model, &err); (void) topo_prop_get_string(node, TOPO_PGROUP_STORAGE, TOPO_STORAGE_MANUFACTURER, &manuf, &err); (void) topo_prop_get_string(node, TOPO_PGROUP_STORAGE, TOPO_STORAGE_SERIAL_NUM, &serial, &err); (void) topo_prop_get_string(node, TOPO_PGROUP_STORAGE, TOPO_STORAGE_FIRMWARE_REV, &firmrev, &err); (void) topo_prop_get_string(node, TOPO_PGROUP_STORAGE, TOPO_STORAGE_CAPACITY, &capacity, &err); frup = new_dmfru(manuf != NULL ? manuf : "", model != NULL ? model : "", firmrev != NULL ? firmrev : "", serial != NULL ? serial : "", capacity == NULL ? 0 : strtoull(capacity, 0, 0)); if (model) topo_hdl_strfree(thp, model); if (manuf) topo_hdl_strfree(thp, manuf); if (serial) topo_hdl_strfree(thp, serial); if (firmrev) topo_hdl_strfree(thp, firmrev); if (capacity) topo_hdl_strfree(thp, capacity); /* Add the fru information to the diskmon: */ dm_assert(pthread_mutex_lock(&diskp->fru_mutex) == 0); dm_assert(diskp->frup == NULL); diskp->frup = frup; dm_assert(pthread_mutex_unlock(&diskp->fru_mutex) == 0); return (0); } static int indicator_breakup(char *identifier, ind_state_t *state, char **name) { if (identifier[0] != '+' && identifier[0] != '-') { log_msg(MM_CONF, "Invalid indicator name `%s'\n", identifier); return (-1); } *state = (identifier[0] == '+') ? INDICATOR_ON : INDICATOR_OFF; *name = &identifier[1]; return (0); } static int topoprop_indicator_add(indicator_t **indp, char *ind_name, char *ind_action) { /* The Indicator name is of the form: "[+-][A-Za-z][A-Za-z0-9]+" */ indicator_t *newindp; ind_state_t state; char *name; if (indicator_breakup(ind_name, &state, &name) != 0) return (-1); newindp = new_indicator(state, name, ind_action); link_indicator(indp, newindp); return (0); } static hotplug_state_t str2dmstate(char *str) { if (strcasecmp("configured", str) == 0) { return (HPS_CONFIGURED); } else if (strcasecmp("unconfigured", str) == 0) { return (HPS_UNCONFIGURED); } else if (strcasecmp("absent", str) == 0) { return (HPS_ABSENT); } else if (strcasecmp("present", str) == 0) { return (HPS_PRESENT); } else return (HPS_UNKNOWN); } static int topoprop_indrule_add(indrule_t **indrp, char *sts, char *acts) { ind_action_t *indactp = NULL; ind_state_t state; char *name, *lasts, *p; int stateslen = strlen(sts) + 1; int actionslen = strlen(acts) + 1; char *states = dstrdup(sts); char *actions = dstrdup(acts); state_transition_t strans; boolean_t failed = B_FALSE; conf_err_t err; char msgbuf[MAX_CONF_MSG_LEN]; /* The state string is of the form "{STATE}>{STATE}" */ p = strchr(states, '>'); dm_assert(p != NULL); *p = 0; strans.begin = str2dmstate(states); *p = '>'; strans.end = str2dmstate(p + 1); if (strans.begin == HPS_UNKNOWN || strans.end == HPS_UNKNOWN) { log_msg(MM_CONF, "Invalid states property `%s'\n", sts); failed = B_TRUE; } else if ((err = check_state_transition(strans.begin, strans.end)) != E_NO_ERROR) { conf_error_msg(err, msgbuf, MAX_CONF_MSG_LEN, &strans); log_msg(MM_CONF, "%s: Not adding disk to list!\n", msgbuf); failed = B_TRUE; } /* Actions are of the form "{ACTION}[&{ACTION}]" */ if (!failed && (p = strtok_r(actions, "&", &lasts)) != NULL) { /* At least 2 tokens */ do { if (indicator_breakup(p, &state, &name) != 0) { failed = B_TRUE; break; } link_indaction(&indactp, new_indaction(state, name)); } while ((p = strtok_r(NULL, "&", &lasts)) != NULL); } else if (!failed) { /* One token */ if (indicator_breakup(actions, &state, &name) != 0) return (-1); indactp = new_indaction(state, name); } dfree(states, stateslen); dfree(actions, actionslen); if (!failed && (err = check_indactions(indactp)) != E_NO_ERROR) { conf_error_msg(err, msgbuf, MAX_CONF_MSG_LEN, NULL); log_msg(MM_CONF, "%s: Not adding disk to list!\n", msgbuf); failed = B_TRUE; } if (failed) { indaction_free(indactp); return (-1); } else link_indrule(indrp, new_indrule(&strans, indactp)); return (0); } static int topo_add_bay(topo_hdl_t *thp, tnode_t *node, walk_diskmon_t *wdp) { diskmon_t *target_diskp = wdp->target; nvlist_t *nvlp = find_disk_monitor_private_pgroup(node); nvlist_t *prop_nvlp; nvpair_t *nvp = NULL; char *prop_name, *prop_value; #define PNAME_MAX 128 char pname[PNAME_MAX]; char msgbuf[MAX_CONF_MSG_LEN]; char *indicator_name, *indicator_action; char *indrule_states, *indrule_actions; int err = 0, i; conf_err_t conferr; boolean_t conf_failure = B_FALSE; char *unadj_physid = NULL; char physid[MAXPATHLEN]; char *label; nvlist_t *diskprops = NULL; char *cstr = NULL; indicator_t *indp = NULL; indrule_t *indrp = NULL; void *p; diskmon_t *diskp; void *ptr; /* No private properties -- just ignore the port */ if (nvlp == NULL) return (0); /* * Look for a diskmon based on this node's FMRI string. * Once a diskmon has been created, it's not re-created. This is * essential for the times when the tree-walk is called after a * disk is inserted (or removed) -- in that case, the disk node * handler simply updates the FRU information in the diskmon. */ if ((p = fmri2ptr(thp, node, &cstr, &err)) != NULL) { diskp = (diskmon_t *)p; /* * Delete the FRU information from the diskmon. If a disk * is connected, its FRU information will be refreshed by * the disk node code. */ if (diskp->frup && (target_diskp == NULL || diskp == target_diskp)) { dm_assert(pthread_mutex_lock(&diskp->fru_mutex) == 0); dmfru_free(diskp->frup); diskp->frup = NULL; dm_assert(pthread_mutex_unlock(&diskp->fru_mutex) == 0); } wdp->pfmri = cstr; nvlist_free(nvlp); return (0); } /* * Determine the physical path to the attachment point */ if (topo_prop_get_string(node, TOPO_PGROUP_IO, TOPO_IO_AP_PATH, &unadj_physid, &err) == 0) { adjust_dynamic_ap(unadj_physid, physid); topo_hdl_strfree(thp, unadj_physid); } else { /* unadj_physid cannot have been allocated */ if (cstr) dstrfree(cstr); nvlist_free(nvlp); return (-1); } /* */ /* * Process the properties. If we encounter a property that * is not an indicator name, action, or rule, add it to the * disk's props list. */ /* Process indicators */ i = 0; indicator_name = NULL; indicator_action = NULL; do { if (indicator_name != NULL && indicator_action != NULL) { if (topoprop_indicator_add(&indp, indicator_name, indicator_action) != 0) { conf_failure = B_TRUE; } topo_hdl_strfree(thp, indicator_name); topo_hdl_strfree(thp, indicator_action); } (void) snprintf(pname, PNAME_MAX, BAY_IND_NAME "-%d", i); if (topo_prop_get_string(node, DISK_MONITOR_PROPERTIES, pname, &indicator_name, &err) != 0) break; (void) snprintf(pname, PNAME_MAX, BAY_IND_ACTION "-%d", i); if (topo_prop_get_string(node, DISK_MONITOR_PROPERTIES, pname, &indicator_action, &err) != 0) break; i++; } while (!conf_failure && indicator_name != NULL && indicator_action != NULL); if (!conf_failure && indp != NULL && (conferr = check_inds(indp)) != E_NO_ERROR) { conf_error_msg(conferr, msgbuf, MAX_CONF_MSG_LEN, NULL); log_msg(MM_CONF, "%s: Not adding disk to list\n", msgbuf); conf_failure = B_TRUE; } /* Process state rules and indicator actions */ i = 0; indrule_states = NULL; indrule_actions = NULL; do { if (indrule_states != NULL && indrule_actions != NULL) { if (topoprop_indrule_add(&indrp, indrule_states, indrule_actions) != 0) { conf_failure = B_TRUE; } topo_hdl_strfree(thp, indrule_states); topo_hdl_strfree(thp, indrule_actions); } (void) snprintf(pname, PNAME_MAX, BAY_INDRULE_STATES "-%d", i); if (topo_prop_get_string(node, DISK_MONITOR_PROPERTIES, pname, &indrule_states, &err) != 0) break; (void) snprintf(pname, PNAME_MAX, BAY_INDRULE_ACTIONS "-%d", i); if (topo_prop_get_string(node, DISK_MONITOR_PROPERTIES, pname, &indrule_actions, &err) != 0) break; i++; } while (!conf_failure && indrule_states != NULL && indrule_actions != NULL); if (!conf_failure && indrp != NULL && indp != NULL && ((conferr = check_indrules(indrp, (state_transition_t **)&ptr)) != E_NO_ERROR || (conferr = check_consistent_ind_indrules(indp, indrp, (ind_action_t **)&ptr)) != E_NO_ERROR)) { conf_error_msg(conferr, msgbuf, MAX_CONF_MSG_LEN, ptr); log_msg(MM_CONF, "%s: Not adding disk to list\n", msgbuf); conf_failure = B_TRUE; } /* * Now collect miscellaneous properties. * Each property is stored as an embedded nvlist named * TOPO_PROP_VAL. The property name is stored in the value for * key=TOPO_PROP_VAL_NAME and the property's value is * stored in the value for key=TOPO_PROP_VAL_VAL. This is all * necessary so we can subtractively decode the properties that * we do not directly handle (so that these properties are added to * the per-disk properties nvlist), increasing flexibility. */ (void) nvlist_alloc(&diskprops, NV_UNIQUE_NAME, 0); while ((nvp = nvlist_next_nvpair(nvlp, nvp)) != NULL) { /* Only care about embedded nvlists named TOPO_PROP_VAL */ if (nvpair_type(nvp) != DATA_TYPE_NVLIST || strcmp(nvpair_name(nvp), TOPO_PROP_VAL) != 0 || nvpair_value_nvlist(nvp, &prop_nvlp) != 0) continue; if (nonunique_nvlist_lookup_string(prop_nvlp, TOPO_PROP_VAL_NAME, &prop_name) != 0) continue; /* Filter out indicator properties */ if (strstr(prop_name, BAY_IND_NAME) != NULL || strstr(prop_name, BAY_IND_ACTION) != NULL || strstr(prop_name, BAY_INDRULE_STATES) != NULL || strstr(prop_name, BAY_INDRULE_ACTIONS) != NULL) continue; if (nonunique_nvlist_lookup_string(prop_nvlp, TOPO_PROP_VAL_VAL, &prop_value) != 0) continue; /* Add the property to the disk's prop list: */ if (nvlist_add_string(diskprops, prop_name, prop_value) != 0) log_msg(MM_TOPO, "Could not add disk property `%s' with " "value `%s'\n", prop_name, prop_value); } nvlist_free(nvlp); if (cstr != NULL) { namevalpr_t nvpr; nvlist_t *dmap_nvl; nvpr.name = DISK_AP_PROP_APID; nvpr.value = strncmp(physid, "/devices", 8) == 0 ? (physid + 8) : physid; /* * Set the diskmon's location to the value in this port's label. * If there's a disk plugged in, the location will be updated * to be the disk label (e.g. HD_ID_00). Until a disk is * inserted, though, there won't be a disk libtopo node * created. */ /* Pass physid without the leading "/devices": */ dmap_nvl = namevalpr_to_nvlist(&nvpr); diskp = new_diskmon(dmap_nvl, indp, indrp, diskprops); if (topo_node_label(node, &label, &err) == 0) { diskp->location = dstrdup(label); topo_hdl_strfree(thp, label); } else diskp->location = dstrdup("unknown location"); if (!conf_failure && diskp != NULL) { /* Add this diskmon to the disk list */ cfgdata_add_diskmon(config_data, diskp); if (nvlist_add_uint64(g_topo2diskmon, cstr, (uint64_t)(uintptr_t)diskp) != 0) { log_msg(MM_TOPO, "Could not add pointer to nvlist " "for `%s'!\n", cstr); } } else if (diskp != NULL) { diskmon_free(diskp); } else { nvlist_free(dmap_nvl); if (indp) ind_free(indp); if (indrp) indrule_free(indrp); nvlist_free(diskprops); } wdp->pfmri = cstr; } return (0); } /*ARGSUSED*/ static int gather_topo_cfg(topo_hdl_t *thp, tnode_t *node, void *arg) { char *nodename = topo_node_name(node); if (strcmp(DISK, nodename) == 0) return (topo_add_disk(thp, node, (walk_diskmon_t *)arg) ? TOPO_WALK_ERR : TOPO_WALK_NEXT); else if (strcmp(BAY, nodename) == 0) return (topo_add_bay(thp, node, (walk_diskmon_t *)arg) ? TOPO_WALK_ERR : TOPO_WALK_NEXT); return (TOPO_WALK_NEXT); } /*ARGSUSED*/ int update_configuration_from_topo(fmd_hdl_t *hdl, diskmon_t *diskp) { int err; topo_hdl_t *thp; topo_walk_t *twp; walk_diskmon_t wd; if ((thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION)) == NULL) { return (TOPO_OPEN_ERROR); } wd.target = diskp; wd.pfmri = NULL; if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, gather_topo_cfg, &wd, &err)) == NULL) { fmd_hdl_topo_rele(hdl, thp); return (err ? TOPO_WALK_INIT_ERROR : TOPO_SUCCESS); } if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) { topo_walk_fini(twp); if (wd.pfmri != NULL) dstrfree(wd.pfmri); fmd_hdl_topo_rele(hdl, thp); return (TOPO_WALK_ERROR); } topo_walk_fini(twp); fmd_hdl_topo_rele(hdl, thp); if (wd.pfmri != NULL) dstrfree(wd.pfmri); return (TOPO_SUCCESS); } int init_configuration_from_topo(void) { return (nvlist_alloc(&g_topo2diskmon, NV_UNIQUE_NAME, 0)); } void fini_configuration_from_topo(void) { if (g_topo2diskmon) { nvlist_free(g_topo2diskmon); } } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _TOPO_GATHER_H #define _TOPO_GATHER_H /* * bay/disk libtopo plugin property consumer * (creates diskmon configuration objects) */ #ifdef __cplusplus extern "C" { #endif /* NOTE: some aspects of this code are still x4500 specific */ #define DISK_MONITOR_PROPERTIES "sfx4500-properties" /* Properties added to the machine-specific properties pgroup */ #define BAY_IND_NAME "indicator-name" #define BAY_IND_ACTION "indicator-action" #define BAY_INDRULE_STATES "indicator-rule-states" #define BAY_INDRULE_ACTIONS "indicator-rule-actions" #define TOPO_SUCCESS 0 #define TOPO_WALK_ERROR 1 #define TOPO_WALK_INIT_ERROR 2 #define TOPO_SNAP_ERROR 3 #define TOPO_OPEN_ERROR 4 int update_configuration_from_topo(fmd_hdl_t *, diskmon_t *diskp); int init_configuration_from_topo(void); void fini_configuration_from_topo(void); diskmon_t *dm_fmri_to_diskmon(fmd_hdl_t *hdl, nvlist_t *fmri); #ifdef __cplusplus } #endif #endif /* _TOPO_GATHER_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "disk_monitor.h" extern log_class_t g_verbose; static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; static void verror(const char *fmt, va_list ap) { int error = errno; dm_assert(pthread_mutex_lock(&log_mutex) == 0); fmd_hdl_vdebug(g_fm_hdl, fmt, ap); if (fmt[strlen(fmt) - 1] != '\n') fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error)); dm_assert(pthread_mutex_unlock(&log_mutex) == 0); } static void vwarn_e(const char *fmt, va_list ap) { int error = errno; dm_assert(pthread_mutex_lock(&log_mutex) == 0); fmd_hdl_debug(g_fm_hdl, "WARNING: "); fmd_hdl_vdebug(g_fm_hdl, fmt, ap); if (fmt[strlen(fmt) - 1] != '\n') fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error)); dm_assert(pthread_mutex_unlock(&log_mutex) == 0); } static void vwarn(const char *fmt, va_list ap) { dm_assert(pthread_mutex_lock(&log_mutex) == 0); fmd_hdl_debug(g_fm_hdl, "WARNING: "); fmd_hdl_vdebug(g_fm_hdl, fmt, ap); dm_assert(pthread_mutex_unlock(&log_mutex) == 0); } void vcont(log_class_t cl, const char *fmt, va_list ap) { int error = errno; if ((g_verbose & cl) != cl) return; dm_assert(pthread_mutex_lock(&log_mutex) == 0); fmd_hdl_vdebug(g_fm_hdl, fmt, ap); if (fmt[strlen(fmt) - 1] != '\n') fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error)); dm_assert(pthread_mutex_unlock(&log_mutex) == 0); } void log_msg(log_class_t cl, const char *fmt, ...) { va_list ap; if ((g_verbose & cl) != cl) return; dm_assert(pthread_mutex_lock(&log_mutex) == 0); va_start(ap, fmt); fmd_hdl_vdebug(g_fm_hdl, fmt, ap); va_end(ap); dm_assert(pthread_mutex_unlock(&log_mutex) == 0); } /*PRINTFLIKE1*/ void log_err(const char *fmt, ...) { va_list ap; if ((g_verbose & MM_ERR) != MM_ERR) return; va_start(ap, fmt); verror(fmt, ap); va_end(ap); } /*PRINTFLIKE1*/ void log_warn(const char *fmt, ...) { va_list ap; if ((g_verbose & MM_WARN) != MM_WARN) return; va_start(ap, fmt); vwarn(fmt, ap); va_end(ap); } /*PRINTFLIKE1*/ void log_warn_e(const char *fmt, ...) { va_list ap; if ((g_verbose & MM_WARN) != MM_WARN) return; va_start(ap, fmt); vwarn_e(fmt, ap); va_end(ap); } void dfree(void *p, size_t sz) { fmd_hdl_free(g_fm_hdl, p, sz); } void dstrfree(char *s) { fmd_hdl_strfree(g_fm_hdl, s); } void * dmalloc(size_t sz) { return (fmd_hdl_alloc(g_fm_hdl, sz, FMD_SLEEP)); } void * dzmalloc(size_t sz) { return (fmd_hdl_zalloc(g_fm_hdl, sz, FMD_SLEEP)); } char * dstrdup(const char *s) { return (fmd_hdl_strdup(g_fm_hdl, s, FMD_SLEEP)); } void queue_add(qu_t *qp, void *data) { struct q_node *qnp = (struct q_node *)qp->nalloc(sizeof (struct q_node)); struct q_node *nodep; qnp->data = data; qnp->next = NULL; dm_assert(pthread_mutex_lock(&qp->mutex) == 0); if (qp->nodep == NULL) qp->nodep = qnp; else { nodep = qp->nodep; while (nodep->next != NULL) nodep = nodep->next; nodep->next = qnp; } /* If the queue was empty, we need to wake people up */ if (qp->boe && qp->nodep == qnp) dm_assert(pthread_cond_broadcast(&qp->cvar) == 0); dm_assert(pthread_mutex_unlock(&qp->mutex) == 0); } void * queue_remove(qu_t *qp) { void *rv = NULL; struct q_node *nextnode; dm_assert(pthread_mutex_lock(&qp->mutex) == 0); /* Wait while the queue is empty */ while (qp->boe && qp->nodep == NULL) { (void) pthread_cond_wait(&qp->cvar, &qp->mutex); } /* * If Block-On-Empty is false, the queue may be empty */ if (qp->nodep != NULL) { rv = qp->nodep->data; nextnode = qp->nodep->next; qp->nfree(qp->nodep, sizeof (struct q_node)); qp->nodep = nextnode; } dm_assert(pthread_mutex_unlock(&qp->mutex) == 0); return (rv); } qu_t * new_queue(boolean_t block_on_empty, void *(*nodealloc)(size_t), void (*nodefree)(void *, size_t), void (*data_deallocator)(void *)) { qu_t *newqp = (qu_t *)dmalloc(sizeof (qu_t)); newqp->boe = block_on_empty; newqp->nalloc = nodealloc; newqp->nfree = nodefree; newqp->data_dealloc = data_deallocator; dm_assert(pthread_mutex_init(&newqp->mutex, NULL) == 0); dm_assert(pthread_cond_init(&newqp->cvar, NULL) == 0); newqp->nodep = NULL; return (newqp); } void queue_free(qu_t **qpp) { qu_t *qp = *qpp; void *item; dm_assert(pthread_mutex_destroy(&qp->mutex) == 0); dm_assert(pthread_cond_destroy(&qp->cvar) == 0); qp->boe = B_FALSE; while ((item = queue_remove(qp)) != NULL) { qp->data_dealloc(item); } dm_assert(qp->nodep == NULL); dfree(qp, sizeof (qu_t)); *qpp = NULL; } int _dm_assert(const char *assertion, const char *file, int line, const char *func) { /* * No newline is appended to the assertion message so that * errno can be translated for us by fmd_hdl_abort(). */ if (func) fmd_hdl_abort(g_fm_hdl, "Assertion failed: " "%s, file: %s, line: %d, function: %s", assertion, file, line, func); else fmd_hdl_abort(g_fm_hdl, "Assertion failed: " "%s, file: %s, line: %d", assertion, file, line); /*NOTREACHED*/ return (0); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _UTIL_H #define _UTIL_H /* * Utility functions and macros */ #ifdef __cplusplus extern "C" { #endif #include #include #include extern int _dm_assert(const char *assertion, const char *file, int line, const char *func); #if defined(__STDC__) #if __STDC_VERSION__ - 0 >= 199901L #define dm_assert(EX) (void)((EX) ? 0 : \ _dm_assert(#EX, __FILE__, __LINE__, __func__)) #else #define dm_assert(EX) (void)((EX) ? 0 : \ _dm_assert(#EX, __FILE__, __LINE__, NULL)) #endif /* __STDC_VERSION__ - 0 >= 199901L */ #else #define dm_assert(EX) (void)((EX) ? 0 : \ _dm_assert("EX", __FILE__, __LINE__, NULL)) #endif /* __STDC__ */ /* * The following structures comprise the implementation of the * queue structure that's used to construct the list of state * changes. Removals from the queue are blocking operations that * cause the thread to wait until new entries are added. */ struct q_node { void *data; struct q_node *next; }; typedef struct q_head { /* * Block On Empty (when queue is empty, the calling thread will be * blocked until something is added) */ boolean_t boe; pthread_mutex_t mutex; pthread_cond_t cvar; void *(*nalloc)(size_t); void (*nfree)(void *, size_t); void (*data_dealloc)(void *); struct q_node *nodep; } qu_t; typedef enum log_class { MM_CONF = 0x0001, MM_HPMGR = 0x0004, MM_SCHGMGR = 0x0008, MM_MAIN = 0x0040, MM_TOPO = 0x0100, MM_ERR = 0x0200, MM_WARN = 0x0400, MM_NOTE = 0x0800, MM_OTHER = 0x1000 } log_class_t; extern void queue_add(qu_t *qp, void *data); extern void *queue_remove(qu_t *qp); extern qu_t *new_queue(boolean_t block_on_empty, void *(*nodealloc)(size_t), void (*nodefree)(void *, size_t), void (*deallocator)(void *)); extern void queue_free(qu_t **qp); extern void *dmalloc(size_t sz); extern void *dzmalloc(size_t sz); extern char *dstrdup(const char *s); extern void dfree(void *p, size_t sz); extern void dstrfree(char *s); extern void log_msg(log_class_t cl, const char *fmt, ...); extern void log_err(const char *fmt, ...); extern void log_warn(const char *fmt, ...); extern void log_warn_e(const char *fmt, ...); extern void vcont(log_class_t cl, const char *fmt, va_list val); #ifdef __cplusplus } #endif #endif /* _UTIL_H */ # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" MODULE = disk-transport CLASS = common SRCS = disk_transport.c include ../../Makefile.plugin CFLAGS += $(INCS) LINTFLAGS += $(INCS) LDLIBS += -L$(ROOTLIB)/fm -ldiskstatus -ltopo LDFLAGS += -R/usr/lib/fm # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # Copyright 2016 Nexenta Systems, Inc. All rights reserved. # # # To disable transport of "high solid state media used %" faults uncomment: # setprop ignore-ssm-wearout true /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. */ /* * Disk error transport module * * This transport module is responsible for translating between disk errors * and FMA ereports. It is a read-only transport module, and checks for the * following failures: * * - overtemp * - predictive failure * - self-test failure * - solid state media wearout * * These failures are detected via the TOPO_METH_DISK_STATUS method, which * leverages libdiskstatus to do the actual analysis. This transport module is * in charge of the following tasks: * * - discovering available devices * - periodically checking devices * - managing device addition/removal */ #include #include #include #include #include #include #include #include #include #include static struct dt_stat { fmd_stat_t dropped; } dt_stats = { { "dropped", FMD_TYPE_UINT64, "number of dropped ereports" } }; typedef struct disk_monitor { fmd_hdl_t *dm_hdl; fmd_xprt_t *dm_xprt; id_t dm_timer; hrtime_t dm_interval; char *dm_sim_search; char *dm_sim_file; boolean_t dm_timer_istopo; } disk_monitor_t; static void dt_post_ereport(fmd_hdl_t *hdl, fmd_xprt_t *xprt, const char *protocol, const char *faultname, uint64_t ena, nvlist_t *detector, nvlist_t *payload) { nvlist_t *nvl; int e = 0; char fullclass[PATH_MAX]; (void) snprintf(fullclass, sizeof (fullclass), "%s.io.%s.disk.%s", FM_EREPORT_CLASS, protocol, faultname); if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) == 0) { e |= nvlist_add_string(nvl, FM_CLASS, fullclass); e |= nvlist_add_uint8(nvl, FM_VERSION, FM_EREPORT_VERSION); e |= nvlist_add_uint64(nvl, FM_EREPORT_ENA, ena); e |= nvlist_add_nvlist(nvl, FM_EREPORT_DETECTOR, detector); e |= nvlist_merge(nvl, payload, 0); if (e == 0) { fmd_xprt_post(hdl, xprt, nvl, 0); } else { nvlist_free(nvl); dt_stats.dropped.fmds_value.ui64++; } } else { dt_stats.dropped.fmds_value.ui64++; } } /* * Check a single topo node for failure. This simply invokes the disk status * method, and generates any ereports as necessary. */ static int dt_analyze_disk(topo_hdl_t *thp, tnode_t *node, void *arg) { nvlist_t *result; nvlist_t *fmri, *faults; char *protocol; int err; disk_monitor_t *dmp = arg; nvpair_t *elem; boolean_t fault; nvlist_t *details; char *fmristr; nvlist_t *in = NULL; if (topo_node_resource(node, &fmri, &err) != 0) { fmd_hdl_error(dmp->dm_hdl, "failed to get fmri: %s\n", topo_strerror(err)); return (TOPO_WALK_ERR); } if (topo_hdl_nvalloc(thp, &in, NV_UNIQUE_NAME) != 0) { nvlist_free(fmri); return (TOPO_WALK_ERR); } if (dmp->dm_sim_search) { fmristr = NULL; if (topo_fmri_nvl2str(thp, fmri, &fmristr, &err) == 0 && strstr(fmristr, dmp->dm_sim_search) != 0) (void) nvlist_add_string(in, "path", dmp->dm_sim_file); topo_hdl_strfree(thp, fmristr); } /* * Try to invoke the method. If this fails (most likely because the * method is not supported), then ignore this node. */ if (topo_method_invoke(node, TOPO_METH_DISK_STATUS, TOPO_METH_DISK_STATUS_VERSION, in, &result, &err) != 0) { nvlist_free(fmri); nvlist_free(in); return (TOPO_WALK_NEXT); } nvlist_free(in); /* * Check for faults and post ereport(s) if needed */ if (nvlist_lookup_nvlist(result, "faults", &faults) == 0 && nvlist_lookup_string(result, "protocol", &protocol) == 0) { elem = NULL; while ((elem = nvlist_next_nvpair(faults, elem)) != NULL) { if (nvpair_type(elem) != DATA_TYPE_BOOLEAN_VALUE) continue; (void) nvpair_value_boolean_value(elem, &fault); if (!fault || nvlist_lookup_nvlist(result, nvpair_name(elem), &details) != 0) continue; if (strcmp(nvpair_name(elem), FM_EREPORT_SCSI_SSMWEAROUT) == 0 && fmd_prop_get_int32(dmp->dm_hdl, "ignore-ssm-wearout") == FMD_B_TRUE) continue; dt_post_ereport(dmp->dm_hdl, dmp->dm_xprt, protocol, nvpair_name(elem), fmd_event_ena_create(dmp->dm_hdl), fmri, details); } } nvlist_free(result); nvlist_free(fmri); return (TOPO_WALK_NEXT); } /* * Periodic timeout. Iterates over all hc:// topo nodes, calling * dt_analyze_disk() for each one. */ /*ARGSUSED*/ static void dt_timeout(fmd_hdl_t *hdl, id_t id, void *data) { topo_hdl_t *thp; topo_walk_t *twp; int err; disk_monitor_t *dmp = fmd_hdl_getspecific(hdl); dmp->dm_hdl = hdl; thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, dt_analyze_disk, dmp, &err)) == NULL) { fmd_hdl_topo_rele(hdl, thp); fmd_hdl_error(hdl, "failed to get topology: %s\n", topo_strerror(err)); return; } if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) { topo_walk_fini(twp); fmd_hdl_topo_rele(hdl, thp); fmd_hdl_error(hdl, "failed to walk topology\n"); return; } topo_walk_fini(twp); fmd_hdl_topo_rele(hdl, thp); dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL, dmp->dm_interval); dmp->dm_timer_istopo = B_FALSE; } /* * Called when the topology may have changed. We want to examine all disks in * case a new one has been inserted, but we don't want to overwhelm the system * in the event of a flurry of topology changes, as most likely only a small * number of disks are changing. To avoid this, we set the timer for a small * but non-trivial interval (by default 1 minute), and ignore intervening * changes during this period. This still gives us a reasonable response time * to newly inserted devices without overwhelming the system if lots of hotplug * activity is going on. */ /*ARGSUSED*/ static void dt_topo_change(fmd_hdl_t *hdl, topo_hdl_t *thp) { disk_monitor_t *dmp = fmd_hdl_getspecific(hdl); if (dmp->dm_timer_istopo) return; fmd_timer_remove(hdl, dmp->dm_timer); dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL, fmd_prop_get_int64(hdl, "min-interval")); dmp->dm_timer_istopo = B_TRUE; } static const fmd_prop_t fmd_props[] = { { "interval", FMD_TYPE_TIME, "1h" }, { "min-interval", FMD_TYPE_TIME, "1min" }, { "simulate", FMD_TYPE_STRING, "" }, { "ignore-ssm-wearout", FMD_TYPE_BOOL, "false"}, { NULL, 0, NULL } }; static const fmd_hdl_ops_t fmd_ops = { NULL, /* fmdo_recv */ dt_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ dt_topo_change, /* fmdo_topo_change */ }; static const fmd_hdl_info_t fmd_info = { "Disk Transport Agent", "1.1", &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { disk_monitor_t *dmp; char *simulate; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (dt_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&dt_stats); dmp = fmd_hdl_zalloc(hdl, sizeof (disk_monitor_t), FMD_SLEEP); fmd_hdl_setspecific(hdl, dmp); dmp->dm_xprt = fmd_xprt_open(hdl, FMD_XPRT_RDONLY, NULL, NULL); dmp->dm_interval = fmd_prop_get_int64(hdl, "interval"); /* * Determine if we have the simulate property set. This property allows * the developer to substitute a faulty device based off all or part of * an FMRI string. For example, one could do: * * setprop simulate "bay=4/disk=4 /path/to/sim.so" * * When the transport module encounters an FMRI containing the given * string, then it will open the simulator file instead of the * corresponding device. This can be any file, but is intended to be a * libdiskstatus simulator shared object, capable of faking up SCSI * responses. * * The property consists of two strings, an FMRI fragment and an * absolute path, separated by whitespace. */ simulate = fmd_prop_get_string(hdl, "simulate"); if (simulate[0] != '\0') { const char *sep; size_t len; for (sep = simulate; *sep != '\0'; sep++) { if (isspace(*sep)) break; } if (*sep != '\0') { len = sep - simulate; dmp->dm_sim_search = fmd_hdl_alloc(hdl, len + 1, FMD_SLEEP); (void) memcpy(dmp->dm_sim_search, simulate, len); dmp->dm_sim_search[len] = '\0'; } for (; *sep != '\0'; sep++) { if (!isspace(*sep)) break; } if (*sep != '\0') { dmp->dm_sim_file = fmd_hdl_strdup(hdl, sep, FMD_SLEEP); } else if (dmp->dm_sim_search) { fmd_hdl_strfree(hdl, dmp->dm_sim_search); dmp->dm_sim_search = NULL; } } fmd_prop_free_string(hdl, simulate); /* * Call our initial timer routine. This will do an initial check of all * the disks, and then start the periodic timeout. */ dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL, 0); } void _fmd_fini(fmd_hdl_t *hdl) { disk_monitor_t *dmp; dmp = fmd_hdl_getspecific(hdl); if (dmp) { fmd_xprt_close(hdl, dmp->dm_xprt); fmd_hdl_strfree(hdl, dmp->dm_sim_search); fmd_hdl_strfree(hdl, dmp->dm_sim_file); fmd_hdl_free(hdl, dmp, sizeof (*dmp)); } } # # 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 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" MODULE = event-transport CLASS = plat ETMDIR = ../../common/$(MODULE) SRCS = \ etm.c \ $($(PLATFORMS)_SRCS) include ../../Makefile.plugin INCDIRS += \ . \ $(ETMDIR) CPPFLAGS += $(INCDIRS:%=-I%) LDFLAGS += $(LIBFLAGS:%=-R%) LDLIBS += $(LIBDIRS:%=-L%) $(XPORTLIBS) %.o: $(ETMDIR)/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) %.ln: $(ETMDIR)/%.c $(LINT.c) -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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * FMA Event Transport Module * * Plugin for sending/receiving FMA events to/from a remote endoint. */ #include #include #include #include #include #include #include #include #include #include "etm_xport_api.h" #include "etm_proto.h" /* * ETM declarations */ typedef enum etm_connection_status { C_UNINITIALIZED = 0, C_OPEN, /* Connection is open */ C_CLOSED, /* Connection is closed */ C_LIMBO, /* Bad value in header from peer */ C_TIMED_OUT /* Reconnection to peer timed out */ } etm_connstat_t; typedef enum etm_fmd_queue_status { Q_UNINITIALIZED = 100, Q_INIT_PENDING, /* Queue initialization in progress */ Q_OPEN, /* Queue is open */ Q_SUSPENDED /* Queue is suspended */ } etm_qstat_t; /* Per endpoint data */ typedef struct etm_endpoint_map { uint8_t epm_ver; /* Protocol version being used */ char *epm_ep_str; /* Endpoint ID string */ int epm_xprtflags; /* FMD transport open flags */ etm_xport_hdl_t epm_tlhdl; /* Transport Layer instance handle */ pthread_mutex_t epm_lock; /* Protects remainder of struct */ pthread_cond_t epm_tx_cv; /* Cond var for send/transmit */ int epm_txbusy; /* Busy doing send/transmit */ fmd_xprt_t *epm_xprthdl; /* FMD transport handle */ etm_qstat_t epm_qstat; /* Status of fmd xprt queue */ nvlist_t *epm_ep_nvl; /* Endpoint ID nv_list */ etm_xport_conn_t epm_oconn; /* Connection for outgoing events */ etm_connstat_t epm_cstat; /* Status of connection */ id_t epm_timer_id; /* Timer id */ int epm_timer_in_use; /* Indicates if timer is in use */ hrtime_t epm_reconn_end; /* Reconnection end time */ struct etm_endpoint_map *epm_next; } etm_epmap_t; #define ETM_HDR_INVALID (ETM_HDR_TYPE_TOO_HIGH + 1) #define ETM_HDR_BADVERSION (ETM_HDR_TYPE_TOO_HIGH + 2) #define ETM_HDR_BADTYPE (ETM_HDR_TYPE_TOO_HIGH + 3) #define ETM_EP_INST_MAX 4 /* Max chars in endpt instance */ #define ETM_CLIENT_XPRT_FLAGS FMD_XPRT_RDWR #define ETM_SERVER_XPRT_FLAGS (FMD_XPRT_RDWR | FMD_XPRT_ACCEPT) #define ALLOC_BUF(hdl, buf, size) \ buf = fmd_hdl_zalloc((hdl), (size), FMD_SLEEP); #define FREE_BUF(hdl, buf, size) fmd_hdl_free((hdl), (buf), (size)); #define IS_CLIENT(mp) (((mp)->epm_xprtflags & FMD_XPRT_ACCEPT) ? 0 : 1) #define INCRSTAT(x) { (void) pthread_mutex_lock(&Etm_mod_lock); \ (x)++; \ (void) pthread_mutex_unlock(&Etm_mod_lock); \ } #define DECRSTAT(x) { (void) pthread_mutex_lock(&Etm_mod_lock); \ (x)--; \ (void) pthread_mutex_unlock(&Etm_mod_lock); \ } #define ADDSTAT(x, y) { (void) pthread_mutex_lock(&Etm_mod_lock); \ (x) += (y); \ (void) pthread_mutex_unlock(&Etm_mod_lock); \ } /* * Global variables */ static pthread_mutex_t Etm_mod_lock = PTHREAD_MUTEX_INITIALIZER; /* Protects globals */ static hrtime_t Reconn_interval; /* Time between reconnection attempts */ static hrtime_t Reconn_timeout; /* Time allowed for reconnection */ static hrtime_t Rw_timeout; /* Time allowed for I/O operation */ static int Etm_dump = 0; /* Enables hex dump for debug */ static int Etm_exit = 0; /* Flag for exit */ static etm_epmap_t *Epmap_head = NULL; /* Head of list of epmap structs */ /* Module statistics */ static struct etm_stats { /* read counters */ fmd_stat_t read_ack; fmd_stat_t read_bytes; fmd_stat_t read_msg; fmd_stat_t post_filter; /* write counters */ fmd_stat_t write_ack; fmd_stat_t write_bytes; fmd_stat_t write_msg; fmd_stat_t send_filter; /* error counters */ fmd_stat_t error_protocol; fmd_stat_t error_drop_read; fmd_stat_t error_read; fmd_stat_t error_read_badhdr; fmd_stat_t error_write; fmd_stat_t error_send_filter; fmd_stat_t error_post_filter; /* misc */ fmd_stat_t peer_count; } Etm_stats = { /* read counters */ { "read_ack", FMD_TYPE_UINT64, "ACKs read" }, { "read_bytes", FMD_TYPE_UINT64, "Bytes read" }, { "read_msg", FMD_TYPE_UINT64, "Messages read" }, { "post_filter", FMD_TYPE_UINT64, "Drops by post_filter" }, /* write counters */ { "write_ack", FMD_TYPE_UINT64, "ACKs sent" }, { "write_bytes", FMD_TYPE_UINT64, "Bytes sent" }, { "write_msg", FMD_TYPE_UINT64, "Messages sent" }, { "send_filter", FMD_TYPE_UINT64, "Drops by send_filter" }, /* ETM error counters */ { "error_protocol", FMD_TYPE_UINT64, "ETM protocol errors" }, { "error_drop_read", FMD_TYPE_UINT64, "Dropped read messages" }, { "error_read", FMD_TYPE_UINT64, "Read I/O errors" }, { "error_read_badhdr", FMD_TYPE_UINT64, "Bad headers read" }, { "error_write", FMD_TYPE_UINT64, "Write I/O errors" }, { "error_send_filter", FMD_TYPE_UINT64, "Send filter errors" }, { "error_post_filter", FMD_TYPE_UINT64, "Post filter errors" }, /* ETM Misc */ { "peer_count", FMD_TYPE_UINT64, "Number of peers initialized" }, }; /* * ETM Private functions */ /* * Hex dump for debug. */ static void etm_hex_dump(fmd_hdl_t *hdl, void *buf, size_t buflen, int direction) { int i, j, k; int16_t *c; if (Etm_dump == 0) return; j = buflen / 16; /* Number of complete 8-column rows */ k = buflen % 16; /* Is there a last (non-8-column) row? */ if (direction) fmd_hdl_debug(hdl, "--- WRITE Message Dump ---"); else fmd_hdl_debug(hdl, "--- READ Message Dump ---"); fmd_hdl_debug(hdl, " Displaying %d bytes", buflen); /* Dump the complete 8-column rows */ for (i = 0; i < j; i++) { c = (int16_t *)buf + (i * 8); fmd_hdl_debug(hdl, "%3d: %4x %4x %4x %4x %4x %4x %4x %4x", i, *(c+0), *(c+1), *(c+2), *(c+3), *(c+4), *(c+5), *(c+6), *(c+7)); } /* Dump the last (incomplete) row */ c = (int16_t *)buf + (i * 8); switch (k) { case 4: fmd_hdl_debug(hdl, "%3d: %4x %4x", i, *(c+0), *(c+1)); break; case 8: fmd_hdl_debug(hdl, "%3d: %4x %4x %4x %4x", i, *(c+0), *(c+1), *(c+2), *(c+3)); break; case 12: fmd_hdl_debug(hdl, "%3d: %4x %4x %4x %4x %4x %4x", i, *(c+0), *(c+1), *(c+2), *(c+3), *(c+4), *(c+5)); break; } fmd_hdl_debug(hdl, "--- End Dump ---"); } /* * Provide the length of a message based on the data in the given ETM header. */ static size_t etm_get_msglen(void *buf) { etm_proto_hdr_t *hp = (etm_proto_hdr_t *)buf; return (ntohl(hp->hdr_msglen)); } /* * Check the contents of the ETM header for errors. * Return the header type (hdr_type). */ static int etm_check_hdr(fmd_hdl_t *hdl, etm_epmap_t *mp, void *buf) { etm_proto_hdr_t *hp = (etm_proto_hdr_t *)buf; if (bcmp(hp->hdr_delim, ETM_DELIM, ETM_DELIMLEN) != 0) { fmd_hdl_debug(hdl, "Bad delimiter in ETM header from %s " ": 0x%x\n", mp->epm_ep_str, hp->hdr_delim); return (ETM_HDR_INVALID); } if ((hp->hdr_type == ETM_HDR_C_HELLO) || (hp->hdr_type == ETM_HDR_S_HELLO)) { /* Until version is negotiated, other fields may be wrong */ return (hp->hdr_type); } if (hp->hdr_ver != mp->epm_ver) { fmd_hdl_debug(hdl, "Bad version in ETM header from %s : 0x%x\n", mp->epm_ep_str, hp->hdr_ver); return (ETM_HDR_BADVERSION); } if ((hp->hdr_type == ETM_HDR_TYPE_TOO_LOW) || (hp->hdr_type >= ETM_HDR_TYPE_TOO_HIGH)) { fmd_hdl_debug(hdl, "Bad type in ETM header from %s : 0x%x\n", mp->epm_ep_str, hp->hdr_type); return (ETM_HDR_BADTYPE); } return (hp->hdr_type); } /* * Create an ETM header of a given type in the given buffer. * Return length of header. */ static size_t etm_create_hdr(void *buf, uint8_t ver, uint8_t type, uint32_t msglen) { etm_proto_hdr_t *hp = (etm_proto_hdr_t *)buf; bcopy(ETM_DELIM, hp->hdr_delim, ETM_DELIMLEN); hp->hdr_ver = ver; hp->hdr_type = type; hp->hdr_msglen = htonl(msglen); return (ETM_HDRLEN); } /* * Convert message bytes to nvlist and post to fmd. * Return zero for success, non-zero for failure. * * Note : nvl is free'd by fmd. */ static int etm_post_msg(fmd_hdl_t *hdl, etm_epmap_t *mp, void *buf, size_t buflen) { nvlist_t *nvl; int rv; if (nvlist_unpack((char *)buf, buflen, &nvl, 0)) { fmd_hdl_error(hdl, "failed to unpack message"); return (1); } rv = etm_xport_post_filter(hdl, nvl, mp->epm_ep_str); if (rv == ETM_XPORT_FILTER_DROP) { fmd_hdl_debug(hdl, "post_filter dropped event"); INCRSTAT(Etm_stats.post_filter.fmds_value.ui64); nvlist_free(nvl); return (0); } else if (rv == ETM_XPORT_FILTER_ERROR) { fmd_hdl_debug(hdl, "post_filter error : %s", strerror(errno)); INCRSTAT(Etm_stats.error_post_filter.fmds_value.ui64); /* Still post event */ } (void) pthread_mutex_lock(&mp->epm_lock); (void) pthread_mutex_lock(&Etm_mod_lock); if (!Etm_exit) { (void) pthread_mutex_unlock(&Etm_mod_lock); if (mp->epm_qstat == Q_OPEN) { fmd_xprt_post(hdl, mp->epm_xprthdl, nvl, 0); rv = 0; } else if (mp->epm_qstat == Q_SUSPENDED) { fmd_xprt_resume(hdl, mp->epm_xprthdl); if (mp->epm_timer_in_use) { fmd_timer_remove(hdl, mp->epm_timer_id); mp->epm_timer_in_use = 0; } mp->epm_qstat = Q_OPEN; fmd_hdl_debug(hdl, "queue resumed for %s", mp->epm_ep_str); fmd_xprt_post(hdl, mp->epm_xprthdl, nvl, 0); rv = 0; } else { fmd_hdl_debug(hdl, "unable to post message, qstat = %d", mp->epm_qstat); nvlist_free(nvl); /* Remote peer will attempt to resend event */ rv = 2; } } else { (void) pthread_mutex_unlock(&Etm_mod_lock); fmd_hdl_debug(hdl, "unable to post message, module exiting"); nvlist_free(nvl); /* Remote peer will attempt to resend event */ rv = 3; } (void) pthread_mutex_unlock(&mp->epm_lock); return (rv); } /* * Handle the startup handshake to the server. The client always initiates * the startup handshake. In the following sequence, we are the client and * the remote endpoint is the server. * * Client sends C_HELLO and transitions to Q_INIT_PENDING state. * Server sends S_HELLO and transitions to Q_INIT_PENDING state. * Client sends ACK and transitions to Q_OPEN state. * Server receives ACK and transitions to Q_OPEN state. * * Return 0 for success, nonzero for failure. */ static int etm_handle_startup(fmd_hdl_t *hdl, etm_epmap_t *mp) { etm_proto_hdr_t *hp; size_t hdrlen = ETM_HDRLEN; int hdrstat; char hbuf[ETM_HDRLEN]; if ((mp->epm_oconn = etm_xport_open(hdl, mp->epm_tlhdl)) == NULL) return (1); mp->epm_cstat = C_OPEN; hdrlen = etm_create_hdr(hbuf, mp->epm_ver, ETM_HDR_C_HELLO, 0); if ((etm_xport_write(hdl, mp->epm_oconn, Rw_timeout, hbuf, hdrlen)) != hdrlen) { fmd_hdl_error(hdl, "Failed to write C_HELLO to %s", mp->epm_ep_str); return (2); } mp->epm_qstat = Q_INIT_PENDING; if ((etm_xport_read(hdl, mp->epm_oconn, Rw_timeout, hbuf, hdrlen)) != hdrlen) { fmd_hdl_error(hdl, "Failed to read S_HELLO from %s", mp->epm_ep_str); return (3); } hdrstat = etm_check_hdr(hdl, mp, hbuf); if (hdrstat != ETM_HDR_S_HELLO) { fmd_hdl_error(hdl, "Protocol error, did not receive S_HELLO " "from %s", mp->epm_ep_str); return (4); } /* * Get version from the server. * Currently, only one version is supported. */ hp = (etm_proto_hdr_t *)(void *)hbuf; if (hp->hdr_ver != ETM_PROTO_V1) { fmd_hdl_error(hdl, "Unable to use same version as %s : %d", mp->epm_ep_str, hp->hdr_ver); return (5); } mp->epm_ver = hp->hdr_ver; hdrlen = etm_create_hdr(hbuf, mp->epm_ver, ETM_HDR_ACK, 0); if ((etm_xport_write(hdl, mp->epm_oconn, Rw_timeout, hbuf, hdrlen)) != hdrlen) { fmd_hdl_error(hdl, "Failed to write ACK for S_HELLO to %s", mp->epm_ep_str); return (6); } /* * Call fmd_xprt_open and fmd_xprt_setspecific with * Etm_mod_lock held to avoid race with etm_send thread. */ (void) pthread_mutex_lock(&Etm_mod_lock); if ((mp->epm_xprthdl = fmd_xprt_open(hdl, mp->epm_xprtflags, mp->epm_ep_nvl, NULL)) == NULL) { fmd_hdl_abort(hdl, "Failed to init xprthdl for %s", mp->epm_ep_str); } fmd_xprt_setspecific(hdl, mp->epm_xprthdl, mp); (void) pthread_mutex_unlock(&Etm_mod_lock); mp->epm_qstat = Q_OPEN; fmd_hdl_debug(hdl, "queue open for %s", mp->epm_ep_str); return (0); } /* * Open a connection to the peer, send a SHUTDOWN message, * and close the connection. */ static void etm_send_shutdown(fmd_hdl_t *hdl, etm_epmap_t *mp) { size_t hdrlen = ETM_HDRLEN; char hbuf[ETM_HDRLEN]; if ((mp->epm_oconn = etm_xport_open(hdl, mp->epm_tlhdl)) == NULL) return; hdrlen = etm_create_hdr(hbuf, mp->epm_ver, ETM_HDR_SHUTDOWN, 0); (void) etm_xport_write(hdl, mp->epm_oconn, Rw_timeout, hbuf, hdrlen); (void) etm_xport_close(hdl, mp->epm_oconn); mp->epm_oconn = NULL; } /* * Alloc a nvlist and add a string for the endpoint. * Return zero for success, non-zero for failure. */ static int etm_get_ep_nvl(fmd_hdl_t *hdl, etm_epmap_t *mp) { /* * Cannot use nvlist_xalloc(3NVPAIR) due to a recursive mutex situation * in fmd when this nvlist_t is free'd. */ (void) nvlist_alloc(&mp->epm_ep_nvl, NV_UNIQUE_NAME, 0); if (nvlist_add_string(mp->epm_ep_nvl, "domain-id", mp->epm_ep_str)) { fmd_hdl_error(hdl, "failed to add domain-id string to nvlist " "for %s", mp->epm_ep_str); nvlist_free(mp->epm_ep_nvl); return (1); } return (0); } /* * Free the nvlist for the endpoint_id string. */ /*ARGSUSED*/ static void etm_free_ep_nvl(fmd_hdl_t *hdl, etm_epmap_t *mp) { nvlist_free(mp->epm_ep_nvl); } /* * Check for a duplicate endpoint/peer string. */ /*ARGSUSED*/ static int etm_check_dup_ep_str(fmd_hdl_t *hdl, char *epname) { etm_epmap_t *mp; for (mp = Epmap_head; mp != NULL; mp = mp->epm_next) if (strcmp(epname, mp->epm_ep_str) == 0) return (1); return (0); } /* * Attempt to re-open a connection with the remote endpoint. */ static void etm_reconnect(fmd_hdl_t *hdl, etm_epmap_t *mp) { if ((mp->epm_reconn_end > 0) && (mp->epm_cstat == C_UNINITIALIZED)) { if (gethrtime() < mp->epm_reconn_end) { if ((mp->epm_oconn = etm_xport_open(hdl, mp->epm_tlhdl)) == NULL) { fmd_hdl_debug(hdl, "reconnect failed for %s", mp->epm_ep_str); mp->epm_timer_id = fmd_timer_install(hdl, mp, NULL, Reconn_interval); mp->epm_timer_in_use = 1; } else { fmd_hdl_debug(hdl, "reconnect success for %s", mp->epm_ep_str); mp->epm_reconn_end = 0; mp->epm_cstat = C_OPEN; } } else { fmd_hdl_error(hdl, "Reconnect timed out for %s\n", mp->epm_ep_str); mp->epm_reconn_end = 0; mp->epm_cstat = C_TIMED_OUT; } } if (mp->epm_cstat == C_OPEN) { fmd_xprt_resume(hdl, mp->epm_xprthdl); mp->epm_qstat = Q_OPEN; fmd_hdl_debug(hdl, "queue resumed for %s", mp->epm_ep_str); } } /* * Suspend a given connection and setup for reconnection retries. * Assume caller holds lock on epm_lock. */ static void etm_suspend_reconnect(fmd_hdl_t *hdl, etm_epmap_t *mp) { (void) pthread_mutex_lock(&Etm_mod_lock); if (Etm_exit) { (void) pthread_mutex_unlock(&Etm_mod_lock); return; } (void) pthread_mutex_unlock(&Etm_mod_lock); if (mp->epm_oconn != NULL) { (void) etm_xport_close(hdl, mp->epm_oconn); mp->epm_oconn = NULL; } mp->epm_reconn_end = gethrtime() + Reconn_timeout; mp->epm_cstat = C_UNINITIALIZED; if (mp->epm_xprthdl != NULL) { fmd_xprt_suspend(hdl, mp->epm_xprthdl); mp->epm_qstat = Q_SUSPENDED; fmd_hdl_debug(hdl, "queue suspended for %s", mp->epm_ep_str); if (mp->epm_timer_in_use == 0) { mp->epm_timer_id = fmd_timer_install(hdl, mp, NULL, Reconn_interval); mp->epm_timer_in_use = 1; } } } /* * Reinitialize the connection. The old fmd_xprt_t handle must be * removed/closed first. * Assume caller holds lock on epm_lock. */ static void etm_reinit(fmd_hdl_t *hdl, etm_epmap_t *mp) { /* * To avoid a deadlock, wait for etm_send to finish before * calling fmd_xprt_close() */ while (mp->epm_txbusy) (void) pthread_cond_wait(&mp->epm_tx_cv, &mp->epm_lock); if (mp->epm_xprthdl != NULL) { fmd_xprt_close(hdl, mp->epm_xprthdl); fmd_hdl_debug(hdl, "queue closed for %s", mp->epm_ep_str); mp->epm_xprthdl = NULL; /* mp->epm_ep_nvl is free'd in fmd_xprt_close */ mp->epm_ep_nvl = NULL; } if (mp->epm_timer_in_use) { fmd_timer_remove(hdl, mp->epm_timer_id); mp->epm_timer_in_use = 0; } if (mp->epm_oconn != NULL) { (void) etm_xport_close(hdl, mp->epm_oconn); mp->epm_oconn = NULL; } mp->epm_cstat = C_UNINITIALIZED; mp->epm_qstat = Q_UNINITIALIZED; } /* * Receive data from ETM transport layer. * Note : This is not the fmdo_recv entry point. * */ static int etm_recv(fmd_hdl_t *hdl, etm_xport_conn_t conn, etm_epmap_t *mp) { size_t buflen, hdrlen; void *buf; char hbuf[ETM_HDRLEN]; int hdrstat, rv; hdrlen = ETM_HDRLEN; if ((etm_xport_read(hdl, conn, Rw_timeout, hbuf, hdrlen)) != hdrlen) { fmd_hdl_debug(hdl, "failed to read header from %s", mp->epm_ep_str); INCRSTAT(Etm_stats.error_read.fmds_value.ui64); return (EIO); } hdrstat = etm_check_hdr(hdl, mp, hbuf); switch (hdrstat) { case ETM_HDR_INVALID: (void) pthread_mutex_lock(&mp->epm_lock); if (mp->epm_cstat == C_OPEN) mp->epm_cstat = C_CLOSED; (void) pthread_mutex_unlock(&mp->epm_lock); INCRSTAT(Etm_stats.error_read_badhdr.fmds_value.ui64); rv = ECANCELED; break; case ETM_HDR_BADTYPE: case ETM_HDR_BADVERSION: hdrlen = etm_create_hdr(hbuf, mp->epm_ver, ETM_HDR_NAK, 0); if ((etm_xport_write(hdl, conn, Rw_timeout, hbuf, hdrlen)) != hdrlen) { fmd_hdl_debug(hdl, "failed to write NAK to %s", mp->epm_ep_str); INCRSTAT(Etm_stats.error_write.fmds_value.ui64); return (EIO); } (void) pthread_mutex_lock(&mp->epm_lock); mp->epm_cstat = C_LIMBO; (void) pthread_mutex_unlock(&mp->epm_lock); INCRSTAT(Etm_stats.error_read_badhdr.fmds_value.ui64); rv = ENOTSUP; break; case ETM_HDR_C_HELLO: /* Client is initiating a startup handshake */ (void) pthread_mutex_lock(&mp->epm_lock); etm_reinit(hdl, mp); mp->epm_qstat = Q_INIT_PENDING; (void) pthread_mutex_unlock(&mp->epm_lock); hdrlen = etm_create_hdr(hbuf, mp->epm_ver, ETM_HDR_S_HELLO, 0); if ((etm_xport_write(hdl, conn, Rw_timeout, hbuf, hdrlen)) != hdrlen) { fmd_hdl_debug(hdl, "failed to write S_HELLO to %s", mp->epm_ep_str); INCRSTAT(Etm_stats.error_write.fmds_value.ui64); return (EIO); } rv = 0; break; case ETM_HDR_ACK: (void) pthread_mutex_lock(&mp->epm_lock); if (mp->epm_qstat == Q_INIT_PENDING) { /* This is client's ACK from startup handshake */ /* mp->epm_ep_nvl is free'd in fmd_xprt_close */ if (mp->epm_ep_nvl == NULL) (void) etm_get_ep_nvl(hdl, mp); /* * Call fmd_xprt_open and fmd_xprt_setspecific with * Etm_mod_lock held to avoid race with etm_send thread. */ (void) pthread_mutex_lock(&Etm_mod_lock); if ((mp->epm_xprthdl = fmd_xprt_open(hdl, mp->epm_xprtflags, mp->epm_ep_nvl, NULL)) == NULL) { fmd_hdl_abort(hdl, "Failed to init xprthdl " "for %s", mp->epm_ep_str); } fmd_xprt_setspecific(hdl, mp->epm_xprthdl, mp); (void) pthread_mutex_unlock(&Etm_mod_lock); mp->epm_qstat = Q_OPEN; (void) pthread_mutex_unlock(&mp->epm_lock); fmd_hdl_debug(hdl, "queue open for %s", mp->epm_ep_str); } else { (void) pthread_mutex_unlock(&mp->epm_lock); fmd_hdl_debug(hdl, "protocol error, not expecting ACK " "from %s\n", mp->epm_ep_str); INCRSTAT(Etm_stats.error_protocol.fmds_value.ui64); } rv = 0; break; case ETM_HDR_SHUTDOWN: fmd_hdl_debug(hdl, "received shutdown from %s", mp->epm_ep_str); (void) pthread_mutex_lock(&mp->epm_lock); etm_reinit(hdl, mp); if (IS_CLIENT(mp)) { /* * A server shutdown is considered to be temporary. * Prepare for reconnection. */ mp->epm_timer_id = fmd_timer_install(hdl, mp, NULL, Reconn_interval); mp->epm_timer_in_use = 1; } (void) pthread_mutex_unlock(&mp->epm_lock); rv = ECANCELED; break; case ETM_HDR_MSG: (void) pthread_mutex_lock(&mp->epm_lock); if (mp->epm_qstat == Q_UNINITIALIZED) { /* Peer (client) is unaware that we've restarted */ (void) pthread_mutex_unlock(&mp->epm_lock); hdrlen = etm_create_hdr(hbuf, mp->epm_ver, ETM_HDR_S_RESTART, 0); if ((etm_xport_write(hdl, conn, Rw_timeout, hbuf, hdrlen)) != hdrlen) { fmd_hdl_debug(hdl, "failed to write S_RESTART " "to %s", mp->epm_ep_str); INCRSTAT(Etm_stats.error_write.fmds_value.ui64); return (EIO); } return (ECANCELED); } (void) pthread_mutex_unlock(&mp->epm_lock); buflen = etm_get_msglen(hbuf); ALLOC_BUF(hdl, buf, buflen); if (etm_xport_read(hdl, conn, Rw_timeout, buf, buflen) != buflen) { fmd_hdl_debug(hdl, "failed to read message from %s", mp->epm_ep_str); FREE_BUF(hdl, buf, buflen); INCRSTAT(Etm_stats.error_read.fmds_value.ui64); return (EIO); } INCRSTAT(Etm_stats.read_msg.fmds_value.ui64); ADDSTAT(Etm_stats.read_bytes.fmds_value.ui64, buflen); etm_hex_dump(hdl, buf, buflen, 0); if (etm_post_msg(hdl, mp, buf, buflen)) { INCRSTAT(Etm_stats.error_drop_read.fmds_value.ui64); FREE_BUF(hdl, buf, buflen); return (EIO); } FREE_BUF(hdl, buf, buflen); hdrlen = etm_create_hdr(hbuf, mp->epm_ver, ETM_HDR_ACK, 0); if ((etm_xport_write(hdl, conn, Rw_timeout, hbuf, hdrlen)) != hdrlen) { fmd_hdl_debug(hdl, "failed to write ACK to %s", mp->epm_ep_str); INCRSTAT(Etm_stats.error_write.fmds_value.ui64); return (EIO); } INCRSTAT(Etm_stats.write_ack.fmds_value.ui64); /* * If we got this far and the current state of the * outbound/sending connection is TIMED_OUT or * LIMBO, then we should reinitialize it. */ (void) pthread_mutex_lock(&mp->epm_lock); if (mp->epm_cstat == C_TIMED_OUT || mp->epm_cstat == C_LIMBO) { if (mp->epm_oconn != NULL) { (void) etm_xport_close(hdl, mp->epm_oconn); mp->epm_oconn = NULL; } mp->epm_cstat = C_UNINITIALIZED; fmd_xprt_resume(hdl, mp->epm_xprthdl); if (mp->epm_timer_in_use) { fmd_timer_remove(hdl, mp->epm_timer_id); mp->epm_timer_in_use = 0; } mp->epm_qstat = Q_OPEN; fmd_hdl_debug(hdl, "queue resumed for %s", mp->epm_ep_str); } (void) pthread_mutex_unlock(&mp->epm_lock); rv = 0; break; default: fmd_hdl_debug(hdl, "protocol error, unexpected header " "from %s : %d", mp->epm_ep_str, hdrstat); INCRSTAT(Etm_stats.error_protocol.fmds_value.ui64); rv = 0; } return (rv); } /* * ETM transport layer callback function. * The transport layer calls this function to : * (a) pass an incoming message (flag == ETM_CBFLAG_RECV) * (b) tell us to reinitialize the connection (flag == ETM_CBFLAG_REINIT) */ static int etm_cb_func(fmd_hdl_t *hdl, etm_xport_conn_t conn, etm_cb_flag_t flag, void *arg) { etm_epmap_t *mp = (etm_epmap_t *)arg; int rv = 0; (void) pthread_mutex_lock(&Etm_mod_lock); if (Etm_exit) { (void) pthread_mutex_unlock(&Etm_mod_lock); return (ECANCELED); } (void) pthread_mutex_unlock(&Etm_mod_lock); switch (flag) { case ETM_CBFLAG_RECV: rv = etm_recv(hdl, conn, mp); break; case ETM_CBFLAG_REINIT: (void) pthread_mutex_lock(&mp->epm_lock); etm_reinit(hdl, mp); etm_send_shutdown(hdl, mp); (void) pthread_mutex_unlock(&mp->epm_lock); /* * Return ECANCELED so the transport layer will close the * server connection. The transport layer is responsible for * reestablishing this connection (should a connection request * arrive from the peer). */ rv = ECANCELED; break; default: fmd_hdl_debug(hdl, "Unknown callback flag : 0x%x", flag); rv = ENOTSUP; } return (rv); } /* * Allocate and initialize an etm_epmap_t struct for the given endpoint * name string. */ static void etm_init_epmap(fmd_hdl_t *hdl, char *epname, int flags) { etm_epmap_t *newmap; if (etm_check_dup_ep_str(hdl, epname)) { fmd_hdl_debug(hdl, "skipping duplicate peer : %s", epname); return; } newmap = fmd_hdl_zalloc(hdl, sizeof (etm_epmap_t), FMD_SLEEP); newmap->epm_ep_str = fmd_hdl_strdup(hdl, epname, FMD_SLEEP); newmap->epm_xprtflags = flags; newmap->epm_cstat = C_UNINITIALIZED; newmap->epm_qstat = Q_UNINITIALIZED; newmap->epm_ver = ETM_PROTO_V1; /* Currently support one proto ver */ newmap->epm_txbusy = 0; (void) pthread_mutex_init(&newmap->epm_lock, NULL); (void) pthread_cond_init(&newmap->epm_tx_cv, NULL); if (etm_get_ep_nvl(hdl, newmap)) { fmd_hdl_strfree(hdl, newmap->epm_ep_str); fmd_hdl_free(hdl, newmap, sizeof (etm_epmap_t)); return; } (void) pthread_mutex_lock(&newmap->epm_lock); if ((newmap->epm_tlhdl = etm_xport_init(hdl, newmap->epm_ep_str, etm_cb_func, newmap)) == NULL) { fmd_hdl_debug(hdl, "failed to init tlhdl for %s\n", newmap->epm_ep_str); etm_free_ep_nvl(hdl, newmap); (void) pthread_mutex_unlock(&newmap->epm_lock); (void) pthread_mutex_destroy(&newmap->epm_lock); fmd_hdl_strfree(hdl, newmap->epm_ep_str); fmd_hdl_free(hdl, newmap, sizeof (etm_epmap_t)); return; } if (IS_CLIENT(newmap)) { if (etm_handle_startup(hdl, newmap)) { /* * For whatever reason, we could not complete the * startup handshake with the server. Set the timer * and try again. */ if (newmap->epm_oconn != NULL) { (void) etm_xport_close(hdl, newmap->epm_oconn); newmap->epm_oconn = NULL; } newmap->epm_cstat = C_UNINITIALIZED; newmap->epm_qstat = Q_UNINITIALIZED; newmap->epm_timer_id = fmd_timer_install(hdl, newmap, NULL, Reconn_interval); newmap->epm_timer_in_use = 1; } } else { /* * We may be restarting after a crash. If so, the client * may be unaware of this. */ etm_send_shutdown(hdl, newmap); } /* Add this transport instance handle to the list */ newmap->epm_next = Epmap_head; Epmap_head = newmap; (void) pthread_mutex_unlock(&newmap->epm_lock); INCRSTAT(Etm_stats.peer_count.fmds_value.ui64); } /* * Parse the given property list string and call etm_init_epmap * for each endpoint. */ static void etm_create_epmaps(fmd_hdl_t *hdl, char *eplist, int flags) { char *epstr, *ep, *prefix, *lasts, *numstr; char epname[MAXPATHLEN]; size_t slen, nlen; int beg, end, i; if (eplist == NULL) return; /* * Create a copy of eplist for parsing. * strtok/strtok_r(3C) will insert null chars to the string. * Therefore, fmd_hdl_strdup/fmd_hdl_strfree cannot be used. */ slen = strlen(eplist); epstr = fmd_hdl_zalloc(hdl, slen + 1, FMD_SLEEP); (void) strcpy(epstr, eplist); /* * The following are supported for the "client_list" and * "server_list" properties : * * A space-separated list of endpoints. * "dev:///dom0 dev:///dom1 dev:///dom2" * * An array syntax for a range of instances. * "dev:///dom[0:2]" * * A combination of both. * "dev:///dom0 dev:///dom[1:2]" */ ep = strtok_r(epstr, " ", &lasts); while (ep != NULL) { if (strchr(ep, '[') != NULL) { /* * This string is using array syntax. * Check the string for correct syntax. */ if ((strchr(ep, ':') == NULL) || (strchr(ep, ']') == NULL)) { fmd_hdl_error(hdl, "Syntax error in property " "that includes : %s\n", ep); ep = strtok_r(NULL, " ", &lasts); continue; } /* expand the array syntax */ prefix = strtok(ep, "["); numstr = strtok(NULL, ":"); if ((numstr == NULL) || (!isdigit(*numstr))) { fmd_hdl_error(hdl, "Syntax error in property " "that includes : %s[\n", prefix); ep = strtok_r(NULL, " ", &lasts); continue; } beg = atoi(numstr); numstr = strtok(NULL, "]"); if ((numstr == NULL) || (!isdigit(*numstr))) { fmd_hdl_error(hdl, "Syntax error in property " "that includes : %s[\n", prefix); ep = strtok_r(NULL, " ", &lasts); continue; } end = atoi(numstr); nlen = strlen(prefix) + ETM_EP_INST_MAX; if (nlen > MAXPATHLEN) { fmd_hdl_error(hdl, "Endpoint prop string " "exceeds MAXPATHLEN\n"); ep = strtok_r(NULL, " ", &lasts); continue; } for (i = beg; i <= end; i++) { bzero(epname, MAXPATHLEN); (void) snprintf(epname, nlen, "%s%d", prefix, i); etm_init_epmap(hdl, epname, flags); } } else { etm_init_epmap(hdl, ep, flags); } ep = strtok_r(NULL, " ", &lasts); } fmd_hdl_free(hdl, epstr, slen + 1); } /* * Free the transport infrastructure for an endpoint. */ static void etm_free_epmap(fmd_hdl_t *hdl, etm_epmap_t *mp) { size_t hdrlen; char hbuf[ETM_HDRLEN]; (void) pthread_mutex_lock(&mp->epm_lock); /* * If an etm_send thread is in progress, wait for it to finish. * The etm_recv thread is managed by the transport layer and will * be destroyed with etm_xport_fini(). */ while (mp->epm_txbusy) (void) pthread_cond_wait(&mp->epm_tx_cv, &mp->epm_lock); if (mp->epm_timer_in_use) fmd_timer_remove(hdl, mp->epm_timer_id); if (mp->epm_oconn != NULL) { hdrlen = etm_create_hdr(hbuf, mp->epm_ver, ETM_HDR_SHUTDOWN, 0); (void) etm_xport_write(hdl, mp->epm_oconn, Rw_timeout, hbuf, hdrlen); (void) etm_xport_close(hdl, mp->epm_oconn); mp->epm_oconn = NULL; } if (mp->epm_xprthdl != NULL) { fmd_xprt_close(hdl, mp->epm_xprthdl); /* mp->epm_ep_nvl is free'd in fmd_xprt_close */ mp->epm_ep_nvl = NULL; } if (mp->epm_ep_nvl != NULL) etm_free_ep_nvl(hdl, mp); if (mp->epm_tlhdl != NULL) (void) etm_xport_fini(hdl, mp->epm_tlhdl); (void) pthread_mutex_unlock(&mp->epm_lock); (void) pthread_mutex_destroy(&mp->epm_lock); fmd_hdl_strfree(hdl, mp->epm_ep_str); fmd_hdl_free(hdl, mp, sizeof (etm_epmap_t)); DECRSTAT(Etm_stats.peer_count.fmds_value.ui64); } /* * FMD entry points */ /* * FMD fmdo_send entry point. * Send an event to the remote endpoint and receive an ACK. */ static int etm_send(fmd_hdl_t *hdl, fmd_xprt_t *xprthdl, fmd_event_t *ep, nvlist_t *nvl) { etm_epmap_t *mp; nvlist_t *msgnvl; int hdrstat, rv, cnt = 0; char *buf, *nvbuf, *class; size_t nvsize, buflen, hdrlen; struct timespec tms; (void) pthread_mutex_lock(&Etm_mod_lock); if (Etm_exit) { (void) pthread_mutex_unlock(&Etm_mod_lock); return (FMD_SEND_RETRY); } (void) pthread_mutex_unlock(&Etm_mod_lock); mp = fmd_xprt_getspecific(hdl, xprthdl); for (;;) { if (pthread_mutex_trylock(&mp->epm_lock) == 0) { break; } else { /* * Another thread may be (1) trying to close this * fmd_xprt_t, or (2) posting an event to it. * If (1), don't want to spend too much time here. * If (2), allow it to finish and release epm_lock. */ if (cnt++ < 10) { tms.tv_sec = 0; tms.tv_nsec = (cnt * 10000); (void) nanosleep(&tms, NULL); } else { return (FMD_SEND_RETRY); } } } mp->epm_txbusy++; if (mp->epm_qstat == Q_UNINITIALIZED) { mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); return (FMD_SEND_FAILED); } if (mp->epm_cstat == C_CLOSED) { etm_suspend_reconnect(hdl, mp); mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); return (FMD_SEND_RETRY); } if (mp->epm_cstat == C_LIMBO) { if (mp->epm_oconn != NULL) { (void) etm_xport_close(hdl, mp->epm_oconn); mp->epm_oconn = NULL; } fmd_xprt_suspend(hdl, xprthdl); mp->epm_qstat = Q_SUSPENDED; mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); fmd_hdl_debug(hdl, "queue suspended for %s", mp->epm_ep_str); return (FMD_SEND_RETRY); } if (mp->epm_oconn == NULL) { if ((mp->epm_oconn = etm_xport_open(hdl, mp->epm_tlhdl)) == NULL) { etm_suspend_reconnect(hdl, mp); mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); return (FMD_SEND_RETRY); } else { mp->epm_cstat = C_OPEN; } } if (nvlist_lookup_string(nvl, FM_CLASS, &class) != 0) fmd_hdl_abort(hdl, "No class string in nvlist"); msgnvl = fmd_xprt_translate(hdl, xprthdl, ep); if (msgnvl == NULL) { mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); fmd_hdl_error(hdl, "Failed to translate event %p\n", (void *) ep); return (FMD_SEND_FAILED); } rv = etm_xport_send_filter(hdl, msgnvl, mp->epm_ep_str); if (rv == ETM_XPORT_FILTER_DROP) { mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); fmd_hdl_debug(hdl, "send_filter dropped event"); nvlist_free(msgnvl); INCRSTAT(Etm_stats.send_filter.fmds_value.ui64); return (FMD_SEND_SUCCESS); } else if (rv == ETM_XPORT_FILTER_ERROR) { fmd_hdl_debug(hdl, "send_filter error : %s", strerror(errno)); INCRSTAT(Etm_stats.error_send_filter.fmds_value.ui64); /* Still send event */ } (void) pthread_mutex_unlock(&mp->epm_lock); (void) nvlist_size(msgnvl, &nvsize, NV_ENCODE_XDR); hdrlen = ETM_HDRLEN; buflen = nvsize + hdrlen; ALLOC_BUF(hdl, buf, buflen); nvbuf = buf + hdrlen; (void) etm_create_hdr(buf, mp->epm_ver, ETM_HDR_MSG, nvsize); if (rv = nvlist_pack(msgnvl, &nvbuf, &nvsize, NV_ENCODE_XDR, 0)) { (void) pthread_mutex_lock(&mp->epm_lock); mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); fmd_hdl_error(hdl, "Failed to pack event : %s\n", strerror(rv)); nvlist_free(msgnvl); FREE_BUF(hdl, buf, buflen); return (FMD_SEND_FAILED); } nvlist_free(msgnvl); if (etm_xport_write(hdl, mp->epm_oconn, Rw_timeout, buf, buflen) != buflen) { fmd_hdl_debug(hdl, "failed to send message to %s", mp->epm_ep_str); (void) pthread_mutex_lock(&mp->epm_lock); etm_suspend_reconnect(hdl, mp); mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); FREE_BUF(hdl, buf, buflen); INCRSTAT(Etm_stats.error_write.fmds_value.ui64); return (FMD_SEND_RETRY); } INCRSTAT(Etm_stats.write_msg.fmds_value.ui64); ADDSTAT(Etm_stats.write_bytes.fmds_value.ui64, nvsize); etm_hex_dump(hdl, nvbuf, nvsize, 1); if (etm_xport_read(hdl, mp->epm_oconn, Rw_timeout, buf, hdrlen) != hdrlen) { fmd_hdl_debug(hdl, "failed to read ACK from %s", mp->epm_ep_str); (void) pthread_mutex_lock(&mp->epm_lock); etm_suspend_reconnect(hdl, mp); mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); FREE_BUF(hdl, buf, buflen); INCRSTAT(Etm_stats.error_read.fmds_value.ui64); return (FMD_SEND_RETRY); } hdrstat = etm_check_hdr(hdl, mp, buf); FREE_BUF(hdl, buf, buflen); if (hdrstat == ETM_HDR_ACK) { INCRSTAT(Etm_stats.read_ack.fmds_value.ui64); } else { (void) pthread_mutex_lock(&mp->epm_lock); (void) etm_xport_close(hdl, mp->epm_oconn); mp->epm_oconn = NULL; if (hdrstat == ETM_HDR_NAK) { /* Peer received a bad value in the header */ if (mp->epm_xprthdl != NULL) { mp->epm_cstat = C_LIMBO; fmd_xprt_suspend(hdl, xprthdl); mp->epm_qstat = Q_SUSPENDED; fmd_hdl_debug(hdl, "received NAK, queue " "suspended for %s", mp->epm_ep_str); } rv = FMD_SEND_RETRY; } else if (hdrstat == ETM_HDR_S_RESTART) { /* Server has restarted */ mp->epm_cstat = C_CLOSED; mp->epm_qstat = Q_UNINITIALIZED; fmd_hdl_debug(hdl, "server %s restarted", mp->epm_ep_str); /* * Cannot call fmd_xprt_close here, so we'll do it * on the timeout thread. */ if (mp->epm_timer_in_use == 0) { mp->epm_timer_id = fmd_timer_install( hdl, mp, NULL, 0); mp->epm_timer_in_use = 1; } /* * fault.* or list.* events will be replayed if a * transport is opened with the same auth. * Other events will be discarded. */ rv = FMD_SEND_FAILED; } else { mp->epm_cstat = C_CLOSED; fmd_hdl_debug(hdl, "bad ACK from %s", mp->epm_ep_str); rv = FMD_SEND_RETRY; } mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); INCRSTAT(Etm_stats.error_read_badhdr.fmds_value.ui64); return (rv); } (void) pthread_mutex_lock(&mp->epm_lock); mp->epm_txbusy--; (void) pthread_cond_broadcast(&mp->epm_tx_cv); (void) pthread_mutex_unlock(&mp->epm_lock); return (FMD_SEND_SUCCESS); } /* * FMD fmdo_timeout entry point.. */ /*ARGSUSED*/ static void etm_timeout(fmd_hdl_t *hdl, id_t id, void *data) { etm_epmap_t *mp = (etm_epmap_t *)data; (void) pthread_mutex_lock(&mp->epm_lock); mp->epm_timer_in_use = 0; if (mp->epm_qstat == Q_UNINITIALIZED) { /* Server has shutdown and we (client) need to reconnect */ if (mp->epm_xprthdl != NULL) { fmd_xprt_close(hdl, mp->epm_xprthdl); fmd_hdl_debug(hdl, "queue closed for %s", mp->epm_ep_str); mp->epm_xprthdl = NULL; /* mp->epm_ep_nvl is free'd in fmd_xprt_close */ mp->epm_ep_nvl = NULL; } if (mp->epm_ep_nvl == NULL) (void) etm_get_ep_nvl(hdl, mp); if (etm_handle_startup(hdl, mp)) { if (mp->epm_oconn != NULL) { (void) etm_xport_close(hdl, mp->epm_oconn); mp->epm_oconn = NULL; } mp->epm_cstat = C_UNINITIALIZED; mp->epm_qstat = Q_UNINITIALIZED; mp->epm_timer_id = fmd_timer_install(hdl, mp, NULL, Reconn_interval); mp->epm_timer_in_use = 1; } } else { etm_reconnect(hdl, mp); } (void) pthread_mutex_unlock(&mp->epm_lock); } /* * FMD Module declarations */ static const fmd_hdl_ops_t etm_ops = { NULL, /* fmdo_recv */ etm_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ etm_send, /* fmdo_send */ }; static const fmd_prop_t etm_props[] = { { "client_list", FMD_TYPE_STRING, NULL }, { "server_list", FMD_TYPE_STRING, NULL }, { "reconnect_interval", FMD_TYPE_UINT64, "10000000000" }, { "reconnect_timeout", FMD_TYPE_UINT64, "300000000000" }, { "rw_timeout", FMD_TYPE_UINT64, "2000000000" }, { "filter_path", FMD_TYPE_STRING, NULL }, { NULL, 0, NULL } }; static const fmd_hdl_info_t etm_info = { "Event Transport Module", "2.0", &etm_ops, etm_props }; /* * Initialize the transport for use by ETM. */ void _fmd_init(fmd_hdl_t *hdl) { char *propstr; if (fmd_hdl_register(hdl, FMD_API_VERSION, &etm_info) != 0) { return; /* invalid data in configuration file */ } /* Create global stats */ (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (Etm_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&Etm_stats); /* Get module properties */ Reconn_timeout = fmd_prop_get_int64(hdl, "reconnect_timeout"); Reconn_interval = fmd_prop_get_int64(hdl, "reconnect_interval"); Rw_timeout = fmd_prop_get_int64(hdl, "rw_timeout"); propstr = fmd_prop_get_string(hdl, "client_list"); etm_create_epmaps(hdl, propstr, ETM_SERVER_XPRT_FLAGS); fmd_prop_free_string(hdl, propstr); propstr = fmd_prop_get_string(hdl, "server_list"); etm_create_epmaps(hdl, propstr, ETM_CLIENT_XPRT_FLAGS); fmd_prop_free_string(hdl, propstr); if (Etm_stats.peer_count.fmds_value.ui64 == 0) { fmd_hdl_debug(hdl, "Failed to init any endpoint\n"); fmd_hdl_unregister(hdl); return; } } /* * Teardown the transport */ void _fmd_fini(fmd_hdl_t *hdl) { etm_epmap_t *mp, *next; (void) pthread_mutex_lock(&Etm_mod_lock); Etm_exit = 1; (void) pthread_mutex_unlock(&Etm_mod_lock); mp = Epmap_head; while (mp) { next = mp->epm_next; etm_free_epmap(hdl, mp); mp = next; } fmd_hdl_unregister(hdl); } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * FMA ETM-to-ETM Protocol header * * const/type defns for protocol used between two event transport * modules (ETMs) */ #ifndef _ETM_PROTO_H #define _ETM_PROTO_H #ifdef __cplusplus extern "C" { #endif #include #define ETM_DELIM "\177ETM" /* Start of header delimiter */ #define ETM_DELIMLEN 4 /* Length of header deimiter */ typedef struct etm_proto_header { char hdr_delim[ETM_DELIMLEN]; /* Start of header delimiter */ uint8_t hdr_ver; /* ETM protocol version */ uint8_t hdr_type; /* Header type */ uint8_t hdr_pad1; /* reserved/padding/alignment */ uint8_t hdr_pad2; /* reserved/padding/alignment */ uint32_t hdr_msglen; /* Length of following message */ } etm_proto_hdr_t; typedef enum etm_proto_header_type { ETM_HDR_TYPE_TOO_LOW = 0, /* Range check place holder */ ETM_HDR_MSG, /* FMA event to follow */ ETM_HDR_S_RESTART, /* Server re-start indication */ ETM_HDR_C_HELLO, /* Client startup indication */ ETM_HDR_S_HELLO, /* Server response to C_HELLO */ ETM_HDR_ACK, /* Acknowledgement */ ETM_HDR_NAK, /* Negative acknowledgement */ ETM_HDR_SHUTDOWN, /* Notify remote ETM of shutdown */ ETM_HDR_TYPE_TOO_HIGH /* Range check place holder */ } etm_proto_hdr_type_t; #define ETM_HDRLEN sizeof (etm_proto_hdr_t) #define ETM_PROTO_V1 1 #ifdef __cplusplus } #endif #endif /* _ETM_PROTO_H */ /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * FMA ETM-to-Transport API header * * const/type defns for transporting data between an Event Transport * Module (ETM) and its associated Transport Layer within a fault domain. */ #ifndef _ETM_XPORT_API_H #define _ETM_XPORT_API_H #ifdef __cplusplus extern "C" { #endif #include typedef void* etm_xport_hdl_t; /* transport instance handle */ typedef void* etm_xport_conn_t; /* transport connection handle */ typedef enum etm_callback_flag { ETM_CBFLAG_REINIT, /* reinitialize connection */ ETM_CBFLAG_RECV /* receive message */ } etm_cb_flag_t; /* * Connection Management */ /* * Initialize/setup transport instance before any connections are opened. * Return transport instance handle if successful, or NULL for failure. */ etm_xport_hdl_t etm_xport_init(fmd_hdl_t *hdl, char *endpoint_id, int (*cb_func)(fmd_hdl_t *hdl, etm_xport_conn_t conn, etm_cb_flag_t flag, void *arg), void *cb_func_arg); /* * Callback function provided to etm_xport_init(). * This function is called by the transport layer to notify the common layer * that action is required (receive a message, reinitialize a connection, etc.). * Return zero for success. * For any non-zero return value, the connection should be closed. */ int etm_xport_cb_func(fmd_hdl_t *hdl, etm_xport_conn_t conn, etm_cb_flag_t flag, void *arg); /* * Finish/teardown any transport infrastructure. * Return zero if successful, or nonzero for failure. */ int etm_xport_fini(fmd_hdl_t *hdl, etm_xport_hdl_t tlhdl); /* * Open a connection with the given endpoint. * Return the transport connection handle if successful, or NULL for failure. */ etm_xport_conn_t etm_xport_open(fmd_hdl_t *hdl, etm_xport_hdl_t tlhdl); /* * Close a connection. * Return zero if successful, or nonzero for failure. */ int etm_xport_close(fmd_hdl_t *hdl, etm_xport_conn_t conn); /* * Input/Output */ /* * Try to read byte_cnt bytes from the connection into the given buffer. * Return number of bytes successfully read, or a value < 0 for failure. */ ssize_t etm_xport_read(fmd_hdl_t *hdl, etm_xport_conn_t conn, hrtime_t timeout, void *buf, size_t byte_cnt); /* * Try to write byte_cnt bytes to the connection from the given buffer. * Return number of bytes successfully written, or a value < 0 for failure. */ ssize_t etm_xport_write(fmd_hdl_t *hdl, etm_xport_conn_t conn, hrtime_t timeout, void *buf, size_t byte_cnt); /* * Filter */ #define ETM_XPORT_FILTER_OK (1) /* OK to send/post event */ #define ETM_XPORT_FILTER_DROP (0) /* Do not send/post event */ #define ETM_XPORT_FILTER_ERROR (-1) /* Error */ /* * Make a decision whether or not to send an event to a remote endpoint. * Return ETM_XPORT_FILTER_OK, ETM_XPORT_FILTER_DROP, or ETM_XPORT_FILTER_ERROR * and set errno for failure. */ int etm_xport_send_filter(fmd_hdl_t *hdl, nvlist_t *event, const char *dest); /* * Make a decision whether or not to post an event to FMD. * Return ETM_XPORT_FILTER_OK, ETM_XPORT_FILTER_DROP, or ETM_XPORT_FILTER_ERROR * and set errno for failure. */ int etm_xport_post_filter(fmd_hdl_t *hdl, nvlist_t *event, const char *src); #ifdef __cplusplus } #endif #endif /* _ETM_XPORT_API_H */ # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # # eft.so (the eversholt DE) # # Copyright (c) 2018, Joyent, Inc. .KEEP_STATE: EVERSRCDIR=../../../eversholt/common MODULE = eft CLASS = common DMOD = $(MODULE).so YSRCS=escparse.y SRCS = alloc.c check.c config.c eft.c eftread.c esclex.c eval.c evnv.c \ fme.c iexpr.c io.c ipath.c itree.c lut.c literals.c out.c platform.c \ ptree.c stable.c stats.c tree.c DMOD_SRCS = eft_mdb.c include ../../Makefile.plugin CPPFLAGS += -DFMAPLUGIN -I$(EVERSRCDIR) -I. CERRWARN += $(CNOWARN_UNINIT) CERRWARN += -Wno-switch CERRWARN += -Wno-parentheses CERRWARN += -Wno-implicit-function-declaration # Hammerhead: Suppress pointer/int cast warnings in legacy code CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # because of labels from yacc escparse.o : CERRWARN += -Wno-unused-label # not linted SMATCH=off $(PROG) : LDFLAGS += -R/usr/lib/fm $(PROG) : LDLIBS += -L$(ROOTLIB)/fm -ltopo CLEANFILES += y.tab.h y.tab.c esclex.o: escparse.o %.o: $(EVERSRCDIR)/%.c $(COMPILE.c) $< $(CTFCONVERT_O) %.ln: $(EVERSRCDIR)/%.c $(LINT.c) -c $< escparse.o: $(EVERSRCDIR)/escparse.y $(YACC) -dt $(EVERSRCDIR)/escparse.y $(COMPILE.c) -DYYDEBUG -c -o $@ y.tab.c $(CTFCONVERT_O) # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. This file is usr/src/cmd/fma/modules/common/eversholt/README. The files in this directory build the "eft.so" plugin to the fault management daemon (fmd). This plugin is a diagnosis engine driven by one or more eversholt fault tree (.eft) files. eft.c implements the diagnosis engine entry points expected by the fmd. The diagnosis engine is considered platform-neutral code and is being used on other, non-Solaris platforms. The platform specific services it requires are either provided by the fmd interfaces, or by the routines in platform.c in this directory. Most of the files that go into building eft.so are shared with the eversholt compiler. Those files are pulled in by the Makefile in this directory. They live in: usr/src/cmd/fma/eversholt/common When running under Solaris, eft.so uses the hardware topology library (libtopo) to collect configuration information. /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * alloc.c -- memory allocation wrapper functions, for eft.so FMD module * */ #include #include #include #include #include "alloc.h" #include "out.h" #include "stats.h" extern fmd_hdl_t *Hdl; /* handle from eft.c */ /* room to store size, possibly more to maintain alignment for long longs */ #define HDRSIZ sizeof (long long) static struct stats *Malloctotal; static struct stats *Freetotal; static struct stats *Malloccount; static struct stats *Freecount; static int totalcount; void alloc_init(void) { Malloctotal = stats_new_counter("alloc.total", "bytes allocated", 1); Freetotal = stats_new_counter("free.total", "bytes freed", 1); Malloccount = stats_new_counter("alloc.calls", "alloc calls", 1); Freecount = stats_new_counter("free.calls", "free calls", 1); } void alloc_fini(void) { struct stats *mt, *ft, *mc, *fc; mt = Malloctotal; ft = Freetotal; mc = Malloccount; fc = Freecount; Malloctotal = NULL; Freetotal = NULL; Malloccount = NULL; Freecount = NULL; stats_delete(mt); stats_delete(ft); stats_delete(mc); stats_delete(fc); } /* * alloc_malloc -- a malloc() with checks * * this routine is typically called via the MALLOC() macro in alloc.h */ /*ARGSUSED*/ void * alloc_malloc(size_t nbytes, const char *fname, int line) { char *retval; ASSERT(nbytes > 0); retval = fmd_hdl_alloc(Hdl, nbytes + HDRSIZ, FMD_SLEEP); /* retval can't be NULL since fmd_hdl_alloc() sleeps for memory */ bcopy((void *)&nbytes, (void *)retval, sizeof (nbytes)); retval += HDRSIZ; if (Malloctotal) stats_counter_add(Malloctotal, nbytes); if (Malloccount) stats_counter_bump(Malloccount); totalcount += nbytes + HDRSIZ; return ((void *)retval); } /* * alloc_realloc -- a realloc() with checks * * this routine is typically called via the REALLOC() macro in alloc.h */ void * alloc_realloc(void *ptr, size_t nbytes, const char *fname, int line) { void *retval = alloc_malloc(nbytes, fname, line); if (ptr != NULL) { size_t osize; bcopy((void *)((char *)ptr - HDRSIZ), (void *)&osize, sizeof (osize)); /* now we have the new memory, copy in the old contents */ bcopy(ptr, retval, (osize < nbytes) ? osize : nbytes); /* don't need the old memory anymore */ alloc_free((char *)ptr, fname, line); } return (retval); } /* * alloc_strdup -- a strdup() with checks * * this routine is typically called via the STRDUP() macro in alloc.h */ char * alloc_strdup(const char *ptr, const char *fname, int line) { char *retval = alloc_malloc(strlen(ptr) + 1, fname, line); (void) strcpy(retval, ptr); return (retval); } /* * alloc_free -- a free() with checks * * this routine is typically called via the FREE() macro in alloc.h */ /*ARGSUSED*/ void alloc_free(void *ptr, const char *fname, int line) { size_t osize; ASSERT(ptr != NULL); bcopy((void *)((char *)ptr - HDRSIZ), (void *)&osize, sizeof (osize)); /* nothing to check in this version */ fmd_hdl_free(Hdl, (char *)ptr - HDRSIZ, osize + HDRSIZ); if (Freetotal) stats_counter_add(Freetotal, osize); if (Freecount) stats_counter_bump(Freecount); totalcount -= osize + HDRSIZ; } int alloc_total() { return (totalcount); } /* * variants that don't maintain size in header - saves space */ void * alloc_xmalloc(size_t nbytes) { char *retval; ASSERT(nbytes > 0); retval = fmd_hdl_alloc(Hdl, nbytes, FMD_SLEEP); if (Malloctotal) stats_counter_add(Malloctotal, nbytes); if (Malloccount) stats_counter_bump(Malloccount); totalcount += nbytes; return ((void *)retval); } void alloc_xfree(void *ptr, size_t size) { ASSERT(ptr != NULL); fmd_hdl_free(Hdl, (char *)ptr, size); if (Freetotal) stats_counter_add(Freetotal, size); if (Freecount) stats_counter_bump(Freecount); totalcount -= size; } /* * 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. */ /* * config.c -- system configuration cache module * * this module caches the system configuration in a format useful * to eft. the information is loaded into this module by * config_snapshot() at the beginning of each FME. config_snapshot() * calls the platform-specific platform_config_snapshot() to get * the configuration information loaded up. */ #include #include #include #include #include #include #include "alloc.h" #include "out.h" #include "literals.h" #include "stable.h" #include "lut.h" #include "tree.h" #include "itree.h" #include "ipath.h" #include "ptree.h" #include "eval.h" #include "config.h" #include "config_impl.h" #include "fme.h" #include "platform.h" static const char *config_lastcomp; /* * newcnode -- local function to allocate new config node */ static struct config * newcnode(const char *s, int num) { struct config *retval; retval = MALLOC(sizeof (struct config)); retval->s = s; retval->num = num; retval->next = NULL; retval->props = NULL; retval->child = retval->parent = NULL; return (retval); } /* * If we need to cache certain types of nodes for reverse look-up or * somesuch, do it here. Currently we need to cache nodes representing * cpus. */ static void config_node_cache(struct cfgdata *cdata, struct config *n) { if (n->s != stable("cpu")) return; cdata->cpucache = lut_add(cdata->cpucache, (void *)n->num, (void *)n, NULL); } /* * config_lookup -- lookup/add components in configuration cache */ struct config * config_lookup(struct config *croot, char *path, int add) { char *pathbegin = path; struct config *parent = croot; struct config *cp; struct config *lastcp; struct config *newnode; char *thiscom; /* this component */ char *nextcom; /* next component */ char svdigit; int len; int num; const char *s; int exists; if (parent == NULL) out(O_DIE, "uninitialized configuration"); while (*path) { if ((nextcom = strchr(path, '/')) != NULL) *nextcom = '\0'; if ((len = strlen(path)) == 0) out(O_DIE, "config_lookup: zero length component"); /* start at end of string and work backwards */ thiscom = &path[len - 1]; if (!isdigit(*thiscom)) out(O_DIE, "config_lookup: " "component \"%s\" has no number following it", path); while (thiscom > path && isdigit(*thiscom)) thiscom--; if (thiscom == path && isdigit(*thiscom)) out(O_DIE, "config_lookup: " "component \"%s\" has no name part", path); thiscom++; /* move to first numeric character */ num = atoi(thiscom); svdigit = *thiscom; *thiscom = '\0'; s = stable(path); if (add) config_lastcomp = s; *thiscom = svdigit; if (nextcom != NULL) *nextcom++ = '/'; /* now we have s & num, figure out if it exists already */ exists = 0; lastcp = NULL; for (cp = parent->child; cp; lastcp = cp, cp = cp->next) if (cp->s == s && cp->num == num) { exists = 1; parent = cp; } if (!exists) { /* creating new node */ if (!add) { /* * indicate component not found by copying * it to path (allows better error messages * in the caller). */ (void) strcpy(pathbegin, s); return (NULL); } newnode = newcnode(s, num); if (lastcp) lastcp->next = newnode; else parent->child = newnode; newnode->parent = parent; parent = newnode; } if (nextcom == NULL) return (parent); /* all done */ /* move on to next component */ path = nextcom; } return (parent); } /* * addconfigprop -- add a config prop to a config cache entry */ static void addconfigprop(const char *lhs, struct node *rhs, void *arg) { struct config *cp = (struct config *)arg; ASSERT(cp != NULL); ASSERT(lhs != NULL); ASSERT(rhs != NULL); ASSERT(rhs->t == T_QUOTE); config_setprop(cp, lhs, STRDUP(rhs->u.quote.s)); } /* * addconfig -- add a config from parse tree to given configuration cache */ /*ARGSUSED*/ static void addconfig(struct node *lhs, struct node *rhs, void *arg) { struct config *parent = (struct config *)arg; struct config *cp; const char *s; int num; struct config *lastcp; struct config *newnode; int exists; struct lut *lutp; ASSERT(rhs->t == T_CONFIG); lutp = rhs->u.stmt.lutp; rhs = rhs->u.stmt.np; while (rhs != NULL) { ASSERT(rhs->t == T_NAME); ASSERT(rhs->u.name.child->t == T_NUM); s = rhs->u.name.s; num = rhs->u.name.child->u.ull; /* now we have s & num, figure out if it exists already */ exists = 0; lastcp = NULL; for (cp = parent->child; cp; lastcp = cp, cp = cp->next) if (cp->s == s && cp->num == num) { exists = 1; parent = cp; } if (!exists) { /* creating new node */ newnode = newcnode(s, num); if (lastcp) lastcp->next = newnode; else parent->child = newnode; newnode->parent = parent; parent = newnode; } /* move on to next component */ rhs = rhs->u.name.next; } /* add configuration properties */ lut_walk(lutp, (lut_cb)addconfigprop, (void *)parent); } /* * config_cook -- convert raw config strings to eft internal representation */ void config_cook(struct cfgdata *cdata) { struct config *newnode; char *cfgstr, *equals; const char *pn, *sv; char *pv; const char *ptr; extern struct lut *Usedprops; extern struct lut *Usednames; cdata->cooked = newcnode(NULL, 0); if ((cfgstr = cdata->begin) == cdata->nextfree) { out(O_ALTFP|O_VERB, "Platform provided no config data."); goto eftcfgs; } /* * add the following properties to the "usedprops" table as they * are used internally by eft */ ptr = stable("module"); Usedprops = lut_add(Usedprops, (void *)ptr, (void *)ptr, NULL); ptr = stable("resource"); Usedprops = lut_add(Usedprops, (void *)ptr, (void *)ptr, NULL); ptr = stable("serial"); Usedprops = lut_add(Usedprops, (void *)ptr, (void *)ptr, NULL); out(O_ALTFP|O_VERB3, "Raw config data follows:"); out(O_ALTFP|O_VERB3|O_NONL, "nextfree is %p\n%p ", (void *)cdata->nextfree, (void *)cfgstr); while (cfgstr < cdata->nextfree) { if (!*cfgstr) out(O_ALTFP|O_VERB3|O_NONL, "\n%p ", (void *)(cfgstr + 1)); else out(O_ALTFP|O_VERB3|O_NONL, "%c", *cfgstr); cfgstr++; } out(O_ALTFP|O_VERB3, NULL); cfgstr = cdata->begin; while (cfgstr < cdata->nextfree) { while (*cfgstr == '/' && cfgstr < cdata->nextfree) { out(O_ALTFP|O_VERB3, "next string (%p) is %s", (void *)cfgstr, cfgstr); /* skip the initial slash from libtopo */ newnode = config_lookup(cdata->cooked, cfgstr + 1, 1); /* * Note we'll only cache nodes that have * properties on them. Intermediate nodes * will have been added to the config tree, * but we don't have easy means of accessing * them except if we climb the tree from this * newnode to the root. * * Luckily, the nodes we care to cache * (currently just cpus) always have some * properties attached to them * so we don't bother climbing the tree. */ config_node_cache(cdata, newnode); cfgstr += strlen(cfgstr) + 1; } if (cfgstr >= cdata->nextfree) break; out(O_ALTFP|O_VERB3, "next string (%p) is %s", (void *)cfgstr, cfgstr); if ((equals = strchr(cfgstr, '=')) == NULL) { out(O_ALTFP|O_VERB3, "raw config data bad (%p); " "property missing equals.\n", (void *)cfgstr); break; } *equals = '\0'; pn = stable(cfgstr); /* * only actually add the props if the rules use them (saves * memory) */ if ((lut_lookup(Usedprops, (void *)pn, NULL) != NULL || strncmp(pn, "serd_", 5) == 0) && lut_lookup(Usednames, (void *)config_lastcomp, NULL) != NULL) { pv = STRDUP(equals + 1); out(O_ALTFP|O_VERB3, "add prop (%s) val %p", pn, (void *)pv); config_setprop(newnode, pn, pv); } /* * If this property is a device path, tp or devid, cache it * for quick lookup. */ if (config_lastcomp == stable(SCSI_DEVICE) || config_lastcomp == stable(SMP_DEVICE)) { /* * we can't get ereports on SCSI_DEVICE or SMP_DEVICE * nodes, so don't cache. */ out(O_ALTFP|O_VERB3, "not caching %s for %s", pn, config_lastcomp); } else if (pn == stable(TOPO_IO_DEV)) { sv = stable(equals + 1); out(O_ALTFP|O_VERB3, "caching dev %s", sv); cdata->devcache = lut_add(cdata->devcache, (void *)sv, (void *)newnode, NULL); } else if (pn == stable(TOPO_IO_DEVID) || pn == stable(TOPO_PROP_SES_DEVID) || pn == stable(TOPO_PROP_SMP_DEVID)) { sv = stable(equals + 1); out(O_ALTFP|O_VERB3, "caching devid %s", sv); cdata->devidcache = lut_add(cdata->devidcache, (void *)sv, (void *)newnode, NULL); } else if (pn == stable(TOPO_STORAGE_TARGET_PORT_L0IDS)) { /* * This was stored as a set of space-separated strings. * Find each string in turn and add to the lut. Then if * a ereport comes in with a target-path matching any * of the strings we will match it. */ char *x, *y = equals; while (y != NULL) { x = y + 1; y = strchr(x, ' '); if (y != NULL) *y = '\0'; sv = stable(x); out(O_ALTFP|O_VERB3, "caching tp %s", sv); cdata->tpcache = lut_add(cdata->tpcache, (void *)sv, (void *)newnode, NULL); if (y != NULL) *y = ' '; } } *equals = '='; cfgstr += strlen(cfgstr) + 1; } eftcfgs: /* now run through Configs table, adding to config cache */ lut_walk(Configs, (lut_cb)addconfig, (void *)cdata->cooked); } /* * config_snapshot -- gather a snapshot of the current configuration */ struct cfgdata * config_snapshot(void) { struct cfgdata *rawcfg; rawcfg = platform_config_snapshot(); config_cook(rawcfg); return (rawcfg); } /* * prop_destructor -- free a prop value */ /*ARGSUSED*/ static void prop_destructor(void *left, void *right, void *arg) { FREE(right); } /* * structconfig_free -- free a struct config pointer and all its relatives */ void structconfig_free(struct config *cp) { if (cp == NULL) return; structconfig_free(cp->child); structconfig_free(cp->next); lut_free(cp->props, prop_destructor, NULL); FREE(cp); } /* * config_free -- free a configuration snapshot */ void config_free(struct cfgdata *cp) { if (cp == NULL) return; if (--cp->raw_refcnt == 0) { if (cp->devcache != NULL) lut_free(cp->devcache, NULL, NULL); cp->devcache = NULL; if (cp->tpcache != NULL) lut_free(cp->tpcache, NULL, NULL); cp->tpcache = NULL; if (cp->devidcache != NULL) lut_free(cp->devidcache, NULL, NULL); cp->devidcache = NULL; if (cp->cpucache != NULL) lut_free(cp->cpucache, NULL, NULL); cp->cpucache = NULL; if (cp->begin != NULL) FREE(cp->begin); FREE(cp); } } /* * config_next -- get the "next" config node */ struct config * config_next(struct config *cp) { ASSERT(cp != NULL); return ((struct config *)((struct config *)cp)->next); } /* * config_child -- get the "child" of a config node */ struct config * config_child(struct config *cp) { ASSERT(cp != NULL); return ((struct config *)((struct config *)cp)->child); } /* * config_parent -- get the "parent" of a config node */ struct config * config_parent(struct config *cp) { ASSERT(cp != NULL); return ((struct config *)((struct config *)cp)->parent); } /* * config_setprop -- add a property to a config node */ void config_setprop(struct config *cp, const char *propname, const char *propvalue) { const char *pn = stable(propname); cp->props = lut_add(cp->props, (void *)pn, (void *)propvalue, NULL); } /* * config_getprop -- lookup a config property */ const char * config_getprop(struct config *cp, const char *propname) { return (lut_lookup(cp->props, (void *) stable(propname), NULL)); } /* * config_getcompname -- get the component name of a config node */ void config_getcompname(struct config *cp, char **name, int *inst) { ASSERT(cp != NULL); if (name != NULL) *name = (char *)cp->s; if (inst != NULL) *inst = cp->num; } /* * config_nodeize -- convert the config element represented by cp to struct * node format */ static struct node * config_nodeize(struct config *cp) { struct node *tmpn, *ptmpn; struct node *numn; const char *sname; if (cp == NULL || cp->s == NULL) return (NULL); sname = stable(cp->s); numn = newnode(T_NUM, NULL, 0); numn->u.ull = cp->num; tmpn = tree_name_iterator(tree_name(sname, IT_VERTICAL, NULL, 0), numn); if ((ptmpn = config_nodeize(cp->parent)) == NULL) return (tmpn); return (tree_name_append(ptmpn, tmpn)); } /*ARGSUSED*/ static void prtdevcache(void *lhs, void *rhs, void *arg) { out(O_ALTFP|O_VERB3, "%s -> %p", (char *)lhs, rhs); } /*ARGSUSED*/ static void prtdevidcache(void *lhs, void *rhs, void *arg) { out(O_ALTFP|O_VERB3, "%s -> %p", (char *)lhs, rhs); } /*ARGSUSED*/ static void prttpcache(void *lhs, void *rhs, void *arg) { out(O_ALTFP|O_VERB3, "%s -> %p", (char *)lhs, rhs); } /*ARGSUSED*/ static void prtcpucache(void *lhs, void *rhs, void *arg) { out(O_ALTFP|O_VERB, "%u -> %p", (uint32_t)lhs, rhs); } /* * config_bydev_lookup -- look up the path in our devcache lut. If we find * it return the config path, but as a struct node. */ struct node * config_bydev_lookup(struct cfgdata *fromcfg, const char *path) { struct config *find; struct node *np; out(O_ALTFP|O_VERB3, "Device path cache:"); lut_walk(fromcfg->devcache, (lut_cb)prtdevcache, NULL); if ((find = lut_lookup(fromcfg->devcache, (void *) stable(path), NULL)) == NULL) return (NULL); np = config_nodeize(find); if (np != NULL) { out(O_ALTFP|O_VERB, "Matching config entry:"); ptree_name_iter(O_ALTFP|O_VERB|O_NONL, np); out(O_ALTFP|O_VERB, NULL); } return (np); } /* * config_bydevid_lookup -- look up the path in our DEVIDcache lut. * If we find it return the config path, but as a struct node. */ struct node * config_bydevid_lookup(struct cfgdata *fromcfg, const char *devid) { struct config *find; struct node *np; out(O_ALTFP|O_VERB3, "Device id cache:"); lut_walk(fromcfg->devcache, (lut_cb)prtdevidcache, NULL); if ((find = lut_lookup(fromcfg->devidcache, (void *) stable(devid), NULL)) == NULL) return (NULL); np = config_nodeize(find); if (np != NULL) { out(O_ALTFP|O_VERB, "Matching config entry:"); ptree_name_iter(O_ALTFP|O_VERB|O_NONL, np); out(O_ALTFP|O_VERB, NULL); } return (np); } /* * config_bytp_lookup -- look up the path in our TPcache lut. * If we find it return the config path, but as a struct node. */ struct node * config_bytp_lookup(struct cfgdata *fromcfg, const char *tp) { struct config *find; struct node *np; out(O_ALTFP|O_VERB3, "Device id cache:"); lut_walk(fromcfg->devcache, (lut_cb)prttpcache, NULL); if ((find = lut_lookup(fromcfg->tpcache, (void *) stable(tp), NULL)) == NULL) return (NULL); np = config_nodeize(find); if (np != NULL) { out(O_ALTFP|O_VERB, "Matching config entry:"); ptree_name_iter(O_ALTFP|O_VERB|O_NONL, np); out(O_ALTFP|O_VERB, NULL); } return (np); } /* * config_bycpuid_lookup -- look up the cpu id in our CPUcache lut. * If we find it return the config path, but as a struct node. */ struct node * config_bycpuid_lookup(struct cfgdata *fromcfg, uint32_t id) { struct config *find; struct node *np; out(O_ALTFP|O_VERB, "Cpu cache:"); lut_walk(fromcfg->cpucache, (lut_cb)prtcpucache, NULL); if ((find = lut_lookup(fromcfg->cpucache, (void *)id, NULL)) == NULL) return (NULL); np = config_nodeize(find); if (np != NULL) { out(O_ALTFP|O_VERB3, "Matching config entry:"); ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, np); out(O_ALTFP|O_VERB3, NULL); } return (np); } /* * printprop -- print prop associated with config node */ static void printprop(const char *lhs, const char *rhs, void *arg) { int flags = (int)arg; out(flags, "\t%s=%s", lhs, rhs); } /* * pconf -- internal printing function to recurse through the tree */ static void pconf(int flags, struct config *cp, char *buf, int offset, int limit) { char *sep = "/"; if (offset) sep = "/"; else sep = ""; (void) snprintf(&buf[offset], limit - offset, "%s%s%d", sep, cp->s, cp->num); if (cp->child == NULL) { out(flags, "%s", buf); lut_walk(cp->props, (lut_cb)printprop, (void *)flags); } else pconf(flags, cp->child, buf, strlen(buf), limit); if (cp->next) pconf(flags, cp->next, buf, offset, limit); } /* * config_print -- spew the current configuration cache */ #define MAXCONFLINE 4096 void config_print(int flags, struct config *croot) { char buf[MAXCONFLINE]; if (croot == NULL) out(flags, "empty configuration"); else pconf(flags, croot->child, buf, 0, MAXCONFLINE); } /* * 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. * * config.h -- public definitions for config module * * this module supports management of system configuration information * */ #ifndef _EFT_CONFIG_H #define _EFT_CONFIG_H #include #ifdef __cplusplus extern "C" { #endif #include "tree.h" #include "lut.h" struct cfgdata { int raw_refcnt; /* * The begin field points to the first byte of raw * configuration information and end to the byte past the last * byte where configuration information may be stored. * nextfree points to where the next string may be added. */ char *begin; char *end; char *nextfree; struct config *cooked; struct lut *devcache; struct lut *devidcache; struct lut *tpcache; struct lut *cpucache; }; void structconfig_free(struct config *cp); struct cfgdata *config_snapshot(void); void config_cook(struct cfgdata *cdata); void config_free(struct cfgdata *cdata); struct config *config_lookup(struct config *croot, char *path, int add); struct config *config_next(struct config *cp); struct config *config_child(struct config *cp); struct config *config_parent(struct config *cp); const char *config_getprop(struct config *cp, const char *name); void config_setprop(struct config *cp, const char *name, const char *val); void config_getcompname(struct config *cp, char **name, int *inst); int config_is_connected(struct node *np, struct config *croot, struct evalue *valuep); int config_is_type(struct node *np, struct config *croot, struct evalue *valuep); int config_is_on(struct node *np, struct config *croot, struct evalue *valuep); int config_is_present(struct node *np, struct config *croot, struct evalue *valuep); void config_print(int flags, struct config *croot); struct node *config_bydev_lookup(struct cfgdata *, const char *); struct node *config_bycpuid_lookup(struct cfgdata *, uint32_t); struct node *config_bydevid_lookup(struct cfgdata *, const char *); struct node *config_bytp_lookup(struct cfgdata *, const char *); #ifdef __cplusplus } #endif #endif /* _EFT_CONFIG_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _EFT_CONFIG_IMPL_H #define _EFT_CONFIG_IMPL_H #ifdef __cplusplus extern "C" { #endif /* * private to config.c and mdb module. * * Data structure for storing config. all access to * to this information happens using the config.h interfaces. */ struct config { struct config *next; struct config *child; struct config *parent; const char *s; intptr_t num; struct lut *props; }; #ifdef __cplusplus } #endif #endif /* _EFT_CONFIG_IMPL_H */ /* * 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. */ #include #include #include #include #include #include #include "out.h" #include "stats.h" #include "alloc.h" #include "stable.h" #include "literals.h" #include "lut.h" #include "esclex.h" #include "tree.h" #include "ipath.h" #include "itree.h" #include "iexpr.h" #include "ptree.h" #include "check.h" #include "version.h" #include "fme.h" #include "eval.h" #include "config.h" #include "platform.h" /* * eversholt diagnosis engine (eft.so) main entry points */ fmd_hdl_t *Hdl; /* handle in global for platform.c */ int Debug = 1; /* turn on here and let fmd_hdl_debug() decide if really on */ hrtime_t Hesitate; /* hesitation time in ns */ char *Serd_Override; /* override for Serd engines */ int Verbose; int Estats; int Warn; /* zero -- eft.so should not issue language warnings */ char **Efts; int Max_fme; /* Maximum number of open FMEs */ /* stuff exported by yacc-generated parsers */ extern void yyparse(void); extern int yydebug; extern struct lut *Dicts; extern void literals_init(void); extern void literals_fini(void); struct eftsubr { const char *prefix; void (*hdlr)(fmd_hdl_t *, fmd_event_t *, nvlist_t *, const char *); } eftsubrs[] = { { "ereport.", fme_receive_external_report }, { "list.repaired", fme_receive_repair_list }, { NULL, NULL } }; /*ARGSUSED*/ static void eft_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { struct eftsubr *sp = eftsubrs; while (sp->prefix != NULL) { if (strncmp(class, sp->prefix, strlen(sp->prefix)) == 0) break; sp++; } if (sp->prefix != NULL) { (sp->hdlr)(hdl, ep, nvl, class); } else { out(O_DIE, "eft_recv: event class \"%s\" does not match our " "subscriptions", class); } } /*ARGSUSED*/ static void eft_timeout(fmd_hdl_t *hdl, id_t tid, void *arg) { out(O_ALTFP|O_STAMP, "\neft.so timer %ld fired with arg %p", tid, arg); if (arg == NULL) return; fme_timer_fired(arg, tid); } static void eft_close(fmd_hdl_t *hdl, fmd_case_t *fmcase) { out(O_ALTFP, "eft_close called for case %s", fmd_case_uuid(hdl, fmcase)); fme_close_case(hdl, fmcase); } /* * The "serd_override" property allows the N and T parameters of specified serd * engines to be overridden. The property is a string consisting of one or more * space separated triplets. Each triplet is of the form "name,N,T" where "name" * is the name of the serd engine and N and T are the new paremeters to use. * For example "serd.io.device.nonfatal,5,3h" would set the parameters for the * serd.io.device.nonfatal engine to 5 in 3 hours. */ static const fmd_prop_t eft_props[] = { { "estats", FMD_TYPE_BOOL, "false" }, { "hesitate", FMD_TYPE_INT64, "10000000000" }, { "serd_override", FMD_TYPE_STRING, NULL }, { "verbose", FMD_TYPE_INT32, "0" }, { "warn", FMD_TYPE_BOOL, "false" }, { "status", FMD_TYPE_STRING, NULL }, { "maxfme", FMD_TYPE_INT32, "0" }, { NULL, 0, NULL } }; /*ARGSUSED*/ static void eft_topo_change(fmd_hdl_t *hdl, topo_hdl_t *thp) { fme_receive_topology_change(); } static const fmd_hdl_ops_t eft_ops = { eft_recv, /* fmdo_recv */ eft_timeout, /* fmdo_timeout */ eft_close, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ eft_topo_change /* fmdo_topo_change */ }; #define xstr(s) str(s) #define str(s) #s static const fmd_hdl_info_t fmd_info = { "eft diagnosis engine", xstr(VERSION_MAJOR) "." xstr(VERSION_MINOR), &eft_ops, eft_props }; /* * ename_strdup -- like strdup but ename comes in and class string goes out */ static char * ename_strdup(struct node *np) { struct node *mynp; int len; char *buf; /* calculate length of buffer required */ len = 0; for (mynp = np; mynp; mynp = mynp->u.name.next) len += strlen(mynp->u.name.s) + 1; /* +1 for dot or NULL */ buf = MALLOC(len); buf[0] = '\0'; /* now build the string */ while (np) { (void) strcat(buf, np->u.name.s); np = np->u.name.next; if (np) (void) strcat(buf, "."); } return (buf); } /*ARGSUSED*/ static void dosubscribe(struct node *lhs, struct node *rhs, void *arg) { char *ename = ename_strdup(lhs); fmd_hdl_subscribe(Hdl, ename); FREE(ename); } /*ARGSUSED*/ static void dodiscardprint(struct node *lhs, struct node *rhs, void *arg) { char *ename = (char *)lhs; out(O_VERB, "allow silent discard_if_config_unknown: \"%s\"", ename); } extern struct stats *Filecount; /* * Call all of the _fini() routines to clean up the exiting DE */ void call_finis(void) { platform_free_eft_files(Efts); Efts = NULL; platform_fini(); fme_fini(); itree_fini(); ipath_fini(); iexpr_fini(); istat_fini(); serd_fini(); lex_free(); check_fini(); tree_fini(); lut_fini(); literals_fini(); stable_fini(); stats_fini(); out_fini(); alloc_fini(); } /*ARGSUSED*/ static void doopendict(const char *lhs, void *rhs, void *arg) { out(O_VERB, "opendict: \"%s\"", lhs); fmd_hdl_opendict(Hdl, lhs); } void _fmd_init(fmd_hdl_t *hdl) { fmd_case_t *casep = NULL; int count; char *fname; (void) fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info); /* keep handle for routines like out() which need it */ Hdl = hdl; /* set up out(O_ALTFP) first things so it is available for debug */ alloc_init(); out_init("eft"); if ((fname = fmd_prop_get_string(hdl, "status")) != NULL) { FILE *fp; if ((fp = fopen(fname, "a")) == NULL) { fmd_prop_free_string(hdl, fname); out(O_DIE|O_SYS, "status property file: %s", fname); } (void) setlinebuf(fp); out_altfp(fp); out(O_DEBUG, "appending status changes to \"%s\"", fname); fmd_prop_free_string(hdl, fname); out(O_ALTFP|O_STAMP, "\neft.so startup"); } Estats = fmd_prop_get_int32(hdl, "estats"); stats_init(Estats); stable_init(0); literals_init(); platform_init(); lut_init(); tree_init(); ipath_init(); iexpr_init(); Efts = platform_get_eft_files(); lex_init(Efts, NULL, 0); check_init(); /* * If we read no .eft files, we can't do any * diagnosing, so we just unload ourselves */ if (stats_counter_value(Filecount) == 0) { (void) lex_fini(); call_finis(); fmd_hdl_debug(hdl, "no fault trees provided."); fmd_hdl_unregister(hdl); return; } yyparse(); (void) lex_fini(); tree_report(); if (count = out_errcount()) out(O_DIE, "%d language error%s encountered, exiting.", OUTS(count)); /* subscribe to events we expect to consume */ lut_walk(Ereportenames, (lut_cb)dosubscribe, NULL); lut_walk(Ereportenames_discard, (lut_cb)dodiscardprint, NULL); /* subscribe to repair events so we can clear state on repair */ fmd_hdl_subscribe(hdl, "list.repaired"); /* open dictionaries referenced by all .eft files */ lut_walk(Dicts, (lut_cb)doopendict, (void *)0); Verbose = fmd_prop_get_int32(hdl, "verbose"); Warn = fmd_prop_get_int32(hdl, "warn"); Hesitate = fmd_prop_get_int64(hdl, "hesitate"); Serd_Override = fmd_prop_get_string(hdl, "serd_override"); Max_fme = fmd_prop_get_int32(hdl, "maxfme"); out(O_DEBUG, "initialized, verbose %d warn %d maxfme %d", Verbose, Warn, Max_fme); fme_istat_load(hdl); fme_serd_load(hdl); out(O_VERB, "reconstituting any existing fmes"); while ((casep = fmd_case_next(hdl, casep)) != NULL) { fme_restart(hdl, casep); } } /*ARGSUSED*/ void _fmd_fini(fmd_hdl_t *hdl) { call_finis(); } # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" # # fmd configuration file for the eft.so diagnosis engine. # # The following configuration options are valid: # # setprop estats true # # Enables "extended stats" visible via "fmstat -m eft". # Useful only when doing performance work on eft.so. # # setprop status # # Tracks status of internal fault management exercises # to the given file each time the status changes. # Dumps an even larger amount of information if verbose # is also set. Note that every ereport received and # every timer that expires will cause the file to grow. # # setprop verbose # # Increase debugging verbosity to (1, 2, or 3). # # setprop warn true # # Turns on EFT file language warnings. Useful only # in very obscure debugging cases. # # setprop maxfme # # Sets the maximum number of open fault management exercises. # The value "0" (zero) specifies no limit. # dictionary SUNOS /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2019 Joyent, Inc. */ #include #include #include #include "ipath_impl.h" #include "lut_impl.h" #include "config_impl.h" #include "stats_impl.h" #define LUT_SIZE_INIT 300 #define LUT_SIZE_INCR 100 struct lut_cp { uintptr_t lutcp_addr; struct lut lutcp_lut; }; #define LCPSZ sizeof (struct lut_cp) struct lut_dump_desc { struct lut_cp *ld_array; int ld_arraysz; int ld_nents; }; static void lut_dump_array_alloc(struct lut_dump_desc *lddp) { struct lut_cp *new; if (lddp->ld_array == NULL) { lddp->ld_arraysz = LUT_SIZE_INIT; lddp->ld_array = mdb_zalloc(LUT_SIZE_INIT * LCPSZ, UM_SLEEP); return; } new = mdb_zalloc((lddp->ld_arraysz + LUT_SIZE_INCR) * LCPSZ, UM_SLEEP); bcopy(lddp->ld_array, new, lddp->ld_arraysz * LCPSZ); mdb_free(lddp->ld_array, lddp->ld_arraysz * LCPSZ); lddp->ld_array = new; lddp->ld_arraysz += LUT_SIZE_INCR; } static void lut_dump_array_free(struct lut_dump_desc *lddp) { if (lddp->ld_array != NULL) { mdb_free(lddp->ld_array, lddp->ld_arraysz * LCPSZ); lddp->ld_array = NULL; } } static void lut_collect_addent(uintptr_t addr, struct lut *ent, struct lut_dump_desc *lddp) { struct lut_cp *lcp; if (lddp->ld_nents == lddp->ld_arraysz) lut_dump_array_alloc(lddp); lcp = &lddp->ld_array[lddp->ld_nents++]; lcp->lutcp_addr = addr; bcopy(ent, &lcp->lutcp_lut, sizeof (struct lut)); } static int eft_lut_walk(uintptr_t root, struct lut_dump_desc *lddp) { struct lut lutent; if (root) { if (mdb_vread(&lutent, sizeof (struct lut), root) != sizeof (struct lut)) { mdb_warn("failed to read struct lut at %p", root); return (WALK_ERR); } if (eft_lut_walk((uintptr_t)lutent.lut_left, lddp) != WALK_NEXT) return (WALK_ERR); lut_collect_addent(root, &lutent, lddp); if (eft_lut_walk((uintptr_t)lutent.lut_right, lddp) != WALK_NEXT) return (WALK_ERR); } return (WALK_NEXT); } static int lut_collect(uintptr_t addr, struct lut_dump_desc *lddp) { lut_dump_array_alloc(lddp); if (eft_lut_walk(addr, lddp) != WALK_NEXT) { lut_dump_array_free(lddp); return (WALK_ERR); } else { return (WALK_NEXT); /* caller must free dump array */ } } static int lut_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("lut walker requires a lut table address\n"); return (WALK_ERR); } wsp->walk_data = mdb_zalloc(sizeof (struct lut_dump_desc), UM_SLEEP); wsp->walk_arg = 0; if (lut_collect(wsp->walk_addr, wsp->walk_data) == WALK_NEXT) { return (WALK_NEXT); } else { mdb_warn("failed to suck in full lut\n"); mdb_free(wsp->walk_data, sizeof (struct lut_dump_desc)); return (WALK_ERR); } } static int lut_walk_step(mdb_walk_state_t *wsp) { struct lut_dump_desc *lddp = wsp->walk_data; int *ip = (int *)&wsp->walk_arg; struct lut_cp *lcp = &lddp->ld_array[*ip]; if (*ip == lddp->ld_nents) return (WALK_DONE); ++*ip; return (wsp->walk_callback(lcp->lutcp_addr, &lcp->lutcp_lut, wsp->walk_cbdata)); } static int ipath_walk_init(mdb_walk_state_t *wsp) { struct ipath *ipath; ipath = mdb_alloc(sizeof (struct ipath), UM_SLEEP); if (mdb_vread((void *)ipath, sizeof (struct ipath), wsp->walk_addr) != sizeof (struct ipath)) { mdb_warn("failed to read struct ipath at %p", wsp->walk_addr); return (WALK_ERR); } wsp->walk_data = (void *)ipath; if (ipath->s == NULL) return (WALK_DONE); else return (WALK_NEXT); } static void ipath_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct ipath)); } static int ipath_walk_step(mdb_walk_state_t *wsp) { int status; struct ipath *ipath = (struct ipath *)wsp->walk_data; struct ipath *ip = (struct ipath *)wsp->walk_addr; if (ip == NULL || ipath->s == NULL) return (WALK_DONE); status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(ip + 1); if (mdb_vread(wsp->walk_data, sizeof (struct ipath), wsp->walk_addr) != sizeof (struct ipath)) { mdb_warn("failed to read struct ipath at %p", wsp->walk_addr); return (WALK_ERR); } return (status); } static void lut_walk_fini(mdb_walk_state_t *wsp) { struct lut_dump_desc *lddp = wsp->walk_data; lut_dump_array_free(lddp); mdb_free(lddp, sizeof (struct lut_dump_desc)); } /*ARGSUSED*/ static int ipath_node(uintptr_t addr, const void *data, void *arg) { struct ipath *ipath = (struct ipath *)data; char buf[128]; if (mdb_readstr(buf, (size_t)sizeof (buf), (uintptr_t)ipath->s) < 0) (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>", ipath->s); mdb_printf("/%s=%d", buf, ipath->i); return (DCMD_OK); } /*ARGSUSED*/ static int ipath(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { if (argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) addr = mdb_get_dot(); if (mdb_pwalk("eft_ipath", ipath_node, NULL, addr) != 0) return (DCMD_ERR); return (DCMD_OK); } /*ARGSUSED*/ static int eft_count(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct lut lut; struct istat_entry istat_entry; struct stats count; GElf_Sym sym; char buf[128]; if (argc) return (DCMD_USAGE); if (!(flags & DCMD_ADDRSPEC)) { if (mdb_lookup_by_obj(MDB_OBJ_EVERY, "Istats", &sym) == -1 || sym.st_size != sizeof (addr)) return (DCMD_ERR); if (mdb_vread(&addr, sizeof (addr), (uintptr_t)sym.st_value) != sizeof (addr)) return (DCMD_ERR); if (addr == 0) return (DCMD_OK); if (mdb_pwalk_dcmd("lut", "eft_count", argc, argv, addr) != 0) return (DCMD_ERR); return (DCMD_OK); } if (mdb_vread(&lut, sizeof (struct lut), addr) != sizeof (struct lut)) { mdb_warn("failed to read struct lut at %p", addr); return (DCMD_ERR); } if (mdb_vread(&istat_entry, sizeof (struct istat_entry), (uintptr_t)lut.lut_lhs) != sizeof (struct istat_entry)) { mdb_warn("failed to read struct istat_entry at %p", addr); return (DCMD_ERR); } if (mdb_vread(&count, sizeof (struct stats), (uintptr_t)lut.lut_rhs) != sizeof (struct stats)) { mdb_warn("failed to read struct stats at %p", addr); return (DCMD_ERR); } if (mdb_readstr(buf, (size_t)sizeof (buf), (uintptr_t)istat_entry.ename) < 0) (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>", istat_entry.ename); mdb_printf("%s@", buf); (void) ipath((uintptr_t)istat_entry.ipath, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" %d\n", count.fmd_stats.fmds_value.i32); return (DCMD_OK); } /*ARGSUSED*/ static int eft_time(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { unsigned long long val; unsigned long long ull; int opt_p = 0; if (!(flags & DCMD_ADDRSPEC)) addr = mdb_get_dot(); ull = addr; if (argc) { if (mdb_getopts(argc, argv, 'l', MDB_OPT_UINT64, &ull, 'p', MDB_OPT_SETBITS, TRUE, &opt_p, MDB_OPT_UINT64, NULL) != argc) { return (DCMD_USAGE); } } if (opt_p) { if (mdb_vread(&ull, sizeof (ull), addr) != sizeof (ull)) { mdb_warn("failed to read timeval at %p", addr); return (DCMD_ERR); } } #define NOREMAINDER(den, num, val) (((val) = ((den) / (num))) * (num) == (den)) if (ull == 0) mdb_printf("0us"); else if (ull >= TIMEVAL_EVENTUALLY) mdb_printf("infinity"); else if (NOREMAINDER(ull, 1000000000ULL*60*60*24*365, val)) mdb_printf("%lluyear%s", val, (val == 1) ? "" : "s"); else if (NOREMAINDER(ull, 1000000000ULL*60*60*24*30, val)) mdb_printf("%llumonth%s", val, (val == 1) ? "" : "s"); else if (NOREMAINDER(ull, 1000000000ULL*60*60*24*7, val)) mdb_printf("%lluweek%s", val, (val == 1) ? "" : "s"); else if (NOREMAINDER(ull, 1000000000ULL*60*60*24, val)) mdb_printf("%lluday%s", val, (val == 1) ? "" : "s"); else if (NOREMAINDER(ull, 1000000000ULL*60*60, val)) mdb_printf("%lluhour%s", val, (val == 1) ? "" : "s"); else if (NOREMAINDER(ull, 1000000000ULL*60, val)) mdb_printf("%lluminute%s", val, (val == 1) ? "" : "s"); else if (NOREMAINDER(ull, 1000000000ULL, val)) mdb_printf("%llusecond%s", val, (val == 1) ? "" : "s"); else if (NOREMAINDER(ull, 1000000ULL, val)) mdb_printf("%llums", val); else if (NOREMAINDER(ull, 1000ULL, val)) mdb_printf("%lluus", val); else mdb_printf("%lluns", ull); return (DCMD_OK); } /*ARGSUSED*/ static int eft_node(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct node node; int opt_v = 0; char buf[128]; if (!(flags & DCMD_ADDRSPEC)) addr = mdb_get_dot(); if (argc) { if (mdb_getopts(argc, argv, 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc) { return (DCMD_USAGE); } } if (addr == 0) return (DCMD_OK); if (mdb_vread(&node, sizeof (node), addr) != sizeof (node)) { mdb_warn("failed to read struct node at %p", addr); return (DCMD_ERR); } if (opt_v) { if (mdb_readstr(buf, (size_t)sizeof (buf), (uintptr_t)node.file) < 0) (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>", node.file); mdb_printf("%s len %d\n", buf, node.line); } switch (node.t) { case T_NOTHING: /* used to keep going on error cases */ mdb_printf("nothing"); break; case T_NAME: /* identifiers, sometimes chained */ if (mdb_readstr(buf, (size_t)sizeof (buf), (uintptr_t)node.u.name.s) < 0) (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>", node.u.name.s); mdb_printf("%s", buf); if (node.u.name.cp) { struct config cp; if (mdb_vread(&cp, sizeof (cp), (uintptr_t)node.u.name.cp) != sizeof (cp)) { mdb_warn("failed to read struct config at %p", node.u.name.cp); return (DCMD_ERR); } mdb_printf("%d", cp.num); } else if (node.u.name.it == IT_HORIZONTAL) { if (node.u.name.child && !node.u.name.childgen) { mdb_printf("<"); (void) eft_node((uintptr_t)node.u.name.child, DCMD_ADDRSPEC, 0, NULL); mdb_printf(">"); } else { mdb_printf("<> "); } } else if (node.u.name.child) { mdb_printf("["); (void) eft_node((uintptr_t)node.u.name.child, DCMD_ADDRSPEC, 0, NULL); mdb_printf("]"); } if (node.u.name.next) { if (node.u.name.it == IT_ENAME) mdb_printf("."); else mdb_printf("/"); (void) eft_node((uintptr_t)node.u.name.next, DCMD_ADDRSPEC, 0, NULL); } break; case T_GLOBID: /* globals (e.g. $a) */ if (mdb_readstr(buf, (size_t)sizeof (buf), (uintptr_t)node.u.globid.s) < 0) (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>", node.u.globid.s); mdb_printf("$%s", buf); break; case T_EVENT: /* class@path{expr} */ (void) eft_node((uintptr_t)node.u.event.ename, DCMD_ADDRSPEC, 0, NULL); mdb_printf("@"); (void) eft_node((uintptr_t)node.u.event.epname, DCMD_ADDRSPEC, 0, NULL); if (node.u.event.eexprlist) { mdb_printf(" { "); (void) eft_node((uintptr_t)node.u.event.eexprlist, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" }"); } break; case T_ENGINE: /* upset threshold engine (e.g. SERD) */ mdb_printf("engine "); (void) eft_node((uintptr_t)node.u.event.ename, DCMD_ADDRSPEC, 0, NULL); break; case T_ASRU: /* ASRU declaration */ mdb_printf("asru "); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } break; case T_FRU: /* FRU declaration */ mdb_printf("fru "); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } break; case T_TIMEVAL: /* num w/time suffix (ns internally) */ { mdb_arg_t mdb_arg[2]; mdb_arg[0].a_type = MDB_TYPE_STRING; mdb_arg[0].a_un.a_str = "-l"; mdb_arg[1].a_type = MDB_TYPE_IMMEDIATE; mdb_arg[1].a_un.a_val = node.u.ull; (void) eft_time((uintptr_t)0, 0, 2, mdb_arg); break; } case T_NUM: /* num (ull internally) */ mdb_printf("%llu", node.u.ull); break; case T_QUOTE: /* quoted string */ if (mdb_readstr(buf, (size_t)sizeof (buf), (uintptr_t)node.u.quote.s) < 0) (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>", node.u.quote.s); mdb_printf("\"%s\"", buf); break; case T_FUNC: /* func(arglist) */ if (mdb_readstr(buf, (size_t)sizeof (buf), (uintptr_t)node.u.func.s) < 0) (void) mdb_snprintf(buf, (size_t)sizeof (buf), "<%p>", node.u.func.s); mdb_printf("%s(", buf); (void) eft_node((uintptr_t)node.u.func.arglist, DCMD_ADDRSPEC, 0, NULL); mdb_printf(")"); break; case T_NVPAIR: /* name=value pair in decl */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" = "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_ASSIGN: /* assignment statement */ mdb_printf("("); (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" = "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); mdb_printf(")"); break; case T_CONDIF: /* a and T_CONDELSE in (a ? b : c ) */ mdb_printf("("); (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" ? "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); mdb_printf(")"); break; case T_CONDELSE: /* lists b and c in (a ? b : c ) */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" : "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_NOT: /* boolean ! operator */ mdb_printf("!"); (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); break; case T_AND: /* boolean && operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" && "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_OR: /* boolean || operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" || "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_EQ: /* boolean == operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" == "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_NE: /* boolean != operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" != "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_SUB: /* integer - operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" - "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_ADD: /* integer + operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" + "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_MUL: /* integer * operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" * "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_DIV: /* integer / operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" / "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_MOD: /* integer % operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" % "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_LT: /* boolean < operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" < "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_LE: /* boolean <= operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" <= "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_GT: /* boolean > operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" > "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_GE: /* boolean >= operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" >= "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_BITAND: /* bitwise & operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" & "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_BITOR: /* bitwise | operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" | "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_BITXOR: /* bitwise ^ operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" ^ "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_BITNOT: /* bitwise ~ operator */ mdb_printf(" ~"); (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); break; case T_LSHIFT: /* bitwise << operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" << "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_RSHIFT: /* bitwise >> operator */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(" >> "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_ARROW: /* lhs (N)->(K) rhs */ (void) eft_node((uintptr_t)node.u.arrow.lhs, DCMD_ADDRSPEC, 0, NULL); if (node.u.arrow.nnp) { mdb_printf("("); (void) eft_node((uintptr_t)node.u.arrow.nnp, DCMD_ADDRSPEC, 0, NULL); mdb_printf(")"); } mdb_printf("->"); if (node.u.arrow.knp) { mdb_printf("("); (void) eft_node((uintptr_t)node.u.arrow.knp, DCMD_ADDRSPEC, 0, NULL); mdb_printf(")"); } (void) eft_node((uintptr_t)node.u.arrow.rhs, DCMD_ADDRSPEC, 0, NULL); break; case T_LIST: /* comma-separated list */ (void) eft_node((uintptr_t)node.u.expr.left, DCMD_ADDRSPEC, 0, NULL); mdb_printf(", "); (void) eft_node((uintptr_t)node.u.expr.right, DCMD_ADDRSPEC, 0, NULL); break; case T_FAULT: /* fault declaration */ mdb_printf("fault."); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } break; case T_UPSET: /* upset declaration */ mdb_printf("upset."); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } break; case T_DEFECT: /* defect declaration */ mdb_printf("defect."); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } break; case T_ERROR: /* error declaration */ mdb_printf("error."); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } break; case T_EREPORT: /* ereport declaration */ mdb_printf("ereport."); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } break; case T_SERD: /* SERD engine declaration */ mdb_printf("serd."); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } else if (node.u.stmt.lutp) { if (mdb_pwalk_dcmd("lut", "eft_node", 0, NULL, (uintptr_t)node.u.stmt.lutp) != 0) return (DCMD_ERR); } break; case T_STAT: /* STAT engine declaration */ mdb_printf("stat."); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } else if (node.u.stmt.lutp) { if (mdb_pwalk_dcmd("lut", "eft_node", 0, NULL, (uintptr_t)node.u.stmt.lutp) != 0) return (DCMD_ERR); } break; case T_PROP: /* prop statement */ mdb_printf("prop "); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); break; case T_MASK: /* mask statement */ mdb_printf("mask "); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); break; case T_CONFIG: /* config statement */ mdb_printf("config "); (void) eft_node((uintptr_t)node.u.stmt.np, DCMD_ADDRSPEC, 0, NULL); if (node.u.stmt.nvpairs) { mdb_printf(" "); (void) eft_node((uintptr_t)node.u.stmt.nvpairs, DCMD_ADDRSPEC, 0, NULL); } break; default: mdb_printf("not a eversholt node\n"); break; } return (DCMD_OK); } static const mdb_walker_t walkers[] = { { "lut", "walk a lookup table", lut_walk_init, lut_walk_step, lut_walk_fini, NULL }, { "eft_ipath", "walk ipath", ipath_walk_init, ipath_walk_step, ipath_walk_fini, NULL }, { NULL, NULL, NULL, NULL, NULL, NULL } }; static const mdb_dcmd_t dcmds[] = { { "eft_ipath", "?", "print an ipath", ipath }, { "eft_count", "?", "print eversholt stats", eft_count }, { "eft_node", "?[-v]", "print eversholt node", eft_node }, { "eft_time", "?[-p][-l time]", "print eversholt timeval", eft_time }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * eval.c -- constraint evaluation module * * this module evaluates constraints. */ #include #include #include #include #include #include "alloc.h" #include "out.h" #include "stable.h" #include "literals.h" #include "lut.h" #include "tree.h" #include "ptree.h" #include "itree.h" #include "ipath.h" #include "eval.h" #include "config.h" #include "platform.h" #include "fme.h" #include "stats.h" static struct node *eval_dup(struct node *np, struct lut *ex, struct node *events[]); static int check_expr_args(struct evalue *lp, struct evalue *rp, enum datatype dtype, struct node *np); static struct node *eval_fru(struct node *np); static struct node *eval_asru(struct node *np); extern fmd_hdl_t *Hdl; /* handle from eft.c */ /* * begins_with -- return true if rhs path begins with everything in lhs path */ static int begins_with(struct node *lhs, struct node *rhs, struct lut *ex) { int lnum; int rnum; struct iterinfo *iterinfop; if (lhs == NULL) return (1); /* yep -- it all matched */ if (rhs == NULL) return (0); /* nope, ran out of rhs first */ ASSERTeq(lhs->t, T_NAME, ptree_nodetype2str); ASSERTeq(rhs->t, T_NAME, ptree_nodetype2str); if (lhs->u.name.s != rhs->u.name.s) return (0); /* nope, different component names */ if (lhs->u.name.child && lhs->u.name.child->t == T_NUM) { lnum = (int)lhs->u.name.child->u.ull; } else if (lhs->u.name.child && lhs->u.name.child->t == T_NAME) { iterinfop = lut_lookup(ex, (void *)lhs->u.name.child->u.name.s, NULL); if (iterinfop != NULL) lnum = iterinfop->num; else out(O_DIE, "begins_with: unexpected lhs child"); } else { out(O_DIE, "begins_with: unexpected lhs child"); } if (rhs->u.name.child && rhs->u.name.child->t == T_NUM) { rnum = (int)rhs->u.name.child->u.ull; } else if (rhs->u.name.child && rhs->u.name.child->t == T_NAME) { iterinfop = lut_lookup(ex, (void *)rhs->u.name.child->u.name.s, NULL); if (iterinfop != NULL) rnum = iterinfop->num; else out(O_DIE, "begins_with: unexpected rhs child"); } else { out(O_DIE, "begins_with: unexpected rhs child"); } if (lnum != rnum) return (0); /* nope, instance numbers were different */ return (begins_with(lhs->u.name.next, rhs->u.name.next, ex)); } /* * eval_getname - used by eval_func to evaluate a name, preferably without using * eval_dup (but if it does have to use eval_dup then the *dupedp flag is set). */ static struct node * eval_getname(struct node *funcnp, struct lut *ex, struct node *events[], struct node *np, struct lut **globals, struct config *croot, struct arrow *arrowp, int try, int *dupedp) { struct node *nodep; const char *funcname = funcnp->u.func.s; struct evalue val; if (np->t == T_NAME) nodep = np; else if (np->t == T_FUNC && np->u.func.s == L_fru) nodep = eval_fru(np->u.func.arglist); else if (np->t == T_FUNC && np->u.func.s == L_asru) nodep = eval_asru(np->u.func.arglist); else if (np->t == T_FUNC) { if (eval_expr(np, ex, events, globals, croot, arrowp, try, &val) == 0) /* * Can't evaluate yet. Return null so constraint is * deferred. */ return (NULL); if (val.t == NODEPTR) return ((struct node *)(uintptr_t)val.v); else /* * just return the T_FUNC - which the caller will * reject. */ return (np); } else out(O_DIE, "%s: unexpected type: %s", funcname, ptree_nodetype2str(np->t)); if (try) { if (eval_expr(nodep, ex, events, globals, croot, arrowp, try, &val) && val.t == NODEPTR) nodep = (struct node *)(uintptr_t)val.v; else { *dupedp = 1; nodep = eval_dup(nodep, ex, events); } } return (nodep); } /*ARGSUSED*/ static int eval_cat(struct node *np, struct lut *ex, struct node *events[], struct lut **globals, struct config *croot, struct arrow *arrowp, int try, struct evalue *valuep) { if (np->t == T_LIST) { struct evalue lval; struct evalue rval; int len; char *s; if (!eval_cat(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_cat(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); len = snprintf(NULL, 0, "%s%s", (char *)(uintptr_t)lval.v, (char *)(uintptr_t)rval.v); s = MALLOC(len + 1); (void) snprintf(s, len + 1, "%s%s", (char *)(uintptr_t)lval.v, (char *)(uintptr_t)rval.v); outfl(O_ALTFP|O_VERB2, np->file, np->line, "eval_cat: %s %s returns %s", (char *)(uintptr_t)lval.v, (char *)(uintptr_t)rval.v, s); valuep->t = STRING; valuep->v = (uintptr_t)stable(s); FREE(s); } else { if (!eval_expr(np, ex, events, globals, croot, arrowp, try, valuep)) return (0); if (check_expr_args(valuep, NULL, STRING, np)) return (0); } return (1); } /* * evaluate a variety of functions and place result in valuep. return 1 if * function evaluation was successful; 0 if otherwise (e.g., the case of an * invalid argument to the function) */ /*ARGSUSED*/ static int eval_func(struct node *funcnp, struct lut *ex, struct node *events[], struct node *np, struct lut **globals, struct config *croot, struct arrow *arrowp, int try, struct evalue *valuep) { const char *funcname = funcnp->u.func.s; int duped_lhs = 0, duped_rhs = 0, duped = 0; struct node *lhs; struct node *rhs; struct config *cp; struct node *nodep; char *path; struct evalue val; if (funcname == L_within) { /* within()'s are not really constraints -- always true */ valuep->t = UINT64; valuep->v = 1; return (1); } else if (funcname == L_is_under) { lhs = eval_getname(funcnp, ex, events, np->u.expr.left, globals, croot, arrowp, try, &duped_lhs); rhs = eval_getname(funcnp, ex, events, np->u.expr.right, globals, croot, arrowp, try, &duped_rhs); if (!rhs || !lhs) return (0); if (rhs->t != T_NAME || lhs->t != T_NAME) { valuep->t = UNDEFINED; return (1); } valuep->t = UINT64; valuep->v = begins_with(lhs, rhs, ex); out(O_ALTFP|O_VERB2|O_NONL, "eval_func:is_under("); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, lhs); out(O_ALTFP|O_VERB2|O_NONL, ","); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, rhs); out(O_ALTFP|O_VERB2|O_NONL, ") returned %d", (int)valuep->v); if (duped_lhs) tree_free(lhs); if (duped_rhs) tree_free(rhs); return (1); } else if (funcname == L_confprop || funcname == L_confprop_defined) { const char *s; /* for now s will point to a quote [see addconfigprop()] */ ASSERT(np->u.expr.right->t == T_QUOTE); nodep = eval_getname(funcnp, ex, events, np->u.expr.left, globals, croot, arrowp, try, &duped); if (!nodep) return (0); if (nodep->t != T_NAME) { valuep->t = UNDEFINED; return (1); } if (nodep->u.name.last->u.name.cp != NULL) { cp = nodep->u.name.last->u.name.cp; } else { path = ipath2str(NULL, ipath(nodep)); cp = config_lookup(croot, path, 0); FREE((void *)path); } if (cp == NULL) { if (funcname == L_confprop) { out(O_ALTFP|O_VERB3, "%s: path ", funcname); ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, nodep); out(O_ALTFP|O_VERB3, " not found"); valuep->v = (uintptr_t)stable(""); valuep->t = STRING; if (duped) tree_free(nodep); return (1); } else { valuep->v = 0; valuep->t = UINT64; if (duped) tree_free(nodep); return (1); } } s = config_getprop(cp, np->u.expr.right->u.quote.s); if (s == NULL && strcmp(np->u.expr.right->u.quote.s, "class-code") == 0) s = config_getprop(cp, "CLASS-CODE"); if (s == NULL) { if (funcname == L_confprop) { out(O_ALTFP|O_VERB3|O_NONL, "%s: \"%s\" not found for path ", funcname, np->u.expr.right->u.quote.s); ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, nodep); valuep->v = (uintptr_t)stable(""); valuep->t = STRING; if (duped) tree_free(nodep); return (1); } else { valuep->v = 0; valuep->t = UINT64; if (duped) tree_free(nodep); return (1); } } if (funcname == L_confprop) { valuep->v = (uintptr_t)stable(s); valuep->t = STRING; out(O_ALTFP|O_VERB3|O_NONL, " %s(\"", funcname); ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, nodep); out(O_ALTFP|O_VERB3|O_NONL, "\", \"%s\") = \"%s\" ", np->u.expr.right->u.quote.s, (char *)(uintptr_t)valuep->v); } else { valuep->v = 1; valuep->t = UINT64; } if (duped) tree_free(nodep); return (1); } else if (funcname == L_is_connected) { const char *connstrings[] = { "connected", "CONNECTED", NULL }; struct config *cp[2]; const char *matchthis[2], *s; char *nameslist, *w; int i, j; lhs = eval_getname(funcnp, ex, events, np->u.expr.left, globals, croot, arrowp, try, &duped_lhs); rhs = eval_getname(funcnp, ex, events, np->u.expr.right, globals, croot, arrowp, try, &duped_rhs); if (!rhs || !lhs) return (0); if (rhs->t != T_NAME || lhs->t != T_NAME) { valuep->t = UNDEFINED; return (1); } path = ipath2str(NULL, ipath(lhs)); matchthis[1] = stable(path); if (lhs->u.name.last->u.name.cp != NULL) cp[0] = lhs->u.name.last->u.name.cp; else cp[0] = config_lookup(croot, path, 0); FREE((void *)path); path = ipath2str(NULL, ipath(rhs)); matchthis[0] = stable(path); if (rhs->u.name.last->u.name.cp != NULL) cp[1] = rhs->u.name.last->u.name.cp; else cp[1] = config_lookup(croot, path, 0); FREE((void *)path); if (duped_lhs) tree_free(lhs); if (duped_rhs) tree_free(rhs); valuep->t = UINT64; valuep->v = 0; if (cp[0] == NULL || cp[1] == NULL) return (1); /* to thine self always be connected */ if (cp[0] == cp[1]) { valuep->v = 1; return (1); } /* * Extract "connected" property from each cp. Search this * property for the name associated with the other cp[]. */ for (i = 0; i < 2 && valuep->v == 0; i++) { for (j = 0; connstrings[j] != NULL && valuep->v == 0; j++) { s = config_getprop(cp[i], stable(connstrings[j])); if (s != NULL) { nameslist = STRDUP(s); w = strtok(nameslist, " ,"); while (w != NULL) { if (stable(w) == matchthis[i]) { valuep->v = 1; break; } w = strtok(NULL, " ,"); } FREE(nameslist); } } } return (1); } else if (funcname == L_is_type) { const char *typestrings[] = { "type", "TYPE", NULL }; const char *s; int i; nodep = eval_getname(funcnp, ex, events, np, globals, croot, arrowp, try, &duped); if (!nodep) return (0); if (nodep->t != T_NAME) { valuep->t = UNDEFINED; return (1); } if (nodep->u.name.last->u.name.cp != NULL) { cp = nodep->u.name.last->u.name.cp; } else { path = ipath2str(NULL, ipath(nodep)); cp = config_lookup(croot, path, 0); FREE((void *)path); } if (duped) tree_free(nodep); valuep->t = STRING; valuep->v = (uintptr_t)stable(""); if (cp == NULL) return (1); for (i = 0; typestrings[i] != NULL; i++) { s = config_getprop(cp, stable(typestrings[i])); if (s != NULL) { valuep->v = (uintptr_t)stable(s); break; } } return (1); } else if (funcname == L_is_on) { const char *onstrings[] = { "on", "ON", NULL }; const char *truestrings[] = { "yes", "YES", "y", "Y", "true", "TRUE", "t", "T", "1", NULL }; const char *s; int i, j; nodep = eval_getname(funcnp, ex, events, np, globals, croot, arrowp, try, &duped); if (!nodep) return (0); if (nodep->t != T_NAME) { valuep->t = UNDEFINED; return (1); } if (nodep->u.name.last->u.name.cp != NULL) { cp = nodep->u.name.last->u.name.cp; } else { path = ipath2str(NULL, ipath(nodep)); cp = config_lookup(croot, path, 0); FREE((void *)path); } if (duped) tree_free(nodep); valuep->t = UINT64; valuep->v = 0; if (cp == NULL) return (1); for (i = 0; onstrings[i] != NULL; i++) { s = config_getprop(cp, stable(onstrings[i])); if (s != NULL) { s = stable(s); for (j = 0; truestrings[j] != NULL; j++) { if (s == stable(truestrings[j])) { valuep->v = 1; return (1); } } } } return (1); } else if (funcname == L_is_present) { nodep = eval_getname(funcnp, ex, events, np, globals, croot, arrowp, try, &duped); if (!nodep) return (0); if (nodep->t != T_NAME) { valuep->t = UNDEFINED; return (1); } if (nodep->u.name.last->u.name.cp != NULL) { cp = nodep->u.name.last->u.name.cp; } else { path = ipath2str(NULL, ipath(nodep)); cp = config_lookup(croot, path, 0); FREE((void *)path); } if (duped) tree_free(nodep); valuep->t = UINT64; valuep->v = 0; if (cp != NULL) valuep->v = 1; return (1); } else if (funcname == L_has_fault) { nvlist_t *rsrc = NULL; nodep = eval_getname(funcnp, ex, events, np->u.expr.left, globals, croot, arrowp, try, &duped); if (!nodep) return (0); if (nodep->t != T_NAME) { valuep->t = UNDEFINED; return (1); } path = ipath2str(NULL, ipath(nodep)); platform_unit_translate(0, croot, TOPO_PROP_RESOURCE, &rsrc, path); outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line, "has_fault("); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, nodep); out(O_ALTFP|O_VERB2|O_NONL, "(%s), \"%s\") ", path, np->u.expr.right->u.quote.s); FREE((void *)path); if (duped) tree_free(nodep); if (rsrc == NULL) { valuep->v = 0; out(O_ALTFP|O_VERB2, "no path"); } else { valuep->v = fmd_nvl_fmri_has_fault(Hdl, rsrc, FMD_HAS_FAULT_RESOURCE, strcmp(np->u.expr.right->u.quote.s, "") == 0 ? NULL : (char *)np->u.expr.right->u.quote.s); out(O_ALTFP|O_VERB2, "returned %lld", valuep->v); nvlist_free(rsrc); } valuep->t = UINT64; return (1); } else if (funcname == L_count) { struct stats *statp; struct istat_entry ent; ASSERTinfo(np->t == T_EVENT, ptree_nodetype2str(np->t)); nodep = np->u.event.epname; if (try) { if (eval_expr(nodep, ex, events, globals, croot, arrowp, try, &val) && val.t == NODEPTR) nodep = (struct node *)(uintptr_t)val.v; else { duped = 1; nodep = eval_dup(nodep, ex, events); } } ent.ename = np->u.event.ename->u.name.s; ent.ipath = ipath(nodep); valuep->t = UINT64; if ((statp = (struct stats *) lut_lookup(Istats, &ent, (lut_cmp)istat_cmp)) == NULL) valuep->v = 0; else valuep->v = stats_counter_value(statp); if (duped) tree_free(nodep); return (1); } else if (funcname == L_envprop) { outfl(O_DIE, np->file, np->line, "eval_func: %s not yet supported", funcname); } if (try) return (0); if (funcname == L_fru) { valuep->t = NODEPTR; valuep->v = (uintptr_t)eval_fru(np); return (1); } else if (funcname == L_asru) { valuep->t = NODEPTR; valuep->v = (uintptr_t)eval_asru(np); return (1); } else if (funcname == L_defined) { ASSERTeq(np->t, T_GLOBID, ptree_nodetype2str); valuep->t = UINT64; valuep->v = (lut_lookup(*globals, (void *)np->u.globid.s, NULL) != NULL); return (1); } else if (funcname == L_call) { return (! platform_call(np, globals, croot, arrowp, valuep)); } else if (funcname == L_payloadprop) { outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line, "payloadprop(\"%s\") ", np->u.quote.s); if (arrowp->head->myevent->count == 0) { /* * Haven't seen this ereport yet, so must defer */ out(O_ALTFP|O_VERB2, "ereport not yet seen - defer."); return (0); } else if (platform_payloadprop(np, valuep)) { /* platform_payloadprop() returned false */ out(O_ALTFP|O_VERB, "not found."); valuep->t = UNDEFINED; return (1); } else { switch (valuep->t) { case NODEPTR: if (((struct node *)(uintptr_t) (valuep->v))->t == T_NAME) { char *s = ipath2str(NULL, ipath((struct node *) (uintptr_t)valuep->v)); out(O_ALTFP|O_VERB2, "found: \"%s\"", s); FREE(s); } else out(O_ALTFP|O_VERB2, "found: %llu", valuep->v); break; case UINT64: out(O_ALTFP|O_VERB2, "found: %llu", valuep->v); break; case STRING: out(O_ALTFP|O_VERB2, "found: \"%s\"", (char *)(uintptr_t)valuep->v); break; default: out(O_ALTFP|O_VERB2, "found: undefined"); break; } return (1); } } else if (funcname == L_setpayloadprop) { struct evalue *payloadvalp; int alloced = 0; ASSERTinfo(np->t == T_LIST, ptree_nodetype2str(np->t)); ASSERTinfo(np->u.expr.left->t == T_QUOTE, ptree_nodetype2str(np->u.expr.left->t)); if (!(arrowp->head->myevent->cached_state & REQMNTS_CREDIBLE)) return (0); outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line, "setpayloadprop: %s: %s=", arrowp->tail->myevent->enode->u.event.ename->u.name.s, np->u.expr.left->u.quote.s); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, np->u.expr.right); /* * allocate a struct evalue to hold the payload property's * value, unless we've been here already, in which case we * might calculate a different value, but we'll store it * in the already-allocated struct evalue. */ if ((payloadvalp = (struct evalue *)lut_lookup( arrowp->tail->myevent->payloadprops, (void *)np->u.expr.left->u.quote.s, NULL)) == NULL) { payloadvalp = MALLOC(sizeof (*payloadvalp)); alloced = 1; } if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, payloadvalp)) { out(O_ALTFP|O_VERB2, " (cannot eval)"); if (alloced) FREE(payloadvalp); return (0); } else { if (payloadvalp->t == UNDEFINED) { /* function is always true */ out(O_ALTFP|O_VERB2, " (undefined)"); valuep->t = UINT64; valuep->v = 1; return (1); } if (payloadvalp->t == UINT64) out(O_ALTFP|O_VERB2, " (%llu)", payloadvalp->v); else out(O_ALTFP|O_VERB2, " (\"%s\")", (char *)(uintptr_t)payloadvalp->v); } /* add to table of payload properties for current problem */ arrowp->tail->myevent->payloadprops = lut_add(arrowp->tail->myevent->payloadprops, (void *)np->u.expr.left->u.quote.s, (void *)payloadvalp, NULL); /* function is always true */ valuep->t = UINT64; valuep->v = 1; return (1); } else if (funcname == L_cat) { int retval = eval_cat(np, ex, events, globals, croot, arrowp, try, valuep); outfl(O_ALTFP|O_VERB2, np->file, np->line, "cat: returns %s", (char *)(uintptr_t)valuep->v); return (retval); } else if (funcname == L_setserdn || funcname == L_setserdt || funcname == L_setserdsuffix || funcname == L_setserdincrement) { struct evalue *serdvalp; int alloced = 0; char *str; struct event *flt = arrowp->tail->myevent; if (!(arrowp->head->myevent->cached_state & REQMNTS_CREDIBLE)) return (0); if (funcname == L_setserdn) str = "n"; else if (funcname == L_setserdt) str = "t"; else if (funcname == L_setserdsuffix) str = "suffix"; else if (funcname == L_setserdincrement) str = "increment"; /* * allocate a struct evalue to hold the serd property's * value, unless we've been here already, in which case we * might calculate a different value, but we'll store it * in the already-allocated struct evalue. */ if ((serdvalp = (struct evalue *)lut_lookup(flt->serdprops, (void *)str, (lut_cmp)strcmp)) == NULL) { serdvalp = MALLOC(sizeof (*serdvalp)); alloced = 1; } if (!eval_expr(np, ex, events, globals, croot, arrowp, try, serdvalp)) { outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line, "setserd%s: %s: ", str, flt->enode->u.event.ename->u.name.s); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, np); out(O_ALTFP|O_VERB2, " (cannot eval)"); if (alloced) FREE(serdvalp); return (0); } else if (serdvalp->t == UNDEFINED) { outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line, "setserd%s: %s: ", str, flt->enode->u.event.ename->u.name.s); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, np); out(O_ALTFP|O_VERB2, " (undefined)"); } else { outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line, "setserd%s: %s: ", str, flt->enode->u.event.ename->u.name.s); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, np); if ((funcname == L_setserdincrement || funcname == L_setserdn) && serdvalp->t == STRING) { serdvalp->t = UINT64; serdvalp->v = strtoull((char *) (uintptr_t)serdvalp->v, NULL, 0); } if (funcname == L_setserdt && serdvalp->t == UINT64) { int len = snprintf(NULL, 0, "%lldns", serdvalp->v); char *buf = MALLOC(len + 1); (void) snprintf(buf, len + 1, "%lldns", serdvalp->v); serdvalp->t = STRING; serdvalp->v = (uintptr_t)stable(buf); FREE(buf); } if (funcname == L_setserdsuffix && serdvalp->t == UINT64) { int len = snprintf(NULL, 0, "%lld", serdvalp->v); char *buf = MALLOC(len + 1); (void) snprintf(buf, len + 1, "%lld", serdvalp->v); serdvalp->t = STRING; serdvalp->v = (uintptr_t)stable(buf); FREE(buf); } if (serdvalp->t == UINT64) out(O_ALTFP|O_VERB2, " (%llu)", serdvalp->v); else out(O_ALTFP|O_VERB2, " (\"%s\")", (char *)(uintptr_t)serdvalp->v); flt->serdprops = lut_add(flt->serdprops, (void *)str, (void *)serdvalp, (lut_cmp)strcmp); } valuep->t = UINT64; valuep->v = 1; return (1); } else if (funcname == L_payloadprop_defined) { outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line, "payloadprop_defined(\"%s\") ", np->u.quote.s); if (arrowp->head->myevent->count == 0) { /* * Haven't seen this ereport yet, so must defer */ out(O_ALTFP|O_VERB2, "ereport not yet seen - defer."); return (0); } else if (platform_payloadprop(np, NULL)) { /* platform_payloadprop() returned false */ valuep->v = 0; out(O_ALTFP|O_VERB2, "not found."); } else { valuep->v = 1; out(O_ALTFP|O_VERB2, "found."); } valuep->t = UINT64; return (1); } else if (funcname == L_payloadprop_contains) { int nvals; struct evalue *vals; struct evalue cmpval; ASSERTinfo(np->t == T_LIST, ptree_nodetype2str(np->t)); ASSERTinfo(np->u.expr.left->t == T_QUOTE, ptree_nodetype2str(np->u.expr.left->t)); outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line, "payloadprop_contains(\"%s\", ", np->u.expr.left->u.quote.s); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, np->u.expr.right); out(O_ALTFP|O_VERB2|O_NONL, ") "); /* evaluate the expression we're comparing against */ if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &cmpval)) { out(O_ALTFP|O_VERB2|O_NONL, "(cannot eval) "); return (0); } else { switch (cmpval.t) { case UNDEFINED: out(O_ALTFP|O_VERB2, "(undefined type)"); break; case UINT64: out(O_ALTFP|O_VERB2, "(%llu) ", cmpval.v); break; case STRING: out(O_ALTFP|O_VERB2, "(\"%s\") ", (char *)(uintptr_t)cmpval.v); break; case NODEPTR: out(O_ALTFP|O_VERB2|O_NONL, "("); ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, (struct node *)(uintptr_t)(cmpval.v)); out(O_ALTFP|O_VERB2, ") "); break; } } /* get the payload values and check for a match */ vals = platform_payloadprop_values(np->u.expr.left->u.quote.s, &nvals); valuep->t = UINT64; valuep->v = 0; if (arrowp->head->myevent->count == 0) { /* * Haven't seen this ereport yet, so must defer */ out(O_ALTFP|O_VERB2, "ereport not yet seen - defer."); return (0); } else if (nvals == 0) { out(O_ALTFP|O_VERB2, "not found."); return (1); } else { struct evalue preval; int i; out(O_ALTFP|O_VERB2|O_NONL, "found %d values ", nvals); for (i = 0; i < nvals; i++) { preval.t = vals[i].t; preval.v = vals[i].v; if (check_expr_args(&vals[i], &cmpval, UNDEFINED, np)) continue; /* * If we auto-converted the value to a * string, we need to free the * original tree value. */ if (preval.t == NODEPTR && ((struct node *)(uintptr_t)(preval.v))->t == T_NAME) { tree_free((struct node *)(uintptr_t) preval.v); } if (vals[i].v == cmpval.v) { valuep->v = 1; break; } } if (valuep->v) out(O_ALTFP|O_VERB2, "match."); else out(O_ALTFP|O_VERB2, "no match."); for (i = 0; i < nvals; i++) { if (vals[i].t == NODEPTR) { tree_free((struct node *)(uintptr_t) vals[i].v); break; } } FREE(vals); } return (1); } else if (funcname == L_confcall) { return (!platform_confcall(np, globals, croot, arrowp, valuep)); } else outfl(O_DIE, np->file, np->line, "eval_func: unexpected func: %s", funcname); /*NOTREACHED*/ return (0); } /* * defines for u.expr.temp - these are used for T_OR and T_AND so that if * we worked out that part of the expression was true or false during an * earlier eval_expr, then we don't need to dup that part. */ #define EXPR_TEMP_BOTH_UNK 0 #define EXPR_TEMP_LHS_UNK 1 #define EXPR_TEMP_RHS_UNK 2 static struct node * eval_dup(struct node *np, struct lut *ex, struct node *events[]) { struct node *newnp; if (np == NULL) return (NULL); switch (np->t) { case T_GLOBID: return (tree_globid(np->u.globid.s, np->file, np->line)); case T_ASSIGN: case T_CONDIF: case T_CONDELSE: case T_NE: case T_EQ: case T_LT: case T_LE: case T_GT: case T_GE: case T_BITAND: case T_BITOR: case T_BITXOR: case T_BITNOT: case T_LSHIFT: case T_RSHIFT: case T_NOT: case T_ADD: case T_SUB: case T_MUL: case T_DIV: case T_MOD: return (tree_expr(np->t, eval_dup(np->u.expr.left, ex, events), eval_dup(np->u.expr.right, ex, events))); case T_LIST: case T_AND: switch (np->u.expr.temp) { case EXPR_TEMP_LHS_UNK: return (eval_dup(np->u.expr.left, ex, events)); case EXPR_TEMP_RHS_UNK: return (eval_dup(np->u.expr.right, ex, events)); default: return (tree_expr(np->t, eval_dup(np->u.expr.left, ex, events), eval_dup(np->u.expr.right, ex, events))); } case T_OR: switch (np->u.expr.temp) { case EXPR_TEMP_LHS_UNK: return (eval_dup(np->u.expr.left, ex, events)); case EXPR_TEMP_RHS_UNK: return (eval_dup(np->u.expr.right, ex, events)); default: return (tree_expr(T_OR, eval_dup(np->u.expr.left, ex, events), eval_dup(np->u.expr.right, ex, events))); } case T_NAME: { struct iterinfo *iterinfop; int got_matchf = 0; int got_matcht = 0; struct evalue value; struct node *np1f, *np2f, *np1t, *np2t, *retp = NULL; struct node *npstart, *npcont, *npend, *npref, *newnp, *nprest; /* * Check if we already have a match of the nonwildcarded path * in oldepname (check both to and from events). */ for (np1f = np, np2f = events[0]->u.event.oldepname; np1f != NULL && np2f != NULL; np1f = np1f->u.name.next, np2f = np2f->u.name.next) { if (strcmp(np1f->u.name.s, np2f->u.name.s) != 0) break; if (np1f->u.name.child->t != np2f->u.name.child->t) break; if (np1f->u.name.child->t == T_NUM && np1f->u.name.child->u.ull != np2f->u.name.child->u.ull) break; if (np1f->u.name.child->t == T_NAME && strcmp(np1f->u.name.child->u.name.s, np2f->u.name.child->u.name.s) != 0) break; got_matchf++; } for (np1t = np, np2t = events[1]->u.event.oldepname; np1t != NULL && np2t != NULL; np1t = np1t->u.name.next, np2t = np2t->u.name.next) { if (strcmp(np1t->u.name.s, np2t->u.name.s) != 0) break; if (np1t->u.name.child->t != np2t->u.name.child->t) break; if (np1t->u.name.child->t == T_NUM && np1t->u.name.child->u.ull != np2t->u.name.child->u.ull) break; if (np1t->u.name.child->t == T_NAME && strcmp(np1t->u.name.child->u.name.s, np2t->u.name.child->u.name.s) != 0) break; got_matcht++; } nprest = np; if (got_matchf || got_matcht) { /* * so we are wildcarding. Copy ewname in full, plus * matching section of oldepname. Use whichever gives * the closest match. */ if (got_matchf > got_matcht) { npstart = events[0]->u.event.ewname; npcont = events[0]->u.event.oldepname; npend = np2f; nprest = np1f; } else { npstart = events[1]->u.event.ewname; npcont = events[1]->u.event.oldepname; npend = np2t; nprest = np1t; } for (npref = npstart; npref != NULL; npref = npref->u.name.next) { newnp = newnode(T_NAME, np->file, np->line); newnp->u.name.t = npref->u.name.t; newnp->u.name.s = npref->u.name.s; newnp->u.name.last = newnp; newnp->u.name.it = npref->u.name.it; newnp->u.name.cp = npref->u.name.cp; newnp->u.name.child = newnode(T_NUM, np->file, np->line); if (eval_expr(npref->u.name.child, ex, events, NULL, NULL, NULL, 1, &value) == 0 || value.t != UINT64) { outfl(O_DIE, np->file, np->line, "eval_dup: could not resolve " "iterator of %s", np->u.name.s); } newnp->u.name.child->u.ull = value.v; if (retp == NULL) { retp = newnp; } else { retp->u.name.last->u.name.next = newnp; retp->u.name.last = newnp; } } for (npref = npcont; npref != NULL && npref != npend; npref = npref->u.name.next) { newnp = newnode(T_NAME, np->file, np->line); newnp->u.name.t = npref->u.name.t; newnp->u.name.s = npref->u.name.s; newnp->u.name.last = newnp; newnp->u.name.it = npref->u.name.it; newnp->u.name.cp = npref->u.name.cp; newnp->u.name.child = newnode(T_NUM, np->file, np->line); if (eval_expr(npref->u.name.child, ex, events, NULL, NULL, NULL, 1, &value) == 0 || value.t != UINT64) { outfl(O_DIE, np->file, np->line, "eval_dup: could not resolve " "iterator of %s", np->u.name.s); } newnp->u.name.child->u.ull = value.v; if (retp == NULL) { retp = newnp; } else { retp->u.name.last->u.name.next = newnp; retp->u.name.last = newnp; } } } else { /* * not wildcarding - check if explicit iterator */ iterinfop = lut_lookup(ex, (void *)np->u.name.s, NULL); if (iterinfop != NULL) { /* explicit iterator; not part of pathname */ newnp = newnode(T_NUM, np->file, np->line); newnp->u.ull = iterinfop->num; return (newnp); } } /* * finally, whether wildcarding or not, we need to copy the * remaining part of the path (if any). This must be defined * absolutely (no more expansion/wildcarding). */ for (npref = nprest; npref != NULL; npref = npref->u.name.next) { newnp = newnode(T_NAME, np->file, np->line); newnp->u.name.t = npref->u.name.t; newnp->u.name.s = npref->u.name.s; newnp->u.name.last = newnp; newnp->u.name.it = npref->u.name.it; newnp->u.name.cp = npref->u.name.cp; newnp->u.name.child = newnode(T_NUM, np->file, np->line); if (eval_expr(npref->u.name.child, ex, events, NULL, NULL, NULL, 1, &value) == 0 || value.t != UINT64) { outfl(O_DIE, np->file, np->line, "eval_dup: could not resolve " "iterator of %s", np->u.name.s); } newnp->u.name.child->u.ull = value.v; if (retp == NULL) { retp = newnp; } else { retp->u.name.last->u.name.next = newnp; retp->u.name.last = newnp; } } return (retp); } case T_EVENT: newnp = newnode(T_NAME, np->file, np->line); newnp->u.name.t = np->u.event.ename->u.name.t; newnp->u.name.s = np->u.event.ename->u.name.s; newnp->u.name.it = np->u.event.ename->u.name.it; newnp->u.name.last = newnp; return (tree_event(newnp, eval_dup(np->u.event.epname, ex, events), eval_dup(np->u.event.eexprlist, ex, events))); case T_FUNC: return (tree_func(np->u.func.s, eval_dup(np->u.func.arglist, ex, events), np->file, np->line)); case T_QUOTE: newnp = newnode(T_QUOTE, np->file, np->line); newnp->u.quote.s = np->u.quote.s; return (newnp); case T_NUM: newnp = newnode(T_NUM, np->file, np->line); newnp->u.ull = np->u.ull; return (newnp); case T_TIMEVAL: newnp = newnode(T_TIMEVAL, np->file, np->line); newnp->u.ull = np->u.ull; return (newnp); default: outfl(O_DIE, np->file, np->line, "eval_dup: unexpected node type: %s", ptree_nodetype2str(np->t)); } /*NOTREACHED*/ return (0); } /* * eval_potential -- see if constraint is potentially true * * this function is used at instance tree creation time to see if * any constraints are already known to be false. if this function * returns false, then the constraint will always be false and there's * no need to include the propagation arrow in the instance tree. * * if this routine returns true, either the constraint is known to * be always true (so there's no point in attaching the constraint * to the propagation arrow in the instance tree), or the constraint * contains "deferred" expressions like global variables or poller calls * and so it must be evaluated during calls to fme_eval(). in this last * case, where a constraint needs to be attached to the propagation arrow * in the instance tree, this routine returns a newly created constraint * in *newc where all the non-deferred things have been filled in. * * so in summary: * * return of false: constraint can never be true, *newc will be NULL. * * return of true with *newc unchanged: constraint will always be true. * * return of true with *newc changed: use new constraint in *newc. * * the lookup table for all explicit iterators, ex, is passed in. * * *newc can either be NULL on entry, or if can contain constraints from * previous calls to eval_potential() (i.e. for building up an instance * tree constraint from several potential constraints). if *newc already * contains constraints, anything added to it will be joined by adding * a T_AND node at the top of *newc. */ int eval_potential(struct node *np, struct lut *ex, struct node *events[], struct node **newc, struct config *croot) { struct node *newnp; struct evalue value; if (eval_expr(np, ex, events, NULL, croot, NULL, 1, &value) == 0) { /* * couldn't eval expression because * it contains deferred items. make * a duplicate expression with all the * non-deferred items expanded. */ newnp = eval_dup(np, ex, events); if (*newc == NULL) { /* * constraint is potentially true if deferred * expression in newnp is true. *newc was NULL * so new constraint is just the one in newnp. */ *newc = newnp; return (1); } else { /* * constraint is potentially true if deferred * expression in newnp is true. *newc already * contained a constraint so add an AND with the * constraint in newnp. */ *newc = tree_expr(T_AND, *newc, newnp); return (1); } } else if (value.t == UNDEFINED) { /* constraint can never be true */ return (0); } else if (value.t == UINT64 && value.v == 0) { /* constraint can never be true */ return (0); } else { /* constraint is always true (nothing deferred to eval) */ return (1); } } static int check_expr_args(struct evalue *lp, struct evalue *rp, enum datatype dtype, struct node *np) { /* auto-convert T_NAMES to strings */ if (lp->t == NODEPTR && ((struct node *)(uintptr_t)(lp->v))->t == T_NAME) { char *s = ipath2str(NULL, ipath((struct node *)(uintptr_t)lp->v)); lp->t = STRING; lp->v = (uintptr_t)stable(s); FREE(s); out(O_ALTFP|O_VERB2, "convert lhs path to \"%s\"", (char *)(uintptr_t)lp->v); } if (rp != NULL && rp->t == NODEPTR && ((struct node *)(uintptr_t)(rp->v))->t == T_NAME) { char *s = ipath2str(NULL, ipath((struct node *)(uintptr_t)rp->v)); rp->t = STRING; rp->v = (uintptr_t)stable(s); FREE(s); out(O_ALTFP|O_VERB2, "convert rhs path to \"%s\"", (char *)(uintptr_t)rp->v); } /* auto-convert numbers to strings */ if (dtype == STRING) { if (lp->t == UINT64) { int len = snprintf(NULL, 0, "%llx", lp->v); char *s = MALLOC(len + 1); (void) snprintf(s, len + 1, "%llx", lp->v); lp->t = STRING; lp->v = (uintptr_t)stable(s); FREE(s); } if (rp != NULL && rp->t == UINT64) { int len = snprintf(NULL, 0, "%llx", rp->v); char *s = MALLOC(len + 1); (void) snprintf(s, len + 1, "%llx", rp->v); rp->t = STRING; rp->v = (uintptr_t)stable(s); FREE(s); } } /* auto-convert strings to numbers */ if (dtype == UINT64) { if (lp->t == STRING) { lp->t = UINT64; lp->v = strtoull((char *)(uintptr_t)lp->v, NULL, 0); } if (rp != NULL && rp->t == STRING) { rp->t = UINT64; rp->v = strtoull((char *)(uintptr_t)rp->v, NULL, 0); } } if (dtype != UNDEFINED && lp->t != dtype) { outfl(O_DIE, np->file, np->line, "invalid datatype of argument for operation %s", ptree_nodetype2str(np->t)); /* NOTREACHED */ return (1); } if (rp != NULL && lp->t != rp->t) { outfl(O_DIE, np->file, np->line, "mismatch in datatype of arguments for operation %s", ptree_nodetype2str(np->t)); /* NOTREACHED */ return (1); } return (0); } /* * eval_expr -- evaluate expression into *valuep * * the meaning of the return value depends on the input value of try. * * for try == 1: if any deferred items are encounted, bail out and return * false. returns true if we made it through entire expression without * hitting any deferred items. * * for try == 0: return true if all operations were performed successfully. * return false if otherwise. for example, any of the following conditions * will result in a false return value: * - attempted use of an uninitialized global variable * - failure in function evaluation * - illegal arithmetic operation (argument out of range) */ int eval_expr(struct node *np, struct lut *ex, struct node *events[], struct lut **globals, struct config *croot, struct arrow *arrowp, int try, struct evalue *valuep) { struct evalue *gval; struct evalue lval; struct evalue rval; if (np == NULL) { valuep->t = UINT64; valuep->v = 1; /* no constraint means "true" */ return (1); } valuep->t = UNDEFINED; switch (np->t) { case T_GLOBID: if (try) return (0); /* * only handle case of getting (and not setting) the value * of a global variable */ gval = lut_lookup(*globals, (void *)np->u.globid.s, NULL); if (gval == NULL) { return (0); } else { valuep->t = gval->t; valuep->v = gval->v; return (1); } case T_ASSIGN: if (try) return (0); /* * first evaluate rhs, then try to store value in lhs which * should be a global variable */ if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); ASSERT(np->u.expr.left->t == T_GLOBID); gval = lut_lookup(*globals, (void *)np->u.expr.left->u.globid.s, NULL); if (gval == NULL) { gval = MALLOC(sizeof (*gval)); *globals = lut_add(*globals, (void *) np->u.expr.left->u.globid.s, gval, NULL); } gval->t = rval.t; gval->v = rval.v; if (gval->t == UINT64) { out(O_ALTFP|O_VERB2, "assign $%s=%llu", np->u.expr.left->u.globid.s, gval->v); } else { out(O_ALTFP|O_VERB2, "assign $%s=\"%s\"", np->u.expr.left->u.globid.s, (char *)(uintptr_t)gval->v); } /* * but always return true -- an assignment should not * cause a constraint to be false. */ valuep->t = UINT64; valuep->v = 1; return (1); case T_EQ: #define IMPLICIT_ASSIGN_IN_EQ #ifdef IMPLICIT_ASSIGN_IN_EQ /* * if lhs is an uninitialized global variable, perform * an assignment. * * one insidious side effect of implicit assignment is * that the "==" operator does not return a Boolean if * implicit assignment was performed. */ if (try == 0 && np->u.expr.left->t == T_GLOBID && (gval = lut_lookup(*globals, (void *)np->u.expr.left->u.globid.s, NULL)) == NULL) { if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); gval = MALLOC(sizeof (*gval)); *globals = lut_add(*globals, (void *) np->u.expr.left->u.globid.s, gval, NULL); gval->t = rval.t; gval->v = rval.v; valuep->t = rval.t; valuep->v = rval.v; return (1); } #endif /* IMPLICIT_ASSIGN_IN_EQ */ if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (rval.t == UINT64 || lval.t == UINT64) { if (check_expr_args(&lval, &rval, UINT64, np)) return (0); } else { if (check_expr_args(&lval, &rval, UNDEFINED, np)) return (0); } valuep->t = UINT64; valuep->v = (lval.v == rval.v); return (1); case T_LT: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = UINT64; valuep->v = (lval.v < rval.v); return (1); case T_LE: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = UINT64; valuep->v = (lval.v <= rval.v); return (1); case T_GT: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = UINT64; valuep->v = (lval.v > rval.v); return (1); case T_GE: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = UINT64; valuep->v = (lval.v >= rval.v); return (1); case T_BITAND: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = lval.t; valuep->v = (lval.v & rval.v); return (1); case T_BITOR: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = lval.t; valuep->v = (lval.v | rval.v); return (1); case T_BITXOR: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = lval.t; valuep->v = (lval.v ^ rval.v); return (1); case T_BITNOT: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); ASSERT(np->u.expr.right == NULL); if (check_expr_args(&lval, NULL, UINT64, np)) return (0); valuep->t = UINT64; valuep->v = ~ lval.v; return (1); case T_LSHIFT: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = UINT64; valuep->v = (lval.v << rval.v); return (1); case T_RSHIFT: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = UINT64; valuep->v = (lval.v >> rval.v); return (1); case T_CONDIF: { struct node *retnp; int dotrue = 0; /* * evaluate * expression ? stmtA [ : stmtB ] * * first see if expression is true or false, then determine * if stmtA (or stmtB, if it exists) should be evaluated. * * "dotrue = 1" means stmtA should be evaluated. */ if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (lval.t != UNDEFINED && lval.v != 0) dotrue = 1; ASSERT(np->u.expr.right != NULL); if (np->u.expr.right->t == T_CONDELSE) { if (dotrue) retnp = np->u.expr.right->u.expr.left; else retnp = np->u.expr.right->u.expr.right; } else { /* no ELSE clause */ if (dotrue) retnp = np->u.expr.right; else { outfl(O_DIE, np->file, np->line, "eval_expr: missing condelse"); } } if (!eval_expr(retnp, ex, events, globals, croot, arrowp, try, valuep)) return (0); return (1); } case T_CONDELSE: /* * shouldn't get here, since T_CONDELSE is supposed to be * evaluated as part of T_CONDIF */ out(O_ALTFP|O_DIE, "eval_expr: wrong context for operation %s", ptree_nodetype2str(np->t)); /*NOTREACHED*/ break; case T_NE: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (rval.t == UINT64 || lval.t == UINT64) { if (check_expr_args(&lval, &rval, UINT64, np)) return (0); } else { if (check_expr_args(&lval, &rval, UNDEFINED, np)) return (0); } valuep->t = UINT64; valuep->v = (lval.v != rval.v); return (1); case T_LIST: case T_AND: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, valuep)) { /* * if lhs is unknown, still check rhs. If that * is false we can return false irrespective of lhs */ if (!try) { np->u.expr.temp = EXPR_TEMP_BOTH_UNK; return (0); } if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, valuep)) { np->u.expr.temp = EXPR_TEMP_BOTH_UNK; return (0); } if (valuep->v != 0) { np->u.expr.temp = EXPR_TEMP_LHS_UNK; return (0); } } if (valuep->v == 0) { valuep->t = UINT64; return (1); } if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, valuep)) { np->u.expr.temp = EXPR_TEMP_RHS_UNK; return (0); } valuep->t = UINT64; valuep->v = valuep->v == 0 ? 0 : 1; return (1); case T_OR: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, valuep)) { /* * if lhs is unknown, still check rhs. If that * is true we can return true irrespective of lhs */ if (!try) { np->u.expr.temp = EXPR_TEMP_BOTH_UNK; return (0); } if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, valuep)) { np->u.expr.temp = EXPR_TEMP_BOTH_UNK; return (0); } if (valuep->v == 0) { np->u.expr.temp = EXPR_TEMP_LHS_UNK; return (0); } } if (valuep->v != 0) { valuep->t = UINT64; valuep->v = 1; return (1); } if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, valuep)) { np->u.expr.temp = EXPR_TEMP_RHS_UNK; return (0); } valuep->t = UINT64; valuep->v = valuep->v == 0 ? 0 : 1; return (1); case T_NOT: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, valuep)) return (0); valuep->t = UINT64; valuep->v = ! valuep->v; return (1); case T_ADD: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = lval.t; valuep->v = lval.v + rval.v; return (1); case T_SUB: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); /* since valuep is unsigned, return false if lval.v < rval.v */ if (lval.v < rval.v) { outfl(O_DIE, np->file, np->line, "eval_expr: T_SUB result is out of range"); } valuep->t = lval.t; valuep->v = lval.v - rval.v; return (1); case T_MUL: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); valuep->t = lval.t; valuep->v = lval.v * rval.v; return (1); case T_DIV: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); /* return false if dividing by zero */ if (rval.v == 0) { outfl(O_DIE, np->file, np->line, "eval_expr: T_DIV division by zero"); } valuep->t = lval.t; valuep->v = lval.v / rval.v; return (1); case T_MOD: if (!eval_expr(np->u.expr.left, ex, events, globals, croot, arrowp, try, &lval)) return (0); if (!eval_expr(np->u.expr.right, ex, events, globals, croot, arrowp, try, &rval)) return (0); if (check_expr_args(&lval, &rval, UINT64, np)) return (0); /* return false if dividing by zero */ if (rval.v == 0) { outfl(O_DIE, np->file, np->line, "eval_expr: T_MOD division by zero"); } valuep->t = lval.t; valuep->v = lval.v % rval.v; return (1); case T_NAME: if (try) { struct iterinfo *iterinfop; struct node *np1, *np2; int i, gotmatch = 0; /* * Check if we have an exact match of the nonwildcarded * path in oldepname - if so we can just use the * full wildcarded path in epname. */ for (i = 0; i < 1; i++) { for (np1 = np, np2 = events[i]->u.event.oldepname; np1 != NULL && np2 != NULL; np1 = np1->u.name.next, np2 = np2->u.name.next) { if (strcmp(np1->u.name.s, np2->u.name.s) != 0) break; if (np1->u.name.child->t != np2->u.name.child->t) break; if (np1->u.name.child->t == T_NUM && np1->u.name.child->u.ull != np2->u.name.child->u.ull) break; if (np1->u.name.child->t == T_NAME && strcmp(np1->u.name.child->u.name.s, np2->u.name.child->u.name.s) != 0) break; gotmatch++; } if (np1 == NULL && np2 == NULL) { valuep->t = NODEPTR; valuep->v = (uintptr_t) events[i]->u.event.epname; return (1); } } if (!gotmatch) { /* * we're not wildcarding. However at * itree_create() time, we can also expand * simple iterators - so check for those. */ iterinfop = lut_lookup(ex, (void *)np->u.name.s, NULL); if (iterinfop != NULL) { valuep->t = UINT64; valuep->v = (unsigned long long)iterinfop->num; return (1); } } /* * For anything else we'll have to wait for eval_dup(). */ return (0); } /* return address of struct node */ valuep->t = NODEPTR; valuep->v = (uintptr_t)np; return (1); case T_QUOTE: valuep->t = STRING; valuep->v = (uintptr_t)np->u.quote.s; return (1); case T_FUNC: return (eval_func(np, ex, events, np->u.func.arglist, globals, croot, arrowp, try, valuep)); case T_NUM: case T_TIMEVAL: valuep->t = UINT64; valuep->v = np->u.ull; return (1); default: outfl(O_DIE, np->file, np->line, "eval_expr: unexpected node type: %s", ptree_nodetype2str(np->t)); } /*NOTREACHED*/ return (0); } /* * eval_fru() and eval_asru() don't do much, but are called from a number * of places. */ static struct node * eval_fru(struct node *np) { ASSERT(np->t == T_NAME); return (np); } static struct node * eval_asru(struct node *np) { ASSERT(np->t == T_NAME); return (np); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * eval.h -- public definitions for eval module * */ #ifndef _EFT_EVAL_H #define _EFT_EVAL_H #ifdef __cplusplus extern "C" { #endif struct evalue { enum datatype { UNDEFINED = 0, UINT64, /* use for Boolean as well */ STRING, /* usually pointer from stable() */ NODEPTR /* (struct node *) */ } t; /* * using v to handle all values eliminates the need for switch() * blocks during assignments, comparisons and other operations */ unsigned long long v; }; int eval_potential(struct node *np, struct lut *ex, struct node *events[], struct node **newc, struct config *croot); int eval_expr(struct node *np, struct lut *ex, struct node *events[], struct lut **globals, struct config *croot, struct arrow *arrowp, int try, struct evalue *valuep); #ifdef __cplusplus } #endif #endif /* _EFT_EVAL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * evnv.c -- eversholt specific nvpair manipulation functions * * this module provides the simulated fault management exercise. */ #include #include #include #include "evnv.h" #include "out.h" #define min(a, b) (((a) <= (b)) ? (a) : (b)) extern nv_alloc_t Eft_nv_hdl; static void outindent(int depth) { while (depth-- > 0) out(O_ALTFP|O_VERB3|O_NONL, " "); } /* * evnv_cmpnvl -- compare two asrus in their nvlist form */ int evnv_cmpnvl(nvlist_t *nvl1, nvlist_t *nvl2, int depth) { /* * an assumption here is that each list was constructed in the * same order, which is a safe assumption since we built the * list of ourself (well, libtopo did at any rate) */ data_type_t t1, t2; nvlist_t **la1 = NULL; nvlist_t **la2 = NULL; nvlist_t *l1 = NULL; nvlist_t *l2 = NULL; nvpair_t *p1 = NULL; nvpair_t *p2 = NULL; uint64_t lv1, lv2; uint_t m, na1, na2; char *s1, *s2; int ret, i; for (;;) { p1 = nvlist_next_nvpair(nvl1, p1); p2 = nvlist_next_nvpair(nvl2, p2); if (p1 == NULL && p2 == NULL) { outindent(depth); out(O_ALTFP|O_VERB3, "equal nvls\n"); return (0); } if (p1 == NULL) return (-1); if (p2 == NULL) return (1); s1 = nvpair_name(p1); s2 = nvpair_name(p2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: pair %s vs %s", s1, s2); if ((ret = strcmp(s1, s2)) != 0) return (ret); t1 = nvpair_type(p1); t2 = nvpair_type(p2); if (t1 != t2) return (t1 - t2); /* * We don't compare all possible types, just the * ones we know are likely to actually be present * in nvlists we've generated. */ switch (t1) { case DATA_TYPE_NVLIST: (void) nvpair_value_nvlist(p1, &l1); (void) nvpair_value_nvlist(p2, &l2); if ((ret = evnv_cmpnvl(l1, l2, depth + 1)) != 0) return (ret); break; case DATA_TYPE_NVLIST_ARRAY: (void) nvpair_value_nvlist_array(p1, &la1, &na1); (void) nvpair_value_nvlist_array(p2, &la2, &na2); m = min(na1, na2); for (i = 0; i < m; i++) { if ((ret = evnv_cmpnvl(*la1, *la2, depth + 1)) != 0) return (ret); la1++; la2++; } if (na1 < na2) return (-1); else if (na2 < na1) return (1); break; case DATA_TYPE_STRING: (void) nvpair_value_string(p1, &s1); (void) nvpair_value_string(p2, &s2); if ((ret = strcmp(s1, s2)) != 0) { outindent(depth); if (ret < 0) out(O_ALTFP|O_VERB3, "cmpnvl: %s < %s", s1, s2); else out(O_ALTFP|O_VERB3, "cmpnvl: %s > %s", s1, s2); return (ret); } break; case DATA_TYPE_UINT64: lv1 = lv2 = 0; (void) nvpair_value_uint64(p1, &lv1); (void) nvpair_value_uint64(p2, &lv2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: %llu vs %llu", lv1, lv2); if (lv1 > lv2) return (1); else if (lv2 > lv1) return (-1); break; case DATA_TYPE_INT64: lv1 = lv2 = 0; (void) nvpair_value_int64(p1, (int64_t *)&lv1); (void) nvpair_value_int64(p2, (int64_t *)&lv2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: %lld vs %lld", lv1, lv2); if (lv1 > lv2) return (1); else if (lv2 > lv1) return (-1); break; case DATA_TYPE_UINT32: lv1 = lv2 = 0; (void) nvpair_value_uint32(p1, (uint32_t *)&lv1); (void) nvpair_value_uint32(p2, (uint32_t *)&lv2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: %u vs %u", *(uint32_t *)&lv1, *(uint32_t *)&lv2); if (lv1 > lv2) return (1); else if (lv2 > lv1) return (-1); break; case DATA_TYPE_INT32: lv1 = lv2 = 0; (void) nvpair_value_int32(p1, (int32_t *)&lv1); (void) nvpair_value_int32(p2, (int32_t *)&lv2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: %d vs %d", *(int32_t *)&lv1, *(int32_t *)&lv2); if (lv1 > lv2) return (1); else if (lv2 > lv1) return (-1); break; case DATA_TYPE_UINT16: lv1 = lv2 = 0; (void) nvpair_value_uint16(p1, (uint16_t *)&lv1); (void) nvpair_value_uint16(p2, (uint16_t *)&lv2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: %u vs %u", *(uint16_t *)&lv1, *(uint16_t *)&lv2); if (lv1 > lv2) return (1); else if (lv2 > lv1) return (-1); break; case DATA_TYPE_INT16: lv1 = lv2 = 0; (void) nvpair_value_int16(p1, (int16_t *)&lv1); (void) nvpair_value_int16(p2, (int16_t *)&lv2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: %d vs %d", *(int16_t *)&lv1, *(int16_t *)&lv2); if (lv1 > lv2) return (1); else if (lv2 > lv1) return (-1); break; case DATA_TYPE_UINT8: lv1 = lv2 = 0; (void) nvpair_value_uint8(p1, (uint8_t *)&lv1); (void) nvpair_value_uint8(p2, (uint8_t *)&lv2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: %u vs %u", *(uint8_t *)&lv1, *(uint8_t *)&lv2); if (lv1 > lv2) return (1); else if (lv2 > lv1) return (-1); break; case DATA_TYPE_INT8: lv1 = lv2 = 0; (void) nvpair_value_int8(p1, (int8_t *)&lv1); (void) nvpair_value_int8(p2, (int8_t *)&lv2); outindent(depth); out(O_ALTFP|O_VERB3, "cmpnvl: %d vs %d", *(int8_t *)&lv1, *(int8_t *)&lv2); if (lv1 > lv2) return (1); else if (lv2 > lv1) return (-1); break; } } } /* * evnv_dupnvl -- duplicate a payload nvlist, keeping only the interesting stuff */ nvlist_t * evnv_dupnvl(nvlist_t *nvp) { nvlist_t *retval = NULL; int nvret; if (nvp == NULL) return (NULL); if ((nvret = nvlist_xdup(nvp, &retval, &Eft_nv_hdl)) != 0) out(O_DIE, "dupnvl: dup failed: %d", nvret); return (retval); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * evnv.h -- public definitions for evnv module * */ #ifndef _EFT_EVNV_H #define _EFT_EVNV_H #ifdef __cplusplus extern "C" { #endif int evnv_cmpnvl(nvlist_t *nvl1, nvlist_t *nvl2, int depth); nvlist_t *evnv_dupnvl(nvlist_t *nvp); #ifdef __cplusplus } #endif #endif /* _EFT_EVNV_H */ /* * 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) 2003, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Milan Jurik. All rights reserved. * Copyright (c) 2018, Joyent, Inc. * * fme.c -- fault management exercise module * * this module provides the simulated fault management exercise. */ #include #include #include #include #include #include #include #include #include #include #include "alloc.h" #include "out.h" #include "stats.h" #include "stable.h" #include "literals.h" #include "lut.h" #include "tree.h" #include "ptree.h" #include "itree.h" #include "ipath.h" #include "fme.h" #include "evnv.h" #include "eval.h" #include "config.h" #include "platform.h" #include "esclex.h" struct lut *Istats; struct lut *SerdEngines; nvlist_t *Action_nvl; /* imported from eft.c... */ extern hrtime_t Hesitate; extern char *Serd_Override; extern nv_alloc_t Eft_nv_hdl; extern int Max_fme; extern fmd_hdl_t *Hdl; static int Istat_need_save; static int Serd_need_save; void istat_save(void); void serd_save(void); /* fme under construction is global so we can free it on module abort */ static struct fme *Nfmep; static int Undiag_reason = UD_VAL_UNKNOWN; static int Nextid = 0; static int Open_fme_count = 0; /* Count of open FMEs */ /* list of fault management exercises underway */ static struct fme { struct fme *next; /* next exercise */ unsigned long long ull; /* time when fme was created */ int id; /* FME id */ struct config *config; /* cooked configuration data */ struct lut *eventtree; /* propagation tree for this FME */ /* * The initial error report that created this FME is kept in * two forms. e0 points to the instance tree node and is used * by fme_eval() as the starting point for the inference * algorithm. e0r is the event handle FMD passed to us when * the ereport first arrived and is used when setting timers, * which are always relative to the time of this initial * report. */ struct event *e0; fmd_event_t *e0r; id_t timer; /* for setting an fmd time-out */ struct event *ecurrent; /* ereport under consideration */ struct event *suspects; /* current suspect list */ struct event *psuspects; /* previous suspect list */ int nsuspects; /* count of suspects */ int posted_suspects; /* true if we've posted a diagnosis */ int uniqobs; /* number of unique events observed */ int peek; /* just peeking, don't track suspects */ int overflow; /* true if overflow FME */ enum fme_state { FME_NOTHING = 5000, /* not evaluated yet */ FME_WAIT, /* need to wait for more info */ FME_CREDIBLE, /* suspect list is credible */ FME_DISPROVED, /* no valid suspects found */ FME_DEFERRED /* don't know yet (k-count not met) */ } state; unsigned long long pull; /* time passed since created */ unsigned long long wull; /* wait until this time for re-eval */ struct event *observations; /* observation list */ struct lut *globals; /* values of global variables */ /* fmd interfacing */ fmd_hdl_t *hdl; /* handle for talking with fmd */ fmd_case_t *fmcase; /* what fmd 'case' we associate with */ /* stats */ struct stats *Rcount; struct stats *Hcallcount; struct stats *Rcallcount; struct stats *Ccallcount; struct stats *Ecallcount; struct stats *Tcallcount; struct stats *Marrowcount; struct stats *diags; } *FMElist, *EFMElist, *ClosedFMEs; static struct case_list { fmd_case_t *fmcase; struct case_list *next; } *Undiagablecaselist; static void fme_eval(struct fme *fmep, fmd_event_t *ffep); static enum fme_state hypothesise(struct fme *fmep, struct event *ep, unsigned long long at_latest_by, unsigned long long *pdelay); static struct node *eventprop_lookup(struct event *ep, const char *propname); static struct node *pathstring2epnamenp(char *path); static void publish_undiagnosable(fmd_hdl_t *hdl, fmd_event_t *ffep, fmd_case_t *fmcase, nvlist_t *detector, char *arg); static char *undiag_2reason_str(int ud, char *arg); static const char *undiag_2defect_str(int ud); static void restore_suspects(struct fme *fmep); static void save_suspects(struct fme *fmep); static void destroy_fme(struct fme *f); static void fme_receive_report(fmd_hdl_t *hdl, fmd_event_t *ffep, const char *eventstring, const struct ipath *ipp, nvlist_t *nvl); static void istat_counter_reset_cb(struct istat_entry *entp, struct stats *statp, const struct ipath *ipp); static void istat_counter_topo_chg_cb(struct istat_entry *entp, struct stats *statp, void *unused); static void serd_reset_cb(struct serd_entry *entp, void *unused, const struct ipath *ipp); static void serd_topo_chg_cb(struct serd_entry *entp, void *unused, void *unused2); static void destroy_fme_bufs(struct fme *fp); static struct fme * alloc_fme(void) { struct fme *fmep; fmep = MALLOC(sizeof (*fmep)); bzero(fmep, sizeof (*fmep)); return (fmep); } /* * fme_ready -- called when all initialization of the FME (except for * stats) has completed successfully. Adds the fme to global lists * and establishes its stats. */ static struct fme * fme_ready(struct fme *fmep) { char nbuf[100]; Nfmep = NULL; /* don't need to free this on module abort now */ if (EFMElist) { EFMElist->next = fmep; EFMElist = fmep; } else FMElist = EFMElist = fmep; (void) sprintf(nbuf, "fme%d.Rcount", fmep->id); fmep->Rcount = stats_new_counter(nbuf, "ereports received", 0); (void) sprintf(nbuf, "fme%d.Hcall", fmep->id); fmep->Hcallcount = stats_new_counter(nbuf, "calls to hypothesise()", 1); (void) sprintf(nbuf, "fme%d.Rcall", fmep->id); fmep->Rcallcount = stats_new_counter(nbuf, "calls to requirements_test()", 1); (void) sprintf(nbuf, "fme%d.Ccall", fmep->id); fmep->Ccallcount = stats_new_counter(nbuf, "calls to causes_test()", 1); (void) sprintf(nbuf, "fme%d.Ecall", fmep->id); fmep->Ecallcount = stats_new_counter(nbuf, "calls to effects_test()", 1); (void) sprintf(nbuf, "fme%d.Tcall", fmep->id); fmep->Tcallcount = stats_new_counter(nbuf, "calls to triggered()", 1); (void) sprintf(nbuf, "fme%d.Marrow", fmep->id); fmep->Marrowcount = stats_new_counter(nbuf, "arrows marked by mark_arrows()", 1); (void) sprintf(nbuf, "fme%d.diags", fmep->id); fmep->diags = stats_new_counter(nbuf, "suspect lists diagnosed", 0); out(O_ALTFP|O_VERB2, "newfme: config snapshot contains..."); config_print(O_ALTFP|O_VERB2, fmep->config); return (fmep); } extern void ipath_dummy_lut(struct arrow *); extern struct lut *itree_create_dummy(const char *, const struct ipath *); /* ARGSUSED */ static void set_needed_arrows(struct event *ep, struct event *ep2, struct fme *fmep) { struct bubble *bp; struct arrowlist *ap; for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { if (bp->t != B_FROM) continue; for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) { ap->arrowp->pnode->u.arrow.needed = 1; ipath_dummy_lut(ap->arrowp); } } } /* ARGSUSED */ static void unset_needed_arrows(struct event *ep, struct event *ep2, struct fme *fmep) { struct bubble *bp; struct arrowlist *ap; for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { if (bp->t != B_FROM) continue; for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) ap->arrowp->pnode->u.arrow.needed = 0; } } static void globals_destructor(void *left, void *right, void *arg); static void clear_arrows(struct event *ep, struct event *ep2, struct fme *fmep); static boolean_t prune_propagations(const char *e0class, const struct ipath *e0ipp) { char nbuf[100]; unsigned long long my_delay = TIMEVAL_EVENTUALLY; extern struct lut *Usednames; Nfmep = alloc_fme(); Nfmep->id = Nextid; Nfmep->state = FME_NOTHING; Nfmep->eventtree = itree_create_dummy(e0class, e0ipp); if ((Nfmep->e0 = itree_lookup(Nfmep->eventtree, e0class, e0ipp)) == NULL) { itree_free(Nfmep->eventtree); FREE(Nfmep); Nfmep = NULL; return (B_FALSE); } Nfmep->ecurrent = Nfmep->observations = Nfmep->e0; Nfmep->e0->count++; (void) sprintf(nbuf, "fme%d.Rcount", Nfmep->id); Nfmep->Rcount = stats_new_counter(nbuf, "ereports received", 0); (void) sprintf(nbuf, "fme%d.Hcall", Nfmep->id); Nfmep->Hcallcount = stats_new_counter(nbuf, "calls to hypothesise()", 1); (void) sprintf(nbuf, "fme%d.Rcall", Nfmep->id); Nfmep->Rcallcount = stats_new_counter(nbuf, "calls to requirements_test()", 1); (void) sprintf(nbuf, "fme%d.Ccall", Nfmep->id); Nfmep->Ccallcount = stats_new_counter(nbuf, "calls to causes_test()", 1); (void) sprintf(nbuf, "fme%d.Ecall", Nfmep->id); Nfmep->Ecallcount = stats_new_counter(nbuf, "calls to effects_test()", 1); (void) sprintf(nbuf, "fme%d.Tcall", Nfmep->id); Nfmep->Tcallcount = stats_new_counter(nbuf, "calls to triggered()", 1); (void) sprintf(nbuf, "fme%d.Marrow", Nfmep->id); Nfmep->Marrowcount = stats_new_counter(nbuf, "arrows marked by mark_arrows()", 1); (void) sprintf(nbuf, "fme%d.diags", Nfmep->id); Nfmep->diags = stats_new_counter(nbuf, "suspect lists diagnosed", 0); Nfmep->peek = 1; lut_walk(Nfmep->eventtree, (lut_cb)unset_needed_arrows, (void *)Nfmep); lut_free(Usednames, NULL, NULL); Usednames = NULL; lut_walk(Nfmep->eventtree, (lut_cb)clear_arrows, (void *)Nfmep); (void) hypothesise(Nfmep, Nfmep->e0, Nfmep->ull, &my_delay); itree_prune(Nfmep->eventtree); lut_walk(Nfmep->eventtree, (lut_cb)set_needed_arrows, (void *)Nfmep); stats_delete(Nfmep->Rcount); stats_delete(Nfmep->Hcallcount); stats_delete(Nfmep->Rcallcount); stats_delete(Nfmep->Ccallcount); stats_delete(Nfmep->Ecallcount); stats_delete(Nfmep->Tcallcount); stats_delete(Nfmep->Marrowcount); stats_delete(Nfmep->diags); itree_free(Nfmep->eventtree); lut_free(Nfmep->globals, globals_destructor, NULL); FREE(Nfmep); return (B_TRUE); } static struct fme * newfme(const char *e0class, const struct ipath *e0ipp, fmd_hdl_t *hdl, fmd_case_t *fmcase, fmd_event_t *ffep, nvlist_t *nvl) { struct cfgdata *cfgdata; int init_size; extern int alloc_total(); nvlist_t *detector = NULL; char *pathstr; char *arg; /* * First check if e0ipp is actually in the topology so we can give a * more useful error message. */ ipathlastcomp(e0ipp); pathstr = ipath2str(NULL, e0ipp); cfgdata = config_snapshot(); platform_unit_translate(0, cfgdata->cooked, TOPO_PROP_RESOURCE, &detector, pathstr); FREE(pathstr); structconfig_free(cfgdata->cooked); config_free(cfgdata); if (detector == NULL) { /* See if class permits silent discard on unknown component. */ if (lut_lookup(Ereportenames_discard, (void *)e0class, NULL)) { out(O_ALTFP|O_VERB2, "Unable to map \"%s\" ereport " "to component path, but silent discard allowed.", e0class); fmd_case_close(hdl, fmcase); } else { Undiag_reason = UD_VAL_BADEVENTPATH; (void) nvlist_lookup_nvlist(nvl, FM_EREPORT_DETECTOR, &detector); arg = ipath2str(e0class, e0ipp); publish_undiagnosable(hdl, ffep, fmcase, detector, arg); FREE(arg); } return (NULL); } /* * Next run a quick first pass of the rules with a dummy config. This * allows us to prune those rules which can't possibly cause this * ereport. */ if (!prune_propagations(e0class, e0ipp)) { /* * The fault class must have been in the rules or we would * not have registered for it (and got a "nosub"), and the * pathname must be in the topology or we would have failed the * previous test. So to get here means the combination of * class and pathname in the ereport must be invalid. */ Undiag_reason = UD_VAL_BADEVENTCLASS; arg = ipath2str(e0class, e0ipp); publish_undiagnosable(hdl, ffep, fmcase, detector, arg); nvlist_free(detector); FREE(arg); return (NULL); } /* * Now go ahead and create the real fme using the pruned rules. */ init_size = alloc_total(); out(O_ALTFP|O_STAMP, "start config_snapshot using %d bytes", init_size); nvlist_free(detector); pathstr = ipath2str(NULL, e0ipp); cfgdata = config_snapshot(); platform_unit_translate(0, cfgdata->cooked, TOPO_PROP_RESOURCE, &detector, pathstr); FREE(pathstr); platform_save_config(hdl, fmcase); out(O_ALTFP|O_STAMP, "config_snapshot added %d bytes", alloc_total() - init_size); Nfmep = alloc_fme(); Nfmep->id = Nextid++; Nfmep->config = cfgdata->cooked; config_free(cfgdata); Nfmep->posted_suspects = 0; Nfmep->uniqobs = 0; Nfmep->state = FME_NOTHING; Nfmep->pull = 0ULL; Nfmep->overflow = 0; Nfmep->fmcase = fmcase; Nfmep->hdl = hdl; if ((Nfmep->eventtree = itree_create(Nfmep->config)) == NULL) { Undiag_reason = UD_VAL_INSTFAIL; arg = ipath2str(e0class, e0ipp); publish_undiagnosable(hdl, ffep, fmcase, detector, arg); nvlist_free(detector); FREE(arg); structconfig_free(Nfmep->config); destroy_fme_bufs(Nfmep); FREE(Nfmep); Nfmep = NULL; return (NULL); } itree_ptree(O_ALTFP|O_VERB2, Nfmep->eventtree); if ((Nfmep->e0 = itree_lookup(Nfmep->eventtree, e0class, e0ipp)) == NULL) { Undiag_reason = UD_VAL_BADEVENTI; arg = ipath2str(e0class, e0ipp); publish_undiagnosable(hdl, ffep, fmcase, detector, arg); nvlist_free(detector); FREE(arg); itree_free(Nfmep->eventtree); structconfig_free(Nfmep->config); destroy_fme_bufs(Nfmep); FREE(Nfmep); Nfmep = NULL; return (NULL); } nvlist_free(detector); return (fme_ready(Nfmep)); } void fme_fini(void) { struct fme *sfp, *fp; struct case_list *ucasep, *nextcasep; ucasep = Undiagablecaselist; while (ucasep != NULL) { nextcasep = ucasep->next; FREE(ucasep); ucasep = nextcasep; } Undiagablecaselist = NULL; /* clean up closed fmes */ fp = ClosedFMEs; while (fp != NULL) { sfp = fp->next; destroy_fme(fp); fp = sfp; } ClosedFMEs = NULL; fp = FMElist; while (fp != NULL) { sfp = fp->next; destroy_fme(fp); fp = sfp; } FMElist = EFMElist = NULL; /* if we were in the middle of creating an fme, free it now */ if (Nfmep) { destroy_fme(Nfmep); Nfmep = NULL; } } /* * Allocated space for a buffer name. 20 bytes allows for * a ridiculous 9,999,999 unique observations. */ #define OBBUFNMSZ 20 /* * serialize_observation * * Create a recoverable version of the current observation * (f->ecurrent). We keep a serialized version of each unique * observation in order that we may resume correctly the fme in the * correct state if eft or fmd crashes and we're restarted. */ static void serialize_observation(struct fme *fp, const char *cls, const struct ipath *ipp) { size_t pkdlen; char tmpbuf[OBBUFNMSZ]; char *pkd = NULL; char *estr; (void) snprintf(tmpbuf, OBBUFNMSZ, "observed%d", fp->uniqobs); estr = ipath2str(cls, ipp); fmd_buf_create(fp->hdl, fp->fmcase, tmpbuf, strlen(estr) + 1); fmd_buf_write(fp->hdl, fp->fmcase, tmpbuf, (void *)estr, strlen(estr) + 1); FREE(estr); if (fp->ecurrent != NULL && fp->ecurrent->nvp != NULL) { (void) snprintf(tmpbuf, OBBUFNMSZ, "observed%d.nvp", fp->uniqobs); if (nvlist_xpack(fp->ecurrent->nvp, &pkd, &pkdlen, NV_ENCODE_XDR, &Eft_nv_hdl) != 0) out(O_DIE|O_SYS, "pack of observed nvl failed"); fmd_buf_create(fp->hdl, fp->fmcase, tmpbuf, pkdlen); fmd_buf_write(fp->hdl, fp->fmcase, tmpbuf, (void *)pkd, pkdlen); FREE(pkd); } fp->uniqobs++; fmd_buf_write(fp->hdl, fp->fmcase, WOBUF_NOBS, (void *)&fp->uniqobs, sizeof (fp->uniqobs)); } /* * init_fme_bufs -- We keep several bits of state about an fme for * use if eft or fmd crashes and we're restarted. */ static void init_fme_bufs(struct fme *fp) { fmd_buf_create(fp->hdl, fp->fmcase, WOBUF_PULL, sizeof (fp->pull)); fmd_buf_write(fp->hdl, fp->fmcase, WOBUF_PULL, (void *)&fp->pull, sizeof (fp->pull)); fmd_buf_create(fp->hdl, fp->fmcase, WOBUF_ID, sizeof (fp->id)); fmd_buf_write(fp->hdl, fp->fmcase, WOBUF_ID, (void *)&fp->id, sizeof (fp->id)); fmd_buf_create(fp->hdl, fp->fmcase, WOBUF_NOBS, sizeof (fp->uniqobs)); fmd_buf_write(fp->hdl, fp->fmcase, WOBUF_NOBS, (void *)&fp->uniqobs, sizeof (fp->uniqobs)); fmd_buf_create(fp->hdl, fp->fmcase, WOBUF_POSTD, sizeof (fp->posted_suspects)); fmd_buf_write(fp->hdl, fp->fmcase, WOBUF_POSTD, (void *)&fp->posted_suspects, sizeof (fp->posted_suspects)); } static void destroy_fme_bufs(struct fme *fp) { char tmpbuf[OBBUFNMSZ]; int o; platform_restore_config(fp->hdl, fp->fmcase); fmd_buf_destroy(fp->hdl, fp->fmcase, WOBUF_CFGLEN); fmd_buf_destroy(fp->hdl, fp->fmcase, WOBUF_CFG); fmd_buf_destroy(fp->hdl, fp->fmcase, WOBUF_PULL); fmd_buf_destroy(fp->hdl, fp->fmcase, WOBUF_ID); fmd_buf_destroy(fp->hdl, fp->fmcase, WOBUF_POSTD); fmd_buf_destroy(fp->hdl, fp->fmcase, WOBUF_NOBS); for (o = 0; o < fp->uniqobs; o++) { (void) snprintf(tmpbuf, OBBUFNMSZ, "observed%d", o); fmd_buf_destroy(fp->hdl, fp->fmcase, tmpbuf); (void) snprintf(tmpbuf, OBBUFNMSZ, "observed%d.nvp", o); fmd_buf_destroy(fp->hdl, fp->fmcase, tmpbuf); } } /* * reconstitute_observations -- convert a case's serialized observations * back into struct events. Returns zero if all observations are * successfully reconstituted. */ static int reconstitute_observations(struct fme *fmep) { struct event *ep; struct node *epnamenp = NULL; size_t pkdlen; char *pkd = NULL; char *tmpbuf = alloca(OBBUFNMSZ); char *sepptr; char *estr; int ocnt; int elen; for (ocnt = 0; ocnt < fmep->uniqobs; ocnt++) { (void) snprintf(tmpbuf, OBBUFNMSZ, "observed%d", ocnt); elen = fmd_buf_size(fmep->hdl, fmep->fmcase, tmpbuf); if (elen == 0) { out(O_ALTFP, "reconstitute_observation: no %s buffer found.", tmpbuf); Undiag_reason = UD_VAL_MISSINGOBS; break; } estr = MALLOC(elen); fmd_buf_read(fmep->hdl, fmep->fmcase, tmpbuf, estr, elen); sepptr = strchr(estr, '@'); if (sepptr == NULL) { out(O_ALTFP, "reconstitute_observation: %s: " "missing @ separator in %s.", tmpbuf, estr); Undiag_reason = UD_VAL_MISSINGPATH; FREE(estr); break; } *sepptr = '\0'; if ((epnamenp = pathstring2epnamenp(sepptr + 1)) == NULL) { out(O_ALTFP, "reconstitute_observation: %s: " "trouble converting path string \"%s\" " "to internal representation.", tmpbuf, sepptr + 1); Undiag_reason = UD_VAL_MISSINGPATH; FREE(estr); break; } /* construct the event */ ep = itree_lookup(fmep->eventtree, stable(estr), ipath(epnamenp)); if (ep == NULL) { out(O_ALTFP, "reconstitute_observation: %s: " "lookup of \"%s\" in itree failed.", tmpbuf, ipath2str(estr, ipath(epnamenp))); Undiag_reason = UD_VAL_BADOBS; tree_free(epnamenp); FREE(estr); break; } tree_free(epnamenp); /* * We may or may not have a saved nvlist for the observation */ (void) snprintf(tmpbuf, OBBUFNMSZ, "observed%d.nvp", ocnt); pkdlen = fmd_buf_size(fmep->hdl, fmep->fmcase, tmpbuf); if (pkdlen != 0) { pkd = MALLOC(pkdlen); fmd_buf_read(fmep->hdl, fmep->fmcase, tmpbuf, pkd, pkdlen); ASSERT(ep->nvp == NULL); if (nvlist_xunpack(pkd, pkdlen, &ep->nvp, &Eft_nv_hdl) != 0) out(O_DIE|O_SYS, "pack of observed nvl failed"); FREE(pkd); } if (ocnt == 0) fmep->e0 = ep; FREE(estr); fmep->ecurrent = ep; ep->count++; /* link it into list of observations seen */ ep->observations = fmep->observations; fmep->observations = ep; } if (ocnt == fmep->uniqobs) { (void) fme_ready(fmep); return (0); } return (1); } /* * restart_fme -- called during eft initialization. Reconstitutes * an in-progress fme. */ void fme_restart(fmd_hdl_t *hdl, fmd_case_t *inprogress) { nvlist_t *defect; struct case_list *bad; struct fme *fmep; struct cfgdata *cfgdata; size_t rawsz; struct event *ep; char *tmpbuf = alloca(OBBUFNMSZ); char *sepptr; char *estr; int elen; struct node *epnamenp = NULL; int init_size; extern int alloc_total(); char *reason; /* * ignore solved or closed cases */ if (fmd_case_solved(hdl, inprogress) || fmd_case_closed(hdl, inprogress)) return; fmep = alloc_fme(); fmep->fmcase = inprogress; fmep->hdl = hdl; if (fmd_buf_size(hdl, inprogress, WOBUF_POSTD) == 0) { out(O_ALTFP, "restart_fme: no saved posted status"); Undiag_reason = UD_VAL_MISSINGINFO; goto badcase; } else { fmd_buf_read(hdl, inprogress, WOBUF_POSTD, (void *)&fmep->posted_suspects, sizeof (fmep->posted_suspects)); } if (fmd_buf_size(hdl, inprogress, WOBUF_ID) == 0) { out(O_ALTFP, "restart_fme: no saved id"); Undiag_reason = UD_VAL_MISSINGINFO; goto badcase; } else { fmd_buf_read(hdl, inprogress, WOBUF_ID, (void *)&fmep->id, sizeof (fmep->id)); } if (Nextid <= fmep->id) Nextid = fmep->id + 1; out(O_ALTFP, "Replay FME %d", fmep->id); if (fmd_buf_size(hdl, inprogress, WOBUF_CFGLEN) != sizeof (size_t)) { out(O_ALTFP, "restart_fme: No config data"); Undiag_reason = UD_VAL_MISSINGINFO; goto badcase; } fmd_buf_read(hdl, inprogress, WOBUF_CFGLEN, (void *)&rawsz, sizeof (size_t)); if ((fmep->e0r = fmd_case_getprincipal(hdl, inprogress)) == NULL) { out(O_ALTFP, "restart_fme: No event zero"); Undiag_reason = UD_VAL_MISSINGZERO; goto badcase; } if (fmd_buf_size(hdl, inprogress, WOBUF_PULL) == 0) { out(O_ALTFP, "restart_fme: no saved wait time"); Undiag_reason = UD_VAL_MISSINGINFO; goto badcase; } else { fmd_buf_read(hdl, inprogress, WOBUF_PULL, (void *)&fmep->pull, sizeof (fmep->pull)); } if (fmd_buf_size(hdl, inprogress, WOBUF_NOBS) == 0) { out(O_ALTFP, "restart_fme: no count of observations"); Undiag_reason = UD_VAL_MISSINGINFO; goto badcase; } else { fmd_buf_read(hdl, inprogress, WOBUF_NOBS, (void *)&fmep->uniqobs, sizeof (fmep->uniqobs)); } (void) snprintf(tmpbuf, OBBUFNMSZ, "observed0"); elen = fmd_buf_size(fmep->hdl, fmep->fmcase, tmpbuf); if (elen == 0) { out(O_ALTFP, "reconstitute_observation: no %s buffer found.", tmpbuf); Undiag_reason = UD_VAL_MISSINGOBS; goto badcase; } estr = MALLOC(elen); fmd_buf_read(fmep->hdl, fmep->fmcase, tmpbuf, estr, elen); sepptr = strchr(estr, '@'); if (sepptr == NULL) { out(O_ALTFP, "reconstitute_observation: %s: " "missing @ separator in %s.", tmpbuf, estr); Undiag_reason = UD_VAL_MISSINGPATH; FREE(estr); goto badcase; } *sepptr = '\0'; if ((epnamenp = pathstring2epnamenp(sepptr + 1)) == NULL) { out(O_ALTFP, "reconstitute_observation: %s: " "trouble converting path string \"%s\" " "to internal representation.", tmpbuf, sepptr + 1); Undiag_reason = UD_VAL_MISSINGPATH; FREE(estr); goto badcase; } (void) prune_propagations(stable(estr), ipath(epnamenp)); tree_free(epnamenp); FREE(estr); init_size = alloc_total(); out(O_ALTFP|O_STAMP, "start config_restore using %d bytes", init_size); cfgdata = MALLOC(sizeof (struct cfgdata)); cfgdata->cooked = NULL; cfgdata->devcache = NULL; cfgdata->devidcache = NULL; cfgdata->tpcache = NULL; cfgdata->cpucache = NULL; cfgdata->raw_refcnt = 1; if (rawsz > 0) { if (fmd_buf_size(hdl, inprogress, WOBUF_CFG) != rawsz) { out(O_ALTFP, "restart_fme: Config data size mismatch"); Undiag_reason = UD_VAL_CFGMISMATCH; goto badcase; } cfgdata->begin = MALLOC(rawsz); cfgdata->end = cfgdata->nextfree = cfgdata->begin + rawsz; fmd_buf_read(hdl, inprogress, WOBUF_CFG, cfgdata->begin, rawsz); } else { cfgdata->begin = cfgdata->end = cfgdata->nextfree = NULL; } config_cook(cfgdata); fmep->config = cfgdata->cooked; config_free(cfgdata); out(O_ALTFP|O_STAMP, "config_restore added %d bytes", alloc_total() - init_size); if ((fmep->eventtree = itree_create(fmep->config)) == NULL) { /* case not properly saved or irretrievable */ out(O_ALTFP, "restart_fme: NULL instance tree"); Undiag_reason = UD_VAL_INSTFAIL; goto badcase; } itree_ptree(O_ALTFP|O_VERB2, fmep->eventtree); if (reconstitute_observations(fmep) != 0) goto badcase; out(O_ALTFP|O_NONL, "FME %d replay observations: ", fmep->id); for (ep = fmep->observations; ep; ep = ep->observations) { out(O_ALTFP|O_NONL, " "); itree_pevent_brief(O_ALTFP|O_NONL, ep); } out(O_ALTFP, NULL); Open_fme_count++; /* give the diagnosis algorithm a shot at the new FME state */ fme_eval(fmep, fmep->e0r); return; badcase: if (fmep->eventtree != NULL) itree_free(fmep->eventtree); if (fmep->config) structconfig_free(fmep->config); destroy_fme_bufs(fmep); FREE(fmep); /* * Since we're unable to restart the case, add it to the undiagable * list and solve and close it as appropriate. */ bad = MALLOC(sizeof (struct case_list)); bad->next = NULL; if (Undiagablecaselist != NULL) bad->next = Undiagablecaselist; Undiagablecaselist = bad; bad->fmcase = inprogress; out(O_ALTFP|O_NONL, "[case %s (unable to restart), ", fmd_case_uuid(hdl, bad->fmcase)); if (fmd_case_solved(hdl, bad->fmcase)) { out(O_ALTFP|O_NONL, "already solved, "); } else { out(O_ALTFP|O_NONL, "solving, "); defect = fmd_nvl_create_fault(hdl, undiag_2defect_str(Undiag_reason), 100, NULL, NULL, NULL); reason = undiag_2reason_str(Undiag_reason, NULL); (void) nvlist_add_string(defect, UNDIAG_REASON, reason); FREE(reason); fmd_case_add_suspect(hdl, bad->fmcase, defect); fmd_case_solve(hdl, bad->fmcase); Undiag_reason = UD_VAL_UNKNOWN; } if (fmd_case_closed(hdl, bad->fmcase)) { out(O_ALTFP, "already closed ]"); } else { out(O_ALTFP, "closing ]"); fmd_case_close(hdl, bad->fmcase); } } /*ARGSUSED*/ static void globals_destructor(void *left, void *right, void *arg) { struct evalue *evp = (struct evalue *)right; if (evp->t == NODEPTR) tree_free((struct node *)(uintptr_t)evp->v); evp->v = (uintptr_t)NULL; FREE(evp); } void destroy_fme(struct fme *f) { stats_delete(f->Rcount); stats_delete(f->Hcallcount); stats_delete(f->Rcallcount); stats_delete(f->Ccallcount); stats_delete(f->Ecallcount); stats_delete(f->Tcallcount); stats_delete(f->Marrowcount); stats_delete(f->diags); if (f->eventtree != NULL) itree_free(f->eventtree); if (f->config) structconfig_free(f->config); lut_free(f->globals, globals_destructor, NULL); FREE(f); } static const char * fme_state2str(enum fme_state s) { switch (s) { case FME_NOTHING: return ("NOTHING"); case FME_WAIT: return ("WAIT"); case FME_CREDIBLE: return ("CREDIBLE"); case FME_DISPROVED: return ("DISPROVED"); case FME_DEFERRED: return ("DEFERRED"); default: return ("UNKNOWN"); } } static int is_problem(enum nametype t) { return (t == N_FAULT || t == N_DEFECT || t == N_UPSET); } static int is_defect(enum nametype t) { return (t == N_DEFECT); } static int is_upset(enum nametype t) { return (t == N_UPSET); } static void fme_print(int flags, struct fme *fmep) { struct event *ep; out(flags, "Fault Management Exercise %d", fmep->id); out(flags, "\t State: %s", fme_state2str(fmep->state)); out(flags|O_NONL, "\t Start time: "); ptree_timeval(flags|O_NONL, &fmep->ull); out(flags, NULL); if (fmep->wull) { out(flags|O_NONL, "\t Wait time: "); ptree_timeval(flags|O_NONL, &fmep->wull); out(flags, NULL); } out(flags|O_NONL, "\t E0: "); if (fmep->e0) itree_pevent_brief(flags|O_NONL, fmep->e0); else out(flags|O_NONL, "NULL"); out(flags, NULL); out(flags|O_NONL, "\tObservations:"); for (ep = fmep->observations; ep; ep = ep->observations) { out(flags|O_NONL, " "); itree_pevent_brief(flags|O_NONL, ep); } out(flags, NULL); out(flags|O_NONL, "\tSuspect list:"); for (ep = fmep->suspects; ep; ep = ep->suspects) { out(flags|O_NONL, " "); itree_pevent_brief(flags|O_NONL, ep); } out(flags, NULL); if (fmep->eventtree != NULL) { out(flags|O_VERB2, "\t Tree:"); itree_ptree(flags|O_VERB2, fmep->eventtree); } } static struct node * pathstring2epnamenp(char *path) { char *sep = "/"; struct node *ret; char *ptr; if ((ptr = strtok(path, sep)) == NULL) out(O_DIE, "pathstring2epnamenp: invalid empty class"); ret = tree_iname(stable(ptr), NULL, 0); while ((ptr = strtok(NULL, sep)) != NULL) ret = tree_name_append(ret, tree_iname(stable(ptr), NULL, 0)); return (ret); } /* * for a given upset sp, increment the corresponding SERD engine. if the * SERD engine trips, return the ename and ipp of the resulting ereport. * returns true if engine tripped and *enamep and *ippp were filled in. */ static int serd_eval(struct fme *fmep, fmd_hdl_t *hdl, fmd_event_t *ffep, fmd_case_t *fmcase, struct event *sp, const char **enamep, const struct ipath **ippp) { struct node *serdinst; char *serdname; char *serdresource; char *serdclass; struct node *nid; struct serd_entry *newentp; int i, serdn = -1, serdincrement = 1, len = 0; char *serdsuffix = NULL, *serdt = NULL; struct evalue *ep; ASSERT(sp->t == N_UPSET); ASSERT(ffep != NULL); if ((ep = (struct evalue *)lut_lookup(sp->serdprops, (void *)"n", (lut_cmp)strcmp)) != NULL) { ASSERT(ep->t == UINT64); serdn = (int)ep->v; } if ((ep = (struct evalue *)lut_lookup(sp->serdprops, (void *)"t", (lut_cmp)strcmp)) != NULL) { ASSERT(ep->t == STRING); serdt = (char *)(uintptr_t)ep->v; } if ((ep = (struct evalue *)lut_lookup(sp->serdprops, (void *)"suffix", (lut_cmp)strcmp)) != NULL) { ASSERT(ep->t == STRING); serdsuffix = (char *)(uintptr_t)ep->v; } if ((ep = (struct evalue *)lut_lookup(sp->serdprops, (void *)"increment", (lut_cmp)strcmp)) != NULL) { ASSERT(ep->t == UINT64); serdincrement = (int)ep->v; } /* * obtain instanced SERD engine from the upset sp. from this * derive serdname, the string used to identify the SERD engine. */ serdinst = eventprop_lookup(sp, L_engine); if (serdinst == NULL) return (-1); len = strlen(serdinst->u.stmt.np->u.event.ename->u.name.s) + 1; if (serdsuffix != NULL) len += strlen(serdsuffix); serdclass = MALLOC(len); if (serdsuffix != NULL) (void) snprintf(serdclass, len, "%s%s", serdinst->u.stmt.np->u.event.ename->u.name.s, serdsuffix); else (void) snprintf(serdclass, len, "%s", serdinst->u.stmt.np->u.event.ename->u.name.s); serdresource = ipath2str(NULL, ipath(serdinst->u.stmt.np->u.event.epname)); len += strlen(serdresource) + 1; serdname = MALLOC(len); (void) snprintf(serdname, len, "%s@%s", serdclass, serdresource); FREE(serdresource); /* handle serd engine "id" property, if there is one */ if ((nid = lut_lookup(serdinst->u.stmt.lutp, (void *)L_id, NULL)) != NULL) { struct evalue *gval; char suffixbuf[200]; char *suffix; char *nserdname; size_t nname; out(O_ALTFP|O_NONL, "serd \"%s\" id: ", serdname); ptree_name_iter(O_ALTFP|O_NONL, nid); ASSERTinfo(nid->t == T_GLOBID, ptree_nodetype2str(nid->t)); if ((gval = lut_lookup(fmep->globals, (void *)nid->u.globid.s, NULL)) == NULL) { out(O_ALTFP, " undefined"); } else if (gval->t == UINT64) { out(O_ALTFP, " %llu", gval->v); (void) sprintf(suffixbuf, "%llu", gval->v); suffix = suffixbuf; } else { out(O_ALTFP, " \"%s\"", (char *)(uintptr_t)gval->v); suffix = (char *)(uintptr_t)gval->v; } nname = strlen(serdname) + strlen(suffix) + 2; nserdname = MALLOC(nname); (void) snprintf(nserdname, nname, "%s:%s", serdname, suffix); FREE(serdname); serdname = nserdname; } /* * if the engine is empty, and we have an override for n/t then * destroy and recreate it. */ if ((serdn != -1 || serdt != NULL) && fmd_serd_exists(hdl, serdname) && fmd_serd_empty(hdl, serdname)) fmd_serd_destroy(hdl, serdname); if (!fmd_serd_exists(hdl, serdname)) { struct node *nN, *nT; const char *s; struct node *nodep; struct config *cp; char *path; uint_t nval; hrtime_t tval; int i; char *ptr; int got_n_override = 0, got_t_override = 0; /* no SERD engine yet, so create it */ nodep = serdinst->u.stmt.np->u.event.epname; path = ipath2str(NULL, ipath(nodep)); cp = config_lookup(fmep->config, path, 0); FREE((void *)path); /* * We allow serd paramaters to be overridden, either from * eft.conf file values (if Serd_Override is set) or from * driver properties (for "serd.io.device" engines). */ if (Serd_Override != NULL) { char *save_ptr, *ptr1, *ptr2, *ptr3; ptr3 = save_ptr = STRDUP(Serd_Override); while (*ptr3 != '\0') { ptr1 = strchr(ptr3, ','); *ptr1 = '\0'; if (strcmp(ptr3, serdclass) == 0) { ptr2 = strchr(ptr1 + 1, ','); *ptr2 = '\0'; nval = atoi(ptr1 + 1); out(O_ALTFP, "serd override %s_n %d", serdclass, nval); ptr3 = strchr(ptr2 + 1, ' '); if (ptr3) *ptr3 = '\0'; ptr = STRDUP(ptr2 + 1); out(O_ALTFP, "serd override %s_t %s", serdclass, ptr); got_n_override = 1; got_t_override = 1; break; } else { ptr2 = strchr(ptr1 + 1, ','); ptr3 = strchr(ptr2 + 1, ' '); if (ptr3 == NULL) break; } ptr3++; } FREE(save_ptr); } if (cp && got_n_override == 0) { /* * convert serd engine class into property name */ char *prop_name = MALLOC(strlen(serdclass) + 3); for (i = 0; i < strlen(serdclass); i++) { if (serdclass[i] == '.') prop_name[i] = '_'; else prop_name[i] = serdclass[i]; } prop_name[i++] = '_'; prop_name[i++] = 'n'; prop_name[i] = '\0'; if (s = config_getprop(cp, prop_name)) { nval = atoi(s); out(O_ALTFP, "serd override %s_n %s", serdclass, s); got_n_override = 1; } prop_name[i - 1] = 't'; if (s = config_getprop(cp, prop_name)) { ptr = STRDUP(s); out(O_ALTFP, "serd override %s_t %s", serdclass, s); got_t_override = 1; } FREE(prop_name); } if (serdn != -1 && got_n_override == 0) { nval = serdn; out(O_ALTFP, "serd override %s_n %d", serdclass, serdn); got_n_override = 1; } if (serdt != NULL && got_t_override == 0) { ptr = STRDUP(serdt); out(O_ALTFP, "serd override %s_t %s", serdclass, serdt); got_t_override = 1; } if (!got_n_override) { nN = lut_lookup(serdinst->u.stmt.lutp, (void *)L_N, NULL); ASSERT(nN->t == T_NUM); nval = (uint_t)nN->u.ull; } if (!got_t_override) { nT = lut_lookup(serdinst->u.stmt.lutp, (void *)L_T, NULL); ASSERT(nT->t == T_TIMEVAL); tval = (hrtime_t)nT->u.ull; } else { const unsigned long long *ullp; const char *suffix; int len; len = strspn(ptr, "0123456789"); suffix = stable(&ptr[len]); ullp = (unsigned long long *)lut_lookup(Timesuffixlut, (void *)suffix, NULL); ptr[len] = '\0'; tval = strtoull(ptr, NULL, 0) * (ullp ? *ullp : 1ll); FREE(ptr); } fmd_serd_create(hdl, serdname, nval, tval); } newentp = MALLOC(sizeof (*newentp)); newentp->ename = stable(serdclass); FREE(serdclass); newentp->ipath = ipath(serdinst->u.stmt.np->u.event.epname); newentp->hdl = hdl; if (lut_lookup(SerdEngines, newentp, (lut_cmp)serd_cmp) == NULL) { SerdEngines = lut_add(SerdEngines, (void *)newentp, (void *)newentp, (lut_cmp)serd_cmp); Serd_need_save = 1; serd_save(); } else { FREE(newentp); } /* * increment SERD engine. if engine fires, reset serd * engine and return trip_strcode if required. */ for (i = 0; i < serdincrement; i++) { if (fmd_serd_record(hdl, serdname, ffep)) { fmd_case_add_serd(hdl, fmcase, serdname); fmd_serd_reset(hdl, serdname); if (ippp) { struct node *tripinst = lut_lookup(serdinst->u.stmt.lutp, (void *)L_trip, NULL); ASSERT(tripinst != NULL); *enamep = tripinst->u.event.ename->u.name.s; *ippp = ipath(tripinst->u.event.epname); out(O_ALTFP|O_NONL, "[engine fired: %s, sending: ", serdname); ipath_print(O_ALTFP|O_NONL, *enamep, *ippp); out(O_ALTFP, "]"); } else { out(O_ALTFP, "[engine fired: %s, no trip]", serdname); } FREE(serdname); return (1); } } FREE(serdname); return (0); } /* * search a suspect list for upsets. feed each upset to serd_eval() and * build up tripped[], an array of ereports produced by the firing of * any SERD engines. then feed each ereport back into * fme_receive_report(). * * returns ntrip, the number of these ereports produced. */ static int upsets_eval(struct fme *fmep, fmd_event_t *ffep) { /* we build an array of tripped ereports that we send ourselves */ struct { const char *ename; const struct ipath *ipp; } *tripped; struct event *sp; int ntrip, nupset, i; /* * count the number of upsets to determine the upper limit on * expected trip ereport strings. remember that one upset can * lead to at most one ereport. */ nupset = 0; for (sp = fmep->suspects; sp; sp = sp->suspects) { if (sp->t == N_UPSET) nupset++; } if (nupset == 0) return (0); /* * get to this point if we have upsets and expect some trip * ereports */ tripped = alloca(sizeof (*tripped) * nupset); bzero((void *)tripped, sizeof (*tripped) * nupset); ntrip = 0; for (sp = fmep->suspects; sp; sp = sp->suspects) if (sp->t == N_UPSET && serd_eval(fmep, fmep->hdl, ffep, fmep->fmcase, sp, &tripped[ntrip].ename, &tripped[ntrip].ipp) == 1) ntrip++; for (i = 0; i < ntrip; i++) { struct event *ep, *nep; struct fme *nfmep; fmd_case_t *fmcase; const struct ipath *ipp; const char *eventstring; int prev_verbose; unsigned long long my_delay = TIMEVAL_EVENTUALLY; enum fme_state state; /* * First try and evaluate a case with the trip ereport plus * all the other ereports that cause the trip. If that fails * to evaluate then try again with just this ereport on its own. */ out(O_ALTFP|O_NONL, "fme_receive_report_serd: "); ipath_print(O_ALTFP|O_NONL, tripped[i].ename, tripped[i].ipp); out(O_ALTFP|O_STAMP, NULL); ep = fmep->e0; eventstring = ep->enode->u.event.ename->u.name.s; ipp = ep->ipp; /* * create a duplicate fme and case */ fmcase = fmd_case_open(fmep->hdl, NULL); out(O_ALTFP|O_NONL, "duplicate fme for event ["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " ]"); if ((nfmep = newfme(eventstring, ipp, fmep->hdl, fmcase, ffep, ep->nvp)) == NULL) { out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " CANNOT DIAGNOSE]"); continue; } Open_fme_count++; nfmep->pull = fmep->pull; init_fme_bufs(nfmep); out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " created FME%d, case %s]", nfmep->id, fmd_case_uuid(nfmep->hdl, nfmep->fmcase)); if (ffep) { fmd_case_setprincipal(nfmep->hdl, nfmep->fmcase, ffep); fmd_case_add_ereport(nfmep->hdl, nfmep->fmcase, ffep); nfmep->e0r = ffep; } /* * add the original ereports */ for (ep = fmep->observations; ep; ep = ep->observations) { eventstring = ep->enode->u.event.ename->u.name.s; ipp = ep->ipp; out(O_ALTFP|O_NONL, "adding event ["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " ]"); nep = itree_lookup(nfmep->eventtree, eventstring, ipp); if (nep->count++ == 0) { nep->observations = nfmep->observations; nfmep->observations = nep; serialize_observation(nfmep, eventstring, ipp); nep->nvp = evnv_dupnvl(ep->nvp); } if (ep->ffep && ep->ffep != ffep) fmd_case_add_ereport(nfmep->hdl, nfmep->fmcase, ep->ffep); stats_counter_bump(nfmep->Rcount); } /* * add the serd trigger ereport */ if ((ep = itree_lookup(nfmep->eventtree, tripped[i].ename, tripped[i].ipp)) == NULL) { /* * The trigger ereport is not in the instance tree. It * was presumably removed by prune_propagations() as * this combination of events is not present in the * rules. */ out(O_ALTFP, "upsets_eval: e0 not in instance tree"); Undiag_reason = UD_VAL_BADEVENTI; goto retry_lone_ereport; } out(O_ALTFP|O_NONL, "adding event ["); ipath_print(O_ALTFP|O_NONL, tripped[i].ename, tripped[i].ipp); out(O_ALTFP, " ]"); nfmep->ecurrent = ep; ep->nvp = NULL; ep->count = 1; ep->observations = nfmep->observations; nfmep->observations = ep; /* * just peek first. */ nfmep->peek = 1; prev_verbose = Verbose; if (Debug == 0) Verbose = 0; lut_walk(nfmep->eventtree, (lut_cb)clear_arrows, (void *)nfmep); state = hypothesise(nfmep, nfmep->e0, nfmep->ull, &my_delay); nfmep->peek = 0; Verbose = prev_verbose; if (state == FME_DISPROVED) { out(O_ALTFP, "upsets_eval: hypothesis disproved"); Undiag_reason = UD_VAL_UNSOLVD; retry_lone_ereport: /* * However the trigger ereport on its own might be * diagnosable, so check for that. Undo the new fme * and case we just created and call fme_receive_report. */ out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, tripped[i].ename, tripped[i].ipp); out(O_ALTFP, " retrying with just trigger ereport]"); itree_free(nfmep->eventtree); nfmep->eventtree = NULL; structconfig_free(nfmep->config); nfmep->config = NULL; destroy_fme_bufs(nfmep); fmd_case_close(nfmep->hdl, nfmep->fmcase); fme_receive_report(fmep->hdl, ffep, tripped[i].ename, tripped[i].ipp, NULL); continue; } /* * and evaluate */ serialize_observation(nfmep, tripped[i].ename, tripped[i].ipp); fme_eval(nfmep, ffep); } return (ntrip); } /* * fme_receive_external_report -- call when an external ereport comes in * * this routine just converts the relevant information from the ereport * into a format used internally and passes it on to fme_receive_report(). */ void fme_receive_external_report(fmd_hdl_t *hdl, fmd_event_t *ffep, nvlist_t *nvl, const char *class) { struct node *epnamenp; fmd_case_t *fmcase; const struct ipath *ipp; nvlist_t *detector = NULL; class = stable(class); /* Get the component path from the ereport */ epnamenp = platform_getpath(nvl); /* See if we ended up without a path. */ if (epnamenp == NULL) { /* See if class permits silent discard on unknown component. */ if (lut_lookup(Ereportenames_discard, (void *)class, NULL)) { out(O_ALTFP|O_VERB2, "Unable to map \"%s\" ereport " "to component path, but silent discard allowed.", class); } else { /* * XFILE: Failure to find a component is bad unless * 'discard_if_config_unknown=1' was specified in the * ereport definition. Indicate undiagnosable. */ Undiag_reason = UD_VAL_NOPATH; fmcase = fmd_case_open(hdl, NULL); /* * We don't have a component path here (which means that * the detector was not in hc-scheme and couldn't be * converted to hc-scheme. Report the raw detector as * the suspect resource if there is one. */ (void) nvlist_lookup_nvlist(nvl, FM_EREPORT_DETECTOR, &detector); publish_undiagnosable(hdl, ffep, fmcase, detector, (char *)class); } return; } ipp = ipath(epnamenp); tree_free(epnamenp); fme_receive_report(hdl, ffep, class, ipp, nvl); } /*ARGSUSED*/ void fme_receive_repair_list(fmd_hdl_t *hdl, fmd_event_t *ffep, nvlist_t *nvl, const char *eventstring) { char *uuid; nvlist_t **nva; uint_t nvc; const struct ipath *ipp; if (nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) != 0 || nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST, &nva, &nvc) != 0) { out(O_ALTFP, "No uuid or fault list for list.repaired event"); return; } out(O_ALTFP, "Processing list.repaired from case %s", uuid); while (nvc-- != 0) { /* * Reset any istat or serd engine associated with this path. */ char *path; if ((ipp = platform_fault2ipath(*nva++)) == NULL) continue; path = ipath2str(NULL, ipp); out(O_ALTFP, "fme_receive_repair_list: resetting state for %s", path); FREE(path); lut_walk(Istats, (lut_cb)istat_counter_reset_cb, (void *)ipp); istat_save(); lut_walk(SerdEngines, (lut_cb)serd_reset_cb, (void *)ipp); serd_save(); } } /*ARGSUSED*/ void fme_receive_topology_change(void) { lut_walk(Istats, (lut_cb)istat_counter_topo_chg_cb, NULL); istat_save(); lut_walk(SerdEngines, (lut_cb)serd_topo_chg_cb, NULL); serd_save(); } static int mark_arrows(struct fme *fmep, struct event *ep, int mark, unsigned long long at_latest_by, unsigned long long *pdelay, int keep); /* ARGSUSED */ static void clear_arrows(struct event *ep, struct event *ep2, struct fme *fmep) { struct bubble *bp; struct arrowlist *ap; ep->cached_state = 0; ep->keep_in_tree = 0; for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { if (bp->t != B_FROM) continue; bp->mark = 0; for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) ap->arrowp->mark = 0; } } static void fme_receive_report(fmd_hdl_t *hdl, fmd_event_t *ffep, const char *eventstring, const struct ipath *ipp, nvlist_t *nvl) { struct event *ep; struct fme *fmep = NULL; struct fme *ofmep = NULL; struct fme *cfmep, *svfmep; int matched = 0; nvlist_t *defect; fmd_case_t *fmcase; char *reason; out(O_ALTFP|O_NONL, "fme_receive_report: "); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP|O_STAMP, NULL); /* decide which FME it goes to */ for (fmep = FMElist; fmep; fmep = fmep->next) { int prev_verbose; unsigned long long my_delay = TIMEVAL_EVENTUALLY; enum fme_state state; nvlist_t *pre_peek_nvp = NULL; if (fmep->overflow) { if (!(fmd_case_closed(fmep->hdl, fmep->fmcase))) ofmep = fmep; continue; } /* * ignore solved or closed cases */ if (fmep->posted_suspects || fmd_case_solved(fmep->hdl, fmep->fmcase) || fmd_case_closed(fmep->hdl, fmep->fmcase)) continue; /* look up event in event tree for this FME */ if ((ep = itree_lookup(fmep->eventtree, eventstring, ipp)) == NULL) continue; /* note observation */ fmep->ecurrent = ep; if (ep->count++ == 0) { /* link it into list of observations seen */ ep->observations = fmep->observations; fmep->observations = ep; ep->nvp = evnv_dupnvl(nvl); } else { /* use new payload values for peek */ pre_peek_nvp = ep->nvp; ep->nvp = evnv_dupnvl(nvl); } /* tell hypothesise() not to mess with suspect list */ fmep->peek = 1; /* don't want this to be verbose (unless Debug is set) */ prev_verbose = Verbose; if (Debug == 0) Verbose = 0; lut_walk(fmep->eventtree, (lut_cb)clear_arrows, (void *)fmep); state = hypothesise(fmep, fmep->e0, fmep->ull, &my_delay); fmep->peek = 0; /* put verbose flag back */ Verbose = prev_verbose; if (state != FME_DISPROVED) { /* found an FME that explains the ereport */ matched++; out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " explained by FME%d]", fmep->id); nvlist_free(pre_peek_nvp); if (ep->count == 1) serialize_observation(fmep, eventstring, ipp); if (ffep) { fmd_case_add_ereport(hdl, fmep->fmcase, ffep); ep->ffep = ffep; } stats_counter_bump(fmep->Rcount); /* re-eval FME */ fme_eval(fmep, ffep); } else { /* not a match, undo noting of observation */ fmep->ecurrent = NULL; if (--ep->count == 0) { /* unlink it from observations */ fmep->observations = ep->observations; ep->observations = NULL; nvlist_free(ep->nvp); ep->nvp = NULL; } else { nvlist_free(ep->nvp); ep->nvp = pre_peek_nvp; } } } if (matched) return; /* explained by at least one existing FME */ /* clean up closed fmes */ cfmep = ClosedFMEs; while (cfmep != NULL) { svfmep = cfmep->next; destroy_fme(cfmep); cfmep = svfmep; } ClosedFMEs = NULL; if (ofmep) { out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " ADDING TO OVERFLOW FME]"); if (ffep) fmd_case_add_ereport(hdl, ofmep->fmcase, ffep); return; } else if (Max_fme && (Open_fme_count >= Max_fme)) { out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " MAX OPEN FME REACHED]"); fmcase = fmd_case_open(hdl, NULL); /* Create overflow fme */ if ((fmep = newfme(eventstring, ipp, hdl, fmcase, ffep, nvl)) == NULL) { out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " CANNOT OPEN OVERFLOW FME]"); return; } Open_fme_count++; init_fme_bufs(fmep); fmep->overflow = B_TRUE; if (ffep) fmd_case_add_ereport(hdl, fmep->fmcase, ffep); Undiag_reason = UD_VAL_MAXFME; defect = fmd_nvl_create_fault(hdl, undiag_2defect_str(Undiag_reason), 100, NULL, NULL, NULL); reason = undiag_2reason_str(Undiag_reason, NULL); (void) nvlist_add_string(defect, UNDIAG_REASON, reason); FREE(reason); fmd_case_add_suspect(hdl, fmep->fmcase, defect); fmd_case_solve(hdl, fmep->fmcase); Undiag_reason = UD_VAL_UNKNOWN; return; } /* open a case */ fmcase = fmd_case_open(hdl, NULL); /* start a new FME */ if ((fmep = newfme(eventstring, ipp, hdl, fmcase, ffep, nvl)) == NULL) { out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " CANNOT DIAGNOSE]"); return; } Open_fme_count++; init_fme_bufs(fmep); out(O_ALTFP|O_NONL, "["); ipath_print(O_ALTFP|O_NONL, eventstring, ipp); out(O_ALTFP, " created FME%d, case %s]", fmep->id, fmd_case_uuid(hdl, fmep->fmcase)); ep = fmep->e0; ASSERT(ep != NULL); /* note observation */ fmep->ecurrent = ep; if (ep->count++ == 0) { /* link it into list of observations seen */ ep->observations = fmep->observations; fmep->observations = ep; ep->nvp = evnv_dupnvl(nvl); serialize_observation(fmep, eventstring, ipp); } else { /* new payload overrides any previous */ nvlist_free(ep->nvp); ep->nvp = evnv_dupnvl(nvl); } stats_counter_bump(fmep->Rcount); if (ffep) { fmd_case_add_ereport(hdl, fmep->fmcase, ffep); fmd_case_setprincipal(hdl, fmep->fmcase, ffep); fmep->e0r = ffep; ep->ffep = ffep; } /* give the diagnosis algorithm a shot at the new FME state */ fme_eval(fmep, ffep); } void fme_status(int flags) { struct fme *fmep; if (FMElist == NULL) { out(flags, "No fault management exercises underway."); return; } for (fmep = FMElist; fmep; fmep = fmep->next) fme_print(flags, fmep); } /* * "indent" routines used mostly for nicely formatted debug output, but also * for sanity checking for infinite recursion bugs. */ #define MAX_INDENT 1024 static const char *indent_s[MAX_INDENT]; static int current_indent; static void indent_push(const char *s) { if (current_indent < MAX_INDENT) indent_s[current_indent++] = s; else out(O_DIE, "unexpected recursion depth (%d)", current_indent); } static void indent_set(const char *s) { current_indent = 0; indent_push(s); } static void indent_pop(void) { if (current_indent > 0) current_indent--; else out(O_DIE, "recursion underflow"); } static void indent(void) { int i; if (!Verbose) return; for (i = 0; i < current_indent; i++) out(O_ALTFP|O_VERB|O_NONL, indent_s[i]); } #define SLNEW 1 #define SLCHANGED 2 #define SLWAIT 3 #define SLDISPROVED 4 static void print_suspects(int circumstance, struct fme *fmep) { struct event *ep; out(O_ALTFP|O_NONL, "["); if (circumstance == SLCHANGED) { out(O_ALTFP|O_NONL, "FME%d diagnosis changed. state: %s, " "suspect list:", fmep->id, fme_state2str(fmep->state)); } else if (circumstance == SLWAIT) { out(O_ALTFP|O_NONL, "FME%d set wait timer %ld ", fmep->id, fmep->timer); ptree_timeval(O_ALTFP|O_NONL, &fmep->wull); } else if (circumstance == SLDISPROVED) { out(O_ALTFP|O_NONL, "FME%d DIAGNOSIS UNKNOWN", fmep->id); } else { out(O_ALTFP|O_NONL, "FME%d DIAGNOSIS PRODUCED:", fmep->id); } if (circumstance == SLWAIT || circumstance == SLDISPROVED) { out(O_ALTFP, "]"); return; } for (ep = fmep->suspects; ep; ep = ep->suspects) { out(O_ALTFP|O_NONL, " "); itree_pevent_brief(O_ALTFP|O_NONL, ep); } out(O_ALTFP, "]"); } static struct node * eventprop_lookup(struct event *ep, const char *propname) { return (lut_lookup(ep->props, (void *)propname, NULL)); } #define MAXDIGITIDX 23 static char numbuf[MAXDIGITIDX + 1]; static int node2uint(struct node *n, uint_t *valp) { struct evalue value; struct lut *globals = NULL; if (n == NULL) return (1); /* * check value.v since we are being asked to convert an unsigned * long long int to an unsigned int */ if (! eval_expr(n, NULL, NULL, &globals, NULL, NULL, 0, &value) || value.t != UINT64 || value.v > (1ULL << 32)) return (1); *valp = (uint_t)value.v; return (0); } static nvlist_t * node2fmri(struct node *n) { nvlist_t **pa, *f, *p; struct node *nc; uint_t depth = 0; char *numstr, *nullbyte; char *failure; int err, i; /* XXX do we need to be able to handle a non-T_NAME node? */ if (n == NULL || n->t != T_NAME) return (NULL); for (nc = n; nc != NULL; nc = nc->u.name.next) { if (nc->u.name.child == NULL || nc->u.name.child->t != T_NUM) break; depth++; } if (nc != NULL) { /* We bailed early, something went wrong */ return (NULL); } if ((err = nvlist_xalloc(&f, NV_UNIQUE_NAME, &Eft_nv_hdl)) != 0) out(O_DIE|O_SYS, "alloc of fmri nvl failed"); pa = alloca(depth * sizeof (nvlist_t *)); for (i = 0; i < depth; i++) pa[i] = NULL; err = nvlist_add_string(f, FM_FMRI_SCHEME, FM_FMRI_SCHEME_HC); err |= nvlist_add_uint8(f, FM_VERSION, FM_HC_SCHEME_VERSION); err |= nvlist_add_string(f, FM_FMRI_HC_ROOT, ""); err |= nvlist_add_uint32(f, FM_FMRI_HC_LIST_SZ, depth); if (err != 0) { failure = "basic construction of FMRI failed"; goto boom; } numbuf[MAXDIGITIDX] = '\0'; nullbyte = &numbuf[MAXDIGITIDX]; i = 0; for (nc = n; nc != NULL; nc = nc->u.name.next) { err = nvlist_xalloc(&p, NV_UNIQUE_NAME, &Eft_nv_hdl); if (err != 0) { failure = "alloc of an hc-pair failed"; goto boom; } err = nvlist_add_string(p, FM_FMRI_HC_NAME, nc->u.name.s); numstr = ulltostr(nc->u.name.child->u.ull, nullbyte); err |= nvlist_add_string(p, FM_FMRI_HC_ID, numstr); if (err != 0) { failure = "construction of an hc-pair failed"; goto boom; } pa[i++] = p; } err = nvlist_add_nvlist_array(f, FM_FMRI_HC_LIST, pa, depth); if (err == 0) { for (i = 0; i < depth; i++) nvlist_free(pa[i]); return (f); } failure = "addition of hc-pair array to FMRI failed"; boom: for (i = 0; i < depth; i++) nvlist_free(pa[i]); nvlist_free(f); out(O_DIE, "%s", failure); /*NOTREACHED*/ return (NULL); } /* an ipath cache entry is an array of these, with s==NULL at the end */ struct ipath { const char *s; /* component name (in stable) */ int i; /* instance number */ }; static nvlist_t * ipath2fmri(struct ipath *ipath) { nvlist_t **pa, *f, *p; uint_t depth = 0; char *numstr, *nullbyte; char *failure; int err, i; struct ipath *ipp; for (ipp = ipath; ipp->s != NULL; ipp++) depth++; if ((err = nvlist_xalloc(&f, NV_UNIQUE_NAME, &Eft_nv_hdl)) != 0) out(O_DIE|O_SYS, "alloc of fmri nvl failed"); pa = alloca(depth * sizeof (nvlist_t *)); for (i = 0; i < depth; i++) pa[i] = NULL; err = nvlist_add_string(f, FM_FMRI_SCHEME, FM_FMRI_SCHEME_HC); err |= nvlist_add_uint8(f, FM_VERSION, FM_HC_SCHEME_VERSION); err |= nvlist_add_string(f, FM_FMRI_HC_ROOT, ""); err |= nvlist_add_uint32(f, FM_FMRI_HC_LIST_SZ, depth); if (err != 0) { failure = "basic construction of FMRI failed"; goto boom; } numbuf[MAXDIGITIDX] = '\0'; nullbyte = &numbuf[MAXDIGITIDX]; i = 0; for (ipp = ipath; ipp->s != NULL; ipp++) { err = nvlist_xalloc(&p, NV_UNIQUE_NAME, &Eft_nv_hdl); if (err != 0) { failure = "alloc of an hc-pair failed"; goto boom; } err = nvlist_add_string(p, FM_FMRI_HC_NAME, ipp->s); numstr = ulltostr(ipp->i, nullbyte); err |= nvlist_add_string(p, FM_FMRI_HC_ID, numstr); if (err != 0) { failure = "construction of an hc-pair failed"; goto boom; } pa[i++] = p; } err = nvlist_add_nvlist_array(f, FM_FMRI_HC_LIST, pa, depth); if (err == 0) { for (i = 0; i < depth; i++) nvlist_free(pa[i]); return (f); } failure = "addition of hc-pair array to FMRI failed"; boom: for (i = 0; i < depth; i++) nvlist_free(pa[i]); nvlist_free(f); out(O_DIE, "%s", failure); /*NOTREACHED*/ return (NULL); } static uint8_t percentof(uint_t part, uint_t whole) { unsigned long long p = part * 1000; return ((p / whole / 10) + (((p / whole % 10) >= 5) ? 1 : 0)); } struct rsl { struct event *suspect; nvlist_t *asru; nvlist_t *fru; nvlist_t *rsrc; }; static void publish_suspects(struct fme *fmep, struct rsl *srl); /* * rslfree -- free internal members of struct rsl not expected to be * freed elsewhere. */ static void rslfree(struct rsl *freeme) { nvlist_free(freeme->asru); nvlist_free(freeme->fru); if (freeme->rsrc != freeme->asru) nvlist_free(freeme->rsrc); } /* * rslcmp -- compare two rsl structures. Use the following * comparisons to establish cardinality: * * 1. Name of the suspect's class. (simple strcmp) * 2. Name of the suspect's ASRU. (trickier, since nvlist) * */ static int rslcmp(const void *a, const void *b) { struct rsl *r1 = (struct rsl *)a; struct rsl *r2 = (struct rsl *)b; int rv; rv = strcmp(r1->suspect->enode->u.event.ename->u.name.s, r2->suspect->enode->u.event.ename->u.name.s); if (rv != 0) return (rv); if (r1->rsrc == NULL && r2->rsrc == NULL) return (0); if (r1->rsrc == NULL) return (-1); if (r2->rsrc == NULL) return (1); return (evnv_cmpnvl(r1->rsrc, r2->rsrc, 0)); } /* * get_resources -- for a given suspect, determine what ASRU, FRU and * RSRC nvlists should be advertised in the final suspect list. */ void get_resources(struct event *sp, struct rsl *rsrcs, struct config *croot) { struct node *asrudef, *frudef; const struct ipath *asrupath, *frupath; nvlist_t *asru = NULL, *fru = NULL; nvlist_t *rsrc = NULL; char *pathstr; /* * First find any ASRU and/or FRU defined in the * initial fault tree. */ asrudef = eventprop_lookup(sp, L_ASRU); frudef = eventprop_lookup(sp, L_FRU); /* * Create ipaths based on those definitions */ asrupath = ipath(asrudef); frupath = ipath(frudef); /* * Allow for platform translations of the FMRIs */ pathstr = ipath2str(NULL, sp->ipp); platform_unit_translate(is_defect(sp->t), croot, TOPO_PROP_RESOURCE, &rsrc, pathstr); FREE(pathstr); pathstr = ipath2str(NULL, asrupath); platform_unit_translate(is_defect(sp->t), croot, TOPO_PROP_ASRU, &asru, pathstr); FREE(pathstr); pathstr = ipath2str(NULL, frupath); platform_unit_translate(is_defect(sp->t), croot, TOPO_PROP_FRU, &fru, pathstr); FREE(pathstr); rsrcs->suspect = sp; rsrcs->asru = asru; rsrcs->fru = fru; rsrcs->rsrc = rsrc; } /* * trim_suspects -- prior to publishing, we may need to remove some * suspects from the list. If we're auto-closing upsets, we don't * want any of those in the published list. If the ASRUs for multiple * defects resolve to the same ASRU (driver) we only want to publish * that as a single suspect. */ static int trim_suspects(struct fme *fmep, struct rsl *begin, struct rsl *begin2, fmd_event_t *ffep) { struct event *ep; struct rsl *rp = begin; struct rsl *rp2 = begin2; int mess_zero_count = 0; int serd_rval; uint_t messval; /* remove any unwanted upsets and populate our array */ for (ep = fmep->psuspects; ep; ep = ep->psuspects) { if (is_upset(ep->t)) continue; serd_rval = serd_eval(fmep, fmep->hdl, ffep, fmep->fmcase, ep, NULL, NULL); if (serd_rval == 0) continue; if (node2uint(eventprop_lookup(ep, L_message), &messval) == 0 && messval == 0) { get_resources(ep, rp2, fmep->config); rp2++; mess_zero_count++; } else { get_resources(ep, rp, fmep->config); rp++; fmep->nsuspects++; } } return (mess_zero_count); } /* * addpayloadprop -- add a payload prop to a problem */ static void addpayloadprop(const char *lhs, struct evalue *rhs, nvlist_t *fault) { nvlist_t *rsrc, *hcs; ASSERT(fault != NULL); ASSERT(lhs != NULL); ASSERT(rhs != NULL); if (nvlist_lookup_nvlist(fault, FM_FAULT_RESOURCE, &rsrc) != 0) out(O_DIE, "cannot add payloadprop \"%s\" to fault", lhs); if (nvlist_lookup_nvlist(rsrc, FM_FMRI_HC_SPECIFIC, &hcs) != 0) { out(O_ALTFP|O_VERB2, "addpayloadprop: create hc_specific"); if (nvlist_xalloc(&hcs, NV_UNIQUE_NAME, &Eft_nv_hdl) != 0) out(O_DIE, "cannot add payloadprop \"%s\" to fault", lhs); if (nvlist_add_nvlist(rsrc, FM_FMRI_HC_SPECIFIC, hcs) != 0) out(O_DIE, "cannot add payloadprop \"%s\" to fault", lhs); nvlist_free(hcs); if (nvlist_lookup_nvlist(rsrc, FM_FMRI_HC_SPECIFIC, &hcs) != 0) out(O_DIE, "cannot add payloadprop \"%s\" to fault", lhs); } else out(O_ALTFP|O_VERB2, "addpayloadprop: reuse hc_specific"); if (rhs->t == UINT64) { out(O_ALTFP|O_VERB2, "addpayloadprop: %s=%llu", lhs, rhs->v); if (nvlist_add_uint64(hcs, lhs, rhs->v) != 0) out(O_DIE, "cannot add payloadprop \"%s\" to fault", lhs); } else { out(O_ALTFP|O_VERB2, "addpayloadprop: %s=\"%s\"", lhs, (char *)(uintptr_t)rhs->v); if (nvlist_add_string(hcs, lhs, (char *)(uintptr_t)rhs->v) != 0) out(O_DIE, "cannot add payloadprop \"%s\" to fault", lhs); } } static char *Istatbuf; static char *Istatbufptr; static int Istatsz; /* * istataddsize -- calculate size of istat and add it to Istatsz */ /*ARGSUSED2*/ static void istataddsize(const struct istat_entry *lhs, struct stats *rhs, void *arg) { int val; ASSERT(lhs != NULL); ASSERT(rhs != NULL); if ((val = stats_counter_value(rhs)) == 0) return; /* skip zero-valued stats */ /* count up the size of the stat name */ Istatsz += ipath2strlen(lhs->ename, lhs->ipath); Istatsz++; /* for the trailing NULL byte */ /* count up the size of the stat value */ Istatsz += snprintf(NULL, 0, "%d", val); Istatsz++; /* for the trailing NULL byte */ } /* * istat2str -- serialize an istat, writing result to *Istatbufptr */ /*ARGSUSED2*/ static void istat2str(const struct istat_entry *lhs, struct stats *rhs, void *arg) { char *str; int len; int val; ASSERT(lhs != NULL); ASSERT(rhs != NULL); if ((val = stats_counter_value(rhs)) == 0) return; /* skip zero-valued stats */ /* serialize the stat name */ str = ipath2str(lhs->ename, lhs->ipath); len = strlen(str); ASSERT(Istatbufptr + len + 1 < &Istatbuf[Istatsz]); (void) strlcpy(Istatbufptr, str, &Istatbuf[Istatsz] - Istatbufptr); Istatbufptr += len; FREE(str); *Istatbufptr++ = '\0'; /* serialize the stat value */ Istatbufptr += snprintf(Istatbufptr, &Istatbuf[Istatsz] - Istatbufptr, "%d", val); *Istatbufptr++ = '\0'; ASSERT(Istatbufptr <= &Istatbuf[Istatsz]); } void istat_save() { if (Istat_need_save == 0) return; /* figure out how big the serialzed info is */ Istatsz = 0; lut_walk(Istats, (lut_cb)istataddsize, NULL); if (Istatsz == 0) { /* no stats to save */ fmd_buf_destroy(Hdl, NULL, WOBUF_ISTATS); return; } /* create the serialized buffer */ Istatbufptr = Istatbuf = MALLOC(Istatsz); lut_walk(Istats, (lut_cb)istat2str, NULL); /* clear out current saved stats */ fmd_buf_destroy(Hdl, NULL, WOBUF_ISTATS); /* write out the new version */ fmd_buf_write(Hdl, NULL, WOBUF_ISTATS, Istatbuf, Istatsz); FREE(Istatbuf); Istat_need_save = 0; } int istat_cmp(struct istat_entry *ent1, struct istat_entry *ent2) { if (ent1->ename != ent2->ename) return (ent2->ename - ent1->ename); if (ent1->ipath != ent2->ipath) return ((char *)ent2->ipath - (char *)ent1->ipath); return (0); } /* * istat-verify -- verify the component associated with a stat still exists * * if the component no longer exists, this routine resets the stat and * returns 0. if the component still exists, it returns 1. */ static int istat_verify(struct node *snp, struct istat_entry *entp) { struct stats *statp; nvlist_t *fmri; fmri = node2fmri(snp->u.event.epname); if (platform_path_exists(fmri)) { nvlist_free(fmri); return (1); } nvlist_free(fmri); /* component no longer in system. zero out the associated stats */ if ((statp = (struct stats *) lut_lookup(Istats, entp, (lut_cmp)istat_cmp)) == NULL || stats_counter_value(statp) == 0) return (0); /* stat is already reset */ Istat_need_save = 1; stats_counter_reset(statp); return (0); } static void istat_bump(struct node *snp, int n) { struct stats *statp; struct istat_entry ent; ASSERT(snp != NULL); ASSERTinfo(snp->t == T_EVENT, ptree_nodetype2str(snp->t)); ASSERT(snp->u.event.epname != NULL); /* class name should be hoisted into a single stable entry */ ASSERT(snp->u.event.ename->u.name.next == NULL); ent.ename = snp->u.event.ename->u.name.s; ent.ipath = ipath(snp->u.event.epname); if (!istat_verify(snp, &ent)) { /* component no longer exists in system, nothing to do */ return; } if ((statp = (struct stats *) lut_lookup(Istats, &ent, (lut_cmp)istat_cmp)) == NULL) { /* need to create the counter */ int cnt = 0; struct node *np; char *sname; char *snamep; struct istat_entry *newentp; /* count up the size of the stat name */ np = snp->u.event.ename; while (np != NULL) { cnt += strlen(np->u.name.s); cnt++; /* for the '.' or '@' */ np = np->u.name.next; } np = snp->u.event.epname; while (np != NULL) { cnt += snprintf(NULL, 0, "%s%llu", np->u.name.s, np->u.name.child->u.ull); cnt++; /* for the '/' or trailing NULL byte */ np = np->u.name.next; } /* build the stat name */ snamep = sname = alloca(cnt); np = snp->u.event.ename; while (np != NULL) { snamep += snprintf(snamep, &sname[cnt] - snamep, "%s", np->u.name.s); np = np->u.name.next; if (np) *snamep++ = '.'; } *snamep++ = '@'; np = snp->u.event.epname; while (np != NULL) { snamep += snprintf(snamep, &sname[cnt] - snamep, "%s%llu", np->u.name.s, np->u.name.child->u.ull); np = np->u.name.next; if (np) *snamep++ = '/'; } *snamep++ = '\0'; /* create the new stat & add it to our list */ newentp = MALLOC(sizeof (*newentp)); *newentp = ent; statp = stats_new_counter(NULL, sname, 0); Istats = lut_add(Istats, (void *)newentp, (void *)statp, (lut_cmp)istat_cmp); } /* if n is non-zero, set that value instead of bumping */ if (n) { stats_counter_reset(statp); stats_counter_add(statp, n); } else stats_counter_bump(statp); Istat_need_save = 1; ipath_print(O_ALTFP|O_VERB2, ent.ename, ent.ipath); out(O_ALTFP|O_VERB2, " %s to value %d", n ? "set" : "incremented", stats_counter_value(statp)); } /*ARGSUSED*/ static void istat_destructor(void *left, void *right, void *arg) { struct istat_entry *entp = (struct istat_entry *)left; struct stats *statp = (struct stats *)right; FREE(entp); stats_delete(statp); } /* * Callback used in a walk of the Istats to reset matching stat counters. */ static void istat_counter_reset_cb(struct istat_entry *entp, struct stats *statp, const struct ipath *ipp) { char *path; if (entp->ipath == ipp) { path = ipath2str(entp->ename, ipp); out(O_ALTFP, "istat_counter_reset_cb: resetting %s", path); FREE(path); stats_counter_reset(statp); Istat_need_save = 1; } } /*ARGSUSED*/ static void istat_counter_topo_chg_cb(struct istat_entry *entp, struct stats *statp, void *unused) { char *path; nvlist_t *fmri; fmri = ipath2fmri((struct ipath *)(entp->ipath)); if (!platform_path_exists(fmri)) { path = ipath2str(entp->ename, entp->ipath); out(O_ALTFP, "istat_counter_topo_chg_cb: not present %s", path); FREE(path); stats_counter_reset(statp); Istat_need_save = 1; } nvlist_free(fmri); } void istat_fini(void) { lut_free(Istats, istat_destructor, NULL); } static char *Serdbuf; static char *Serdbufptr; static int Serdsz; /* * serdaddsize -- calculate size of serd and add it to Serdsz */ /*ARGSUSED*/ static void serdaddsize(const struct serd_entry *lhs, struct stats *rhs, void *arg) { ASSERT(lhs != NULL); /* count up the size of the stat name */ Serdsz += ipath2strlen(lhs->ename, lhs->ipath); Serdsz++; /* for the trailing NULL byte */ } /* * serd2str -- serialize a serd engine, writing result to *Serdbufptr */ /*ARGSUSED*/ static void serd2str(const struct serd_entry *lhs, struct stats *rhs, void *arg) { char *str; int len; ASSERT(lhs != NULL); /* serialize the serd engine name */ str = ipath2str(lhs->ename, lhs->ipath); len = strlen(str); ASSERT(Serdbufptr + len + 1 <= &Serdbuf[Serdsz]); (void) strlcpy(Serdbufptr, str, &Serdbuf[Serdsz] - Serdbufptr); Serdbufptr += len; FREE(str); *Serdbufptr++ = '\0'; ASSERT(Serdbufptr <= &Serdbuf[Serdsz]); } void serd_save() { if (Serd_need_save == 0) return; /* figure out how big the serialzed info is */ Serdsz = 0; lut_walk(SerdEngines, (lut_cb)serdaddsize, NULL); if (Serdsz == 0) { /* no serd engines to save */ fmd_buf_destroy(Hdl, NULL, WOBUF_SERDS); return; } /* create the serialized buffer */ Serdbufptr = Serdbuf = MALLOC(Serdsz); lut_walk(SerdEngines, (lut_cb)serd2str, NULL); /* clear out current saved stats */ fmd_buf_destroy(Hdl, NULL, WOBUF_SERDS); /* write out the new version */ fmd_buf_write(Hdl, NULL, WOBUF_SERDS, Serdbuf, Serdsz); FREE(Serdbuf); Serd_need_save = 0; } int serd_cmp(struct serd_entry *ent1, struct serd_entry *ent2) { if (ent1->ename != ent2->ename) return (ent2->ename - ent1->ename); if (ent1->ipath != ent2->ipath) return ((char *)ent2->ipath - (char *)ent1->ipath); return (0); } void fme_serd_load(fmd_hdl_t *hdl) { int sz; char *sbuf; char *sepptr; char *ptr; struct serd_entry *newentp; struct node *epname; nvlist_t *fmri; char *namestring; if ((sz = fmd_buf_size(hdl, NULL, WOBUF_SERDS)) == 0) return; sbuf = alloca(sz); fmd_buf_read(hdl, NULL, WOBUF_SERDS, sbuf, sz); ptr = sbuf; while (ptr < &sbuf[sz]) { sepptr = strchr(ptr, '@'); *sepptr = '\0'; namestring = ptr; sepptr++; ptr = sepptr; ptr += strlen(ptr); ptr++; /* move past the '\0' separating paths */ epname = pathstring2epnamenp(sepptr); fmri = node2fmri(epname); if (platform_path_exists(fmri)) { newentp = MALLOC(sizeof (*newentp)); newentp->hdl = hdl; newentp->ipath = ipath(epname); newentp->ename = stable(namestring); SerdEngines = lut_add(SerdEngines, (void *)newentp, (void *)newentp, (lut_cmp)serd_cmp); } else Serd_need_save = 1; tree_free(epname); nvlist_free(fmri); } /* save it back again in case some of the paths no longer exist */ serd_save(); } /*ARGSUSED*/ static void serd_destructor(void *left, void *right, void *arg) { struct serd_entry *entp = (struct serd_entry *)left; FREE(entp); } /* * Callback used in a walk of the SerdEngines to reset matching serd engines. */ /*ARGSUSED*/ static void serd_reset_cb(struct serd_entry *entp, void *unused, const struct ipath *ipp) { char *path; if (entp->ipath == ipp) { path = ipath2str(entp->ename, ipp); out(O_ALTFP, "serd_reset_cb: resetting %s", path); fmd_serd_reset(entp->hdl, path); FREE(path); Serd_need_save = 1; } } /*ARGSUSED*/ static void serd_topo_chg_cb(struct serd_entry *entp, void *unused, void *unused2) { char *path; nvlist_t *fmri; fmri = ipath2fmri((struct ipath *)(entp->ipath)); if (!platform_path_exists(fmri)) { path = ipath2str(entp->ename, entp->ipath); out(O_ALTFP, "serd_topo_chg_cb: not present %s", path); fmd_serd_reset(entp->hdl, path); FREE(path); Serd_need_save = 1; } nvlist_free(fmri); } void serd_fini(void) { lut_free(SerdEngines, serd_destructor, NULL); } static void publish_suspects(struct fme *fmep, struct rsl *srl) { struct rsl *rp; nvlist_t *fault; uint8_t cert; uint_t *frs; uint_t frsum, fr; uint_t messval; uint_t retireval; uint_t responseval; struct node *snp; int frcnt, fridx; boolean_t allfaulty = B_TRUE; struct rsl *erl = srl + fmep->nsuspects - 1; /* * sort the array */ qsort(srl, fmep->nsuspects, sizeof (struct rsl), rslcmp); /* sum the fitrates */ frs = alloca(fmep->nsuspects * sizeof (uint_t)); fridx = frcnt = frsum = 0; for (rp = srl; rp <= erl; rp++) { struct node *n; n = eventprop_lookup(rp->suspect, L_FITrate); if (node2uint(n, &fr) != 0) { out(O_DEBUG|O_NONL, "event "); ipath_print(O_DEBUG|O_NONL, rp->suspect->enode->u.event.ename->u.name.s, rp->suspect->ipp); out(O_VERB, " has no FITrate (using 1)"); fr = 1; } else if (fr == 0) { out(O_DEBUG|O_NONL, "event "); ipath_print(O_DEBUG|O_NONL, rp->suspect->enode->u.event.ename->u.name.s, rp->suspect->ipp); out(O_VERB, " has zero FITrate (using 1)"); fr = 1; } frs[fridx++] = fr; frsum += fr; frcnt++; } /* Add them in reverse order of our sort, as fmd reverses order */ for (rp = erl; rp >= srl; rp--) { cert = percentof(frs[--fridx], frsum); fault = fmd_nvl_create_fault(fmep->hdl, rp->suspect->enode->u.event.ename->u.name.s, cert, rp->asru, rp->fru, rp->rsrc); if (fault == NULL) out(O_DIE, "fault creation failed"); /* if "message" property exists, add it to the fault */ if (node2uint(eventprop_lookup(rp->suspect, L_message), &messval) == 0) { out(O_ALTFP, "[FME%d, %s adds message=%d to suspect list]", fmep->id, rp->suspect->enode->u.event.ename->u.name.s, messval); if (nvlist_add_boolean_value(fault, FM_SUSPECT_MESSAGE, (messval) ? B_TRUE : B_FALSE) != 0) { out(O_DIE, "cannot add no-message to fault"); } } /* if "retire" property exists, add it to the fault */ if (node2uint(eventprop_lookup(rp->suspect, L_retire), &retireval) == 0) { out(O_ALTFP, "[FME%d, %s adds retire=%d to suspect list]", fmep->id, rp->suspect->enode->u.event.ename->u.name.s, retireval); if (nvlist_add_boolean_value(fault, FM_SUSPECT_RETIRE, (retireval) ? B_TRUE : B_FALSE) != 0) { out(O_DIE, "cannot add no-retire to fault"); } } /* if "response" property exists, add it to the fault */ if (node2uint(eventprop_lookup(rp->suspect, L_response), &responseval) == 0) { out(O_ALTFP, "[FME%d, %s adds response=%d to suspect list]", fmep->id, rp->suspect->enode->u.event.ename->u.name.s, responseval); if (nvlist_add_boolean_value(fault, FM_SUSPECT_RESPONSE, (responseval) ? B_TRUE : B_FALSE) != 0) { out(O_DIE, "cannot add no-response to fault"); } } /* add any payload properties */ lut_walk(rp->suspect->payloadprops, (lut_cb)addpayloadprop, (void *)fault); rslfree(rp); /* * If "action" property exists, evaluate it; this must be done * before the allfaulty check below since some actions may * modify the asru to be used in fmd_nvl_fmri_has_fault. This * needs to be restructured if any new actions are introduced * that have effects that we do not want to be visible if * we decide not to publish in the dupclose check below. */ if ((snp = eventprop_lookup(rp->suspect, L_action)) != NULL) { struct evalue evalue; out(O_ALTFP|O_NONL, "[FME%d, %s action ", fmep->id, rp->suspect->enode->u.event.ename->u.name.s); ptree_name_iter(O_ALTFP|O_NONL, snp); out(O_ALTFP, "]"); Action_nvl = fault; (void) eval_expr(snp, NULL, NULL, NULL, NULL, NULL, 0, &evalue); } fmd_case_add_suspect(fmep->hdl, fmep->fmcase, fault); /* * check if the asru is already marked as "faulty". */ if (allfaulty) { nvlist_t *asru; out(O_ALTFP|O_VERB, "FME%d dup check ", fmep->id); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, rp->suspect); out(O_ALTFP|O_VERB|O_NONL, " "); if (nvlist_lookup_nvlist(fault, FM_FAULT_ASRU, &asru) != 0) { out(O_ALTFP|O_VERB, "NULL asru"); allfaulty = B_FALSE; } else if (fmd_nvl_fmri_has_fault(fmep->hdl, asru, FMD_HAS_FAULT_ASRU, NULL)) { out(O_ALTFP|O_VERB, "faulty"); } else { out(O_ALTFP|O_VERB, "not faulty"); allfaulty = B_FALSE; } } } if (!allfaulty) { /* * don't update the count stat if all asrus are already * present and unrepaired in the asru cache */ for (rp = erl; rp >= srl; rp--) { struct event *suspect = rp->suspect; if (suspect == NULL) continue; /* if "count" exists, increment the appropriate stat */ if ((snp = eventprop_lookup(suspect, L_count)) != NULL) { out(O_ALTFP|O_NONL, "[FME%d, %s count ", fmep->id, suspect->enode->u.event.ename->u.name.s); ptree_name_iter(O_ALTFP|O_NONL, snp); out(O_ALTFP, "]"); istat_bump(snp, 0); } } istat_save(); /* write out any istat changes */ } } static const char * undiag_2defect_str(int ud) { switch (ud) { case UD_VAL_MISSINGINFO: case UD_VAL_MISSINGOBS: case UD_VAL_MISSINGPATH: case UD_VAL_MISSINGZERO: case UD_VAL_BADOBS: case UD_VAL_CFGMISMATCH: return (UNDIAG_DEFECT_CHKPT); case UD_VAL_BADEVENTI: case UD_VAL_BADEVENTPATH: case UD_VAL_BADEVENTCLASS: case UD_VAL_INSTFAIL: case UD_VAL_NOPATH: case UD_VAL_UNSOLVD: return (UNDIAG_DEFECT_FME); case UD_VAL_MAXFME: return (UNDIAG_DEFECT_LIMIT); case UD_VAL_UNKNOWN: default: return (UNDIAG_DEFECT_UNKNOWN); } } static const char * undiag_2fault_str(int ud) { switch (ud) { case UD_VAL_BADEVENTI: case UD_VAL_BADEVENTPATH: case UD_VAL_BADEVENTCLASS: case UD_VAL_INSTFAIL: case UD_VAL_NOPATH: case UD_VAL_UNSOLVD: return (UNDIAG_FAULT_FME); default: return (NULL); } } static char * undiag_2reason_str(int ud, char *arg) { const char *ptr; char *buf; int with_arg = 0; switch (ud) { case UD_VAL_BADEVENTPATH: ptr = UD_STR_BADEVENTPATH; with_arg = 1; break; case UD_VAL_BADEVENTCLASS: ptr = UD_STR_BADEVENTCLASS; with_arg = 1; break; case UD_VAL_BADEVENTI: ptr = UD_STR_BADEVENTI; with_arg = 1; break; case UD_VAL_BADOBS: ptr = UD_STR_BADOBS; break; case UD_VAL_CFGMISMATCH: ptr = UD_STR_CFGMISMATCH; break; case UD_VAL_INSTFAIL: ptr = UD_STR_INSTFAIL; with_arg = 1; break; case UD_VAL_MAXFME: ptr = UD_STR_MAXFME; break; case UD_VAL_MISSINGINFO: ptr = UD_STR_MISSINGINFO; break; case UD_VAL_MISSINGOBS: ptr = UD_STR_MISSINGOBS; break; case UD_VAL_MISSINGPATH: ptr = UD_STR_MISSINGPATH; break; case UD_VAL_MISSINGZERO: ptr = UD_STR_MISSINGZERO; break; case UD_VAL_NOPATH: ptr = UD_STR_NOPATH; with_arg = 1; break; case UD_VAL_UNSOLVD: ptr = UD_STR_UNSOLVD; break; case UD_VAL_UNKNOWN: default: ptr = UD_STR_UNKNOWN; break; } if (with_arg) { buf = MALLOC(strlen(ptr) + strlen(arg) - 1); (void) sprintf(buf, ptr, arg); } else { buf = MALLOC(strlen(ptr) + 1); (void) sprintf(buf, ptr); } return (buf); } static void publish_undiagnosable(fmd_hdl_t *hdl, fmd_event_t *ffep, fmd_case_t *fmcase, nvlist_t *detector, char *arg) { struct case_list *newcase; nvlist_t *defect, *fault; const char *faultstr; char *reason = undiag_2reason_str(Undiag_reason, arg); out(O_ALTFP, "[undiagnosable ereport received, " "creating and closing a new case (%s)]", reason); newcase = MALLOC(sizeof (struct case_list)); newcase->next = NULL; newcase->fmcase = fmcase; if (Undiagablecaselist != NULL) newcase->next = Undiagablecaselist; Undiagablecaselist = newcase; if (ffep != NULL) fmd_case_add_ereport(hdl, newcase->fmcase, ffep); /* add defect */ defect = fmd_nvl_create_fault(hdl, undiag_2defect_str(Undiag_reason), 50, NULL, NULL, detector); (void) nvlist_add_string(defect, UNDIAG_REASON, reason); (void) nvlist_add_boolean_value(defect, FM_SUSPECT_RETIRE, B_FALSE); (void) nvlist_add_boolean_value(defect, FM_SUSPECT_RESPONSE, B_FALSE); fmd_case_add_suspect(hdl, newcase->fmcase, defect); /* add fault if appropriate */ faultstr = undiag_2fault_str(Undiag_reason); if (faultstr != NULL) { fault = fmd_nvl_create_fault(hdl, faultstr, 50, NULL, NULL, detector); (void) nvlist_add_string(fault, UNDIAG_REASON, reason); (void) nvlist_add_boolean_value(fault, FM_SUSPECT_RETIRE, B_FALSE); (void) nvlist_add_boolean_value(fault, FM_SUSPECT_RESPONSE, B_FALSE); fmd_case_add_suspect(hdl, newcase->fmcase, fault); } FREE(reason); /* solve and close case */ fmd_case_solve(hdl, newcase->fmcase); fmd_case_close(hdl, newcase->fmcase); Undiag_reason = UD_VAL_UNKNOWN; } static void fme_undiagnosable(struct fme *f) { nvlist_t *defect, *fault, *detector = NULL; struct event *ep; char *pathstr; const char *faultstr; char *reason = undiag_2reason_str(Undiag_reason, NULL); out(O_ALTFP, "[solving/closing FME%d, case %s (%s)]", f->id, fmd_case_uuid(f->hdl, f->fmcase), reason); for (ep = f->observations; ep; ep = ep->observations) { if (ep->ffep != f->e0r) fmd_case_add_ereport(f->hdl, f->fmcase, ep->ffep); pathstr = ipath2str(NULL, ipath(platform_getpath(ep->nvp))); platform_unit_translate(0, f->config, TOPO_PROP_RESOURCE, &detector, pathstr); FREE(pathstr); /* add defect */ defect = fmd_nvl_create_fault(f->hdl, undiag_2defect_str(Undiag_reason), 50 / f->uniqobs, NULL, NULL, detector); (void) nvlist_add_string(defect, UNDIAG_REASON, reason); (void) nvlist_add_boolean_value(defect, FM_SUSPECT_RETIRE, B_FALSE); (void) nvlist_add_boolean_value(defect, FM_SUSPECT_RESPONSE, B_FALSE); fmd_case_add_suspect(f->hdl, f->fmcase, defect); /* add fault if appropriate */ faultstr = undiag_2fault_str(Undiag_reason); if (faultstr == NULL) continue; fault = fmd_nvl_create_fault(f->hdl, faultstr, 50 / f->uniqobs, NULL, NULL, detector); (void) nvlist_add_string(fault, UNDIAG_REASON, reason); (void) nvlist_add_boolean_value(fault, FM_SUSPECT_RETIRE, B_FALSE); (void) nvlist_add_boolean_value(fault, FM_SUSPECT_RESPONSE, B_FALSE); fmd_case_add_suspect(f->hdl, f->fmcase, fault); nvlist_free(detector); } FREE(reason); fmd_case_solve(f->hdl, f->fmcase); fmd_case_close(f->hdl, f->fmcase); Undiag_reason = UD_VAL_UNKNOWN; } /* * fme_close_case * * Find the requested case amongst our fmes and close it. Free up * the related fme. */ void fme_close_case(fmd_hdl_t *hdl, fmd_case_t *fmcase) { struct case_list *ucasep, *prevcasep = NULL; struct fme *prev = NULL; struct fme *fmep; for (ucasep = Undiagablecaselist; ucasep; ucasep = ucasep->next) { if (fmcase != ucasep->fmcase) { prevcasep = ucasep; continue; } if (prevcasep == NULL) Undiagablecaselist = Undiagablecaselist->next; else prevcasep->next = ucasep->next; FREE(ucasep); return; } for (fmep = FMElist; fmep; fmep = fmep->next) { if (fmep->hdl == hdl && fmep->fmcase == fmcase) break; prev = fmep; } if (fmep == NULL) { out(O_WARN, "Eft asked to close unrecognized case [%s].", fmd_case_uuid(hdl, fmcase)); return; } if (EFMElist == fmep) EFMElist = prev; if (prev == NULL) FMElist = FMElist->next; else prev->next = fmep->next; fmep->next = NULL; /* Get rid of any timer this fme has set */ if (fmep->wull != 0) fmd_timer_remove(fmep->hdl, fmep->timer); if (ClosedFMEs == NULL) { ClosedFMEs = fmep; } else { fmep->next = ClosedFMEs; ClosedFMEs = fmep; } Open_fme_count--; /* See if we can close the overflow FME */ if (Open_fme_count <= Max_fme) { for (fmep = FMElist; fmep; fmep = fmep->next) { if (fmep->overflow && !(fmd_case_closed(fmep->hdl, fmep->fmcase))) break; } if (fmep != NULL) fmd_case_close(fmep->hdl, fmep->fmcase); } } /* * fme_set_timer() * If the time we need to wait for the given FME is less than the * current timer, kick that old timer out and establish a new one. */ static int fme_set_timer(struct fme *fmep, unsigned long long wull) { out(O_ALTFP|O_VERB|O_NONL, " fme_set_timer: request to wait "); ptree_timeval(O_ALTFP|O_VERB, &wull); if (wull <= fmep->pull) { out(O_ALTFP|O_VERB|O_NONL, "already have waited at least "); ptree_timeval(O_ALTFP|O_VERB, &fmep->pull); out(O_ALTFP|O_VERB, NULL); /* we've waited at least wull already, don't need timer */ return (0); } out(O_ALTFP|O_VERB|O_NONL, " currently "); if (fmep->wull != 0) { out(O_ALTFP|O_VERB|O_NONL, "waiting "); ptree_timeval(O_ALTFP|O_VERB, &fmep->wull); out(O_ALTFP|O_VERB, NULL); } else { out(O_ALTFP|O_VERB|O_NONL, "not waiting"); out(O_ALTFP|O_VERB, NULL); } if (fmep->wull != 0) if (wull >= fmep->wull) /* New timer would fire later than established timer */ return (0); if (fmep->wull != 0) { fmd_timer_remove(fmep->hdl, fmep->timer); } fmep->timer = fmd_timer_install(fmep->hdl, (void *)fmep, fmep->e0r, wull); out(O_ALTFP|O_VERB, "timer set, id is %ld", fmep->timer); fmep->wull = wull; return (1); } void fme_timer_fired(struct fme *fmep, id_t tid) { struct fme *ffmep = NULL; for (ffmep = FMElist; ffmep; ffmep = ffmep->next) if (ffmep == fmep) break; if (ffmep == NULL) { out(O_WARN, "Timer fired for an FME (%p) not in FMEs list.", (void *)fmep); return; } out(O_ALTFP|O_VERB, "Timer fired %lx", tid); fmep->pull = fmep->wull; fmep->wull = 0; fmd_buf_write(fmep->hdl, fmep->fmcase, WOBUF_PULL, (void *)&fmep->pull, sizeof (fmep->pull)); fme_eval(fmep, fmep->e0r); } /* * Preserve the fme's suspect list in its psuspects list, NULLing the * suspects list in the meantime. */ static void save_suspects(struct fme *fmep) { struct event *ep; struct event *nextep; /* zero out the previous suspect list */ for (ep = fmep->psuspects; ep; ep = nextep) { nextep = ep->psuspects; ep->psuspects = NULL; } fmep->psuspects = NULL; /* zero out the suspect list, copying it to previous suspect list */ fmep->psuspects = fmep->suspects; for (ep = fmep->suspects; ep; ep = nextep) { nextep = ep->suspects; ep->psuspects = ep->suspects; ep->suspects = NULL; ep->is_suspect = 0; } fmep->suspects = NULL; fmep->nsuspects = 0; } /* * Retrieve the fme's suspect list from its psuspects list. */ static void restore_suspects(struct fme *fmep) { struct event *ep; struct event *nextep; fmep->nsuspects = 0; fmep->suspects = fmep->psuspects; for (ep = fmep->psuspects; ep; ep = nextep) { fmep->nsuspects++; nextep = ep->psuspects; ep->suspects = ep->psuspects; } } /* * this is what we use to call the Emrys prototype code instead of main() */ static void fme_eval(struct fme *fmep, fmd_event_t *ffep) { struct event *ep; unsigned long long my_delay = TIMEVAL_EVENTUALLY; struct rsl *srl = NULL; struct rsl *srl2 = NULL; int mess_zero_count; int rpcnt; save_suspects(fmep); out(O_ALTFP, "Evaluate FME %d", fmep->id); indent_set(" "); lut_walk(fmep->eventtree, (lut_cb)clear_arrows, (void *)fmep); fmep->state = hypothesise(fmep, fmep->e0, fmep->ull, &my_delay); out(O_ALTFP|O_NONL, "FME%d state: %s, suspect list:", fmep->id, fme_state2str(fmep->state)); for (ep = fmep->suspects; ep; ep = ep->suspects) { out(O_ALTFP|O_NONL, " "); itree_pevent_brief(O_ALTFP|O_NONL, ep); } out(O_ALTFP, NULL); switch (fmep->state) { case FME_CREDIBLE: print_suspects(SLNEW, fmep); (void) upsets_eval(fmep, ffep); /* * we may have already posted suspects in upsets_eval() which * can recurse into fme_eval() again. If so then just return. */ if (fmep->posted_suspects) return; stats_counter_bump(fmep->diags); rpcnt = fmep->nsuspects; save_suspects(fmep); /* * create two lists, one for "message=1" faults and one for * "message=0" faults. If we have a mixture we will generate * two separate suspect lists. */ srl = MALLOC(rpcnt * sizeof (struct rsl)); bzero(srl, rpcnt * sizeof (struct rsl)); srl2 = MALLOC(rpcnt * sizeof (struct rsl)); bzero(srl2, rpcnt * sizeof (struct rsl)); mess_zero_count = trim_suspects(fmep, srl, srl2, ffep); /* * If the resulting suspect list has no members, we're * done so simply close the case. Otherwise sort and publish. */ if (fmep->nsuspects == 0 && mess_zero_count == 0) { out(O_ALTFP, "[FME%d, case %s (all suspects are upsets)]", fmep->id, fmd_case_uuid(fmep->hdl, fmep->fmcase)); fmd_case_close(fmep->hdl, fmep->fmcase); } else if (fmep->nsuspects != 0 && mess_zero_count == 0) { publish_suspects(fmep, srl); out(O_ALTFP, "[solving FME%d, case %s]", fmep->id, fmd_case_uuid(fmep->hdl, fmep->fmcase)); fmd_case_solve(fmep->hdl, fmep->fmcase); } else if (fmep->nsuspects == 0 && mess_zero_count != 0) { fmep->nsuspects = mess_zero_count; publish_suspects(fmep, srl2); out(O_ALTFP, "[solving FME%d, case %s]", fmep->id, fmd_case_uuid(fmep->hdl, fmep->fmcase)); fmd_case_solve(fmep->hdl, fmep->fmcase); } else { struct event *obsp; struct fme *nfmep; publish_suspects(fmep, srl); out(O_ALTFP, "[solving FME%d, case %s]", fmep->id, fmd_case_uuid(fmep->hdl, fmep->fmcase)); fmd_case_solve(fmep->hdl, fmep->fmcase); /* * Got both message=0 and message=1 so create a * duplicate case. Also need a temporary duplicate fme * structure for use by publish_suspects(). */ nfmep = alloc_fme(); nfmep->id = Nextid++; nfmep->hdl = fmep->hdl; nfmep->nsuspects = mess_zero_count; nfmep->fmcase = fmd_case_open(fmep->hdl, NULL); out(O_ALTFP|O_STAMP, "[creating parallel FME%d, case %s]", nfmep->id, fmd_case_uuid(nfmep->hdl, nfmep->fmcase)); Open_fme_count++; if (ffep) { fmd_case_setprincipal(nfmep->hdl, nfmep->fmcase, ffep); fmd_case_add_ereport(nfmep->hdl, nfmep->fmcase, ffep); } for (obsp = fmep->observations; obsp; obsp = obsp->observations) if (obsp->ffep && obsp->ffep != ffep) fmd_case_add_ereport(nfmep->hdl, nfmep->fmcase, obsp->ffep); publish_suspects(nfmep, srl2); out(O_ALTFP, "[solving FME%d, case %s]", nfmep->id, fmd_case_uuid(nfmep->hdl, nfmep->fmcase)); fmd_case_solve(nfmep->hdl, nfmep->fmcase); FREE(nfmep); } FREE(srl); FREE(srl2); restore_suspects(fmep); fmep->posted_suspects = 1; fmd_buf_write(fmep->hdl, fmep->fmcase, WOBUF_POSTD, (void *)&fmep->posted_suspects, sizeof (fmep->posted_suspects)); /* * Now the suspects have been posted, we can clear up * the instance tree as we won't be looking at it again. * Also cancel the timer as the case is now solved. */ if (fmep->wull != 0) { fmd_timer_remove(fmep->hdl, fmep->timer); fmep->wull = 0; } break; case FME_WAIT: ASSERT(my_delay > fmep->ull); (void) fme_set_timer(fmep, my_delay); print_suspects(SLWAIT, fmep); itree_prune(fmep->eventtree); return; case FME_DISPROVED: print_suspects(SLDISPROVED, fmep); Undiag_reason = UD_VAL_UNSOLVD; fme_undiagnosable(fmep); break; } itree_free(fmep->eventtree); fmep->eventtree = NULL; structconfig_free(fmep->config); fmep->config = NULL; destroy_fme_bufs(fmep); } static void indent(void); static int triggered(struct fme *fmep, struct event *ep, int mark); static enum fme_state effects_test(struct fme *fmep, struct event *fault_event, unsigned long long at_latest_by, unsigned long long *pdelay); static enum fme_state requirements_test(struct fme *fmep, struct event *ep, unsigned long long at_latest_by, unsigned long long *pdelay); static enum fme_state causes_test(struct fme *fmep, struct event *ep, unsigned long long at_latest_by, unsigned long long *pdelay); static int checkconstraints(struct fme *fmep, struct arrow *arrowp) { struct constraintlist *ctp; struct evalue value; char *sep = ""; if (arrowp->forever_false) { indent(); out(O_ALTFP|O_VERB|O_NONL, " Forever false constraint: "); for (ctp = arrowp->constraints; ctp != NULL; ctp = ctp->next) { out(O_ALTFP|O_VERB|O_NONL, sep); ptree(O_ALTFP|O_VERB|O_NONL, ctp->cnode, 1, 0); sep = ", "; } out(O_ALTFP|O_VERB, NULL); return (0); } if (arrowp->forever_true) { indent(); out(O_ALTFP|O_VERB|O_NONL, " Forever true constraint: "); for (ctp = arrowp->constraints; ctp != NULL; ctp = ctp->next) { out(O_ALTFP|O_VERB|O_NONL, sep); ptree(O_ALTFP|O_VERB|O_NONL, ctp->cnode, 1, 0); sep = ", "; } out(O_ALTFP|O_VERB, NULL); return (1); } for (ctp = arrowp->constraints; ctp != NULL; ctp = ctp->next) { if (eval_expr(ctp->cnode, NULL, NULL, &fmep->globals, fmep->config, arrowp, 0, &value)) { /* evaluation successful */ if (value.t == UNDEFINED || value.v == 0) { /* known false */ arrowp->forever_false = 1; indent(); out(O_ALTFP|O_VERB|O_NONL, " False constraint: "); ptree(O_ALTFP|O_VERB|O_NONL, ctp->cnode, 1, 0); out(O_ALTFP|O_VERB, NULL); return (0); } } else { /* evaluation unsuccessful -- unknown value */ indent(); out(O_ALTFP|O_VERB|O_NONL, " Deferred constraint: "); ptree(O_ALTFP|O_VERB|O_NONL, ctp->cnode, 1, 0); out(O_ALTFP|O_VERB, NULL); return (1); } } /* known true */ arrowp->forever_true = 1; indent(); out(O_ALTFP|O_VERB|O_NONL, " True constraint: "); for (ctp = arrowp->constraints; ctp != NULL; ctp = ctp->next) { out(O_ALTFP|O_VERB|O_NONL, sep); ptree(O_ALTFP|O_VERB|O_NONL, ctp->cnode, 1, 0); sep = ", "; } out(O_ALTFP|O_VERB, NULL); return (1); } static int triggered(struct fme *fmep, struct event *ep, int mark) { struct bubble *bp; struct arrowlist *ap; int count = 0; stats_counter_bump(fmep->Tcallcount); for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { if (bp->t != B_TO) continue; for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) { /* check count of marks against K in the bubble */ if ((ap->arrowp->mark & mark) && ++count >= bp->nork) return (1); } } return (0); } static int mark_arrows(struct fme *fmep, struct event *ep, int mark, unsigned long long at_latest_by, unsigned long long *pdelay, int keep) { struct bubble *bp; struct arrowlist *ap; unsigned long long overall_delay = TIMEVAL_EVENTUALLY; unsigned long long my_delay; enum fme_state result; int retval = 0; for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { if (bp->t != B_FROM) continue; stats_counter_bump(fmep->Marrowcount); for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) { struct event *ep2 = ap->arrowp->head->myevent; /* * if we're clearing marks, we can avoid doing * all that work evaluating constraints. */ if (mark == 0) { if (ap->arrowp->arrow_marked == 0) continue; ap->arrowp->arrow_marked = 0; ap->arrowp->mark &= ~EFFECTS_COUNTER; if (keep && (ep2->cached_state & (WAIT_EFFECT|CREDIBLE_EFFECT|PARENT_WAIT))) ep2->keep_in_tree = 1; ep2->cached_state &= ~(WAIT_EFFECT|CREDIBLE_EFFECT|PARENT_WAIT); (void) mark_arrows(fmep, ep2, mark, 0, NULL, keep); continue; } ap->arrowp->arrow_marked = 1; if (ep2->cached_state & REQMNTS_DISPROVED) { indent(); out(O_ALTFP|O_VERB|O_NONL, " ALREADY DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); continue; } if (ep2->cached_state & WAIT_EFFECT) { indent(); out(O_ALTFP|O_VERB|O_NONL, " ALREADY EFFECTS WAIT "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); continue; } if (ep2->cached_state & CREDIBLE_EFFECT) { indent(); out(O_ALTFP|O_VERB|O_NONL, " ALREADY EFFECTS CREDIBLE "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); continue; } if ((ep2->cached_state & PARENT_WAIT) && (mark & PARENT_WAIT)) { indent(); out(O_ALTFP|O_VERB|O_NONL, " ALREADY PARENT EFFECTS WAIT "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); continue; } platform_set_payloadnvp(ep2->nvp); if (checkconstraints(fmep, ap->arrowp) == 0) { platform_set_payloadnvp(NULL); indent(); out(O_ALTFP|O_VERB|O_NONL, " CONSTRAINTS FAIL "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); continue; } platform_set_payloadnvp(NULL); ap->arrowp->mark |= EFFECTS_COUNTER; if (!triggered(fmep, ep2, EFFECTS_COUNTER)) { indent(); out(O_ALTFP|O_VERB|O_NONL, " K-COUNT NOT YET MET "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); continue; } ep2->cached_state &= ~PARENT_WAIT; /* * if we've reached an ereport and no propagation time * is specified, use the Hesitate value */ if (ep2->t == N_EREPORT && at_latest_by == 0ULL && ap->arrowp->maxdelay == 0ULL) { out(O_ALTFP|O_VERB|O_NONL, " default wait "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); result = requirements_test(fmep, ep2, Hesitate, &my_delay); } else { result = requirements_test(fmep, ep2, at_latest_by + ap->arrowp->maxdelay, &my_delay); } if (result == FME_WAIT) { retval = WAIT_EFFECT; if (overall_delay > my_delay) overall_delay = my_delay; ep2->cached_state |= WAIT_EFFECT; indent(); out(O_ALTFP|O_VERB|O_NONL, " EFFECTS WAIT "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); indent_push(" E"); if (mark_arrows(fmep, ep2, PARENT_WAIT, at_latest_by, &my_delay, 0) == WAIT_EFFECT) { retval = WAIT_EFFECT; if (overall_delay > my_delay) overall_delay = my_delay; } indent_pop(); } else if (result == FME_DISPROVED) { indent(); out(O_ALTFP|O_VERB|O_NONL, " EFFECTS DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); } else { ep2->cached_state |= mark; indent(); if (mark == CREDIBLE_EFFECT) out(O_ALTFP|O_VERB|O_NONL, " EFFECTS CREDIBLE "); else out(O_ALTFP|O_VERB|O_NONL, " PARENT EFFECTS WAIT "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep2); out(O_ALTFP|O_VERB, NULL); indent_push(" E"); if (mark_arrows(fmep, ep2, mark, at_latest_by, &my_delay, 0) == WAIT_EFFECT) { retval = WAIT_EFFECT; if (overall_delay > my_delay) overall_delay = my_delay; } indent_pop(); } } } if (retval == WAIT_EFFECT) *pdelay = overall_delay; return (retval); } static enum fme_state effects_test(struct fme *fmep, struct event *fault_event, unsigned long long at_latest_by, unsigned long long *pdelay) { struct event *error_event; enum fme_state return_value = FME_CREDIBLE; unsigned long long overall_delay = TIMEVAL_EVENTUALLY; unsigned long long my_delay; stats_counter_bump(fmep->Ecallcount); indent_push(" E"); indent(); out(O_ALTFP|O_VERB|O_NONL, "->"); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, fault_event); out(O_ALTFP|O_VERB, NULL); if (mark_arrows(fmep, fault_event, CREDIBLE_EFFECT, at_latest_by, &my_delay, 0) == WAIT_EFFECT) { return_value = FME_WAIT; if (overall_delay > my_delay) overall_delay = my_delay; } for (error_event = fmep->observations; error_event; error_event = error_event->observations) { indent(); out(O_ALTFP|O_VERB|O_NONL, " "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, error_event); if (!(error_event->cached_state & CREDIBLE_EFFECT)) { if (error_event->cached_state & (PARENT_WAIT|WAIT_EFFECT)) { out(O_ALTFP|O_VERB, " NOT YET triggered"); continue; } return_value = FME_DISPROVED; out(O_ALTFP|O_VERB, " NOT triggered"); break; } else { out(O_ALTFP|O_VERB, " triggered"); } } if (return_value == FME_DISPROVED) { (void) mark_arrows(fmep, fault_event, 0, 0, NULL, 0); } else { fault_event->keep_in_tree = 1; (void) mark_arrows(fmep, fault_event, 0, 0, NULL, 1); } indent(); out(O_ALTFP|O_VERB|O_NONL, "<-EFFECTS %s ", fme_state2str(return_value)); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, fault_event); out(O_ALTFP|O_VERB, NULL); indent_pop(); if (return_value == FME_WAIT) *pdelay = overall_delay; return (return_value); } static enum fme_state requirements_test(struct fme *fmep, struct event *ep, unsigned long long at_latest_by, unsigned long long *pdelay) { int waiting_events; int credible_events; int deferred_events; enum fme_state return_value = FME_CREDIBLE; unsigned long long overall_delay = TIMEVAL_EVENTUALLY; unsigned long long arrow_delay; unsigned long long my_delay; struct event *ep2; struct bubble *bp; struct arrowlist *ap; if (ep->cached_state & REQMNTS_CREDIBLE) { indent(); out(O_ALTFP|O_VERB|O_NONL, " REQMNTS ALREADY CREDIBLE "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, NULL); return (FME_CREDIBLE); } if (ep->cached_state & REQMNTS_DISPROVED) { indent(); out(O_ALTFP|O_VERB|O_NONL, " REQMNTS ALREADY DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, NULL); return (FME_DISPROVED); } if (ep->cached_state & REQMNTS_WAIT) { indent(); *pdelay = ep->cached_delay; out(O_ALTFP|O_VERB|O_NONL, " REQMNTS ALREADY WAIT "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB|O_NONL, ", wait for: "); ptree_timeval(O_ALTFP|O_VERB|O_NONL, &at_latest_by); out(O_ALTFP|O_VERB, NULL); return (FME_WAIT); } stats_counter_bump(fmep->Rcallcount); indent_push(" R"); indent(); out(O_ALTFP|O_VERB|O_NONL, "->"); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB|O_NONL, ", at latest by: "); ptree_timeval(O_ALTFP|O_VERB|O_NONL, &at_latest_by); out(O_ALTFP|O_VERB, NULL); if (ep->t == N_EREPORT) { if (ep->count == 0) { if (fmep->pull >= at_latest_by) { return_value = FME_DISPROVED; } else { ep->cached_delay = *pdelay = at_latest_by; return_value = FME_WAIT; } } indent(); switch (return_value) { case FME_CREDIBLE: ep->cached_state |= REQMNTS_CREDIBLE; out(O_ALTFP|O_VERB|O_NONL, "<-REQMNTS CREDIBLE "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); break; case FME_DISPROVED: ep->cached_state |= REQMNTS_DISPROVED; out(O_ALTFP|O_VERB|O_NONL, "<-REQMNTS DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); break; case FME_WAIT: ep->cached_state |= REQMNTS_WAIT; out(O_ALTFP|O_VERB|O_NONL, "<-REQMNTS WAIT "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB|O_NONL, " to "); ptree_timeval(O_ALTFP|O_VERB|O_NONL, &at_latest_by); break; default: out(O_DIE, "requirements_test: unexpected fme_state"); break; } out(O_ALTFP|O_VERB, NULL); indent_pop(); return (return_value); } /* this event is not a report, descend the tree */ for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { int n; if (bp->t != B_FROM) continue; n = bp->nork; credible_events = 0; waiting_events = 0; deferred_events = 0; arrow_delay = TIMEVAL_EVENTUALLY; /* * n is -1 for 'A' so adjust it. * XXX just count up the arrows for now. */ if (n < 0) { n = 0; for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) n++; indent(); out(O_ALTFP|O_VERB, " Bubble Counted N=%d", n); } else { indent(); out(O_ALTFP|O_VERB, " Bubble N=%d", n); } if (n == 0) continue; if (!(bp->mark & (BUBBLE_ELIDED|BUBBLE_OK))) { for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) { ep2 = ap->arrowp->head->myevent; platform_set_payloadnvp(ep2->nvp); (void) checkconstraints(fmep, ap->arrowp); if (!ap->arrowp->forever_false) { /* * if all arrows are invalidated by the * constraints, then we should elide the * whole bubble to be consistant with * the tree creation time behaviour */ bp->mark |= BUBBLE_OK; platform_set_payloadnvp(NULL); break; } platform_set_payloadnvp(NULL); } } for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) { ep2 = ap->arrowp->head->myevent; if (n <= credible_events) break; ap->arrowp->mark |= REQMNTS_COUNTER; if (triggered(fmep, ep2, REQMNTS_COUNTER)) /* XXX adding max timevals! */ switch (requirements_test(fmep, ep2, at_latest_by + ap->arrowp->maxdelay, &my_delay)) { case FME_DEFERRED: deferred_events++; break; case FME_CREDIBLE: credible_events++; break; case FME_DISPROVED: break; case FME_WAIT: if (my_delay < arrow_delay) arrow_delay = my_delay; waiting_events++; break; default: out(O_DIE, "Bug in requirements_test."); } else deferred_events++; } if (!(bp->mark & BUBBLE_OK) && waiting_events == 0) { bp->mark |= BUBBLE_ELIDED; continue; } indent(); out(O_ALTFP|O_VERB, " Credible: %d Waiting %d", credible_events + deferred_events, waiting_events); if (credible_events + deferred_events + waiting_events < n) { /* Can never meet requirements */ ep->cached_state |= REQMNTS_DISPROVED; indent(); out(O_ALTFP|O_VERB|O_NONL, "<-REQMNTS DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, NULL); indent_pop(); return (FME_DISPROVED); } if (credible_events + deferred_events < n) { /* will have to wait */ /* wait time is shortest known */ if (arrow_delay < overall_delay) overall_delay = arrow_delay; return_value = FME_WAIT; } else if (credible_events < n) { if (return_value != FME_WAIT) return_value = FME_DEFERRED; } } /* * don't mark as FME_DEFERRED. If this event isn't reached by another * path, then this will be considered FME_CREDIBLE. But if it is * reached by a different path so the K-count is met, then might * get overridden by FME_WAIT or FME_DISPROVED. */ if (return_value == FME_WAIT) { ep->cached_state |= REQMNTS_WAIT; ep->cached_delay = *pdelay = overall_delay; } else if (return_value == FME_CREDIBLE) { ep->cached_state |= REQMNTS_CREDIBLE; } indent(); out(O_ALTFP|O_VERB|O_NONL, "<-REQMNTS %s ", fme_state2str(return_value)); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, NULL); indent_pop(); return (return_value); } static enum fme_state causes_test(struct fme *fmep, struct event *ep, unsigned long long at_latest_by, unsigned long long *pdelay) { unsigned long long overall_delay = TIMEVAL_EVENTUALLY; unsigned long long my_delay; int credible_results = 0; int waiting_results = 0; enum fme_state fstate; struct event *tail_event; struct bubble *bp; struct arrowlist *ap; int k = 1; stats_counter_bump(fmep->Ccallcount); indent_push(" C"); indent(); out(O_ALTFP|O_VERB|O_NONL, "->"); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, NULL); for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { if (bp->t != B_TO) continue; k = bp->nork; /* remember the K value */ for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) { int do_not_follow = 0; /* * if we get to the same event multiple times * only worry about the first one. */ if (ap->arrowp->tail->myevent->cached_state & CAUSES_TESTED) { indent(); out(O_ALTFP|O_VERB|O_NONL, " causes test already run for "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ap->arrowp->tail->myevent); out(O_ALTFP|O_VERB, NULL); continue; } /* * see if false constraint prevents us * from traversing this arrow */ platform_set_payloadnvp(ep->nvp); if (checkconstraints(fmep, ap->arrowp) == 0) do_not_follow = 1; platform_set_payloadnvp(NULL); if (do_not_follow) { indent(); out(O_ALTFP|O_VERB|O_NONL, " False arrow from "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ap->arrowp->tail->myevent); out(O_ALTFP|O_VERB, NULL); continue; } ap->arrowp->tail->myevent->cached_state |= CAUSES_TESTED; tail_event = ap->arrowp->tail->myevent; fstate = hypothesise(fmep, tail_event, at_latest_by, &my_delay); switch (fstate) { case FME_WAIT: if (my_delay < overall_delay) overall_delay = my_delay; waiting_results++; break; case FME_CREDIBLE: credible_results++; break; case FME_DISPROVED: break; default: out(O_DIE, "Bug in causes_test"); } } } /* compare against K */ if (credible_results + waiting_results < k) { indent(); out(O_ALTFP|O_VERB|O_NONL, "<-CAUSES DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, NULL); indent_pop(); return (FME_DISPROVED); } if (waiting_results != 0) { *pdelay = overall_delay; indent(); out(O_ALTFP|O_VERB|O_NONL, "<-CAUSES WAIT "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB|O_NONL, " to "); ptree_timeval(O_ALTFP|O_VERB|O_NONL, &at_latest_by); out(O_ALTFP|O_VERB, NULL); indent_pop(); return (FME_WAIT); } indent(); out(O_ALTFP|O_VERB|O_NONL, "<-CAUSES CREDIBLE "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, NULL); indent_pop(); return (FME_CREDIBLE); } static enum fme_state hypothesise(struct fme *fmep, struct event *ep, unsigned long long at_latest_by, unsigned long long *pdelay) { enum fme_state rtr, otr; unsigned long long my_delay; unsigned long long overall_delay = TIMEVAL_EVENTUALLY; stats_counter_bump(fmep->Hcallcount); indent_push(" H"); indent(); out(O_ALTFP|O_VERB|O_NONL, "->"); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB|O_NONL, ", at latest by: "); ptree_timeval(O_ALTFP|O_VERB|O_NONL, &at_latest_by); out(O_ALTFP|O_VERB, NULL); rtr = requirements_test(fmep, ep, at_latest_by, &my_delay); if ((rtr == FME_WAIT) && (my_delay < overall_delay)) overall_delay = my_delay; if (rtr != FME_DISPROVED) { if (is_problem(ep->t)) { otr = effects_test(fmep, ep, at_latest_by, &my_delay); if (otr != FME_DISPROVED) { if (fmep->peek == 0 && ep->is_suspect == 0) { ep->suspects = fmep->suspects; ep->is_suspect = 1; fmep->suspects = ep; fmep->nsuspects++; } } } else otr = causes_test(fmep, ep, at_latest_by, &my_delay); if ((otr == FME_WAIT) && (my_delay < overall_delay)) overall_delay = my_delay; if ((otr != FME_DISPROVED) && ((rtr == FME_WAIT) || (otr == FME_WAIT))) *pdelay = overall_delay; } if (rtr == FME_DISPROVED) { indent(); out(O_ALTFP|O_VERB|O_NONL, "<-DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, " (doesn't meet requirements)"); indent_pop(); return (FME_DISPROVED); } if ((otr == FME_DISPROVED) && is_problem(ep->t)) { indent(); out(O_ALTFP|O_VERB|O_NONL, "<-DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, " (doesn't explain all reports)"); indent_pop(); return (FME_DISPROVED); } if (otr == FME_DISPROVED) { indent(); out(O_ALTFP|O_VERB|O_NONL, "<-DISPROVED "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, " (causes are not credible)"); indent_pop(); return (FME_DISPROVED); } if ((rtr == FME_WAIT) || (otr == FME_WAIT)) { indent(); out(O_ALTFP|O_VERB|O_NONL, "<-WAIT "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB|O_NONL, " to "); ptree_timeval(O_ALTFP|O_VERB|O_NONL, &overall_delay); out(O_ALTFP|O_VERB, NULL); indent_pop(); return (FME_WAIT); } indent(); out(O_ALTFP|O_VERB|O_NONL, "<-CREDIBLE "); itree_pevent_brief(O_ALTFP|O_VERB|O_NONL, ep); out(O_ALTFP|O_VERB, NULL); indent_pop(); return (FME_CREDIBLE); } /* * fme_istat_load -- reconstitute any persistent istats */ void fme_istat_load(fmd_hdl_t *hdl) { int sz; char *sbuf; char *ptr; if ((sz = fmd_buf_size(hdl, NULL, WOBUF_ISTATS)) == 0) { out(O_ALTFP, "fme_istat_load: No stats"); return; } sbuf = alloca(sz); fmd_buf_read(hdl, NULL, WOBUF_ISTATS, sbuf, sz); /* * pick apart the serialized stats * * format is: * , '@', , '\0', , '\0' * for example: * "stat.first@stat0/path0\02\0stat.second@stat0/path1\023\0" * * since this is parsing our own serialized data, any parsing issues * are fatal, so we check for them all with ASSERT() below. */ ptr = sbuf; while (ptr < &sbuf[sz]) { char *sepptr; struct node *np; int val; sepptr = strchr(ptr, '@'); ASSERT(sepptr != NULL); *sepptr = '\0'; /* construct the event */ np = newnode(T_EVENT, NULL, 0); np->u.event.ename = newnode(T_NAME, NULL, 0); np->u.event.ename->u.name.t = N_STAT; np->u.event.ename->u.name.s = stable(ptr); np->u.event.ename->u.name.it = IT_ENAME; np->u.event.ename->u.name.last = np->u.event.ename; ptr = sepptr + 1; ASSERT(ptr < &sbuf[sz]); ptr += strlen(ptr); ptr++; /* move past the '\0' separating path from value */ ASSERT(ptr < &sbuf[sz]); ASSERT(isdigit(*ptr)); val = atoi(ptr); ASSERT(val > 0); ptr += strlen(ptr); ptr++; /* move past the final '\0' for this entry */ np->u.event.epname = pathstring2epnamenp(sepptr + 1); ASSERT(np->u.event.epname != NULL); istat_bump(np, val); tree_free(np); } istat_save(); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * fme.h -- public definitions for fme module * * this module supports the management of a "fault management exercise". */ #ifndef _EFT_FME_H #define _EFT_FME_H #include #ifdef __cplusplus extern "C" { #endif #define UNDIAG_DEFECT_CHKPT "defect.sunos.eft.undiag.checkpoint" #define UNDIAG_DEFECT_FME "defect.sunos.eft.unexpected_telemetry" #define UNDIAG_DEFECT_LIMIT "defect.sunos.eft.undiag.limit" #define UNDIAG_DEFECT_UNKNOWN "defect.sunos.eft.undiag.unknown" #define UNDIAG_FAULT_FME "fault.sunos.eft.unexpected_telemetry" #define UNDIAG_REASON "reason" /* Undiagnosable reason values */ #define UD_VAL_UNKNOWN 0 #define UD_VAL_BADEVENTI 1 #define UD_VAL_BADOBS 2 #define UD_VAL_CFGMISMATCH 3 #define UD_VAL_INSTFAIL 4 #define UD_VAL_MAXFME 5 #define UD_VAL_MISSINGINFO 6 #define UD_VAL_MISSINGOBS 7 #define UD_VAL_MISSINGPATH 8 #define UD_VAL_MISSINGZERO 9 #define UD_VAL_NOPATH 10 #define UD_VAL_UNSOLVD 11 #define UD_VAL_BADEVENTPATH 12 #define UD_VAL_BADEVENTCLASS 13 /* Undiagnosable reason strings */ #define UD_STR_UNKNOWN "undiagnosable reason unknown" #define UD_STR_MISSINGPATH "bad or missing path in persisted observation" #define UD_STR_MISSINGINFO "buffer persisting case info is AWOL" #define UD_STR_MISSINGZERO "buffer persisting principal ereport is AWOL" #define UD_STR_CFGMISMATCH "persisted config buffer size != actual size" #define UD_STR_MISSINGOBS "buffer persisting an observation is AWOL" #define UD_STR_BADEVENTI "%s not found in pruned instance tree" #define UD_STR_INSTFAIL "%s pruned instance tree is empty" #define UD_STR_UNSOLVD "all hypotheses were disproved" #define UD_STR_BADOBS "persisted observation not found in instance tree" #define UD_STR_NOPATH "no valid path to component was found in %s" #define UD_STR_MAXFME "reached the maximum number of open FMEs (maxfme)" #define UD_STR_BADEVENTPATH "%s path was not in topology" #define UD_STR_BADEVENTCLASS "%s class and path are incompatible" #define WOBUF_CFGLEN "rawcfglen" #define WOBUF_POSTD "posted" #define WOBUF_NOBS "observations" #define WOBUF_PULL "timewaited" #define WOBUF_CFG "rawcfgdata" #define WOBUF_ID "fmeid" #define WOBUF_ISTATS "istats" #define WOBUF_SERDS "serds" extern struct lut *Istats; /* instanced stats a la "count=" */ extern struct lut *SerdEngines; struct fme; void fme_receive_external_report(fmd_hdl_t *hdl, fmd_event_t *ffep, nvlist_t *nvl, const char *eventstring); void fme_receive_topology_change(void); void fme_receive_repair_list(fmd_hdl_t *hdl, fmd_event_t *ffep, nvlist_t *nvl, const char *eventstring); void fme_restart(fmd_hdl_t *hdl, fmd_case_t *inprogress); void fme_istat_load(fmd_hdl_t *hdl); void fme_serd_load(fmd_hdl_t *hdl); void fme_close_case(fmd_hdl_t *hdl, fmd_case_t *fmcase); void fme_timer_fired(struct fme *, id_t); void fme_status(int flags); void fme_fini(void); void istat_fini(void); struct istat_entry { const char *ename; const struct ipath *ipath; }; int istat_cmp(struct istat_entry *ent1, struct istat_entry *ent2); void serd_fini(void); struct serd_entry { const char *ename; const struct ipath *ipath; fmd_hdl_t *hdl; }; int serd_cmp(struct serd_entry *ent1, struct serd_entry *ent2); #ifdef __cplusplus } #endif #endif /* _EFT_FME_H */ /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * iexpr.c -- instanced expression cache module * * this module provides a cache of fully instantized expressions. */ #include #include #include "alloc.h" #include "out.h" #include "lut.h" #include "tree.h" #include "ptree.h" #include "itree.h" #include "ipath.h" #include "iexpr.h" #include "stats.h" #include "eval.h" #include "config.h" #define IEXPRSZ 1024 /* hash table size */ static struct stats *Niexpr; /* the cache is a hash table of these structs */ static struct iexpr { struct node *np; struct iexpr *next; /* next entry in hash bucket */ int count; } *Cache[IEXPRSZ]; /* * iexpr_init -- initialize the iexpr module */ void iexpr_init(void) { Niexpr = stats_new_counter("iexpr.niexpr", "iexpr cache entries", 1); } /* * iexpr_hash -- produce a simple hash from an instanced expression tree */ static unsigned iexpr_hash(struct node *np) { if (np == NULL) return (1); switch (np->t) { case T_GLOBID: return ((uintptr_t)np->u.globid.s); case T_ASSIGN: case T_CONDIF: case T_CONDELSE: case T_NE: case T_EQ: case T_LT: case T_LE: case T_GT: case T_GE: case T_BITAND: case T_BITOR: case T_BITXOR: case T_BITNOT: case T_LSHIFT: case T_RSHIFT: case T_LIST: case T_AND: case T_OR: case T_NOT: case T_ADD: case T_SUB: case T_MUL: case T_DIV: case T_MOD: return ((int)np->t * (iexpr_hash(np->u.expr.left) + iexpr_hash(np->u.expr.right))); case T_NAME: return ((uintptr_t)np->u.name.s); case T_EVENT: return (iexpr_hash(np->u.event.ename) + iexpr_hash(np->u.event.epname)); case T_FUNC: return ((uintptr_t)np->u.func.s + iexpr_hash(np->u.func.arglist)); case T_QUOTE: return ((uintptr_t)np->u.quote.s); case T_NUM: case T_TIMEVAL: return ((int)np->u.ull); default: outfl(O_DIE, np->file, np->line, "iexpr_hash: unexpected node type: %s", ptree_nodetype2str(np->t)); } /*NOTREACHED*/ return (1); } /* * iexpr_cmp -- compare two instanced expression trees */ static int iexpr_cmp(struct node *np1, struct node *np2) { int diff; if (np1 == np2) return (0); if (np1 == NULL) return (1); if (np2 == NULL) return (-1); if (np1->t != np2->t) return (np2->t - np1->t); /* types match, need to see additional info matches */ switch (np1->t) { case T_GLOBID: return (np2->u.globid.s - np1->u.globid.s); case T_ASSIGN: case T_CONDIF: case T_CONDELSE: case T_NE: case T_EQ: case T_LT: case T_LE: case T_GT: case T_GE: case T_BITAND: case T_BITOR: case T_BITXOR: case T_BITNOT: case T_LSHIFT: case T_RSHIFT: case T_LIST: case T_AND: case T_OR: case T_NOT: case T_ADD: case T_SUB: case T_MUL: case T_DIV: case T_MOD: diff = iexpr_cmp(np1->u.expr.left, np2->u.expr.left); if (diff != 0) return (diff); return (iexpr_cmp(np1->u.expr.right, np2->u.expr.right)); case T_NAME: if (np2->u.name.s != np1->u.name.s) return (np2->u.name.s - np1->u.name.s); diff = iexpr_cmp(np1->u.name.child, np2->u.name.child); if (diff != 0) return (diff); return (iexpr_cmp(np1->u.name.next, np2->u.name.next)); case T_EVENT: diff = iexpr_cmp(np1->u.event.ename, np2->u.event.ename); if (diff != 0) return (diff); return (iexpr_cmp(np1->u.event.epname, np2->u.event.epname)); case T_FUNC: if (np1->u.func.s != np2->u.func.s) return (np2->u.func.s - np1->u.func.s); return (iexpr_cmp(np1->u.func.arglist, np2->u.func.arglist)); case T_QUOTE: return (np2->u.quote.s - np1->u.quote.s); case T_NUM: case T_TIMEVAL: if (np2->u.ull > np1->u.ull) return (1); else if (np1->u.ull > np2->u.ull) return (-1); else return (0); default: outfl(O_DIE, np1->file, np1->line, "iexpr_cmp: unexpected node type: %s", ptree_nodetype2str(np1->t)); } /*NOTREACHED*/ return (0); } /* * iexpr -- find instanced expr in cache, or add it if necessary */ struct node * iexpr(struct node *np) { unsigned idx = iexpr_hash(np) % IEXPRSZ; struct iexpr *bucketp = Cache[idx]; struct iexpr *cp; /* search cache */ for (cp = bucketp; cp != NULL; cp = cp->next) if (iexpr_cmp(cp->np, np) == 0) { /* found it */ tree_free(np); cp->count++; return (cp->np); } /* allocate new cache entry */ cp = MALLOC(sizeof (*cp)); cp->np = np; cp->next = bucketp; cp->count = 1; Cache[idx] = cp; stats_counter_bump(Niexpr); return (np); } void iexpr_free(struct node *np) { unsigned idx = iexpr_hash(np) % IEXPRSZ; struct iexpr *cp; struct iexpr *prevcp = NULL; /* search cache */ for (cp = Cache[idx]; cp != NULL; cp = cp->next) { if (iexpr_cmp(cp->np, np) == 0) { /* found it */ cp->count--; if (cp->count == 0) { tree_free(cp->np); if (prevcp == NULL) Cache[idx] = cp->next; else prevcp->next = cp->next; FREE(cp); } return; } prevcp = cp; } } /* * iexpr_cached -- return true if np is in the iexpr cache */ int iexpr_cached(struct node *np) { struct iexpr *cp = Cache[iexpr_hash(np) % IEXPRSZ]; /* search cache */ for (; cp != NULL; cp = cp->next) if (iexpr_cmp(cp->np, np) == 0) { /* found it */ return (1); } return (0); } /* * iexpr_fini -- free the iexpr cache */ void iexpr_fini(void) { int i; for (i = 0; i < IEXPRSZ; i++) { struct iexpr *cp; struct iexpr *ncp; for (cp = Cache[i]; cp != NULL; cp = ncp) { tree_free(cp->np); ncp = cp->next; FREE(cp); } Cache[i] = NULL; } } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * iexpr.h -- public definitions for iexpr module * */ #ifndef _EFT_IEXPR_H #define _EFT_IEXPR_H #ifdef __cplusplus extern "C" { #endif void iexpr_init(void); struct node *iexpr(struct node *np); int iexpr_cached(struct node *np); void iexpr_free(struct node *np); void iexpr_fini(void); #ifdef __cplusplus } #endif #endif /* _EFT_IEXPR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * io.c -- input/output routines for the eft DE */ #include #include "io.h" extern fmd_hdl_t *Hdl; /* handle in global for platform.c */ void io_abort(const char *buf) { fmd_hdl_abort(Hdl, "%s\n", buf); } void io_die(const char *buf) { fmd_hdl_abort(Hdl, "%s\n", buf); } void io_err(const char *buf) { fmd_hdl_abort(Hdl, "%s\n", buf); } void io_out(const char *buf) { fmd_hdl_debug(Hdl, "%s\n", buf); } void io_exit(int code) { fmd_hdl_abort(Hdl, "eft: exitcode %d\n", code); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * ipath.c -- instanced pathname module * * this module provides a cache of fully instantized component paths, * stored in a fairly compact format. */ #include #include #include "alloc.h" #include "out.h" #include "lut.h" #include "tree.h" #include "ptree.h" #include "itree.h" #include "ipath.h" #include "ipath_impl.h" #include "stats.h" #include "eval.h" #include "config.h" static struct stats *Nipath; static struct stats *Nbytes; static struct lut *Ipaths; /* the ipath cache itself */ /* * ipath_init -- initialize the ipath module */ void ipath_init(void) { Nipath = stats_new_counter("ievent.nipath", "ipath cache entries", 1); Nbytes = stats_new_counter("ievent.nbytes", "total cache size", 1); } /* * ipath_cmp -- compare two ipath entries * * since two ipaths containing the same components and instance * numbers always point to the same cache entry, they are equal * if their pointers are equal, so this function is not necessary * to test if two ipaths are same. but when inserting a new ipath * into the cache, we must use the same lut comparison logic as when * we're searching for it, so this function must always match the * itree_epnamecmp() function's logic (see below) for searching the lut. */ static int ipath_cmp(struct ipath *ipp1, struct ipath *ipp2) { int i; ASSERT(ipp1 != NULL); ASSERT(ipp2 != NULL); for (i = 0; ipp1[i].s != NULL && ipp2[i].s != NULL; i++) if (ipp1[i].s != ipp2[i].s) return (ipp2[i].s - ipp1[i].s); else if (ipp1[i].i != ipp2[i].i) return (ipp2[i].i - ipp1[i].i); if (ipp1[i].s == NULL && ipp2[i].s == NULL) return (0); else if (ipp1[i].s == NULL) return (1); else return (-1); } /* * ipath_epnamecmp -- compare an ipath with a struct node *epname list * * this function is used when searching the cache, allowing us to search * a lut full of ipaths by looking directly at a struct node *epname * (without having to convert it first). the comparison logic here must * exactly match itree_cmp()'s logic (see above) so lut lookups use find * the same node as lut inserts. */ static int ipath_epnamecmp(struct ipath *ipp, struct node *np) { int i; ASSERT(np != NULL); ASSERT(ipp != NULL); for (i = 0; ipp[i].s != NULL && np != NULL; i++, np = np->u.name.next) { ASSERTinfo(np->t == T_NAME, ptree_nodetype2str(np->t)); if (ipp[i].s != np->u.name.s) return (np->u.name.s - ipp[i].s); else { int inum; if (np->u.name.child != NULL && np->u.name.child->t == T_NUM) inum = (int)np->u.name.child->u.ull; else config_getcompname(np->u.name.cp, NULL, &inum); if (ipp[i].i != inum) return (inum - ipp[i].i); } } if (ipp[i].s == NULL && np == NULL) return (0); else if (ipp[i].s == NULL) return (1); else return (-1); } /* * The following functions are only used in the "itree_create_dummy()" first * pass at itree creation. ipath_dummy() creates paths used in the itree (see * comment above add_event_dummy() for details). ipath_for_usednames() creates * a different set of paths using the full names from the propagations. These * are only used by ipath_dummy_lut() in order to set up the Usednames lut * correctly, which in turn allows conf propteries on any alement in those * names to be used in constraints. */ struct lut *Usednames; void ipath_dummy_lut(struct arrow *arrowp) { const struct ipath *ipp; ipp = arrowp->head->myevent->ipp_un; while (ipp->s != NULL) { Usednames = lut_add(Usednames, (void *)ipp->s, (void *)ipp->s, NULL); ipp++; } ipp = arrowp->tail->myevent->ipp_un; while (ipp->s != NULL) { Usednames = lut_add(Usednames, (void *)ipp->s, (void *)ipp->s, NULL); ipp++; } } struct ipath * ipath_dummy(struct node *np, struct ipath *ipp) { struct ipath *ret; ret = ipp; while (ipp[1].s != NULL) ipp++; if (strcmp(ipp[0].s, np->u.name.last->u.name.s) == 0) return (ret); ret = MALLOC(sizeof (*ret) * 2); ret[0].s = np->u.name.last->u.name.s; ret[0].i = 0; ret[1].s = NULL; if ((ipp = lut_lookup(Ipaths, (void *)ret, (lut_cmp)ipath_cmp)) != NULL) { FREE(ret); return (ipp); } Ipaths = lut_add(Ipaths, (void *)ret, (void *)ret, (lut_cmp)ipath_cmp); stats_counter_bump(Nipath); stats_counter_add(Nbytes, 2 * sizeof (struct ipath)); return (ret); } struct ipath * ipath_for_usednames(struct node *np) { struct ipath *ret, *ipp; int i = 0; struct node *np2; for (np2 = np; np2 != NULL; np2 = np2->u.name.next) i++; ret = MALLOC(sizeof (*ret) * (i + 1)); for (i = 0, np2 = np; np2 != NULL; np2 = np2->u.name.next) { ret[i].s = np2->u.name.s; ret[i++].i = 0; } ret[i].s = NULL; if ((ipp = lut_lookup(Ipaths, (void *)ret, (lut_cmp)ipath_cmp)) != NULL) { FREE(ret); return (ipp); } Ipaths = lut_add(Ipaths, (void *)ret, (void *)ret, (lut_cmp)ipath_cmp); stats_counter_bump(Nipath); stats_counter_add(Nbytes, (i + 1) * sizeof (struct ipath)); return (ret); } /* * ipath -- find instanced path in cache, or add it if necessary */ const struct ipath * ipath(struct node *np) { struct ipath *ret; int count; struct node *namep; int i; if ((ret = lut_lookup(Ipaths, (void *)np, (lut_cmp)ipath_epnamecmp)) != NULL) return (ret); /* already in cache */ /* * not in cache, make new cache entry. * start by counting the length of the name. */ count = 0; namep = np; while (namep != NULL) { ASSERTinfo(namep->t == T_NAME, ptree_nodetype2str(namep->t)); count++; namep = namep->u.name.next; } ASSERT(count > 0); /* allocate array for name and last NULL entry */ ret = MALLOC(sizeof (*ret) * (count + 1)); ret[count].s = NULL; /* fill in ipath entry */ namep = np; i = 0; while (namep != NULL) { ASSERT(i < count); ret[i].s = namep->u.name.s; if (namep->u.name.child != NULL && namep->u.name.child->t == T_NUM) ret[i].i = (int)namep->u.name.child->u.ull; else config_getcompname(namep->u.name.cp, NULL, &ret[i].i); i++; namep = namep->u.name.next; } /* add it to the cache */ Ipaths = lut_add(Ipaths, (void *)ret, (void *)ret, (lut_cmp)ipath_cmp); stats_counter_bump(Nipath); stats_counter_add(Nbytes, (count + 1) * sizeof (struct ipath)); return (ret); } /* * ipath2str -- convert ename and ipath to class@path string * * if both ename and ipp are provided (non-NULL), the resulting string * will be "class@path". otherwise, the string will just contain the * event class name (e.g. "ereport.io.pci.device") or just the path * name (e.g. "mothboard0/hostbridge0/pcibus1/pcidev0/pcifn1"), depending * on which argument is non-NULL. */ char * ipath2str(const char *ename, const struct ipath *ipp) { int i; size_t len = 0; char *ret; char *cp; /* count up length of class string */ if (ename != NULL) len += strlen(ename); /* count up length of path string, including slash separators */ if (ipp != NULL) { for (i = 0; ipp[i].s != NULL; i++) { /* add slash separator, but no leading slash */ if (i != 0) len++; len += snprintf(NULL, 0, "%s%d", ipp[i].s, ipp[i].i); } } if (ename != NULL && ipp != NULL) len++; /* room for '@' */ len++; /* room for final '\0' */ cp = ret = MALLOC(len); if (ename != NULL) { /* construct class string */ (void) strcpy(cp, ename); cp += strlen(cp); } /* if doing both strings, put '@' between them */ if (ename != NULL && ipp != NULL) *cp++ = '@'; if (ipp != NULL) { /* construct path string */ for (i = 0; ipp[i].s != NULL; i++) { if (i != 0) *cp++ = '/'; (void) snprintf(cp, &ret[len] - cp, "%s%d", ipp[i].s, ipp[i].i); cp += strlen(cp); } } *cp++ = '\0'; return (ret); } void ipathlastcomp(const struct ipath *ipp) { int i; for (i = 0; ipp[i].s != NULL; i++) ; out(O_ALTFP, "newfme: add %s to Usednames", ipp[i - 1].s); Usednames = lut_add(Usednames, (void *)ipp[i - 1].s, (void *)ipp[i - 1].s, NULL); } /* * ipath2strlen -- calculate the len of what ipath2str() would return */ size_t ipath2strlen(const char *ename, const struct ipath *ipp) { int i; size_t len = 0; /* count up length of class string */ if (ename != NULL) len += strlen(ename); /* count up length of path string, including slash separators */ if (ipp != NULL) { for (i = 0; ipp[i].s != NULL; i++) { /* add slash separator, but no leading slash */ if (i != 0) len++; len += snprintf(NULL, 0, "%s%d", ipp[i].s, ipp[i].i); } } if (ename != NULL && ipp != NULL) len++; /* room for '@' */ return (len); } /* * ipath_print -- print out an ename, ipath, or both with '@' between them */ void ipath_print(int flags, const char *ename, const struct ipath *ipp) { if (ename != NULL) { out(flags|O_NONL, ename); if (ipp != NULL) out(flags|O_NONL, "@"); } if (ipp != NULL) { char *sep = ""; while (ipp->s != NULL) { out(flags|O_NONL, "%s%s%d", sep, ipp->s, ipp->i); ipp++; sep = "/"; } } } /*ARGSUSED*/ static void ipath_destructor(void *left, void *right, void *arg) { struct ipath *ipp = (struct ipath *)right; FREE(ipp); } /* * ipath_fini -- free the ipath cache */ void ipath_fini(void) { lut_free(Ipaths, ipath_destructor, NULL); Ipaths = NULL; lut_free(Usednames, NULL, NULL); Usednames = NULL; if (Nipath) { stats_delete(Nipath); Nipath = NULL; } if (Nbytes) { stats_delete(Nbytes); Nbytes = NULL; } } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * ipath.h -- public definitions for ipath module * */ #ifndef _EFT_IPATH_H #define _EFT_IPATH_H #ifdef __cplusplus extern "C" { #endif void ipath_init(void); const struct ipath *ipath(struct node *np); void ipathlastcomp(const struct ipath *ipp); char *ipath2str(const char *ename, const struct ipath *ipp); size_t ipath2strlen(const char *ename, const struct ipath *ipp); void ipath_print(int flags, const char *ename, const struct ipath *ipp); void ipath_fini(void); #ifdef __cplusplus } #endif #endif /* _EFT_IPATH_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _EFT_IPATH_IMPL_H #define _EFT_IPATH_IMPL_H #ifdef __cplusplus extern "C" { #endif /* * ipath_impl.h -- ipath private data shared with mdb module */ /* an ipath cache entry is an array of these, with s==NULL at the end */ struct ipath { const char *s; /* component name (in stable) */ int i; /* instance number */ }; #ifdef __cplusplus } #endif #endif /* _EFT_IPATH_IMPL_H */ /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * itree.c -- instance tree creation and manipulation * * this module provides the instance tree */ #include #include #include #include #include "alloc.h" #include "out.h" #include "stable.h" #include "literals.h" #include "lut.h" #include "tree.h" #include "ptree.h" #include "itree.h" #include "ipath.h" #include "iexpr.h" #include "eval.h" #include "config.h" /* * struct info contains the state we keep when expanding a prop statement * as part of constructing the instance tree. state kept in struct info * is the non-recursive stuff -- the stuff that doesn't need to be on * the stack. the rest of the state that is passed between all the * mutually recursive functions, is required to be on the stack since * we need to backtrack and recurse as we do the instance tree construction. */ struct info { struct lut *lut; struct node *anp; /* arrow np */ struct lut *ex; /* dictionary of explicit iterators */ struct config *croot; } Ninfo; /* * struct wildcardinfo is used to track wildcarded portions of paths. * * for example, if the epname of an event is "c/d" and the path "a/b/c/d" * exists, the wildcard path ewname is filled in with the path "a/b". when * matching is done, epname is temporarily replaced with the concatenation * of ewname and epname. * * a linked list of these structs is used to track the expansion of each * event node as it is processed in vmatch() --> vmatch_event() calls. */ struct wildcardinfo { struct node *nptop; /* event node fed to vmatch */ struct node *oldepname; /* epname without the wildcard part */ struct node *ewname; /* wildcard path */ int got_wc_hz; struct wildcardinfo *next; }; static struct wildcardinfo *wcproot = NULL; static void vmatch(struct info *infop, struct node *np, struct node *lnp, struct node *anp); static void hmatch(struct info *infop, struct node *np, struct node *nextnp); static void hmatch_event(struct info *infop, struct node *eventnp, struct node *epname, struct config *ncp, struct node *nextnp, int rematch); static void itree_pbubble(int flags, struct bubble *bp); static void itree_pruner(void *left, void *right, void *arg); static void itree_destructor(void *left, void *right, void *arg); static int itree_set_arrow_traits(struct arrow *ap, struct node *fromev, struct node *toev, struct lut *ex); static void itree_free_arrowlists(struct bubble *bubp, int arrows_too); static void itree_prune_arrowlists(struct bubble *bubp); static void arrow_add_within(struct arrow *ap, struct node *xpr); static struct arrow *itree_add_arrow(struct node *apnode, struct node *fromevent, struct node *toevent, struct lut *ex); static struct event *find_or_add_event(struct info *infop, struct node *np); static void add_arrow(struct bubble *bp, struct arrow *ap); static struct constraintlist *itree_add_constraint(struct arrow *arrowp, struct node *c); static struct bubble *itree_add_bubble(struct event *eventp, enum bubbletype btype, int nork, int gen); static void itree_free_bubble(struct bubble *freeme); static void itree_free_constraints(struct arrow *ap); /* * the following struct contains the state we build up during * vertical and horizontal expansion so that generate() * has everything it needs to construct the appropriate arrows. * after setting up the state by calling: * generate_arrownp() * generate_nork() * generate_new() * generate_from() * generate_to() * the actual arrow generation is done by calling: * generate() */ static struct { int generation; /* generation number of arrow set */ int matched; /* number of matches */ struct node *arrownp; /* top-level parse tree for arrow */ int n; /* n value associated with arrow */ int k; /* k value associated with arrow */ struct node *fromnp; /* left-hand-side event in parse tree */ struct node *tonp; /* right-hand-side event in parse tree */ struct event *frome; /* left-hand-side event in instance tree */ struct event *toe; /* right-hand-side event in instance tree */ struct bubble *frombp; /* bubble arrow comes from */ struct bubble *tobp; /* bubble arrow goes to */ } G; static void generate_arrownp(struct node *arrownp) { G.arrownp = arrownp; } static void generate_nork(int n, int k) { G.n = n; G.k = k; } static void generate_new(void) { G.generation++; } static void generate_from(struct node *fromeventnp) { G.frombp = NULL; G.fromnp = fromeventnp; } static void generate_to(struct node *toeventnp) { G.tonp = toeventnp; } static void generate(struct info *infop) { struct arrow *arrowp; ASSERT(G.arrownp != NULL); ASSERT(G.fromnp != NULL); ASSERT(G.tonp != NULL); out(O_ALTFP|O_VERB3|O_NONL, " Arrow \""); ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, G.fromnp); out(O_ALTFP|O_VERB3|O_NONL, "\" -> \""); ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, G.tonp); out(O_ALTFP|O_VERB3|O_NONL, "\" "); arrowp = itree_add_arrow(G.arrownp, G.fromnp, G.tonp, infop->ex); if (arrowp == NULL) { out(O_ALTFP|O_VERB3, "(prevented by constraints)"); } else { out(O_ALTFP|O_VERB3, ""); if (!G.frombp) { G.frome = find_or_add_event(infop, G.fromnp); G.frombp = itree_add_bubble(G.frome, B_FROM, G.n, 0); } G.toe = find_or_add_event(infop, G.tonp); G.tobp = itree_add_bubble(G.toe, B_TO, G.k, G.generation); arrowp->tail = G.frombp; arrowp->head = G.tobp; add_arrow(G.frombp, arrowp); add_arrow(G.tobp, arrowp); } } enum childnode_action { CN_NONE, CN_DUP }; static struct node * tname_dup(struct node *namep, enum childnode_action act) { struct node *retp = NULL; const char *file; int line; if (namep == NULL) return (NULL); file = namep->file; line = namep->line; for (; namep != NULL; namep = namep->u.name.next) { struct node *newnp = newnode(T_NAME, file, line); newnp->u.name.t = namep->u.name.t; newnp->u.name.s = namep->u.name.s; newnp->u.name.last = newnp; newnp->u.name.it = namep->u.name.it; newnp->u.name.cp = namep->u.name.cp; if (act == CN_DUP) { struct node *npc; npc = namep->u.name.child; if (npc != NULL) { switch (npc->t) { case T_NUM: newnp->u.name.child = newnode(T_NUM, file, line); newnp->u.name.child->u.ull = npc->u.ull; break; case T_NAME: newnp->u.name.child = tree_name(npc->u.name.s, npc->u.name.it, file, line); break; default: out(O_DIE, "tname_dup: " "invalid child type %s", ptree_nodetype2str(npc->t)); } } } if (retp == NULL) { retp = newnp; } else { retp->u.name.last->u.name.next = newnp; retp->u.name.last = newnp; } } return (retp); } struct prop_wlk_data { struct lut *props; struct node *epname; }; static struct lut *props2instance(struct node *, struct node *); /* * let oldepname be a subset of epname. return the subsection of epname * that ends with oldepname. make each component in the path explicitly * instanced (i.e., with a T_NUM child). */ static struct node * tname_dup_to_epname(struct node *oldepname, struct node *epname) { struct node *npref, *npend, *np1, *np2; struct node *ret = NULL; int foundmatch = 0; if (epname == NULL) return (NULL); /* * search for the longest path in epname which contains * oldnode->u.event.epname. set npend to point to just past the * end of this path. */ npend = NULL; for (npref = epname; npref; npref = npref->u.name.next) { if (npref->u.name.s == oldepname->u.name.s) { for (np1 = npref, np2 = oldepname; np1 != NULL && np2 != NULL; np1 = np1->u.name.next, np2 = np2->u.name.next) { if (np1->u.name.s != np2->u.name.s) break; } if (np2 == NULL) { foundmatch = 1; npend = np1; if (np1 == NULL) { /* oldepname matched npref up to end */ break; } } } } if (foundmatch == 0) { /* * if oldepname could not be found in epname, return a * duplicate of the former. do not try to instantize * oldepname since it might not be a path. */ return (tname_dup(oldepname, CN_DUP)); } /* * dup (epname -- npend). all children should be T_NUMs. */ for (npref = epname; ! (npref == NULL || npref == npend); npref = npref->u.name.next) { struct node *newnp = newnode(T_NAME, oldepname->file, oldepname->line); newnp->u.name.t = npref->u.name.t; newnp->u.name.s = npref->u.name.s; newnp->u.name.last = newnp; newnp->u.name.it = npref->u.name.it; newnp->u.name.cp = npref->u.name.cp; newnp->u.name.child = newnode(T_NUM, oldepname->file, oldepname->line); if (npref->u.name.child == NULL || npref->u.name.child->t != T_NUM) { int childnum; ASSERT(npref->u.name.cp != NULL); config_getcompname(npref->u.name.cp, NULL, &childnum); newnp->u.name.child->u.ull = childnum; } else { newnp->u.name.child->u.ull = npref->u.name.child->u.ull; } if (ret == NULL) { ret = newnp; } else { ret->u.name.last->u.name.next = newnp; ret->u.name.last = newnp; } } return (ret); } /* * restriction: oldnode->u.event.epname has to be equivalent to or a subset * of epname */ static struct node * tevent_dup_to_epname(struct node *oldnode, struct node *epname) { struct node *ret; ret = newnode(T_EVENT, oldnode->file, oldnode->line); ret->u.event.ename = tname_dup(oldnode->u.event.ename, CN_NONE); ret->u.event.epname = tname_dup_to_epname(oldnode->u.event.epname, epname); return (ret); } static void nv_instantiate(void *name, void *val, void *arg) { struct prop_wlk_data *pd = (struct prop_wlk_data *)arg; struct node *orhs = (struct node *)val; struct node *nrhs; /* handle engines by instantizing the entire engine */ if (name == L_engine) { ASSERT(orhs->t == T_EVENT); ASSERT(orhs->u.event.ename->u.name.t == N_SERD); /* there are only SERD engines for now */ nrhs = newnode(T_SERD, orhs->file, orhs->line); nrhs->u.stmt.np = tevent_dup_to_epname(orhs, pd->epname); nrhs->u.stmt.lutp = props2instance(orhs, pd->epname); pd->props = lut_add(pd->props, name, nrhs, NULL); return; } switch (orhs->t) { case T_NUM: nrhs = newnode(T_NUM, orhs->file, orhs->line); nrhs->u.ull = orhs->u.ull; pd->props = lut_add(pd->props, name, nrhs, NULL); break; case T_TIMEVAL: nrhs = newnode(T_TIMEVAL, orhs->file, orhs->line); nrhs->u.ull = orhs->u.ull; pd->props = lut_add(pd->props, name, nrhs, NULL); break; case T_NAME: nrhs = tname_dup_to_epname(orhs, pd->epname); pd->props = lut_add(pd->props, name, nrhs, NULL); break; case T_EVENT: nrhs = tevent_dup_to_epname(orhs, pd->epname); pd->props = lut_add(pd->props, name, nrhs, NULL); break; case T_GLOBID: nrhs = newnode(T_GLOBID, orhs->file, orhs->line); nrhs->u.globid.s = orhs->u.globid.s; pd->props = lut_add(pd->props, name, nrhs, NULL); break; case T_FUNC: /* for T_FUNC, we don't duplicate it, just point to node */ pd->props = lut_add(pd->props, name, orhs, NULL); break; default: out(O_DIE, "unexpected nvpair value type %s", ptree_nodetype2str(((struct node *)val)->t)); } } static struct lut * props2instance(struct node *eventnp, struct node *epname) { struct prop_wlk_data pd; pd.props = NULL; pd.epname = epname; ASSERT(eventnp->u.event.declp != NULL); lut_walk(eventnp->u.event.declp->u.stmt.lutp, nv_instantiate, &pd); return (pd.props); } /*ARGSUSED*/ static void instances_destructor(void *left, void *right, void *arg) { struct node *dn = (struct node *)right; if (dn->t == T_SERD) { /* we allocated the lut during itree_create(), so free it */ lut_free(dn->u.stmt.lutp, instances_destructor, NULL); dn->u.stmt.lutp = NULL; } if (dn->t != T_FUNC) /* T_FUNC pointed to original node */ tree_free(dn); } /*ARGSUSED*/ static void payloadprops_destructor(void *left, void *right, void *arg) { FREE(right); } /*ARGSUSED*/ static void serdprops_destructor(void *left, void *right, void *arg) { FREE(right); } /* * event_cmp -- used via lut_lookup/lut_add on instance tree lut */ static int event_cmp(struct event *ep1, struct event *ep2) { int diff; if ((diff = ep2->enode->u.event.ename->u.name.s - ep1->enode->u.event.ename->u.name.s) != 0) return (diff); if ((diff = (char *)ep2->ipp - (char *)ep1->ipp) != 0) return (diff); return (0); } struct event * itree_lookup(struct lut *itp, const char *ename, const struct ipath *ipp) { struct event searchevent; /* just used for searching */ struct node searcheventnode; struct node searchenamenode; searchevent.enode = &searcheventnode; searcheventnode.t = T_EVENT; searcheventnode.u.event.ename = &searchenamenode; searchenamenode.t = T_NAME; searchenamenode.u.name.s = ename; searchevent.ipp = ipp; return (lut_lookup(itp, (void *)&searchevent, (lut_cmp)event_cmp)); } static struct event * find_or_add_event(struct info *infop, struct node *np) { struct event *ret; struct event searchevent; /* just used for searching */ ASSERTeq(np->t, T_EVENT, ptree_nodetype2str); searchevent.enode = np; searchevent.ipp = ipath(np->u.event.epname); if ((ret = lut_lookup(infop->lut, (void *)&searchevent, (lut_cmp)event_cmp)) != NULL) return (ret); /* wasn't already in tree, allocate it */ ret = alloc_xmalloc(sizeof (*ret)); bzero(ret, sizeof (*ret)); ret->t = np->u.event.ename->u.name.t; ret->enode = np; ret->ipp = searchevent.ipp; ret->props = props2instance(np, np->u.event.epname); infop->lut = lut_add(infop->lut, (void *)ret, (void *)ret, (lut_cmp)event_cmp); return (ret); } /* * Used for handling expansions where first part of oldepname is a horizontal * expansion. Recurses through entire tree. oldepname argument is always the * full path as in the rules. Once we find a match we go back to using * hmatch_event to handle the rest. */ static void hmatch_full_config(struct info *infop, struct node *eventnp, struct node *oldepname, struct config *ncp, struct node *nextnp, struct iterinfo *iterinfop) { char *cp_s; int cp_num; struct config *cp; struct node *saved_ewname; struct node *saved_epname; struct config *pcp, *ocp; struct node *cpnode; struct node *ewlp, *ewfp; for (cp = ncp; cp; cp = config_next(cp)) { config_getcompname(cp, &cp_s, &cp_num); if (cp_s == oldepname->u.name.s) { /* * Found one. */ iterinfop->num = cp_num; /* * Need to set ewname, epname for correct node as is * needed by constraint path matching. This code is * similar to that in vmatch_event. */ saved_ewname = eventnp->u.event.ewname; saved_epname = eventnp->u.event.epname; ocp = oldepname->u.name.cp; /* * Find correct ewname by walking back up the config * tree adding each name portion as we go. */ pcp = config_parent(cp); eventnp->u.event.ewname = NULL; for (; pcp != infop->croot; pcp = config_parent(pcp)) { config_getcompname(pcp, &cp_s, &cp_num); cpnode = tree_name(cp_s, IT_NONE, NULL, 0); cpnode->u.name.child = newnode(T_NUM, NULL, 0); cpnode->u.name.child->u.ull = cp_num; cpnode->u.name.cp = pcp; if (eventnp->u.event.ewname != NULL) { cpnode->u.name.next = eventnp->u.event.ewname; cpnode->u.name.last = eventnp->u.event.ewname-> u.name.last; } eventnp->u.event.ewname = cpnode; } /* * Now create correct epname by duping new ewname * and appending oldepname. */ ewfp = tname_dup(eventnp->u.event.ewname, CN_DUP); ewlp = ewfp->u.name.last; ewfp->u.name.last = oldepname->u.name.last; ewlp->u.name.next = oldepname; oldepname->u.name.cp = cp; eventnp->u.event.epname = ewfp; outfl(O_ALTFP|O_VERB3|O_NONL, infop->anp->file, infop->anp->line, "hmatch_full_config: "); ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, eventnp->u.event.epname); out(O_ALTFP|O_VERB3, NULL); /* * Now complete hmatch. */ hmatch_event(infop, eventnp, oldepname->u.name.next, config_child(cp), nextnp, 1); /* * set everything back again */ oldepname->u.name.cp = ocp; iterinfop->num = -1; ewlp->u.name.next = NULL; ewfp->u.name.last = ewlp; tree_free(ewfp); tree_free(eventnp->u.event.ewname); eventnp->u.event.ewname = saved_ewname; eventnp->u.event.epname = saved_epname; } /* * Try the next level down. */ hmatch_full_config(infop, eventnp, oldepname, config_child(cp), nextnp, iterinfop); } } /* * hmatch_event -- perform any appropriate horizontal expansion on an event * * this routine is used to perform horizontal expansion on both the * left-hand-side events in a prop, and the right-hand-side events. * when called to handle a left-side event, nextnp point to the right * side of the prop that should be passed to hmatch() for each match * found by horizontal expansion. when no horizontal expansion exists, * we will still "match" one event for every event found in the list on * the left-hand-side of the prop because vmatch() already found that * there's at least one match during vertical expansion. */ static void hmatch_event(struct info *infop, struct node *eventnp, struct node *epname, struct config *ncp, struct node *nextnp, int rematch) { if (epname == NULL) { /* * end of pathname recursion, either we just located * a left-hand-side event and we're ready to move on * to the expanding the right-hand-side events, or * we're further down the recursion and we just located * a right-hand-side event. the passed-in parameter * "nextnp" tells us whether we're working on the left * side and need to move on to nextnp, or if nextnp is * NULL, we're working on the right side. */ if (nextnp) { /* * finished a left side expansion, move on to right. * tell generate() what event we just matched so * it can be used at the source of any arrows * we generate as we match events on the right side. */ generate_from(eventnp); hmatch(infop, nextnp, NULL); } else { /* * finished a right side expansion. tell generate * the information about the destination and let * it construct the arrows as appropriate. */ generate_to(eventnp); generate(infop); } return; } ASSERTeq(epname->t, T_NAME, ptree_nodetype2str); if (epname->u.name.cp == NULL) return; /* * we only get here when eventnp already has a completely * instanced epname in it already. so we first recurse * down to the end of the name and as the recursion pops * up, we look for opportunities to advance horizontal * expansions on to the next match. */ if (epname->u.name.it == IT_HORIZONTAL || rematch) { struct config *cp; struct config *ocp = epname->u.name.cp; char *cp_s; int cp_num; int ocp_num; struct iterinfo *iterinfop = NULL; const char *iters; int hexpand = 0; if (epname->u.name.it != IT_HORIZONTAL) { /* * Ancestor was horizontal though, so must rematch * against the name/num found in vmatch. */ config_getcompname(ocp, NULL, &ocp_num); } else { iters = epname->u.name.child->u.name.s; if ((iterinfop = lut_lookup(infop->ex, (void *)iters, NULL)) == NULL) { /* * do horizontal expansion on this node */ hexpand = 1; iterinfop = alloc_xmalloc( sizeof (struct iterinfo)); iterinfop->num = -1; iterinfop->np = epname; infop->ex = lut_add(infop->ex, (void *)iters, iterinfop, NULL); } else if (iterinfop->num == -1) { hexpand = 1; } else { /* * This name has already been used in a * horizontal expansion. This time just match it */ ocp_num = iterinfop->num; } } /* * handle case where this is the first section of oldepname * and it is horizontally expanded. Instead of just looking for * siblings, we want to scan the entire config tree for further * matches. */ if (epname == eventnp->u.event.oldepname && epname->u.name.it == IT_HORIZONTAL) { /* * Run through config looking for any that match the * name. */ hmatch_full_config(infop, eventnp, epname, infop->croot, nextnp, iterinfop); return; } /* * Run through siblings looking for any that match the name. * If hexpand not set then num must also match ocp_num. */ for (cp = rematch ? ncp : ocp; cp; cp = config_next(cp)) { config_getcompname(cp, &cp_s, &cp_num); if (cp_s == epname->u.name.s) { if (hexpand) iterinfop->num = cp_num; else if (ocp_num != cp_num) continue; epname->u.name.cp = cp; hmatch_event(infop, eventnp, epname->u.name.next, config_child(cp), nextnp, 1); } } epname->u.name.cp = ocp; if (hexpand) iterinfop->num = -1; } else { hmatch_event(infop, eventnp, epname->u.name.next, NULL, nextnp, 0); } } /* * hmatch -- check for horizontal expansion matches * * np points to the things we're matching (like a T_LIST or a T_EVENT) * and if we're working on a left-side of a prop, nextnp points to * the other side of the prop that we'll tackle next when this recursion * bottoms out. when all the events in the entire prop arrow have been * horizontally expanded, generate() will be called to generate the * actualy arrow. */ static void hmatch(struct info *infop, struct node *np, struct node *nextnp) { if (np == NULL) return; /* all done */ /* * for each item in the list of events (which could just * be a single event, or it could get larger in the loop * below due to horizontal expansion), call hmatch on * the right side and create arrows to each element. */ switch (np->t) { case T_LIST: /* loop through the list */ if (np->u.expr.left) hmatch(infop, np->u.expr.left, nextnp); if (np->u.expr.right) hmatch(infop, np->u.expr.right, nextnp); break; case T_EVENT: hmatch_event(infop, np, np->u.event.epname, NULL, nextnp, 0); break; default: outfl(O_DIE, np->file, np->line, "hmatch: unexpected type: %s", ptree_nodetype2str(np->t)); } } static int itree_np2nork(struct node *norknp) { if (norknp == NULL) return (1); else if (norknp->t == T_NAME && norknp->u.name.s == L_A) return (-1); /* our internal version of "all" */ else if (norknp->t == T_NUM) return ((int)norknp->u.ull); else outfl(O_DIE, norknp->file, norknp->line, "itree_np2nork: internal error type %s", ptree_nodetype2str(norknp->t)); /*NOTREACHED*/ return (1); } static struct iterinfo * newiterinfo(int num, struct node *np) { struct iterinfo *ret = alloc_xmalloc(sizeof (*ret)); ret->num = num; ret->np = np; return (ret); } /*ARGSUSED*/ static void iterinfo_destructor(void *left, void *right, void *arg) { struct iterinfo *iterinfop = (struct iterinfo *)right; alloc_xfree(iterinfop, sizeof (*iterinfop)); } static void vmatch_event(struct info *infop, struct config *cp, struct node *np, struct node *lnp, struct node *anp, struct wildcardinfo *wcp) { char *cp_s; int cp_num; struct node *ewlp, *ewfp; struct config *pcp; struct node *cpnode; int newewname = 0; /* * handle case where the first section of the path name is horizontally * expanded. The whole expansion is handled by hmatch on the first * match here - so we just skip any subsequent matches here. */ if (wcp->got_wc_hz == 1) return; if (np == NULL) { /* * Reached the end of the name. u.name.cp pointers should be set * up for each part of name. From this we can use config tree * to build up the wildcard part of the name (if any). */ pcp = config_parent(wcp->nptop->u.event.epname->u.name.cp); if (pcp == infop->croot) { /* * no wildcarding done - move on to next entry */ wcp->nptop->u.event.ewname = wcp->ewname; wcp->nptop->u.event.oldepname = wcp->oldepname; vmatch(infop, np, lnp, anp); wcp->got_wc_hz = 0; return; } if (wcp->ewname == NULL) { /* * ewname not yet set up - do it now */ newewname = 1; for (; pcp != infop->croot; pcp = config_parent(pcp)) { config_getcompname(pcp, &cp_s, &cp_num); cpnode = tree_name(cp_s, IT_NONE, NULL, 0); cpnode->u.name.child = newnode(T_NUM, NULL, 0); cpnode->u.name.child->u.ull = cp_num; cpnode->u.name.cp = pcp; if (wcp->ewname != NULL) { cpnode->u.name.next = wcp->ewname; cpnode->u.name.last = wcp->ewname->u.name.last; } wcp->ewname = cpnode; } } /* * dup ewname and append oldepname */ ewfp = tname_dup(wcp->ewname, CN_DUP); ewlp = ewfp->u.name.last; ewfp->u.name.last = wcp->oldepname->u.name.last; ewlp->u.name.next = wcp->oldepname; wcp->nptop->u.event.epname = ewfp; wcp->nptop->u.event.ewname = wcp->ewname; wcp->nptop->u.event.oldepname = wcp->oldepname; vmatch(infop, np, lnp, anp); wcp->got_wc_hz = 0; wcp->nptop->u.event.epname = wcp->oldepname; /* * reduce duped ewname to original then free */ ewlp->u.name.next = NULL; ewfp->u.name.last = ewlp; tree_free(ewfp); if (newewname) { /* * free ewname if allocated above */ tree_free(wcp->ewname); wcp->ewname = NULL; } return; } /* * We have an np. See if we can match it in this section of * the config tree. */ if (cp == NULL) return; /* no more config to match against */ for (; cp; cp = config_next(cp)) { config_getcompname(cp, &cp_s, &cp_num); if (cp_s == np->u.name.s) { /* found a matching component name */ if (np->u.name.child && np->u.name.child->t == T_NUM) { /* * an explicit instance number was given * in the source. so only consider this * a configuration match if the number * also matches. */ if (cp_num != np->u.name.child->u.ull) continue; } else if (np->u.name.it != IT_HORIZONTAL) { struct iterinfo *iterinfop; const char *iters; /* * vertical iterator. look it up in * the appropriate lut and if we get * back a value it is either one that we * set earlier, in which case we record * the new value for this iteration and * keep matching, or it is one that was * set by an earlier reference to the * iterator, in which case we only consider * this a configuration match if the number * matches cp_num. */ ASSERT(np->u.name.child != NULL); ASSERT(np->u.name.child->t == T_NAME); iters = np->u.name.child->u.name.s; if ((iterinfop = lut_lookup(infop->ex, (void *)iters, NULL)) == NULL) { /* we're the first use, record our np */ infop->ex = lut_add(infop->ex, (void *)iters, newiterinfo(cp_num, np), NULL); } else if (np == iterinfop->np) { /* * we're the first use, back again * for another iteration. so update * the num bound to this iterator in * the lut. */ iterinfop->num = cp_num; } else if (cp_num != iterinfop->num) { /* * an earlier reference to this * iterator bound it to a different * instance number, so there's no * match here after all. * * however, it's possible that this * component should really be part of * the wildcard. we explore this by * forcing this component into the * wildcarded section. * * for an more details of what's * going to happen now, see * comments block below entitled * "forcing components into * wildcard path". */ if (np == wcp->nptop->u.event.epname) vmatch_event(infop, config_child(cp), np, lnp, anp, wcp); continue; } } /* * if this was an IT_HORIZONTAL name, hmatch() will * expand all matches horizontally into a list. * we know the list will contain at least * one element (the one we just matched), * so we just let hmatch_event() do the rest. * * recurse on to next component. Note that * wildcarding is now turned off. */ np->u.name.cp = cp; vmatch_event(infop, config_child(cp), np->u.name.next, lnp, anp, wcp); np->u.name.cp = NULL; /* * handle case where this is the first section of the * path name and it is horizontally expanded. * In this case we want all matching nodes in the config * to be expanded horizontally - so set got_wc_hz and * leave all further processing to hmatch. */ if (G.matched && np == wcp->nptop->u.event.epname && np->u.name.it == IT_HORIZONTAL) wcp->got_wc_hz = 1; /* * forcing components into wildcard path: * * if this component is the first match, force it * to be part of the wildcarded path and see if we * can get additional matches. * * here's an example. suppose we have the * definition * event foo@x/y * and configuration * a0/x0/y0/a1/x1/y1 * * the code up to this point will treat "a0" as the * wildcarded part of the path and "x0/y0" as the * nonwildcarded part, resulting in the instanced * event * foo@a0/x0/y0 * * in order to discover the next match (.../x1/y1) * in the configuration we have to force "x0" into * the wildcarded part of the path. * by doing so, we discover the wildcarded part * "a0/x0/y0/a1" and the nonwildcarded part "x1/y1" * * the following call to vmatch_event() is also * needed to properly handle the configuration * b0/x0/b1/x1/y1 * * the recursions into vmatch_event() will start * off uncovering "b0" as the wildcarded part and * "x0" as the start of the nonwildcarded path. * however, the next recursion will not result in a * match since there is no "y" following "x0". the * subsequent match of (wildcard = "b0/x0/b1" and * nonwildcard = "x1/y1") will be discovered only * if "x0" is forced to be a part of the wildcarded * path. */ if (np == wcp->nptop->u.event.epname) vmatch_event(infop, config_child(cp), np, lnp, anp, wcp); if (np->u.name.it == IT_HORIZONTAL) { /* * hmatch() finished iterating through * the configuration as described above, so * don't continue iterating here. */ return; } } else if (np == wcp->nptop->u.event.epname) { /* * no match - carry on down the tree looking for * wildcarding */ vmatch_event(infop, config_child(cp), np, lnp, anp, wcp); } } } /* * vmatch -- find the next vertical expansion match in the config database * * this routine is called with three node pointers: * np -- the parse we're matching * lnp -- the rest of the list we're currently working on * anp -- the rest of the arrow we're currently working on * * the expansion matching happens via three types of recursion: * * - when given an arrow, handle the left-side and then recursively * handle the right side (which might be another cascaded arrow). * * - when handling one side of an arrow, recurse through the T_LIST * to get to each event (or just move on to the event if there * is a single event instead of a list) since the arrow parse * trees recurse left, we actually start with the right-most * event list in the prop statement and work our way towards * the left-most event list. * * - when handling an event, recurse down each component of the * pathname, matching in the config database and recording the * matches in the explicit iterator dictionary as we go. * * when the bottom of this matching recursion is met, meaning we have * set the "cp" pointers on all the names in the entire statement, * we call hmatch() which does it's own recursion to handle horizontal * expandsion and then call generate() to generate nodes, bubbles, and * arrows in the instance tree. generate() looks at the cp pointers to * see what instance numbers were matched in the configuration database. * * when horizontal expansion appears, vmatch() finds only the first match * and hmatch() then takes the horizontal expansion through all the other * matches when generating the arrows in the instance tree. * * the "infop" passed down through the recursion contains a dictionary * of the explicit iterators (all the implicit iterators have been converted * to explicit iterators when the parse tree was created by tree.c), which * allows things like this to work correctly: * * prop error.a@x[n]/y/z -> error.b@x/y[n]/z -> error.c@x/y/z[n]; * * during the top level call, the explicit iterator "n" will match an * instance number in the config database, and the result will be recorded * in the explicit iterator dictionary and passed down via "infop". so * when the recursive call tries to match y[n] in the config database, it * will only match the same instance number as x[n] did since the dictionary * is consulted to see if "n" took on a value already. * * at any point during the recursion, match*() can return to indicate * a match was not found in the config database and that the caller should * move on to the next potential match, if any. * * constraints are completely ignored by match(), so the statement: * * prop error.a@x[n] -> error.b@x[n] {n != 0}; * * might very well match x[0] if it appears in the config database. it * is the generate() routine that takes that match and then decides what * arrow, if any, should be generated in the instance tree. generate() * looks at the explicit iterator dictionary to get values like "n" in * the above example so that it can evaluate constraints. * */ static void vmatch(struct info *infop, struct node *np, struct node *lnp, struct node *anp) { struct node *np1, *np2, *oldepname, *oldnptop; int epmatches; struct config *cp; struct wildcardinfo *wcp; if (np == NULL) { G.matched = 1; if (lnp) vmatch(infop, lnp, NULL, anp); else if (anp) vmatch(infop, anp, NULL, NULL); else { struct node *src; struct node *dst; /* end of vertical match recursion */ outfl(O_ALTFP|O_VERB3|O_NONL, infop->anp->file, infop->anp->line, "vmatch: "); ptree_name_iter(O_ALTFP|O_VERB3|O_NONL, infop->anp); out(O_ALTFP|O_VERB3, NULL); generate_nork( itree_np2nork(infop->anp->u.arrow.nnp), itree_np2nork(infop->anp->u.arrow.knp)); dst = infop->anp->u.arrow.rhs; src = infop->anp->u.arrow.lhs; for (;;) { generate_new(); /* new set of arrows */ if (src->t == T_ARROW) { hmatch(infop, src->u.arrow.rhs, dst); generate_nork( itree_np2nork(src->u.arrow.nnp), itree_np2nork(src->u.arrow.knp)); dst = src->u.arrow.rhs; src = src->u.arrow.lhs; } else { hmatch(infop, src, dst); break; } } } return; } switch (np->t) { case T_EVENT: { epmatches = 0; /* * see if we already have a match in the wcps */ for (wcp = wcproot; wcp; wcp = wcp->next) { oldepname = wcp->oldepname; oldnptop = wcp->nptop; for (np1 = oldepname, np2 = np->u.event.epname; np1 != NULL && np2 != NULL; np1 = np1->u.name.next, np2 = np2->u.name.next) { if (strcmp(np1->u.name.s, np2->u.name.s) != 0) break; if (np1->u.name.child->t != np2->u.name.child->t) break; if (np1->u.name.child->t == T_NUM && np1->u.name.child->u.ull != np2->u.name.child->u.ull) break; if (np1->u.name.child->t == T_NAME && strcmp(np1->u.name.child->u.name.s, np2->u.name.child->u.name.s) != 0) break; epmatches++; } if (epmatches) break; } if (epmatches && np1 == NULL && np2 == NULL) { /* * complete names match, can just borrow the fields */ oldepname = np->u.event.epname; np->u.event.epname = oldnptop->u.event.epname; np->u.event.oldepname = wcp->oldepname; np->u.event.ewname = wcp->ewname; vmatch(infop, NULL, lnp, anp); np->u.event.epname = oldepname; return; } G.matched = 0; if (epmatches) { /* * just first part of names match - do wildcarding * by using existing wcp including ewname and also * copying as much of pwname as is valid, then start * vmatch_event() at start of non-matching section */ for (np1 = oldepname, np2 = np->u.event.epname; epmatches != 0; epmatches--) { cp = np1->u.name.cp; np2->u.name.cp = cp; np1 = np1->u.name.next; np2 = np2->u.name.next; } wcp->oldepname = np->u.event.epname; wcp->nptop = np; vmatch_event(infop, config_child(cp), np2, lnp, anp, wcp); wcp->oldepname = oldepname; wcp->nptop = oldnptop; if (G.matched == 0) { /* * This list entry is NULL. Move on to next item * in the list. */ vmatch(infop, NULL, lnp, anp); } return; } /* * names do not match - allocate a new wcp */ wcp = MALLOC(sizeof (struct wildcardinfo)); wcp->next = wcproot; wcproot = wcp; wcp->nptop = np; wcp->oldepname = np->u.event.epname; wcp->ewname = NULL; wcp->got_wc_hz = 0; vmatch_event(infop, config_child(infop->croot), np->u.event.epname, lnp, anp, wcp); wcproot = wcp->next; FREE(wcp); if (G.matched == 0) { /* * This list entry is NULL. Move on to next item in the * list. */ vmatch(infop, NULL, lnp, anp); } break; } case T_LIST: ASSERT(lnp == NULL); vmatch(infop, np->u.expr.right, np->u.expr.left, anp); break; case T_ARROW: ASSERT(lnp == NULL && anp == NULL); vmatch(infop, np->u.arrow.rhs, NULL, np->u.arrow.parent); break; default: outfl(O_DIE, np->file, np->line, "vmatch: unexpected type: %s", ptree_nodetype2str(np->t)); } } static void find_first_arrow(struct info *infop, struct node *anp) { if (anp->u.arrow.lhs->t == T_ARROW) { anp->u.arrow.lhs->u.arrow.parent = anp; find_first_arrow(infop, anp->u.arrow.lhs); } else vmatch(infop, anp->u.arrow.lhs, NULL, anp); } static void cp_reset(struct node *np) { if (np == NULL) return; switch (np->t) { case T_NAME: np->u.name.cp = NULL; cp_reset(np->u.name.next); break; case T_LIST: cp_reset(np->u.expr.left); cp_reset(np->u.expr.right); break; case T_ARROW: cp_reset(np->u.arrow.lhs); cp_reset(np->u.arrow.rhs); break; case T_EVENT: cp_reset(np->u.event.epname); break; } } /* * itree_create -- apply the current config to the current parse tree * * returns a lut mapping fully-instance-qualified names to struct events. * */ struct lut * itree_create(struct config *croot) { struct lut *retval; struct node *propnp; extern int alloc_total(); int init_size; Ninfo.lut = NULL; Ninfo.croot = croot; init_size = alloc_total(); out(O_ALTFP|O_STAMP, "start itree_create using %d bytes", init_size); for (propnp = Props; propnp; propnp = propnp->u.stmt.next) { struct node *anp = propnp->u.stmt.np; ASSERTeq(anp->t, T_ARROW, ptree_nodetype2str); if (!anp->u.arrow.needed) continue; Ninfo.anp = anp; Ninfo.ex = NULL; generate_arrownp(anp); anp->u.arrow.parent = NULL; find_first_arrow(&Ninfo, anp); if (Ninfo.ex) { lut_free(Ninfo.ex, iterinfo_destructor, NULL); Ninfo.ex = NULL; } cp_reset(anp); } out(O_ALTFP|O_STAMP, "itree_create added %d bytes", alloc_total() - init_size); retval = Ninfo.lut; Ninfo.lut = NULL; return (retval); } /* * initial first pass of the rules. * We don't use the config at all. Just check the last part of the pathname * in the rules. If this matches the last part of the pathname in the first * ereport, then set pathname to the pathname in the ereport. If not then * set pathname to just the last part of pathname with instance number 0. * Constraints are ignored and all nork values are set to 0. If after all that * any rules can still not be associated with the ereport, then they are set * to not needed in prune_propagations() and ignored in the real itree_create() * which follows. */ static struct event * add_event_dummy(struct node *np, const struct ipath *ipp) { struct event *ret; struct event searchevent; /* just used for searching */ extern struct ipath *ipath_dummy(struct node *, struct ipath *); struct ipath *ipp_un; extern struct ipath *ipath_for_usednames(struct node *); searchevent.enode = np; searchevent.ipp = ipath_dummy(np->u.event.epname, (struct ipath *)ipp); ipp_un = ipath_for_usednames(np->u.event.epname); if ((ret = lut_lookup(Ninfo.lut, (void *)&searchevent, (lut_cmp)event_cmp)) != NULL) return (ret); ret = alloc_xmalloc(sizeof (*ret)); bzero(ret, sizeof (*ret)); ret->t = np->u.event.ename->u.name.t; ret->enode = np; ret->ipp = searchevent.ipp; ret->ipp_un = ipp_un; Ninfo.lut = lut_add(Ninfo.lut, (void *)ret, (void *)ret, (lut_cmp)event_cmp); return (ret); } /*ARGSUSED*/ struct lut * itree_create_dummy(const char *e0class, const struct ipath *e0ipp) { struct node *propnp; struct event *frome, *toe; struct bubble *frombp, *tobp; struct arrow *arrowp; struct node *src, *dst, *slst, *dlst, *arrownp, *oldarrownp; int gen = 0; extern int alloc_total(); int init_size; Ninfo.lut = NULL; init_size = alloc_total(); out(O_ALTFP|O_STAMP, "start itree_create using %d bytes", init_size); for (propnp = Props; propnp; propnp = propnp->u.stmt.next) { arrownp = propnp->u.stmt.np; while (arrownp) { gen++; dlst = arrownp->u.arrow.rhs; slst = arrownp->u.arrow.lhs; oldarrownp = arrownp; if (slst->t == T_ARROW) { arrownp = slst; slst = slst->u.arrow.rhs; } else { arrownp = NULL; } while (slst) { if (slst->t == T_LIST) { src = slst->u.expr.right; slst = slst->u.expr.left; } else { src = slst; slst = NULL; } frome = add_event_dummy(src, e0ipp); frombp = itree_add_bubble(frome, B_FROM, 0, 0); while (dlst) { if (dlst->t == T_LIST) { dst = dlst->u.expr.right; dlst = dlst->u.expr.left; } else { dst = dlst; dlst = NULL; } arrowp = alloc_xmalloc( sizeof (struct arrow)); bzero(arrowp, sizeof (struct arrow)); arrowp->pnode = oldarrownp; toe = add_event_dummy(dst, e0ipp); tobp = itree_add_bubble(toe, B_TO, 0, gen); arrowp->tail = frombp; arrowp->head = tobp; add_arrow(frombp, arrowp); add_arrow(tobp, arrowp); arrow_add_within(arrowp, dst->u.event.declp->u.stmt.np-> u.event.eexprlist); arrow_add_within(arrowp, dst->u.event.eexprlist); } } } } out(O_ALTFP|O_STAMP, "itree_create added %d bytes", alloc_total() - init_size); return (Ninfo.lut); } void itree_free(struct lut *lutp) { int init_size; init_size = alloc_total(); out(O_ALTFP|O_STAMP, "start itree_free"); lut_free(lutp, itree_destructor, NULL); out(O_ALTFP|O_STAMP, "itree_free freed %d bytes", init_size - alloc_total()); } void itree_prune(struct lut *lutp) { int init_size; init_size = alloc_total(); out(O_ALTFP|O_STAMP, "start itree_prune"); lut_walk(lutp, itree_pruner, NULL); out(O_ALTFP|O_STAMP, "itree_prune freed %d bytes", init_size - alloc_total()); } void itree_pevent_brief(int flags, struct event *ep) { ASSERT(ep != NULL); ASSERT(ep->enode != NULL); ASSERT(ep->ipp != NULL); ipath_print(flags, ep->enode->u.event.ename->u.name.s, ep->ipp); } /*ARGSUSED*/ static void itree_pevent(struct event *lhs, struct event *ep, void *arg) { struct plut_wlk_data propd; struct bubble *bp; int flags = (int)(intptr_t)arg; itree_pevent_brief(flags, ep); if (ep->t == N_EREPORT) out(flags, " (count %d)", ep->count); else out(flags, NULL); if (ep->props) { propd.flags = flags; propd.first = 1; out(flags, "Properties:"); lut_walk(ep->props, ptree_plut, (void *)&propd); } for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { /* Print only TO bubbles in this loop */ if (bp->t != B_TO) continue; itree_pbubble(flags, bp); } for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { /* Print only INHIBIT bubbles in this loop */ if (bp->t != B_INHIBIT) continue; itree_pbubble(flags, bp); } for (bp = itree_next_bubble(ep, NULL); bp; bp = itree_next_bubble(ep, bp)) { /* Print only FROM bubbles in this loop */ if (bp->t != B_FROM) continue; itree_pbubble(flags, bp); } } static void itree_pbubble(int flags, struct bubble *bp) { struct constraintlist *cp; struct arrowlist *ap; ASSERT(bp != NULL); out(flags|O_NONL, " "); if (bp->mark) out(flags|O_NONL, "*"); else out(flags|O_NONL, " "); if (bp->t == B_FROM) out(flags|O_NONL, "N=%d to:", bp->nork); else if (bp->t == B_TO) out(flags|O_NONL, "K=%d from:", bp->nork); else out(flags|O_NONL, "K=%d masked from:", bp->nork); if (bp->t == B_TO || bp->t == B_INHIBIT) { for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) { ASSERT(ap->arrowp->head == bp); ASSERT(ap->arrowp->tail != NULL); ASSERT(ap->arrowp->tail->myevent != NULL); out(flags|O_NONL, " "); itree_pevent_brief(flags, ap->arrowp->tail->myevent); } out(flags, NULL); return; } for (ap = itree_next_arrow(bp, NULL); ap; ap = itree_next_arrow(bp, ap)) { ASSERT(ap->arrowp->tail == bp); ASSERT(ap->arrowp->head != NULL); ASSERT(ap->arrowp->head->myevent != NULL); out(flags|O_NONL, " "); itree_pevent_brief(flags, ap->arrowp->head->myevent); out(flags|O_NONL, " "); ptree_timeval(flags, &ap->arrowp->mindelay); out(flags|O_NONL, ","); ptree_timeval(flags, &ap->arrowp->maxdelay); /* Display anything from the propogation node? */ out(O_VERB3|O_NONL, " <%s:%d>", ap->arrowp->pnode->file, ap->arrowp->pnode->line); if (itree_next_constraint(ap->arrowp, NULL)) out(flags|O_NONL, " {"); for (cp = itree_next_constraint(ap->arrowp, NULL); cp; cp = itree_next_constraint(ap->arrowp, cp)) { ptree(flags, cp->cnode, 1, 0); if (itree_next_constraint(ap->arrowp, cp)) out(flags|O_NONL, ", "); } if (itree_next_constraint(ap->arrowp, NULL)) out(flags|O_NONL, "}"); } out(flags, NULL); } void itree_ptree(int flags, struct lut *itp) { lut_walk(itp, (lut_cb)itree_pevent, (void *)(intptr_t)flags); } /*ARGSUSED*/ static void itree_destructor(void *left, void *right, void *arg) { struct event *ep = (struct event *)right; struct bubble *nextbub, *bub; /* Free the properties */ if (ep->props) lut_free(ep->props, instances_destructor, NULL); /* Free the payload properties */ if (ep->payloadprops) lut_free(ep->payloadprops, payloadprops_destructor, NULL); /* Free the serd properties */ if (ep->serdprops) lut_free(ep->serdprops, serdprops_destructor, NULL); /* Free my bubbles */ for (bub = ep->bubbles; bub != NULL; ) { nextbub = bub->next; /* * Free arrows if they are FROM me. Free arrowlists on * other types of bubbles (but not the attached arrows, * which will be freed when we free the originating * bubble. */ if (bub->t == B_FROM) itree_free_arrowlists(bub, 1); else itree_free_arrowlists(bub, 0); itree_free_bubble(bub); bub = nextbub; } nvlist_free(ep->nvp); alloc_xfree(ep, sizeof (*ep)); } /*ARGSUSED*/ static void itree_pruner(void *left, void *right, void *arg) { struct event *ep = (struct event *)right; struct bubble *nextbub, *bub; if (ep->keep_in_tree) return; /* Free the properties */ lut_free(ep->props, instances_destructor, NULL); /* Free the payload properties */ lut_free(ep->payloadprops, payloadprops_destructor, NULL); /* Free the serd properties */ lut_free(ep->serdprops, serdprops_destructor, NULL); /* Free my bubbles */ for (bub = ep->bubbles; bub != NULL; ) { nextbub = bub->next; itree_prune_arrowlists(bub); itree_free_bubble(bub); bub = nextbub; } nvlist_free(ep->nvp); ep->props = NULL; ep->payloadprops = NULL; ep->serdprops = NULL; ep->bubbles = NULL; ep->nvp = NULL; } static void itree_free_bubble(struct bubble *freeme) { alloc_xfree(freeme, sizeof (*freeme)); } static struct bubble * itree_add_bubble(struct event *eventp, enum bubbletype btype, int nork, int gen) { struct bubble *prev = NULL; struct bubble *curr; struct bubble *newb; /* Use existing bubbles as appropriate when possible */ for (curr = eventp->bubbles; curr != NULL; prev = curr, curr = curr->next) { if (btype == B_TO && curr->t == B_TO) { /* see if an existing "to" bubble works for us */ if (gen == curr->gen) return (curr); /* matched gen number */ else if (nork == 1 && curr->nork == 1) { curr->gen = gen; return (curr); /* coalesce K==1 bubbles */ } } else if (btype == B_FROM && curr->t == B_FROM) { /* see if an existing "from" bubble works for us */ if ((nork == N_IS_ALL && curr->nork == N_IS_ALL) || (nork == 0 && curr->nork == 0)) return (curr); } } newb = alloc_xmalloc(sizeof (struct bubble)); newb->next = NULL; newb->t = btype; newb->myevent = eventp; newb->nork = nork; newb->mark = 0; newb->gen = gen; newb->arrows = NULL; if (prev == NULL) eventp->bubbles = newb; else prev->next = newb; return (newb); } struct bubble * itree_next_bubble(struct event *eventp, struct bubble *last) { struct bubble *next; for (;;) { if (last != NULL) next = last->next; else next = eventp->bubbles; if (next == NULL || next->arrows != NULL) return (next); /* bubble was empty, skip it */ last = next; } } static void add_arrow(struct bubble *bp, struct arrow *ap) { struct arrowlist *prev = NULL; struct arrowlist *curr; struct arrowlist *newal; newal = alloc_xmalloc(sizeof (struct arrowlist)); bzero(newal, sizeof (struct arrowlist)); newal->arrowp = ap; curr = itree_next_arrow(bp, NULL); while (curr != NULL) { prev = curr; curr = itree_next_arrow(bp, curr); } if (prev == NULL) bp->arrows = newal; else prev->next = newal; } static struct arrow * itree_add_arrow(struct node *apnode, struct node *fromevent, struct node *toevent, struct lut *ex) { struct arrow *newa; newa = alloc_xmalloc(sizeof (struct arrow)); bzero(newa, sizeof (struct arrow)); newa->pnode = apnode; newa->constraints = NULL; /* * Set default delays, then try to re-set them from * any within() constraints. */ newa->mindelay = newa->maxdelay = 0ULL; if (itree_set_arrow_traits(newa, fromevent, toevent, ex) == 0) { alloc_xfree(newa, sizeof (struct arrow)); return (NULL); } return (newa); } /* returns false if traits show that arrow should not be added after all */ static int itree_set_arrow_traits(struct arrow *ap, struct node *fromev, struct node *toev, struct lut *ex) { struct node *events[] = { NULL, NULL, NULL }; struct node *newc = NULL; ASSERTeq(fromev->t, T_EVENT, ptree_nodetype2str); ASSERTeq(toev->t, T_EVENT, ptree_nodetype2str); /* * search for the within values first on the declaration of * the destination event, and then on the prop. this allows * one to specify a "default" within by putting it on the * declaration, but then allow overriding on the prop statement. */ arrow_add_within(ap, toev->u.event.declp->u.stmt.np->u.event.eexprlist); arrow_add_within(ap, toev->u.event.eexprlist); /* * handle any global constraints inherited from the * "fromev" event's declaration */ ASSERT(fromev->u.event.declp != NULL); ASSERT(fromev->u.event.declp->u.stmt.np != NULL); #ifdef notdef /* XXX not quite ready to evaluate constraints from decls yet */ if (fromev->u.event.declp->u.stmt.np->u.event.eexprlist) (void) itree_add_constraint(ap, fromev->u.event.declp->u.stmt.np->u.event.eexprlist); #endif /* notdef */ /* handle constraints on the from event in the prop statement */ events[0] = fromev; events[1] = toev; if (eval_potential(fromev->u.event.eexprlist, ex, events, &newc, Ninfo.croot) == 0) return (0); /* constraint disallows arrow */ /* * handle any global constraints inherited from the * "toev" event's declaration */ ASSERT(toev->u.event.declp != NULL); ASSERT(toev->u.event.declp->u.stmt.np != NULL); #ifdef notdef /* XXX not quite ready to evaluate constraints from decls yet */ if (toev->u.event.declp->u.stmt.np->u.event.eexprlist) (void) itree_add_constraint(ap, toev->u.event.declp->u.stmt.np->u.event.eexprlist); #endif /* notdef */ /* handle constraints on the to event in the prop statement */ events[0] = toev; events[1] = fromev; if (eval_potential(toev->u.event.eexprlist, ex, events, &newc, Ninfo.croot) == 0) { if (newc != NULL) tree_free(newc); return (0); /* constraint disallows arrow */ } /* if we came up with any deferred constraints, add them to arrow */ if (newc != NULL) { out(O_ALTFP|O_VERB3, "(deferred constraints)"); (void) itree_add_constraint(ap, iexpr(newc)); } return (1); /* constraints allow arrow */ } /* * Set within() constraint. If the constraint were set multiple times, * the last one would "win". */ static void arrow_add_within(struct arrow *ap, struct node *xpr) { struct node *arglist; /* end of expressions list */ if (xpr == NULL) return; switch (xpr->t) { case T_LIST: arrow_add_within(ap, xpr->u.expr.left); arrow_add_within(ap, xpr->u.expr.right); return; case T_FUNC: if (xpr->u.func.s != L_within) return; arglist = xpr->u.func.arglist; switch (arglist->t) { case T_TIMEVAL: ap->mindelay = 0; ap->maxdelay = arglist->u.ull; break; case T_NAME: ASSERT(arglist->u.name.s == L_infinity); ap->mindelay = 0; ap->maxdelay = TIMEVAL_EVENTUALLY; break; case T_LIST: ASSERT(arglist->u.expr.left->t == T_TIMEVAL); ap->mindelay = arglist->u.expr.left->u.ull; switch (arglist->u.expr.right->t) { case T_TIMEVAL: ap->maxdelay = arglist->u.ull; break; case T_NAME: ASSERT(arglist->u.expr.right->u.name.s == L_infinity); ap->maxdelay = TIMEVAL_EVENTUALLY; break; default: out(O_DIE, "within: unexpected 2nd arg type"); } break; default: out(O_DIE, "within: unexpected 1st arg type"); } break; default: return; } } static void itree_free_arrowlists(struct bubble *bubp, int arrows_too) { struct arrowlist *al, *nal; al = bubp->arrows; while (al != NULL) { nal = al->next; if (arrows_too) { itree_free_constraints(al->arrowp); alloc_xfree(al->arrowp, sizeof (struct arrow)); } alloc_xfree(al, sizeof (*al)); al = nal; } } static void itree_delete_arrow(struct bubble *bubp, struct arrow *arrow) { struct arrowlist *al, *oal; al = bubp->arrows; if (al->arrowp == arrow) { bubp->arrows = al->next; alloc_xfree(al, sizeof (*al)); return; } while (al != NULL) { oal = al; al = al->next; ASSERT(al != NULL); if (al->arrowp == arrow) { oal->next = al->next; alloc_xfree(al, sizeof (*al)); return; } } } static void itree_prune_arrowlists(struct bubble *bubp) { struct arrowlist *al, *nal; al = bubp->arrows; while (al != NULL) { nal = al->next; if (bubp->t == B_FROM) itree_delete_arrow(al->arrowp->head, al->arrowp); else itree_delete_arrow(al->arrowp->tail, al->arrowp); itree_free_constraints(al->arrowp); alloc_xfree(al->arrowp, sizeof (struct arrow)); alloc_xfree(al, sizeof (*al)); al = nal; } } struct arrowlist * itree_next_arrow(struct bubble *bubble, struct arrowlist *last) { struct arrowlist *next; if (last != NULL) next = last->next; else next = bubble->arrows; return (next); } static struct constraintlist * itree_add_constraint(struct arrow *arrowp, struct node *c) { struct constraintlist *prev = NULL; struct constraintlist *curr; struct constraintlist *newc; for (curr = arrowp->constraints; curr != NULL; prev = curr, curr = curr->next) ; newc = alloc_xmalloc(sizeof (struct constraintlist)); newc->next = NULL; newc->cnode = c; if (prev == NULL) arrowp->constraints = newc; else prev->next = newc; return (newc); } struct constraintlist * itree_next_constraint(struct arrow *arrowp, struct constraintlist *last) { struct constraintlist *next; if (last != NULL) next = last->next; else next = arrowp->constraints; return (next); } static void itree_free_constraints(struct arrow *ap) { struct constraintlist *cl, *ncl; cl = ap->constraints; while (cl != NULL) { ncl = cl->next; ASSERT(cl->cnode != NULL); if (!iexpr_cached(cl->cnode)) tree_free(cl->cnode); else iexpr_free(cl->cnode); alloc_xfree(cl, sizeof (*cl)); cl = ncl; } } const char * itree_bubbletype2str(enum bubbletype t) { static char buf[100]; switch (t) { case B_FROM: return L_from; case B_TO: return L_to; case B_INHIBIT: return L_inhibit; default: (void) sprintf(buf, "[unexpected bubbletype: %d]", t); return (buf); } } /* * itree_fini -- clean up any half-built itrees */ void itree_fini(void) { if (Ninfo.lut != NULL) { itree_free(Ninfo.lut); Ninfo.lut = NULL; } if (Ninfo.ex) { lut_free(Ninfo.ex, iterinfo_destructor, NULL); Ninfo.ex = NULL; } } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * itree.h -- public definitions for itree module * */ #ifndef _EFT_ITREE_H #define _EFT_ITREE_H #ifdef __cplusplus extern "C" { #endif /* the "fault" field in the event struct requires the definition of nvlist_t */ #include #include /* Numerical representation of propagation N value (A), short for All */ #define N_IS_ALL -1 /* * effects_test event cached_state bits * - reset on each call to effects_test() */ #define CREDIBLE_EFFECT 1 #define WAIT_EFFECT 2 #define PARENT_WAIT 4 /* * arrow mark bits (for K-count) */ #define EFFECTS_COUNTER 8 #define REQMNTS_COUNTER 16 /* * requirements_test event cached_state bits */ #define REQMNTS_CREDIBLE 32 #define REQMNTS_DISPROVED 64 #define REQMNTS_WAIT 128 /* * requirements_test bubble mark bits */ #define BUBBLE_ELIDED 256 #define BUBBLE_OK 512 /* * causes_test event cached_state bits */ #define CAUSES_TESTED 1024 struct event { struct event *suspects; struct event *psuspects; struct event *observations; /* for lists like suspect list */ fmd_event_t *ffep; nvlist_t *nvp; /* payload nvp for ereports */ struct node *enode; /* event node in parse tree */ const struct ipath *ipp; /* instanced version of event */ const struct ipath *ipp_un; /* full version for Usednames */ struct lut *props; /* instanced version of nvpairs */ struct lut *payloadprops; /* nvpairs for problem payload */ struct lut *serdprops; /* nvpairs for dynamic serd args */ int count; /* for reports, number seen */ enum nametype t:3; /* defined in tree.h */ int is_suspect:1; /* true if on suspect list */ int keep_in_tree:1; int cached_state:11; unsigned long long cached_delay; struct bubble { struct bubble *next; struct event *myevent; int gen; /* generation # */ int nork; int mark:11; enum bubbletype { B_FROM, B_TO, B_INHIBIT } t:2; struct arrowlist { struct arrowlist *next; struct arrow { struct bubble *head; struct bubble *tail; /* prop node in parse tree */ struct node *pnode; struct constraintlist { struct constraintlist *next; /* deferred constraints */ struct node *cnode; } *constraints; int forever_false:1; int forever_true:1; int arrow_marked:1; int mark:11; unsigned long long mindelay; unsigned long long maxdelay; } *arrowp; } *arrows; } *bubbles; }; /* * struct iterinfo is the stuff we store in the dictionary of iterators * when we assign a value to an iterator. it not only contains the value * we assigned to the iterator, it contains a node pointer which we use to * determine if we're the one that defined the value when popping [vh]match() * recursion. */ struct iterinfo { int num; struct node *np; }; struct lut *itree_create(struct config *croot); void itree_free(struct lut *itp); void itree_prune(struct lut *itp); struct event *itree_lookup(struct lut *itp, const char *ename, const struct ipath *ipp); struct arrowlist *itree_next_arrow(struct bubble *bubblep, struct arrowlist *last); struct bubble *itree_next_bubble(struct event *eventp, struct bubble *last); struct constraintlist *itree_next_constraint(struct arrow *arrowp, struct constraintlist *last); void itree_pevent_brief(int flags, struct event *eventp); void itree_ptree(int flags, struct lut *itp); const char *itree_bubbletype2str(enum bubbletype t); void itree_fini(void); #ifdef __cplusplus } #endif #endif /* _EFT_ITREE_H */ /* * 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. * * platform.c -- interfaces to the platform's configuration information * * this platform.c allows eft to run on Solaris systems. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alloc.h" #include "out.h" #include "tree.h" #include "itree.h" #include "ipath.h" #include "ptree.h" #include "fme.h" #include "stable.h" #include "eval.h" #include "config.h" #include "platform.h" extern fmd_hdl_t *Hdl; /* handle from eft.c */ /* * Lastcfg points to the last configuration snapshot we made. */ static struct cfgdata *Lastcfg; static fmd_hdl_t *Lasthdl; static fmd_case_t *Lastfmcase; static const char *lastcomp; static int in_getpath; extern struct lut *Usednames; int prune_raw_config = 0; static topo_hdl_t *Eft_topo_hdl; void * topo_use_alloc(size_t bytes) { void *p = alloc_malloc(bytes, NULL, 0); bzero(p, bytes); return (p); } void topo_use_free(void *p) { alloc_free(p, NULL, 0); } /*ARGSUSED*/ static void * alloc_nv_alloc(nv_alloc_t *nva, size_t size) { return (alloc_malloc(size, NULL, 0)); } /*ARGSUSED*/ static void alloc_nv_free(nv_alloc_t *nva, void *p, size_t sz) { alloc_free(p, NULL, 0); } const nv_alloc_ops_t Eft_nv_alloc_ops = { NULL, /* nv_ao_init() */ NULL, /* nv_ao_fini() */ alloc_nv_alloc, /* nv_ao_alloc() */ alloc_nv_free, /* nv_ao_free() */ NULL /* nv_ao_reset() */ }; nv_alloc_t Eft_nv_hdl; static char *Root; static char *Mach; static char *Plat; static char tmpbuf[MAXPATHLEN]; static char numbuf[MAXPATHLEN]; /* * platform_globals -- set global variables based on sysinfo() calls */ static void platform_globals() { Root = fmd_prop_get_string(Hdl, "fmd.rootdir"); Mach = fmd_prop_get_string(Hdl, "fmd.machine"); Plat = fmd_prop_get_string(Hdl, "fmd.platform"); } static void platform_free_globals() { fmd_prop_free_string(Hdl, Root); fmd_prop_free_string(Hdl, Mach); fmd_prop_free_string(Hdl, Plat); } /* * platform_init -- perform any platform-specific initialization */ void platform_init(void) { (void) nv_alloc_init(&Eft_nv_hdl, &Eft_nv_alloc_ops); Eft_topo_hdl = fmd_hdl_topo_hold(Hdl, TOPO_VERSION); platform_globals(); out(O_ALTFP, "platform_init() sucessful"); } void platform_fini(void) { if (Lastcfg != NULL) { config_free(Lastcfg); Lastcfg = NULL; } fmd_hdl_topo_rele(Hdl, Eft_topo_hdl); platform_free_globals(); (void) nv_alloc_fini(&Eft_nv_hdl); out(O_ALTFP, "platform_fini() sucessful"); } /* * hc_fmri_nodeize -- convert hc-scheme FMRI to eft compatible format * * this is an internal platform.c helper routine */ static struct node * hc_fmri_nodeize(nvlist_t *hcfmri) { struct node *pathtree = NULL; struct node *tmpn; nvlist_t **hc_prs; uint_t hc_nprs; const char *sname; char *ename; char *eid; int e, r; /* * What to do with/about hc-root? Would we have any clue what * to do with it if it weren't /? For now, we don't bother * even looking it up. */ /* * Get the hc-list of elements in the FMRI */ if (nvlist_lookup_nvlist_array(hcfmri, FM_FMRI_HC_LIST, &hc_prs, &hc_nprs) != 0) { out(O_ALTFP, "XFILE: hc FMRI missing %s", FM_FMRI_HC_LIST); return (NULL); } for (e = 0; e < hc_nprs; e++) { ename = NULL; eid = NULL; r = nvlist_lookup_string(hc_prs[e], FM_FMRI_HC_NAME, &ename); r |= nvlist_lookup_string(hc_prs[e], FM_FMRI_HC_ID, &eid); if (r != 0) { /* probably should bail */ continue; } sname = stable(ename); tmpn = tree_name_iterator( tree_name(sname, IT_VERTICAL, NULL, 0), tree_num(eid, NULL, 0)); if (pathtree == NULL) pathtree = tmpn; else (void) tree_name_append(pathtree, tmpn); } return (pathtree); } /* * platform_getpath -- extract eft-compatible path from ereport */ struct node * platform_getpath(nvlist_t *nvl) { struct node *ret; nvlist_t *dfmri, *real_fmri, *resource; char *scheme; char *path; char *devid; char *tp; uint32_t cpuid; int err; enum {DT_HC, DT_DEVID, DT_TP, DT_DEV, DT_CPU, DT_UNKNOWN} type = DT_UNKNOWN; /* Find the detector */ if (nvlist_lookup_nvlist(nvl, FM_EREPORT_DETECTOR, &dfmri) != 0) { out(O_ALTFP, "XFILE: ereport has no detector FMRI"); return (NULL); } /* get the scheme from the detector */ if (nvlist_lookup_string(dfmri, FM_FMRI_SCHEME, &scheme) != 0) { out(O_ALTFP, "XFILE: detector FMRI missing scheme"); return (NULL); } /* based on scheme, determine type */ if (strcmp(scheme, FM_FMRI_SCHEME_HC) == 0) { /* already in hc scheme */ type = DT_HC; } else if (strcmp(scheme, FM_FMRI_SCHEME_DEV) == 0) { /* * devid takes precedence over tp which takes precedence over * path */ if (nvlist_lookup_string(dfmri, FM_FMRI_DEV_ID, &devid) == 0) type = DT_DEVID; else if (nvlist_lookup_string(dfmri, TOPO_STORAGE_TARGET_PORT_L0ID, &tp) == 0) type = DT_TP; else if (nvlist_lookup_string(dfmri, FM_FMRI_DEV_PATH, &path) == 0) type = DT_DEV; else { out(O_ALTFP, "XFILE: detector FMRI missing %s or %s", FM_FMRI_DEV_ID, FM_FMRI_DEV_PATH); return (NULL); } } else if (strcmp(scheme, FM_FMRI_SCHEME_CPU) == 0) { if (nvlist_lookup_uint32(dfmri, FM_FMRI_CPU_ID, &cpuid) == 0) type = DT_CPU; else { out(O_ALTFP, "XFILE: detector FMRI missing %s", FM_FMRI_CPU_ID); return (NULL); } } else { out(O_ALTFP, "XFILE: detector FMRI not recognized " "(scheme is %s, expect %s or %s or %s)", scheme, FM_FMRI_SCHEME_HC, FM_FMRI_SCHEME_DEV, FM_FMRI_SCHEME_CPU); return (NULL); } out(O_ALTFP|O_VERB, "Received ereport in scheme %s", scheme); /* take a config snapshot */ lut_free(Usednames, NULL, NULL); Usednames = NULL; in_getpath = 1; if (config_snapshot() == NULL) { if (type == DT_HC) { /* * If hc-scheme use the fmri that was passed in. */ in_getpath = 0; return (hc_fmri_nodeize(dfmri)); } out(O_ALTFP, "XFILE: cannot snapshot configuration"); in_getpath = 0; return (NULL); } /* * For hc scheme, if we can find the resource from the tolopogy, use * that - otherwise use the fmri that was passed in. For other schemes * look up the path, cpuid, tp or devid in the topology. */ switch (type) { case DT_HC: if (topo_fmri_getprop(Eft_topo_hdl, dfmri, TOPO_PGROUP_PROTOCOL, TOPO_PROP_RESOURCE, NULL, &resource, &err) == -1) { ret = hc_fmri_nodeize(dfmri); break; } else if (nvlist_lookup_nvlist(resource, TOPO_PROP_VAL_VAL, &real_fmri) != 0) ret = hc_fmri_nodeize(dfmri); else ret = hc_fmri_nodeize(real_fmri); nvlist_free(resource); break; case DT_DEV: if ((ret = config_bydev_lookup(Lastcfg, path)) == NULL) out(O_ALTFP, "platform_getpath: no configuration node " "has device path matching \"%s\".", path); break; case DT_TP: if ((ret = config_bytp_lookup(Lastcfg, tp)) == NULL) out(O_ALTFP, "platform_getpath: no configuration node " "has tp matching \"%s\".", tp); break; case DT_DEVID: if ((ret = config_bydevid_lookup(Lastcfg, devid)) == NULL) out(O_ALTFP, "platform_getpath: no configuration node " "has devid matching \"%s\".", devid); break; case DT_CPU: if ((ret = config_bycpuid_lookup(Lastcfg, cpuid)) == NULL) out(O_ALTFP, "platform_getpath: no configuration node " "has cpu-id matching %u.", cpuid); break; } /* free the snapshot */ structconfig_free(Lastcfg->cooked); config_free(Lastcfg); in_getpath = 0; return (ret); } /* Allocate space for raw config strings in chunks of this size */ #define STRSBUFLEN 512 /* * cfgadjust -- Make sure the amount we want to add to the raw config string * buffer will fit, and if not, increase the size of the buffer. */ static void cfgadjust(struct cfgdata *rawdata, int addlen) { int curnext, newlen; if (rawdata->nextfree + addlen >= rawdata->end) { newlen = (((rawdata->nextfree - rawdata->begin + 1 + addlen) / STRSBUFLEN) + 1) * STRSBUFLEN; curnext = rawdata->nextfree - rawdata->begin; rawdata->begin = REALLOC(rawdata->begin, newlen); rawdata->nextfree = rawdata->begin + curnext; rawdata->end = rawdata->begin + newlen; } } static char * hc_path(tnode_t *node) { int i, err; char *name, *instance, *estr; nvlist_t *fmri, **hcl; ulong_t ul; uint_t nhc; if (topo_prop_get_fmri(node, TOPO_PGROUP_PROTOCOL, TOPO_PROP_RESOURCE, &fmri, &err) < 0) return (NULL); if (nvlist_lookup_nvlist_array(fmri, FM_FMRI_HC_LIST, &hcl, &nhc) != 0) { nvlist_free(fmri); return (NULL); } tmpbuf[0] = '\0'; for (i = 0; i < nhc; ++i) { err = nvlist_lookup_string(hcl[i], FM_FMRI_HC_NAME, &name); err |= nvlist_lookup_string(hcl[i], FM_FMRI_HC_ID, &instance); if (err) { nvlist_free(fmri); return (NULL); } ul = strtoul(instance, &estr, 10); /* conversion to number failed? */ if (estr == instance) { nvlist_free(fmri); return (NULL); } (void) strlcat(tmpbuf, "/", MAXPATHLEN); (void) strlcat(tmpbuf, name, MAXPATHLEN); (void) snprintf(numbuf, MAXPATHLEN, "%lu", ul); (void) strlcat(tmpbuf, numbuf, MAXPATHLEN); lastcomp = stable(name); } nvlist_free(fmri); return (tmpbuf); } static void add_prop_val(topo_hdl_t *thp, struct cfgdata *rawdata, char *propn, nvpair_t *pv_nvp) { int addlen, err; char *propv, *fmristr = NULL; nvlist_t *fmri; uint32_t ui32; int64_t i64; int32_t i32; boolean_t bool; uint64_t ui64; char buf[32]; /* big enough for any 64-bit int */ uint_t nelem; int i, j, sz; char **propvv; /* * malformed prop nvpair */ if (propn == NULL) return; switch (nvpair_type(pv_nvp)) { case DATA_TYPE_STRING_ARRAY: /* * Convert string array into single space-separated string */ (void) nvpair_value_string_array(pv_nvp, &propvv, &nelem); for (sz = 0, i = 0; i < nelem; i++) sz += strlen(propvv[i]) + 1; propv = MALLOC(sz); for (j = 0, i = 0; i < nelem; j++, i++) { (void) strcpy(&propv[j], propvv[i]); j += strlen(propvv[i]); if (i < nelem - 1) propv[j] = ' '; } break; case DATA_TYPE_STRING: (void) nvpair_value_string(pv_nvp, &propv); break; case DATA_TYPE_NVLIST: /* * At least try to collect the protocol * properties */ (void) nvpair_value_nvlist(pv_nvp, &fmri); if (topo_fmri_nvl2str(thp, fmri, &fmristr, &err) < 0) { out(O_ALTFP, "cfgcollect: failed to convert fmri to " "string"); return; } else { propv = fmristr; } break; case DATA_TYPE_UINT64: /* * Convert uint64 to hex strings */ (void) nvpair_value_uint64(pv_nvp, &ui64); (void) snprintf(buf, sizeof (buf), "0x%llx", ui64); propv = buf; break; case DATA_TYPE_BOOLEAN_VALUE: /* * Convert boolean_t to hex strings */ (void) nvpair_value_boolean_value(pv_nvp, &bool); (void) snprintf(buf, sizeof (buf), "0x%llx", (uint64_t)bool); propv = buf; break; case DATA_TYPE_INT32: /* * Convert int32 to hex strings */ (void) nvpair_value_int32(pv_nvp, &i32); (void) snprintf(buf, sizeof (buf), "0x%llx", (uint64_t)(int64_t)i32); propv = buf; break; case DATA_TYPE_INT64: /* * Convert int64 to hex strings */ (void) nvpair_value_int64(pv_nvp, &i64); (void) snprintf(buf, sizeof (buf), "0x%llx", (uint64_t)i64); propv = buf; break; case DATA_TYPE_UINT32: /* * Convert uint32 to hex strings */ (void) nvpair_value_uint32(pv_nvp, &ui32); (void) snprintf(buf, sizeof (buf), "0x%llx", (uint64_t)ui32); propv = buf; break; default: out(O_ALTFP, "cfgcollect: failed to get property value for " "%s", propn); return; } /* = & NULL */ addlen = strlen(propn) + strlen(propv) + 2; cfgadjust(rawdata, addlen); (void) snprintf(rawdata->nextfree, rawdata->end - rawdata->nextfree, "%s=%s", propn, propv); if (strcmp(propn, TOPO_PROP_RESOURCE) == 0) out(O_ALTFP|O_VERB3, "cfgcollect: %s", propv); if (nvpair_type(pv_nvp) == DATA_TYPE_STRING_ARRAY) FREE(propv); rawdata->nextfree += addlen; if (fmristr != NULL) topo_hdl_strfree(thp, fmristr); } /* * cfgcollect -- Assemble raw configuration data in string form suitable * for checkpointing. */ static int cfgcollect(topo_hdl_t *thp, tnode_t *node, void *arg) { struct cfgdata *rawdata = (struct cfgdata *)arg; int err, addlen; char *propn, *path = NULL; nvlist_t *p_nv, *pg_nv, *pv_nv; nvpair_t *nvp, *pg_nvp, *pv_nvp; if (topo_node_flags(node) == TOPO_NODE_FACILITY) return (TOPO_WALK_NEXT); path = hc_path(node); if (path == NULL) return (TOPO_WALK_ERR); addlen = strlen(path) + 1; cfgadjust(rawdata, addlen); (void) strcpy(rawdata->nextfree, path); rawdata->nextfree += addlen; /* * If the prune_raw_config flag is set then we will only include in the * raw config those nodes that are used by the rules remaining after * prune_propagations() has been run - ie only those that could possibly * be relevant to the incoming ereport given the current rules. This * means that any other parts of the config will not get saved to the * checkpoint file (even if they may theoretically be used if the * rules are subsequently modified). * * For now prune_raw_config is 0 for Solaris, though it is expected to * be set to 1 for fmsp. * * Note we only prune the raw config like this if we have been called * from newfme(), not if we have been called when handling dev or cpu * scheme ereports from platform_getpath(), as this is called before * prune_propagations() - again this is not an issue on fmsp as the * ereports are all in hc scheme. */ if (!in_getpath && prune_raw_config && lut_lookup(Usednames, (void *)lastcomp, NULL) == NULL) return (TOPO_WALK_NEXT); /* * Collect properties * * eversholt should support alternate property types * Better yet, topo properties could be represented as * a packed nvlist */ p_nv = topo_prop_getprops(node, &err); for (nvp = nvlist_next_nvpair(p_nv, NULL); nvp != NULL; nvp = nvlist_next_nvpair(p_nv, nvp)) { if (strcmp(TOPO_PROP_GROUP, nvpair_name(nvp)) != 0 || nvpair_type(nvp) != DATA_TYPE_NVLIST) continue; (void) nvpair_value_nvlist(nvp, &pg_nv); for (pg_nvp = nvlist_next_nvpair(pg_nv, NULL); pg_nvp != NULL; pg_nvp = nvlist_next_nvpair(pg_nv, pg_nvp)) { if (strcmp(TOPO_PROP_VAL, nvpair_name(pg_nvp)) != 0 || nvpair_type(pg_nvp) != DATA_TYPE_NVLIST) continue; (void) nvpair_value_nvlist(pg_nvp, &pv_nv); propn = NULL; for (pv_nvp = nvlist_next_nvpair(pv_nv, NULL); pv_nvp != NULL; pv_nvp = nvlist_next_nvpair(pv_nv, pv_nvp)) { /* Get property name */ if (strcmp(TOPO_PROP_VAL_NAME, nvpair_name(pv_nvp)) == 0) (void) nvpair_value_string(pv_nvp, &propn); /* * Get property value */ if (strcmp(TOPO_PROP_VAL_VAL, nvpair_name(pv_nvp)) == 0) add_prop_val(thp, rawdata, propn, pv_nvp); } } } nvlist_free(p_nv); return (TOPO_WALK_NEXT); } void platform_restore_config(fmd_hdl_t *hdl, fmd_case_t *fmcase) { if (hdl == Lasthdl && fmcase == Lastfmcase) { size_t cfglen; fmd_buf_read(Lasthdl, Lastfmcase, WOBUF_CFGLEN, (void *)&cfglen, sizeof (size_t)); Lastcfg->begin = MALLOC(cfglen); Lastcfg->end = Lastcfg->nextfree = Lastcfg->begin + cfglen; fmd_buf_read(Lasthdl, Lastfmcase, WOBUF_CFG, Lastcfg->begin, cfglen); Lasthdl = NULL; Lastfmcase = NULL; } } void platform_save_config(fmd_hdl_t *hdl, fmd_case_t *fmcase) { size_t cfglen; /* * Put the raw config into an fmd_buf. Then we can free it to * save space. */ Lastfmcase = fmcase; Lasthdl = hdl; cfglen = Lastcfg->nextfree - Lastcfg->begin; fmd_buf_create(hdl, fmcase, WOBUF_CFGLEN, sizeof (cfglen)); fmd_buf_write(hdl, fmcase, WOBUF_CFGLEN, (void *)&cfglen, sizeof (cfglen)); if (cfglen != 0) { fmd_buf_create(hdl, fmcase, WOBUF_CFG, cfglen); fmd_buf_write(hdl, fmcase, WOBUF_CFG, Lastcfg->begin, cfglen); } FREE(Lastcfg->begin); Lastcfg->begin = NULL; Lastcfg->end = NULL; Lastcfg->nextfree = NULL; } /* * platform_config_snapshot -- gather a snapshot of the current configuration */ struct cfgdata * platform_config_snapshot(void) { int err; topo_walk_t *twp; static uint64_t lastgen; uint64_t curgen; /* * If the DR generation number has changed, * we need to grab a new snapshot, otherwise we * can simply point them at the last config. */ if (prune_raw_config == 0 && (curgen = fmd_fmri_get_drgen()) <= lastgen && Lastcfg != NULL) { Lastcfg->raw_refcnt++; /* * if config has been backed away to an fmd_buf, restore it */ if (Lastcfg->begin == NULL) platform_restore_config(Lasthdl, Lastfmcase); return (Lastcfg); } lastgen = curgen; /* we're getting a new config, so clean up the last one */ if (Lastcfg != NULL) { config_free(Lastcfg); } Lastcfg = MALLOC(sizeof (struct cfgdata)); Lastcfg->raw_refcnt = 2; /* caller + Lastcfg */ Lastcfg->begin = Lastcfg->nextfree = Lastcfg->end = NULL; Lastcfg->cooked = NULL; Lastcfg->devcache = NULL; Lastcfg->devidcache = NULL; Lastcfg->tpcache = NULL; Lastcfg->cpucache = NULL; fmd_hdl_topo_rele(Hdl, Eft_topo_hdl); Eft_topo_hdl = fmd_hdl_topo_hold(Hdl, TOPO_VERSION); if ((twp = topo_walk_init(Eft_topo_hdl, FM_FMRI_SCHEME_HC, cfgcollect, Lastcfg, &err)) == NULL) { out(O_DIE, "platform_config_snapshot: NULL topology tree: %s", topo_strerror(err)); } if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) { topo_walk_fini(twp); out(O_DIE, "platform_config_snapshot: error walking topology " "tree"); } topo_walk_fini(twp); out(O_ALTFP|O_STAMP, "raw config complete"); return (Lastcfg); } static const char * cfgstrprop_lookup(struct config *croot, char *path, const char *pname) { struct config *cresource; const char *fmristr; /* * The first order of business is to find the resource in the * config database so we can examine properties associated with * that node. */ if ((cresource = config_lookup(croot, path, 0)) == NULL) { out(O_ALTFP, "Cannot find config info for %s.", path); return (NULL); } if ((fmristr = config_getprop(cresource, pname)) == NULL) { out(O_ALTFP, "Cannot find %s property for %s resource " "re-write", pname, path); return (NULL); } return (fmristr); } /* * Get FMRI for a particular unit from libtopo. The unit is specified by the * "path" argument (a stringified ipath). "prop" argument should be one * of the constants TOPO_PROP_RESOURCE, TOPO_PROP_ASRU, TOPO_PROP_FRU, etc. */ /*ARGSUSED*/ void platform_unit_translate(int isdefect, struct config *croot, const char *prop, nvlist_t **fmrip, char *path) { const char *fmristr; char *serial; nvlist_t *fmri; int err; fmristr = cfgstrprop_lookup(croot, path, prop); if (fmristr == NULL) { out(O_ALTFP, "Cannot rewrite unit FMRI for %s.", path); return; } if (topo_fmri_str2nvl(Eft_topo_hdl, fmristr, &fmri, &err) < 0) { out(O_ALTFP, "Can not convert config info: %s", topo_strerror(err)); out(O_ALTFP, "Cannot rewrite unit FMRI for %s.", path); return; } /* * If we don't have a serial number in the unit then check if it * is available as a separate property and if so then add it. */ if (nvlist_lookup_string(fmri, FM_FMRI_HC_SERIAL_ID, &serial) != 0) { serial = (char *)cfgstrprop_lookup(croot, path, FM_FMRI_HC_SERIAL_ID); if (serial != NULL) (void) nvlist_add_string(fmri, FM_FMRI_HC_SERIAL_ID, serial); } *fmrip = fmri; } /* * platform_get_files -- return names of all files we should load * * search directories in dirname[] for all files with names ending with the * substring fnstr. dirname[] should be a NULL-terminated array. fnstr * may be set to "*" to indicate all files in a directory. * * if nodups is non-zero, then the first file of a given name found is * the only file added to the list of names. for example if nodups is * set and we're looking for .efts, and find a pci.eft in the dirname[0], * then no pci.eft found in any of the other dirname[] entries will be * included in the final list of names. * * this routine doesn't return NULL, even if no files are found (in that * case, a char ** is returned with the first element NULL). */ static char ** platform_get_files(const char *dirname[], const char *fnstr, int nodups) { DIR *dirp; struct dirent *dp; struct lut *foundnames = NULL; char **files = NULL; /* char * array of filenames found */ int nfiles = 0; /* files found so far */ int slots = 0; /* char * slots allocated in files */ size_t fnlen, d_namelen; size_t totlen; int i; static char *nullav; ASSERT(fnstr != NULL); fnlen = strlen(fnstr); for (i = 0; dirname[i] != NULL; i++) { out(O_VERB, "Looking for %s files in %s", fnstr, dirname[i]); if ((dirp = opendir(dirname[i])) == NULL) { out(O_DEBUG|O_SYS, "platform_get_files: opendir failed for %s", dirname[i]); continue; } while ((dp = readdir(dirp)) != NULL) { if ((fnlen == 1 && *fnstr == '*') || ((d_namelen = strlen(dp->d_name)) >= fnlen && strncmp(dp->d_name + d_namelen - fnlen, fnstr, fnlen) == 0)) { if (nodups != 0) { const char *snm = stable(dp->d_name); if (lut_lookup(foundnames, (void *)snm, NULL) != NULL) { out(O_VERB, "platform_get_files: " "skipping repeated name " "%s/%s", dirname[i], snm); continue; } foundnames = lut_add(foundnames, (void *)snm, (void *)snm, NULL); } if (nfiles > slots - 2) { /* allocate ten more slots */ slots += 10; files = (char **)REALLOC(files, slots * sizeof (char *)); } /* prepend directory name and / */ totlen = strlen(dirname[i]) + 1; totlen += strlen(dp->d_name) + 1; files[nfiles] = MALLOC(totlen); out(O_VERB, "File %d: \"%s/%s\"", nfiles, dirname[i], dp->d_name); (void) snprintf(files[nfiles++], totlen, "%s/%s", dirname[i], dp->d_name); } } (void) closedir(dirp); } if (foundnames != NULL) lut_free(foundnames, NULL, NULL); if (nfiles == 0) return (&nullav); files[nfiles] = NULL; return (files); } /* * search for files in a standard set of directories */ static char ** platform_get_files_stddirs(char *fname, int nodups) { const char *dirlist[4]; char **flist; char *eftgendir, *eftmachdir, *eftplatdir; eftgendir = MALLOC(MAXPATHLEN); eftmachdir = MALLOC(MAXPATHLEN); eftplatdir = MALLOC(MAXPATHLEN); /* Generic files that apply to any machine */ (void) snprintf(eftgendir, MAXPATHLEN, "%s/usr/lib/fm/eft", Root); (void) snprintf(eftmachdir, MAXPATHLEN, "%s/usr/platform/%s/lib/fm/eft", Root, Mach); (void) snprintf(eftplatdir, MAXPATHLEN, "%s/usr/platform/%s/lib/fm/eft", Root, Plat); dirlist[0] = eftplatdir; dirlist[1] = eftmachdir; dirlist[2] = eftgendir; dirlist[3] = NULL; flist = platform_get_files(dirlist, fname, nodups); FREE(eftplatdir); FREE(eftmachdir); FREE(eftgendir); return (flist); } /* * platform_run_poller -- execute a poller * * when eft needs to know if a polled ereport exists this routine * is called so the poller code may be run in a platform-specific way. * there's no return value from this routine -- either the polled ereport * is generated (and delivered *before* this routine returns) or not. * any errors, like "poller unknown" are considered platform-specific * should be handled here rather than passing an error back up. */ /*ARGSUSED*/ void platform_run_poller(const char *poller) { } /* * fork and execve path with argument array argv and environment array * envp. data from stdout and stderr are placed in outbuf and errbuf, * respectively. * * see execve(2) for more descriptions for path, argv and envp. */ static int forkandexecve(const char *path, char *const argv[], char *const envp[], char *outbuf, size_t outbuflen, char *errbuf, size_t errbuflen) { pid_t pid; int outpipe[2], errpipe[2]; int rt = 0; /* * run the cmd and see if it failed. this function is *not* a * generic command runner -- we depend on some knowledge we * have about the commands we run. first of all, we expect * errors to spew something to stdout, and that something is * typically short enough to fit into a pipe so we can wait() * for the command to complete and then fetch the error text * from the pipe. */ if (pipe(outpipe) < 0) if (strlcat(errbuf, ": pipe(outpipe) failed", errbuflen) >= errbuflen) return (1); if (pipe(errpipe) < 0) if (strlcat(errbuf, ": pipe(errpipe) failed", errbuflen) >= errbuflen) return (1); if ((pid = fork()) < 0) { rt = (int)strlcat(errbuf, ": fork() failed", errbuflen); } else if (pid) { int wstat, count; /* parent */ (void) close(errpipe[1]); (void) close(outpipe[1]); /* PHASE2 need to guard against hang in child? */ if (waitpid(pid, &wstat, 0) < 0) if (strlcat(errbuf, ": waitpid() failed", errbuflen) >= errbuflen) return (1); /* check for stderr contents */ if (ioctl(errpipe[0], FIONREAD, &count) >= 0 && count) { if (read(errpipe[0], errbuf, errbuflen) <= 0) { /* * read failed even though ioctl indicated * that nonzero bytes were available for * reading */ if (strlcat(errbuf, ": read(errpipe) failed", errbuflen) >= errbuflen) return (1); } /* * handle case where errbuf is not properly * terminated */ if (count > errbuflen - 1) count = errbuflen - 1; if (errbuf[count - 1] != '\0' && errbuf[count - 1] != '\n') errbuf[count] = '\0'; } else if (WIFSIGNALED(wstat)) if (strlcat(errbuf, ": signaled", errbuflen) >= errbuflen) return (1); else if (WIFEXITED(wstat) && WEXITSTATUS(wstat)) if (strlcat(errbuf, ": abnormal exit", errbuflen) >= errbuflen) return (1); /* check for stdout contents */ if (ioctl(outpipe[0], FIONREAD, &count) >= 0 && count) { if (read(outpipe[0], outbuf, outbuflen) <= 0) { /* * read failed even though ioctl indicated * that nonzero bytes were available for * reading */ if (strlcat(errbuf, ": read(outpipe) failed", errbuflen) >= errbuflen) return (1); } /* * handle case where outbuf is not properly * terminated */ if (count > outbuflen - 1) count = outbuflen - 1; if (outbuf[count - 1] != '\0' && outbuf[count - 1] != '\n') outbuf[count] = '\0'; } (void) close(errpipe[0]); (void) close(outpipe[0]); } else { /* child */ (void) dup2(errpipe[1], fileno(stderr)); (void) close(errpipe[0]); (void) dup2(outpipe[1], fileno(stdout)); (void) close(outpipe[0]); if (execve(path, argv, envp)) perror(path); _exit(1); } return (rt); } #define MAXDIGITIDX 23 static int arglist2argv(struct node *np, struct lut **globals, struct config *croot, struct arrow *arrowp, char ***argv, int *argc, int *argvlen) { struct node *namep; char numbuf[MAXDIGITIDX + 1]; char *numstr, *nullbyte; char *addthisarg = NULL; if (np == NULL) return (0); switch (np->t) { case T_QUOTE: addthisarg = STRDUP(np->u.func.s); break; case T_LIST: if (arglist2argv(np->u.expr.left, globals, croot, arrowp, argv, argc, argvlen)) return (1); /* * only leftmost element of a list can provide the command * name (after which *argc becomes 1) */ ASSERT(*argc > 0); if (arglist2argv(np->u.expr.right, globals, croot, arrowp, argv, argc, argvlen)) return (1); break; case T_FUNC: case T_GLOBID: case T_ASSIGN: case T_CONDIF: case T_CONDELSE: case T_EQ: case T_NE: case T_LT: case T_LE: case T_GT: case T_GE: case T_BITAND: case T_BITOR: case T_BITXOR: case T_BITNOT: case T_LSHIFT: case T_RSHIFT: case T_AND: case T_OR: case T_NOT: case T_ADD: case T_SUB: case T_MUL: case T_DIV: case T_MOD: { struct evalue value; if (!eval_expr(np, NULL, NULL, globals, croot, arrowp, 0, &value)) return (1); switch (value.t) { case UINT64: numbuf[MAXDIGITIDX] = '\0'; nullbyte = &numbuf[MAXDIGITIDX]; numstr = ulltostr(value.v, nullbyte); addthisarg = STRDUP(numstr); break; case STRING: addthisarg = STRDUP((const char *)(uintptr_t)value.v); break; case NODEPTR : namep = (struct node *)(uintptr_t)value.v; addthisarg = ipath2str(NULL, ipath(namep)); break; default: out(O_ERR, "call: arglist2argv: unexpected result from" " operation %s", ptree_nodetype2str(np->t)); return (1); } break; } case T_NUM: case T_TIMEVAL: numbuf[MAXDIGITIDX] = '\0'; nullbyte = &numbuf[MAXDIGITIDX]; numstr = ulltostr(np->u.ull, nullbyte); addthisarg = STRDUP(numstr); break; case T_NAME: addthisarg = ipath2str(NULL, ipath(np)); break; case T_EVENT: addthisarg = ipath2str(np->u.event.ename->u.name.s, ipath(np->u.event.epname)); break; default: out(O_ERR, "call: arglist2argv: node type %s is unsupported", ptree_nodetype2str(np->t)); return (1); /*NOTREACHED*/ break; } if (*argc == 0 && addthisarg != NULL) { /* * first argument added is the command name. */ char **files; files = platform_get_files_stddirs(addthisarg, 0); /* do not proceed if number of files found != 1 */ if (files[0] == NULL) out(O_DIE, "call: function %s not found", addthisarg); if (files[1] != NULL) out(O_DIE, "call: multiple functions %s found", addthisarg); FREE(addthisarg); addthisarg = STRDUP(files[0]); FREE(files[0]); FREE(files); } if (addthisarg != NULL) { if (*argc >= *argvlen - 2) { /* * make sure argv is long enough so it has a * terminating element set to NULL */ *argvlen += 10; *argv = (char **)REALLOC(*argv, sizeof (char *) * *argvlen); } (*argv)[*argc] = addthisarg; (*argc)++; (*argv)[*argc] = NULL; } return (0); } static int generate_envp(struct arrow *arrowp, char ***envp, int *envc, int *envplen) { char *envnames[] = { "EFT_FROM_EVENT", "EFT_TO_EVENT", "EFT_FILE", "EFT_LINE", NULL }; char *envvalues[4]; char *none = "(none)"; size_t elen; int i; *envc = 4; /* * make sure envp is long enough so it has a terminating element * set to NULL */ *envplen = *envc + 1; *envp = (char **)MALLOC(sizeof (char *) * *envplen); envvalues[0] = ipath2str( arrowp->tail->myevent->enode->u.event.ename->u.name.s, arrowp->tail->myevent->ipp); envvalues[1] = ipath2str( arrowp->head->myevent->enode->u.event.ename->u.name.s, arrowp->head->myevent->ipp); if (arrowp->head->myevent->enode->file == NULL) { envvalues[2] = STRDUP(none); envvalues[3] = STRDUP(none); } else { envvalues[2] = STRDUP(arrowp->head->myevent->enode->file); /* large enough for max int */ envvalues[3] = MALLOC(sizeof (char) * 25); (void) snprintf(envvalues[3], sizeof (envvalues[3]), "%d", arrowp->head->myevent->enode->line); } for (i = 0; envnames[i] != NULL && i < *envc; i++) { elen = strlen(envnames[i]) + strlen(envvalues[i]) + 2; (*envp)[i] = MALLOC(elen); (void) snprintf((*envp)[i], elen, "%s=%s", envnames[i], envvalues[i]); FREE(envvalues[i]); } (*envp)[*envc] = NULL; return (0); } /* * platform_call -- call an external function * * evaluate a user-defined function and place result in valuep. return 0 * if function evaluation was successful; 1 if otherwise. */ int platform_call(struct node *np, struct lut **globals, struct config *croot, struct arrow *arrowp, struct evalue *valuep) { /* * use rather short buffers. only the first string on outbuf[] is * taken as output from the called function. any message in * errbuf[] is echoed out as an error message. */ char outbuf[256], errbuf[512]; struct stat buf; char **argv, **envp; int argc, argvlen, envc, envplen; int i, ret; /* * np is the argument list. the user-defined function is the first * element of the list. */ ASSERT(np->t == T_LIST); argv = NULL; argc = 0; argvlen = 0; if (arglist2argv(np, globals, croot, arrowp, &argv, &argc, &argvlen) || argc == 0) return (1); /* * make sure program has executable bit set */ if (stat(argv[0], &buf) == 0) { int exec_bit_set = 0; if (buf.st_uid == geteuid() && buf.st_mode & S_IXUSR) exec_bit_set = 1; else if (buf.st_gid == getegid() && buf.st_mode & S_IXGRP) exec_bit_set = 1; else if (buf.st_mode & S_IXOTH) exec_bit_set = 1; if (exec_bit_set == 0) out(O_DIE, "call: executable bit not set on %s", argv[0]); } else { out(O_DIE, "call: failure in stat(), errno = %d\n", errno); } envp = NULL; envc = 0; envplen = 0; if (generate_envp(arrowp, &envp, &envc, &envplen)) return (1); outbuf[0] = '\0'; errbuf[0] = '\0'; ret = forkandexecve((const char *) argv[0], (char *const *) argv, (char *const *) envp, outbuf, sizeof (outbuf), errbuf, sizeof (errbuf)); for (i = 0; i < envc; i++) FREE(envp[i]); if (envp) FREE(envp); if (ret) { outfl(O_OK, np->file, np->line, "call: failure in fork + exec of %s", argv[0]); } else { char *ptr; /* chomp the result */ for (ptr = outbuf; *ptr; ptr++) if (*ptr == '\n' || *ptr == '\r') { *ptr = '\0'; break; } valuep->t = STRING; valuep->v = (uintptr_t)stable(outbuf); } if (errbuf[0] != '\0') { ret = 1; outfl(O_OK, np->file, np->line, "call: unexpected stderr output from %s: %s", argv[0], errbuf); } for (i = 0; i < argc; i++) FREE(argv[i]); FREE(argv); return (ret); } /* * platform_confcall -- call a configuration database function * * returns result in *valuep, return 0 on success */ /*ARGSUSED*/ int platform_confcall(struct node *np, struct lut **globals, struct config *croot, struct arrow *arrowp, struct evalue *valuep) { outfl(O_ALTFP|O_VERB, np->file, np->line, "unknown confcall"); return (0); } /* * platform_get_eft_files -- return names of all eft files we should load * * this routine doesn't return NULL, even if no files are found (in that * case, a char ** is returned with the first element NULL). */ char ** platform_get_eft_files(void) { return (platform_get_files_stddirs(".eft", 1)); } void platform_free_eft_files(char **flist) { char **f; if (flist == NULL || *flist == NULL) return; /* no files were found so we're done */ f = flist; while (*f != NULL) { FREE(*f); f++; } FREE(flist); } static nvlist_t *payloadnvp = NULL; void platform_set_payloadnvp(nvlist_t *nvlp) { /* * cannot replace a non-NULL payloadnvp with a non-NULL nvlp */ ASSERT(payloadnvp != NULL ? nvlp == NULL : 1); payloadnvp = nvlp; } /* * given array notation in inputstr such as "foo[1]" or "foo [ 1 ]" (spaces * allowed), figure out the array name and index. return 0 if successful, * nonzero if otherwise. */ static int get_array_info(const char *inputstr, const char **name, unsigned int *index) { char *indexptr, *indexend, *dupname, *endname; if (strchr(inputstr, '[') == NULL) return (1); dupname = STRDUP(inputstr); indexptr = strchr(dupname, '['); indexend = strchr(dupname, ']'); /* * return if array notation is not complete or if index is negative */ if (indexend == NULL || indexptr >= indexend || strchr(indexptr, '-') != NULL) { FREE(dupname); return (1); } /* * search past any spaces between the name string and '[' */ endname = indexptr; while (isspace(*(endname - 1)) && dupname < endname) endname--; *endname = '\0'; ASSERT(dupname < endname); /* * search until indexptr points to the first digit and indexend * points to the last digit */ while (!isdigit(*indexptr) && indexptr < indexend) indexptr++; while (!isdigit(*indexend) && indexptr <= indexend) indexend--; *(indexend + 1) = '\0'; *index = (unsigned int)atoi(indexptr); *name = stable(dupname); FREE(dupname); return (0); } /* * platform_payloadprop -- fetch a payload value * * XXX this function should be replaced and eval_func() should be * XXX changed to use the more general platform_payloadprop_values(). */ int platform_payloadprop(struct node *np, struct evalue *valuep) { nvlist_t *basenvp; nvlist_t *embnvp = NULL; nvpair_t *nvpair; const char *nameptr, *propstr, *lastnameptr; int not_array = 0; unsigned int index = 0; uint_t nelem; char *nvpname, *nameslist = NULL; char *scheme = NULL; ASSERT(np->t == T_QUOTE); propstr = np->u.quote.s; if (payloadnvp == NULL) { out(O_ALTFP | O_VERB2, "platform_payloadprop: no nvp for %s", propstr); return (1); } basenvp = payloadnvp; /* * first handle any embedded nvlists. if propstr is "foo.bar[2]" * then lastnameptr should end up being "bar[2]" with basenvp set * to the nvlist for "foo". (the search for "bar" within "foo" * will be done later.) */ if (strchr(propstr, '.') != NULL) { nvlist_t **arraynvp; uint_t nelem; char *w; int ier; nameslist = STRDUP(propstr); lastnameptr = strtok(nameslist, "."); /* * decompose nameslist into its component names while * extracting the embedded nvlist */ while ((w = strtok(NULL, ".")) != NULL) { if (get_array_info(lastnameptr, &nameptr, &index)) { ier = nvlist_lookup_nvlist(basenvp, lastnameptr, &basenvp); } else { /* handle array of nvlists */ ier = nvlist_lookup_nvlist_array(basenvp, nameptr, &arraynvp, &nelem); if (ier == 0) { if ((uint_t)index > nelem - 1) ier = 1; else basenvp = arraynvp[index]; } } if (ier) { out(O_ALTFP, "platform_payloadprop: " " invalid list for %s (in %s)", lastnameptr, propstr); FREE(nameslist); return (1); } lastnameptr = w; } } else { lastnameptr = propstr; } /* if property is an array reference, extract array name and index */ not_array = get_array_info(lastnameptr, &nameptr, &index); if (not_array) nameptr = stable(lastnameptr); if (nameslist != NULL) FREE(nameslist); /* search for nvpair entry */ nvpair = NULL; while ((nvpair = nvlist_next_nvpair(basenvp, nvpair)) != NULL) { nvpname = nvpair_name(nvpair); ASSERT(nvpname != NULL); if (nameptr == stable(nvpname)) break; } if (nvpair == NULL) { out(O_ALTFP, "platform_payloadprop: no entry for %s", propstr); return (1); } else if (valuep == NULL) { /* * caller is interested in the existence of a property with * this name, regardless of type or value */ return (0); } valuep->t = UNDEFINED; /* * get to this point if we found an entry. figure out its data * type and copy its value. */ (void) nvpair_value_nvlist(nvpair, &embnvp); if (nvlist_lookup_string(embnvp, FM_FMRI_SCHEME, &scheme) == 0) { if (strcmp(scheme, FM_FMRI_SCHEME_HC) == 0) { valuep->t = NODEPTR; valuep->v = (uintptr_t)hc_fmri_nodeize(embnvp); return (0); } } switch (nvpair_type(nvpair)) { case DATA_TYPE_BOOLEAN: case DATA_TYPE_BOOLEAN_VALUE: { boolean_t val; (void) nvpair_value_boolean_value(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_BYTE: { uchar_t val; (void) nvpair_value_byte(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_STRING: { char *val; valuep->t = STRING; (void) nvpair_value_string(nvpair, &val); valuep->v = (uintptr_t)stable(val); break; } case DATA_TYPE_INT8: { int8_t val; (void) nvpair_value_int8(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_UINT8: { uint8_t val; (void) nvpair_value_uint8(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_INT16: { int16_t val; (void) nvpair_value_int16(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_UINT16: { uint16_t val; (void) nvpair_value_uint16(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_INT32: { int32_t val; (void) nvpair_value_int32(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_UINT32: { uint32_t val; (void) nvpair_value_uint32(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_INT64: { int64_t val; (void) nvpair_value_int64(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_UINT64: { uint64_t val; (void) nvpair_value_uint64(nvpair, &val); valuep->t = UINT64; valuep->v = (unsigned long long)val; break; } case DATA_TYPE_BOOLEAN_ARRAY: { boolean_t *val; (void) nvpair_value_boolean_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_BYTE_ARRAY: { uchar_t *val; (void) nvpair_value_byte_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_STRING_ARRAY: { char **val; (void) nvpair_value_string_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = STRING; valuep->v = (uintptr_t)stable(val[index]); break; } case DATA_TYPE_INT8_ARRAY: { int8_t *val; (void) nvpair_value_int8_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_UINT8_ARRAY: { uint8_t *val; (void) nvpair_value_uint8_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_INT16_ARRAY: { int16_t *val; (void) nvpair_value_int16_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_UINT16_ARRAY: { uint16_t *val; (void) nvpair_value_uint16_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_INT32_ARRAY: { int32_t *val; (void) nvpair_value_int32_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_UINT32_ARRAY: { uint32_t *val; (void) nvpair_value_uint32_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_INT64_ARRAY: { int64_t *val; (void) nvpair_value_int64_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } case DATA_TYPE_UINT64_ARRAY: { uint64_t *val; (void) nvpair_value_uint64_array(nvpair, &val, &nelem); if (not_array == 1 || index >= nelem) goto invalid; valuep->t = UINT64; valuep->v = (unsigned long long)val[index]; break; } default : out(O_ALTFP|O_VERB2, "platform_payloadprop: unsupported data type for %s", propstr); return (1); } return (0); invalid: out(O_ALTFP|O_VERB2, "platform_payloadprop: invalid array reference for %s", propstr); return (1); } /*ARGSUSED*/ int platform_path_exists(nvlist_t *fmri) { return (fmd_nvl_fmri_present(Hdl, fmri)); } struct evalue * platform_payloadprop_values(const char *propstr, int *nvals) { struct evalue *retvals; nvlist_t *basenvp; nvpair_t *nvpair; char *nvpname; *nvals = 0; if (payloadnvp == NULL) return (NULL); basenvp = payloadnvp; /* search for nvpair entry */ nvpair = NULL; while ((nvpair = nvlist_next_nvpair(basenvp, nvpair)) != NULL) { nvpname = nvpair_name(nvpair); ASSERT(nvpname != NULL); if (strcmp(propstr, nvpname) == 0) break; } if (nvpair == NULL) return (NULL); /* property not found */ switch (nvpair_type(nvpair)) { case DATA_TYPE_NVLIST: { nvlist_t *embnvp = NULL; char *scheme = NULL; (void) nvpair_value_nvlist(nvpair, &embnvp); if (nvlist_lookup_string(embnvp, FM_FMRI_SCHEME, &scheme) == 0) { if (strcmp(scheme, FM_FMRI_SCHEME_HC) == 0) { *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); retvals->t = NODEPTR; retvals->v = (uintptr_t)hc_fmri_nodeize(embnvp); return (retvals); } } return (NULL); } case DATA_TYPE_NVLIST_ARRAY: { char *scheme = NULL; nvlist_t **nvap; uint_t nel; int i; int hccount; /* * since we're only willing to handle hc fmri's, we * must count them first before allocating retvals. */ if (nvpair_value_nvlist_array(nvpair, &nvap, &nel) != 0) return (NULL); hccount = 0; for (i = 0; i < nel; i++) { if (nvlist_lookup_string(nvap[i], FM_FMRI_SCHEME, &scheme) == 0 && strcmp(scheme, FM_FMRI_SCHEME_HC) == 0) { hccount++; } } if (hccount == 0) return (NULL); *nvals = hccount; retvals = MALLOC(sizeof (struct evalue) * hccount); hccount = 0; for (i = 0; i < nel; i++) { if (nvlist_lookup_string(nvap[i], FM_FMRI_SCHEME, &scheme) == 0 && strcmp(scheme, FM_FMRI_SCHEME_HC) == 0) { retvals[hccount].t = NODEPTR; retvals[hccount].v = (uintptr_t) hc_fmri_nodeize(nvap[i]); hccount++; } } return (retvals); } case DATA_TYPE_BOOLEAN: case DATA_TYPE_BOOLEAN_VALUE: { boolean_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_boolean_value(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_BYTE: { uchar_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_byte(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_STRING: { char *val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); retvals->t = STRING; (void) nvpair_value_string(nvpair, &val); retvals->v = (uintptr_t)stable(val); return (retvals); } case DATA_TYPE_INT8: { int8_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_int8(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_UINT8: { uint8_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_uint8(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_INT16: { int16_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_int16(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_UINT16: { uint16_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_uint16(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_INT32: { int32_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_int32(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_UINT32: { uint32_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_uint32(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_INT64: { int64_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_int64(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_UINT64: { uint64_t val; *nvals = 1; retvals = MALLOC(sizeof (struct evalue)); (void) nvpair_value_uint64(nvpair, &val); retvals->t = UINT64; retvals->v = (unsigned long long)val; return (retvals); } case DATA_TYPE_BOOLEAN_ARRAY: { boolean_t *val; uint_t nel; int i; (void) nvpair_value_boolean_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_BYTE_ARRAY: { uchar_t *val; uint_t nel; int i; (void) nvpair_value_byte_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_STRING_ARRAY: { char **val; uint_t nel; int i; (void) nvpair_value_string_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = STRING; retvals[i].v = (uintptr_t)stable(val[i]); } return (retvals); } case DATA_TYPE_INT8_ARRAY: { int8_t *val; uint_t nel; int i; (void) nvpair_value_int8_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_UINT8_ARRAY: { uint8_t *val; uint_t nel; int i; (void) nvpair_value_uint8_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_INT16_ARRAY: { int16_t *val; uint_t nel; int i; (void) nvpair_value_int16_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_UINT16_ARRAY: { uint16_t *val; uint_t nel; int i; (void) nvpair_value_uint16_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_INT32_ARRAY: { int32_t *val; uint_t nel; int i; (void) nvpair_value_int32_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_UINT32_ARRAY: { uint32_t *val; uint_t nel; int i; (void) nvpair_value_uint32_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_INT64_ARRAY: { int64_t *val; uint_t nel; int i; (void) nvpair_value_int64_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } case DATA_TYPE_UINT64_ARRAY: { uint64_t *val; uint_t nel; int i; (void) nvpair_value_uint64_array(nvpair, &val, &nel); *nvals = nel; retvals = MALLOC(sizeof (struct evalue) * nel); for (i = 0; i < nel; i++) { retvals[i].t = UINT64; retvals[i].v = (unsigned long long)val[i]; } return (retvals); } } return (NULL); } /* * When a list.repaired event is seen the following is called for * each fault in the associated fault list to convert the given FMRI * to an instanced path. Only hc scheme is supported. */ const struct ipath * platform_fault2ipath(nvlist_t *flt) { nvlist_t *rsrc; struct node *np; char *scheme; const struct ipath *ip; if (nvlist_lookup_nvlist(flt, FM_FAULT_RESOURCE, &rsrc) != 0) { out(O_ALTFP, "platform_fault2ipath: no resource member"); return (NULL); } else if (nvlist_lookup_string(rsrc, FM_FMRI_SCHEME, &scheme) != 0) { out(O_ALTFP, "platform_fault2ipath: no scheme type for rsrc"); return (NULL); } if (strncmp(scheme, FM_FMRI_SCHEME_HC, sizeof (FM_FMRI_SCHEME_HC) - 1) != 0) { out(O_ALTFP, "platform_fault2ipath: returning NULL for non-hc " "scheme %s", scheme); return (NULL); } if ((np = hc_fmri_nodeize(rsrc)) == NULL) return (NULL); /* nodeize will already have whinged */ ip = ipath(np); tree_free(np); return (ip); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * platform -- platform-specific access to configuration database */ #ifndef _EFT_PLATFORM_H #define _EFT_PLATFORM_H #include #ifdef __cplusplus extern "C" { #endif #include #include #include extern nvlist_t *Action_nvl; /* nvl for problem with action=... prop on it */ void platform_init(void); void platform_fini(void); void platform_run_poller(const char *poller); void platform_set_payloadnvp(nvlist_t *nvlp); void platform_unit_translate(int, struct config *, const char *, nvlist_t **, char *); struct cfgdata *platform_config_snapshot(void); void platform_restore_config(fmd_hdl_t *hdl, fmd_case_t *fmcase); void platform_save_config(fmd_hdl_t *hdl, fmd_case_t *fmcase); struct node *platform_getpath(nvlist_t *nvl); char **platform_get_eft_files(void); void platform_free_eft_files(char **); int platform_call(struct node *np, struct lut **globals, struct config *croot, struct arrow *arrowp, struct evalue *valuep); int platform_confcall(struct node *np, struct lut **globals, struct config *croot, struct arrow *arrowp, struct evalue *valuep); int platform_payloadprop(struct node *np, struct evalue *valuep); struct evalue *platform_payloadprop_values(const char *s, int *nvals); int platform_path_exists(nvlist_t *fmri); const struct ipath *platform_fault2ipath(nvlist_t *flt); #ifdef __cplusplus } #endif #endif /* _EFT_PLATFORM_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * stats.c -- simple stats tracking table module * * this version of stats.c links with eft and implements the * stats using the fmd's stats API. */ #include #include #include "stats.h" #include "alloc.h" #include "out.h" #include "stats_impl.h" #include extern fmd_hdl_t *Hdl; /* handle from eft.c */ static int Ext; /* true if extended stats are enabled */ /* * stats_init -- initialize the stats module * */ void stats_init(int ext) { Ext = ext; } void stats_fini(void) { } static struct stats * stats_new(const char *name, const char *desc, enum stats_type t) { struct stats *ret = MALLOC(sizeof (*ret)); bzero(ret, sizeof (*ret)); ret->t = t; (void) strlcpy(ret->fmd_stats.fmds_desc, desc, sizeof (ret->fmd_stats.fmds_desc)); /* NULL name means generate a unique name */ if (name == NULL) { static int uniqstat; (void) snprintf(ret->fmd_stats.fmds_name, sizeof (ret->fmd_stats.fmds_name), "stat.rules%d", uniqstat++); } else { (void) strlcpy(ret->fmd_stats.fmds_name, name, sizeof (ret->fmd_stats.fmds_name)); } switch (t) { case STATS_COUNTER: ret->fmd_stats.fmds_type = FMD_TYPE_INT32; break; case STATS_ELAPSE: ret->fmd_stats.fmds_type = FMD_TYPE_TIME; break; case STATS_STRING: ret->fmd_stats.fmds_type = FMD_TYPE_STRING; break; default: out(O_DIE, "stats_new: unknown type %d", t); } (void) fmd_stat_create(Hdl, FMD_STAT_NOALLOC, 1, &(ret->fmd_stats)); return (ret); } void stats_delete(struct stats *sp) { if (sp == NULL) return; fmd_stat_destroy(Hdl, 1, &(sp->fmd_stats)); FREE(sp); } struct stats * stats_new_counter(const char *name, const char *desc, int ext) { if (ext && !Ext) return (NULL); /* extended stats not enabled */ return (stats_new(name, desc, STATS_COUNTER)); } void stats_counter_bump(struct stats *sp) { if (sp == NULL) return; ASSERT(sp->t == STATS_COUNTER); sp->fmd_stats.fmds_value.i32++; } void stats_counter_add(struct stats *sp, int n) { if (sp == NULL) return; ASSERT(sp->t == STATS_COUNTER); sp->fmd_stats.fmds_value.i32 += n; } void stats_counter_reset(struct stats *sp) { if (sp == NULL) return; ASSERT(sp->t == STATS_COUNTER); sp->fmd_stats.fmds_value.i32 = 0; } int stats_counter_value(struct stats *sp) { if (sp == NULL) return (0); ASSERT(sp->t == STATS_COUNTER); return (sp->fmd_stats.fmds_value.i32); } struct stats * stats_new_elapse(const char *name, const char *desc, int ext) { if (ext && !Ext) return (NULL); /* extended stats not enabled */ return (stats_new(name, desc, STATS_ELAPSE)); } void stats_elapse_start(struct stats *sp) { if (sp == NULL) return; ASSERT(sp->t == STATS_ELAPSE); sp->start = gethrtime(); } void stats_elapse_stop(struct stats *sp) { if (sp == NULL) return; ASSERT(sp->t == STATS_ELAPSE); sp->stop = gethrtime(); sp->fmd_stats.fmds_value.ui64 = sp->stop - sp->start; } struct stats * stats_new_string(const char *name, const char *desc, int ext) { struct stats *r; if (ext && !Ext) return (NULL); /* extended stats not enabled */ r = stats_new(name, desc, STATS_STRING); return (r); } void stats_string_set(struct stats *sp, const char *s) { if (sp == NULL) return; ASSERT(sp->t == STATS_STRING); if (sp->fmd_stats.fmds_value.str) fmd_hdl_strfree(Hdl, sp->fmd_stats.fmds_value.str); sp->fmd_stats.fmds_value.str = fmd_hdl_strdup(Hdl, s, FMD_SLEEP); } /* * stats_publish -- spew all stats * */ void stats_publish(void) { /* nothing to do for eft */ } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _EFT_STATS_IMPL_H #define _EFT_STATS_IMPL_H #ifdef __cplusplus extern "C" { #endif #include "fme.h" struct stats { fmd_stat_t fmd_stats; enum stats_type { STATS_COUNTER = 3000, STATS_ELAPSE, STATS_STRING } t; /* used for STATS_ELAPSE */ hrtime_t start; hrtime_t stop; }; #ifdef __cplusplus } #endif #endif /* _EFT_STATS_IMPL_H */ # # 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # MODULE = ext-event-transport CLASS = common SRCS = fmevt_main.c \ fmevt_outbound.c \ fmevt_inbound.c \ fmevt_inbound_default.c \ fmevt_inbound_on.c \ fmevt_inbound_smf.c \ fmevt_inbound_sunos.c include ../../Makefile.plugin CFLAGS += $(INCS) LINTFLAGS += $(INCS) # Hammerhead: Suppress pointer/int cast warnings in legacy fmevt code CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast LDLIBS += -L$(ROOTLIB)/fm -lsysevent -lfmevent -ltopo -luuid -lscf -lc LDFLAGS += -R/usr/lib/fm # # 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # # # Configuration for the ext-event-transport transport module. This module # forwards protocol events out of fmd for subscription by other entities # using other libfmevent interfaces. # # # You can disable the forwarding functionality by setting this true # setprop protocol_forward_disable false # # The channel we forward events on can be changed for simulation environments # by changing the "outbound_channel" string property from its default. # The maximum number of events that can queue in the channel (each # consuming a little kernel memory) is controlled by "outbound_channel_depth". # # setprop outbound_channel ... # setprop outbound_channel_depth 256 # # Protocol event classes that will be forwarded. # Changing this list may lead to breakage and/or excessive event forwarding. # subscribe list.* subscribe ireport.* /* * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _FMEVT_H #define _FMEVT_H /* * ext-event-transport module - implementation detail. */ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include "../../../../../lib/fm/libfmevent/common/fmev_channels.h" extern fmd_hdl_t *fmevt_hdl; extern const fmd_prop_t fmevt_props[]; extern void fmevt_init_outbound(fmd_hdl_t *); extern void fmevt_fini_outbound(fmd_hdl_t *); extern void fmevt_init_inbound(fmd_hdl_t *); extern void fmevt_fini_inbound(fmd_hdl_t *); extern void fmevt_recv(fmd_hdl_t *, fmd_event_t *, nvlist_t *, const char *); /* * Post-processing */ /* * Structure passed to a post-processing functions with details of the * raw event. */ struct fmevt_ppargs { const char *pp_rawclass; /* class from event publication */ const char *pp_rawsubclass; /* subclass from event publication */ hrtime_t pp_hrt; /* hrtime of event publication */ int pp_user; /* userland or kernel source? */ int pp_priv; /* privileged? */ fmev_pri_t pp_pri; /* published priority */ char pp_uuidstr[36 + 1]; /* uuid we'll use for first event */ }; /* * The maximum length that a protocol event class name generated * in post-processing can be. */ #define FMEVT_MAX_CLASS 64 /* * A post-processing function may derive up to this number of separate * protocol events for each raw event. */ #define FMEVT_FANOUT_MAX 5 /* * Post-processing function type. The function receives raw event * details in the struct fmevt_ppargs. It must prepare up to * FMEVT_FANOUT_MAX protocol events (usually just one event) * based on the raw event, and return the number of events * to be posted. The array of class pointers must have that * number of non-NULL entries. You may return 0 to ditch an event; * in this case the caller will not perform an frees so you must * tidy up. * * The array of string pointers has the first member pointed to * some storage of size FMEV_MAX_CLASS into which the post-processing * function must render the protocol event classname. If fanning * out into more than one event then the post-processing function * must allocate additional buffers (using fmd_hdl_alloc) and return * pointers to these in the array of string pointers (but do not change * the first element); buffers allocated and returned in this way will * be freed by the caller as it iterates over the protocol events to * post them. Similarly the function must prepare an attributes * nvlist for each event; it can return the raw attributes or it * can fmd_nvl_alloc or fmd_nvl_dup and return those (to be freed * by the caller). * * Events will be generated based on the results as follows: * * event[i] = * * timestamp = as supplied by incoming event and in pp_hrt * class = class_array[i]; entry 0 is allocated, fmd_hdl_alloc others * detector = generated detector as passed to function * uuid = generated UUID, or that supplied by raw event * attr = nvlist_array[i], can be absent; may return raw attributes * */ typedef uint_t fmevt_pp_func_t( char *[FMEVT_FANOUT_MAX], /* event class(es) */ nvlist_t *[FMEVT_FANOUT_MAX], /* event attributes */ const char *, /* ruleset */ const nvlist_t *, /* detector */ nvlist_t *, /* raw attributes */ const struct fmevt_ppargs *); /* more raw event info */ extern fmevt_pp_func_t fmevt_pp_on_ereport; extern fmevt_pp_func_t fmevt_pp_smf; extern fmevt_pp_func_t fmevt_pp_on_sunos; extern fmevt_pp_func_t fmevt_pp_on_private; extern fmevt_pp_func_t fmevt_pp_unregistered; #ifdef __cplusplus } #endif #endif /* _FMEVT_H */ /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Receive (on GPEC channels) raw events published by a few select producers * using the private libfmevent publication interfaces, and massage those * raw events into full protocol events. Each raw event selects a "ruleset" * by which to perform the transformation into a protocol event. * * Only publication from userland running privileged is supported; two * channels are used - one for high-value and one for low-value events. * There is some planning in the implementation below for kernel hi and low * value channels, and for non-privileged userland low and hi value channels. */ #include #include #include #include #include #include #include #include #include "fmevt.h" static struct fmevt_inbound_stats { fmd_stat_t raw_callbacks; fmd_stat_t raw_noattrlist; fmd_stat_t raw_nodetector; fmd_stat_t pp_bad_ruleset; fmd_stat_t pp_explicitdrop; fmd_stat_t pp_fallthrurule; fmd_stat_t pp_fanoutmax; fmd_stat_t pp_intldrop; fmd_stat_t pp_badclass; fmd_stat_t pp_nvlallocfail; fmd_stat_t pp_nvlbuildfail; fmd_stat_t pp_badreturn; fmd_stat_t xprt_posted; } inbound_stats = { { "raw_callbacks", FMD_TYPE_UINT64, "total raw event callbacks from producers" }, { "raw_noattrlist", FMD_TYPE_UINT64, "missing attribute list" }, { "raw_nodetector", FMD_TYPE_UINT64, "unable to add detector" }, { "pp_bad_ruleset", FMD_TYPE_UINT64, "post-process bad ruleset" }, { "pp_explicitdrop", FMD_TYPE_UINT64, "ruleset drops event with NULL func" }, { "pp_fanoutmax", FMD_TYPE_UINT64, "post-processing produced too many events" }, { "pp_intldrop", FMD_TYPE_UINT64, "post-processing requested event drop" }, { "pp_badclass", FMD_TYPE_UINT64, "post-processing produced invalid event class" }, { "pp_nvlallocfail", FMD_TYPE_UINT64, "fmd_nvl_alloc failed" }, { "pp_nvlbuildfail", FMD_TYPE_UINT64, "nvlist_add_foo failed in building event" }, { "pp_badreturn", FMD_TYPE_UINT64, "inconsistent number of events returned" }, { "xprt_posted", FMD_TYPE_UINT64, "protocol events posted with fmd_xprt_post" }, }; static int isglobalzone; static char zonename[ZONENAME_MAX]; #define BUMPSTAT(stat) inbound_stats.stat.fmds_value.ui64++ #define CBF_USER 0x1U #define CBF_PRIV 0x2U #define CBF_LV 0x4U #define CBF_HV 0x8U #define CBF_ALL (CBF_USER | CBF_PRIV | CBF_LV | CBF_HV) static struct fmevt_chaninfo { const char *ci_propname; /* property to get channel name */ evchan_t *ci_binding; /* GPEC binding for this channel */ char ci_sid[MAX_SUBID_LEN]; /* subscriber id */ uint32_t ci_cbarg; /* callback cookie */ uint32_t ci_sflags; /* subscription flags to use */ } chaninfo[] = { { "user_priv_highval_channel", NULL, { 0 }, CBF_USER | CBF_PRIV | CBF_HV, EVCH_SUB_KEEP }, { "user_priv_lowval_channel", NULL, { 0 }, CBF_USER | CBF_PRIV | CBF_LV, EVCH_SUB_KEEP }, }; static pthread_cond_t fmevt_cv = PTHREAD_COND_INITIALIZER; static pthread_mutex_t fmevt_lock = PTHREAD_MUTEX_INITIALIZER; static int fmevt_exiting; static fmd_xprt_t *fmevt_xprt; static uint32_t fmevt_xprt_refcnt; static sysevent_subattr_t *subattr; /* * Rulesets we recognize and who handles them. Additions and changes * must follow the Portfolio Review process. At ths time only * the FMEV_RULESET_ON_SUNOS and FMEVT_RULESET_SMF rulesets are * formally recognized by that process - the others here are experimental. */ static struct fmevt_rs { char *rs_pat; fmevt_pp_func_t *rs_ppfunc; char *rs_namespace; char *rs_subsys; } rulelist[] = { { FMEV_RULESET_SMF, fmevt_pp_smf }, { FMEV_RULESET_ON_EREPORT, fmevt_pp_on_ereport }, { FMEV_RULESET_ON_SUNOS, fmevt_pp_on_sunos }, { FMEV_RULESET_ON_PRIVATE, fmevt_pp_on_private }, { FMEV_RULESET_UNREGISTERED, fmevt_pp_unregistered } }; /* * Take a ruleset specification string and separate it into namespace * and subsystem components. */ static int fmevt_rs_burst(fmd_hdl_t *hdl, char *ruleset, char **nsp, char **subsysp, boolean_t alloc) { char *ns, *s; size_t len; if (ruleset == NULL || *ruleset == '\0' || strnlen(ruleset, FMEV_MAX_RULESET_LEN) == FMEV_MAX_RULESET_LEN) return (0); if (alloc == B_FALSE) { s = ruleset; ns = strsep(&s, FMEV_RS_SEPARATOR); if (s == NULL || s == ns + 1) return (0); } else { if ((s = strstr(ruleset, FMEV_RS_SEPARATOR)) == NULL || s == ruleset + strlen(ruleset) - 1) return (0); len = s - ruleset; ns = fmd_hdl_alloc(hdl, len + 1, FMD_SLEEP); (void) strncpy(ns, ruleset, len); ns[len] = '\0'; s++; } if (nsp) *nsp = ns; /* caller must free if alloc == B_TRUE */ if (subsysp) *subsysp = s; /* always within original ruleset string */ return (1); } static int fmevt_rs_init(fmd_hdl_t *hdl) { int i; for (i = 0; i < sizeof (rulelist) / sizeof (rulelist[0]); i++) { struct fmevt_rs *rsp = &rulelist[i]; if (!fmevt_rs_burst(hdl, rsp->rs_pat, &rsp->rs_namespace, &rsp->rs_subsys, B_TRUE)) return (0); } return (1); } /* * Construct a "sw" scheme detector FMRI. * * We make no use of priv or pri. */ /*ARGSUSED3*/ static nvlist_t * fmevt_detector(nvlist_t *attr, char *ruleset, int user, int priv, fmev_pri_t pri) { char buf[FMEV_MAX_RULESET_LEN + 1]; char *ns, *subsys; nvlist_t *obj, *dtcr, *site, *ctxt; char *execname = NULL; int32_t i32; int64_t i64; int err = 0; char *str; (void) strncpy(buf, ruleset, sizeof (buf)); if (!fmevt_rs_burst(NULL, buf, &ns, &subsys, B_FALSE)) return (NULL); obj = fmd_nvl_alloc(fmevt_hdl, FMD_SLEEP); dtcr = fmd_nvl_alloc(fmevt_hdl, FMD_SLEEP); site = fmd_nvl_alloc(fmevt_hdl, FMD_SLEEP); ctxt = fmd_nvl_alloc(fmevt_hdl, FMD_SLEEP); if (obj == NULL || dtcr == NULL || site == NULL || ctxt == NULL) { err++; goto done; } /* * Build up 'object' nvlist. */ if (nvlist_lookup_string(attr, "__fmev_execname", &execname) == 0) err += nvlist_add_string(obj, FM_FMRI_SW_OBJ_PATH, execname); /* * Build up 'site' nvlist. We should have source file and line * number and, if the producer was compiled with C99, function name. */ if (nvlist_lookup_string(attr, "__fmev_file", &str) == 0) { err += nvlist_add_string(site, FM_FMRI_SW_SITE_FILE, str); (void) nvlist_remove(attr, "__fmev_file", DATA_TYPE_STRING); } if (nvlist_lookup_string(attr, "__fmev_func", &str) == 0) { err += nvlist_add_string(site, FM_FMRI_SW_SITE_FUNC, str); (void) nvlist_remove(attr, "__fmev_func", DATA_TYPE_STRING); } if (nvlist_lookup_int64(attr, "__fmev_line", &i64) == 0) { err += nvlist_add_int64(site, FM_FMRI_SW_SITE_LINE, i64); (void) nvlist_remove(attr, "__fmev_line", DATA_TYPE_INT64); } /* * Build up 'context' nvlist. We do not include contract id at * this time. */ err += nvlist_add_string(ctxt, FM_FMRI_SW_CTXT_ORIGIN, user ? "userland" : "kernel"); if (execname) { err += nvlist_add_string(ctxt, FM_FMRI_SW_CTXT_EXECNAME, execname); (void) nvlist_remove(attr, "__fmev_execname", DATA_TYPE_STRING); } if (nvlist_lookup_int32(attr, "__fmev_pid", &i32) == 0) { err += nvlist_add_int32(ctxt, FM_FMRI_SW_CTXT_PID, i32); (void) nvlist_remove(attr, "__fmev_pid", DATA_TYPE_INT32); } if (!isglobalzone) err += nvlist_add_string(ctxt, FM_FMRI_SW_CTXT_ZONE, zonename); /* Put it all together */ err += nvlist_add_uint8(dtcr, FM_VERSION, SW_SCHEME_VERSION0); err += nvlist_add_string(dtcr, FM_FMRI_SCHEME, FM_FMRI_SCHEME_SW); err += nvlist_add_nvlist(dtcr, FM_FMRI_SW_OBJ, obj); err += nvlist_add_nvlist(dtcr, FM_FMRI_SW_SITE, site); err += nvlist_add_nvlist(dtcr, FM_FMRI_SW_CTXT, ctxt); done: nvlist_free(obj); nvlist_free(site); nvlist_free(ctxt); if (err == 0) { return (dtcr); } else { nvlist_free(dtcr); return (NULL); } } static int class_ok(char *class) { static const char *approved[] = { FM_IREPORT_CLASS ".", FM_EREPORT_CLASS "." }; int i; for (i = 0; i < sizeof (approved) / sizeof (approved[0]); i++) { if (strncmp(class, approved[i], strlen(approved[i])) == 0) return (1); } return (0); } static void fmevt_postprocess(char *ruleset, nvlist_t *dtcr, nvlist_t *rawattr, struct fmevt_ppargs *eap) { uint_t expected = 0, processed = 0; char rs2burst[FMEV_MAX_RULESET_LEN + 1]; char *class[FMEVT_FANOUT_MAX]; nvlist_t *attr[FMEVT_FANOUT_MAX]; fmevt_pp_func_t *dispf = NULL; char buf[FMEV_MAX_CLASS]; char *ns, *subsys; int i, found = 0; uuid_t uu; (void) strncpy(rs2burst, ruleset, sizeof (rs2burst)); if (!fmevt_rs_burst(NULL, rs2burst, &ns, &subsys, B_FALSE)) { BUMPSTAT(pp_bad_ruleset); return; } /* * Lookup a matching rule in our table. */ for (i = 0; i < sizeof (rulelist) / sizeof (rulelist[0]); i++) { struct fmevt_rs *rsp = &rulelist[i]; if (*ns != '*' && *rsp->rs_namespace != '*' && strcmp(ns, rsp->rs_namespace) != 0) continue; if (*subsys != '*' && *rsp->rs_subsys != '*' && strcmp(subsys, rsp->rs_subsys) != 0) continue; dispf = rsp->rs_ppfunc; found = 1; break; } /* * If a ruleset matches but specifies a NULL function then * it's electing to drop the event. If no rule was matched * then default to unregistered processing. */ if (dispf == NULL) { if (found) { BUMPSTAT(pp_explicitdrop); return; } else { BUMPSTAT(pp_fallthrurule); dispf = fmevt_pp_unregistered; } } /* * Clear the arrays in which class strings and attribute * nvlists can be returned. Pass a pointer to our stack buffer * that the callee can use for the first event class (for others * it must fmd_hdl_alloc and we'll free below). We will free * and nvlists that are returned. */ bzero(class, sizeof (class)); bzero(attr, sizeof (attr)); class[0] = buf; /* * Generate an event UUID which will be used for the first * event generated by post-processing; if post-processing * fans out into more than one event the additional events * can reference this uuid (but we don't generate their * UUIDs until later). */ uuid_generate(uu); uuid_unparse(uu, eap->pp_uuidstr); /* * Call selected post-processing function. See block comment * in fmevt.h for a description of this process. */ expected = (*dispf)(class, attr, ruleset, (const nvlist_t *)dtcr, rawattr, (const struct fmevt_ppargs *)eap); if (expected > FMEVT_FANOUT_MAX) { BUMPSTAT(pp_fanoutmax); return; /* without freeing class and nvl - could leak */ } else if (expected == 0) { BUMPSTAT(pp_intldrop); return; } /* * Post as many events as the callback completed. */ for (i = 0; i < FMEVT_FANOUT_MAX; i++) { char uuidstr[36 + 1]; char *uuidstrp; nvlist_t *nvl; int err = 0; if (class[i] == NULL) continue; if (!class_ok(class[i])) { BUMPSTAT(pp_badclass); continue; } if (processed++ == 0) { uuidstrp = eap->pp_uuidstr; } else { uuid_generate(uu); uuid_unparse(uu, uuidstr); uuidstrp = uuidstr; } if ((nvl = fmd_nvl_alloc(fmevt_hdl, FMD_SLEEP)) == NULL) { BUMPSTAT(pp_nvlallocfail); continue; } err += nvlist_add_uint8(nvl, FM_VERSION, 0); err += nvlist_add_string(nvl, FM_CLASS, (const char *)class[i]); err += nvlist_add_string(nvl, FM_IREPORT_UUID, uuidstrp); err += nvlist_add_nvlist(nvl, FM_IREPORT_DETECTOR, dtcr); err += nvlist_add_string(nvl, FM_IREPORT_PRIORITY, fmev_pri_string(eap->pp_pri) ? fmev_pri_string(eap->pp_pri) : "?"); if (attr[i] != NULL) err += nvlist_add_nvlist(nvl, FM_IREPORT_ATTRIBUTES, attr[i]); /* * If we post the event into fmd_xport_post then the * transport code is responsible for freeing the nvl we * posted. */ if (err == 0) { fmd_xprt_post(fmevt_hdl, fmevt_xprt, nvl, eap->pp_hrt); } else { BUMPSTAT(pp_nvlbuildfail); nvlist_free(nvl); } } if (processed != expected) BUMPSTAT(pp_badreturn); for (i = 0; i < FMEVT_FANOUT_MAX; i++) { /* * We provided storage for class[0] but any * additional events have allocated a string. */ if (i > 0 && class[i] != NULL) fmd_hdl_strfree(fmevt_hdl, class[i]); /* * Free all attribute lists passed in if they are not * just a pointer to the raw attributes */ if (attr[i] != NULL && attr[i] != rawattr) nvlist_free(attr[i]); } } static int fmevt_cb(sysevent_t *sep, void *arg) { char *ruleset = NULL, *rawclass, *rawsubclass; uint32_t cbarg = (uint32_t)arg; nvlist_t *rawattr = NULL; struct fmevt_ppargs ea; nvlist_t *dtcr; int user, priv; fmev_pri_t pri; BUMPSTAT(raw_callbacks); if (cbarg & ~CBF_ALL) fmd_hdl_abort(fmevt_hdl, "event receipt callback with " "invalid flags\n"); user = (cbarg & CBF_USER) != 0; priv = (cbarg & CBF_PRIV) != 0; pri = (cbarg & CBF_HV ? FMEV_HIPRI : FMEV_LOPRI); (void) pthread_mutex_lock(&fmevt_lock); if (fmevt_exiting) { while (fmevt_xprt_refcnt > 0) (void) pthread_cond_wait(&fmevt_cv, &fmevt_lock); (void) pthread_mutex_unlock(&fmevt_lock); return (0); /* discard event */ } fmevt_xprt_refcnt++; (void) pthread_mutex_unlock(&fmevt_lock); ruleset = sysevent_get_vendor_name(sep); /* must free */ rawclass = sysevent_get_class_name(sep); /* valid with sep */ rawsubclass = sysevent_get_subclass_name(sep); /* valid with sep */ if (sysevent_get_attr_list(sep, &rawattr) != 0) { BUMPSTAT(raw_noattrlist); goto done; } if ((dtcr = fmevt_detector(rawattr, ruleset, user, priv, pri)) == NULL) { BUMPSTAT(raw_nodetector); goto done; } ea.pp_rawclass = rawclass; ea.pp_rawsubclass = rawsubclass; sysevent_get_time(sep, &ea.pp_hrt); ea.pp_user = user; ea.pp_priv = priv; ea.pp_pri = pri; fmevt_postprocess(ruleset, dtcr, rawattr, &ea); nvlist_free(dtcr); done: (void) pthread_mutex_lock(&fmevt_lock); if (--fmevt_xprt_refcnt == 0 && fmevt_exiting) (void) pthread_cond_broadcast(&fmevt_cv); (void) pthread_mutex_unlock(&fmevt_lock); if (ruleset) free(ruleset); nvlist_free(rawattr); return (0); /* in all cases consider the event delivered */ } void fmevt_init_inbound(fmd_hdl_t *hdl) { char *sidpfx; zoneid_t zoneid; int i; if (!fmevt_rs_init(hdl)) fmd_hdl_abort(hdl, "error in fmevt_rs_init\n"); (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (inbound_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&inbound_stats); zoneid = getzoneid(); isglobalzone = (zoneid == GLOBAL_ZONEID); if (getzonenamebyid(zoneid, zonename, sizeof (zonename)) == -1) fmd_hdl_abort(hdl, "getzonenamebyid failed"); if ((subattr = sysevent_subattr_alloc()) == NULL) fmd_hdl_abort(hdl, "failed to allocate subscription " "attributes: %s"); sysevent_subattr_thrcreate(subattr, fmd_doorthr_create, NULL); sysevent_subattr_thrsetup(subattr, fmd_doorthr_setup, NULL); sidpfx = fmd_prop_get_string(hdl, "sidprefix"); fmevt_xprt = fmd_xprt_open(hdl, FMD_XPRT_RDONLY, NULL, NULL); for (i = 0; i < sizeof (chaninfo) / sizeof (chaninfo[0]); i++) { struct fmevt_chaninfo *cip = &chaninfo[i]; char *channel = fmd_prop_get_string(hdl, cip->ci_propname); int err; if (sysevent_evc_bind(channel, &cip->ci_binding, EVCH_CREAT | EVCH_HOLD_PEND_INDEF) != 0) fmd_hdl_abort(hdl, "failed to bind GPEC channel for " "channel %s", channel); (void) snprintf(cip->ci_sid, sizeof (cip->ci_sid), "%s_%c%c%c", sidpfx, cip->ci_cbarg & CBF_USER ? 'u' : 'k', cip->ci_cbarg & CBF_PRIV ? 'p' : 'n', cip->ci_cbarg & CBF_HV ? 'h' : 'l'); err = sysevent_evc_xsubscribe(cip->ci_binding, cip->ci_sid, EC_ALL, fmevt_cb, (void *)cip->ci_cbarg, cip->ci_sflags, subattr); if (err == EEXIST) fmd_hdl_abort(hdl, "another fmd is active on " "channel %s\n", channel); else if (err != 0) fmd_hdl_abort(hdl, "failed to subscribe to channel %s", channel); fmd_prop_free_string(hdl, channel); } fmd_prop_free_string(hdl, sidpfx); } void fmevt_fini_inbound(fmd_hdl_t *hdl) { int i; for (i = 0; i < sizeof (chaninfo) / sizeof (chaninfo[0]); i++) { struct fmevt_chaninfo *cip = &chaninfo[i]; if (cip->ci_binding) { (void) sysevent_evc_unsubscribe(cip->ci_binding, cip->ci_sid); (void) sysevent_evc_unbind(cip->ci_binding); cip->ci_binding = NULL; } } if (subattr) { sysevent_subattr_free(subattr); subattr = NULL; } if (fmevt_xprt) { /* drain before destruction */ (void) pthread_mutex_lock(&fmevt_lock); fmevt_exiting = 1; while (fmevt_xprt_refcnt > 0) (void) pthread_cond_wait(&fmevt_cv, &fmevt_lock); (void) pthread_mutex_unlock(&fmevt_lock); fmd_xprt_close(hdl, fmevt_xprt); } } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include "fmevt.h" /* * Post-processing according to the FMEV_RULESET_UNREGISTERED ruleset. * * Generate a class of ireport.unregistered.. * with attributes just those of the raw event. If either the raw * class or subclass is NULL or empty then drop the event. */ /*ARGSUSED*/ uint_t fmevt_pp_unregistered(char *classes[FMEVT_FANOUT_MAX], nvlist_t *attr[FMEVT_FANOUT_MAX], const char *ruleset, const nvlist_t *detector, nvlist_t *rawattr, const struct fmevt_ppargs *eap) { if (eap->pp_rawclass == NULL || eap->pp_rawsubclass == NULL) return (0); if (snprintf(classes[0], FMEVT_MAX_CLASS, "%s.%s.%s.%s", FM_IREPORT_CLASS, "unregistered", eap->pp_rawclass, eap->pp_rawsubclass) >= FMEVT_MAX_CLASS - 1) return (0); attr[0] = rawattr; return (1); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include "fmevt.h" /* * Post-processing according to the FMEV_RULESET_ON_EREPORT ruleset. * Produce a single event of class ereport... * If either the raw class or subclass is NULL, drop the event. * The event will have the raw attributes. */ /*ARGSUSED*/ uint_t fmevt_pp_on_ereport(char *classes[FMEVT_FANOUT_MAX], nvlist_t *attr[FMEVT_FANOUT_MAX], const char *ruleset, const nvlist_t *detector, nvlist_t *rawattr, const struct fmevt_ppargs *eap) { if (eap->pp_rawclass == NULL || eap->pp_rawsubclass == NULL) return (0); if (snprintf(classes[0], FMEVT_MAX_CLASS, "%s.%s.%s", FM_EREPORT_CLASS, eap->pp_rawclass, eap->pp_rawsubclass) >= FMEVT_MAX_CLASS - 1) return (0); attr[0] = rawattr; return (1); } /* * Post-processing according to the FMEV_RULESET_ON_PRIVATE ruleset. * Produce a single event of class * ireport.private.solaris-osnet... * If either the raw class or subclass is NULL, drop the event. * The event will have the raw attributes. */ /*ARGSUSED*/ uint_t fmevt_pp_on_private(char *classes[FMEVT_FANOUT_MAX], nvlist_t *attr[FMEVT_FANOUT_MAX], const char *ruleset, const nvlist_t *detector, nvlist_t *rawattr, const struct fmevt_ppargs *eap) { if (eap->pp_rawclass == NULL || eap->pp_rawsubclass == NULL) return (0); if (snprintf(classes[0], FMEVT_MAX_CLASS, "%s.%s.%s.%s", FM_IREPORT_CLASS, "private.solaris-osnet", eap->pp_rawclass, eap->pp_rawsubclass) >= FMEVT_MAX_CLASS - 1) return (0); attr[0] = rawattr; return (1); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include "fmevt.h" /* * Post-processing according to the FMEV_RULESET_SMF ruleset. * * Raw event we expect: * * ========================================================================== * Class: "state-transition" * Subclasses: The new state, one of SCF_STATE_STRING_* from libscf.h * Attr: * Name DATA_TYPE_* Description * ------------ --------------- --------------------------------------------- * fmri STRING svc:/... (svc scheme shorthand version) * transition INT32 (old_state << 16) | new_state * reason-version UINT32 reason-short namespace version * reason-short STRING Short/keyword reason for transition * reason-long STRING Long-winded reason for the transition * ========================================================================== * * Protocol event components we return: * * ========================================================================== * Class: ireport.os.smf.state-transition. * Attr: * Name DATA_TYPE_* Description * ------------ --------------- ---------------------------------------- * svc NVLIST "svc" scheme FMRI of affected service instance * svc-string STRING SMF FMRI in short string form svc:/foo/bar * from-state STRING Previous state; SCF_STATE_STRING_* * to-state STRING New state; SCF_STATE_STRING_* * reason-version UINT32 reason-short namespace version * reason-short STRING Short/keyword reason for transition * reason-long STRING Long-winded reason for the transition * ========================================================================== */ /* * svc.startd generates events using the FMRI shorthand (svc:/foo/bar) * instead of the standard form (svc:///foo/bar). This function converts to * the standard representation. The caller must free the allocated string. */ static char * shortfmri_to_fmristr(fmd_hdl_t *hdl, const char *shortfmristr) { size_t len; char *fmristr; if (strncmp(shortfmristr, "svc:/", 5) != 0) return (NULL); len = strlen(shortfmristr) + 3; fmristr = fmd_hdl_alloc(hdl, len, FMD_SLEEP); (void) snprintf(fmristr, len, "svc:///%s", shortfmristr + 5); return (fmristr); } /* * Convert a shorthand svc FMRI into a full svc FMRI nvlist */ static nvlist_t * shortfmri_to_fmri(fmd_hdl_t *hdl, const char *shortfmristr) { nvlist_t *ret, *fmri; topo_hdl_t *thp; char *fmristr; int err; if ((fmristr = shortfmri_to_fmristr(hdl, shortfmristr)) == NULL) return (NULL); thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if (topo_fmri_str2nvl(thp, fmristr, &fmri, &err) != 0) { fmd_hdl_error(hdl, "failed to convert '%s' to nvlist\n", fmristr); fmd_hdl_strfree(hdl, fmristr); fmd_hdl_topo_rele(hdl, thp); return (NULL); } fmd_hdl_strfree(hdl, fmristr); if ((ret = fmd_nvl_dup(hdl, fmri, FMD_SLEEP)) == NULL) { fmd_hdl_error(hdl, "failed to dup fmri\n"); nvlist_free(fmri); fmd_hdl_topo_rele(hdl, thp); return (NULL); } nvlist_free(fmri); fmd_hdl_topo_rele(hdl, thp); return (ret); } /*ARGSUSED*/ uint_t fmevt_pp_smf(char *classes[FMEVT_FANOUT_MAX], nvlist_t *attr[FMEVT_FANOUT_MAX], const char *ruleset, const nvlist_t *detector, nvlist_t *rawattr, const struct fmevt_ppargs *eap) { int32_t transition, from, to; const char *fromstr, *tostr; char *svcname, *rsn, *rsnl; nvlist_t *myattr; nvlist_t *fmri; uint32_t ver; if (!fmd_prop_get_int32(fmevt_hdl, "inbound_postprocess_smf")) return (0); if (rawattr == NULL || strcmp(eap->pp_rawclass, "state-transition") != 0 || nvlist_lookup_string(rawattr, "fmri", &svcname) != 0 || nvlist_lookup_int32(rawattr, "transition", &transition) != 0 || nvlist_lookup_string(rawattr, "reason-short", &rsn) != 0 || nvlist_lookup_string(rawattr, "reason-long", &rsnl) != 0 || nvlist_lookup_uint32(rawattr, "reason-version", &ver) != 0) return (0); from = transition >> 16; to = transition & 0xffff; fromstr = smf_state_to_string(from); tostr = smf_state_to_string(to); if (fromstr == NULL || tostr == NULL) return (0); if (strcmp(eap->pp_rawsubclass, tostr) != 0) return (0); if ((fmri = shortfmri_to_fmri(fmevt_hdl, svcname)) == NULL) return (0); if (snprintf(classes[0], FMEVT_MAX_CLASS, "%s.%s.%s.%s", FM_IREPORT_CLASS, "os.smf", eap->pp_rawclass, eap->pp_rawsubclass) >= FMEVT_MAX_CLASS - 1) return (0); if ((myattr = fmd_nvl_alloc(fmevt_hdl, FMD_SLEEP)) == NULL) return (0); if (nvlist_add_nvlist(myattr, "svc", fmri) != 0 || nvlist_add_string(myattr, "svc-string", svcname) != 0 || nvlist_add_string(myattr, "from-state", fromstr) != 0 || nvlist_add_string(myattr, "to-state", tostr) != 0 || nvlist_add_uint32(myattr, "reason-version", ver) != 0 || nvlist_add_string(myattr, "reason-short", rsn) != 0 || nvlist_add_string(myattr, "reason-long", rsnl) != 0) { nvlist_free(fmri); nvlist_free(myattr); return (0); } attr[0] = myattr; nvlist_free(fmri); return (1); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include "fmevt.h" /* * Support for the FMEV_RULESET_ON_SUNOS ruleset. */ /* * Panic events. */ /*ARGSUSED*/ static int pp_sunos_panic(char *classes[FMEVT_FANOUT_MAX], nvlist_t *attr[FMEVT_FANOUT_MAX], const char *ruleset, const nvlist_t *detector, nvlist_t *rawattr, const struct fmevt_ppargs *eap) { nvlist_t *myattr; time_t panictime32; int64_t panictime; char buf[128]; struct tm ts; if (strcmp(eap->pp_rawsubclass, "dump_pending_on_device") != 0 && strcmp(eap->pp_rawsubclass, "savecore_failure") != 0 && strcmp(eap->pp_rawsubclass, "dump_available") != 0) return (0); if (snprintf(classes[0], FMEVT_MAX_CLASS, "%s.%s.%s", FM_IREPORT_CLASS, "os.sunos.panic", eap->pp_rawsubclass) >= FMEVT_MAX_CLASS - 1) return (0); if (nvlist_lookup_int64(rawattr, "crashtime", &panictime) != 0) return (0); panictime32 = (time_t)panictime; myattr = fmd_nvl_dup(fmevt_hdl, rawattr, FMD_SLEEP); if (localtime_r(&panictime32, &ts) != NULL && strftime(buf, sizeof (buf), "%c %Z", &ts) != 0) (void) nvlist_add_string(myattr, "panic-time", buf); attr[0] = myattr; return (1); } /*ARGSUSED*/ uint_t fmevt_pp_on_sunos(char *classes[FMEVT_FANOUT_MAX], nvlist_t *attr[FMEVT_FANOUT_MAX], const char *ruleset, const nvlist_t *detector, nvlist_t *rawattr, const struct fmevt_ppargs *eap) { if (strcmp(eap->pp_rawclass, "panic") == 0) return (pp_sunos_panic(classes, attr, ruleset, detector, rawattr, eap)); return (0); } /* * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include "fmevt.h" const fmd_prop_t fmevt_props[] = { { "protocol_forward_disable", FMD_TYPE_BOOL, "false" }, { "outbound_channel", FMD_TYPE_STRING, FMD_SNOOP_CHANNEL }, { "outbound_channel_depth", FMD_TYPE_INT32, "256" }, { "user_priv_highval_channel", FMD_TYPE_STRING, FMEV_CHAN_USER_PRIV_HV }, { "user_priv_lowval_channel", FMD_TYPE_STRING, FMEV_CHAN_USER_PRIV_LV }, { "sidprefix", FMD_TYPE_STRING, "fmd" }, { "inbound_postprocess_smf", FMD_TYPE_BOOL, "true" }, { NULL, 0, NULL }, }; static const fmd_hdl_ops_t fmd_ops = { fmevt_recv, /* fmdo_recv */ NULL, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ NULL /* fmdo_topo */ }; static const fmd_hdl_info_t fmd_info = { "External FM event transport", "0.2", &fmd_ops, fmevt_props }; fmd_hdl_t *fmevt_hdl; void _fmd_init(fmd_hdl_t *hdl) { /* * Register the handle, pulling in configuration from our * conf file. This includes our event class subscriptions * for those events that we will forward out of fmd. */ if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; fmevt_hdl = hdl; fmevt_init_outbound(hdl); fmevt_init_inbound(hdl); } void _fmd_fini(fmd_hdl_t *hdl) { fmevt_fini_outbound(hdl); fmevt_fini_inbound(hdl); } /* * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include "fmevt.h" static evchan_t *fmevt_outbound_chan; static struct fmevt_outbound_stats { fmd_stat_t recv_calls; fmd_stat_t recv_list; fmd_stat_t recv_ireport; fmd_stat_t recv_other; fmd_stat_t fwd_success; fmd_stat_t fwd_failure; } outbound_stats = { { "outbound_recv_calls", FMD_TYPE_UINT64, "total events received for forwarding" }, { "outbound_cat1class_list", FMD_TYPE_UINT64, "events received matching list.*" }, { "outbound_cat1class_ireport", FMD_TYPE_UINT64, "events received matching ireport.*" }, { "outbound_cat1class_other", FMD_TYPE_UINT64, "events of other classes" }, { "outbound_fwd_success", FMD_TYPE_UINT64, "events forwarded successfully" }, { "outbound_fwd_failure", FMD_TYPE_UINT64, "events we failed to forward" } }; #define BUMPSTAT(stat) outbound_stats.stat.fmds_value.ui64++ /* * In the .conf file we subscribe to list.* and ireport.* event classes. * Any additions to that set could cause some unexpected behaviour. * For example adding fault.foo won't work (since we don't publish * faults directly but only within a list.suspect) but we will get * any list.* including fault.foo as a suspect. */ /*ARGSUSED*/ void fmevt_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { BUMPSTAT(recv_calls); if (strncmp(class, "list.", 5) == 0) BUMPSTAT(recv_list); else if (strncmp(class, "ireport.", 8) == 0) BUMPSTAT(recv_ireport); else BUMPSTAT(recv_other); if (sysevent_evc_publish(fmevt_outbound_chan, class, "", SUNW_VENDOR, FM_PUB, nvl, EVCH_SLEEP) == 0) { BUMPSTAT(fwd_success); } else { BUMPSTAT(fwd_failure); fmd_hdl_debug(hdl, "sysevent_evc_publish failed:"); } } void fmevt_init_outbound(fmd_hdl_t *hdl) { int32_t channel_depth; char *channel_name; nvlist_t *nvl; if (fmd_prop_get_int32(hdl, "protocol_forward_disable") == B_TRUE) { fmd_hdl_debug(hdl, "protocol forwarding disabled " "through .conf file setting\n"); return; } (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (outbound_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&outbound_stats); /* * Allow simulation environment to change outbound channel name. */ channel_name = fmd_prop_get_string(hdl, "outbound_channel"); if (sysevent_evc_bind(channel_name, &fmevt_outbound_chan, EVCH_CREAT | EVCH_HOLD_PEND_INDEF) != 0) { fmd_hdl_abort(hdl, "Unable to bind channel %s", channel_name); } channel_depth = fmd_prop_get_int32(hdl, "outbound_channel_depth"); if (sysevent_evc_control(fmevt_outbound_chan, EVCH_SET_CHAN_LEN, (uint32_t)channel_depth) != 0) { fmd_hdl_abort(hdl, "Unable to set depth of channel %s to %d", channel_name, channel_depth); } fmd_prop_free_string(hdl, channel_name); nvl = fmd_nvl_alloc(hdl, FMD_SLEEP); (void) nvlist_add_nvlist(nvl, "fmdauth", (nvlist_t *)fmd_hdl_fmauth(hdl)); (void) sysevent_evc_setpropnvl(fmevt_outbound_chan, nvl); nvlist_free(nvl); } /*ARGSUSED*/ void fmevt_fini_outbound(fmd_hdl_t *hdl) { if (fmevt_outbound_chan != NULL) { (void) sysevent_evc_unbind(fmevt_outbound_chan); fmevt_outbound_chan = NULL; } } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. MODULE = fabric-xlate CLASS = common # Hammerhead: fx_epkt.c removed - requires SPARC sun4v px_err.h header SRCS = fabric-xlate.c fx_fabric.c fx_fire.c fx_subr.c include ../../Makefile.plugin CPPFLAGS += -I$(ROOT)/usr/include/libxml2 -I$(KMFDIR)/include -I. INCDIRS = $(SRC)/uts/common CFLAGS += -I$(INCDIRS) LDFLAGS += -L$(ROOT)/usr/lib/fm -R/usr/lib/fm LDLIBS += -luutil -lxml2 -L$(ROOTLIB)/fm -ltopo CERRWARN += -Wno-parentheses CERRWARN += -Wno-switch CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress pointer/int cast warnings in legacy code CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # not linted SMATCH=off /* * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include "fabric-xlate.h" #define XMLTOPOFILE "/var/run/fab-xlate-topo.xml" fmd_xprt_t *fab_fmd_xprt; /* FMD transport layer handle */ char fab_buf[FM_MAX_CLASS]; /* Static FM Topo XML Format and XML XPath Context */ static xmlDocPtr fab_doc = NULL; xmlXPathContextPtr fab_xpathCtx = NULL; static int fab_valid_topo = 0; static pthread_mutex_t fab_lock = PTHREAD_MUTEX_INITIALIZER; static void fab_update_topo(fmd_hdl_t *hdl) { topo_hdl_t *thp = NULL; FILE *fp = NULL; int err = 0; int fd = -1; /* Open the temporary file with proper ownership */ while (fd == -1) { if ((unlink(XMLTOPOFILE) == -1) && (errno != ENOENT)) { fmd_hdl_debug(hdl, "Failed to remove XML topo file\n"); return; } fd = open(XMLTOPOFILE, O_RDWR | O_CREAT | O_EXCL, 0600); if ((fd == -1) && (errno != EEXIST)) { fmd_hdl_debug(hdl, "Failed to create XML topo file\n"); return; } } /* Associate a stream with the temporary file */ if ((fp = fdopen(fd, "w")) == NULL) { fmd_hdl_debug(hdl, "Failed to open XML topo file\n"); goto cleanup; } /* Hold topology */ if ((thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION)) == NULL) { fmd_hdl_debug(hdl, "Failed to hold topo\n"); goto cleanup; } /* Print topology to XML file */ if (topo_xml_print(thp, fp, FM_FMRI_SCHEME_HC, &err) < 0) { fmd_hdl_debug(hdl, "Failed to get XML topo\n"); fmd_hdl_topo_rele(hdl, thp); goto cleanup; } fflush(fp); /* Release topology */ fmd_hdl_topo_rele(hdl, thp); /* Reload topology from XML file */ if (fab_xpathCtx) xmlXPathFreeContext(fab_xpathCtx); if (fab_doc) xmlFreeDoc(fab_doc); fab_doc = xmlParseFile(XMLTOPOFILE); fab_xpathCtx = xmlXPathNewContext(fab_doc); fab_set_fake_rp(hdl); fab_valid_topo = 1; cleanup: if (fp != NULL) (void) fclose(fp); else if (fd != -1) (void) close(fd); (void) unlink(XMLTOPOFILE); } /*ARGSUSED*/ static void fab_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { nvlist_t *new_nvl; (void) pthread_mutex_lock(&fab_lock); if (!fab_valid_topo) fab_update_topo(hdl); (void) pthread_mutex_unlock(&fab_lock); if (nvlist_dup(nvl, &new_nvl, NV_UNIQUE_NAME) != 0) { fmd_hdl_error(hdl, "failed to duplicate event"); return; } if (fmd_nvl_class_match(hdl, new_nvl, "ereport.io.pci.fabric")) { fab_xlate_fabric_erpts(hdl, new_nvl, class); } else { fab_pr(hdl, ep, new_nvl); if (fmd_nvl_class_match(hdl, new_nvl, "ereport.io.pciex.rc.epkt")) { fab_xlate_epkt_erpts(hdl, new_nvl, class); } else { fab_xlate_fire_erpts(hdl, new_nvl, class); } } nvlist_free(new_nvl); } /* ARGSUSED */ static void fab_topo(fmd_hdl_t *hdl, topo_hdl_t *topo) { (void) pthread_mutex_lock(&fab_lock); fab_valid_topo = 0; (void) pthread_mutex_unlock(&fab_lock); } static const fmd_hdl_ops_t fmd_ops = { fab_recv, /* fmdo_recv */ NULL, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ fab_topo, /* fmdo_topo */ }; static const fmd_hdl_info_t fmd_info = { "Fabric Ereport Translater", "1.0", &fmd_ops, NULL }; void _fmd_init(fmd_hdl_t *hdl) { if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; /* Init libxml */ xmlInitParser(); fab_fmd_xprt = fmd_xprt_open(hdl, FMD_XPRT_RDONLY, NULL, NULL); fmd_hdl_debug(hdl, "Fabric Translater Started\n"); fab_setup_master_table(); } void _fmd_fini(fmd_hdl_t *hdl) { /* Fini xpath */ if (fab_xpathCtx) xmlXPathFreeContext(fab_xpathCtx); /* Free xml document */ if (fab_doc) xmlFreeDoc(fab_doc); /* Fini libxml */ xmlCleanupParser(); fmd_xprt_close(hdl, fab_fmd_xprt); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # # fmd configuration file for the fabric-xlate.so diagnosis engine. # subscribe ereport.io.pci.fabric subscribe ereport.io.fire.pec.* subscribe ereport.io.fire.dmc.byp_oor subscribe ereport.io.fire.dmc.byp_err subscribe ereport.io.fire.dmc.trn_oor subscribe ereport.io.fire.dmc.tte_inv subscribe ereport.io.fire.dmc.tte_prt subscribe ereport.io.n2.dmu.iotsbdesc_inv subscribe ereport.io.n2.dmu.sun4v_adj_va_uf subscribe ereport.io.n2.dmu.sun4v_inv_pg_sz subscribe ereport.io.n2.dmu.sun4v_key_err subscribe ereport.io.n2.dmu.sun4v_va_oor subscribe ereport.io.pciex.rc.epkt /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. */ #ifndef _FABRIC_XLATE_H #define _FABRIC_XLATE_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define STRCMP(s1, s2) (strcmp((const char *)s1, (const char *)s2) == 0) /* * These values are used for the xxx_tgt_trans value in fab_data_t. They are * originally set in pcie_fault.c and originally defined in pcie_impl.h. */ #define PF_ADDR_DMA (1 << 0) #define PF_ADDR_PIO (1 << 1) #define PF_ADDR_CFG (1 << 2) /* * The fabric ereport preparation functions (fab_prep_*) in fab_erpt_tbl_t * structures may return an error if the ereport could not be set up properly. * Typically, these errors are errnos. It is possible that based on incoming * ereport payload data, we might not want to generate an ereport at all: In * this case, the preparation functions may instead return PF_EREPORT_IGNORE, * which is set at a high value so as not to collide with the errnos. */ #define PF_EREPORT_IGNORE INT_MAX extern fmd_xprt_t *fab_fmd_xprt; /* FMD transport layer handle */ extern char fab_buf[]; /* PCI-E config space data for error handling and fabric ereports */ typedef struct fab_data { /* Original ereport NVL */ nvlist_t *nvl; /* Device Information */ uint16_t bdf; uint16_t device_id; uint16_t vendor_id; uint8_t rev_id; uint16_t dev_type; uint16_t pcie_off; uint16_t pcix_off; uint16_t aer_off; uint16_t ecc_ver; /* Ereport Information */ uint32_t remainder; uint32_t severity; /* Error Registers */ uint16_t pci_err_status; /* pci status register */ uint16_t pci_cfg_comm; /* pci command register */ uint16_t pci_bdg_sec_stat; /* PCI secondary status reg */ uint16_t pci_bdg_ctrl; /* PCI bridge control reg */ uint16_t pcix_command; /* pcix command register */ uint32_t pcix_status; /* pcix status register */ uint16_t pcix_bdg_sec_stat; /* pcix bridge secondary status reg */ uint32_t pcix_bdg_stat; /* pcix bridge status reg */ uint16_t pcix_ecc_control_0; /* pcix ecc control status reg */ uint16_t pcix_ecc_status_0; /* pcix ecc control status reg */ uint32_t pcix_ecc_fst_addr_0; /* pcix ecc first address reg */ uint32_t pcix_ecc_sec_addr_0; /* pcix ecc second address reg */ uint32_t pcix_ecc_attr_0; /* pcix ecc attributes reg */ uint16_t pcix_ecc_control_1; /* pcix ecc control status reg */ uint16_t pcix_ecc_status_1; /* pcix ecc control status reg */ uint32_t pcix_ecc_fst_addr_1; /* pcix ecc first address reg */ uint32_t pcix_ecc_sec_addr_1; /* pcix ecc second address reg */ uint32_t pcix_ecc_attr_1; /* pcix ecc attributes reg */ uint16_t pcie_err_status; /* pcie device status register */ uint16_t pcie_err_ctl; /* pcie error control register */ uint32_t pcie_dev_cap; /* pcie device capabilities register */ uint32_t pcie_adv_ctl; /* pcie advanced control reg */ uint32_t pcie_ue_status; /* pcie ue error status reg */ uint32_t pcie_ue_mask; /* pcie ue error mask reg */ uint32_t pcie_ue_sev; /* pcie ue error severity reg */ uint32_t pcie_ue_hdr[4]; /* pcie ue header log */ uint32_t pcie_ce_status; /* pcie ce error status reg */ uint32_t pcie_ce_mask; /* pcie ce error mask reg */ uint32_t pcie_ue_tgt_trans; /* Fault trans type from AER Logs */ uint64_t pcie_ue_tgt_addr; /* Fault addr from AER Logs */ pcie_req_id_t pcie_ue_tgt_bdf; /* Fault bdf from SAER Logs */ boolean_t pcie_ue_no_tgt_erpt; /* Don't send target ereports */ uint32_t pcie_sue_ctl; /* pcie bridge secondary ue control */ uint32_t pcie_sue_status; /* pcie bridge secondary ue status */ uint32_t pcie_sue_mask; /* pcie bridge secondary ue mask */ uint32_t pcie_sue_sev; /* pcie bridge secondary ue severity */ uint32_t pcie_sue_hdr[4]; /* pcie bridge secondary ue hdr log */ uint32_t pcie_sue_tgt_trans; /* Fault trans type from AER Logs */ uint64_t pcie_sue_tgt_addr; /* Fault addr from AER Logs */ pcie_req_id_t pcie_sue_tgt_bdf; /* Fault bdf from SAER Logs */ uint32_t pcie_rp_status; /* root complex status register */ uint16_t pcie_rp_ctl; /* root complex control register */ uint32_t pcie_rp_err_status; /* pcie root complex error status reg */ uint32_t pcie_rp_err_cmd; /* pcie root complex error cmd reg */ uint16_t pcie_rp_ce_src_id; /* pcie root complex ce source id */ uint16_t pcie_rp_ue_src_id; /* pcie root complex ue source id */ /* * The slot register values refer to the registers of the component's * parent slot, not the component itself. * * You should only use the register values -- i.e., * pcie_slot_{cap,control,status} -- if pcie_slot_data_valid is set to * true. */ boolean_t pcie_slot_data_valid; /* true if slot data is valid */ uint32_t pcie_slot_cap; /* pcie slot capabilities */ uint16_t pcie_slot_control; /* pcie slot control */ uint16_t pcie_slot_status; /* pcie slot status */ /* Flags */ boolean_t pcie_rp_send_all; /* need to send ereports on all rps */ } fab_data_t; typedef struct fab_erpt_tbl { const char *err_class; /* Final Ereport Class */ uint32_t reg_bit; /* Error Bit Mask */ const char *tgt_class; /* Target Ereport Class */ } fab_erpt_tbl_t; typedef struct fab_err_tbl { fab_erpt_tbl_t *erpt_tbl; /* ereport table */ uint32_t reg_offset; /* sts reg for ereport table offset */ uint32_t reg_size; /* size of the status register */ /* Pointer to function that prepares the ereport body */ int (*fab_prep)(fmd_hdl_t *, fab_data_t *, nvlist_t *, fab_erpt_tbl_t *); } fab_err_tbl_t; extern void fab_setup_master_table(); /* Main functions for converting "fabric" ereports */ extern void fab_xlate_pcie_erpts(fmd_hdl_t *, fab_data_t *); extern void fab_xlate_fabric_erpts(fmd_hdl_t *, nvlist_t *, const char *); extern void fab_xlate_fire_erpts(fmd_hdl_t *, nvlist_t *, const char *); extern void fab_xlate_epkt_erpts(fmd_hdl_t *, nvlist_t *, const char *); /* Common functions for sending translated ereports */ extern int fab_prep_basic_erpt(fmd_hdl_t *, nvlist_t *, nvlist_t *, boolean_t); extern void fab_send_tgt_erpt(fmd_hdl_t *, fab_data_t *, const char *, boolean_t); extern void fab_send_erpt(fmd_hdl_t *hdl, fab_data_t *data, fab_err_tbl_t *tbl); /* Misc Functions */ extern void fab_pr(fmd_hdl_t *, fmd_event_t *, nvlist_t *); extern boolean_t fab_get_hcpath(fmd_hdl_t *, nvlist_t *, char **, size_t *); extern boolean_t fab_get_rcpath(fmd_hdl_t *, nvlist_t *, char *); extern char *fab_find_rppath_by_df(fmd_hdl_t *, nvlist_t *, uint8_t); extern char *fab_find_rppath_by_devbdf(fmd_hdl_t *, nvlist_t *, pcie_req_id_t); extern char *fab_find_rppath_by_devpath(fmd_hdl_t *, const char *); extern char *fab_find_addr(fmd_hdl_t *hdl, nvlist_t *nvl, uint64_t addr); extern char *fab_find_bdf(fmd_hdl_t *hdl, nvlist_t *nvl, pcie_req_id_t bdf); extern boolean_t fab_hc2dev(fmd_hdl_t *, const char *, char **); extern boolean_t fab_hc2dev_nvl(fmd_hdl_t *, nvlist_t *, char **); extern char *fab_get_rpdev(fmd_hdl_t *); extern void fab_set_fake_rp(fmd_hdl_t *); extern void fab_send_erpt_all_rps(fmd_hdl_t *, nvlist_t *); #ifdef __cplusplus } #endif #endif /* _FABRIC_XLATE_H */ /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include "fabric-xlate.h" #define EPKT_DESC(b, o, p, c, d) (BLOCK_##b << 16 | OP_##o << 12 | \ PH_##p << 8 | CND_##c << 4 | DIR_##d) /* EPKT Table used only for RC/RP errors */ typedef struct fab_epkt_tbl { uint32_t epkt_desc; uint32_t pcie_ue_sts; /* Equivalent PCIe UE Status */ uint16_t pci_err_sts; /* Equivalent PCI Error Status */ uint16_t pci_bdg_sts; /* Equivalent PCI Bridge Status */ const char *tgt_class; /* Target Ereport Class */ } fab_epkt_tbl_t; static fab_epkt_tbl_t fab_epkt_tbl[] = { EPKT_DESC(MMU, XLAT, DATA, INV, RDWR), PCIE_AER_UCE_CA, 0, PCI_STAT_S_TARG_AB, 0, EPKT_DESC(MMU, XLAT, ADDR, UNMAP, RDWR), PCIE_AER_UCE_CA, 0, PCI_STAT_S_TARG_AB, 0, EPKT_DESC(MMU, XLAT, DATA, PROT, RDWR), PCIE_AER_UCE_CA, 0, PCI_STAT_S_TARG_AB, 0, EPKT_DESC(INTR, MSI32, DATA, ILL, IRR), PCIE_AER_UCE_MTLP, PCI_STAT_S_SYSERR, 0, 0, EPKT_DESC(PORT, PIO, IRR, RCA, WRITE), 0, PCI_STAT_S_SYSERR, PCI_STAT_R_TARG_AB, 0, EPKT_DESC(PORT, PIO, IRR, RUR, WRITE), 0, PCI_STAT_S_SYSERR, PCI_STAT_R_MAST_AB, 0, EPKT_DESC(PORT, PIO, IRR, INV, RDWR), PCIE_AER_UCE_MTLP, PCI_STAT_S_SYSERR, 0, 0, EPKT_DESC(PORT, PIO, IRR, TO, READ), PCIE_AER_UCE_TO, PCI_STAT_S_SYSERR, 0, PCI_TARG_MA, EPKT_DESC(PORT, PIO, IRR, TO, WRITE), PCIE_AER_UCE_TO, PCI_STAT_S_SYSERR, 0, PCI_TARG_MA, EPKT_DESC(PORT, PIO, IRR, UC, IRR), PCIE_AER_UCE_UC, PCI_STAT_S_SYSERR, 0, 0, EPKT_DESC(PORT, LINK, FC, TO, IRR), PCIE_AER_UCE_FCP, PCI_STAT_S_SYSERR, 0, 0, 0, 0, 0, 0, 0 }; /* ARGSUSED */ void fab_epkt_to_data(fmd_hdl_t *hdl, nvlist_t *nvl, fab_data_t *data) { data->nvl = nvl; /* Always Root Complex */ data->dev_type = PCIE_PCIECAP_DEV_TYPE_ROOT; data->pcie_ue_sev = (PCIE_AER_UCE_DLP | PCIE_AER_UCE_SD | PCIE_AER_UCE_FCP | PCIE_AER_UCE_RO | PCIE_AER_UCE_MTLP); } static int fab_xlate_epkt(fmd_hdl_t *hdl, fab_data_t *data, px_rc_err_t *epktp) { fab_epkt_tbl_t *entry; uint32_t temp; for (entry = fab_epkt_tbl; entry->epkt_desc != 0; entry++) { temp = *(uint32_t *)&epktp->rc_descr >> 12; if (entry->epkt_desc == temp) goto send; } return (0); send: fmd_hdl_debug(hdl, "Translate epkt DESC = %#x\n", temp); /* Fill in PCI Status Register */ data->pci_err_status = entry->pci_err_sts; data->pci_bdg_sec_stat = entry->pci_bdg_sts; /* Fill in the device status register */ if (epktp->rc_descr.STOP) data->pcie_err_status = PCIE_DEVSTS_FE_DETECTED; else if (epktp->rc_descr.C) data->pcie_err_status = PCIE_DEVSTS_CE_DETECTED; else data->pcie_err_status = PCIE_DEVSTS_NFE_DETECTED; /* Fill in the AER UE register */ data->pcie_ue_status = entry->pcie_ue_sts; /* Fill in the AER Control register */ temp = entry->pcie_ue_sts; for (data->pcie_adv_ctl = (uint32_t)-1; temp; data->pcie_adv_ctl++) temp = temp >> 1; /* Send target ereports */ data->pcie_ue_no_tgt_erpt = B_TRUE; if (entry->tgt_class && !epktp->rc_descr.STOP) { if (epktp->rc_descr.D) { data->pcie_ue_tgt_trans = PF_ADDR_DMA; data->pcie_ue_tgt_addr = epktp->addr; } else if (epktp->rc_descr.M) { data->pcie_ue_tgt_trans = PF_ADDR_PIO; data->pcie_ue_tgt_addr = epktp->addr; } if (data->pcie_ue_tgt_trans) fab_send_tgt_erpt(hdl, data, entry->tgt_class, B_TRUE); } return (1); } void fab_xlate_epkt_erpts(fmd_hdl_t *hdl, nvlist_t *nvl, const char *class) { fab_data_t data = {0}; px_rc_err_t epkt = {0}; pcie_tlp_hdr_t *tlp_hdr; void *ptr; uint8_t ver; int err; char *devpath, *rppath = NULL; nvlist_t *detector; fmd_hdl_debug(hdl, "epkt ereport received: %s\n", class); fab_epkt_to_data(hdl, nvl, &data); err = nvlist_lookup_uint8(nvl, "epkt_ver", &ver); err |= nvlist_lookup_uint32(nvl, "desc", (uint32_t *)&epkt.rc_descr); err |= nvlist_lookup_uint32(nvl, "size", &epkt.size); err |= nvlist_lookup_uint64(nvl, "addr", &epkt.addr); err |= nvlist_lookup_uint64(nvl, "hdr1", &epkt.hdr[0]); err |= nvlist_lookup_uint64(nvl, "hdr2", &epkt.hdr[1]); err |= nvlist_lookup_uint64(nvl, "reserved", &epkt.reserved); if (err != 0) { fmd_hdl_debug(hdl, "Failed to retrieve all epkt payloads"); return; } fmd_hdl_debug(hdl, "epkt flags: %c%c%c%c%c%c%c%c%c %s", epkt.rc_descr.S ? 'S' : '-', epkt.rc_descr.M ? 'M' : '-', epkt.rc_descr.S ? 'Q' : '-', epkt.rc_descr.D ? 'D' : '-', epkt.rc_descr.R ? 'R' : '-', epkt.rc_descr.H ? 'H' : '-', epkt.rc_descr.C ? 'C' : '-', epkt.rc_descr.I ? 'I' : '-', epkt.rc_descr.B ? 'B' : '-', epkt.rc_descr.STOP ? "STOP" : ""); /* * If the least byte of the 'reserved' is non zero, it is device * and function of the port */ if (epkt.reserved && 0xff) rppath = fab_find_rppath_by_df(hdl, nvl, epkt.reserved & 0xff); if (epkt.rc_descr.H) { data.pcie_ue_hdr[0] = (uint32_t)(epkt.hdr[0] >> 32); data.pcie_ue_hdr[1] = (uint32_t)epkt.hdr[0]; data.pcie_ue_hdr[2] = (uint32_t)(epkt.hdr[1] >> 32); data.pcie_ue_hdr[3] = (uint32_t)(epkt.hdr[1]); tlp_hdr = (pcie_tlp_hdr_t *)&data.pcie_ue_hdr[0]; ptr = &data.pcie_ue_hdr[1]; switch (tlp_hdr->type) { case PCIE_TLP_TYPE_IO: case PCIE_TLP_TYPE_MEM: case PCIE_TLP_TYPE_MEMLK: { pcie_mem64_t *pmp = ptr; data.pcie_ue_tgt_trans = PF_ADDR_PIO; data.pcie_ue_tgt_bdf = pmp->rid; if (tlp_hdr->fmt & 0x1) data.pcie_ue_tgt_addr = ((uint64_t)pmp->addr1 << 32) | pmp->addr0; else data.pcie_ue_tgt_addr = ((pcie_memio32_t *)ptr)->addr0; break; } case PCIE_TLP_TYPE_CFG0: case PCIE_TLP_TYPE_CFG1: { pcie_cfg_t *pcp = ptr; data.pcie_ue_tgt_trans = PF_ADDR_CFG; data.pcie_ue_tgt_bdf = (pcp->bus << 8) | (pcp->dev << 3) | pcp->func; break; } case PCIE_TLP_TYPE_CPL: case PCIE_TLP_TYPE_CPLLK: data.pcie_ue_tgt_bdf = ((pcie_cpl_t *)ptr)->rid; break; } fmd_hdl_debug(hdl, "HEADER 0 0x%x", data.pcie_ue_hdr[0]); fmd_hdl_debug(hdl, "HEADER 1 0x%x", data.pcie_ue_hdr[1]); fmd_hdl_debug(hdl, "HEADER 2 0x%x", data.pcie_ue_hdr[2]); fmd_hdl_debug(hdl, "HEADER 3 0x%x", data.pcie_ue_hdr[3]); fmd_hdl_debug(hdl, "In header bdf = %#hx addr = %#llx", data.pcie_ue_tgt_bdf, (uint64_t)data.pcie_ue_tgt_addr); /* find the root port to which this error is related */ if (rppath == NULL && data.pcie_ue_tgt_bdf) rppath = fab_find_rppath_by_devbdf(hdl, nvl, data.pcie_ue_tgt_bdf); } /* find the root port by address */ if (rppath == NULL && epkt.rc_descr.M != 0) { devpath = fab_find_addr(hdl, nvl, epkt.addr); if (devpath) { rppath = fab_find_rppath_by_devpath(hdl, devpath); fmd_hdl_strfree(hdl, devpath); } } /* * reset the detector in the original ereport to the root port */ if (rppath) { if (nvlist_alloc(&detector, NV_UNIQUE_NAME, 0) != 0) { fmd_hdl_error(hdl, "failed to allocate nvlist"); fmd_hdl_strfree(hdl, rppath); return; } (void) nvlist_add_string(detector, FM_VERSION, FM_DEV_SCHEME_VERSION); (void) nvlist_add_string(detector, FM_FMRI_SCHEME, FM_FMRI_SCHEME_DEV); (void) nvlist_add_string(detector, FM_FMRI_DEV_PATH, rppath); (void) nvlist_remove_all(nvl, FM_EREPORT_DETECTOR); (void) nvlist_add_nvlist(nvl, FM_EREPORT_DETECTOR, detector); nvlist_free(detector); fmd_hdl_strfree(hdl, rppath); } else { /* * We can not locate the root port the error originated from. * Likely this is because the original ereport is malformed or * the hw error register has corrupted contents. In this case, * the best we can do is send ereports on all root ports. * * Set pcie_rp_send_all for fab_send_erpt() to process later. */ fmd_hdl_debug(hdl, "RP not fond. Will translate on all RPs.\n"); data.pcie_rp_send_all = B_TRUE; } (void) fab_xlate_epkt(hdl, &data, &epkt); fab_xlate_pcie_erpts(hdl, &data); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include "fabric-xlate.h" #define FAB_LOOKUP(sz, name, field) \ (void) nvlist_lookup_uint ## sz(nvl, name, field) static boolean_t fab_xlate_fake_rp = B_TRUE; static fab_err_tbl_t *fab_master_err_tbl; /* * Translation tables for converting "fabric" error bits into "pci" ereports. * , , */ /* MACRO for table entries with no TGT ereports */ #define NT(class, bit, prep) class, bit, prep, NULL /* Translate Fabric ereports to ereport.io.pci.* */ fab_erpt_tbl_t fab_pci_erpt_tbl[] = { PCI_DET_PERR, PCI_STAT_PERROR, NULL, PCI_MDPE, PCI_STAT_S_PERROR, NULL, PCI_SIG_SERR, PCI_STAT_S_SYSERR, NULL, PCI_MA, PCI_STAT_R_MAST_AB, NULL, PCI_REC_TA, PCI_STAT_R_TARG_AB, NULL, PCI_SIG_TA, PCI_STAT_S_TARG_AB, NULL, NULL, 0, NULL }; /* Translate Fabric ereports to ereport.io.pci.sec-* */ static fab_erpt_tbl_t fab_pci_bdg_erpt_tbl[] = { PCI_DET_PERR, PCI_STAT_PERROR, NULL, PCI_MDPE, PCI_STAT_S_PERROR, NULL, PCI_REC_SERR, PCI_STAT_S_SYSERR, NULL, #ifdef sparc PCI_MA, PCI_STAT_R_MAST_AB, NULL, #endif PCI_REC_TA, PCI_STAT_R_TARG_AB, NULL, PCI_SIG_TA, PCI_STAT_S_TARG_AB, NULL, NULL, 0, NULL, }; /* Translate Fabric ereports to ereport.io.pci.dto */ static fab_erpt_tbl_t fab_pci_bdg_ctl_erpt_tbl[] = { PCI_DTO, PCI_BCNF_BCNTRL_DTO_STAT, NULL, NULL, 0, NULL }; /* Translate Fabric ereports to ereport.io.pciex.* */ static fab_erpt_tbl_t fab_pcie_ce_erpt_tbl[] = { PCIEX_RE, PCIE_AER_CE_RECEIVER_ERR, NULL, PCIEX_RNR, PCIE_AER_CE_REPLAY_ROLLOVER, NULL, PCIEX_RTO, PCIE_AER_CE_REPLAY_TO, NULL, PCIEX_BDP, PCIE_AER_CE_BAD_DLLP, NULL, PCIEX_BTP, PCIE_AER_CE_BAD_TLP, NULL, PCIEX_ANFE, PCIE_AER_CE_AD_NFE, NULL, NULL, 0, NULL }; /* * Translate Fabric ereports to ereport.io.pciex.* * The Target Ereports for this section is only used on leaf devices, with the * exception of TO */ static fab_erpt_tbl_t fab_pcie_ue_erpt_tbl[] = { PCIEX_TE, PCIE_AER_UCE_TRAINING, NULL, PCIEX_DLP, PCIE_AER_UCE_DLP, NULL, PCIEX_SD, PCIE_AER_UCE_SD, NULL, PCIEX_ROF, PCIE_AER_UCE_RO, NULL, PCIEX_FCP, PCIE_AER_UCE_FCP, NULL, PCIEX_MFP, PCIE_AER_UCE_MTLP, NULL, PCIEX_CTO, PCIE_AER_UCE_TO, PCI_TARG_MA, PCIEX_UC, PCIE_AER_UCE_UC, NULL, PCIEX_ECRC, PCIE_AER_UCE_ECRC, NULL, PCIEX_CA, PCIE_AER_UCE_CA, PCI_TARG_REC_TA, #ifdef sparc PCIEX_UR, PCIE_AER_UCE_UR, PCI_TARG_MA, #endif PCIEX_POIS, PCIE_AER_UCE_PTLP, PCI_TARG_MDPE, NULL, 0, NULL }; /* Translate Fabric ereports to ereport.io.pciex.* */ static fab_erpt_tbl_t fab_pcie_sue_erpt_tbl[] = { PCIEX_S_TA_SC, PCIE_AER_SUCE_TA_ON_SC, PCI_TARG_REC_TA, PCIEX_S_MA_SC, PCIE_AER_SUCE_MA_ON_SC, PCI_TARG_MA, PCIEX_S_RTA, PCIE_AER_SUCE_RCVD_TA, PCI_TARG_REC_TA, #ifdef sparc PCIEX_S_RMA, PCIE_AER_SUCE_RCVD_MA, PCI_TARG_MA, #endif PCIEX_S_USC, PCIE_AER_SUCE_USC_ERR, NULL, PCIEX_S_USCMD, PCIE_AER_SUCE_USC_MSG_DATA_ERR, PCI_TARG_REC_TA, PCIEX_S_UDE, PCIE_AER_SUCE_UC_DATA_ERR, PCI_TARG_MDPE, PCIEX_S_UAT, PCIE_AER_SUCE_UC_ATTR_ERR, PCI_TARG_MDPE, PCIEX_S_UADR, PCIE_AER_SUCE_UC_ADDR_ERR, PCI_TARG_MDPE, PCIEX_S_TEX, PCIE_AER_SUCE_TIMER_EXPIRED, NULL, PCIEX_S_PERR, PCIE_AER_SUCE_PERR_ASSERT, PCI_TARG_MDPE, PCIEX_S_SERR, PCIE_AER_SUCE_SERR_ASSERT, NULL, PCIEX_INTERR, PCIE_AER_SUCE_INTERNAL_ERR, NULL, NULL, 0, NULL }; /* Translate Fabric ereports to ereport.io.pcix.* */ static fab_erpt_tbl_t fab_pcix_erpt_tbl[] = { PCIX_SPL_DIS, PCI_PCIX_SPL_DSCD, NULL, PCIX_UNEX_SPL, PCI_PCIX_UNEX_SPL, NULL, PCIX_RX_SPL_MSG, PCI_PCIX_RX_SPL_MSG, NULL, NULL, 0, NULL }; static fab_erpt_tbl_t *fab_pcix_bdg_erpt_tbl = fab_pcix_erpt_tbl; /* Translate Fabric ereports to ereport.io.pcix.sec-* */ static fab_erpt_tbl_t fab_pcix_bdg_sec_erpt_tbl[] = { PCIX_SPL_DIS, PCI_PCIX_BSS_SPL_DSCD, NULL, PCIX_UNEX_SPL, PCI_PCIX_BSS_UNEX_SPL, NULL, PCIX_BSS_SPL_OR, PCI_PCIX_BSS_SPL_OR, NULL, PCIX_BSS_SPL_DLY, PCI_PCIX_BSS_SPL_DLY, NULL, NULL, 0, NULL }; /* Translate Fabric ereports to ereport.io.pciex.* */ static fab_erpt_tbl_t fab_pcie_nadv_erpt_tbl[] = { #ifdef sparc PCIEX_UR, PCIE_DEVSTS_UR_DETECTED, NULL, #endif PCIEX_FAT, PCIE_DEVSTS_FE_DETECTED, NULL, PCIEX_NONFAT, PCIE_DEVSTS_NFE_DETECTED, NULL, PCIEX_CORR, PCIE_DEVSTS_CE_DETECTED, NULL, NULL, 0, NULL }; /* Translate Fabric ereports to ereport.io.pciex.* */ static fab_erpt_tbl_t fab_pcie_rc_erpt_tbl[] = { PCIEX_RC_FE_MSG, PCIE_AER_RE_STS_FE_MSGS_RCVD, NULL, PCIEX_RC_NFE_MSG, PCIE_AER_RE_STS_NFE_MSGS_RCVD, NULL, PCIEX_RC_CE_MSG, PCIE_AER_RE_STS_CE_RCVD, NULL, PCIEX_RC_MCE_MSG, PCIE_AER_RE_STS_MUL_CE_RCVD, NULL, PCIEX_RC_MUE_MSG, PCIE_AER_RE_STS_MUL_FE_NFE_RCVD, NULL, NULL, 0, NULL }; /* * Translate Fabric ereports to pseudo ereport.io.pciex.* RC Fabric Messages. * If the RP is not a PCIe compliant RP or does not support AER, rely on the * leaf fabric ereport to help create a xxx_MSG ereport coming from the RC. */ static fab_erpt_tbl_t fab_pcie_fake_rc_erpt_tbl[] = { PCIEX_RC_FE_MSG, PCIE_DEVSTS_FE_DETECTED, NULL, PCIEX_RC_NFE_MSG, PCIE_DEVSTS_NFE_DETECTED, NULL, PCIEX_RC_CE_MSG, PCIE_DEVSTS_CE_DETECTED, NULL, NULL, 0, NULL, }; /* ARGSUSED */ void fab_pci_fabric_to_data(fmd_hdl_t *hdl, nvlist_t *nvl, fab_data_t *data) { data->nvl = nvl; /* Generic PCI device information */ FAB_LOOKUP(16, "bdf", &data->bdf); FAB_LOOKUP(16, "device_id", &data->device_id); FAB_LOOKUP(16, "vendor_id", &data->vendor_id); FAB_LOOKUP(8, "rev_id", &data->rev_id); FAB_LOOKUP(16, "dev_type", &data->dev_type); FAB_LOOKUP(16, "pcie_off", &data->pcie_off); FAB_LOOKUP(16, "pcix_off", &data->pcix_off); FAB_LOOKUP(16, "aer_off", &data->aer_off); FAB_LOOKUP(16, "ecc_ver", &data->ecc_ver); /* Misc ereport information */ FAB_LOOKUP(32, "remainder", &data->remainder); FAB_LOOKUP(32, "severity", &data->severity); /* PCI registers */ FAB_LOOKUP(16, "pci_status", &data->pci_err_status); FAB_LOOKUP(16, "pci_command", &data->pci_cfg_comm); /* PCI bridge registers */ FAB_LOOKUP(16, "pci_bdg_sec_status", &data->pci_bdg_sec_stat); FAB_LOOKUP(16, "pci_bdg_ctrl", &data->pci_bdg_ctrl); /* PCIx registers */ FAB_LOOKUP(32, "pcix_status", &data->pcix_status); FAB_LOOKUP(16, "pcix_command", &data->pcix_command); /* PCIx ECC Registers */ FAB_LOOKUP(16, "pcix_ecc_control_0", &data->pcix_ecc_control_0); FAB_LOOKUP(16, "pcix_ecc_status_0", &data->pcix_ecc_status_0); FAB_LOOKUP(32, "pcix_ecc_fst_addr_0", &data->pcix_ecc_fst_addr_0); FAB_LOOKUP(32, "pcix_ecc_sec_addr_0", &data->pcix_ecc_sec_addr_0); FAB_LOOKUP(32, "pcix_ecc_attr_0", &data->pcix_ecc_attr_0); /* PCIx ECC Bridge Registers */ FAB_LOOKUP(16, "pcix_ecc_control_1", &data->pcix_ecc_control_1); FAB_LOOKUP(16, "pcix_ecc_status_1", &data->pcix_ecc_status_1); FAB_LOOKUP(32, "pcix_ecc_fst_addr_1", &data->pcix_ecc_fst_addr_1); FAB_LOOKUP(32, "pcix_ecc_sec_addr_1", &data->pcix_ecc_sec_addr_1); FAB_LOOKUP(32, "pcix_ecc_attr_1", &data->pcix_ecc_attr_1); /* PCIx Bridge */ FAB_LOOKUP(32, "pcix_bdg_status", &data->pcix_bdg_stat); FAB_LOOKUP(16, "pcix_bdg_sec_status", &data->pcix_bdg_sec_stat); /* PCIe registers */ FAB_LOOKUP(16, "pcie_status", &data->pcie_err_status); FAB_LOOKUP(16, "pcie_command", &data->pcie_err_ctl); FAB_LOOKUP(32, "pcie_dev_cap", &data->pcie_dev_cap); /* PCIe AER registers */ FAB_LOOKUP(32, "pcie_adv_ctl", &data->pcie_adv_ctl); FAB_LOOKUP(32, "pcie_ue_status", &data->pcie_ue_status); FAB_LOOKUP(32, "pcie_ue_mask", &data->pcie_ue_mask); FAB_LOOKUP(32, "pcie_ue_sev", &data->pcie_ue_sev); FAB_LOOKUP(32, "pcie_ue_hdr0", &data->pcie_ue_hdr[0]); FAB_LOOKUP(32, "pcie_ue_hdr1", &data->pcie_ue_hdr[1]); FAB_LOOKUP(32, "pcie_ue_hdr2", &data->pcie_ue_hdr[2]); FAB_LOOKUP(32, "pcie_ue_hdr3", &data->pcie_ue_hdr[3]); FAB_LOOKUP(32, "pcie_ce_status", &data->pcie_ce_status); FAB_LOOKUP(32, "pcie_ce_mask", &data->pcie_ce_mask); FAB_LOOKUP(32, "pcie_ue_tgt_trans", &data->pcie_ue_tgt_trans); FAB_LOOKUP(64, "pcie_ue_tgt_addr", &data->pcie_ue_tgt_addr); FAB_LOOKUP(16, "pcie_ue_tgt_bdf", &data->pcie_ue_tgt_bdf); /* PCIe BDG AER registers */ FAB_LOOKUP(32, "pcie_sue_adv_ctl", &data->pcie_sue_ctl); FAB_LOOKUP(32, "pcie_sue_status", &data->pcie_sue_status); FAB_LOOKUP(32, "pcie_sue_mask", &data->pcie_sue_mask); FAB_LOOKUP(32, "pcie_sue_sev", &data->pcie_sue_sev); FAB_LOOKUP(32, "pcie_sue_hdr0", &data->pcie_sue_hdr[0]); FAB_LOOKUP(32, "pcie_sue_hdr1", &data->pcie_sue_hdr[1]); FAB_LOOKUP(32, "pcie_sue_hdr2", &data->pcie_sue_hdr[2]); FAB_LOOKUP(32, "pcie_sue_hdr3", &data->pcie_sue_hdr[3]); FAB_LOOKUP(32, "pcie_sue_tgt_trans", &data->pcie_sue_tgt_trans); FAB_LOOKUP(64, "pcie_sue_tgt_addr", &data->pcie_sue_tgt_addr); FAB_LOOKUP(16, "pcie_sue_tgt_bdf", &data->pcie_sue_tgt_bdf); /* PCIe RP registers */ FAB_LOOKUP(32, "pcie_rp_status", &data->pcie_rp_status); FAB_LOOKUP(16, "pcie_rp_control", &data->pcie_rp_ctl); /* PCIe RP AER registers */ FAB_LOOKUP(32, "pcie_adv_rp_status", &data->pcie_rp_err_status); FAB_LOOKUP(32, "pcie_adv_rp_command", &data->pcie_rp_err_cmd); FAB_LOOKUP(16, "pcie_adv_rp_ce_src_id", &data->pcie_rp_ce_src_id); FAB_LOOKUP(16, "pcie_adv_rp_ue_src_id", &data->pcie_rp_ue_src_id); /* * PCIe Parent Slot Registers * * These are only passed in the ereport if the parent PCIe component * supports the registers and the registers have valid data. As such, we * look up one slot register value first: If that value is present in * the input ereport data, then we know the others should be there as * well. We also set the pcie_slot_data_valid flag to ensure we know * the slot register data is safe to use in the module. */ data->pcie_slot_data_valid = B_FALSE; if (nvlist_lookup_uint32(nvl, "pcie_slot_cap", &data->pcie_slot_cap) == 0) { FAB_LOOKUP(16, "pcie_slot_control", &data->pcie_slot_control); FAB_LOOKUP(16, "pcie_slot_status", &data->pcie_slot_status); data->pcie_slot_data_valid = B_TRUE; } } static int fab_prep_pci_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; int err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCI_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCI_CONFIG_STATUS, data->pci_err_status); (void) nvlist_add_uint16(erpt, PCI_CONFIG_COMMAND, data->pci_cfg_comm); return (err); } static int fab_prep_pci_bdg_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; int err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s-%s", PCI_ERROR_SUBCLASS, PCI_SEC_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCI_SEC_CONFIG_STATUS, data->pci_bdg_sec_stat); (void) nvlist_add_uint16(erpt, PCI_BCNTRL, data->pci_bdg_ctrl); return (err); } static int fab_prep_pci_bdg_ctl_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; int err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCI_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCI_SEC_CONFIG_STATUS, data->pci_bdg_sec_stat); (void) nvlist_add_uint16(erpt, PCI_BCNTRL, data->pci_bdg_ctrl); return (err); } static int fab_prep_pcie_ce_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; int err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCIEX_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIEX_DEVSTS_REG, data->pcie_err_status); (void) nvlist_add_uint32(erpt, PCIEX_CE_STATUS_REG, data->pcie_ce_status); return (err); } static int fab_prep_pcie_ue_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; uint32_t first_err = 1 << (data->pcie_adv_ctl & PCIE_AER_CTL_FST_ERR_PTR_MASK); int err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); if (data->pcie_slot_data_valid) { (void) nvlist_add_uint32(erpt, "pcie_slot_cap", data->pcie_slot_cap); (void) nvlist_add_uint16(erpt, "pcie_slot_control", data->pcie_slot_control); (void) nvlist_add_uint16(erpt, "pcie_slot_status", data->pcie_slot_status); /* * It is possible to see uncorrectable errors for a slot that * are related to the slot's child device being physically * removed from the slot. As such, in the case that the slot * reports that it is empty, we do not want to generate an * ereport for all errors. Generating an ereport here will cause * the eft module to fault the device and io-retire to * subsequently retire the device. Retiring the device makes * little sense given that the device is physically gone; more * confusingly, if plugged back into the slot, it would be * marked retired already. * * The only error ignored for this case is Completion Timeout. * It is possible more errors should be ignored, and if they * are seen in the field it might be worth broadening the set * of ignored errors. */ if (tbl->reg_bit == PCIE_AER_UCE_TO && ((data->pcie_slot_status & PCIE_SLOTSTS_PRESENCE_DETECTED) == 0x0)) { return (PF_EREPORT_IGNORE); } } /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCIEX_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIEX_DEVSTS_REG, data->pcie_err_status); (void) nvlist_add_uint32(erpt, PCIEX_UE_STATUS_REG, data->pcie_ue_status); (void) nvlist_add_uint32(erpt, PCIEX_UE_SEV_REG, data->pcie_ue_sev); (void) nvlist_add_uint32(erpt, PCIEX_ADV_CTL, data->pcie_adv_ctl); fmd_hdl_debug(hdl, "Bit 0x%x First Err 0x%x", tbl->reg_bit, first_err); if ((tbl->reg_bit == first_err) && data->pcie_ue_tgt_bdf) { (void) nvlist_add_uint16(erpt, PCIEX_SRC_ID, data->pcie_ue_tgt_bdf); (void) nvlist_add_boolean_value(erpt, PCIEX_SRC_VALID, B_TRUE); } else { (void) nvlist_add_uint16(erpt, PCIEX_SRC_ID, 0); (void) nvlist_add_boolean_value(erpt, PCIEX_SRC_VALID, B_FALSE); } if ((tbl->reg_bit == first_err) && !data->pcie_ue_no_tgt_erpt && data->pcie_ue_tgt_trans) { if (tbl->tgt_class) fab_send_tgt_erpt(hdl, data, tbl->tgt_class, B_TRUE); } return (err); } static int fab_prep_pcie_sue_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; uint32_t first_err = 1 << (data->pcie_sue_ctl & PCIE_AER_SCTL_FST_ERR_PTR_MASK); int err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCIEX_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint32(erpt, PCIEX_SEC_UE_STATUS, data->pcie_sue_status); fmd_hdl_debug(hdl, "Bit 0x%x First Err 0x%x", tbl->reg_bit, first_err); if ((tbl->reg_bit == first_err) && data->pcie_sue_tgt_bdf) { (void) nvlist_add_uint16(erpt, PCIEX_SRC_ID, data->pcie_sue_tgt_bdf); (void) nvlist_add_boolean_value(erpt, PCIEX_SRC_VALID, B_TRUE); } else { (void) nvlist_add_uint16(erpt, PCIEX_SRC_ID, 0); (void) nvlist_add_boolean_value(erpt, PCIEX_SRC_VALID, B_FALSE); } if ((tbl->reg_bit == first_err) && !data->pcie_ue_no_tgt_erpt && data->pcie_sue_tgt_trans) { if (tbl->tgt_class) fab_send_tgt_erpt(hdl, data, tbl->tgt_class, B_FALSE); } return (err); } static int fab_prep_pcix_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; int err = 0; /* Only send if this is not a bridge */ if (!data->pcix_status || data->pcix_bdg_sec_stat) return (1); err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCIX_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint8(erpt, PCIX_COMMAND, data->pcix_command); (void) nvlist_add_uint32(erpt, PCIX_STATUS, data->pcix_status); return (err); } static void fab_send_pcix_ecc_erpt(fmd_hdl_t *hdl, fab_data_t *data) { nvlist_t *erpt; int ecc_phase = (data->pcix_ecc_status_0 & PCI_PCIX_ECC_PHASE) >> 0x4; int ecc_corr = data->pcix_ecc_status_0 & PCI_PCIX_ECC_CORR; int sec_ue = data->pcix_ecc_status_0 & PCI_PCIX_ECC_S_UE; int sec_ce = data->pcix_ecc_status_0 & PCI_PCIX_ECC_S_CE; uint32_t ctlstat = (data->pcix_ecc_control_0 << 16) | data->pcix_ecc_status_0; switch (ecc_phase) { case PCI_PCIX_ECC_PHASE_NOERR: break; case PCI_PCIX_ECC_PHASE_FADDR: case PCI_PCIX_ECC_PHASE_SADDR: (void) snprintf(fab_buf, FM_MAX_CLASS, "%s.%s", PCIX_ERROR_SUBCLASS, ecc_corr ? PCIX_ECC_CE_ADDR : PCIX_ECC_UE_ADDR); break; case PCI_PCIX_ECC_PHASE_ATTR: (void) snprintf(fab_buf, FM_MAX_CLASS, "%s.%s", PCIX_ERROR_SUBCLASS, ecc_corr ? PCIX_ECC_CE_ATTR : PCIX_ECC_UE_ATTR); break; case PCI_PCIX_ECC_PHASE_DATA32: case PCI_PCIX_ECC_PHASE_DATA64: (void) snprintf(fab_buf, FM_MAX_CLASS, "%s.%s", PCIX_ERROR_SUBCLASS, ecc_corr ? PCIX_ECC_CE_DATA : PCIX_ECC_UE_DATA); break; } if (ecc_phase) { if (nvlist_alloc(&erpt, NV_UNIQUE_NAME, 0) != 0) goto done; (void) fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIX_COMMAND, data->pcix_command); (void) nvlist_add_uint32(erpt, PCIX_STATUS, data->pcix_status); (void) nvlist_add_uint32(erpt, PCIX_ECC_CTLSTAT, ctlstat); (void) nvlist_add_uint32(erpt, PCIX_ECC_ATTR, data->pcix_ecc_attr_0); fmd_hdl_debug(hdl, "Sending ecc ereport: %s\n", fab_buf); fmd_xprt_post(hdl, fab_fmd_xprt, erpt, 0); if (fmd_xprt_error(hdl, fab_fmd_xprt)) fmd_hdl_debug(hdl, "Failed to send ECC ereport\n"); } if (sec_ce || sec_ue) { (void) snprintf(fab_buf, FM_MAX_CLASS, "%s.%s", PCIX_ERROR_SUBCLASS, sec_ce ? PCIX_ECC_S_CE : PCIX_ECC_S_UE); if (nvlist_alloc(&erpt, NV_UNIQUE_NAME, 0) != 0) goto done; (void) fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIX_COMMAND, data->pcix_command); (void) nvlist_add_uint32(erpt, PCIX_STATUS, data->pcix_status); (void) nvlist_add_uint32(erpt, PCIX_ECC_CTLSTAT, ctlstat); (void) nvlist_add_uint32(erpt, PCIX_ECC_ATTR, data->pcix_ecc_attr_0); fmd_hdl_debug(hdl, "Sending ecc ereport: %s\n", fab_buf); fmd_xprt_post(hdl, fab_fmd_xprt, erpt, 0); if (fmd_xprt_error(hdl, fab_fmd_xprt)) fmd_hdl_debug(hdl, "Failed to send ECC ereport\n"); } return; done: fmd_hdl_debug(hdl, "Failed to send ECC ereport\n"); } static int fab_prep_pcix_bdg_sec_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; int err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s%s", PCIX_ERROR_SUBCLASS, PCIX_SEC_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIX_SEC_STATUS, data->pcix_bdg_sec_stat); (void) nvlist_add_uint32(erpt, PCIX_BDG_STAT, data->pcix_bdg_stat); return (err); } static int fab_prep_pcix_bdg_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; int err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCIX_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIX_SEC_STATUS, data->pcix_bdg_sec_stat); (void) nvlist_add_uint32(erpt, PCIX_BDG_STAT, data->pcix_bdg_stat); return (err); } static void fab_send_pcix_bdg_ecc_erpt(fmd_hdl_t *hdl, fab_data_t *data) { nvlist_t *erpt; int ecc_phase = (data->pcix_ecc_status_1 & PCI_PCIX_ECC_PHASE) >> 0x4; int ecc_corr = data->pcix_ecc_status_1 & PCI_PCIX_ECC_CORR; int sec_ue = data->pcix_ecc_status_1 & PCI_PCIX_ECC_S_UE; int sec_ce = data->pcix_ecc_status_1 & PCI_PCIX_ECC_S_CE; uint32_t ctlstat = (data->pcix_ecc_control_1 << 16) | data->pcix_ecc_status_1; switch (ecc_phase) { case PCI_PCIX_ECC_PHASE_NOERR: break; case PCI_PCIX_ECC_PHASE_FADDR: case PCI_PCIX_ECC_PHASE_SADDR: (void) snprintf(fab_buf, FM_MAX_CLASS, "%s.%s%s", PCIX_ERROR_SUBCLASS, PCIX_SEC_ERROR_SUBCLASS, ecc_corr ? PCIX_ECC_CE_ADDR : PCIX_ECC_UE_ADDR); break; case PCI_PCIX_ECC_PHASE_ATTR: (void) snprintf(fab_buf, FM_MAX_CLASS, "%s.%s%s", PCIX_ERROR_SUBCLASS, PCIX_SEC_ERROR_SUBCLASS, ecc_corr ? PCIX_ECC_CE_ATTR : PCIX_ECC_UE_ATTR); break; case PCI_PCIX_ECC_PHASE_DATA32: case PCI_PCIX_ECC_PHASE_DATA64: (void) snprintf(fab_buf, FM_MAX_CLASS, "%s.%s%s", PCIX_ERROR_SUBCLASS, PCIX_SEC_ERROR_SUBCLASS, ecc_corr ? PCIX_ECC_CE_DATA : PCIX_ECC_UE_DATA); break; } if (ecc_phase) { if (nvlist_alloc(&erpt, NV_UNIQUE_NAME, 0) != 0) goto done; (void) fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIX_SEC_STATUS, data->pcix_bdg_sec_stat); (void) nvlist_add_uint32(erpt, PCIX_BDG_STAT, data->pcix_bdg_stat); (void) nvlist_add_uint32(erpt, PCIX_ECC_CTLSTAT, ctlstat); (void) nvlist_add_uint32(erpt, PCIX_ECC_ATTR, data->pcix_ecc_attr_1); fmd_hdl_debug(hdl, "Sending ecc ereport: %s\n", fab_buf); fmd_xprt_post(hdl, fab_fmd_xprt, erpt, 0); if (fmd_xprt_error(hdl, fab_fmd_xprt)) fmd_hdl_debug(hdl, "Failed to send ECC ereport\n"); } if (sec_ce || sec_ue) { (void) snprintf(fab_buf, FM_MAX_CLASS, "%s.%s%s", PCIX_ERROR_SUBCLASS, PCIX_SEC_ERROR_SUBCLASS, sec_ce ? PCIX_ECC_S_CE : PCIX_ECC_S_UE); if (nvlist_alloc(&erpt, NV_UNIQUE_NAME, 0) != 0) goto done; (void) fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIX_SEC_STATUS, data->pcix_bdg_sec_stat); (void) nvlist_add_uint32(erpt, PCIX_BDG_STAT, data->pcix_bdg_stat); (void) nvlist_add_uint32(erpt, PCIX_ECC_CTLSTAT, ctlstat); (void) nvlist_add_uint32(erpt, PCIX_ECC_ATTR, data->pcix_ecc_attr_1); fmd_hdl_debug(hdl, "Sending ecc ereport: %s\n", fab_buf); fmd_xprt_post(hdl, fab_fmd_xprt, erpt, 0); if (fmd_xprt_error(hdl, fab_fmd_xprt)) fmd_hdl_debug(hdl, "Failed to send ECC ereport\n"); } return; done: fmd_hdl_debug(hdl, "Failed to send ECC ereport\n"); } static int fab_prep_pcie_nadv_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; int err = 0; /* Don't send this for PCI device, Root Ports, or PCIe with AER */ if ((data->dev_type == PCIE_PCIECAP_DEV_TYPE_PCI_DEV) || (data->dev_type == PCIE_PCIECAP_DEV_TYPE_ROOT) || data->aer_off) return (1); err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCIEX_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint16(erpt, PCIEX_DEVSTS_REG, data->pcie_err_status); return (err); } static int fab_prep_pcie_rc_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; uint32_t status = data->pcie_rp_err_status; int err = 0; int isFE = 0, isNFE = 0; fmd_hdl_debug(hdl, "XLATE RP Error Class %s", class); if (!data->aer_off) return (-1); /* Only send a FE Msg if the 1st UE error is FE */ if (STRCMP(class, PCIEX_RC_FE_MSG)) if (!(status & PCIE_AER_RE_STS_FIRST_UC_FATAL)) return (-1); else isFE = 1; /* Only send a NFE Msg is the 1st UE error is NFE */ if (STRCMP(class, PCIEX_RC_NFE_MSG)) if (status & PCIE_AER_RE_STS_FIRST_UC_FATAL) return (-1); else isNFE = 1; fmd_hdl_debug(hdl, "XLATE RP Error"); err |= fab_prep_basic_erpt(hdl, data->nvl, erpt, B_FALSE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCIEX_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); (void) nvlist_add_uint32(erpt, PCIEX_ROOT_ERRSTS_REG, status); if ((isFE || isNFE) && data->pcie_rp_ue_src_id) { (void) nvlist_add_uint16(erpt, PCIEX_SRC_ID, data->pcie_rp_ue_src_id); (void) nvlist_add_boolean_value(erpt, PCIEX_SRC_VALID, B_TRUE); } if (STRCMP(class, PCIEX_RC_CE_MSG) && data->pcie_rp_ce_src_id) { (void) nvlist_add_uint16(erpt, PCIEX_SRC_ID, data->pcie_rp_ce_src_id); (void) nvlist_add_boolean_value(erpt, PCIEX_SRC_VALID, B_TRUE); } return (err); } static int fab_prep_pcie_fake_rc_erpt(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, fab_erpt_tbl_t *tbl) { const char *class = tbl->err_class; uint32_t rc_err_sts = 0; int err = 0; /* * Don't send this for PCI device or Root Ports. Only send it on * systems with non-compliant RPs. */ if ((data->dev_type == PCIE_PCIECAP_DEV_TYPE_PCI_DEV) || (data->dev_type == PCIE_PCIECAP_DEV_TYPE_ROOT) || (!fab_xlate_fake_rp)) return (-1); err = fab_prep_basic_erpt(hdl, data->nvl, erpt, B_TRUE); /* Generate an ereport for this error bit. */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCIEX_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); /* Send PCIe RC Ereports */ if (data->pcie_err_status & PCIE_DEVSTS_CE_DETECTED) { rc_err_sts |= PCIE_AER_RE_STS_CE_RCVD; } /* NFE/FE src id takes precedence over CE src id */ if (data->pcie_err_status & PCIE_DEVSTS_NFE_DETECTED) { rc_err_sts |= PCIE_AER_RE_STS_NFE_MSGS_RCVD; rc_err_sts |= PCIE_AER_RE_STS_FE_NFE_RCVD; } if (data->pcie_err_status & PCIE_DEVSTS_FE_DETECTED) { rc_err_sts |= PCIE_AER_RE_STS_FE_MSGS_RCVD; rc_err_sts |= PCIE_AER_RE_STS_FE_NFE_RCVD; } if ((data->pcie_err_status & PCIE_DEVSTS_NFE_DETECTED) && (data->pcie_err_status & PCIE_DEVSTS_FE_DETECTED)) { rc_err_sts |= PCIE_AER_RE_STS_FIRST_UC_FATAL; rc_err_sts |= PCIE_AER_RE_STS_MUL_FE_NFE_RCVD; } (void) nvlist_add_uint32(erpt, PCIEX_ROOT_ERRSTS_REG, rc_err_sts); if (!(rc_err_sts & PCIE_AER_RE_STS_MUL_FE_NFE_RCVD)) { (void) nvlist_add_uint16(erpt, PCIEX_SRC_ID, data->bdf); (void) nvlist_add_boolean_value(erpt, PCIEX_SRC_VALID, B_TRUE); } return (err); } void fab_xlate_pcie_erpts(fmd_hdl_t *hdl, fab_data_t *data) { fab_err_tbl_t *tbl; fmd_hdl_debug(hdl, "Sending Ereports Now"); /* Go through the error logs and send the relevant reports */ for (tbl = fab_master_err_tbl; tbl->erpt_tbl; tbl++) { fab_send_erpt(hdl, data, tbl); } /* Send PCI-X ECC Ereports */ fab_send_pcix_ecc_erpt(hdl, data); fab_send_pcix_bdg_ecc_erpt(hdl, data); } void fab_xlate_fabric_erpts(fmd_hdl_t *hdl, nvlist_t *nvl, const char *class) { fab_data_t data = {0}; fmd_hdl_debug(hdl, "fabric ereport received: %s\n", class); fab_pci_fabric_to_data(hdl, nvl, &data); fab_xlate_pcie_erpts(hdl, &data); } void fab_set_fake_rp(fmd_hdl_t *hdl) { char *rppath = fab_get_rpdev(hdl), *str = NULL; int count = 0; if (!rppath) { fmd_hdl_debug(hdl, "Can't find root port dev path"); return; } /* * For the path '/pci@xxx' is fake root port, * and '/pci@xxx/pci@y' is real root port. */ str = rppath; while (*str) { if (*str == '/') count++; str++; } if (count == 1) fab_xlate_fake_rp = B_TRUE; else /* * If count is 0, then it should still be B_FALSE */ fab_xlate_fake_rp = B_FALSE; fmd_hdl_strfree(hdl, rppath); } #define SET_TBL(n, err, reg, sz) \ fab_master_err_tbl[n].erpt_tbl = fab_ ## err ## _erpt_tbl; \ fab_master_err_tbl[n].reg_offset = offsetof(fab_data_t, reg); \ fab_master_err_tbl[n].reg_size = sz; \ fab_master_err_tbl[n].fab_prep = fab_prep_ ## err ## _erpt; void fab_setup_master_table() { /* Setup the master error table */ fab_master_err_tbl = (fab_err_tbl_t *)calloc(13, sizeof (fab_err_tbl_t)); SET_TBL(0, pci, pci_err_status, 16); SET_TBL(1, pci_bdg, pci_bdg_sec_stat, 16); SET_TBL(2, pci_bdg_ctl, pci_bdg_ctrl, 16); SET_TBL(3, pcie_ce, pcie_ce_status, 32); SET_TBL(4, pcie_ue, pcie_ue_status, 32); SET_TBL(5, pcie_sue, pcie_sue_status, 32); SET_TBL(6, pcix, pcix_status, 32); SET_TBL(7, pcix_bdg_sec, pcix_bdg_sec_stat, 16); SET_TBL(8, pcix_bdg, pcix_bdg_stat, 32); SET_TBL(9, pcie_nadv, pcie_err_status, 16); SET_TBL(10, pcie_rc, pcie_rp_err_status, 32); SET_TBL(11, pcie_fake_rc, pcie_err_status, 16); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include "fabric-xlate.h" typedef struct fab_fire_tbl { const char *err_class; uint32_t fire_bit; /* Fire error bit */ uint16_t pci_err_sts; /* Equivalent PCI Error Status */ uint16_t pci_bdg_sts; /* Equivalent PCI Bridge Status */ } fab_fire_tbl_t; /* * Translation tables for converting fire error bits into "pci" ereports. * * * * * */ #define FAB_FIRE_PEC_BIT(fb) "ereport.io." PCIEX_FIRE "." FIRE_PEC_ ## fb #define FAB_FIRE_DMC_BIT(fb) "ereport.io." PCIEX_FIRE "." FIRE_DMC_ ## fb #define FAB_N2_DMU_BIT(fb) "ereport.io.n2.dmu." fb #define FAB_OB_PEC_BIT(fb) "ereport.io." PCIEX_OBERON "." FIRE_PEC_ ## fb #define FAB_FIRE_UE(fb, bit, sts, bdg) \ FAB_FIRE_PEC_BIT(fb), PCIE_AER_UCE_ ## bit, sts, bdg #define FAB_OB_UE(fb, bit, sts, bdg) \ FAB_OB_PEC_BIT(fb), PCIE_AER_UCE_ ## bit, sts, bdg static fab_fire_tbl_t fab_fire_pec_ue_tbl[] = { FAB_FIRE_UE(UR, UR, PCI_STAT_S_SYSERR, 0), FAB_FIRE_UE(UC, UC, PCI_STAT_S_SYSERR, 0), FAB_OB_UE(ECRC, ECRC, PCI_STAT_S_SYSERR, 0), FAB_FIRE_UE(CTO, TO, PCI_STAT_S_SYSERR, 0), FAB_FIRE_UE(ROF, RO, PCI_STAT_S_SYSERR, 0), FAB_FIRE_UE(MFP, MTLP, PCI_STAT_S_SYSERR, 0), FAB_FIRE_UE(PP, PTLP, PCI_STAT_S_PERROR, (PCI_STAT_S_SYSERR | PCI_STAT_PERROR)), FAB_FIRE_UE(FCP, FCP, PCI_STAT_S_SYSERR, 0), FAB_FIRE_UE(DLP, DLP, PCI_STAT_S_SYSERR, 0), FAB_FIRE_UE(TE, TRAINING, PCI_STAT_S_SYSERR, 0), FAB_FIRE_UE(CA, CA, PCI_STAT_S_TARG_AB, PCI_STAT_S_TARG_AB), NULL, 0, 0, }; #define FAB_FIRE_CE(fb, bit) \ FAB_FIRE_PEC_BIT(fb), PCIE_AER_CE_ ## bit, 0, 0 static fab_fire_tbl_t fab_fire_pec_ce_tbl[] = { FAB_FIRE_CE(RTO, REPLAY_TO), FAB_FIRE_CE(RNR, REPLAY_ROLLOVER), FAB_FIRE_CE(BDP, BAD_DLLP), FAB_FIRE_CE(BTP, BAD_TLP), FAB_FIRE_CE(RE, RECEIVER_ERR), NULL, 0, 0, }; /* * WUC/RUC will need to be special cased for the target ereports, because you * need to decode the tlp log. */ #define FAB_FIRE_WUCRUC(fb) \ FAB_FIRE_PEC_BIT(fb), 0, 0, (PCI_STAT_R_MAST_AB | PCI_STAT_S_SYSERR) #define FAB_FIRE_OE(fb, bit) \ FAB_FIRE_PEC_BIT(fb), PCIE_AER_UCE_ ## bit, PCI_STAT_S_SYSERR, 0 #define FAB_OB_OE(fb, bit) \ FAB_FIRE_PEC_BIT(fb), PCIE_AER_UCE_ ## bit, PCI_STAT_S_SYSERR, 0 static fab_fire_tbl_t fab_fire_pec_oe_tbl[] = { FAB_FIRE_WUCRUC(WUC), FAB_FIRE_WUCRUC(RUC), FAB_FIRE_OE(ERU, DLP), FAB_FIRE_OE(ERO, DLP), FAB_FIRE_OE(EMP, DLP), FAB_FIRE_OE(EPE, DLP), NULL, 0, 0, }; #define FAB_FIRE_DMC(fb) \ FAB_FIRE_DMC_BIT(fb), PCIE_AER_UCE_CA, 0, PCI_STAT_S_TARG_AB #define FAB_N2_DMU(fb) \ FAB_N2_DMU_BIT(fb), PCIE_AER_UCE_CA, 0, PCI_STAT_S_TARG_AB static fab_fire_tbl_t fab_fire_dmc_tbl[] = { FAB_FIRE_DMC(BYP_ERR), FAB_FIRE_DMC(BYP_OOR), FAB_FIRE_DMC(TRN_OOR), FAB_FIRE_DMC(TTE_INV), FAB_FIRE_DMC(TTE_PRT), FAB_N2_DMU("iotsbdesc_inv"), FAB_N2_DMU("sun4v_adj_va_uf"), FAB_N2_DMU("sun4v_inv_pg_sz"), FAB_N2_DMU("sun4v_key_err"), FAB_N2_DMU("sun4v_va_oor"), NULL, 0, 0 }; /* ARGSUSED */ static void fab_fire_to_data(fmd_hdl_t *hdl, nvlist_t *nvl, fab_data_t *data) { data->nvl = nvl; /* Always Root Complex */ data->dev_type = PCIE_PCIECAP_DEV_TYPE_ROOT; data->pcie_ue_sev = (PCIE_AER_UCE_DLP | PCIE_AER_UCE_SD | PCIE_AER_UCE_FCP | PCIE_AER_UCE_RO | PCIE_AER_UCE_MTLP); } static int fab_xlate_fire_ce(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, const char *class) { fab_fire_tbl_t *entry; uint64_t reg; for (entry = fab_fire_pec_ce_tbl; entry->err_class; entry++) { if (STRCMP(class, entry->err_class)) goto send; } return (0); send: fmd_hdl_debug(hdl, "Translate Fire CE %s\n", class); /* Fill in the device status register */ data->pcie_err_status = PCIE_DEVSTS_CE_DETECTED; /* Fill in the AER CE register */ if (nvlist_lookup_uint64(erpt, "tlu-cess", ®) == 0) { data->pcie_ce_status = (uint32_t)reg | (uint32_t)(reg >> 32); } return (1); } static int fab_xlate_fire_ue(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, const char *class) { fab_fire_tbl_t *entry; uint64_t reg; uint32_t temp; pcie_tlp_hdr_t *hdr; for (entry = fab_fire_pec_ue_tbl; entry->err_class; entry++) { if (STRCMP(class, entry->err_class)) goto send; } return (0); send: fmd_hdl_debug(hdl, "Translate Fire UE %s\n", class); /* Fill in PCI Status Register */ data->pci_err_status = entry->pci_err_sts; data->pci_bdg_sec_stat = entry->pci_bdg_sts; /* Fill in the device status register */ if (entry->fire_bit & data->pcie_ue_sev) data->pcie_err_status = PCIE_DEVSTS_FE_DETECTED; else data->pcie_err_status = PCIE_DEVSTS_NFE_DETECTED; if (entry->fire_bit == PCIE_AER_UCE_UR) data->pcie_err_status |= PCIE_DEVSTS_UR_DETECTED; /* Fill in the AER UE register */ if (nvlist_lookup_uint64(erpt, "tlu-uess", ®) == 0) { data->pcie_ue_status = (uint32_t)reg | (uint32_t)(reg >> 32); } /* Fill in the AER Control register */ if ((reg & (uint64_t)entry->fire_bit) && nvlist_lookup_boolean(erpt, "primary")) { temp = entry->fire_bit; for (data->pcie_adv_ctl = (uint32_t)-1; temp; data->pcie_adv_ctl++) temp = temp >> 1; } /* If CTO create target information */ if (entry->fire_bit == PCIE_AER_UCE_TO && nvlist_lookup_boolean(erpt, "primary")) { if (nvlist_lookup_uint64(erpt, "tlu-tueh1l", ®) == 0) { data->pcie_ue_hdr[0] = (uint32_t)(reg >> 32); data->pcie_ue_hdr[1] = (uint32_t)(reg); } if (nvlist_lookup_uint64(erpt, "tlu-tueh2l", ®) == 0) { data->pcie_ue_hdr[2] = (uint32_t)(reg >> 32); data->pcie_ue_hdr[3] = (uint32_t)(reg); } hdr = (pcie_tlp_hdr_t *)(&data->pcie_ue_hdr[0]); switch (hdr->type) { case PCIE_TLP_TYPE_IO: case PCIE_TLP_TYPE_MEM: case PCIE_TLP_TYPE_MEMLK: data->pcie_ue_tgt_trans = PF_ADDR_PIO; if (hdr->fmt & 0x1) { data->pcie_ue_tgt_addr = reg; } else { data->pcie_ue_tgt_addr = data->pcie_ue_hdr[2]; } break; case PCIE_TLP_TYPE_CFG0: case PCIE_TLP_TYPE_CFG1: data->pcie_ue_tgt_trans = PF_ADDR_CFG; data->pcie_ue_tgt_bdf = data->pcie_ue_hdr[2] >> 16; break; } } /* Fill in the AER Header registers */ if (nvlist_lookup_uint64(erpt, "tlu-rueh1l", ®) == 0) { data->pcie_ue_hdr[0] = (uint32_t)(reg >> 32); data->pcie_ue_hdr[1] = (uint32_t)(reg); } if (nvlist_lookup_uint64(erpt, "tlu-rueh2l", ®) == 0) { data->pcie_ue_hdr[2] = (uint32_t)(reg >> 32); data->pcie_ue_hdr[3] = (uint32_t)(reg); } return (1); } static int fab_xlate_fire_oe(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, const char *class) { fab_fire_tbl_t *entry; uint64_t reg; for (entry = fab_fire_pec_oe_tbl; entry->err_class; entry++) { if (STRCMP(class, entry->err_class)) goto send; } return (0); send: fmd_hdl_debug(hdl, "Translate Fire OE %s\n", class); /* Fill in PCI Status Register */ if (entry->fire_bit) { data->pci_err_status = entry->pci_err_sts; data->pci_bdg_sec_stat = entry->pci_bdg_sts; } else { if (nvlist_lookup_uint64(erpt, "tlu-roeeh1l", ®) == 0) { data->pcie_ue_hdr[0] = (uint32_t)(reg >> 32); data->pcie_ue_hdr[1] = (uint32_t)(reg); } if (nvlist_lookup_uint64(erpt, "tlu-roeeh2l", ®) == 0) { data->pcie_ue_hdr[2] = (uint32_t)(reg >> 32); data->pcie_ue_hdr[3] = (uint32_t)(reg); } if (((pcie_tlp_hdr_t *)(&data->pcie_ue_hdr[0]))->type == PCIE_TLP_TYPE_CPL) { pcie_cpl_t *cpl = (pcie_cpl_t *)&data->pcie_ue_hdr[1]; switch (cpl->status) { case PCIE_CPL_STS_UR: data->pci_err_status = 0; data->pci_bdg_sec_stat = PCI_STAT_R_MAST_AB | PCI_STAT_S_SYSERR; break; case PCIE_CPL_STS_CA: data->pci_err_status = 0; data->pci_bdg_sec_stat = PCI_STAT_R_TARG_AB | PCI_STAT_S_SYSERR; break; } } } /* Fill in the device status register */ if (entry->fire_bit & data->pcie_ue_sev) data->pcie_err_status = PCIE_DEVSTS_FE_DETECTED; else data->pcie_err_status = PCIE_DEVSTS_NFE_DETECTED; /* Fill in the AER UE register */ data->pcie_ue_status = entry->fire_bit; return (1); } static int fab_xlate_fire_dmc(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt, const char *class) { fab_fire_tbl_t *entry; uint64_t reg; uint32_t temp; for (entry = fab_fire_dmc_tbl; entry->err_class; entry++) { fmd_hdl_debug(hdl, "Matching %s\n", entry->err_class); if (STRCMP(class, entry->err_class) && nvlist_lookup_boolean(erpt, "primary")) goto send; } return (0); send: fmd_hdl_debug(hdl, "Translate Fire DMC %s\n", class); /* Fill in PCI Status Register */ data->pci_err_status = entry->pci_err_sts; data->pci_bdg_sec_stat = entry->pci_bdg_sts; /* Fill in the device status register */ data->pcie_err_status = PCIE_DEVSTS_NFE_DETECTED; /* Fill in the AER UE register */ data->pcie_ue_status = entry->fire_bit; /* Fill in the AER Control register */ temp = entry->fire_bit; for (data->pcie_adv_ctl = (uint32_t)-1; temp; data->pcie_adv_ctl++) temp = temp >> 1; /* Fill in the AER Header registers */ if (nvlist_lookup_uint64(erpt, "mmu-tfsr", ®) == 0) { fmd_hdl_debug(hdl, "tfsr 0x%llx\n", reg); /* Get the trans type */ temp = (reg & 0x3F0000) >> 16; data->pcie_ue_hdr[0] = (uint32_t)(temp << 24); data->pcie_ue_tgt_trans = PF_ADDR_DMA; /* Get the req id */ temp = (reg & 0xFFFF); data->pcie_ue_hdr[1] = (uint32_t)(temp << 16); data->pcie_ue_tgt_bdf = temp; } if (nvlist_lookup_uint64(erpt, "mmu-tfar", ®) == 0) { fmd_hdl_debug(hdl, "tfar 0x%llx\n", reg); /* Get the address */ data->pcie_ue_hdr[2] = reg; data->pcie_ue_hdr[3] = 0; data->pcie_ue_tgt_addr = reg; } fmd_hdl_debug(hdl, "HEADER 0 0x%x\n", data->pcie_ue_hdr[0]); fmd_hdl_debug(hdl, "HEADER 1 0x%x\n", data->pcie_ue_hdr[1]); fmd_hdl_debug(hdl, "HEADER 2 0x%x\n", data->pcie_ue_hdr[2]); fmd_hdl_debug(hdl, "HEADER 3 0x%x\n", data->pcie_ue_hdr[3]); return (1); } void fab_xlate_fire_erpts(fmd_hdl_t *hdl, nvlist_t *nvl, const char *class) { fab_data_t data = {0}; fmd_hdl_debug(hdl, "Fire RC ereport received: %s\n", class); fab_fire_to_data(hdl, nvl, &data); if (fmd_nvl_class_match(hdl, nvl, "ereport.io.fire.pec.*")) { if (! fab_xlate_fire_ce(hdl, &data, nvl, class) && ! fab_xlate_fire_ue(hdl, &data, nvl, class)) (void) fab_xlate_fire_oe(hdl, &data, nvl, class); } else if (fmd_nvl_class_match(hdl, nvl, "ereport.io.fire.dmc.*") || fmd_nvl_class_match(hdl, nvl, "ereport.io.n2.dmu.*")) (void) fab_xlate_fire_dmc(hdl, &data, nvl, class); fab_xlate_pcie_erpts(hdl, &data); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. * Copyright 2025 Andreas Wacknitz */ #include #include #include #include #include #include #include #include "fabric-xlate.h" #define HAS_PROP(node, name) xmlHasProp(node, (const xmlChar *)name) #define GET_PROP(node, name) ((char *)xmlGetProp(node, (const xmlChar *)name)) #define FREE_PROP(prop) xmlFree((xmlChar *)prop) extern xmlXPathContextPtr fab_xpathCtx; /* ARGSUSED */ int fab_prep_basic_erpt(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t *erpt, boolean_t isRC) { uint64_t *now; uint64_t ena; uint_t nelem; nvlist_t *detector, *new_detector; char rcpath[255]; int err = 0; /* Grab the tod, ena and detector(FMRI) */ err |= nvlist_lookup_uint64_array(nvl, "__tod", &now, &nelem); err |= nvlist_lookup_uint64(nvl, "ena", &ena); err |= nvlist_lookup_nvlist(nvl, FM_EREPORT_DETECTOR, &detector); if (err) return (err); /* Make a copy of the detector */ err = nvlist_dup(detector, &new_detector, NV_UNIQUE_NAME); if (err) return (err); /* Copy the tod and ena to erpt */ (void) nvlist_add_uint64(erpt, FM_EREPORT_ENA, ena); (void) nvlist_add_uint64_array(erpt, "__tod", now, nelem); /* * Create the correct ROOT FMRI from PCIe leaf fabric ereports. Used * only by fab_prep_fake_rc_erpt. See the fab_pciex_fake_rc_erpt_tbl * comments for more information. */ if (isRC && fab_get_rcpath(hdl, nvl, rcpath)) { /* Create the correct PCIe RC new_detector aka FMRI */ (void) nvlist_remove(new_detector, FM_FMRI_DEV_PATH, DATA_TYPE_STRING); (void) nvlist_add_string(new_detector, FM_FMRI_DEV_PATH, rcpath); } /* Copy the FMRI to erpt */ (void) nvlist_add_nvlist(erpt, FM_EREPORT_DETECTOR, new_detector); nvlist_free(new_detector); return (err); } void fab_send_tgt_erpt(fmd_hdl_t *hdl, fab_data_t *data, const char *class, boolean_t isPrimary) { nvlist_t *nvl = data->nvl; nvlist_t *erpt; char *fmri = NULL; uint32_t tgt_trans; uint64_t tgt_addr; uint16_t tgt_bdf; if (isPrimary) { tgt_trans = data->pcie_ue_tgt_trans; tgt_addr = data->pcie_ue_tgt_addr; tgt_bdf = data->pcie_ue_tgt_bdf; } else { tgt_trans = data->pcie_sue_tgt_trans; tgt_addr = data->pcie_sue_tgt_addr; tgt_bdf = data->pcie_sue_tgt_bdf; } fmd_hdl_debug(hdl, "Sending Target Ereport: " "type 0x%x addr 0x%llx fltbdf 0x%x\n", tgt_trans, tgt_addr, tgt_bdf); if (!tgt_trans) return; if ((tgt_trans == PF_ADDR_PIO) && tgt_addr) fmri = fab_find_addr(hdl, nvl, tgt_addr); else if ((tgt_trans == PF_ADDR_CFG || (tgt_trans == PF_ADDR_DMA)) && tgt_bdf) fmri = fab_find_bdf(hdl, nvl, tgt_bdf); if (fmri) { uint64_t *now; uint64_t ena; uint_t nelem; nvlist_t *detector; int err = 0; /* Allocate space for new erpt */ if (nvlist_alloc(&erpt, NV_UNIQUE_NAME, 0) != 0) goto done; /* Generate the target ereport class */ (void) snprintf(fab_buf, FM_MAX_CLASS, "ereport.io.%s.%s", PCI_ERROR_SUBCLASS, class); (void) nvlist_add_string(erpt, FM_CLASS, fab_buf); /* Grab the tod, ena and detector(FMRI) */ err |= nvlist_lookup_uint64_array(nvl, "__tod", &now, &nelem); err |= nvlist_lookup_uint64(nvl, "ena", &ena); /* Copy the tod and ena to erpt */ (void) nvlist_add_uint64(erpt, FM_EREPORT_ENA, ena); (void) nvlist_add_uint64_array(erpt, "__tod", now, nelem); /* Create the correct FMRI */ if (nvlist_alloc(&detector, NV_UNIQUE_NAME, 0) != 0) { nvlist_free(erpt); goto done; } (void) nvlist_add_uint8(detector, FM_VERSION, FM_DEV_SCHEME_VERSION); (void) nvlist_add_string(detector, FM_FMRI_SCHEME, FM_FMRI_SCHEME_DEV); (void) nvlist_add_string(detector, FM_FMRI_DEV_PATH, fmri); (void) nvlist_add_nvlist(erpt, FM_EREPORT_DETECTOR, detector); nvlist_free(detector); /* Add the address payload */ (void) nvlist_add_uint64(erpt, PCI_PA, tgt_addr); fmd_hdl_debug(hdl, "Sending target ereport: %s 0x%x\n", fab_buf, tgt_addr); fmd_xprt_post(hdl, fab_fmd_xprt, erpt, 0); if (fmd_xprt_error(hdl, fab_fmd_xprt)) goto done; fmd_hdl_strfree(hdl, fmri); } else { fmd_hdl_debug(hdl, "Cannot find Target FMRI addr:0x%llx bdf 0x%x\n", tgt_addr, tgt_bdf); } return; done: if (fmri) xmlFree(fmri); fmd_hdl_debug(hdl, "Failed to send Target PCI ereport\n"); } void fab_send_erpt(fmd_hdl_t *hdl, fab_data_t *data, fab_err_tbl_t *tbl) { fab_erpt_tbl_t *erpt_tbl, *entry; nvlist_t *erpt; uint32_t reg; int err; erpt_tbl = tbl->erpt_tbl; if (tbl->reg_size == 16) { reg = (uint32_t)*((uint16_t *) ((uint32_t)data + tbl->reg_offset)); } else { reg = *((uint32_t *)((uint32_t)data + tbl->reg_offset)); } for (entry = erpt_tbl; entry->err_class; entry++) { if (!(reg & entry->reg_bit)) continue; if (nvlist_alloc(&erpt, NV_UNIQUE_NAME, 0) != 0) goto done; err = tbl->fab_prep(hdl, data, erpt, entry); if (err != 0 && err != PF_EREPORT_IGNORE) { fmd_hdl_debug(hdl, "Prepping ereport failed: " "class = %s\n", entry->err_class); nvlist_free(erpt); continue; } if (data->pcie_rp_send_all) { fab_send_erpt_all_rps(hdl, erpt); nvlist_free(erpt); return; } fmd_hdl_debug(hdl, "Sending ereport: %s 0x%x\n", fab_buf, reg); fmd_xprt_post(hdl, fab_fmd_xprt, erpt, 0); if (fmd_xprt_error(hdl, fab_fmd_xprt)) { fmd_hdl_debug(hdl, "Failed to send PCI ereport\n"); return; } } return; done: fmd_hdl_debug(hdl, "Failed to send PCI ereport\n"); } char * fab_xpath_query(fmd_hdl_t *hdl, const char *query) { xmlXPathObjectPtr xpathObj; xmlNodeSetPtr nodes; char *temp, *res; fmd_hdl_debug(hdl, "xpathObj query %s\n", query); xpathObj = xmlXPathEvalExpression((const xmlChar *)query, fab_xpathCtx); if (xpathObj == NULL) return (NULL); fmd_hdl_debug(hdl, "xpathObj 0x%p type %d\n", xpathObj, xpathObj->type); nodes = xpathObj->nodesetval; if (nodes != NULL && nodes->nodeTab != NULL && nodes->nodeTab[0] != NULL) { temp = (char *)xmlNodeGetContent(nodes->nodeTab[0]); if (temp != NULL) { fmd_hdl_debug(hdl, "query result: %s\n", temp); res = fmd_hdl_strdup(hdl, temp, FMD_SLEEP); xmlFree(temp); xmlXPathFreeObject(xpathObj); return (res); } } xmlXPathFreeObject(xpathObj); return (NULL); } #define FAB_HC2DEV_QUERY_SIZE_MIN 160 #define FAB_HC2DEV_QUERY_SIZE(sz) \ ((sz + FAB_HC2DEV_QUERY_SIZE_MIN) * sizeof (char)) /* * hc_path is in form of "/motherboard=0/hostbridge=0/pciexrc=0" */ boolean_t fab_hc2dev(fmd_hdl_t *hdl, const char *hc_path, char **dev_path) { char *query; uint_t len = FAB_HC2DEV_QUERY_SIZE_MIN + strlen(hc_path); query = fmd_hdl_alloc(hdl, len, FMD_SLEEP); (void) snprintf(query, len, "//propval[@name='resource' and contains(" "substring(@value, string-length(@value) - %d + 1), '%s')]" "/parent::*/following-sibling::*/propval[@name='dev']/@value", strlen(hc_path) + 1, hc_path); *dev_path = fab_xpath_query(hdl, query); fmd_hdl_free(hdl, query, len); return (*dev_path != NULL); } static boolean_t fab_hc_path(fmd_hdl_t *hdl, nvlist_t *detector, char **hcpath, size_t *lenp) { char c, *name, *id, *buf; uint_t i, size; nvlist_t **hcl; size_t len = 0, buf_size = 0; if (nvlist_lookup_nvlist_array(detector, FM_FMRI_HC_LIST, &hcl, &size) != 0) return (B_FALSE); for (i = 0; i < size; i++) { if (nvlist_lookup_string(hcl[i], FM_FMRI_HC_NAME, &name) != 0) return (B_FALSE); if (nvlist_lookup_string(hcl[i], FM_FMRI_HC_ID, &id) != 0) return (B_FALSE); buf_size += snprintf(&c, 1, "/%s=%s", name, id); } buf_size++; buf = fmd_hdl_alloc(hdl, buf_size, FMD_SLEEP); for (i = 0; i < size; i++) { (void) nvlist_lookup_string(hcl[i], FM_FMRI_HC_NAME, &name); (void) nvlist_lookup_string(hcl[i], FM_FMRI_HC_ID, &id); len += snprintf(buf + len, buf_size - len, "/%s=%s", name, id); } *hcpath = buf; *lenp = buf_size; return (B_TRUE); } boolean_t fab_hc2dev_nvl(fmd_hdl_t *hdl, nvlist_t *detector, char **dev_path) { char *hcl; size_t len; if (! fab_hc_path(hdl, detector, &hcl, &len)) return (B_FALSE); (void) fab_hc2dev(hdl, hcl, dev_path); fmd_hdl_free(hdl, hcl, len); return (*dev_path != NULL); } boolean_t fab_get_hcpath(fmd_hdl_t *hdl, nvlist_t *nvl, char **hcpath, size_t *len) { nvlist_t *detector; char *scheme; if (nvlist_lookup_nvlist(nvl, FM_EREPORT_DETECTOR, &detector) != 0 || nvlist_lookup_string(detector, FM_FMRI_SCHEME, &scheme) != 0 || ! STRCMP(scheme, FM_FMRI_SCHEME_HC)) return (B_FALSE); return (fab_hc_path(hdl, detector, hcpath, len)); } char * fab_find_rppath_by_df(fmd_hdl_t *hdl, nvlist_t *nvl, uint8_t df) { char query[500]; char str[10]; char *hcpath; size_t len; (void) snprintf(str, sizeof (str), "%0hhx", df); /* * get the string form of the hc detector, eg * /chassis=0/motherboard=0/hostbridge=0 */ if (!fab_get_hcpath(hdl, nvl, &hcpath, &len)) return (NULL); /* * Explanation of the XSL XPATH Query * Line 1: Look at all nodes with the node name "propval" * Line 2: See if the "BDF" of the node matches DF * Line 3-4: See if the the node is pciexrc * Line 5-6: See if the "ASRU" contains root complex * Line 7-8: Go up one level and get prop value of io/dev */ (void) snprintf(query, sizeof (query), "//propval[" "@name='BDF' and contains(substring(@value, " "string-length(@value) - 1), '%s')]" "/parent::*/parent::*/propgroup[@name='pci']/propval" "[@name='extended-capabilities' and @value='%s']" "/parent::*/parent::*/propgroup[@name='protocol']" "/propval[@name='resource' and contains(@value, '%s')]" "/parent::*/parent::*/propgroup[@name='io']" "/propval[@name='dev']/@value", str, PCIEX_ROOT, hcpath); fmd_hdl_free(hdl, hcpath, len); return (fab_xpath_query(hdl, query)); } char * fab_find_rppath_by_devbdf(fmd_hdl_t *hdl, nvlist_t *nvl, pcie_req_id_t bdf) { xmlXPathObjectPtr xpathObj; xmlNodeSetPtr nodes; xmlNodePtr devNode; char *retval, *temp; char query[500]; int i, size, bus, dev, fn; char *hcpath; size_t len; if (bdf != (uint16_t)-1) { bus = (bdf & PCIE_REQ_ID_BUS_MASK) >> PCIE_REQ_ID_BUS_SHIFT; dev = (bdf & PCIE_REQ_ID_DEV_MASK) >> PCIE_REQ_ID_DEV_SHIFT; fn = (bdf & PCIE_REQ_ID_FUNC_MASK) >> PCIE_REQ_ID_FUNC_SHIFT; } /* * get the string form of the hc detector, eg * /chassis=0/motherboard=0/hostbridge=0 */ if (!fab_get_hcpath(hdl, nvl, &hcpath, &len)) goto fail; /* * Explanation of the XSL XPATH Query * Line 1: Look at all nodes with the node name "propval" * Line 2-3: See if the "value" of the node ends with correct PCIEx BDF * Line 4-5: See if the "value" of the node ends with correct PCI BDF * Line 6: Go up one level to the parent of the current node * Line 7: See if child node contains "ASRU" with the same PCIe Root * Line 8: Go up see all the ancestors */ (void) snprintf(query, sizeof (query), "//propval[" "contains(substring(@value, string-length(@value) - 34), " "'pciexbus=%d/pciexdev=%d/pciexfn=%d') or " "contains(substring(@value, string-length(@value) - 28), " "'pcibus=%d/pcidev=%d/pcifn=%d')" "]/parent::" "*/propval[@name='resource' and contains(@value, '%s')]" "/ancestor::*", bus, dev, fn, bus, dev, fn, hcpath); fmd_hdl_free(hdl, hcpath, len); fmd_hdl_debug(hdl, "xpathObj query %s\n", query); xpathObj = xmlXPathEvalExpression((const xmlChar *)query, fab_xpathCtx); if (xpathObj == NULL) goto fail; nodes = xpathObj->nodesetval; size = (nodes) ? nodes->nodeNr : 0; fmd_hdl_debug(hdl, "xpathObj 0x%p type %d size %d\n", xpathObj, xpathObj->type, size); for (i = 0; i < size; i++) { devNode = nodes->nodeTab[i]; if (STRCMP(devNode->name, "range") && HAS_PROP(devNode, "name")) { char *tprop = GET_PROP(devNode, "name"); /* find "range name='pciexrc'" in ancestors */ if (STRCMP(tprop, PCIEX_ROOT)) { /* go down to the pciexrc instance node */ FREE_PROP(tprop); devNode = nodes->nodeTab[i+1]; goto found; } FREE_PROP(tprop); } } goto fail; found: /* Traverse down the xml tree to find the right propgroup */ for (devNode = devNode->children; devNode; devNode = devNode->next) { if (STRCMP(devNode->name, "propgroup")) { char *tprop = GET_PROP(devNode, "name"); if (STRCMP(tprop, "io")) { FREE_PROP(tprop); goto propgroup; } FREE_PROP(tprop); } } goto fail; propgroup: /* Retrive the "dev" propval and return */ for (devNode = devNode->children; devNode; devNode = devNode->next) { if (STRCMP(devNode->name, "propval")) { char *tprop = GET_PROP(devNode, "name"); if (STRCMP(tprop, "dev")) { temp = GET_PROP(devNode, "value"); retval = fmd_hdl_strdup(hdl, temp, FMD_SLEEP); fmd_hdl_debug(hdl, "RP Path: %s\n", retval); xmlFree(temp); xmlXPathFreeObject(xpathObj); } FREE_PROP(tprop); return (retval); } } fail: if (xpathObj != NULL) xmlXPathFreeObject(xpathObj); return (NULL); } char * fab_find_rppath_by_devpath(fmd_hdl_t *hdl, const char *devpath) { char query[500]; /* * Explanation of the XSL XPATH Query * Line 1: Look at all nodes with the node name "propval" * Line 2: See if the node is pciexrc * Line 3: Go up to the io pgroup * Line 4: See if the "dev" prop is parent of devpath * Line 5: Get the 'dev' prop */ (void) snprintf(query, sizeof (query), "//propval" "[@name='extended-capabilities' and @value='%s']" "/parent::*/parent::*/propgroup[@name='io']" "/propval[@name='dev' and starts-with('%s', concat(@value, '/'))]" "/@value", PCIEX_ROOT, devpath); return (fab_xpath_query(hdl, query)); } /* ARGSUSED */ boolean_t fab_get_rcpath(fmd_hdl_t *hdl, nvlist_t *nvl, char *rcpath) { nvlist_t *detector; char *path, *scheme; if (nvlist_lookup_nvlist(nvl, FM_EREPORT_DETECTOR, &detector) != 0) goto fail; if (nvlist_lookup_string(detector, FM_FMRI_SCHEME, &scheme) != 0) goto fail; if (STRCMP(scheme, FM_FMRI_SCHEME_DEV)) { if (nvlist_lookup_string(detector, FM_FMRI_DEV_PATH, &path) != 0) goto fail; (void) strncpy(rcpath, path, FM_MAX_CLASS); } else if (STRCMP(scheme, FM_FMRI_SCHEME_HC)) { /* * This should only occur for ereports that come from the RC * itself. In this case convert HC scheme to dev path. */ if (fab_hc2dev_nvl(hdl, detector, &path)) { (void) strncpy(rcpath, path, FM_MAX_CLASS); fmd_hdl_strfree(hdl, path); } else { goto fail; } } else { return (B_FALSE); } /* * Extract the RC path by taking the first device in the dev path * * /pci@0,0/pci8086,3605@2/pci8086,3500@0/pci8086,3514@1/pci8086,105e@0 * - to - * /pci@0,0 */ path = strchr(rcpath + 1, '/'); if (path) path[0] = '\0'; return (B_TRUE); fail: return (B_FALSE); } char * fab_find_bdf(fmd_hdl_t *hdl, nvlist_t *nvl, pcie_req_id_t bdf) { char *retval; char query[500]; int bus, dev, fn; char rcpath[255]; if (bdf != (uint16_t)-1) { bus = (bdf & PCIE_REQ_ID_BUS_MASK) >> PCIE_REQ_ID_BUS_SHIFT; dev = (bdf & PCIE_REQ_ID_DEV_MASK) >> PCIE_REQ_ID_DEV_SHIFT; fn = (bdf & PCIE_REQ_ID_FUNC_MASK) >> PCIE_REQ_ID_FUNC_SHIFT; } if (!fab_get_rcpath(hdl, nvl, rcpath)) goto fail; /* * Explanation of the XSL XPATH Query * Line 1: Look at all nodes with the node name "propval" * Line 2-3: See if the "value" of the node ends with correct PCIEx BDF * Line 4-5: See if the "value" of the node ends with correct PCI BDF * Line 6: Go up one level to the parent of the current node * Line 7: See if child node contains "ASRU" with the same PCIe Root * Line 8: Traverse up the parent and the other siblings and look for * the io "propgroup" and get the value of the dev "propval" */ (void) snprintf(query, sizeof (query), "//propval[" "contains(substring(@value, string-length(@value) - 34), " "'pciexbus=%d/pciexdev=%d/pciexfn=%d') or " "contains(substring(@value, string-length(@value) - 28), " "'pcibus=%d/pcidev=%d/pcifn=%d')" "]/parent::" "*/propval[@name='ASRU' and contains(@value, '%s')]" "/parent::*/following-sibling::*[@name='io']/propval[@name='dev']/" "@value", bus, dev, fn, bus, dev, fn, rcpath); retval = fab_xpath_query(hdl, query); if (retval) { fmd_hdl_debug(hdl, "BDF Dev Path: %s\n", retval); return (retval); } fail: return (NULL); } char * fab_find_addr(fmd_hdl_t *hdl, nvlist_t *nvl, uint64_t addr) { xmlXPathObjectPtr xpathObj; xmlNodeSetPtr nodes; xmlNodePtr devNode; char *retval, *temp; char query[500]; int size, i, j; uint32_t prop[50]; char *token; pci_regspec_t *assign_p; uint64_t low, hi; char rcpath[255]; if (!fab_get_rcpath(hdl, nvl, rcpath)) goto fail; (void) snprintf(query, sizeof (query), "//propval[" "@name='ASRU' and contains(@value, '%s')]/" "parent::*/following-sibling::*[@name='pci']/" "propval[@name='assigned-addresses']", rcpath); fmd_hdl_debug(hdl, "xpathObj query %s\n", query); xpathObj = xmlXPathEvalExpression((const xmlChar *)query, fab_xpathCtx); if (xpathObj == NULL) goto fail; fmd_hdl_debug(hdl, "xpathObj 0x%p type %d\n", xpathObj, xpathObj->type); nodes = xpathObj->nodesetval; size = (nodes) ? nodes->nodeNr : 0; /* Decode the list of assigned addresses xml nodes for each device */ for (i = 0; i < size; i++) { char *tprop; devNode = nodes->nodeTab[i]; if (!HAS_PROP(devNode, "value")) continue; /* Convert "string" assigned-addresses to pci_regspec_t */ j = 0; tprop = GET_PROP(devNode, "value"); for (token = strtok(tprop, " "); token; token = strtok(NULL, " ")) { prop[j++] = strtoul(token, (char **)NULL, 16); } prop[j] = (uint32_t)-1; FREE_PROP(tprop); /* Check if address belongs to this device */ for (assign_p = (pci_regspec_t *)prop; assign_p->pci_phys_hi != (uint_t)-1; assign_p++) { low = assign_p->pci_phys_low; hi = low + assign_p->pci_size_low; if ((addr < hi) && (addr >= low)) { fmd_hdl_debug(hdl, "Found Address\n"); goto found; } } } goto fail; found: /* Traverse up the xml tree and back down to find the right propgroup */ for (devNode = devNode->parent->parent->children; devNode; devNode = devNode->next) { char *tprop; tprop = GET_PROP(devNode, "name"); if (STRCMP(devNode->name, "propgroup") && STRCMP(tprop, "io")) { FREE_PROP(tprop); goto propgroup; } FREE_PROP(tprop); } goto fail; propgroup: /* Retrive the "dev" propval and return */ for (devNode = devNode->children; devNode; devNode = devNode->next) { char *tprop; tprop = GET_PROP(devNode, "name"); if (STRCMP(devNode->name, "propval") && STRCMP(tprop, "dev")) { FREE_PROP(tprop); temp = GET_PROP(devNode, "value"); retval = fmd_hdl_strdup(hdl, temp, FMD_SLEEP); fmd_hdl_debug(hdl, "Addr Dev Path: %s\n", retval); xmlFree(temp); xmlXPathFreeObject(xpathObj); return (retval); } FREE_PROP(tprop); } fail: if (xpathObj != NULL) xmlXPathFreeObject(xpathObj); return (NULL); } void fab_pr(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl) { nvpair_t *nvp; for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(nvl, nvp)) { data_type_t type = nvpair_type(nvp); const char *name = nvpair_name(nvp); boolean_t b; uint8_t i8; uint16_t i16; uint32_t i32; uint64_t i64; char *str; nvlist_t *cnv; nvlist_t **nvlarr; uint_t arrsize; int arri; if (STRCMP(name, FM_CLASS)) continue; /* already printed by caller */ fmd_hdl_debug(hdl, " %s=", name); switch (type) { case DATA_TYPE_BOOLEAN: fmd_hdl_debug(hdl, "DATA_TYPE_BOOLEAN 1"); break; case DATA_TYPE_BOOLEAN_VALUE: (void) nvpair_value_boolean_value(nvp, &b); fmd_hdl_debug(hdl, "DATA_TYPE_BOOLEAN_VALUE %d", b ? "1" : "0"); break; case DATA_TYPE_BYTE: (void) nvpair_value_byte(nvp, &i8); fmd_hdl_debug(hdl, "DATA_TYPE_BYTE 0x%x", i8); break; case DATA_TYPE_INT8: (void) nvpair_value_int8(nvp, (void *)&i8); fmd_hdl_debug(hdl, "DATA_TYPE_INT8 0x%x", i8); break; case DATA_TYPE_UINT8: (void) nvpair_value_uint8(nvp, &i8); fmd_hdl_debug(hdl, "DATA_TYPE_UINT8 0x%x", i8); break; case DATA_TYPE_INT16: (void) nvpair_value_int16(nvp, (void *)&i16); fmd_hdl_debug(hdl, "DATA_TYPE_INT16 0x%x", i16); break; case DATA_TYPE_UINT16: (void) nvpair_value_uint16(nvp, &i16); fmd_hdl_debug(hdl, "DATA_TYPE_UINT16 0x%x", i16); break; case DATA_TYPE_INT32: (void) nvpair_value_int32(nvp, (void *)&i32); fmd_hdl_debug(hdl, "DATA_TYPE_INT32 0x%x", i32); break; case DATA_TYPE_UINT32: (void) nvpair_value_uint32(nvp, &i32); fmd_hdl_debug(hdl, "DATA_TYPE_UINT32 0x%x", i32); break; case DATA_TYPE_INT64: (void) nvpair_value_int64(nvp, (void *)&i64); fmd_hdl_debug(hdl, "DATA_TYPE_INT64 0x%llx", (u_longlong_t)i64); break; case DATA_TYPE_UINT64: (void) nvpair_value_uint64(nvp, &i64); fmd_hdl_debug(hdl, "DATA_TYPE_UINT64 0x%llx", (u_longlong_t)i64); break; case DATA_TYPE_HRTIME: (void) nvpair_value_hrtime(nvp, (void *)&i64); fmd_hdl_debug(hdl, "DATA_TYPE_HRTIME 0x%llx", (u_longlong_t)i64); break; case DATA_TYPE_STRING: (void) nvpair_value_string(nvp, &str); fmd_hdl_debug(hdl, "DATA_TYPE_STRING \"%s\"", str ? str : ""); break; case DATA_TYPE_NVLIST: fmd_hdl_debug(hdl, "["); (void) nvpair_value_nvlist(nvp, &cnv); fab_pr(hdl, NULL, cnv); fmd_hdl_debug(hdl, " ]"); break; case DATA_TYPE_BOOLEAN_ARRAY: case DATA_TYPE_BYTE_ARRAY: case DATA_TYPE_INT8_ARRAY: case DATA_TYPE_UINT8_ARRAY: case DATA_TYPE_INT16_ARRAY: case DATA_TYPE_UINT16_ARRAY: case DATA_TYPE_INT32_ARRAY: case DATA_TYPE_UINT32_ARRAY: case DATA_TYPE_INT64_ARRAY: case DATA_TYPE_UINT64_ARRAY: case DATA_TYPE_STRING_ARRAY: fmd_hdl_debug(hdl, "[...]"); break; case DATA_TYPE_NVLIST_ARRAY: arrsize = 0; (void) nvpair_value_nvlist_array(nvp, &nvlarr, &arrsize); for (arri = 0; arri < arrsize; arri++) { fab_pr(hdl, ep, nvlarr[arri]); } break; case DATA_TYPE_UNKNOWN: fmd_hdl_debug(hdl, ""); break; } } } char * fab_get_rpdev(fmd_hdl_t *hdl) { char *retval; char query[500]; (void) snprintf(query, sizeof (query), "//propval[" "@name='extended-capabilities' and contains(@value, '%s')]" "/parent::*/parent::*/propgroup[@name='io']" "/propval[@name='dev']/@value", PCIEX_ROOT); retval = fab_xpath_query(hdl, query); if (retval) { fmd_hdl_debug(hdl, "Root port path is %s\n", retval); return (retval); } return (NULL); } void fab_send_erpt_all_rps(fmd_hdl_t *hdl, nvlist_t *erpt) { xmlXPathObjectPtr xpathObj; xmlNodeSetPtr nodes; char *rppath, *hbpath; char query[600]; nvlist_t *detector, *nvl; uint_t i, size; size_t len; /* get hostbridge's path */ if (!fab_get_hcpath(hdl, erpt, &hbpath, &len)) { fmd_hdl_debug(hdl, "fab_send_erpt_on_all_rps: fab_get_hcpath() failed.\n"); return; } (void) snprintf(query, sizeof (query), "//propval[" "@name='extended-capabilities' and contains(@value, '%s')]" "/parent::*/parent::*/propgroup[@name='protocol']" "/propval[@name='resource' and contains(@value, '%s/')" "]/parent::*/parent::*/propgroup[@name='io']" "/propval[@name='dev']/@value", PCIEX_ROOT, hbpath); fmd_hdl_free(hdl, hbpath, len); fmd_hdl_debug(hdl, "xpathObj query %s\n", query); xpathObj = xmlXPathEvalExpression((const xmlChar *)query, fab_xpathCtx); if (xpathObj == NULL) return; nodes = xpathObj->nodesetval; size = (nodes) ? nodes->nodeNr : 0; fmd_hdl_debug(hdl, "xpathObj 0x%p type %d size %d\n", xpathObj, xpathObj->type, size); for (i = 0; i < size; i++) { rppath = (char *)xmlNodeGetContent(nodes->nodeTab[i]); fmd_hdl_debug(hdl, "query result: %s\n", rppath); nvl = detector = NULL; if (nvlist_dup(erpt, &nvl, NV_UNIQUE_NAME) != 0 || nvlist_alloc(&detector, NV_UNIQUE_NAME, 0) != 0) { xmlFree(rppath); nvlist_free(nvl); continue; } /* * set the detector in the original ereport to the root port */ (void) nvlist_add_string(detector, FM_VERSION, FM_DEV_SCHEME_VERSION); (void) nvlist_add_string(detector, FM_FMRI_SCHEME, FM_FMRI_SCHEME_DEV); (void) nvlist_add_string(detector, FM_FMRI_DEV_PATH, rppath); (void) nvlist_remove_all(nvl, FM_EREPORT_DETECTOR); (void) nvlist_add_nvlist(nvl, FM_EREPORT_DETECTOR, detector); nvlist_free(detector); xmlFree(rppath); fmd_hdl_debug(hdl, "Sending ereport: %s\n", fab_buf); fmd_xprt_post(hdl, fab_fmd_xprt, nvl, 0); if (fmd_xprt_error(hdl, fab_fmd_xprt)) fmd_hdl_debug(hdl, "Failed to send PCI ereport\n"); } xmlXPathFreeObject(xpathObj); } # # 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2022 Garrett D'Amore # MODULE = fdd-msg CLASS = common SRCS = fdd_msg.c include ../../Makefile.plugin LDLIBS_i386 = -L$(ROOT)/usr/lib/fm -lipmi -lumem -lfmd_agent LDFLAGS_i386 = -R/usr/lib/fm LDLIBS += $(LDLIBS_$(MACH)) LDFLAGS += $(LDFLAGS_$(MACH)) # # 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # setprop default_fma_cap 0x3 # vendor=GenuineIntel, family=6, model=26 setprop NHMEP_fma_cap 0x3 # vendor=GenuineIntel, family=6, model=46 setprop NHMEX_fma_cap 0x2 # vendor=GenuineIntel, family=6, model=44 setprop WSMEP_fma_cap 0x3 # vendor=GenuineIntel, family=6, model=47 setprop INTLN_fma_cap 0x2 /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * FMA capability messenger * * fdd-msg module is called once when fmd starts up. It does the following * based on different scenarios * * 1. If it's on a x86 platform, fdd-msg module sends fdd running on service * processor a message (ILOM) which indicates the Solaris host FMA capability. * The message is sent via the BMC driver (KCS interface) to the IPMI stack * of ILOM using the IPMI Sun OEM core tunnel command. The sub-command is * CORE_TUNNEL_SUBCMD_HOSTCAP. The IPMI stack posts an host FMA capability * event to the event manager upon receiving this message. fdd subscribes to * the event manager for this event. Upon receving this event, fdd will adjust * its configuration. * * 2. If it's on a Sparc platform, fdd-msg module just exit for now. */ #include #include #include #include #include #include #include #if defined(__x86) #include #include #include #endif #define CMD_SUNOEM_CORE_TUNNEL 0x44 #define CORE_TUNNEL_SUBCMD_HOSTFMACAP 2 #define OEM_DATA_LENGTH 3 #define VERSION 0x10 #if defined(__x86) typedef struct cpu_tbl { char vendor[X86_VENDOR_STRLEN]; int32_t family; int32_t model; char *propname; } cpu_tbl_t; static cpu_tbl_t fma_cap_list[] = { {"GenuineIntel", 6, 26, "NHMEP_fma_cap"}, {"GenuineIntel", 6, 46, "NHMEX_fma_cap"}, {"GenuineIntel", 6, 44, "WSMEP_fma_cap"}, {"GenuineIntel", 6, 47, "INTLN_fma_cap"}, {0, 0, 0, 0} }; #endif static int check_sunoem(ipmi_handle_t *ipmi_hdl) { ipmi_deviceid_t *devid; if ((devid = ipmi_get_deviceid(ipmi_hdl)) == NULL) return (-1); if (!ipmi_is_sun_ilom(devid)) return (-2); return (0); } #if defined(__x86) static int32_t fma_cap_cpu_info(cpu_tbl_t *ci) { nvlist_t **cpus, *nvl; uint_t ncpu, i; fmd_agent_hdl_t *hdl; char *ven; int32_t family, model; if ((hdl = fmd_agent_open(FMD_AGENT_VERSION)) == NULL) return (-1); if (fmd_agent_physcpu_info(hdl, &cpus, &ncpu) != 0) { fmd_agent_close(hdl); return (-1); } fmd_agent_close(hdl); if (cpus == NULL) return (-1); /* * There is no mixed CPU type on x86 systems, it's ok to * just pick the first one */ nvl = cpus[0]; if (nvlist_lookup_string(nvl, FM_PHYSCPU_INFO_VENDOR_ID, &ven) != 0 || nvlist_lookup_int32(nvl, FM_PHYSCPU_INFO_FAMILY, &family) != 0 || nvlist_lookup_int32(nvl, FM_PHYSCPU_INFO_MODEL, &model) != 0) { for (i = 0; i < ncpu; i++) nvlist_free(cpus[i]); umem_free(cpus, sizeof (nvlist_t *) * ncpu); return (-1); } (void) snprintf(ci->vendor, X86_VENDOR_STRLEN, "%s", ven); ci->family = family; ci->model = model; for (i = 0; i < ncpu; i++) nvlist_free(cpus[i]); umem_free(cpus, sizeof (nvlist_t *) * ncpu); return (0); } #endif static uint32_t get_cap_conf(fmd_hdl_t *hdl) { uint32_t fma_cap; #if defined(__x86) int found = 0; cpu_tbl_t *cl, ci; if (fma_cap_cpu_info(&ci) == 0) { fmd_hdl_debug(hdl, "Got CPU info: vendor=%s, family=%d, " "model=%d\n", ci.vendor, ci.family, ci.model); for (cl = fma_cap_list; cl->propname != NULL; cl++) { if (strncmp(ci.vendor, cl->vendor, X86_VENDOR_STRLEN) == 0 && ci.family == cl->family && ci.model == cl->model) { found++; break; } } } else { fmd_hdl_debug(hdl, "Failed to get CPU info"); } if (found) { fma_cap = fmd_prop_get_int32(hdl, cl->propname); fmd_hdl_debug(hdl, "Found property, FMA capability=0x%x", fma_cap); } else { #endif fma_cap = fmd_prop_get_int32(hdl, "default_fma_cap"); fmd_hdl_debug(hdl, "Didn't find FMA capability property, " "use default=0x%x", fma_cap); #if defined(__x86) } #endif return (fma_cap); } static void send_fma_cap_to_ilom(fmd_hdl_t *hdl, uint32_t fma_cap) { int error; char *msg; ipmi_handle_t *ipmi_hdl; ipmi_cmd_t cmd; uint8_t oem_data[OEM_DATA_LENGTH]; if ((ipmi_hdl = ipmi_open(&error, &msg, IPMI_TRANSPORT_BMC, NULL)) == NULL) { /* * If /dev/ipmi0 doesn't exist on the system, then return * without doing anything. */ if (error != EIPMI_BMC_OPEN_FAILED) fmd_hdl_abort(hdl, "Failed to initialize IPMI " "connection: %s\n", msg); fmd_hdl_debug(hdl, "Failed: no IPMI connection present"); return; } /* * Check if it's Sun ILOM */ if (check_sunoem(ipmi_hdl) != 0) { fmd_hdl_debug(hdl, "Service Processor does not run " "Sun ILOM"); ipmi_close(ipmi_hdl); return; } oem_data[0] = CORE_TUNNEL_SUBCMD_HOSTFMACAP; oem_data[1] = VERSION; oem_data[2] = fma_cap; cmd.ic_netfn = IPMI_NETFN_OEM; cmd.ic_lun = 0; cmd.ic_cmd = CMD_SUNOEM_CORE_TUNNEL; cmd.ic_dlen = OEM_DATA_LENGTH; cmd.ic_data = oem_data; if (ipmi_send(ipmi_hdl, &cmd) == NULL) { fmd_hdl_debug(hdl, "Failed to send Solaris FMA " "capability to ilom: %s", ipmi_errmsg(ipmi_hdl)); } ipmi_close(ipmi_hdl); } /*ARGSUSED*/ static void fma_cap_init(fmd_hdl_t *hdl, id_t id, void *data) { uint32_t fma_cap; fma_cap = get_cap_conf(hdl); send_fma_cap_to_ilom(hdl, fma_cap); fmd_hdl_unregister(hdl); } static const fmd_hdl_ops_t fmd_ops = { NULL, /* fmdo_recv */ fma_cap_init, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ NULL, /* fmdo_topo */ }; static const fmd_prop_t fmd_props[] = { { "interval", FMD_TYPE_TIME, "1s" }, { "default_fma_cap", FMD_TYPE_UINT32, "0x3" }, { "NHMEP_fma_cap", FMD_TYPE_UINT32, "0x3" }, { "NHMEX_fma_cap", FMD_TYPE_UINT32, "0x2" }, { "WSMEP_fma_cap", FMD_TYPE_UINT32, "0x3" }, { "INTLN_fma_cap", FMD_TYPE_UINT32, "0x2" }, { NULL, 0, NULL } }; static const fmd_hdl_info_t fmd_info = { "FMA Capability Messenger", "1.1", &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { char isa[8]; /* * For now the module only sends message to ILOM on i386 platforms * till CR 6933053 is fixed. Module unregister may cause etm module * core dump due to 6933053. */ if ((sysinfo(SI_ARCHITECTURE, isa, sizeof (isa)) == -1) || (strcmp(isa, "amd64") != 0 && strncmp(isa, "i386", 4) != 0)) return; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; /* * Setup the timer. */ (void) fmd_timer_install(hdl, NULL, NULL, 2000000000ULL); } /*ARGSUSED*/ void _fmd_fini(fmd_hdl_t *hdl) { } # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # MODULE = io-retire CLASS = common SRCS = rio_main.c include ../../Makefile.plugin LDLIBS += -ldevinfo CERRWARN += -Wno-parentheses # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # fmd configuration file for the io-retire.so agent. # setprop global-disable false subscribe fault.io.* subscribe defect.io.* subscribe defect.ultraSPARC-II.memory.nodiag /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include static int global_disable; struct except_list { char *el_fault; struct except_list *el_next; }; static struct except_list *except_list; static void parse_exception_string(fmd_hdl_t *hdl, char *estr) { char *p; char *next; size_t len; struct except_list *elem; len = strlen(estr); p = estr; for (;;) { /* Remove leading ':' */ while (*p == ':') p++; if (*p == '\0') break; next = strchr(p, ':'); if (next) *next = '\0'; elem = fmd_hdl_alloc(hdl, sizeof (struct except_list), FMD_SLEEP); elem->el_fault = fmd_hdl_strdup(hdl, p, FMD_SLEEP); elem->el_next = except_list; except_list = elem; if (next) { *next = ':'; p = next + 1; } else { break; } } if (len != strlen(estr)) { fmd_hdl_abort(hdl, "Error parsing exception list: %s\n", estr); } } /* * Returns * 1 if fault on exception list * 0 otherwise */ static int fault_exception(fmd_hdl_t *hdl, nvlist_t *fault) { struct except_list *elem; for (elem = except_list; elem; elem = elem->el_next) { if (fmd_nvl_class_match(hdl, fault, elem->el_fault)) { fmd_hdl_debug(hdl, "rio_recv: Skipping fault " "on exception list (%s)\n", elem->el_fault); return (1); } } return (0); } static void free_exception_list(fmd_hdl_t *hdl) { struct except_list *elem; while (except_list) { elem = except_list; except_list = elem->el_next; fmd_hdl_strfree(hdl, elem->el_fault); fmd_hdl_free(hdl, elem, sizeof (*elem)); } } /*ARGSUSED*/ static void rio_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { nvlist_t **faults = NULL; nvlist_t *asru; uint_t nfaults = 0; int f; char *path; char *uuid; char *scheme; di_retire_t drt = {0}; int retire; int rval = 0; int valid_suspect = 0; int error; char *snglfault = FM_FAULT_CLASS"."FM_ERROR_IO"."; boolean_t rtr; /* * If disabled, we don't do retire. We still do unretires though */ if (global_disable && (strcmp(class, FM_LIST_SUSPECT_CLASS) == 0 || strcmp(class, FM_LIST_UPDATED_CLASS) == 0)) { fmd_hdl_debug(hdl, "rio_recv: retire disabled\n"); return; } drt.rt_abort = (void (*)(void *, const char *, ...))fmd_hdl_abort; drt.rt_debug = (void (*)(void *, const char *, ...))fmd_hdl_debug; drt.rt_hdl = hdl; if (strcmp(class, FM_LIST_SUSPECT_CLASS) == 0) { retire = 1; } else if (strcmp(class, FM_LIST_REPAIRED_CLASS) == 0) { retire = 0; } else if (strcmp(class, FM_LIST_UPDATED_CLASS) == 0) { retire = 0; } else if (strcmp(class, FM_LIST_RESOLVED_CLASS) == 0) { return; } else if (strncmp(class, snglfault, strlen(snglfault)) == 0) { retire = 1; faults = &nvl; nfaults = 1; } else { fmd_hdl_debug(hdl, "rio_recv: not list.* class: %s\n", class); return; } if (nfaults == 0 && nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST, &faults, &nfaults) != 0) { fmd_hdl_debug(hdl, "rio_recv: no fault list"); return; } for (f = 0; f < nfaults; f++) { if (nvlist_lookup_boolean_value(faults[f], FM_SUSPECT_RETIRE, &rtr) == 0 && !rtr) { fmd_hdl_debug(hdl, "rio_recv: retire suppressed"); continue; } if (nvlist_lookup_nvlist(faults[f], FM_FAULT_ASRU, &asru) != 0) { fmd_hdl_debug(hdl, "rio_recv: no asru in fault"); continue; } scheme = NULL; if (nvlist_lookup_string(asru, FM_FMRI_SCHEME, &scheme) != 0 || strcmp(scheme, FM_FMRI_SCHEME_DEV) != 0) { fmd_hdl_debug(hdl, "rio_recv: not \"dev\" scheme: %s", scheme ? scheme : ""); continue; } if (fault_exception(hdl, faults[f])) continue; if (nvlist_lookup_string(asru, FM_FMRI_DEV_PATH, &path) != 0 || path[0] == '\0') { fmd_hdl_debug(hdl, "rio_recv: no dev path in asru"); continue; } valid_suspect = 1; if (retire) { if (fmd_nvl_fmri_has_fault(hdl, asru, FMD_HAS_FAULT_ASRU, NULL) == 1) { error = di_retire_device(path, &drt, 0); if (error != 0) { fmd_hdl_debug(hdl, "rio_recv:" " di_retire_device failed:" " error: %d %s", error, path); rval = -1; } } } else { if (fmd_nvl_fmri_has_fault(hdl, asru, FMD_HAS_FAULT_ASRU, NULL) == 0) { error = di_unretire_device(path, &drt); if (error != 0) { fmd_hdl_debug(hdl, "rio_recv:" " di_unretire_device failed:" " error: %d %s", error, path); rval = -1; } } } } /* * Run through again to handle new faults in a list.updated. */ for (f = 0; f < nfaults; f++) { if (nvlist_lookup_boolean_value(faults[f], FM_SUSPECT_RETIRE, &rtr) == 0 && !rtr) { fmd_hdl_debug(hdl, "rio_recv: retire suppressed"); continue; } if (nvlist_lookup_nvlist(faults[f], FM_FAULT_ASRU, &asru) != 0) { fmd_hdl_debug(hdl, "rio_recv: no asru in fault"); continue; } scheme = NULL; if (nvlist_lookup_string(asru, FM_FMRI_SCHEME, &scheme) != 0 || strcmp(scheme, FM_FMRI_SCHEME_DEV) != 0) { fmd_hdl_debug(hdl, "rio_recv: not \"dev\" scheme: %s", scheme ? scheme : ""); continue; } if (fault_exception(hdl, faults[f])) continue; if (nvlist_lookup_string(asru, FM_FMRI_DEV_PATH, &path) != 0 || path[0] == '\0') { fmd_hdl_debug(hdl, "rio_recv: no dev path in asru"); continue; } if (strcmp(class, FM_LIST_UPDATED_CLASS) == 0) { if (fmd_nvl_fmri_has_fault(hdl, asru, FMD_HAS_FAULT_ASRU, NULL) == 1) { error = di_retire_device(path, &drt, 0); if (error != 0) { fmd_hdl_debug(hdl, "rio_recv:" " di_retire_device failed:" " error: %d %s", error, path); } } } } /* * Don't send uuclose or uuresolved unless at least one suspect * was valid for this retire agent and no retires/unretires failed. */ if (valid_suspect == 0) return; /* * The fmd framework takes care of moving a case to the repaired * state. To move the case to the closed state however, we (the * retire agent) need to call fmd_case_uuclose() */ if (strcmp(class, FM_LIST_SUSPECT_CLASS) == 0 && rval == 0) { if (nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) == 0 && !fmd_case_uuclosed(hdl, uuid)) { fmd_case_uuclose(hdl, uuid); } } /* * Similarly to move the case to the resolved state, we (the * retire agent) need to call fmd_case_uuresolved() */ if (strcmp(class, FM_LIST_REPAIRED_CLASS) == 0 && rval == 0 && nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) == 0) fmd_case_uuresolved(hdl, uuid); } static const fmd_hdl_ops_t fmd_ops = { rio_recv, /* fmdo_recv */ NULL, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ }; static const fmd_prop_t rio_props[] = { { "global-disable", FMD_TYPE_BOOL, "false" }, { "fault-exceptions", FMD_TYPE_STRING, NULL }, { NULL, 0, NULL } }; static const fmd_hdl_info_t fmd_info = { "I/O Retire Agent", "2.0", &fmd_ops, rio_props }; void _fmd_init(fmd_hdl_t *hdl) { char *estr; char *estrdup; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) { fmd_hdl_debug(hdl, "failed to register handle\n"); return; } global_disable = fmd_prop_get_int32(hdl, "global-disable"); estrdup = NULL; if (estr = fmd_prop_get_string(hdl, "fault-exceptions")) { estrdup = fmd_hdl_strdup(hdl, estr, FMD_SLEEP); fmd_prop_free_string(hdl, estr); parse_exception_string(hdl, estrdup); fmd_hdl_strfree(hdl, estrdup); } } void _fmd_fini(fmd_hdl_t *hdl) { free_exception_list(hdl); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. MODULE = ip-transport CLASS = common SRCS = ip.c include ../../Makefile.plugin LDLIBS += -lsocket -lnsl CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" /* * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IP_MAGIC "\177FMA" /* magic string identifying a packet header */ #define IP_MAGLEN 4 /* length of magic string */ #define IP_DEBUG_OFF 0 /* No informational debugging printed */ #define IP_DEBUG_FINE 1 /* Basic debug information printed (default) */ #define IP_DEBUG_FINER 2 /* More debug information printed. */ #define IP_DEBUG_FINEST 3 /* All debug information printed */ typedef struct ip_hdr { char iph_magic[IP_MAGLEN]; /* magic string */ uint32_t iph_size; /* packed size */ } ip_hdr_t; typedef struct ip_buf { void *ipb_buf; /* data buffer */ size_t ipb_size; /* size of buffer */ } ip_buf_t; typedef struct ip_cinfo { /* Connection specific information */ struct addrinfo *ipc_addr; /* Connection address(es) */ char *ipc_name; /* The name of the server or interface */ int ipc_retry; /* The number of connection retries */ boolean_t ipc_accept; /* Will connection accept clients */ id_t ipc_timer; /* FMD timer id for connection */ struct ip_cinfo *ipc_next; /* Next conneciton in list */ } ip_cinfo_t; typedef struct ip_xprt { fmd_xprt_t *ipx_xprt; /* transport handle */ int ipx_flags; /* transport flags */ int ipx_fd; /* socket file descriptor */ int ipx_done; /* flag indicating connection closed */ pthread_t ipx_tid; /* recv-side auxiliary thread */ ip_buf_t ipx_sndbuf; /* buffer for sending events */ ip_buf_t ipx_rcvbuf; /* buffer for receiving events */ ip_cinfo_t *ipx_cinfo; /* info for reconnect */ id_t ipx_spnd_timer; /* connection suspend timer */ char *ipx_addr; /* address:port of remote connection */ struct ip_xprt *ipx_next; /* next ip_xprt in global list */ } ip_xprt_t; #define IPX_ID(a) ((a)->ipx_addr == NULL ? "(Not connected)" : (a)->ipx_addr) typedef struct ip_stat { fmd_stat_t ips_accfail; /* failed accepts */ fmd_stat_t ips_badmagic; /* invalid packet headers */ fmd_stat_t ips_packfail; /* failed packs */ fmd_stat_t ips_unpackfail; /* failed unpacks */ } ip_stat_t; static void ip_xprt_create(fmd_xprt_t *, int, int, ip_cinfo_t *, char *); static void ip_xprt_destroy(ip_xprt_t *); static ip_stat_t ip_stat = { { "accfail", FMD_TYPE_UINT64, "failed accepts" }, { "badmagic", FMD_TYPE_UINT64, "invalid packet headers" }, { "packfail", FMD_TYPE_UINT64, "failed packs" }, { "unpackfail", FMD_TYPE_UINT64, "failed unpacks" }, }; static fmd_hdl_t *ip_hdl; /* module handle */ static pthread_mutex_t ip_lock; /* lock for ip_xps list */ static ip_xprt_t *ip_xps; /* list of active transports */ static pthread_mutex_t ip_conns_lock; /* lock for ip_conns list */ static ip_cinfo_t *ip_conns; /* list of all configured connection info */ static nvlist_t *ip_auth; /* authority to use for transport(s) */ static size_t ip_size; /* default buffer size */ static volatile int ip_quit; /* signal to quit */ static int ip_qlen; /* queue length for listen(3SOCKET) */ static int ip_mtbf; /* mtbf for simulating packet drop */ static int ip_external; /* set transport to be "external" */ static int ip_no_remote_repair; /* disallow remote repair */ static int ip_hconly; /* only cache faults that are hc-scheme */ static int ip_rdonly; /* force transport to be rdonly */ static int ip_hc_present_only; /* only cache faults if hc-scheme and present */ static char *ip_domain_name; /* set domain name for received list.suspects */ static hrtime_t ip_burp; /* make mtbf slower by adding this much delay */ static int ip_translate; /* call fmd_xprt_translate() before sending */ static char *ip_port; /* port to connect to (or bind to if server) */ static int ip_retry; /* retry count for ip_xprt_setup() -1=forever */ static hrtime_t ip_sleep; /* sleep delay for ip_xprt_setup() */ static int ip_debug_level; /* level for printing debug messages */ /* * Prints a debug message to the fmd debug framework if the debug level is set * to at least the given level. */ static void ip_debug(int level, char *fmt, ...) { if (ip_debug_level >= level) { va_list args; va_start(args, fmt); fmd_hdl_vdebug(ip_hdl, fmt, args); va_end(args); } } /* * Allocate space in ipx_sndbuf for a header and a packed XDR encoding of * the specified nvlist, and then send the buffer to our remote peer. */ static int ip_fmdo_send(fmd_hdl_t *hdl, fmd_xprt_t *xp, fmd_event_t *ep, nvlist_t *nvl) { ip_xprt_t *ipx; size_t size, nvsize; char *buf, *nvbuf; ip_hdr_t *iph; ssize_t r, n; int err; if (xp == NULL) { ip_debug(IP_DEBUG_FINE, "ip_fmdo_send failed: xp=NULL\n"); return (FMD_SEND_FAILED); } ipx = fmd_xprt_getspecific(hdl, xp); /* * For testing purposes, if ip_mtbf is non-zero, use this to pseudo- * randomly simulate the need for retries. If ip_burp is also set, * then we also suspend the transport for a bit and wake it up again. */ if (ip_mtbf != 0 && gethrtime() % ip_mtbf == 0) { if (ip_burp != 0) { ip_debug(IP_DEBUG_FINE, "burping ipx %s", IPX_ID(ipx)); ipx->ipx_flags |= FMD_XPRT_SUSPENDED; ipx->ipx_spnd_timer = fmd_timer_install( ip_hdl, ipx, NULL, ip_burp); fmd_xprt_suspend(ip_hdl, xp); } return (FMD_SEND_RETRY); } if (ip_translate && (nvl = fmd_xprt_translate(hdl, xp, ep)) == NULL) { fmd_hdl_error(hdl, "failed to translate event %p", (void *)ep); return (FMD_SEND_FAILED); } (void) nvlist_size(nvl, &nvsize, NV_ENCODE_XDR); size = r = sizeof (ip_hdr_t) + nvsize; if (ipx->ipx_sndbuf.ipb_size < size) { fmd_hdl_free(hdl, ipx->ipx_sndbuf.ipb_buf, ipx->ipx_sndbuf.ipb_size); ipx->ipx_sndbuf.ipb_size = P2ROUNDUP(size, 16); ipx->ipx_sndbuf.ipb_buf = fmd_hdl_alloc(hdl, ipx->ipx_sndbuf.ipb_size, FMD_SLEEP); } buf = ipx->ipx_sndbuf.ipb_buf; iph = (ip_hdr_t *)(uintptr_t)buf; nvbuf = buf + sizeof (ip_hdr_t); bcopy(IP_MAGIC, iph->iph_magic, IP_MAGLEN); iph->iph_size = htonl(nvsize); err = nvlist_pack(nvl, &nvbuf, &nvsize, NV_ENCODE_XDR, 0); if (ip_translate) nvlist_free(nvl); if (err != 0) { fmd_hdl_error(ip_hdl, "failed to pack event for " "transport %p: %s\n", (void *)ipx->ipx_xprt, strerror(err)); ip_stat.ips_packfail.fmds_value.ui64++; return (FMD_SEND_FAILED); } while (!ip_quit && r != 0) { if ((n = send(ipx->ipx_fd, buf, r, 0)) < 0) { if (errno != EINTR && errno != EWOULDBLOCK) { ip_debug(IP_DEBUG_FINE, "failed to send to %s", IPX_ID(ipx)); return (FMD_SEND_FAILED); } continue; } buf += n; r -= n; } ip_debug(IP_DEBUG_FINEST, "Sent event %d bytes to %s", size, IPX_ID(ipx)); return (FMD_SEND_SUCCESS); } /* * Sends events over transports that are configured read only. When the module * is in read only mode it will receive all events and only send events that * have a subscription set. * * The configuration file will have to set prop ip_rdonly true and also * subscribe for events that are desired to be sent over the transport in order * for this function to be used. */ /* ARGSUSED */ static void ip_fmdo_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { int err; ip_xprt_t *ipx; if (ip_rdonly && !ip_quit) { (void) pthread_mutex_lock(&ip_lock); for (ipx = ip_xps; ipx != NULL; ipx = ipx->ipx_next) { err = ip_fmdo_send(hdl, ipx->ipx_xprt, ep, nvl); while (FMD_SEND_RETRY == err) { err = ip_fmdo_send(hdl, ipx->ipx_xprt, ep, nvl); } } (void) pthread_mutex_unlock(&ip_lock); } } /* * Receive a chunk of data of the specified size from our remote peer. The * data is received into ipx_rcvbuf, and then a pointer to the buffer is * returned. NOTE: The data is only valid until the next call to ip_xprt_recv. * If the connection breaks or ip_quit is set during receive, NULL is returned. */ static void * ip_xprt_recv(ip_xprt_t *ipx, size_t size) { char *buf = ipx->ipx_rcvbuf.ipb_buf; ssize_t n, r = size; if (ipx->ipx_rcvbuf.ipb_size < size) { fmd_hdl_free(ip_hdl, ipx->ipx_rcvbuf.ipb_buf, ipx->ipx_rcvbuf.ipb_size); ipx->ipx_rcvbuf.ipb_size = P2ROUNDUP(size, 16); ipx->ipx_rcvbuf.ipb_buf = buf = fmd_hdl_alloc(ip_hdl, ipx->ipx_rcvbuf.ipb_size, FMD_SLEEP); } while (!ip_quit && r != 0) { if ((n = recv(ipx->ipx_fd, buf, r, MSG_WAITALL)) == 0) { ipx->ipx_done++; return (NULL); } if (n < 0) { if (errno != EINTR && errno != EWOULDBLOCK) { ip_debug(IP_DEBUG_FINE, "failed to recv on ipx %s", IPX_ID(ipx)); } continue; } /* Reset retry counter after a successful connection */ if (ipx->ipx_cinfo) { ipx->ipx_cinfo->ipc_retry = ip_retry; } buf += n; r -= n; } return (r ? NULL: ipx->ipx_rcvbuf.ipb_buf); } /* * Sets the address/port of the remote connection in the connection info struct * This is called after a TCP session has been set up with a known remote * address (sap) */ static void ip_xprt_set_addr(ip_xprt_t *ipx, const struct sockaddr *sap) { const struct sockaddr_in6 *sin6 = (const void *)sap; const struct sockaddr_in *sin = (const void *)sap; char buf[INET6_ADDRSTRLEN + 16]; struct in_addr v4addr; in_port_t port; int n; ip_debug(IP_DEBUG_FINER, "Enter ip_xprt_set_addr"); if (sap->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, &v4addr); (void) inet_ntop(AF_INET, &v4addr, buf, sizeof (buf)); port = ntohs(sin6->sin6_port); } else if (sap->sa_family == AF_INET6) { (void) inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof (buf)); port = ntohs(sin6->sin6_port); } else { (void) inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof (buf)); port = ntohs(sin->sin_port); } n = strlen(buf); (void) snprintf(buf + n, sizeof (buf) - n, ":%u", port); if (ipx->ipx_addr) fmd_hdl_strfree(ip_hdl, ipx->ipx_addr); ipx->ipx_addr = fmd_hdl_strdup(ip_hdl, buf, FMD_SLEEP); ip_debug(IP_DEBUG_FINE, "connection addr is %s on %p", ipx->ipx_addr, (void *)ipx); } static nvlist_t * ip_xprt_auth(ip_xprt_t *ipx) { nvlist_t *nvl; int err; ip_debug(IP_DEBUG_FINER, "Enter ip_xprt_auth"); if (ip_auth != NULL) err = nvlist_dup(ip_auth, &nvl, 0); else err = nvlist_alloc(&nvl, 0, 0); if (err != 0) { fmd_hdl_abort(ip_hdl, "failed to create nvlist for " "authority: %s\n", strerror(err)); } if (ip_auth != NULL) return (nvl); ip_debug(IP_DEBUG_FINE, "ip_authority %s=%s\n", FM_FMRI_AUTH_SERVER, ipx->ipx_addr); (void) nvlist_add_uint8(nvl, FM_VERSION, FM_FMRI_AUTH_VERSION); (void) nvlist_add_string(nvl, FM_FMRI_AUTH_SERVER, ipx->ipx_addr); return (nvl); } static void ip_xprt_accept(ip_xprt_t *ipx) { struct sockaddr_storage sa; socklen_t salen = sizeof (sa); fmd_xprt_t *xp; int fd; ip_debug(IP_DEBUG_FINER, "Enter ip_xprt_accept"); if ((fd = accept(ipx->ipx_fd, (struct sockaddr *)&sa, &salen)) == -1) { fmd_hdl_error(ip_hdl, "failed to accept connection"); ip_stat.ips_accfail.fmds_value.ui64++; return; } ip_debug(IP_DEBUG_FINE, "Accepted socket on fd %d", fd); ip_xprt_set_addr(ipx, (struct sockaddr *)&sa); xp = fmd_xprt_open(ip_hdl, ipx->ipx_flags, ip_xprt_auth(ipx), NULL); ip_xprt_create(xp, fd, ipx->ipx_flags, ipx->ipx_cinfo, ipx->ipx_addr); } static void ip_xprt_recv_event(ip_xprt_t *ipx) { ip_hdr_t *iph; nvlist_t *nvl; size_t size; void *buf; int err; if ((iph = ip_xprt_recv(ipx, sizeof (ip_hdr_t))) == NULL) return; /* connection broken */ if (bcmp(iph->iph_magic, IP_MAGIC, IP_MAGLEN) != 0) { fmd_hdl_error(ip_hdl, "invalid hdr magic %x.%x.%x.%x from transport %s\n", iph->iph_magic[0], iph->iph_magic[1], iph->iph_magic[2], iph->iph_magic[3], IPX_ID(ipx)); ip_stat.ips_badmagic.fmds_value.ui64++; return; } size = ntohl(iph->iph_size); if ((buf = ip_xprt_recv(ipx, size)) == NULL) return; /* connection broken */ if ((err = nvlist_unpack(buf, size, &nvl, 0)) != 0) { fmd_hdl_error(ip_hdl, "failed to unpack event from " "transport %s: %s\n", IPX_ID(ipx), strerror(err)); ip_stat.ips_unpackfail.fmds_value.ui64++; } else { if (ip_domain_name) fmd_xprt_add_domain(ip_hdl, nvl, ip_domain_name); fmd_xprt_post(ip_hdl, ipx->ipx_xprt, nvl, 0); } if (fmd_xprt_error(ip_hdl, ipx->ipx_xprt)) { fmd_hdl_error(ip_hdl, "protocol error on transport %p", (void *)ipx->ipx_xprt); ipx->ipx_done++; } ip_debug(IP_DEBUG_FINEST, "Recv event %d bytes from %s", size, IPX_ID(ipx)); } static void ip_xprt_thread(void *arg) { ip_xprt_t *ipx = arg; struct sockaddr_storage sa; socklen_t salen = sizeof (sa); struct pollfd pfd; ip_debug(IP_DEBUG_FINER, "Enter ip_xprt_thread"); while (!ip_quit && !ipx->ipx_done) { if (ipx->ipx_xprt != NULL || (ipx->ipx_flags & FMD_XPRT_ACCEPT)) pfd.events = POLLIN; else pfd.events = POLLOUT; pfd.fd = ipx->ipx_fd; pfd.revents = 0; if (poll(&pfd, 1, -1) <= 0) continue; /* loop around and check ip_quit */ if (pfd.revents & (POLLHUP | POLLERR)) { ip_debug(IP_DEBUG_FINE, "hangup fd %d\n", ipx->ipx_fd); break; } if (pfd.revents & POLLOUT) { /* * Once we're connected, there's no reason to have our * calls to recv() and send() be non-blocking since we * we have separate threads for each: clear O_NONBLOCK. */ (void) fcntl(ipx->ipx_fd, F_SETFL, fcntl(ipx->ipx_fd, F_GETFL, 0) & ~O_NONBLOCK); if (getpeername(ipx->ipx_fd, (struct sockaddr *)&sa, &salen) != 0) { ip_debug(IP_DEBUG_FINE, "Not connected, no remote name for fd %d. " " Will retry.", ipx->ipx_fd); bzero(&sa, sizeof (sa)); break; } ip_xprt_set_addr(ipx, (struct sockaddr *)&sa); ipx->ipx_xprt = fmd_xprt_open(ip_hdl, ipx->ipx_flags, ip_xprt_auth(ipx), ipx); ip_debug(IP_DEBUG_FINE, "connect fd %d ipx %p", ipx->ipx_fd, (void *)ipx); continue; } if (pfd.revents & POLLIN) { if (ipx->ipx_xprt == NULL) ip_xprt_accept(ipx); else ip_xprt_recv_event(ipx); } } ipx->ipx_cinfo->ipc_timer = fmd_timer_install(ip_hdl, ipx, NULL, 0); ip_debug(IP_DEBUG_FINE, "close fd %d (timer %d)", ipx->ipx_fd, (int)ipx->ipx_cinfo->ipc_timer); } static void ip_xprt_create(fmd_xprt_t *xp, int fd, int flags, ip_cinfo_t *cinfo, char *addr) { ip_xprt_t *ipx = fmd_hdl_zalloc(ip_hdl, sizeof (ip_xprt_t), FMD_SLEEP); ip_debug(IP_DEBUG_FINER, "Enter ip_xprt_create %p", (void *)ipx); ipx->ipx_xprt = xp; ipx->ipx_flags = flags; ipx->ipx_fd = fd; ipx->ipx_tid = fmd_thr_create(ip_hdl, ip_xprt_thread, ipx); ipx->ipx_cinfo = cinfo; ipx->ipx_addr = fmd_hdl_strdup(ip_hdl, addr, FMD_SLEEP); if (ipx->ipx_xprt != NULL) fmd_xprt_setspecific(ip_hdl, ipx->ipx_xprt, ipx); (void) pthread_mutex_lock(&ip_lock); ipx->ipx_next = ip_xps; ip_xps = ipx; (void) pthread_mutex_unlock(&ip_lock); } static void ip_xprt_destroy(ip_xprt_t *ipx) { ip_xprt_t *ipp, **ppx = &ip_xps; ip_debug(IP_DEBUG_FINER, "Enter ip_xprt_destory %s %p", IPX_ID(ipx), (void *)ipx); (void) pthread_mutex_lock(&ip_lock); for (ipp = *ppx; ipp != NULL; ipp = ipp->ipx_next) { if (ipp != ipx) ppx = &ipp->ipx_next; else break; } if (ipp != ipx) { (void) pthread_mutex_unlock(&ip_lock); fmd_hdl_abort(ip_hdl, "ipx %p not on xps list\n", (void *)ipx); } *ppx = ipx->ipx_next; ipx->ipx_next = NULL; (void) pthread_mutex_unlock(&ip_lock); if (ipx->ipx_spnd_timer) fmd_timer_remove(ip_hdl, ipx->ipx_spnd_timer); fmd_thr_signal(ip_hdl, ipx->ipx_tid); fmd_thr_destroy(ip_hdl, ipx->ipx_tid); if (ipx->ipx_xprt != NULL) fmd_xprt_close(ip_hdl, ipx->ipx_xprt); fmd_hdl_free(ip_hdl, ipx->ipx_sndbuf.ipb_buf, ipx->ipx_sndbuf.ipb_size); fmd_hdl_free(ip_hdl, ipx->ipx_rcvbuf.ipb_buf, ipx->ipx_rcvbuf.ipb_size); (void) close(ipx->ipx_fd); if (ipx->ipx_addr) { fmd_hdl_strfree(ip_hdl, ipx->ipx_addr); ipx->ipx_addr = NULL; } fmd_hdl_free(ip_hdl, ipx, sizeof (ip_xprt_t)); } /* * Loop through the addresses in the connection info structure that were * created by getaddrinfo() in ip_setup_addr during initialization (_fmd_init) * and for each one attempt to create a socket and initialize it. If we are * successful, return zero. If we fail, we check ip_retry: if it is non-zero * we return the last errno and let our caller retry ip_xprt_setup() later. If * ip_retry reaches zero, we call fmd_hdl_abort() with an appropriate message. */ static int ip_xprt_setup(fmd_hdl_t *hdl, ip_cinfo_t *cinfo) { int err, fd, oflags, xflags, optval = 1; struct addrinfo *aip; const char *s1, *s2; struct addrinfo *ail = cinfo->ipc_addr; ip_debug(IP_DEBUG_FINER, "Enter ip_xprt_setup %s\n", cinfo->ipc_name == NULL ? "localhost" : cinfo->ipc_name); /* * Set up flags as specified in the .conf file. Note that these are * mostly only used for testing purposes, allowing the transport to * be set up in various modes. */ xflags = (ip_rdonly == FMD_B_TRUE) ? FMD_XPRT_RDONLY : FMD_XPRT_RDWR; if (cinfo->ipc_accept) xflags |= FMD_XPRT_ACCEPT; if (ip_external == FMD_B_TRUE) xflags |= FMD_XPRT_EXTERNAL; if (ip_no_remote_repair == FMD_B_TRUE) xflags |= FMD_XPRT_NO_REMOTE_REPAIR; if (ip_hconly == FMD_B_TRUE) xflags |= FMD_XPRT_HCONLY; if (ip_hc_present_only == FMD_B_TRUE) xflags |= FMD_XPRT_HC_PRESENT_ONLY; for (aip = ail; aip != NULL; aip = aip->ai_next) { if (aip->ai_family != AF_INET && aip->ai_family != AF_INET6) continue; /* ignore anything that isn't IPv4 or IPv6 */ if ((fd = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol)) == -1) { err = errno; continue; } oflags = fcntl(fd, F_GETFL, 0); (void) fcntl(fd, F_SETFL, oflags | O_NONBLOCK); if (xflags & FMD_XPRT_ACCEPT) { err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (optval)) != 0 || bind(fd, aip->ai_addr, aip->ai_addrlen) != 0 || listen(fd, ip_qlen) != 0; } else { err = connect(fd, aip->ai_addr, aip->ai_addrlen); if (err) err = errno; if (err == EINPROGRESS) err = 0; } if (err == 0) { ip_xprt_create(NULL, fd, xflags, cinfo, NULL); ip_debug(IP_DEBUG_FINER, "Exit ip_xprt_setup"); return (0); } ip_debug(IP_DEBUG_FINE, "Error=%d errno=%d", err, errno); err = errno; (void) close(fd); } if (cinfo->ipc_name != NULL) { s1 = "failed to connect to"; s2 = cinfo->ipc_name; } else { s1 = "failed to listen on"; s2 = ip_port; } if (err == EACCES || cinfo->ipc_retry-- == 0) fmd_hdl_abort(hdl, "%s %s: %s\n", s1, s2, strerror(err)); ip_debug(IP_DEBUG_FINE, "%s %s: %s (will retry)\n", s1, s2, strerror(err)); ip_debug(IP_DEBUG_FINER, "Exit ip_xprt_setup"); return (err); } /* * Free address based resources */ static void ip_addr_cleanup() { ip_cinfo_t *conn; (void) pthread_mutex_lock(&ip_conns_lock); conn = ip_conns; while (conn != NULL) { ip_conns = conn->ipc_next; if (conn->ipc_addr != NULL) freeaddrinfo(conn->ipc_addr); conn->ipc_addr = NULL; if (conn->ipc_timer) fmd_timer_remove(ip_hdl, conn->ipc_timer); fmd_hdl_strfree(ip_hdl, conn->ipc_name); fmd_hdl_free(ip_hdl, conn, sizeof (ip_cinfo_t)); conn = ip_conns; } (void) pthread_mutex_unlock(&ip_conns_lock); fmd_prop_free_string(ip_hdl, ip_port); } static boolean_t ip_argis_cinfo(void *arg) { boolean_t exists = B_FALSE; ip_cinfo_t *conn; (void) pthread_mutex_lock(&ip_conns_lock); for (conn = ip_conns; conn != NULL; conn = conn->ipc_next) { if (conn == arg) { exists = B_TRUE; break; } } (void) pthread_mutex_unlock(&ip_conns_lock); return (exists); } static ip_cinfo_t * ip_create_cinfo(char *server, boolean_t accept) { int err; struct addrinfo aih; ip_cinfo_t *cinfo = fmd_hdl_zalloc( ip_hdl, sizeof (ip_cinfo_t), FMD_NOSLEEP); if (cinfo == NULL) return (NULL); cinfo->ipc_accept = accept; cinfo->ipc_retry = ip_retry; if (server != NULL) { cinfo->ipc_name = fmd_hdl_strdup(ip_hdl, server, FMD_NOSLEEP); if (cinfo->ipc_name == NULL) { fmd_hdl_free(ip_hdl, cinfo, sizeof (ip_cinfo_t)); return (NULL); } } bzero(&aih, sizeof (aih)); aih.ai_flags = AI_ADDRCONFIG; aih.ai_family = AF_UNSPEC; aih.ai_socktype = SOCK_STREAM; if (server != NULL) { ip_debug(IP_DEBUG_FINE, "resolving %s:%s\n", server, ip_port); } else { aih.ai_flags |= AI_PASSIVE; cinfo->ipc_name = fmd_hdl_strdup( ip_hdl, "localhost", FMD_NOSLEEP); if (cinfo->ipc_name == NULL) { fmd_hdl_free(ip_hdl, cinfo, sizeof (ip_cinfo_t)); return (NULL); } } err = getaddrinfo(server, ip_port, &aih, &cinfo->ipc_addr); if (err != 0) { fmd_hdl_error(ip_hdl, "failed to resolve host %s port %s: %s\n", cinfo->ipc_name, ip_port, gai_strerror(err)); cinfo->ipc_addr = NULL; fmd_hdl_strfree(ip_hdl, cinfo->ipc_name); fmd_hdl_free(ip_hdl, cinfo, sizeof (ip_cinfo_t)); cinfo = NULL; } return (cinfo); } /* * Setup a single ip address for ip connection. * If unable to setup any of the addresses then all addresses will be cleaned up * and non-zero will be returned. */ static int ip_setup_addr(char *server, boolean_t accept) { int err = 0; ip_cinfo_t *cinfo = ip_create_cinfo(server, accept); if (cinfo == NULL) { ip_addr_cleanup(); err++; } else { (void) pthread_mutex_lock(&ip_conns_lock); cinfo->ipc_next = ip_conns; ip_conns = cinfo; (void) pthread_mutex_unlock(&ip_conns_lock); } return (err); } /* * Setup a ip addresses for an ip connection. The address can be a comma * separated list of addresses as well. * If unable to setup any of the addresses then all addresses will be cleaned up * and non-zero will be returned. */ static int ip_setup_addrs(char *server, boolean_t accept) { int err = 0; char *addr = server; char *p; for (p = server; *p != '\0'; p++) { if (*p == ',') { *p = '\0'; err = ip_setup_addr(addr, accept); *p = ','; if (err) return (err); addr = ++p; if (*addr == '\0') break; } } if (*addr != '\0') { err = ip_setup_addr(addr, accept); } return (err); } /* * Starts all connections for each configured network address. If there is an * error starting a connection a timer will be started for a retry. */ static void ip_start_connections() { ip_cinfo_t *conn; (void) pthread_mutex_lock(&ip_conns_lock); for (conn = ip_conns; conn != NULL; conn = conn->ipc_next) { if (ip_xprt_setup(ip_hdl, conn) != 0) { conn->ipc_timer = fmd_timer_install(ip_hdl, conn, NULL, ip_sleep); } } (void) pthread_mutex_unlock(&ip_conns_lock); } /* * Timeout handler for the transport module. We use these types of timeouts: * * (a) arg is ip_cinfo_t: attempt ip_xprt_setup(), re-install timeout to retry * (b) arg is ip_xprt_t, FMD_XPRT_SUSPENDED: call fmd_xprt_resume() on arg * (c) arg is ip_xprt_t, !FMD_XPRT_SUSPENDED: call ip_xprt_destroy() on arg * (d) arg is NULL, ignore as this shouldn't happen * * Case (c) is required as we need to cause the module's main thread, which * runs this timeout handler, to join with the transport's auxiliary thread. * If the connection is a client then a timer will be installed to retry * connecting to the server. */ static void ip_timeout(fmd_hdl_t *hdl, id_t id, void *arg) { int install_timer; ip_cinfo_t *cinfo; ip_xprt_t *ipx; if (arg == NULL) { fmd_hdl_error(hdl, "ip_timeout failed because hg arg is NULL"); } else if (ip_argis_cinfo(arg)) { ip_debug(IP_DEBUG_FINER, "Enter ip_timeout (a) install new timer"); cinfo = arg; if ((ip_xprt_setup(hdl, arg) != 0) && !ip_quit) cinfo->ipc_timer = fmd_timer_install( hdl, cinfo, NULL, ip_sleep); else cinfo->ipc_timer = 0; } else { ipx = arg; if (ipx->ipx_flags & FMD_XPRT_SUSPENDED) { ipx->ipx_spnd_timer = 0; ip_debug(IP_DEBUG_FINE, "timer %d waking ipx %p", (int)id, arg); ipx->ipx_flags &= ~FMD_XPRT_SUSPENDED; fmd_xprt_resume(hdl, ipx->ipx_xprt); } else { ip_debug(IP_DEBUG_FINE, "timer %d closing ipx %p", (int)id, arg); cinfo = ipx->ipx_cinfo; install_timer = (ipx->ipx_flags & FMD_XPRT_ACCEPT) != FMD_XPRT_ACCEPT; ip_xprt_destroy(ipx); if (install_timer && !ip_quit) cinfo->ipc_timer = fmd_timer_install( hdl, cinfo, NULL, ip_sleep); else cinfo->ipc_timer = 0; } } } static const fmd_prop_t fmd_props[] = { { "ip_authority", FMD_TYPE_STRING, NULL }, { "ip_bufsize", FMD_TYPE_SIZE, "4k" }, { "ip_burp", FMD_TYPE_TIME, "0" }, { "ip_enable", FMD_TYPE_BOOL, "false" }, { "ip_mtbf", FMD_TYPE_INT32, "0" }, { "ip_external", FMD_TYPE_BOOL, "true" }, { "ip_no_remote_repair", FMD_TYPE_BOOL, "true" }, { "ip_hconly", FMD_TYPE_BOOL, "false" }, { "ip_rdonly", FMD_TYPE_BOOL, "false" }, { "ip_hc_present_only", FMD_TYPE_BOOL, "false" }, { "ip_domain_name", FMD_TYPE_STRING, NULL }, { "ip_port", FMD_TYPE_STRING, "664" }, { "ip_qlen", FMD_TYPE_INT32, "32" }, { "ip_retry", FMD_TYPE_INT32, "-1" }, /* -1=forever */ { "ip_server", FMD_TYPE_STRING, NULL }, /* server name */ { "ip_sleep", FMD_TYPE_TIME, "10s" }, { "ip_translate", FMD_TYPE_BOOL, "false" }, { "ip_bind_addr", FMD_TYPE_STRING, NULL }, /* network interface addr */ { "ip_debug_level", FMD_TYPE_INT32, "1" }, /* debug levels 0-3 */ { NULL, 0, NULL } }; static const fmd_hdl_ops_t fmd_ops = { ip_fmdo_recv, /* fmdo_recv */ ip_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ ip_fmdo_send, /* fmdo_send */ }; static const fmd_hdl_info_t fmd_info = { "IP Transport Agent", "1.0", &fmd_ops, fmd_props }; /* * Initialize the ip-transport module as either a server or a client. Note * that the ip-transport module is not enabled by default under Solaris: * at present we require a developer or tool to "setprop ip_enable true". * If ip-transport is needed in the future out-of-the-box on one or more Sun * platforms, the code to check 'ip_enable' should be replaced with: * * (a) configuring ip-transport to operate in client mode by default, * (b) a platform-specific configuration mechanism, or * (c) a means to assure security and prevent denial-of-service attacks. * * Note that (c) is only an issue when the transport module operates * in server mode (i.e. with the ip_server property set to NULL) on a * generic Solaris system which may be exposed directly to the Internet. * The property ip_bind_addr can be used to define a private network interface * to use so that the service is not exposed to the Internet. */ void _fmd_init(fmd_hdl_t *hdl) { char *addr, *auth, *p, *q, *r, *s; int err; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; /* failed to register handle */ if (fmd_prop_get_int32(hdl, "ip_enable") == FMD_B_FALSE) { fmd_hdl_unregister(hdl); return; } (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (ip_stat) / sizeof (fmd_stat_t), (fmd_stat_t *)&ip_stat); ip_hdl = hdl; (void) pthread_mutex_init(&ip_lock, NULL); ip_burp = fmd_prop_get_int64(hdl, "ip_burp"); ip_mtbf = fmd_prop_get_int32(hdl, "ip_mtbf"); ip_external = fmd_prop_get_int32(hdl, "ip_external"); ip_no_remote_repair = fmd_prop_get_int32(hdl, "ip_no_remote_repair"); ip_hconly = fmd_prop_get_int32(hdl, "ip_hconly"); ip_rdonly = fmd_prop_get_int32(hdl, "ip_rdonly"); ip_hc_present_only = fmd_prop_get_int32(hdl, "ip_hc_present_only"); ip_domain_name = fmd_prop_get_string(hdl, "ip_domain_name"); ip_qlen = fmd_prop_get_int32(hdl, "ip_qlen"); ip_retry = fmd_prop_get_int32(hdl, "ip_retry"); ip_sleep = fmd_prop_get_int64(hdl, "ip_sleep"); ip_translate = fmd_prop_get_int32(hdl, "ip_translate"); ip_size = (size_t)fmd_prop_get_int64(hdl, "ip_bufsize"); ip_size = MAX(ip_size, sizeof (ip_hdr_t)); ip_port = fmd_prop_get_string(hdl, "ip_port"); ip_debug_level = fmd_prop_get_int32(hdl, "ip_debug_level"); ip_conns = NULL; addr = fmd_prop_get_string(hdl, "ip_bind_addr"); if (addr != NULL) { err = ip_setup_addrs(addr, B_TRUE); if (err) { fmd_hdl_abort(hdl, "Unable to setup ip_bind_addr %s", addr); return; } fmd_prop_free_string(hdl, addr); } addr = fmd_prop_get_string(hdl, "ip_server"); if (addr != NULL) { err = ip_setup_addrs(addr, B_FALSE); if (err) { fmd_hdl_abort(hdl, "Unable to setup ip_server %s", addr); return; } fmd_prop_free_string(hdl, addr); } /* * If no specific connecitons configured then set up general server * listening on all network ports. */ if (ip_conns == NULL) { if (ip_setup_addr(NULL, B_TRUE) != 0) { fmd_hdl_abort(hdl, "Unable to setup server."); return; } } /* * If ip_authority is set, tokenize this string and turn it into an * FMA authority represented as a name-value pair list. We will use * this authority for all transports created by this module. If * ip_authority isn't set, we'll compute authorities on the fly. */ if ((auth = fmd_prop_get_string(hdl, "ip_authority")) != NULL) { (void) nvlist_alloc(&ip_auth, 0, 0); (void) nvlist_add_uint8(ip_auth, FM_VERSION, FM_FMRI_AUTH_VERSION); s = strdupa(auth); fmd_prop_free_string(hdl, auth); for (p = strtok_r(s, ",", &q); p != NULL; p = strtok_r(NULL, ",", &q)) { if ((r = strchr(p, '=')) == NULL) { ip_addr_cleanup(); fmd_hdl_abort(hdl, "ip_authority element <%s> " "must be in = form\n", p); } *r = '\0'; (void) nvlist_add_string(ip_auth, p, r + 1); *r = '='; } } ip_start_connections(); } void _fmd_fini(fmd_hdl_t *hdl) { ip_quit++; /* set quit flag before signalling auxiliary threads */ while (ip_xps != NULL) ip_xprt_destroy(ip_xps); nvlist_free(ip_auth); ip_addr_cleanup(); if (ip_domain_name != NULL) fmd_prop_free_string(ip_hdl, ip_domain_name); fmd_hdl_unregister(hdl); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # MODULE = sensor-transport CLASS = common SRCS = sensor_transport.c include ../../Makefile.plugin CFLAGS += $(INCS) LINTFLAGS += $(INCS) LDLIBS += -L$(ROOTLIB)/fm -ltopo LDFLAGS += -R/usr/lib/fm # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # /* * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, Joyent, Inc. */ #include #include #include #include #include #include #include #include #define ST_EREPORT_CLASS "ereport.sensor.failure" typedef struct sensor_fault { struct sensor_fault *sf_next; char *sf_fru; uint32_t sf_num_fails; boolean_t sf_last_faulted; boolean_t sf_faulted; boolean_t sf_unknown; } sensor_fault_t; typedef struct sensor_transport { fmd_hdl_t *st_hdl; fmd_xprt_t *st_xprt; hrtime_t st_interval; id_t st_timer; sensor_fault_t *st_faults; boolean_t st_first; /* * The number of consecutive sensor readings indicating failure that * we'll tolerate before sending an ereport. */ uint32_t st_tolerance; nvlist_t *st_spoofs; } sensor_transport_t; typedef struct st_stats { fmd_stat_t st_bad_fmri; fmd_stat_t st_topo_errs; fmd_stat_t st_repairs; } st_stats_t; st_stats_t st_stats = { { "bad_fmri", FMD_TYPE_UINT64, "bad or missing resource/FRU FMRI" }, { "topo_errors", FMD_TYPE_UINT64, "errors walking topology" }, { "repairs", FMD_TYPE_UINT64, "auto repairs" } }; static int st_check_component_complaints; static int have_complained; static char *spoof_prop = NULL; static int st_check_component(topo_hdl_t *thp, tnode_t *node, void *arg) { sensor_transport_t *stp = arg; fmd_hdl_t *hdl = stp->st_hdl; const char *name = topo_node_name(node); nvlist_t *nvl, *props, *rsrc, *fru; char *fmri; int err, ret; int32_t last_source, source = -1; boolean_t nonrecov, faulted, predictive, source_diff, injected; nvpair_t *nvp; uint64_t ena; nvlist_t *event; sensor_fault_t *sfp, **current; if (strcmp(name, FAN) != 0 && strcmp(name, PSU) != 0) return (0); if (topo_node_resource(node, &rsrc, NULL) != 0) { st_stats.st_bad_fmri.fmds_value.ui64++; return (0); } /* * If the resource isn't present, don't bother invoking the sensor * failure method. It may be that the sensors aren't part of the same * physical FRU and will report failure if the FRU is no longer there. */ if ((ret = topo_fmri_present(thp, rsrc, &err)) < 0) { fmd_hdl_debug(hdl, "topo_fmri_present() failed for %s=%d", name, topo_node_instance(node)); nvlist_free(rsrc); return (0); } if (!ret) { fmd_hdl_debug(hdl, "%s=%d is not present, ignoring", name, topo_node_instance(node)); nvlist_free(rsrc); return (0); } if (topo_method_invoke(node, TOPO_METH_SENSOR_FAILURE, TOPO_METH_SENSOR_FAILURE_VERSION, stp->st_spoofs, &nvl, &err) != 0) { if (err == ETOPO_METHOD_NOTSUP) { st_check_component_complaints++; if (!have_complained) { fmd_hdl_debug(hdl, "Method %s not supported " "on %s=%d", TOPO_METH_SENSOR_FAILURE, name, topo_node_instance(node)); } nvlist_free(rsrc); return (0); } nvl = NULL; } if (topo_node_fru(node, &fru, NULL, &err) != 0) { st_stats.st_bad_fmri.fmds_value.ui64++; nvlist_free(nvl); nvlist_free(rsrc); return (0); } if (topo_fmri_nvl2str(thp, fru, &fmri, &err) != 0) { st_stats.st_bad_fmri.fmds_value.ui64++; nvlist_free(nvl); nvlist_free(fru); nvlist_free(rsrc); return (0); } nvlist_free(fru); faulted = nonrecov = source_diff = injected = B_FALSE; predictive = B_TRUE; if (nvl != NULL) { nvp = NULL; while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { if (nvpair_value_nvlist(nvp, &props) != 0) continue; faulted = B_TRUE; /* * We need some simple rules to handle the case where * there are multiple facility nodes that indicate * a problem with this FRU, but disagree on the values * of nonrecov, predictive or source: * * 1) nonrecov will be set to true if one or more * facility nodes indicates true. Otherwise it will * default to false * * 2) predictive will default to false and remain false * if one or more facility nodes indicate false. * * 3) source will be set to unknown unless all facility * nodes agree on the source * * 4) injected defaults to false, but will be set to * true if any of the sensor states were injected. */ if (nonrecov == B_FALSE) if (nvlist_lookup_boolean_value(props, "nonrecov", &nonrecov) != 0) nonrecov = B_FALSE; if (predictive == B_TRUE) if (nvlist_lookup_boolean_value(props, "predictive", &predictive) != 0) predictive = B_FALSE; (void) nvlist_lookup_boolean_value(props, "injected", &injected); last_source = source; if (nvlist_lookup_uint32(props, "source", (uint32_t *)&source) != 0) source = TOPO_SENSOR_ERRSRC_UNKNOWN; if (last_source != -1 && last_source != source) source_diff = B_TRUE; } if (source_diff) source = TOPO_SENSOR_ERRSRC_UNKNOWN; } /* * See if we know about this fru. */ for (current = &stp->st_faults; *current != NULL; current = &(*current)->sf_next) { if (topo_fmri_strcmp(thp, fmri, (*current)->sf_fru)) break; } sfp = *current; if (sfp == NULL) { /* * We add this FRU to our list under two circumstances: * * 1. This FRU is faulted and needs to be remembered to * avoid duplicate ereports. * * 2. This is the initial pass, and we want to repair the * FRU in case it was repaired while we were offline. */ if (stp->st_first || faulted) { sfp = fmd_hdl_zalloc(hdl, sizeof (sensor_fault_t), FMD_SLEEP); sfp->sf_fru = fmd_hdl_strdup(hdl, fmri, FMD_SLEEP); sfp->sf_next = stp->st_faults; stp->st_faults = sfp; } else { goto out; } } if (faulted) sfp->sf_num_fails++; if (nvl == NULL) sfp->sf_unknown = B_TRUE; if (faulted) { /* * Construct and post the ereport. * * XXFM we only post one ereport per fru. It should be possible * to uniquely identify faulty resources instead and post one * per resource, even if they share the same FRU. */ if (!sfp->sf_last_faulted && (sfp->sf_num_fails > stp->st_tolerance)) { ena = fmd_event_ena_create(hdl); event = fmd_nvl_alloc(hdl, FMD_SLEEP); (void) nvlist_add_string(event, "type", name); (void) nvlist_add_boolean_value(event, "nonrecov", nonrecov); (void) nvlist_add_boolean_value(event, "predictive", predictive); (void) nvlist_add_uint32(event, "source", (uint32_t)source); (void) nvlist_add_nvlist(event, "details", nvl); (void) nvlist_add_string(event, FM_CLASS, ST_EREPORT_CLASS); (void) nvlist_add_uint8(event, FM_VERSION, FM_EREPORT_VERSION); (void) nvlist_add_uint64(event, FM_EREPORT_ENA, ena); (void) nvlist_add_nvlist(event, FM_EREPORT_DETECTOR, rsrc); (void) nvlist_add_boolean_value(event, "__injected", injected); fmd_xprt_post(hdl, stp->st_xprt, event, 0); fmd_hdl_debug(hdl, "posted ereport: %s", ST_EREPORT_CLASS); } sfp->sf_faulted = B_TRUE; } out: topo_hdl_strfree(thp, fmri); nvlist_free(rsrc); nvlist_free(nvl); return (0); } int st_timeout_verbose = 0; /*ARGSUSED*/ static void st_timeout(fmd_hdl_t *hdl, id_t id, void *data) { sensor_transport_t *stp; sensor_fault_t *sfp, **current; topo_hdl_t *thp; topo_walk_t *twp; int err; if (st_timeout_verbose) fmd_hdl_debug(hdl, "timeout: checking topology"); stp = fmd_hdl_getspecific(hdl); thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, st_check_component, stp, &err)) == NULL) { fmd_hdl_topo_rele(hdl, thp); fmd_hdl_error(hdl, "failed to walk topology: %s\n", topo_strerror(err)); st_stats.st_topo_errs.fmds_value.ui64++; return; } if (st_check_component_complaints) have_complained++; /* * Initialize values in our internal FRU list for this iteration of * sensor reads. Keep track of whether the FRU was faulted in the * previous pass so we don't send multiple ereports for the same * problem. */ for (sfp = stp->st_faults; sfp != NULL; sfp = sfp->sf_next) { sfp->sf_unknown = B_FALSE; if (sfp->sf_num_fails > stp->st_tolerance) sfp->sf_last_faulted = sfp->sf_faulted; sfp->sf_faulted = B_FALSE; } if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) { topo_walk_fini(twp); fmd_hdl_topo_rele(hdl, thp); fmd_hdl_error(hdl, "failed to walk topology\n"); st_stats.st_topo_errs.fmds_value.ui64++; return; } /* * Remove any faults that weren't seen in the last pass. */ for (current = &stp->st_faults; *current != NULL; ) { sfp = *current; if (!sfp->sf_faulted && !sfp->sf_unknown) { fmd_hdl_debug(hdl, "repairing %s", sfp->sf_fru); fmd_repair_fru(hdl, sfp->sf_fru); st_stats.st_repairs.fmds_value.ui64++; *current = sfp->sf_next; fmd_hdl_strfree(hdl, sfp->sf_fru); fmd_hdl_free(hdl, sfp, sizeof (sensor_fault_t)); } else { current = &sfp->sf_next; } } stp->st_first = B_FALSE; topo_walk_fini(twp); fmd_hdl_topo_rele(hdl, thp); stp->st_timer = fmd_timer_install(hdl, NULL, NULL, stp->st_interval); } /* * Parse the value of the spoof-sensor-state module property and store the * result in an nvlist of nvlists. The format of the value is 3-tuple, * delimited by colons, as follows: * * FMRIPATTERN:SENSORNAME:SENSORSTATE;... * * where FMRIPATTERN can be a string with wildcards that matches the FMRI * of a node associated with the target sensor facility. * * where SENSORNAME is the node name of the target sensor facility * * where SENSORSTATE is the desired sensor state value to spoof. * * Multiple tuples can be specifed, delimited by semicolons. * * If any errors are encountered while parsing the value, all parsing is * ceased and an ereport will be generated indicating a failure to parse * the value. */ /*ARGSUSED*/ static int parse_spoof_param(fmd_hdl_t *hdl, char *param, sensor_transport_t *stp) { char *sensor, *last_sensor, *field, *last_field; nvlist_t *spoof; if (nvlist_alloc(&stp->st_spoofs, NV_UNIQUE_NAME, 0) != 0) { return (-1); } sensor = strtok_r(param, ";", &last_sensor); while (sensor != NULL) { if (nvlist_alloc(&spoof, NV_UNIQUE_NAME, 0) != 0) goto err; if ((field = strtok_r(sensor, ":", &last_field)) == NULL || nvlist_add_string(spoof, ST_SPOOF_FMRI, field) != 0) goto err; if ((field = strtok_r(NULL, ":", &last_field)) == NULL || nvlist_add_string(spoof, ST_SPOOF_SENSOR, field) != 0) goto err; if ((field = strtok_r(NULL, ":", &last_field)) == NULL || nvlist_add_uint32(spoof, ST_SPOOF_STATE, strtol(field, NULL, 0)) != 0) goto err; if (nvlist_add_nvlist(stp->st_spoofs, sensor, spoof) != 0) goto err; spoof = NULL; sensor = strtok_r(NULL, ";", &last_sensor); } return (0); err: nvlist_free(spoof); nvlist_free(stp->st_spoofs); stp->st_spoofs = NULL; return (-1); } static const fmd_prop_t fmd_props[] = { { "interval", FMD_TYPE_TIME, "1min" }, { "tolerance", FMD_TYPE_UINT32, "1" }, { "spoof_sensor_state", FMD_TYPE_STRING, NULL }, { NULL, 0, NULL } }; static const fmd_hdl_ops_t fmd_ops = { NULL, /* fmdo_recv */ st_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ NULL /* fmdo_topo */ }; static const fmd_hdl_info_t fmd_info = { "Sensor Transport Agent", "1.1", &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { sensor_transport_t *stp; char buf[SYS_NMLN]; /* * The sensor-transport module is currently only supported on x86 * platforms. So to avoid unnecessarily wasting cpu cycles on sparc * walking the hc scheme tree every 60 seconds, we'll bail out before * registering the handle. */ if ((sysinfo(SI_ARCHITECTURE, buf, sizeof (buf)) == -1) || (strcmp(buf, "amd64") != 0 && strcmp(buf, "i386") != 0)) return; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (st_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&st_stats); stp = fmd_hdl_zalloc(hdl, sizeof (sensor_transport_t), FMD_SLEEP); stp->st_interval = fmd_prop_get_int64(hdl, "interval"); stp->st_tolerance = fmd_prop_get_int32(hdl, "tolerance"); spoof_prop = fmd_prop_get_string(hdl, "spoof_sensor_state"); if (spoof_prop != NULL && parse_spoof_param(hdl, spoof_prop, stp) != 0) fmd_hdl_error(hdl, "Error parsing config file"); fmd_hdl_setspecific(hdl, stp); stp->st_xprt = fmd_xprt_open(hdl, FMD_XPRT_RDONLY, NULL, NULL); stp->st_hdl = hdl; stp->st_first = B_TRUE; /* kick off the first asynchronous discovery */ stp->st_timer = fmd_timer_install(hdl, NULL, NULL, 0); } void _fmd_fini(fmd_hdl_t *hdl) { sensor_transport_t *stp; sensor_fault_t *sfp; stp = fmd_hdl_getspecific(hdl); if (stp != NULL) { fmd_xprt_close(hdl, stp->st_xprt); while ((sfp = stp->st_faults) != NULL) { stp->st_faults = sfp->sf_next; fmd_hdl_strfree(hdl, sfp->sf_fru); fmd_hdl_free(hdl, sfp, sizeof (sensor_fault_t)); } nvlist_free(stp->st_spoofs); fmd_hdl_free(hdl, stp, sizeof (sensor_transport_t)); } fmd_prop_free_string(hdl, spoof_prop); } # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. MODULE = ses-log-transport CLASS = common SRCS = ses_log_transport.c include ../../Makefile.plugin CFLAGS += $(INCS) LINTFLAGS += $(INCS) LDLIBS += -L$(ROOTLIB)/fm -ltopo -lseslog LDFLAGS += -R/usr/lib/fm # not linted SMATCH=off # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # # The itnerval property governs the interval between completing one poll of # the expanders and the beginning of the next poll. #setprop interval 60s # The severity is the minimum log severity that will cause an ereport to be # generated. #setprop severity 3 # The path indicates the root path at which the logs will be written out. #setprop path /var/fm/fmd/ses_logs/ # The maxlogsize indicates the maximum size that a log file is allowed to # reach before being rolled. #setprop maxlogsize 1000000 # The logcount indicates the number of "rolled" logs that will be kept. #setprop logcount 5/* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * SCSI Enclosure Services Log Transport Module * * This transport module is responsible for accessing the ses devices seen * from this host, reading their logs, generating ereports for targeted * entries, and then writing the log contents to a well known location in * the filesystem. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * This struct contains the default property values. These may * be overridden by entries in a ses_log_transport.conf file. * The severity is set to -1 here so that the _fmd_init routine will * determine the default severity based on the constants in libseslog.h. */ static const fmd_prop_t fmd_props[] = { { "interval", FMD_TYPE_TIME, "60s"}, { "severity", FMD_TYPE_INT32, "-1"}, { "path", FMD_TYPE_STRING, "/var/fm/fmd/ses_logs/"}, { "logcount", FMD_TYPE_UINT32, "5"}, { "maxlogsize", FMD_TYPE_UINT32, "1000000"}, { NULL, 0, NULL} }; /* Maintains statistics on dropped ereports. */ static struct slt_stat { fmd_stat_t dropped; } slt_stats = { { "dropped", FMD_TYPE_UINT64, "number of dropped ereports"} }; /* * This structure maintains a reference to the input values, transport, and * other data which is held by FMD and retrieved whenever an entry point * is called. */ typedef struct ses_log_monitor { fmd_hdl_t *slt_hdl; /* opaque handle for this transport */ fmd_xprt_t *slt_xprt; /* ereport transport */ id_t slt_timer; /* Timer for FMD polling use */ hrtime_t slt_interval; /* Polling interval */ int32_t slt_severity; /* Min severity for logging ereports */ char *slt_path; /* Output path for log files */ int32_t slt_log_count; /* Max rolled logs to keep */ int32_t slt_max_log_size; /* Max log size before rolling */ nvlist_t *slt_expanders; /* List of expander log entries */ } ses_log_monitor_t; /* Contains expander log data retrieved from a topology node */ typedef struct expander { char slt_label[MAXNAMELEN]; /* The expander name */ char slt_pid[MAXNAMELEN]; /* The system product id */ char slt_key[MAXNAMELEN]; /* The expander key (sas address) */ char slt_path[MAXPATHLEN]; /* The ses path to the expander */ nvlist_t *fmri; /* The fmri for this target */ } expander_t; #define DATA_FIELD "data" /* Label for the expander details */ #define DEFAULT_DATA "0" /* Default expander details value */ #define MIN_LOG_SIZE 100000 /* The minimum log file size. */ #define MIN_LOG_COUNT 1 /* Num of rolled files to keep */ #define EXAMINE_FMRI_VALUE 0 /* Extract fmri val */ #define INVERT_FMRI_INSTANCE 1 /* Invert an FMRI instance value */ #define FATAL_ERROR "fatal" /* ereport val for fatal errors */ #define NON_FATAL_ERROR "non-fatal" /* val for non fatal errors */ #define INVALID_OPERATION 0x01 /* Invalid access_fmri operation */ #define NULL_LOG_DATA 0x02 /* Lib returned NULL log ref */ #define INVALID_SEVERITY 0x03 /* Invalid severity value */ #define DATE_STRING_SIZE 16 /* Size of date string prefix. */ /* Prototype needed for use in declaring and populating tables */ static int invert_fmri(ses_log_monitor_t *, nvlist_t *); /* Holds a code-operation pair. Contains a log code an a function ptr */ typedef struct code_operation { int code; int (*func_ptr)(ses_log_monitor_t *, nvlist_t *); } code_operation_t; /* Holds a platform type and a list of code-operation structures */ typedef struct platform { const char *pid; int count; code_operation_t *codes; } platform_t; /* Holds a reference to all of the platforms */ typedef struct platforms { int pcount; platform_t *plist; } platforms_t; /* This is the genesis list of codes and functions. */ static code_operation_t genesis_codes[] = { { 684002, invert_fmri }, /* Alternate expander is down */ { 685002, invert_fmri } /* Alternate expander is down */ }; /* This is the list of all platforms and their associated code op pairs. */ static platform_t platform_list[] = { { "SUN-GENESIS", sizeof (genesis_codes) / sizeof (code_operation_t), genesis_codes } }; /* This structure holds a reference to the platform list. */ static const platforms_t platforms = { sizeof (platform_list) / sizeof (platform_t), platform_list }; /* * Post ereports using this method. */ static void slt_post_ereport(fmd_hdl_t *hdl, fmd_xprt_t *xprt, const char *ereport_class, uint64_t ena, nvlist_t *detector, nvlist_t *payload) { nvlist_t *nvl; int e = 0; char fullclass[PATH_MAX]; (void) snprintf(fullclass, sizeof (fullclass), "%s.io.sas.log.%s", FM_EREPORT_CLASS, ereport_class); if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) == 0) { e |= nvlist_add_string(nvl, FM_CLASS, fullclass); e |= nvlist_add_uint8(nvl, FM_VERSION, FM_EREPORT_VERSION); e |= nvlist_add_uint64(nvl, FM_EREPORT_ENA, ena); e |= nvlist_add_nvlist(nvl, FM_EREPORT_DETECTOR, detector); e |= nvlist_merge(nvl, payload, 0); if (e == 0) { fmd_xprt_post(hdl, xprt, nvl, 0); } else { nvlist_free(nvl); fmd_hdl_debug(hdl, "Error adding fields to ereport"); slt_stats.dropped.fmds_value.ui64++; } } else { fmd_hdl_debug(hdl, "Could not allocate space for ereport"); slt_stats.dropped.fmds_value.ui64++; } } /* * Create a directory if it doesn't exist. * Parameters: * path: The directory path to create. * mode: The mode used when creating the directory. */ static int do_mkdir(const char *path, mode_t mode) { struct stat st; int status = 0; if (stat(path, &st) != 0) { /* Directory does not exist */ if (mkdir(path, mode) != 0) status = -1; } else if (!S_ISDIR(st.st_mode)) { errno = ENOTDIR; status = -1; } return (status); } /* * Validates that all directories in path exist * path: The directory path to create. * mode: The mode used when creating the directory. */ static int mkpath(char *path, mode_t mode) { char *pp; char *sp; int status = 0; pp = path; while (status == 0 && (sp = strchr(pp, '/')) != 0) { if (sp != pp) { /* Neither root nor double slash in path */ *sp = '\0'; status = do_mkdir(path, mode); *sp = '/'; } pp = sp + 1; } return (status); } /* * Rotate the file from base.max-1->base.max, ... base.1->base.2, base->base.1 * Parameter: * file: The name of the current log file. */ void check_file_size(ses_log_monitor_t *slmp, char *file, int byte_count) { int i; char newFile[MAXPATHLEN]; char oldName[MAXPATHLEN]; struct stat st; int size; stat(file, &st); size = st.st_size; /* * If current file size plus what will be added is larger * than max file size, rotate the logs * For check to see if larger than configured max size. */ if (size + byte_count < slmp->slt_max_log_size) { /* next log entries can fit */ return; } /* next log entries could make log entries too large */ for (i = slmp->slt_log_count; i > 1; i--) { (void) snprintf(newFile, MAXPATHLEN, "%s.%x", file, i); (void) snprintf(oldName, MAXPATHLEN, "%s.%x", file, i - 1); (void) rename(oldName, newFile); } /* Finally move base to base.1 */ (void) rename(file, oldName); } /* * This method exists to give access into the fmri. One purpose is to flip the * instance number on the FMRI for a given hc-list entry. It is also * used to pull the value of an hc-list entry. In all cases, the function * returns the value of the hc-list entry found, NULL if no value was found. */ static char * access_fmri(ses_log_monitor_t *slmp, nvlist_t *fmri, char *target, int operation, int *err) { int i; nvpair_t *nvp; nvpair_t *nvp2; uint_t nelem; nvlist_t **nvl_array; char *name; int ival; char ivs[25]; char *target_val = NULL; if ((*err = nvlist_lookup_nvpair(fmri, "hc-list", &nvp)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "No hc-list in the fmri"); return (NULL); } /* hc-list is an array of nvlists */ (void) nvpair_value_nvlist_array(nvp, &nvl_array, &nelem); /* * Loop until you find the list that has hc-name that equals the * passed in "target" value (such as controller) in it. */ for (i = 0; i < nelem; i++) { /* Skip this pair if it is not labeled hc-name */ if ((nvlist_lookup_nvpair(nvl_array[i], "hc-name", &nvp2)) != 0) { continue; } /* * Extract the value of the name. Continue on an error because * we want to check all of the hc-name entries. */ if (nvpair_value_string(nvp2, &name) != 0) { continue; } /* If this isn't the target, go to the next pair. */ if (strcmp(name, target) != 0) { continue; } if ((*err = nvlist_lookup_nvpair(nvl_array[i], "hc-id", &nvp2)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Could not find hc-id in the fmri for %s", target); return (NULL); } /* * This is the target pair. If we can't get the value then * exit out and log an error. */ if ((*err = nvpair_value_string(nvp2, &target_val)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Target value not returned."); return (NULL); } switch (operation) { case INVERT_FMRI_INSTANCE: ival = atoi(target_val); ival = (ival + 1) % 2; (void) snprintf(ivs, sizeof (ivs), "%d", ival); if ((*err = nvlist_remove_nvpair(nvl_array[i], nvp2)) == 0) { if ((*err = nvlist_add_string(nvl_array[i], "hc-id", ivs)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error setting ivalue."); } } else { fmd_hdl_debug(slmp->slt_hdl, "Error removing original ivalue."); } break; case EXAMINE_FMRI_VALUE: /* * target_val is already set. Return without modifying * its value. */ break; /* Can return target_val as is (NULL) */ default: *err = INVALID_OPERATION; break; } /* End switch on operation */ /* Exit the loop. You have found the target */ break; } return (target_val); } /* * Generate a filename based on the target path * Parameters: * filename: The space for the generated output log file name. * expander: An expander_t struct containing path, pid etc info from the node. * slmp: A pointer to the transport data structure which contains the * configurable file parameters. * byte_count: The number of bytes that will be added to the target file for * this expander. */ static int create_filename(char *fileName, expander_t *expander, ses_log_monitor_t *slmp, int byte_count) { char *ses_node; int i; int label_length; int status = 0; char *subchassis_val = NULL; /* * Add the file name with the path root * and append a forward slash if one is not there. */ (void) snprintf(fileName, MAXPATHLEN, "%s", slmp->slt_path); ses_node = strrchr(fileName, '/'); if ((ses_node != NULL) && (ses_node[0] != '\0')) { (void) strlcat(fileName, "/", MAXPATHLEN); } ses_node = strrchr(expander->slt_path, '/'); (void) strlcat(fileName, ses_node + 1, MAXPATHLEN); /* * If a subchassis is defined, include it in the file name. * Errors are logged in the function. There may legitimately be no * subchassis, so simply continue if none is found. */ subchassis_val = access_fmri(slmp, expander->fmri, SUBCHASSIS, EXAMINE_FMRI_VALUE, &status); if (subchassis_val != NULL) { (void) strlcat(fileName, "_", MAXPATHLEN); (void) strlcat(fileName, SUBCHASSIS, MAXPATHLEN); (void) strlcat(fileName, subchassis_val, MAXPATHLEN); } (void) strlcat(fileName, "_", MAXPATHLEN); /* remove spaces and forward slashes from name */ label_length = strlen(expander->slt_label); for (i = 0; i < label_length; i++) { if ((!isspace(expander->slt_label[i])) && ('/' != expander->slt_label[i])) { (void) strncat(fileName, &expander->slt_label[i], 1); } } (void) strlcat(fileName, "/log", MAXPATHLEN); /* * Ensure directory structure exists for log file. */ status = mkpath(fileName, 0744); /* * Check size of file and rotate if necessary. */ check_file_size(slmp, fileName, byte_count); return (status); } /* * Determines the error class type based on the severity of the entry. * Parameter * severity: A severity level from a log entry. */ static char * error_type(int severity) { char *rval; switch (severity) { case SES_LOG_LEVEL_FATAL: rval = FATAL_ERROR; break; case SES_LOG_LEVEL_ERROR: rval = NON_FATAL_ERROR; break; default: rval = NULL; break; } return (rval); } /* * Allocates and adds an entry for a given expander to the expander list. * Parameters * slmp: A pointer to the ses_log_monitor_t struct for this transport. * key: A unique identifier for this expander. */ static int add_expander_record(ses_log_monitor_t *slmp, char *key) { nvlist_t *expanderDetails; int status = 0; if ((status = nvlist_alloc(&expanderDetails, NV_UNIQUE_NAME, 0)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error allocating expander detail space (%d)", status); return (status); } if ((status = nvlist_add_string(expanderDetails, DATA_FIELD, DEFAULT_DATA)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error adding default data to expander details (%d)", status); } else { if ((status = nvlist_add_nvlist(slmp->slt_expanders, key, expanderDetails)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error storing the default expander details (%d)", status); } } nvlist_free(expanderDetails); return (status); } /* * Retrieves the expander record nvlist that is associated with the * expander identified by the given key. If no match is found, an * entry is created with default values. * Parameters * slmp: A pointer to the ses_log_monitor_t struct for this transport. * key: A pointer to the key for an expander. * expdata: A pointer to a pointer for the last log entry data for this * expander. */ static int get_last_entry(ses_log_monitor_t *slmp, char *key, char **expdata) { nvlist_t *expanderRecord; int err = 0; /* * Retrieve the expander record that matches this expander. A default * entry will be returned if no matching entry is found. */ if ((err = nvlist_lookup_nvlist(slmp->slt_expanders, key, &expanderRecord)) != 0) { if ((err = add_expander_record(slmp, key)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Expander add failed for %s", key); return (err); } if ((err = nvlist_lookup_nvlist(slmp->slt_expanders, key, &expanderRecord)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Could not retrieve the data after adding it", key); return (err); } } if ((err = nvlist_lookup_string(expanderRecord, DATA_FIELD, expdata)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Could not retrieve the expander data field (%d)", err); return (err); } return (err); } /* * Searches the platform lists for target codes. If a match is found then * it calls then indicated function. */ static int check_code(ses_log_monitor_t *slmp, nvlist_t *fmri, char *pid, int code) { int status = 0; int i, x; for (i = 0; i < platforms.pcount; i++) { if (strcmp(platforms.plist[i].pid, pid) == 0) { for (x = 0; x < platforms.plist[i].count; x++) { if (code == platforms.plist[i].codes[x].code) { status = platforms.plist[i].codes[x]. func_ptr(slmp, fmri); break; } } break; } } if (status != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error checking for a code action (%d)", status); } return (status); } /* * Searches the platform lists for for a match on the supplied product id. * Returns non zero if supported, zero otherwise. */ static int platform_supported(char *pid) { int supported = 0; int i; for (i = 0; i < platforms.pcount; i++) { if (strcmp(platforms.plist[i].pid, pid) == 0) { supported = 1; break; } } return (supported); } /* * Inverts the controller instance and the expander instance in the * specified FMRI. */ static int invert_fmri(ses_log_monitor_t *slmp, nvlist_t *fmri) { int err = 0; (void) access_fmri(slmp, fmri, CONTROLLER, INVERT_FMRI_INSTANCE, &err); if (err != 0) { fmd_hdl_debug(slmp->slt_hdl, "error inverting the controller instance: %d", err); return (err); } (void) access_fmri(slmp, fmri, SASEXPANDER, INVERT_FMRI_INSTANCE, &err); if (err != 0) { fmd_hdl_debug(slmp->slt_hdl, "error inverting sas-expander instance: %d", err); } return (err); } /* * Checks the severity of the log entry against the configured boundary, * generates and ereport, and writes the data out to the log file. * Parameters * slmp: A pointer to the ses_log_monitor_t struct for this transport. * entry: The log entry * ena: the ena for this transport. * expander: Contains derived information for this expander. * format_time: The formatted time to append to this entry. * fp: A file pointer for the data to be written out to. */ static int handle_log_entry(ses_log_monitor_t *slmp, nvpair_t *entry, expander_t *expander, char *format_time, FILE *fp) { nvlist_t *entry_data; char *log_entry; char *severity; int severityValue = 0; char *code; char *class_sev = NULL; uint64_t ena; int rval = 0; if ((rval = nvpair_value_nvlist(entry, &entry_data)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Unable to retrieve entry"); return (rval); } if ((rval = nvlist_lookup_string(entry_data, ENTRY_SEVERITY, &severity)) == 0) { severityValue = atoi(severity); if (severityValue >= slmp->slt_severity) { /* * Pull the code and check to see if there are any * special operations to perform for it on the given * platform. */ if ((rval = nvlist_lookup_string(entry_data, ENTRY_CODE, &code)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error retrieving code: %d", rval); return (rval); } /* * Check this code for any actions specific * to this platform. */ (void) check_code(slmp, expander->fmri, expander->slt_pid, atoi(code)); class_sev = error_type(severityValue); if (class_sev == NULL) { fmd_hdl_debug(slmp->slt_hdl, "log severity %d mapped to NULL", severity); return (INVALID_SEVERITY); } /* Create the ENA for this ereport */ ena = fmd_event_ena_create(slmp->slt_hdl); slt_post_ereport(slmp->slt_hdl, slmp->slt_xprt, class_sev, ena, expander->fmri, entry_data); } } else { fmd_hdl_debug(slmp->slt_hdl, "Unable to pull severity from the entry."); return (rval); } /* * Append the log entry to the log file. */ if (fp) { if ((rval = nvlist_lookup_string(entry_data, ENTRY_LOG, &log_entry)) == 0) { (void) fprintf(fp, "%s %s\n", format_time, log_entry); } else { fmd_hdl_debug(slmp->slt_hdl, "Unable to pull log from the entry."); } } return (rval); } /* * The function performs the work of deallocating the space used for an * expander_t structure. * Parameters: * slmp: A pointer to t ses_log_monitor_t struct for this transport. * exp: A pointer to an expander_t structure that identifies an expander. */ static void free_expander(ses_log_monitor_t *slmp, expander_t *exp) { if (exp != NULL) { if (exp->fmri != NULL) { nvlist_free(exp->fmri); } fmd_hdl_free(slmp->slt_hdl, exp, sizeof (expander_t)); } } /* * This function performs the log read on a target * * Parameters: * slmp: A pointer to the ses log monitor structure. * expander: A pointer to an expander object that contains info required * for a call to the libseslog library. * lib_param: The structure used to pass data to and from the library. This * contains the target's information as well as a ponter to returned data. */ static int get_log(ses_log_monitor_t *slmp, expander_t *expander, struct ses_log_call_struct *lib_param) { char *expdata; int err; nvlist_t *expanderRecord; /* Retrieve the last entry for this expander for the lib call */ if ((err = get_last_entry(slmp, expander->slt_key, &expdata)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error collecting expander entry"); return (err); } (void) strncpy(lib_param->target_path, expander->slt_path, MAXPATHLEN); (void) strncpy(lib_param->product_id, expander->slt_pid, MAXNAMELEN); (void) strncpy(lib_param->last_log_entry, expdata, MAXNAMELEN); lib_param->poll_time = slmp->slt_interval; /* * If the library call returned non zero, log it, however, the call * may still have returned valid log data. Check the log data. If it * is NULL, return an error. Otherwise continue processing. */ if ((err = access_ses_log(lib_param)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Library access error: %d", err); } /* Double check that log data actually exists. */ if (lib_param->log_data == NULL) { if (err != 0) { return (err); } return (NULL_LOG_DATA); } /* * If we can retrieve the expander details for this expander then store * the last log entry returned from the library. Otherwise log it * and continue processing. */ if ((err = nvlist_lookup_nvlist(slmp->slt_expanders, expander->slt_key, &expanderRecord)) == 0) { if (nvlist_add_string(expanderRecord, DATA_FIELD, lib_param->last_log_entry) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error saving buffer data in expander details"); } } else { fmd_hdl_debug(slmp->slt_hdl, "Could not retrieve expander to store last entry: %d", err); } return (err); } /* * This function processes the log data from a target. This includes * writing the data to the filesystem and initiating generation of ereports * as needed by calling slt_post_ereport. * * * Parameters: * slmp: A pointer to the ses log monitor structure. * expander: A pointer to an expander object that contains info about the * expander. * lib_param: The structure used to pass data to and from the library. This * contains the target's information as well as a ponter to returned data. */ static int process_log(ses_log_monitor_t *slmp, expander_t *expander, struct ses_log_call_struct *lib_param) { nvlist_t *result; int err; char *pairName; nvpair_t *entry = NULL; FILE *fp = NULL; char fileName[MAXPATHLEN]; time_t now; char format_time[30]; struct tm tim; int output_count; /* * Determine how many bytes will be written out with this response, * pass this count to a function that will determine whether or not * to roll the logs, and will return the name of the file path to use. */ output_count = lib_param->number_log_entries * DATE_STRING_SIZE + lib_param->size_of_log_entries; err = create_filename(fileName, expander, slmp, output_count); if (err == 0) { fp = fopen(fileName, "a"); if (fp == NULL) { fmd_hdl_debug(slmp->slt_hdl, "File open failed"); } } /* Format the time to prepend to the log entry */ now = time(NULL); tim = *(localtime(&now)); (void) strftime(format_time, 30, "%b %d %H:%M:%S ", &tim); /* * For each entry returned, generate an ereport if the severity * is at or above the target level, then append all entries to * the appropriate log file. */ result = lib_param->log_data; while ((entry = nvlist_next_nvpair(result, entry)) != NULL) { pairName = nvpair_name(entry); /* * Process each entry in the result data returned from * the library call. These are log entries and may * warrant an ereport. */ if (strncmp(ENTRY_PREFIX, pairName, 5) == 0) { err = handle_log_entry(slmp, entry, expander, format_time, fp); } } /* Close the log file */ if (fp) { (void) fclose(fp); fp = NULL; } /* Free the space used for the result and the fmri. */ nvlist_free(result); return (0); } /* * This function performs the log read and processing of the logs for a target * as well as writing the data to the filesystem. Ereports are generated * as needed by calling slt_post_ereport. * * Access the log data for a specific ses. * If a log entry should generate an ereport, call slt_post_ereport * Format and store the data at the appropriate location. */ static int slt_process_ses_log(topo_hdl_t *thp, tnode_t *node, void *arg) { ses_log_monitor_t *slmp = arg; nvlist_t *fmri; expander_t *expander; struct ses_log_call_struct lib_param; int err = 0; char *label = NULL; char *target_path = NULL; char *product_id = NULL; char *sas_address = NULL; if (strcmp(SASEXPANDER, topo_node_name(node)) != 0) { /* Not the type of node we are looking for */ return (TOPO_WALK_NEXT); } if (topo_prop_get_string(node, "authority", "product-id", &product_id, &err) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error collecting product_id %d", err); return (TOPO_WALK_NEXT); } /* If the current system type is unsupported stop processing the node */ if (platform_supported(product_id) == 0) { fmd_hdl_debug(slmp->slt_hdl, "Unsupported platform %d", product_id); topo_hdl_strfree(thp, product_id); return (TOPO_WALK_NEXT); } /* Allocate space for the holder structure */ expander = (expander_t *)fmd_hdl_zalloc(slmp->slt_hdl, sizeof (expander_t), FMD_SLEEP); (void) snprintf(expander->slt_pid, MAXNAMELEN, "%s", product_id); topo_hdl_strfree(thp, product_id); if (topo_prop_get_string(node, "protocol", "label", &label, &err) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error collecting label %d", err); free_expander(slmp, expander); return (TOPO_WALK_NEXT); } (void) snprintf(expander->slt_label, MAXNAMELEN, "%s", label); topo_hdl_strfree(thp, label); if (topo_prop_get_string(node, TOPO_PGROUP_SES, TOPO_PROP_SES_DEV_PATH, &target_path, &err) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error collecting ses-devfs-path for %s: %d", expander->slt_label, err); free_expander(slmp, expander); return (TOPO_WALK_NEXT); } (void) snprintf(expander->slt_path, MAXPATHLEN, "%s", target_path); topo_hdl_strfree(thp, target_path); if (topo_prop_get_string(node, TOPO_PGROUP_STORAGE, TOPO_PROP_SAS_ADDR, &sas_address, &err) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error collecting sas_address for %s: %d", expander->slt_label, err); free_expander(slmp, expander); return (TOPO_WALK_NEXT); } if (strlen(sas_address) != 16) { fmd_hdl_debug(slmp->slt_hdl, "sas-address length is not 16: (%s)", sas_address); free_expander(slmp, expander); topo_hdl_strfree(thp, sas_address); return (TOPO_WALK_NEXT); } (void) snprintf(expander->slt_key, MAXNAMELEN, "%s", sas_address); topo_hdl_strfree(thp, sas_address); /* Obtain the fmri for this node and save a reference to it. */ if (topo_node_resource(node, &fmri, &err) != 0) { fmd_hdl_debug(slmp->slt_hdl, "failed to get fmri for %s: %s", expander->slt_label, topo_strerror(err)); free_expander(slmp, expander); return (TOPO_WALK_NEXT); } else { expander->fmri = fmri; } if ((err = get_log(slmp, expander, &lib_param)) != 0) { /* * NULL_LOG_DATA means that no data was returned from the * library. (i.e. There were no log entries.) Just free memory * and return. */ if (err != NULL_LOG_DATA) { fmd_hdl_debug(slmp->slt_hdl, "Error retrieving logs from %s: %d", expander->slt_label, err); } free_expander(slmp, expander); return (TOPO_WALK_NEXT); } if ((err = process_log(slmp, expander, &lib_param)) != 0) { fmd_hdl_debug(slmp->slt_hdl, "Error processing logs from %s: %d", expander->slt_label, err); } /* Free the expander structure before exiting. */ free_expander(slmp, expander); return (TOPO_WALK_NEXT); } /* * Called by the FMD after the specified timeout has expired. * This initiates the processing of the SES device logs. * slt_process_ses_log() performs the actual log retrieval and analysis. * * The last action is to reset the timer so that this method is called again. */ /*ARGSUSED*/ static void slt_timeout(fmd_hdl_t *hdl, id_t id, void *data) { topo_hdl_t *thp; topo_walk_t *twp; int err; /* Retrieve the SES log monitor structure. */ ses_log_monitor_t *slmp = fmd_hdl_getspecific(hdl); if (slmp == NULL) { fmd_hdl_abort(hdl, "Unable to retrieve log monitor structure."); return; } slmp->slt_hdl = hdl; thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); /* * This initializes a topology walk structure for stepping through * the snapshot associated with thp. Note that a callback function * is supplied (slt_process_ses_log in this case). */ if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, slt_process_ses_log, slmp, &err)) == NULL) { fmd_hdl_topo_rele(hdl, thp); fmd_hdl_abort(hdl, "failed to get topology: %s\n", topo_strerror(err)); return; } /* * This function walks through the snapshot and invokes the callback * function supplied when it was set up above. */ if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) { topo_walk_fini(twp); fmd_hdl_topo_rele(hdl, thp); fmd_hdl_abort(hdl, "failed to walk topology\n"); return; } /* This releases the walk structure. */ topo_walk_fini(twp); fmd_hdl_topo_rele(hdl, thp); /* Reset the timer for the next iteration. */ slmp->slt_timer = fmd_timer_install(hdl, NULL, NULL, slmp->slt_interval); } /* * Entry points for the FMD to access this transport. */ static const fmd_hdl_ops_t fmd_ops = { NULL, /* fmdo_recv */ slt_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ NULL, /* fmdo_topo_change */ }; static const fmd_hdl_info_t fmd_info = { "SES Log Transport Agent", "1.0", &fmd_ops, fmd_props }; /* * Initialize the transport. */ void _fmd_init(fmd_hdl_t *hdl) { ses_log_monitor_t *slmp; int error; nvlist_t *expanderList; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (slt_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&slt_stats); slmp = fmd_hdl_zalloc(hdl, sizeof (ses_log_monitor_t), FMD_SLEEP); fmd_hdl_setspecific(hdl, slmp); slmp->slt_xprt = fmd_xprt_open(hdl, FMD_XPRT_RDONLY, NULL, NULL); if (slmp->slt_xprt == NULL) { fmd_hdl_error(hdl, "Unable to obtain a reference to the transport"); fmd_hdl_free(hdl, slmp, sizeof (*slmp)); fmd_hdl_unregister(hdl); return; } /* * interval is validity checked by the framework since it is of type * FMD_TYPE_TIME. */ slmp->slt_interval = fmd_prop_get_int64(hdl, "interval"); /* * Use default the severity if it is out of range. * Setting the severity too high is allowed as this has the effect * of preventing any ereports from being generated. */ slmp->slt_severity = fmd_prop_get_int32(hdl, "severity"); if (slmp->slt_severity < SES_LOG_LEVEL_NOTICE) { slmp->slt_severity = SES_LOG_LEVEL_ERROR; } slmp->slt_log_count = fmd_prop_get_int32(hdl, "logcount"); if (slmp->slt_log_count < MIN_LOG_COUNT) { slmp->slt_log_count = MIN_LOG_COUNT; } slmp->slt_max_log_size = fmd_prop_get_int32(hdl, "maxlogsize"); if (slmp->slt_max_log_size < MIN_LOG_SIZE) { slmp->slt_max_log_size = MIN_LOG_SIZE; } /* Invalid paths will be handled by logging and skipping log creation */ slmp->slt_path = fmd_prop_get_string(hdl, "path"); /* Allocate space for the expander id holder */ if ((error = nvlist_alloc(&expanderList, NV_UNIQUE_NAME, 0)) != 0) { fmd_xprt_close(hdl, slmp->slt_xprt); fmd_hdl_strfree(hdl, slmp->slt_path); fmd_hdl_free(hdl, slmp, sizeof (*slmp)); fmd_hdl_error(hdl, "Error allocating space for the expander list: %d", error); fmd_hdl_unregister(hdl); return; } slmp->slt_expanders = expanderList; /* * Call our initial timer routine, starting the periodic timeout. */ slmp->slt_timer = fmd_timer_install(hdl, NULL, NULL, 0); } /* * Shut down the transport. The primary responsibility is to release any * allocated memory. */ void _fmd_fini(fmd_hdl_t *hdl) { ses_log_monitor_t *slmp; slmp = fmd_hdl_getspecific(hdl); if (slmp) { fmd_timer_remove(hdl, slmp->slt_timer); fmd_xprt_close(hdl, slmp->slt_xprt); fmd_prop_free_string(hdl, slmp->slt_path); nvlist_free(slmp->slt_expanders); fmd_hdl_free(hdl, slmp, sizeof (*slmp)); } } # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" MODULE = sp-monitor CLASS = common SRCS = sp_monitor.c include ../../Makefile.plugin LDLIBS += -lipmi -lsysevent # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2012 Joyent, Inc. All rights reserved. */ /* * /dev/ipmi IPMI monitor * * The purpose of this module is to monitor the connection between the system * and the service processor attached via /dev/ipmi0. The module assumes the SP * supports the Sun OEM uptime IPMI command. If the BMC connection does not * exist, or the uptime function is not implemented, then the module unloads * without doing anything. * * When the module is first loaded, or a reset is detected, the module will * generate the ESC_PLATFORM_SP_RESET sysevent as a system-wide notification to * indicate that this event has occurred. * * Note that this event generation is not guaranteed to have a one-to-one * correspondence with an SP reset. There is no persistence, so if fmd is * restarted we will generate this event again. Thus the event only indicates * the possibility that the SP has been reset. This could be enhanced using fmd * checkpoints to have some persistent state to avoid this scenario. However, * it currently serves the useful dual purpose of notifying consumers of system * startup as well as SP reset through a single channel. */ #include #include #include #include #include #include typedef struct sp_monitor { ipmi_handle_t *sm_hdl; uint32_t sm_seconds; uint32_t sm_generation; hrtime_t sm_interval; } sp_monitor_t; static void sp_post_sysevent(fmd_hdl_t *hdl) { sp_monitor_t *smp = fmd_hdl_getspecific(hdl); sysevent_id_t eid; fmd_hdl_debug(hdl, "SP reset detected, posting sysevent"); if (sysevent_post_event(EC_PLATFORM, ESC_PLATFORM_SP_RESET, SUNW_VENDOR, "fmd", NULL, &eid) != 0) { fmd_hdl_debug(hdl, "failed to send sysevent: %s", strerror(errno)); /* * We reset the seconds and generation so that the next time * through we will try to post the sysevent again. */ smp->sm_seconds = -1U; smp->sm_generation = -1U; } } /*ARGSUSED*/ static void sp_timeout(fmd_hdl_t *hdl, id_t id, void *data) { sp_monitor_t *smp = fmd_hdl_getspecific(hdl); uint32_t seconds, generation; if (ipmi_sunoem_uptime(smp->sm_hdl, &seconds, &generation) != 0) { /* * Ignore uptime failures. We will generate the appropriate * event when it comes back online. */ fmd_hdl_debug(hdl, "failed to get uptime: %s", ipmi_errmsg(smp->sm_hdl)); } else { /* * We want to catch cases where the generation number is * explicitly reset, or when the SP configuration is reset after * a reboot (and the generation number is 0). We also post a * sysevent when the module initially loads, since we can't be * sure if we missed a SP reset or not. */ if (seconds < smp->sm_seconds || generation != smp->sm_generation || smp->sm_seconds == 0) sp_post_sysevent(hdl); smp->sm_seconds = seconds; smp->sm_generation = generation; } (void) fmd_timer_install(hdl, NULL, NULL, smp->sm_interval); } static const fmd_hdl_ops_t fmd_ops = { NULL, /* fmdo_recv */ sp_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ }; static const fmd_prop_t fmd_props[] = { { "interval", FMD_TYPE_TIME, "60sec" }, { NULL, 0, NULL } }; static const fmd_hdl_info_t fmd_info = { "Service Processor Monitor", "1.0", &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { sp_monitor_t *smp; int error; char *msg; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; smp = fmd_hdl_zalloc(hdl, sizeof (sp_monitor_t), FMD_SLEEP); fmd_hdl_setspecific(hdl, smp); if ((smp->sm_hdl = ipmi_open(&error, &msg, IPMI_TRANSPORT_BMC, NULL)) == NULL) { /* * If /dev/ipmi0 doesn't exist on the system, then unload the * module without doing anything. */ if (error != EIPMI_BMC_OPEN_FAILED) fmd_hdl_abort(hdl, "failed to initialize IPMI " "connection: %s\n", msg); fmd_hdl_debug(hdl, "failed to load: no IPMI connection " "present"); fmd_hdl_free(hdl, smp, sizeof (sp_monitor_t)); fmd_hdl_unregister(hdl); return; } /* * Attempt an initial uptime() call. If the IPMI command is * unrecognized, then this is an unsupported platform and the module * should be unloaded. Any other error is treated is transient failure. */ if ((error = ipmi_sunoem_uptime(smp->sm_hdl, &smp->sm_seconds, &smp->sm_generation)) != 0 && ipmi_errno(smp->sm_hdl) == EIPMI_INVALID_COMMAND) { fmd_hdl_debug(hdl, "failed to load: uptime command " "not supported"); ipmi_close(smp->sm_hdl); fmd_hdl_free(hdl, smp, sizeof (sp_monitor_t)); fmd_hdl_unregister(hdl); return; } smp->sm_interval = fmd_prop_get_int64(hdl, "interval"); if (error == 0) fmd_hdl_debug(hdl, "successfully loaded, uptime = %u seconds " "(generation %u)", smp->sm_seconds, smp->sm_generation); else fmd_hdl_debug(hdl, "successfully loaded, but uptime call " "failed: %s", ipmi_errmsg(smp->sm_hdl)); /* * Setup the recurring timer. */ (void) fmd_timer_install(hdl, NULL, NULL, 0); } void _fmd_fini(fmd_hdl_t *hdl) { sp_monitor_t *smp = fmd_hdl_getspecific(hdl); if (smp) { ipmi_close(smp->sm_hdl); fmd_hdl_free(hdl, smp, sizeof (sp_monitor_t)); } } # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # SUBDIRS = software-diagnosis software-response include ../../../Makefile.subdirs # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # CMN_SRCS = common/sw_main_cmn.c SMF_CMN_SRCS = subsidiary/smf/smf_util.c SMF_DE_SRCS = subsidiary/smf/smf_diag.c $(SMF_CMN_SRCS) SMF_RP_SRCS = subsidiary/smf/smf_response.c $(SMF_CMN_SRCS) PANIC_DE_SRCS = subsidiary/panic/panic_diag.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) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _SW_H #define _SW_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include /* * We have two real fmd modules - software-diagnosis and software-response. * Each hosts a number of subsidiary diagnosis engines and response agents, * although these are not fmd modules as such (the intention is to avoid * a proliferation of small C diagnosis and response modules). * * Subsidiary "modules" are not loaded as normal fmd modules are. Instead * each of the real modules software-diagnosis and software-response includes * an array listing the subsidiaries it hosts, and when the real module * is loaded by fmd it iterates over this list to "load" subsidiaries by * calling their nominated init function. */ /* Maximum number of subsidiary "modules" */ #define SW_SUB_MAX 10 /* Maximum number of supported timers across all subsidiaries */ #define SW_TIMER_MAX 20 /* * A subsidiary must perform fmd_hdl_subscribe calls for all events of * interest to it. These are typically performed during its init * function. All subscription callbacks funnel through the shared * fmdo_recv entry point; that function walks through the dispatch list * for each subsidiary and performs a callback for the first matching entry of * each subsidiary. The init entry point for each subsidiary * returns a pointer to an array of struct sw_disp applicable for that * entity. * * Note that the framework does *not* perform any fmd_hdl_subscribe calls * on behalf of the subsidiary - the swd_classpat member below is used * in routing events, not in establishing subscriptions for them. A * subsidiary can subscribe to say "ireport.foo.a" and "ireport.foo.b" * but could elect to nominate a common handler for those via a single * struct sw_disp with swd_classpat of "ireport.foo.*". */ typedef void sw_dispfunc_t(fmd_hdl_t *, fmd_event_t *, nvlist_t *, const char *, void *); struct sw_disp { const char *swd_classpat; /* event classes to callback for */ sw_dispfunc_t *swd_func; /* callback function */ void *swd_arg; /* opaque argument to callback */ }; /* * A diagnosis or response subsidiary must provide a struct sw_subinfo with * all its pertinent information; a pointer to this structure must be * included in the array of struct sw_subinfo pointers in each of * software-diagnosis and software-response. * * swsub_name * This should be chosen to be unique to this subsidiary; * by convention it should also be the name prefix used in any fmd * buffers the subsidiary creates. * * swsub_casetype * A diagnosis subsidiary solves cases using swde_case_* below, and it * must specify in swsub_casetype the type of case it solves. A response * subsidiary must specify SW_CASE_NONE here. A subsidiary may only solve * at most one type of case, and no two subsidiaries must solve the same * case type. We use the case type to associate a subsidiary owner of * the fmd case that is really owned by the host module. * * swsub_init * The initialization function for this subsidiary, akin to the * _fmd_init in a traditional fmd module. This must not be NULL. * * When the host diagnosis/response module initializes the _fmd_init * entry point will call the swsub_init function for each subsidiary * in turn. The fmd handle has already been registered and timers are * available for installation (see below); the swsub_init function must * return a pointer to a NULL-terminated array of struct sw_disp * describing the event dispatch preferences for that module, and fill * an integer we pass with the number of entries in that array (including * the terminating NULL entry). The swsub_init function also receives * a subsidiary-unique id_t assigned by the framework that it should * keep a note of for use in timer installation (see below); this id * should not be persisted to checkpoint data. * * swsub_fini * When the host module _fmd_fini is called it will call this function * for each subsidiary. A subsidiary can specify NULL here. * * swsub_timeout * This is the timeout function to call for expired timers installed by * this subsidiary. See sw_timer_{install,remove} below. May be * NULL if no timers are used by this subsidiary. * * swsub_case_close * This function is called when a case "owned" by a subsidiary * is the subject of an fmdo_close callback. Can be NULL, and * must be NULL for a subsidiary with case type SW_CASE_NONE (such * as a response subsidiary). * * swsub_case_verify * This is called during _fmd_init of the host module. The host module * iterates over all cases that it owns and calls the verify function * for the real owner which may choose to close cases if they no longer * apply. Can be NULL, and must be NULL for a subsidiary with case * type SW_CASE_NONE. */ /* * sw_casetype values are persisted to checkpoints - do not change values. */ enum sw_casetype { SW_CASE_NONE = 0x0ca5e000, SW_CASE_SMF, SW_CASE_PANIC }; /* * Returns for swsub_init. The swsub_fini entry point will only be * called for subsidiaries that returned SW_SUB_INIT_SUCCESS on init. */ #define SW_SUB_INIT_SUCCESS 0 #define SW_SUB_INIT_FAIL_VOLUNTARY 1 /* chose not to init */ #define SW_SUB_INIT_FAIL_ERROR 2 /* error prevented init */ typedef void swsub_case_close_func_t(fmd_hdl_t *, fmd_case_t *); typedef int sw_case_vrfy_func_t(fmd_hdl_t *, fmd_case_t *); struct sw_subinfo { const char *swsub_name; enum sw_casetype swsub_casetype; int (*swsub_init)(fmd_hdl_t *, id_t, const struct sw_disp **, int *); void (*swsub_fini)(fmd_hdl_t *); void (*swsub_timeout)(fmd_hdl_t *, id_t, void *); swsub_case_close_func_t *swsub_case_close; sw_case_vrfy_func_t *swsub_case_verify; }; /* * List sw_subinfo for each subsidiary diagnosis and response "module" here */ extern const struct sw_subinfo smf_diag_info; extern const struct sw_subinfo smf_response_info; extern const struct sw_subinfo panic_diag_info; /* * Timers - as per the fmd module API but with an additional id_t argument * specifying the unique id of the subsidiary installing the timer (provided * to the subsidiary in its swsub_init call). */ extern id_t sw_timer_install(fmd_hdl_t *, id_t, void *, fmd_event_t *, hrtime_t); extern void sw_timer_remove(fmd_hdl_t *, id_t, id_t); /* * The software-diagnosis subsidiaries can open and solve cases; to do so * they must use the following wrappers to the usual fmd module API case * management functions. We need this so that a subsidiary can iterate * over *its* cases (fmd_case_next would iterate over those of other * subsidiaries), receive in the subsidiary a callback when a case it opened * is closed, etc. The subsidiary can use other fmd module API members * for case management, such as fmd_case_add_ereport. * * Each subsidiary opens cases of its own unique type, identified by * the sw_casetype enumeration. The values used in this enumeration * must never change - they are written to checkpoint state. * * swde_case_open * Opens a new case of the correct subsidiary type for the given * subsidiary id. If a uuid string is provided then open a case * with that uuid using fmd_case_open_uuid, allowing case uuid * to match some relevant uuid that was received in one of the * events that has led us to open this case. * * If the subsidiarywishes to associate some persistent * case data with the new case thenit can fmd_hdl_alloc and complete a * suitably-packed serialization structure and include a pointer to it * in the call to sw_case_open together with the structure size and * structure version. The framework will create a new fmd buffer (named * for you, based on the case type) and write the structure out to disk; * when the module or fmd is restarted this structure is restored from * disk for you and reassociated with the case - use swde_case_data to * retrieve a pointer to it. * * swde_case_first, swde_case_next * A subsidiary DE can iterate over its cases using swde_case_first and * swde_case_next. For swde_case_first quote the subsidiary id; * for swde_case_next quote the last case returned. * * swde_case_data * Returns a pointer to the previously-serialized case data, and fills * a uint32_t with the version of that serialized data. * * swde_case_data_write * Whenever a subsidiary modifies its persistent data structure * it must call swde_case_data_write to indicate that the associated * fmd buffer is dirty and needs to be rewritten. * * swde_case_data_upgrade * If the subsidiary ever revs its persistent structure it needs to call * swde_case_data_upgrade to register the new version and structure size, * and write the structure out to a reallocated fmd buffer; the old * case data structure (if any) will be freed. A subsidiary may use * this interface to migrate old persistence structures restored from * checkpoint - swde_case_data will return a version number below the * current. */ extern fmd_case_t *swde_case_open(fmd_hdl_t *, id_t, char *, uint32_t, void *, size_t); extern fmd_case_t *swde_case_first(fmd_hdl_t *, id_t); extern fmd_case_t *swde_case_next(fmd_hdl_t *, fmd_case_t *); extern void *swde_case_data(fmd_hdl_t *, fmd_case_t *, uint32_t *); extern void swde_case_data_write(fmd_hdl_t *, fmd_case_t *); extern void swde_case_data_upgrade(fmd_hdl_t *, fmd_case_t *, uint32_t, void *, size_t); #ifdef __cplusplus } #endif #endif /* _SW_H */ /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _SW_IMPL_H #define _SW_IMPL_H #include "sw.h" /* * The common code between software-response and software-diagnosis * needs somewhere to track the subsidaries that have "registered", * their dispatch tables etc. In the _fmd_init of each module we * call the shared sw_fmd_init code, and there we allocate a * struct sw_modspecific and assign this as the fmd fodule-specific * data with fmd_hdl_setspecific. */ struct sw_modspecific { int swms_dispcnt; const struct sw_subinfo *(*swms_subinfo)[SW_SUB_MAX]; const struct sw_disp *(*swms_disptbl)[SW_SUB_MAX]; pthread_mutex_t swms_timerlock; struct { int swt_state; /* slot in use? */ id_t swt_timerid; /* fmd_timer_install result */ id_t swt_ownerid; /* subsidiary owner id */ } swms_timers[SW_TIMER_MAX]; }; #define SW_TMR_INUSE 1 #define SW_TMR_RMVD 0 #define SW_TMR_UNTOUCHED -1 extern swsub_case_close_func_t *sw_sub_case_close_func(fmd_hdl_t *, enum sw_casetype); extern sw_case_vrfy_func_t *sw_sub_case_vrfy_func(fmd_hdl_t *, enum sw_casetype); /* * Software DE fmdo_close entry point. */ extern void swde_close(fmd_hdl_t *, fmd_case_t *); /* * Shared functions for software-diagnosis and software-response fmd * module implementation using shared code. Subsidiaries do not need * to call these functions. * * sw_fmd_init is called from _fmd_init of the two modules, to do most of * the real work of initializing the subsidiaries etc. * * sw_fmd_fini is called from _fmd_fini and calls the swsub_fini * function of each subsidiary after uninstalling all timers. * * sw_recv is the fmdo_recv entry point; it checks the event against * the dispatch table of each subsidiary and dispatches the first * match for each module. * * sw_timeout is the fmdo_timeout entry point; it looks up the unique id_t * of the subsidiary that installed the timer (via sw_timer_install in which * the id is quoted) and calls the swsub_timeout function for that subsidiary. * * swde_case_init and swde_case_fini initialize and finalize the * software-diagnosis case-tracking infrastructure; swde_case_init * is responsible for unserializing case state. * * sw_id_to_casetype take a subsidiary id and returns the case type it * registered with. */ extern int sw_fmd_init(fmd_hdl_t *, const fmd_hdl_info_t *, const struct sw_subinfo *(*)[SW_SUB_MAX]); extern void sw_fmd_fini(fmd_hdl_t *); extern void sw_recv(fmd_hdl_t *, fmd_event_t *, nvlist_t *, const char *); extern void sw_timeout(fmd_hdl_t *, id_t, void *); extern void swde_case_init(fmd_hdl_t *); extern void swde_case_fini(fmd_hdl_t *); enum sw_casetype sw_id_to_casetype(fmd_hdl_t *, id_t); #endif /* _SW_IMPL_H */ /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Code shared by software-diagnosis and software-response modules. * The fmd module linkage info for the two modules lives in swde_main.c * (for software-diagnosis) and swrp_main.c (for software-response). */ #include "../common/sw_impl.h" /* * Each subsidiary that is hosted is assigned a unique subsidiary id. These * macros convert between the id of a subsidiary and the index used in keeping * track of subsidiaries. Outside of this file these ids should remain * opaque. */ #define ID2IDX(id) ((int)((id) & 0xff0000) >> 16) #define IDX2ID(i) ((id_t)((i) << 16) | 0x1d000000) #define SUBIDVALID(msinfo, id) (((int)(id) & 0xff00ffff) == 0x1d000000 && \ ID2IDX(id) < (msinfo)->swms_dispcnt) static struct { fmd_stat_t sw_recv_total; fmd_stat_t sw_recv_match; fmd_stat_t sw_recv_callback; } sw_stats = { { "sw_recv_total", FMD_TYPE_UINT64, "total events received" }, { "sw_recv_match", FMD_TYPE_UINT64, "events matching some subsidiary" }, { "sw_recv_callback", FMD_TYPE_UINT64, "callbacks to all subsidiaries" }, }; #define BUMPSTAT(stat) sw_stats.stat.fmds_value.ui64++ #define BUMPSTATN(stat, n) sw_stats.stat.fmds_value.ui64 += (n) /* * ========================== Event Receipt ================================= * * The fmdo_recv entry point. See which sub de/response agents have a * matching subscription and callback for the first match from each. * The sub de/response agents should dispatch *all* their subscriptions * via their registered dispatch table, including things like list.repaired. */ void sw_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { struct sw_modspecific *msinfo; int calls = 0; int mod; BUMPSTAT(sw_recv_total); msinfo = (struct sw_modspecific *)fmd_hdl_getspecific(hdl); /* * For each sub module that has a matching class pattern call the * registered callback for that sub DE. Only one match per sub module * is allowed (the first match in its table, others are not checked). */ for (mod = 0; mod < msinfo->swms_dispcnt; mod++) { const struct sw_disp *dp; sw_dispfunc_t *dispf = NULL; for (dp = (*msinfo->swms_disptbl)[mod]; dp != NULL && dp->swd_classpat != NULL; dp++) { if (fmd_nvl_class_match(hdl, nvl, dp->swd_classpat)) { dispf = dp->swd_func; break; } } if (dispf != NULL) { calls++; (*dispf)(hdl, ep, nvl, class, dp->swd_arg); } } BUMPSTAT(sw_recv_match); if (calls) BUMPSTATN(sw_recv_callback, calls); } /* * ========================== Timers ======================================== * * A subsidiary can install a timer; it must pass an additional argument * identifying itself so that we can hand off to the appropriate * swsub_timeout function in the fmdo_timeout entry point when the timer fires. */ id_t sw_timer_install(fmd_hdl_t *hdl, id_t who, void *arg, fmd_event_t *ep, hrtime_t hrt) { struct sw_modspecific *msinfo; const struct sw_subinfo **subinfo; const struct sw_subinfo *sip; int slot, chosen = -1; id_t timerid; msinfo = (struct sw_modspecific *)fmd_hdl_getspecific(hdl); if (!SUBIDVALID(msinfo, who)) fmd_hdl_abort(hdl, "sw_timer_install: invalid subid %d\n", who); subinfo = *msinfo->swms_subinfo; sip = subinfo[ID2IDX(who)]; if (sip-> swsub_timeout == NULL) fmd_hdl_abort(hdl, "sw_timer_install: no swsub_timeout\n"); /* * Look for a slot. Module entry points are single-threaded * in nature, but if someone installs a timer from a door * service function we're contended. */ (void) pthread_mutex_lock(&msinfo->swms_timerlock); for (slot = 0; slot < SW_TIMER_MAX; slot++) { if (msinfo->swms_timers[slot].swt_state != SW_TMR_INUSE) { chosen = slot; break; } } if (chosen == -1) fmd_hdl_abort(hdl, "timer slots exhausted\n"); msinfo->swms_timers[chosen].swt_state = SW_TMR_INUSE; msinfo->swms_timers[chosen].swt_ownerid = who; msinfo->swms_timers[chosen].swt_timerid = timerid = fmd_timer_install(hdl, arg, ep, hrt); (void) pthread_mutex_unlock(&msinfo->swms_timerlock); return (timerid); } /* * Look for a timer installed by a given subsidiary matching timerid. */ static int subtimer_find(struct sw_modspecific *msinfo, id_t who, id_t timerid) { int slot; for (slot = 0; slot < SW_TIMER_MAX; slot++) { if (msinfo->swms_timers[slot].swt_state == SW_TMR_INUSE && (who == -1 || msinfo->swms_timers[slot].swt_ownerid == who) && msinfo->swms_timers[slot].swt_timerid == timerid) return (slot); } return (-1); } void sw_timer_remove(fmd_hdl_t *hdl, id_t who, id_t timerid) { struct sw_modspecific *msinfo; const struct sw_subinfo **subinfo; const struct sw_subinfo *sip; int slot; msinfo = (struct sw_modspecific *)fmd_hdl_getspecific(hdl); if (!SUBIDVALID(msinfo, who)) fmd_hdl_abort(hdl, "sw_timer_remove: invalid subid\n"); subinfo = *msinfo->swms_subinfo; sip = subinfo[ID2IDX(who)]; (void) pthread_mutex_lock(&msinfo->swms_timerlock); if ((slot = subtimer_find(msinfo, who, timerid)) == -1) fmd_hdl_abort(hdl, "sw_timer_remove: timerid %d not found " "for %s\n", timerid, sip->swsub_name); fmd_timer_remove(hdl, timerid); msinfo->swms_timers[slot].swt_state = SW_TMR_RMVD; (void) pthread_mutex_unlock(&msinfo->swms_timerlock); } /* * The fmdo_timeout entry point. */ void sw_timeout(fmd_hdl_t *hdl, id_t timerid, void *arg) { struct sw_modspecific *msinfo; const struct sw_subinfo **subinfo; const struct sw_subinfo *sip; id_t owner; int slot; msinfo = (struct sw_modspecific *)fmd_hdl_getspecific(hdl); (void) pthread_mutex_lock(&msinfo->swms_timerlock); if ((slot = subtimer_find(msinfo, -1, timerid)) == -1) fmd_hdl_abort(hdl, "sw_timeout: timerid %d not found\n"); (void) pthread_mutex_unlock(&msinfo->swms_timerlock); owner = msinfo->swms_timers[slot].swt_ownerid; if (!SUBIDVALID(msinfo, owner)) fmd_hdl_abort(hdl, "sw_timeout: invalid subid\n"); subinfo = *msinfo->swms_subinfo; sip = subinfo[ID2IDX(owner)]; sip->swsub_timeout(hdl, timerid, arg); } /* * ========================== sw_subinfo access ============================= */ enum sw_casetype sw_id_to_casetype(fmd_hdl_t *hdl, id_t who) { struct sw_modspecific *msinfo; const struct sw_subinfo **subinfo; const struct sw_subinfo *sip; msinfo = (struct sw_modspecific *)fmd_hdl_getspecific(hdl); if (!SUBIDVALID(msinfo, who)) fmd_hdl_abort(hdl, "sw_id_to_casetype: invalid subid %d\n", who); subinfo = *msinfo->swms_subinfo; sip = subinfo[ID2IDX(who)]; if ((sip->swsub_casetype & SW_CASE_NONE) != SW_CASE_NONE) fmd_hdl_abort(hdl, "sw_id_to_casetype: bad case type %d " "for %s\n", sip->swsub_casetype, sip->swsub_name); return (sip->swsub_casetype); } /* * Given a case type lookup the struct sw_subinfo for the subsidiary * that opens cases of that type. */ static const struct sw_subinfo * sw_subinfo_bycase(fmd_hdl_t *hdl, enum sw_casetype type) { struct sw_modspecific *msinfo; const struct sw_subinfo **subinfo; const struct sw_subinfo *sip; int i; msinfo = (struct sw_modspecific *)fmd_hdl_getspecific(hdl); subinfo = *msinfo->swms_subinfo; for (i = 0; i < SW_SUB_MAX; i++) { sip = subinfo[i]; if (sip->swsub_casetype == type) return (sip); } return (NULL); } /* * Find the case close function for the given case type; can be NULL. */ swsub_case_close_func_t * sw_sub_case_close_func(fmd_hdl_t *hdl, enum sw_casetype type) { const struct sw_subinfo *sip; if ((sip = sw_subinfo_bycase(hdl, type)) == NULL) fmd_hdl_abort(hdl, "sw_sub_case_close_func: case type " "%d not found\n", type); return (sip->swsub_case_close); } /* * Find the case verify function for the given case type; can be NULL. */ sw_case_vrfy_func_t * sw_sub_case_vrfy_func(fmd_hdl_t *hdl, enum sw_casetype type) { const struct sw_subinfo *sip; if ((sip = sw_subinfo_bycase(hdl, type)) == NULL) fmd_hdl_abort(hdl, "sw_sub_case_vrfy_func: case type " "%d not found\n", type); return (sip->swsub_case_verify); } /* * ========================== Initialization ================================ * * The two modules - software-diagnosis and software-response - call * sw_fmd_init from their _fmd_init entry points. */ static void sw_add_callbacks(fmd_hdl_t *hdl, const char *who, const struct sw_disp *dp, int nelem, struct sw_modspecific *msinfo) { int i; (*msinfo->swms_disptbl)[msinfo->swms_dispcnt++] = dp; if (dp == NULL) return; /* subsidiary failed init */ /* check that the nelem'th entry is the NULL termination */ if (dp[nelem - 1].swd_classpat != NULL || dp[nelem - 1].swd_func != NULL || dp[nelem - 1].swd_arg != NULL) fmd_hdl_abort(hdl, "subsidiary %s dispatch table not NULL-" "terminated\n", who); /* now validate the entries; we allow NULL handlers */ for (i = 0; i < nelem - 1; i++) { if (dp[i].swd_classpat == NULL) fmd_hdl_abort(hdl, "subsidiary %s dispatch table entry " "%d has a NULL pattern or function\n", who, i); } } int sw_fmd_init(fmd_hdl_t *hdl, const fmd_hdl_info_t *hdlinfo, const struct sw_subinfo *(*subsid)[SW_SUB_MAX]) { struct sw_modspecific *msinfo; int i; if (fmd_hdl_register(hdl, FMD_API_VERSION, hdlinfo) != 0) return (0); if (fmd_prop_get_int32(hdl, "enable") != B_TRUE) { fmd_hdl_debug(hdl, "%s disabled though .conf file setting\n", hdlinfo->fmdi_desc); fmd_hdl_unregister(hdl); return (0); } msinfo = fmd_hdl_zalloc(hdl, sizeof (*msinfo), FMD_SLEEP); msinfo->swms_subinfo = subsid; msinfo->swms_disptbl = fmd_hdl_zalloc(hdl, SW_SUB_MAX * sizeof (struct sw_disp *), FMD_SLEEP); (void) pthread_mutex_init(&msinfo->swms_timerlock, NULL); for (i = 0; i < SW_TIMER_MAX; i++) msinfo->swms_timers[i].swt_state = SW_TMR_UNTOUCHED; fmd_hdl_setspecific(hdl, (void *)msinfo); (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (sw_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&sw_stats); /* * Initialize subsidiaries. Each must make any subscription * requests it needs and return a pointer to a NULL-terminated * callback dispatch table and an indication of the number of * entries in that table including the NULL termination entry. */ for (i = 0; i < SW_SUB_MAX; i++) { const struct sw_subinfo *sip = (*subsid)[i]; const struct sw_disp *dp; char dbgbuf[80]; int nelem = -1; int initrslt; if (!sip || sip->swsub_name == NULL) break; initrslt = (*sip->swsub_init)(hdl, IDX2ID(i), &dp, &nelem); (void) snprintf(dbgbuf, sizeof (dbgbuf), "subsidiary %d (id 0x%lx) '%s'", i, IDX2ID(i), sip->swsub_name); switch (initrslt) { case SW_SUB_INIT_SUCCESS: if (dp == NULL || nelem < 1) fmd_hdl_abort(hdl, "%s returned dispatch " "table 0x%p and nelem %d\n", dbgbuf, dp, nelem); fmd_hdl_debug(hdl, "%s initialized\n", dbgbuf); sw_add_callbacks(hdl, sip->swsub_name, dp, nelem, msinfo); break; case SW_SUB_INIT_FAIL_VOLUNTARY: fmd_hdl_debug(hdl, "%s chose not to initialize\n", dbgbuf); sw_add_callbacks(hdl, sip->swsub_name, NULL, -1, msinfo); break; case SW_SUB_INIT_FAIL_ERROR: fmd_hdl_debug(hdl, "%s failed to initialize " "because of an error\n", dbgbuf); sw_add_callbacks(hdl, sip->swsub_name, NULL, -1, msinfo); break; default: fmd_hdl_abort(hdl, "%s returned out-of-range result " "%d\n", dbgbuf, initrslt); break; } } return (1); } void sw_fmd_fini(fmd_hdl_t *hdl) { const struct sw_subinfo **subinfo; struct sw_modspecific *msinfo; int i; msinfo = (struct sw_modspecific *)fmd_hdl_getspecific(hdl); subinfo = *msinfo->swms_subinfo; (void) pthread_mutex_lock(&msinfo->swms_timerlock); for (i = 0; i < SW_TIMER_MAX; i++) { if (msinfo->swms_timers[i].swt_state != SW_TMR_INUSE) continue; fmd_timer_remove(hdl, msinfo->swms_timers[i].swt_timerid); msinfo->swms_timers[i].swt_state = SW_TMR_RMVD; } (void) pthread_mutex_unlock(&msinfo->swms_timerlock); (void) pthread_mutex_destroy(&msinfo->swms_timerlock); for (i = 0; i < msinfo->swms_dispcnt; i++) { const struct sw_subinfo *sip = subinfo[i]; if ((*msinfo->swms_disptbl)[i] == NULL) continue; /* swsub_init did not succeed */ if (sip->swsub_fini != NULL) (*sip->swsub_fini)(hdl); } fmd_hdl_free(hdl, msinfo->swms_disptbl, SW_SUB_MAX * sizeof (struct sw_disp *)); fmd_hdl_setspecific(hdl, NULL); fmd_hdl_free(hdl, msinfo, sizeof (*msinfo)); } # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # MODULE = software-diagnosis CLASS = common include ../Makefile.com # # Sources for the primary software-diagnosis module # SWDE_SRCS = swde_main.c swde_case.c # # Sources for subsidiary diagnosis "modules" that we host. These should # be listed in ../Makefile.com # SUBDE_SRCS = $(SMF_DE_SRCS) $(PANIC_DE_SRCS) # # All sources for softtware-diagnosis # SRCS = $(SWDE_SRCS) $(CMN_SRCS:%=../%) $(SUBDE_SRCS:%=../%) include ../../../Makefile.plugin CFLAGS += $(INCS) LINTFLAGS += $(INCS) LDLIBS += -L$(ROOTLIB)/fm -ltopo -luutil -luuid -lkstat LDFLAGS += -R/usr/lib/fm CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # # # Configuration for the software-diagnosis diagnosis engine. # # # Dictionaries in use by software-diagnosis. The SMF dictionary *must* # be listed before the SUNOS dictionary so that the smf maintenance # defect is found in SMF instead of SUNOS. # dictionary SMF dictionary SUNOS /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include "../common/sw_impl.h" /* * We maintain a single list of all active cases across all * subsidary diagnosis "modules". We also offer some serialization * services to them. * * To open a new case a subsidiary engine should use swde_case_open * indicating the subsidiary id (from which we lookup the enum sw_casetype) * and, optionally, a pointer to a structure for serialization and its size. * * For each case opened with swde_case_open we maintain an swde_case_t * structure in-core. Embedded in this is the swde_case_data_t with * information we need to keep track of and manage this case - it's * case type, buffer name used for the sub-de-private data (if any) * and the size of the sub-de-private structure. It is this * embedded structure which is serialized as the "casedata" buffer, * while the subsidiary-private structure is serialized into another buffer * "casedata_". * * The subsidiary-private data structure, if any, is required to start * with a uint32_t recording the data structure version. This * version is also specified as an argument to swde_case_open, and * we note it in the "casedata" buffer we write out and require * a match on restore. * * When we unserialize we restore our management structure as well as * the sub-de-private structure. * * Here's how serialization works: * * In swde_case_open we create a case data buffer for the case named * SW_CASE_DATA_BUFNAME. We write the buffer out after filling in the * structure version and recording the type of case this is, and if there * is data for the subsidiary then we call swde_subdata to note the * size and version of that data in the case data buf and then to create * and write the subdata in a buffer named SW_CASE_DATA_BUFNAME_. * * If the subsidiary updates its case data it is required to call * swde_case_data_write. This just calls fmd_buf_write for the subsidiary * buffer name. * * A subsidiary can retrieve its private data buffer for a case using * swde_case_data. This also fills a uint32_t with the version of the * buffer that we have for this subsidiary; if that is an old version * the subsidiary can cast appropriately and/or upgrade the buffer as * below. * * When the host module is reloaded it calls swde_case_init to iterate * through all cases we own. For each we call swde_case_unserialize * which restores our case tracking data and any subsidiary-private * data that our case data notes. We then call swde_case_verify which * calls any registered verify function in the subsidiary owner, and if this * returns 0 the case is closed. * * After initial write, we don't usually have to update the * SW_CASE_DATA_BUFNAME buffer unless the subsidiary changes the size or * version of its private buffer. To do that the subsidiary must call * swde_case_data_upgrade. In that function we destroy the old subsidiary * buffer and, if there is still a subsidiary data structure, create a * new buffer appropriately sized and call swde_subdata to write it out * after updating our case structure with new size etc. Finally we write * out our updated case data structure. */ #define SW_CASE_DATA_BUFNAME "casedata" #define SW_CASE_DATA_VERSION_INITIAL 1 #define SW_CASE_DATA_BUFNAMELEN 18 /* 8 + 1 + 8 + 1 */ typedef struct swde_case_data { uint32_t sc_version; /* buffer structure version */ int32_t sc_type; /* enum sw_casetype */ uint32_t sc_sub_bufvers; /* version expected in subsidiary */ char sc_sub_bufname[SW_CASE_DATA_BUFNAMELEN]; /* subsidiary bufname */ int32_t sc_sub_bufsz; /* subsidiary structure size */ } swde_case_data_t; #define SW_CASE_DATA_VERSION SW_CASE_DATA_VERSION_INITIAL /* * In-core case structure. */ typedef struct swde_case { fmd_case_t *swc_fmdcase; /* fmd case handle */ swde_case_data_t swc_data; /* case data for serialization */ void *swc_subdata; /* subsidiary data for serialization */ } swde_case_t; static void swde_case_associate(fmd_hdl_t *hdl, fmd_case_t *cp, swde_case_t *scp, void *subdata) { scp->swc_fmdcase = cp; scp->swc_subdata = subdata; fmd_case_setspecific(hdl, cp, scp); } static void swde_case_unserialize(fmd_hdl_t *hdl, fmd_case_t *cp) { swde_case_t *scp; swde_case_data_t *datap; void *subdata; size_t sz; scp = fmd_hdl_zalloc(hdl, sizeof (*scp), FMD_SLEEP); datap = &scp->swc_data; fmd_buf_read(hdl, cp, SW_CASE_DATA_BUFNAME, datap, sizeof (*datap)); if (datap->sc_version > SW_CASE_DATA_VERSION_INITIAL) { fmd_hdl_free(hdl, scp, sizeof (*scp)); return; } if ((sz = datap->sc_sub_bufsz) != 0) { subdata = fmd_hdl_alloc(hdl, sz, FMD_SLEEP); fmd_buf_read(hdl, cp, datap->sc_sub_bufname, subdata, sz); if (*((uint32_t *)subdata) != datap->sc_sub_bufvers) { fmd_hdl_abort(hdl, "unserialize: expected subdata " "version %u but received %u\n", datap->sc_sub_bufvers, *((uint32_t *)subdata)); } } swde_case_associate(hdl, cp, scp, subdata); } static void swde_subdata(fmd_hdl_t *hdl, fmd_case_t *cp, enum sw_casetype type, swde_case_t *scp, uint32_t subdata_vers, void *subdata, size_t subdata_sz) { swde_case_data_t *datap = &scp->swc_data; if (*((uint32_t *)subdata) != subdata_vers) fmd_hdl_abort(hdl, "swde_subdata: subdata version " "does not match argument\n"); (void) snprintf(datap->sc_sub_bufname, sizeof (datap->sc_sub_bufname), "%s_%08x", SW_CASE_DATA_BUFNAME, type); datap->sc_sub_bufsz = subdata_sz; datap->sc_sub_bufvers = subdata_vers; fmd_buf_create(hdl, cp, datap->sc_sub_bufname, subdata_sz); fmd_buf_write(hdl, cp, datap->sc_sub_bufname, subdata, subdata_sz); } fmd_case_t * swde_case_open(fmd_hdl_t *hdl, id_t who, char *req_uuid, uint32_t subdata_vers, void *subdata, size_t subdata_sz) { enum sw_casetype ct = sw_id_to_casetype(hdl, who); swde_case_data_t *datap; swde_case_t *scp; fmd_case_t *cp; if (ct == SW_CASE_NONE) fmd_hdl_abort(hdl, "swde_case_open for type SW_CASE_NONE\n"); if (subdata != NULL && subdata_sz <= sizeof (uint32_t) || subdata_sz != 0 && subdata == NULL) fmd_hdl_abort(hdl, "swde_case_open: bad subdata\n", ct); scp = fmd_hdl_zalloc(hdl, sizeof (*scp), FMD_SLEEP); datap = &scp->swc_data; if (req_uuid == NULL) { cp = fmd_case_open(hdl, (void *)scp); } else { cp = fmd_case_open_uuid(hdl, req_uuid, (void *)scp); if (cp == NULL) { fmd_hdl_free(hdl, scp, sizeof (*scp)); return (NULL); } } fmd_buf_create(hdl, cp, SW_CASE_DATA_BUFNAME, sizeof (*datap)); datap->sc_version = SW_CASE_DATA_VERSION_INITIAL; datap->sc_type = ct; if (subdata) swde_subdata(hdl, cp, ct, scp, subdata_vers, subdata, subdata_sz); fmd_buf_write(hdl, cp, SW_CASE_DATA_BUFNAME, datap, sizeof (*datap)); swde_case_associate(hdl, cp, scp, subdata); return (cp); } /* * fmdo_close entry point for software-diagnosis */ void swde_close(fmd_hdl_t *hdl, fmd_case_t *cp) { swde_case_t *scp = fmd_case_getspecific(hdl, cp); swde_case_data_t *datap = &scp->swc_data; swsub_case_close_func_t *closefunc; if ((closefunc = sw_sub_case_close_func(hdl, datap->sc_type)) != NULL) closefunc(hdl, cp); /* * Now that the sub-de has had a chance to clean up, do some ourselves. * Note that we free the sub-de-private subdata structure. */ if (scp->swc_subdata) { fmd_hdl_free(hdl, scp->swc_subdata, datap->sc_sub_bufsz); fmd_buf_destroy(hdl, cp, datap->sc_sub_bufname); } fmd_buf_destroy(hdl, cp, SW_CASE_DATA_BUFNAME); fmd_hdl_free(hdl, scp, sizeof (*scp)); } fmd_case_t * swde_case_first(fmd_hdl_t *hdl, id_t who) { enum sw_casetype ct = sw_id_to_casetype(hdl, who); swde_case_t *scp; fmd_case_t *cp; if (ct == SW_CASE_NONE) fmd_hdl_abort(hdl, "swde_case_first for type SW_CASE_NONE\n"); for (cp = fmd_case_next(hdl, NULL); cp; cp = fmd_case_next(hdl, cp)) { scp = fmd_case_getspecific(hdl, cp); if (scp->swc_data.sc_type == ct) break; } return (cp); } fmd_case_t * swde_case_next(fmd_hdl_t *hdl, fmd_case_t *lastcp) { swde_case_t *scp; fmd_case_t *cp; int ct; if (lastcp == NULL) fmd_hdl_abort(hdl, "swde_case_next called for NULL lastcp\n"); scp = fmd_case_getspecific(hdl, lastcp); ct = scp->swc_data.sc_type; cp = lastcp; while ((cp = fmd_case_next(hdl, cp)) != NULL) { scp = fmd_case_getspecific(hdl, cp); if (scp->swc_data.sc_type == ct) break; } return (cp); } void * swde_case_data(fmd_hdl_t *hdl, fmd_case_t *cp, uint32_t *svp) { swde_case_t *scp = fmd_case_getspecific(hdl, cp); swde_case_data_t *datap = &scp->swc_data; if (svp != NULL && scp->swc_subdata) *svp = datap->sc_sub_bufvers; return (scp->swc_subdata); } void swde_case_data_write(fmd_hdl_t *hdl, fmd_case_t *cp) { swde_case_t *scp = fmd_case_getspecific(hdl, cp); swde_case_data_t *datap = &scp->swc_data; if (scp->swc_subdata == NULL) return; fmd_buf_write(hdl, cp, scp->swc_data.sc_sub_bufname, scp->swc_subdata, datap->sc_sub_bufsz); } void swde_case_data_upgrade(fmd_hdl_t *hdl, fmd_case_t *cp, uint32_t subdata_vers, void *subdata, size_t subdata_sz) { swde_case_t *scp = fmd_case_getspecific(hdl, cp); swde_case_data_t *datap = &scp->swc_data; if (scp->swc_subdata) { fmd_buf_destroy(hdl, cp, datap->sc_sub_bufname); fmd_hdl_free(hdl, scp->swc_subdata, datap->sc_sub_bufsz); scp->swc_subdata = NULL; datap->sc_sub_bufsz = 0; datap->sc_sub_bufname[0] = '\0'; } if (subdata != NULL) { scp->swc_subdata = subdata; swde_subdata(hdl, cp, datap->sc_type, scp, subdata_vers, subdata, subdata_sz); } fmd_buf_write(hdl, scp->swc_fmdcase, SW_CASE_DATA_BUFNAME, datap, sizeof (*datap)); } static void swde_case_verify(fmd_hdl_t *hdl, fmd_case_t *cp) { swde_case_t *scp = fmd_case_getspecific(hdl, cp); swde_case_data_t *datap = &scp->swc_data; sw_case_vrfy_func_t *vrfy_func; if ((vrfy_func = sw_sub_case_vrfy_func(hdl, datap->sc_type)) != NULL) { if (vrfy_func(hdl, cp) == 0) fmd_case_close(hdl, cp); } } void swde_case_init(fmd_hdl_t *hdl) { fmd_case_t *cp; for (cp = fmd_case_next(hdl, NULL); cp; cp = fmd_case_next(hdl, cp)) { swde_case_unserialize(hdl, cp); swde_case_verify(hdl, cp); } } /*ARGSUSED*/ void swde_case_fini(fmd_hdl_t *hdl) { } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include "../common/sw_impl.h" static const fmd_prop_t swde_props[] = { { "enable", FMD_TYPE_BOOL, "true" }, { NULL, 0, NULL } }; static const fmd_hdl_ops_t swde_ops = { sw_recv, /* fmdo_recv - provided by common code */ sw_timeout, /* fmdo_timeout - provided by common code */ swde_close, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ NULL /* fmdo_topo */ }; const fmd_hdl_info_t swde_info = { "Software Diagnosis engine", "0.1", &swde_ops, swde_props }; /* * Subsidiary diagnosis "modules" that we host. */ static const struct sw_subinfo *subde[SW_SUB_MAX] = { &smf_diag_info, &panic_diag_info }; void _fmd_init(fmd_hdl_t *hdl) { if (sw_fmd_init(hdl, &swde_info, &subde)) swde_case_init(hdl); } void _fmd_fini(fmd_hdl_t *hdl) { swde_case_fini(hdl); sw_fmd_fini(hdl); } # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # MODULE = software-response CLASS = common include ../Makefile.com SWRP_SRCS = swrp_main.c SUBRP_SRCS = $(SMF_RP_SRCS) SRCS = $(SWRP_SRCS) $(CMN_SRCS:%=../%) $(SUBRP_SRCS:%=../%) include ../../../Makefile.plugin CFLAGS += $(INCS) LINTFLAGS += $(INCS) LDLIBS += -L$(ROOTLIB)/fm -ltopo -lscf LDFLAGS += -R/usr/lib/fm # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # # # Configuration for the software-diagnosis diagnosis engine. # subscribe list.repaired /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include "../common/sw_impl.h" static const fmd_prop_t swrp_props[] = { { "enable", FMD_TYPE_BOOL, "true" }, { NULL, 0, NULL } }; static const fmd_hdl_ops_t swrp_ops = { sw_recv, /* fmdo_recv - provided by common code */ sw_timeout, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ NULL, /* fmdo_send */ NULL /* fmdo_topo */ }; const fmd_hdl_info_t swrp_info = { "Software Response Agent", "0.1", &swrp_ops, swrp_props }; /* * Subsidiary response "modules" that we host. */ static const struct sw_subinfo *subrp[SW_SUB_MAX] = { &smf_response_info, }; void _fmd_init(fmd_hdl_t *hdl) { (void) sw_fmd_init(hdl, &swrp_info, &subrp); } void _fmd_fini(fmd_hdl_t *hdl) { sw_fmd_fini(hdl); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _PANIC_H #define _PANIC_H #ifdef __cplusplus extern "C" { #endif #define SW_SUNOS_PANIC_DETECTED "ireport.os.sunos.panic.dump_pending_on_device" #define SW_SUNOS_PANIC_FAILURE "ireport.os.sunos.panic.savecore_failure" #define SW_SUNOS_PANIC_AVAIL "ireport.os.sunos.panic.dump_available" #define SW_SUNOS_PANIC_DEFECT "defect.sunos.kernel.panic" extern char *sw_panic_fmri2str(fmd_hdl_t *, nvlist_t *); #ifdef __cplusplus } #endif #endif /* _PANIC_H */ /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Panic software-diagnosis subsidiary * * We model a system panic as a defect diagnosis in FMA. When a system * panicks, savecore publishes events which we subscribe to here. * * Our driving events are all raised by savecore, run either from * startup of the dumpadm service or interactively at the command line. * The following describes the logic for the handling of these events. * * On reboot after panic we will run savecore as part of the dumpadm * service startup; we run savecore even if savecore is otherwise * disabled (ie dumpadm -n in effect) - we run savecore -c to check for * a valid dump and raise the initial event. * * If savecore (or savecore -c) observes a valid dump pending on the * device, it raises a "dump_pending_on_device" event provided this * was not an FMA-initiated panic (for those we will replay ereports * from the dump device as usual and make a diagnosis from those; we do * not need to open a case for the panic). We subscribe to the * "dump_pending_on_device" event and use that to open a case; we * open a case requesting the same case uuid as the panic dump image * has for the OS instance uuid - if that fails because of a duplicate * uuid then we have already opened a case for this panic so no need * to open another. * * Included in the "dump_pending_on_device" event is an indication of * whether or not dumpadm is enabled. If not (dumpadm -n in effect) * then we do not expect any further events regarding this panic * until such time as the admin runs savecore manually (if ever). * So in this case we solve the case immediately after open. If/when * subsequent events arrive when savecore is run manually, we will toss * them. * * If dumpadm is enabled then savecore, run from dumpadm service startup, * will attempt to process the dump - either to copy it off the dump * device (if saving compressed) or to uncompress it off the dump device. * If this succeeds savecore raises a "dump_available" event which * includes information on the directory it was saved in, the instance * number, image uuid, compressed form or not, and whether the dump * was complete (as per the dumphdr). If the savecore fails for * some reason then it exits and raises a "savecore_failure" event. * These two events are raised even for FMA-initiated panics. * * We subscribe to both the "dump_available" and "savecore_failed" events, * and in the handling thereof we will close the case opened earlier (if * this is not an FMA-initiated panic). On receipt of the initial * "dump_available" event we also arm a timer for +10 minutes if * dumpadm is enabled - if no "dump_available" or "savecore_failed" arrives * in that time we will solve the case on timeout. * * When the timer fires we check whether the initial event for each panic * case was received more than 30 minutes ago; if it was we solve the case * with what we have. If we're still within the waiting period we rearm * for a further 10 minutes. The timer is shared by all cases that we * create, which is why the fire interval is shorter than the maximum time * we are prepared to wait. */ #include #include #include #include #include "../../common/sw.h" #include "panic.h" #define MAX_STRING_LEN 160 static id_t myid; static id_t mytimerid; /* * Our serialization structure type. */ #define SWDE_PANIC_CASEDATA_VERS 1 typedef struct swde_panic_casedata { uint32_t scd_vers; /* must be first member */ uint64_t scd_receive_time; /* when we first knew of this panic */ size_t scd_nvlbufsz; /* size of following buffer */ /* packed attr nvlist follows */ } swde_panic_casedata_t; static struct { fmd_stat_t swde_panic_diagnosed; fmd_stat_t swde_panic_badclass; fmd_stat_t swde_panic_noattr; fmd_stat_t swde_panic_unexpected_fm_panic; fmd_stat_t swde_panic_badattr; fmd_stat_t swde_panic_badfmri; fmd_stat_t swde_panic_noinstance; fmd_stat_t swde_panic_nouuid; fmd_stat_t swde_panic_dupuuid; fmd_stat_t swde_panic_nocase; fmd_stat_t swde_panic_notime; fmd_stat_t swde_panic_nopanicstr; fmd_stat_t swde_panic_nodumpdir; fmd_stat_t swde_panic_nostack; fmd_stat_t swde_panic_incomplete; fmd_stat_t swde_panic_failed; fmd_stat_t swde_panic_basecasedata; fmd_stat_t swde_panic_failsrlz; } swde_panic_stats = { { "swde_panic_diagnosed", FMD_TYPE_UINT64, "panic defects published" }, { "swde_panic_badclass", FMD_TYPE_UINT64, "incorrect event class received" }, { "swde_panic_noattr", FMD_TYPE_UINT64, "malformed event - missing attr nvlist" }, { "swde_panic_unexpected_fm_panic", FMD_TYPE_UINT64, "dump available for an fm_panic()" }, { "swde_panic_badattr", FMD_TYPE_UINT64, "malformed event - invalid attr list" }, { "swde_panic_badfmri", FMD_TYPE_UINT64, "malformed event - fmri2str fails" }, { "swde_panic_noinstance", FMD_TYPE_UINT64, "malformed event - no instance number" }, { "swde_panic_nouuid", FMD_TYPE_UINT64, "malformed event - missing uuid" }, { "swde_panic_dupuuid", FMD_TYPE_UINT64, "duplicate events received" }, { "swde_panic_nocase", FMD_TYPE_UINT64, "case missing for uuid" }, { "swde_panic_notime", FMD_TYPE_UINT64, "missing crash dump time" }, { "swde_panic_nopanicstr", FMD_TYPE_UINT64, "missing panic string" }, { "swde_panic_nodumpdir", FMD_TYPE_UINT64, "missing crashdump save directory" }, { "swde_panic_nostack", FMD_TYPE_UINT64, "missing panic stack" }, { "swde_panic_incomplete", FMD_TYPE_UINT64, "missing panic incomplete" }, { "swde_panic_failed", FMD_TYPE_UINT64, "missing panic failed" }, { "swde_panic_badcasedata", FMD_TYPE_UINT64, "bad case data during timeout" }, { "swde_panic_failsrlz", FMD_TYPE_UINT64, "failures to serialize case data" }, }; #define BUMPSTAT(stat) swde_panic_stats.stat.fmds_value.ui64++ static nvlist_t * panic_sw_fmri(fmd_hdl_t *hdl, char *object) { nvlist_t *fmri; nvlist_t *sw_obj; int err = 0; fmri = fmd_nvl_alloc(hdl, FMD_SLEEP); err |= nvlist_add_uint8(fmri, FM_VERSION, FM_SW_SCHEME_VERSION); err |= nvlist_add_string(fmri, FM_FMRI_SCHEME, FM_FMRI_SCHEME_SW); sw_obj = fmd_nvl_alloc(hdl, FMD_SLEEP); err |= nvlist_add_string(sw_obj, FM_FMRI_SW_OBJ_PATH, object); err |= nvlist_add_nvlist(fmri, FM_FMRI_SW_OBJ, sw_obj); nvlist_free(sw_obj); if (!err) return (fmri); else return (0); } static const char *dumpfiles[2] = { "unix.%lld", "vmcore.%lld" }; static const char *dumpfiles_comp[2] = { "vmdump.%lld", NULL}; static void swde_panic_solve(fmd_hdl_t *hdl, fmd_case_t *cp, nvlist_t *attr, fmd_event_t *ep, boolean_t savecore_success) { char *dumpdir, *path, *uuid; nvlist_t *defect, *rsrc; nvpair_t *nvp; int i; /* * Attribute members to include in event-specific defect * payload. Some attributes will not be present for some * cases - e.g., if we timed out and solved the case without * a "dump_available" report. */ const char *toadd[] = { "os-instance-uuid", /* same as case uuid */ "panicstr", /* for initial classification work */ "panicstack", /* for initial classification work */ "crashtime", /* in epoch time */ "panic-time", /* Formatted crash time */ }; if (ep != NULL) fmd_case_add_ereport(hdl, cp, ep); /* * As a temporary solution we create and fmri in the sw scheme * in panic_sw_fmri. This should become a generic fmri constructor * * We need to user a resource FMRI which will have a sufficiently * unique string representation such that fmd will not see * repeated panic diagnoses (all using the same defect class) * as duplicates and discard later cases. We can't actually diagnose * the panic to anything specific (e.g., a path to a module and * function/line etc therein). We could pick on a generic * representative such as /kernel/genunix but that could lead * to misunderstanding. So we choose a path based on * and the OS instance UUID - "/.". * There's no file at that path (*) but no matter. We can't use * /vmdump.N or similar because if savecore is disabled * or failed we don't have any file or instance number. * * (*) Some day it would seem tidier to keep all files to do * with a single crash (unix/vmcore/vmdump, analysis output etc) * in a distinct directory, and /. seems like a good * choice. For compatability we'd symlink into it. So that is * another reason for this choice - some day it may exist! */ (void) nvlist_lookup_string(attr, "dumpdir", &dumpdir); (void) nvlist_lookup_string(attr, "os-instance-uuid", &uuid); path = alloca(strlen(dumpdir) + 1 + 1 + 36 + 1); /* LINTED: E_SEC_SPRINTF_UNBOUNDED_COPY */ (void) sprintf(path, "%s/.%s", dumpdir, uuid); rsrc = panic_sw_fmri(hdl, path); defect = fmd_nvl_create_defect(hdl, SW_SUNOS_PANIC_DEFECT, 100, rsrc, NULL, rsrc); nvlist_free(rsrc); (void) nvlist_add_boolean_value(defect, "savecore-succcess", savecore_success); if (savecore_success) { boolean_t compressed; int64_t instance; const char **pathfmts; char buf[2][32]; int files = 0; char *arr[2]; int i; (void) nvlist_lookup_int64(attr, "instance", &instance); (void) nvlist_lookup_boolean_value(attr, "compressed", &compressed); pathfmts = compressed ? &dumpfiles_comp[0] : &dumpfiles[0]; for (i = 0; i < 2; i++) { if (pathfmts[i] == NULL) { arr[i] = NULL; continue; } (void) snprintf(buf[i], 32, pathfmts[i], instance); arr[i] = buf[i]; files++; } (void) nvlist_add_string(defect, "dump-dir", dumpdir); (void) nvlist_add_string_array(defect, "dump-files", arr, files); } else { char *rsn; if (nvlist_lookup_string(attr, "failure-reason", &rsn) == 0) (void) nvlist_add_string(defect, "failure-reason", rsn); } /* * Not all attributes will necessarily be available - eg if * dumpadm was not enabled there'll be no instance and dumpdir. */ for (i = 0; i < sizeof (toadd) / sizeof (toadd[0]); i++) { if (nvlist_lookup_nvpair(attr, toadd[i], &nvp) == 0) (void) nvlist_add_nvpair(defect, nvp); } fmd_case_add_suspect(hdl, cp, defect); fmd_case_solve(hdl, cp); /* * Close the case. Do no free casedata - framework does that for us * on closure callback. */ fmd_case_close(hdl, cp); BUMPSTAT(swde_panic_diagnosed); } /*ARGSUSED*/ static void swde_panic_timeout(fmd_hdl_t *hdl, id_t timerid, void *data) { fmd_case_t *cp = swde_case_first(hdl, myid); swde_panic_casedata_t *cdp; time_t now = time(NULL); nvlist_t *attr; int remain = 0; uint32_t vers; while (cp != NULL) { cdp = swde_case_data(hdl, cp, &vers); if (vers != SWDE_PANIC_CASEDATA_VERS) fmd_hdl_abort(hdl, "case data version confused\n"); if (now > cdp->scd_receive_time + 30 * 60) { if (nvlist_unpack((char *)cdp + sizeof (*cdp), cdp->scd_nvlbufsz, &attr, 0) == 0) { swde_panic_solve(hdl, cp, attr, NULL, B_FALSE); nvlist_free(attr); } else { BUMPSTAT(swde_panic_basecasedata); fmd_case_close(hdl, cp); } } else { remain++; } cp = swde_case_next(hdl, cp); } if (remain) { mytimerid = sw_timer_install(hdl, myid, NULL, NULL, 10ULL * NANOSEC * 60); } } /* * Our verify entry point is called for each of our open cases during * module load. We must return 0 for the case to be closed by our caller, * or 1 to keep it (or if we have already closed it during this call). */ static int swde_panic_vrfy(fmd_hdl_t *hdl, fmd_case_t *cp) { swde_panic_casedata_t *cdp; time_t now = time(NULL); nvlist_t *attr; uint32_t vers; cdp = swde_case_data(hdl, cp, &vers); if (vers != SWDE_PANIC_CASEDATA_VERS) return (0); /* case will be closed */ if (now > cdp->scd_receive_time + 30 * 60) { if (nvlist_unpack((char *)cdp + sizeof (*cdp), cdp->scd_nvlbufsz, &attr, 0) == 0) { swde_panic_solve(hdl, cp, attr, NULL, B_FALSE); nvlist_free(attr); return (1); /* case already closed */ } else { return (0); /* close case */ } } if (mytimerid != 0) mytimerid = sw_timer_install(hdl, myid, NULL, NULL, 10ULL * NANOSEC * 60); return (1); /* retain case */ } /* * Handler for ireport.os.sunos.panic.dump_pending_on_device. * * A future RFE should try adding a means of avoiding diagnosing repeated * defects on panic loops, which would just add to the mayhem and potentially * log lots of calls through ASR. Panics with similar enough panic * strings and/or stacks should not diagnose to new defects with some * period of time, for example. */ /*ARGSUSED*/ void swde_panic_detected(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class, void *arg) { boolean_t fm_panic, expect_savecore; swde_panic_casedata_t *cdp; nvlist_t *attr; fmd_case_t *cp; char *fmribuf; char *uuid; size_t sz; fmd_hdl_debug(hdl, "swde_panic_detected\n"); if (nvlist_lookup_nvlist(nvl, FM_IREPORT_ATTRIBUTES, &attr) != 0) { BUMPSTAT(swde_panic_noattr); return; } if (nvlist_lookup_string(attr, "os-instance-uuid", &uuid) != 0) { BUMPSTAT(swde_panic_nouuid); return; } fmd_hdl_debug(hdl, "swde_panic_detected: OS instance %s\n", uuid); if (nvlist_lookup_boolean_value(attr, "fm-panic", &fm_panic) != 0 || fm_panic == B_TRUE) { BUMPSTAT(swde_panic_unexpected_fm_panic); return; } /* * Prepare serialization data to be associated with a new * case. Our serialization data consists of a swde_panic_casedata_t * structure followed by a packed nvlist of the attributes of * the initial event. */ if (nvlist_size(attr, &sz, NV_ENCODE_NATIVE) != 0) { BUMPSTAT(swde_panic_failsrlz); return; } cdp = fmd_hdl_zalloc(hdl, sizeof (*cdp) + sz, FMD_SLEEP); fmribuf = (char *)cdp + sizeof (*cdp); cdp->scd_vers = SWDE_PANIC_CASEDATA_VERS; cdp->scd_receive_time = time(NULL); cdp->scd_nvlbufsz = sz; /* * Open a case with UUID matching the the panicking kernel, add this * event to the case. */ if ((cp = swde_case_open(hdl, myid, uuid, SWDE_PANIC_CASEDATA_VERS, cdp, sizeof (*cdp) + sz)) == NULL) { BUMPSTAT(swde_panic_dupuuid); fmd_hdl_debug(hdl, "swde_case_open returned NULL - dup?\n"); fmd_hdl_free(hdl, cdp, sizeof (*cdp) + sz); return; } fmd_case_setprincipal(hdl, cp, ep); if (nvlist_lookup_boolean_value(attr, "will-attempt-savecore", &expect_savecore) != 0 || expect_savecore == B_FALSE) { fmd_hdl_debug(hdl, "savecore not being attempted - " "solve now\n"); swde_panic_solve(hdl, cp, attr, ep, B_FALSE); return; } /* * We expect to see either a "dump_available" or a "savecore_failed" * event before too long. In case that never shows up, for whatever * reason, we want to be able to solve the case anyway. */ fmd_case_add_ereport(hdl, cp, ep); (void) nvlist_pack(attr, &fmribuf, &sz, NV_ENCODE_NATIVE, 0); swde_case_data_write(hdl, cp); if (mytimerid == 0) { mytimerid = sw_timer_install(hdl, myid, NULL, ep, 10ULL * NANOSEC * 60); fmd_hdl_debug(hdl, "armed timer\n"); } else { fmd_hdl_debug(hdl, "timer already armed\n"); } } /* * savecore has now run and saved a crash dump to the filesystem. It is * either a compressed dump (vmdump.n) or uncompressed {unix.n, vmcore.n} * Savecore has raised an ireport to say the dump is there. */ /*ARGSUSED*/ void swde_panic_savecore_done(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class, void *arg) { boolean_t savecore_success = (arg != NULL); boolean_t fm_panic; nvlist_t *attr; fmd_case_t *cp; char *uuid; fmd_hdl_debug(hdl, "savecore_done (%s)\n", savecore_success ? "success" : "fail"); if (nvlist_lookup_nvlist(nvl, FM_IREPORT_ATTRIBUTES, &attr) != 0) { BUMPSTAT(swde_panic_noattr); return; } if (nvlist_lookup_boolean_value(attr, "fm-panic", &fm_panic) != 0 || fm_panic == B_TRUE) { return; /* not expected, but just in case */ } if (nvlist_lookup_string(attr, "os-instance-uuid", &uuid) != 0) { BUMPSTAT(swde_panic_nouuid); return; } /* * Find the case related to the panicking kernel; our cases have * the same uuid as the crashed OS image. */ cp = fmd_case_uulookup(hdl, uuid); if (!cp) { /* Unable to find the case. */ fmd_hdl_debug(hdl, "savecore_done: can't find case for " "image %s\n", uuid); BUMPSTAT(swde_panic_nocase); return; } fmd_hdl_debug(hdl, "savecore_done: solving case %s\n", uuid); swde_panic_solve(hdl, cp, attr, ep, savecore_success); } const struct sw_disp swde_panic_disp[] = { { SW_SUNOS_PANIC_DETECTED, swde_panic_detected, NULL }, { SW_SUNOS_PANIC_AVAIL, swde_panic_savecore_done, (void *)1 }, { SW_SUNOS_PANIC_FAILURE, swde_panic_savecore_done, NULL }, /* * Something has to subscribe to every fault * or defect diagnosed in fmd. We do that here, but throw it away. */ { SW_SUNOS_PANIC_DEFECT, NULL, NULL }, { NULL, NULL, NULL } }; /*ARGSUSED*/ int swde_panic_init(fmd_hdl_t *hdl, id_t id, const struct sw_disp **dpp, int *nelemp) { myid = id; if (getzoneid() != GLOBAL_ZONEID) return (SW_SUB_INIT_FAIL_VOLUNTARY); (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (swde_panic_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&swde_panic_stats); fmd_hdl_subscribe(hdl, SW_SUNOS_PANIC_DETECTED); fmd_hdl_subscribe(hdl, SW_SUNOS_PANIC_FAILURE); fmd_hdl_subscribe(hdl, SW_SUNOS_PANIC_AVAIL); *dpp = &swde_panic_disp[0]; *nelemp = sizeof (swde_panic_disp) / sizeof (swde_panic_disp[0]); return (SW_SUB_INIT_SUCCESS); } void swde_panic_fini(fmd_hdl_t *hdl) { if (mytimerid) sw_timer_remove(hdl, myid, mytimerid); } const struct sw_subinfo panic_diag_info = { "panic diagnosis", /* swsub_name */ SW_CASE_PANIC, /* swsub_casetype */ swde_panic_init, /* swsub_init */ swde_panic_fini, /* swsub_fini */ swde_panic_timeout, /* swsub_timeout */ NULL, /* swsub_case_close */ swde_panic_vrfy, /* swsub_case_vrfy */ }; /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _SMF_H #define _SMF_H #ifdef __cplusplus extern "C" { #endif #define TRANCLASS(leaf) "ireport.os.smf.state-transition." leaf #define SW_SMF_MAINT_DEFECT "defect.sunos.smf.svc.maintenance" extern char *sw_smf_svcfmri2str(fmd_hdl_t *, nvlist_t *); extern char *sw_smf_svcfmri2shortstr(fmd_hdl_t *, nvlist_t *); #ifdef __cplusplus } #endif #endif /* _SMF_H */ /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * SMF software-diagnosis subsidiary * * We model service instances in maintenance state as a defect diagnosis * in FMA. When an instance transitions to maintenance state the SMF * graph engine publishes an event which we subscribe to here, and diagnose * a corresponding defect. * * We always solve a case immediately after opening it. But we leave the * case close action to the response agent which needs to cache case UUIDs. * So in the normal case, where software-response is loaded and operational, * our cases will transition to CLOSED state moments after we solve them. * But if fmd restarts in the interim or if software-response is not loaded * then our cases may hang around in SOLVED state for a while, which means * we could iterate over them on receipt of new events. But we don't - * we blindly solve a new case for every new maintenance event received, * and leave it to the fmd duplicate detection and history-based diagnosis * logic to do the right thing. * * Our sibling SMF response subsidiary propogates fmadm-initiated repairs * into SMF, and svcadm-initiated clears back into FMA. In both cases * the case is moved on to the RESOLVED state, even if fmd is unable to * verify that the service is out of maintenance state (i.e., no longer * isolated). If the service immediately re-enters maintenance state then * we diagnose a fresh case. The history-based diagnosis changes in fmd * "do the right thing" and avoid throwing away new cases as duplicates * of old ones hanging around in the "resolved but not all usable again" * state. */ #include #include #include #include "../../common/sw.h" #include "smf.h" static id_t myid; static struct { fmd_stat_t swde_smf_diagnosed; fmd_stat_t swde_smf_bad_class; fmd_stat_t swde_smf_no_attr; fmd_stat_t swde_smf_bad_attr; fmd_stat_t swde_smf_bad_fmri; fmd_stat_t swde_smf_no_uuid; fmd_stat_t swde_smf_no_reason_short; fmd_stat_t swde_smf_no_reason_long; fmd_stat_t swde_smf_no_svcname; fmd_stat_t swde_smf_admin_maint_drop; fmd_stat_t swde_smf_bad_nvlist_pack; fmd_stat_t swde_smf_dupuuid; } swde_smf_stats = { { "swde_smf_diagnosed", FMD_TYPE_UINT64, "maintenance state defects published" }, { "swde_smf_bad_class", FMD_TYPE_UINT64, "incorrect event class received" }, { "swde_smf_no_attr", FMD_TYPE_UINT64, "malformed event - missing attr nvlist" }, { "swde_smf_bad_attr", FMD_TYPE_UINT64, "malformed event - invalid attr list" }, { "swde_smf_bad_fmri", FMD_TYPE_UINT64, "malformed event - fmri2str fails" }, { "swde_smf_no_uuid", FMD_TYPE_UINT64, "malformed event - missing uuid" }, { "swde_smf_no_reason_short", FMD_TYPE_UINT64, "SMF transition event had no reason-short" }, { "swde_smf_no_reason_long", FMD_TYPE_UINT64, "SMF transition event had no reason-long" }, { "swde_smf_no_svcname", FMD_TYPE_UINT64, "SMF transition event had no svc-string" }, { "swde_smf_admin_maint_drop", FMD_TYPE_UINT64, "maintenance transitions requested by admin - no diagnosis" }, { "swde_smf_bad_nvlist_pack", FMD_TYPE_UINT64, "failed nvlist_size or nvlist_pack" }, { "swde_smf_dupuuid", FMD_TYPE_UINT64, "duplicate events received" }, }; #define SWDE_SMF_CASEDATA_VERS 1 typedef struct swde_smf_casedata { uint32_t scd_vers; /* must be first member */ size_t scd_nvlbufsz; /* size of following buffer */ /* packed fmri nvlist follows */ } swde_smf_casedata_t; #define BUMPSTAT(stat) swde_smf_stats.stat.fmds_value.ui64++ /*ARGSUSED*/ void swde_smf_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class, void *arg) { char *rsn = NULL, *rsnl = NULL, *svcname = NULL; nvlist_t *attr, *svcfmri, *defect; swde_smf_casedata_t *cdp; fmd_case_t *cp; char *fmribuf; char *uuid; size_t sz; if (!fmd_nvl_class_match(hdl, nvl, TRANCLASS("maintenance"))) { BUMPSTAT(swde_smf_bad_class); return; } if (nvlist_lookup_nvlist(nvl, FM_IREPORT_ATTRIBUTES, &attr) != 0) { BUMPSTAT(swde_smf_no_attr); return; } if (nvlist_lookup_string(nvl, FM_IREPORT_UUID, &uuid) != 0) { BUMPSTAT(swde_smf_no_uuid); return; } if (nvlist_lookup_nvlist(attr, "svc", &svcfmri) != 0) { BUMPSTAT(swde_smf_bad_attr); return; } if (nvlist_lookup_string(attr, "reason-short", &rsn) != 0) { BUMPSTAT(swde_smf_no_reason_short); return; } if (nvlist_lookup_string(attr, "reason-long", &rsnl) != 0) { BUMPSTAT(swde_smf_no_reason_long); return; } if (nvlist_lookup_string(attr, "svc-string", &svcname) != 0) { BUMPSTAT(swde_smf_no_svcname); return; } if (strcmp(rsn, "administrative_request") == 0) { BUMPSTAT(swde_smf_admin_maint_drop); return; } /* * Our case checkpoint data, version 1. */ if (nvlist_size(svcfmri, &sz, NV_ENCODE_NATIVE) != 0) { BUMPSTAT(swde_smf_bad_nvlist_pack); return; } cdp = fmd_hdl_zalloc(hdl, sizeof (*cdp) + sz, FMD_SLEEP); cdp->scd_vers = SWDE_SMF_CASEDATA_VERS; fmribuf = (char *)cdp + sizeof (*cdp); cdp->scd_nvlbufsz = sz; (void) nvlist_pack(svcfmri, &fmribuf, &sz, NV_ENCODE_NATIVE, 0); /* * Open a case with UUID matching the originating event, and no * associated serialization data. Create a defect and add it to * the case, and link the originating event to the case. This * call will return NULL if a case with the requested UUID already * exists, which would mean we are processing an event twice so * we can discard. */ if ((cp = swde_case_open(hdl, myid, uuid, SWDE_SMF_CASEDATA_VERS, (void *)cdp, sizeof (*cdp) + sz)) == NULL) { BUMPSTAT(swde_smf_dupuuid); fmd_hdl_free(hdl, cdp, sizeof (*cdp) + sz); return; } defect = fmd_nvl_create_defect(hdl, SW_SMF_MAINT_DEFECT, 100, svcfmri, NULL, svcfmri); if (rsn != NULL) (void) nvlist_add_string(defect, "reason-short", rsn); if (rsnl != NULL) (void) nvlist_add_string(defect, "reason-long", rsnl); if (svcname != NULL) (void) nvlist_add_string(defect, "svc-string", svcname); fmd_case_add_suspect(hdl, cp, defect); fmd_case_add_ereport(hdl, cp, ep); /* * Now solve the case, and immediately close it. Although the * resource is already isolated (SMF put it in maintenance state) * we do not immediately close the case here - our sibling response * logic will do that after caching the case UUID. */ fmd_case_solve(hdl, cp); BUMPSTAT(swde_smf_diagnosed); } /* * In the normal course of events we keep in sync with SMF through the * maintenance enter/clear events it raises. Even if a maintenance * state is cleared using svcadm while fmd is not running, the event * will pend and be consumed when fmd does start and we'll close the * case (in the response agent). * * But is is possible for discontinuities to produce some confusion: * * - if an instance is in maintenance state (and so shown in svcs -x * and fmadm faulty output) at the time we clone a new boot * environment then when we boot the new BE we can be out of * sync if the instance is cleared when we boot there * * - meddling with /var/fm state - eg manual clear of files there, * or restore of old state * * So as an extra guard we have a case verify function which is called * at fmd restart (module load for software-diagnosis). We must * return 0 to close the case, non-zero to retain it. */ int swde_smf_vrfy(fmd_hdl_t *hdl, fmd_case_t *cp) { swde_smf_casedata_t *cdp; nvlist_t *svcfmri; uint32_t v; int rv; cdp = swde_case_data(hdl, cp, &v); if (cdp == NULL || v != 1) return (0); /* bad or damaged - just close */ if (nvlist_unpack((char *)cdp + sizeof (*cdp), cdp->scd_nvlbufsz, &svcfmri, 0) != 0) return (0); /* ditto */ switch (fmd_nvl_fmri_service_state(hdl, svcfmri)) { case FMD_SERVICE_STATE_UNUSABLE: /* * Keep case iff in maintenance state */ rv = 1; break; default: /* * Discard the case for all other states - cleared, * service no longer exists, ... whatever. */ rv = 0; break; } nvlist_free(svcfmri); return (rv); } const struct sw_disp swde_smf_disp[] = { { TRANCLASS("maintenance"), swde_smf_recv, NULL }, { NULL, NULL, NULL } }; /*ARGSUSED*/ int swde_smf_init(fmd_hdl_t *hdl, id_t id, const struct sw_disp **dpp, int *nelemp) { myid = id; (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (swde_smf_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&swde_smf_stats); fmd_hdl_subscribe(hdl, TRANCLASS("maintenance")); *dpp = &swde_smf_disp[0]; *nelemp = sizeof (swde_smf_disp) / sizeof (swde_smf_disp[0]); return (SW_SUB_INIT_SUCCESS); } const struct sw_subinfo smf_diag_info = { "smf diagnosis", /* swsub_name */ SW_CASE_SMF, /* swsub_casetype */ swde_smf_init, /* swsub_init */ NULL, /* swsub_fini */ NULL, /* swsub_timeout */ NULL, /* swsub_case_close */ swde_smf_vrfy, /* swsub_case_vrfy */ }; /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * SMF software-response subsidiary */ #include #include #include #include #include #include "../../common/sw.h" #include "smf.h" static struct { fmd_stat_t swrp_smf_repairs; fmd_stat_t swrp_smf_clears; fmd_stat_t swrp_smf_closed; fmd_stat_t swrp_smf_wrongclass; fmd_stat_t swrp_smf_badlist; fmd_stat_t swrp_smf_badresource; fmd_stat_t swrp_smf_badclrevent; fmd_stat_t swrp_smf_noloop; fmd_stat_t swrp_smf_suppressed; fmd_stat_t swrp_smf_cachefull; } swrp_smf_stats = { { "swrp_smf_repairs", FMD_TYPE_UINT64, "repair events received for propogation to SMF" }, { "swrp_smf_clears", FMD_TYPE_UINT64, "notifications from SMF of exiting maint state" }, { "swrp_smf_closed", FMD_TYPE_UINT64, "cases closed" }, { "swrp_smf_wrongclass", FMD_TYPE_UINT64, "unexpected event class received" }, { "swrp_smf_badlist", FMD_TYPE_UINT64, "list event with invalid structure" }, { "swrp_smf_badresource", FMD_TYPE_UINT64, "list.repaired with smf fault but bad svc fmri" }, { "swrp_smf_badclrevent", FMD_TYPE_UINT64, "maint clear event from SMF malformed" }, { "swrp_smf_noloop", FMD_TYPE_UINT64, "avoidance of smf->fmd->smf repairs propogations" }, { "swrp_smf_suppressed", FMD_TYPE_UINT64, "not propogated to smf because no longer in maint" }, { "swrp_smf_cachefull", FMD_TYPE_UINT64, "uuid cache full" }, }; #define BUMPSTAT(stat) swrp_smf_stats.stat.fmds_value.ui64++ #define CACHE_NENT_INC 16 #define CACHE_NENT_MAX 128 struct smf_uuid_cache_ent { char uuid[37]; char fmristr[90]; uint8_t mark; }; #define CACHE_VERSION 1 struct smf_uuid_cache { uint32_t version; /* Version */ uint32_t nentries; /* Real size of array below */ struct smf_uuid_cache_ent entry[1]; /* Cache entries */ }; static struct smf_uuid_cache *uuid_cache; #define UUID_CACHE_BUFNAME "uuid_cache" static void uuid_cache_grow(fmd_hdl_t *hdl) { struct smf_uuid_cache *newcache; size_t newsz; uint32_t n; n = (uuid_cache == NULL ? 0 : uuid_cache->nentries) + CACHE_NENT_INC; newsz = sizeof (struct smf_uuid_cache) + (n - 1) * sizeof (struct smf_uuid_cache_ent); newcache = fmd_hdl_zalloc(hdl, newsz, FMD_SLEEP); newcache->version = CACHE_VERSION; newcache->nentries = n; if (uuid_cache != NULL) { uint32_t oldn = uuid_cache->nentries; size_t oldsz = sizeof (struct smf_uuid_cache) + (oldn - 1) * sizeof (struct smf_uuid_cache_ent); bcopy(&uuid_cache->entry[0], &newcache->entry[0], oldsz); fmd_hdl_free(hdl, uuid_cache, oldsz); fmd_buf_destroy(hdl, NULL, UUID_CACHE_BUFNAME); } uuid_cache = newcache; fmd_buf_create(hdl, NULL, UUID_CACHE_BUFNAME, newsz); } static void uuid_cache_persist(fmd_hdl_t *hdl) { size_t sz = sizeof (struct smf_uuid_cache) + (uuid_cache->nentries - 1) * sizeof (struct smf_uuid_cache_ent); fmd_buf_write(hdl, NULL, UUID_CACHE_BUFNAME, uuid_cache, sz); } /* * Garbage-collect the uuid cache. Any cases that are already resolved * we do not need an entry for. If a case is not resolved but the * service involved in that case is no longer in maintenance state * then we've lost sync somehow, so repair the asru (which will * also resolve the case). */ static void uuid_cache_gc(fmd_hdl_t *hdl) { struct smf_uuid_cache_ent *entp; topo_hdl_t *thp = NULL; nvlist_t *svcfmri; char *svcname; int err, i; for (i = 0; i < uuid_cache->nentries; i++) { entp = &uuid_cache->entry[i]; if (entp->uuid[0] == '\0') continue; if (fmd_case_uuisresolved(hdl, entp->uuid)) { bzero(entp->uuid, sizeof (entp->uuid)); bzero(entp->fmristr, sizeof (entp->fmristr)); entp->mark = 0; } else { if (thp == NULL) thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if (topo_fmri_str2nvl(thp, entp->fmristr, &svcfmri, &err) != 0) { fmd_hdl_error(hdl, "str2nvl failed for %s\n", entp->fmristr); continue; } if (fmd_nvl_fmri_service_state(hdl, svcfmri) != FMD_SERVICE_STATE_UNUSABLE) { svcname = sw_smf_svcfmri2shortstr(hdl, svcfmri); (void) fmd_repair_asru(hdl, entp->fmristr); fmd_hdl_strfree(hdl, svcname); } nvlist_free(svcfmri); } } if (thp) fmd_hdl_topo_rele(hdl, thp); uuid_cache_persist(hdl); } static void uuid_cache_restore(fmd_hdl_t *hdl) { size_t sz = fmd_buf_size(hdl, NULL, UUID_CACHE_BUFNAME); if (sz == 0) return; uuid_cache = fmd_hdl_alloc(hdl, sz, FMD_SLEEP); fmd_buf_read(hdl, NULL, UUID_CACHE_BUFNAME, uuid_cache, sz); /* * Garbage collect now, not just for tidiness but also to help * fmd and smf state stay in sync at module startup. */ uuid_cache_gc(hdl); } /* * Add the UUID of an SMF maintenance defect case to our cache and * record the associated full svc FMRI string for the case. */ static void swrp_smf_cache_add(fmd_hdl_t *hdl, char *uuid, char *fmristr) { struct smf_uuid_cache_ent *entp = NULL; int gced = 0; int i; if (uuid_cache == NULL) uuid_cache_grow(hdl); /* * If we somehow already have an entry for this uuid then * return leaving it undisturbed. */ for (i = 0; i < uuid_cache->nentries; i++) { if (strcmp(uuid, uuid_cache->entry[i].uuid) == 0) return; } scan: for (i = 0; i < uuid_cache->nentries; i++) { if (uuid_cache->entry[i].uuid[0] == '\0') { entp = &uuid_cache->entry[i]; break; } } if (entp == NULL) { uint32_t oldn = uuid_cache->nentries; /* * Before growing the cache we try again after first * garbage-collecting the existing cache for any cases * that are confirmed as resolved. */ if (!gced) { uuid_cache_gc(hdl); gced = 1; goto scan; } if (oldn < CACHE_NENT_MAX) { uuid_cache_grow(hdl); entp = &uuid_cache->entry[oldn]; } else { BUMPSTAT(swrp_smf_cachefull); return; } } (void) strncpy(entp->uuid, uuid, sizeof (entp->uuid)); (void) strncpy(entp->fmristr, fmristr, sizeof (entp->fmristr)); uuid_cache_persist(hdl); } /* * Mark cache entry/entries as resolved - if they match in either uuid * (if not NULL) or fmristr (if not NULL) mark as resolved. Return 1 iff * an entry that matched on uuid was already marked, otherwise (entry * matched on either, matched on uuid but not marked, not found). */ static int swrp_smf_cache_mark(fmd_hdl_t *hdl, char *uuid, char *fmristr) { int dirty = 0; int rv = 0; int i; if (uuid_cache == NULL) return (0); for (i = 0; i < uuid_cache->nentries; i++) { struct smf_uuid_cache_ent *entp = &uuid_cache->entry[i]; if (entp->uuid[0] == '\0') continue; if (uuid && strcmp(uuid, entp->uuid) == 0) { if (entp->mark) rv = 1; entp->mark = 1; dirty++; } else if (fmristr && strcmp(fmristr, entp->fmristr) == 0) { entp->mark = 1; dirty++; } } if (dirty) uuid_cache_persist(hdl); return (rv); } /* * We will receive list events for cases we are not interested in. Test * that this list has exactly one suspect and that it matches the maintenance * defect. Return the defect to the caller in the second argument, * and the defect resource element in the third arg. */ static int suspect_is_maint_defect(fmd_hdl_t *hdl, nvlist_t *nvl, nvlist_t **defectnvl, nvlist_t **rsrcnvl) { nvlist_t **faults; uint_t nfaults; if (nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST, &faults, &nfaults) != 0) { BUMPSTAT(swrp_smf_badlist); return (0); } if (nfaults != 1 || !fmd_nvl_class_match(hdl, faults[0], SW_SMF_MAINT_DEFECT)) return (0); if (nvlist_lookup_nvlist(faults[0], FM_FAULT_RESOURCE, rsrcnvl) != 0) { BUMPSTAT(swrp_smf_badlist); return (0); } *defectnvl = faults[0]; return (1); } /* * Received newly-diagnosed list.suspect events that are for the * maintenane defect we diagnose. Close the case (the resource was already * isolated by SMF) after cachng the case UUID. */ /*ARGSUSED*/ static void swrp_smf_cacheuuid(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class, void *arg) { nvlist_t *defect, *rsrc; char *fmristr, *uuid; if (nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) != 0) { BUMPSTAT(swrp_smf_badlist); return; } if (!suspect_is_maint_defect(hdl, nvl, &defect, &rsrc)) return; if ((fmristr = sw_smf_svcfmri2str(hdl, rsrc)) == NULL) { BUMPSTAT(swrp_smf_badlist); return; } swrp_smf_cache_add(hdl, uuid, fmristr); fmd_hdl_strfree(hdl, fmristr); if (!fmd_case_uuclosed(hdl, uuid)) { fmd_case_uuclose(hdl, uuid); BUMPSTAT(swrp_smf_closed); } } /*ARGSUSED*/ static void swrp_smf2fmd(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class, void *arg) { nvlist_t *attr, *fmri; char *fromstate; char *fmristr; if (!fmd_nvl_class_match(hdl, nvl, TRANCLASS("*"))) { BUMPSTAT(swrp_smf_wrongclass); return; } if (nvlist_lookup_nvlist(nvl, FM_IREPORT_ATTRIBUTES, &attr) != 0 || nvlist_lookup_string(attr, "from-state", &fromstate) != 0) { BUMPSTAT(swrp_smf_badclrevent); return; } /* * Filter those not describing a transition out of maintenance. */ if (strcmp(fromstate, "maintenance") != 0) return; if (nvlist_lookup_nvlist(attr, "svc", &fmri) != 0) { BUMPSTAT(swrp_smf_badclrevent); return; } if ((fmristr = sw_smf_svcfmri2str(hdl, fmri)) == NULL) { BUMPSTAT(swrp_smf_badclrevent); return; } /* * Mark any UUID for a case against this service as resolved * in our cache. When we fmd_repair_asru below fmd will emit * a list.repaired as a result, and our handling of that event * must not propogate the repair towards SMF (since the repair * was initiated via SMF itself and not via fmadm). */ (void) swrp_smf_cache_mark(hdl, NULL, fmristr); (void) fmd_repair_asru(hdl, fmristr); fmd_hdl_strfree(hdl, fmristr); BUMPSTAT(swrp_smf_clears); } /*ARGSUSED*/ static void swrp_fmd2smf(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class, void *arg) { char *fmristr, *shrtfmristr; nvlist_t *defect, *rsrc; char *uuid; int already; if (strcmp(class, FM_LIST_REPAIRED_CLASS) != 0) { BUMPSTAT(swrp_smf_wrongclass); return; } if (nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) != 0) { BUMPSTAT(swrp_smf_badlist); return; } if (!suspect_is_maint_defect(hdl, nvl, &defect, &rsrc)) return; if ((fmristr = sw_smf_svcfmri2str(hdl, rsrc)) == NULL) { BUMPSTAT(swrp_smf_badresource); return; } already = swrp_smf_cache_mark(hdl, uuid, fmristr); fmd_hdl_strfree(hdl, fmristr); /* * If the cache already had a marked entry for this UUID then * this is a list.repaired arising from a SMF-initiated maintenance * clear (propogated with fmd_repair_asru above which then results * in a list.repaired) and so we should not propogate the repair * back towards SMF. But do still force the case to RESOLVED state in * case fmd is unable to confirm the service no longer in maintenance * state (it may have failed again) so that a new case can be opened. */ fmd_case_uuresolved(hdl, uuid); if (already) { BUMPSTAT(swrp_smf_noloop); return; } /* * Only propogate to SMF if we can see that service still * in maintenance state. We're not synchronized with SMF * and this state could change at any time, but if we can * see it's not in maintenance state then things are obviously * moving (e.g., external svcadm active) so we don't poke * at SMF otherwise we confuse things or duplicate operations. */ if (fmd_nvl_fmri_service_state(hdl, rsrc) == FMD_SERVICE_STATE_UNUSABLE) { shrtfmristr = sw_smf_svcfmri2shortstr(hdl, rsrc); if (shrtfmristr != NULL) { (void) smf_restore_instance(shrtfmristr); fmd_hdl_strfree(hdl, shrtfmristr); BUMPSTAT(swrp_smf_repairs); } else { BUMPSTAT(swrp_smf_badresource); } } else { BUMPSTAT(swrp_smf_suppressed); } } const struct sw_disp swrp_smf_disp[] = { { TRANCLASS("*"), swrp_smf2fmd, NULL }, { FM_LIST_SUSPECT_CLASS, swrp_smf_cacheuuid, NULL }, { FM_LIST_REPAIRED_CLASS, swrp_fmd2smf, NULL }, { NULL, NULL, NULL } }; /*ARGSUSED*/ int swrp_smf_init(fmd_hdl_t *hdl, id_t id, const struct sw_disp **dpp, int *nelemp) { (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (swrp_smf_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&swrp_smf_stats); uuid_cache_restore(hdl); /* * We need to subscribe to all SMF transition class events because * we need to look inside the payload to see which events indicate * a transition out of maintenance state. */ fmd_hdl_subscribe(hdl, TRANCLASS("*")); /* * Subscribe to the defect class diagnosed for maintenance events. * The module will then receive list.suspect events including * these defects, and in our dispatch table above we list routing * for list.suspect. */ fmd_hdl_subscribe(hdl, SW_SMF_MAINT_DEFECT); *dpp = &swrp_smf_disp[0]; *nelemp = sizeof (swrp_smf_disp) / sizeof (swrp_smf_disp[0]); return (SW_SUB_INIT_SUCCESS); } /*ARGSUSED*/ void swrp_smf_fini(fmd_hdl_t *hdl) { } const struct sw_subinfo smf_response_info = { "smf repair", /* swsub_name */ SW_CASE_NONE, /* swsub_casetype */ swrp_smf_init, /* swsub_init */ swrp_smf_fini, /* swsub_fini */ NULL, /* swsub_timeout */ NULL, /* swsub_case_close */ NULL, /* swsub_case_vrfy */ }; /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * SMF software diagnosis engine components. */ #include #include #include "../../common/sw.h" #include "smf.h" /* * Given a "svc' scheme FMRI in nvlist form, produce a string form * of the FMRI (with no short-hand). */ char * sw_smf_svcfmri2str(fmd_hdl_t *hdl, nvlist_t *fmri) { char *fmristr = NULL; topo_hdl_t *thp; char *topostr; int err; thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if (topo_fmri_nvl2str(thp, fmri, &topostr, &err) == 0) { fmristr = fmd_hdl_strdup(hdl, (const char *)topostr, FMD_SLEEP); topo_hdl_strfree(thp, topostr); } fmd_hdl_topo_rele(hdl, thp); return (fmristr); /* caller must fmd_hdl_strfree */ } /* * Given a "svc" scheme FMRI in nvlist form, produce a short-hand form * string FMRI "svc:/..." as generally used in SMF cmdline output. */ char * sw_smf_svcfmri2shortstr(fmd_hdl_t *hdl, nvlist_t *fmri) { char *name, *inst, *bufp, *fullname; size_t len; if (nvlist_lookup_string(fmri, FM_FMRI_SVC_NAME, &name) != 0 || nvlist_lookup_string(fmri, FM_FMRI_SVC_INSTANCE, &inst) != 0) return (NULL); len = strlen(name) + strlen(inst) + 8; bufp = fmd_hdl_alloc(hdl, len, FMD_SLEEP); (void) snprintf(bufp, len, "svc:/%s:%s", name, inst); fullname = fmd_hdl_strdup(hdl, bufp, FMD_SLEEP); fmd_hdl_free(hdl, bufp, len); return (fullname); /* caller must fmd_hdl_strfree */ } # # 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. # MODULE = syslog-msgs CLASS = common SRCS = syslog.c include ../../Makefile.plugin LDFLAGS += -L $(ROOT)/usr/lib/fm -R/usr/lib/fm LINTFLAGS += -L$(ROOT)/usr/lib/fm LDLIBS += -lfmd_msg -lscf # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # # syslog-msgs agent properties: # # url - string - base path of URL to use for URL reference in description # gmt - bool - show event time in GMT rather than local timezone (default=false) # console - bool - log messages to system console (default=true) # syslogd - bool - log messages to messages file through syslogd (default=true) # facility - string - syslog(3C) log facility to use (default=LOG_DAEMON) # # The "facility" property may only be configured to use LOG_DAEMON (which is # the default) or one of LOG_LOCAL[0-7]. For information about the use of # local log facilities, refer to syslog(3C), syslogd(8), and syslog.conf(5). # # Example settings: # setprop gmt true # setprop facility LOG_LOCAL0 # /* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct stats { fmd_stat_t bad_vers; fmd_stat_t bad_code; fmd_stat_t log_err; fmd_stat_t msg_err; fmd_stat_t no_msg; } syslog_stats = { { "bad_vers", FMD_TYPE_UINT64, "event version is missing or invalid" }, { "bad_code", FMD_TYPE_UINT64, "event code has no dictionary name" }, { "log_err", FMD_TYPE_UINT64, "failed to log message to log(4D)" }, { "msg_err", FMD_TYPE_UINT64, "failed to log message to sysmsg(4D)" }, { "no_msg", FMD_TYPE_UINT64, "message logging suppressed" } }; static const struct facility { const char *fac_name; int fac_value; } syslog_facs[] = { { "LOG_DAEMON", LOG_DAEMON }, { "LOG_LOCAL0", LOG_LOCAL0 }, { "LOG_LOCAL1", LOG_LOCAL1 }, { "LOG_LOCAL2", LOG_LOCAL2 }, { "LOG_LOCAL3", LOG_LOCAL3 }, { "LOG_LOCAL4", LOG_LOCAL4 }, { "LOG_LOCAL5", LOG_LOCAL5 }, { "LOG_LOCAL6", LOG_LOCAL6 }, { "LOG_LOCAL7", LOG_LOCAL7 }, { NULL, 0 } }; static fmd_msg_hdl_t *syslog_msghdl; /* handle for libfmd_msg calls */ static int syslog_msgall; /* set to message all faults */ static log_ctl_t syslog_ctl; /* log(4D) meta-data for each msg */ static int syslog_logfd = -1; /* log(4D) file descriptor */ static int syslog_msgfd = -1; /* sysmsg(4D) file descriptor */ static int syslog_file; /* log to syslog_logfd */ static int syslog_cons; /* log to syslog_msgfd */ static const char SYSLOG_POINTER[] = "syslog-msgs-pointer"; /* * Ideally we would just use syslog(3C) for outputting our messages, but our * messaging standard defines a nice multi-line format and syslogd(8) is very * inflexible and stupid when it comes to multi-line messages. It pulls data * out of log(4D) and splits it up by \n, printing each line to the console * with its usual prefix of date and sender; it uses the same behavior for the * messages file as well. Further, syslog(3C) provides no CE_CONT equivalent * for userland callers (which at least works around repeated file prefixing). * So with a multi-line message format, your file and console end up like this: * * Dec 02 18:08:40 hostname this is my nicely formatted * Dec 02 18:08:40 hostname message designed for 80 cols * ... * * To resolve these issues, we use our own syslog_emit() wrapper to emit * messages and some knowledge of how the Solaris log drivers work. We first * construct an enlarged format string containing the appropriate msgid(8). * We then format the caller's message using the provided format and buffer. * We send this message to log(4D) using putmsg() with SL_CONSOLE | SL_LOGONLY * set in the log_ctl_t. The log driver allows us to set SL_LOGONLY when we * construct messages ourself, indicating that syslogd should only emit the * message to /var/adm/messages and any remote hosts, and skip the console. * Then we emit the message a second time, without the special prefix, to the * sysmsg(4D) device, which handles console redirection and also permits us * to output any characters we like to the console, including \n and \r. */ static void syslog_emit(fmd_hdl_t *hdl, const char *msg) { struct strbuf ctl, dat; uint32_t msgid; char *buf; size_t buflen; const char *format = "fmd: [ID %u FACILITY_AND_PRIORITY] %s"; STRLOG_MAKE_MSGID(format, msgid); buflen = snprintf(NULL, 0, format, msgid, msg); buf = alloca(buflen + 1); (void) snprintf(buf, buflen + 1, format, msgid, msg); ctl.buf = (void *)&syslog_ctl; ctl.len = sizeof (syslog_ctl); dat.buf = buf; dat.len = buflen + 1; /* * The underlying log driver won't accept messages longer than * LOG_MAXPS bytes. Therefore, messages which exceed this limit will * be truncated and appended with a pointer to the full message. */ if (dat.len > LOG_MAXPS) { char *syslog_pointer, *p; size_t plen; if ((syslog_pointer = fmd_msg_gettext_id(syslog_msghdl, NULL, SYSLOG_POINTER)) == NULL) { /* * This shouldn't happen, but if it does we'll just * truncate the message. */ buf[LOG_MAXPS - 1] = '\0'; dat.len = LOG_MAXPS; } else { plen = strlen(syslog_pointer) + 1; buf[LOG_MAXPS - plen] = '\0'; /* * If possible, the pointer is appended after a newline */ if ((p = strrchr(buf, '\n')) == NULL) p = &buf[LOG_MAXPS - plen]; (void) strcpy(p, syslog_pointer); free(syslog_pointer); dat.len = strlen(buf) + 1; } } if (syslog_file && putmsg(syslog_logfd, &ctl, &dat, 0) != 0) { fmd_hdl_debug(hdl, "putmsg failed: %s\n", strerror(errno)); syslog_stats.log_err.fmds_value.ui64++; } dat.buf = strchr(buf, ']'); dat.len -= (size_t)(dat.buf - buf); dat.buf[0] = '\r'; /* overwrite ']' with carriage return */ dat.buf[1] = '\n'; /* overwrite ' ' with newline */ if (syslog_cons && write(syslog_msgfd, dat.buf, dat.len) != dat.len) { fmd_hdl_debug(hdl, "write failed: %s\n", strerror(errno)); syslog_stats.msg_err.fmds_value.ui64++; } } static void free_notify_prefs(fmd_hdl_t *hdl, nvlist_t **prefs, uint_t nprefs) { int i; for (i = 0; i < nprefs; i++) { nvlist_free(prefs[i]); } fmd_hdl_free(hdl, prefs, sizeof (nvlist_t *) * nprefs); } static int get_notify_prefs(fmd_hdl_t *hdl, nvlist_t *ev_nvl, nvlist_t ***pref_nvl, uint_t *nprefs) { nvlist_t *top_nvl, **np_nvlarr, *mech_nvl; nvlist_t **tmparr; int ret, i; uint_t nelem, nslelem; if ((ret = smf_notify_get_params(&top_nvl, ev_nvl)) != SCF_SUCCESS) { ret = scf_error(); if (ret != SCF_ERROR_NOT_FOUND) { fmd_hdl_debug(hdl, "Error looking up notification " "preferences (%s)", scf_strerror(ret)); return (ret); } return (ret); } if (nvlist_lookup_nvlist_array(top_nvl, SCF_NOTIFY_PARAMS, &np_nvlarr, &nelem) != 0) { fmd_hdl_debug(hdl, "Malformed preference nvlist\n"); ret = SCF_ERROR_INVALID_ARGUMENT; goto pref_done; } tmparr = fmd_hdl_alloc(hdl, nelem * sizeof (nvlist_t *), FMD_SLEEP); nslelem = 0; for (i = 0; i < nelem; i++) { if (nvlist_lookup_nvlist(np_nvlarr[i], "syslog", &mech_nvl) == 0) tmparr[nslelem++] = fmd_nvl_dup(hdl, mech_nvl, FMD_SLEEP); } if (nslelem != 0) { size_t sz = nslelem * sizeof (nvlist_t *); *pref_nvl = fmd_hdl_zalloc(hdl, sz, FMD_SLEEP); *nprefs = nslelem; bcopy(tmparr, *pref_nvl, sz); ret = 0; } else { *pref_nvl = NULL; *nprefs = 0; ret = SCF_ERROR_NOT_FOUND; } fmd_hdl_free(hdl, tmparr, nelem * sizeof (nvlist_t *)); pref_done: nvlist_free(top_nvl); return (ret); } /*ARGSUSED*/ static void syslog_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { uint8_t version; boolean_t domsg, *active; char *msg; nvlist_t **prefs; uint_t nprefs, nelems; int ret; if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 || version > FM_SUSPECT_VERSION) { fmd_hdl_debug(hdl, "invalid event version: %u\n", version); syslog_stats.bad_vers.fmds_value.ui64++; return; /* invalid event version */ } if (!syslog_msgall && nvlist_lookup_boolean_value(nvl, FM_SUSPECT_MESSAGE, &domsg) == 0 && !domsg) { fmd_hdl_debug(hdl, "%s requested no message\n", class); syslog_stats.no_msg.fmds_value.ui64++; return; /* event is not to be messaged */ } ret = get_notify_prefs(hdl, nvl, &prefs, &nprefs); if (ret == SCF_ERROR_NOT_FOUND) { /* * No syslog notification preferences specified for this type of * event, so we're done */ fmd_hdl_debug(hdl, "No syslog notification preferences " "configured for class %s\n", class); syslog_stats.no_msg.fmds_value.ui64++; return; } else if (ret != 0 || nvlist_lookup_boolean_array(prefs[0], "active", &active, &nelems)) { fmd_hdl_debug(hdl, "Failed to retrieve notification " "preferences for class %s\n", class); if (ret == 0) free_notify_prefs(hdl, prefs, nprefs); return; } else if (!active[0]) { fmd_hdl_debug(hdl, "Syslog notifications disabled for " "class %s\n", class); syslog_stats.no_msg.fmds_value.ui64++; free_notify_prefs(hdl, prefs, nprefs); return; } free_notify_prefs(hdl, prefs, nprefs); if ((msg = fmd_msg_gettext_nv(syslog_msghdl, NULL, nvl)) == NULL) { fmd_hdl_debug(hdl, "failed to format message"); syslog_stats.bad_code.fmds_value.ui64++; return; /* libfmd_msg error */ } syslog_ctl.pri &= LOG_FACMASK; if (strcmp(class, FM_LIST_ISOLATED_CLASS) == 0 || strcmp(class, FM_LIST_RESOLVED_CLASS) == 0 || strcmp(class, FM_LIST_REPAIRED_CLASS) == 0 || strcmp(class, FM_LIST_UPDATED_CLASS) == 0) syslog_ctl.pri |= LOG_NOTICE; else syslog_ctl.pri |= LOG_ERR; syslog_emit(hdl, msg); free(msg); } static const fmd_prop_t fmd_props[] = { { "console", FMD_TYPE_BOOL, "true" }, { "facility", FMD_TYPE_STRING, "LOG_DAEMON" }, { "gmt", FMD_TYPE_BOOL, "false" }, { "syslogd", FMD_TYPE_BOOL, "true" }, { "url", FMD_TYPE_STRING, "http://illumos.org/msg/" }, { "message_all", FMD_TYPE_BOOL, "false" }, { NULL, 0, NULL } }; static const fmd_hdl_ops_t fmd_ops = { syslog_recv, /* fmdo_recv */ NULL, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ }; static const fmd_hdl_info_t fmd_info = { "Syslog Messaging Agent", "1.1", &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { const struct facility *fp; char *facname, *tz, *rootdir, *urlbase; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) return; /* invalid data in configuration file */ (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (syslog_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&syslog_stats); if ((syslog_logfd = open("/dev/conslog", O_WRONLY | O_NOCTTY)) == -1) fmd_hdl_abort(hdl, "syslog-msgs failed to open /dev/conslog"); if ((syslog_msgfd = open("/dev/sysmsg", O_WRONLY | O_NOCTTY)) == -1) fmd_hdl_abort(hdl, "syslog-msgs failed to open /dev/sysmsg"); /* * If the "gmt" property is set to true, force our EVENT-TIME to be * reported in GMT time; otherwise we use localtime. tzset() affects * the results of subsequent calls to strftime(3C) above. */ if (fmd_prop_get_int32(hdl, "gmt") == FMD_B_TRUE && ((tz = getenv("TZ")) == NULL || strcmp(tz, "GMT") != 0)) { (void) putenv(fmd_hdl_strdup(hdl, "TZ=GMT", FMD_SLEEP)); tzset(); /* reload env */ } /* * Look up the value of the "facility" property and use it to determine * what syslog LOG_* facility value we use to fill in our log_ctl_t. * The details of our logging method are described above syslog_emit(). */ facname = fmd_prop_get_string(hdl, "facility"); for (fp = syslog_facs; fp->fac_name != NULL; fp++) { if (strcmp(fp->fac_name, facname) == 0) break; } if (fp->fac_name == NULL) fmd_hdl_abort(hdl, "invalid 'facility' setting: %s\n", facname); fmd_prop_free_string(hdl, facname); syslog_ctl.pri = fp->fac_value; syslog_ctl.flags = SL_CONSOLE | SL_LOGONLY; /* * Cache any properties we use every time we receive an event and * subscribe to list.suspect events regardless of the .conf file. */ syslog_file = fmd_prop_get_int32(hdl, "syslogd"); syslog_cons = fmd_prop_get_int32(hdl, "console"); syslog_msgall = fmd_prop_get_int32(hdl, "message_all"); rootdir = fmd_prop_get_string(hdl, "fmd.rootdir"); syslog_msghdl = fmd_msg_init(rootdir, FMD_MSG_VERSION); fmd_prop_free_string(hdl, rootdir); if (syslog_msghdl == NULL) fmd_hdl_abort(hdl, "failed to initialize libfmd_msg"); urlbase = fmd_prop_get_string(hdl, "url"); (void) fmd_msg_url_set(syslog_msghdl, urlbase); fmd_prop_free_string(hdl, urlbase); /* * We subscribe to all FM events and then consult the notification * preferences in the serice configuration repo to determine whether * or not to emit a console message. */ fmd_hdl_subscribe(hdl, FM_LIST_SUSPECT_CLASS); fmd_hdl_subscribe(hdl, FM_LIST_REPAIRED_CLASS); fmd_hdl_subscribe(hdl, FM_LIST_RESOLVED_CLASS); fmd_hdl_subscribe(hdl, FM_LIST_ISOLATED_CLASS); fmd_hdl_subscribe(hdl, FM_LIST_UPDATED_CLASS); } /*ARGSUSED*/ void _fmd_fini(fmd_hdl_t *hdl) { fmd_msg_fini(syslog_msghdl); (void) close(syslog_logfd); (void) close(syslog_msgfd); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # MODULE = zfs-diagnosis CLASS = common SRCS = zfs_de.c include ../../Makefile.plugin LDLIBS += -L$(ROOTLIB)/fm -luutil -lzfs -ltopo LDFLAGS += -R/usr/lib/fm # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # # fmd configuration file for the zfs.so diagnosis engine. # subscribe ereport.fs.zfs.* subscribe resource.fs.zfs.* subscribe resource.sysevent.EC_zfs.ESC_ZFS_vdev_remove subscribe resource.sysevent.EC_zfs.ESC_ZFS_pool_destroy dictionary ZFS /* * 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 2015 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * Our serd engines are named 'zfs___{checksum,io,probe}'. * This #define reserves enough space for two 64-bit hex values plus the length * of the longest string. */ #define MAX_SERDLEN (16 * 2 + sizeof ("zfs___checksum")) /* * On-disk case structure. This must maintain backwards compatibility with * previous versions of the DE. By default, any members appended to the end * will be filled with zeros if they don't exist in a previous version. */ typedef struct zfs_case_data { uint64_t zc_version; uint64_t zc_ena; uint64_t zc_pool_guid; uint64_t zc_vdev_guid; int zc_has_timer; /* defunct */ int zc_pool_state; char zc_serd_checksum[MAX_SERDLEN]; char zc_serd_io[MAX_SERDLEN]; int zc_has_remove_timer; char zc_serd_probe[MAX_SERDLEN]; } zfs_case_data_t; /* * Time-of-day */ typedef struct er_timeval { uint64_t ertv_sec; uint64_t ertv_nsec; } er_timeval_t; /* * In-core case structure. */ typedef struct zfs_case { boolean_t zc_present; uint32_t zc_version; zfs_case_data_t zc_data; fmd_case_t *zc_case; uu_list_node_t zc_node; id_t zc_remove_timer; char *zc_fru; er_timeval_t zc_when; } zfs_case_t; #define CASE_DATA "data" #define CASE_FRU "fru" #define CASE_DATA_VERSION_INITIAL 1 #define CASE_DATA_VERSION_SERD 2 /* The length of the maximum uint64 rendered as a decimal string. */ #define MAX_ULL_STR 21 typedef struct zfs_de_stats { fmd_stat_t old_drops; fmd_stat_t dev_drops; fmd_stat_t vdev_drops; fmd_stat_t import_drops; fmd_stat_t resource_drops; fmd_stat_t pool_drops; } zfs_de_stats_t; zfs_de_stats_t zfs_stats = { { "old_drops", FMD_TYPE_UINT64, "ereports dropped (from before load)" }, { "dev_drops", FMD_TYPE_UINT64, "ereports dropped (dev during open)"}, { "vdev_drops", FMD_TYPE_UINT64, "ereports dropped (weird vdev types)"}, { "import_drops", FMD_TYPE_UINT64, "ereports dropped (during import)" }, { "resource_drops", FMD_TYPE_UINT64, "resource related ereports" }, { "pool_drops", FMD_TYPE_UINT64, "ereports dropped (pool iter failed)"}, }; static hrtime_t zfs_remove_timeout; uu_list_pool_t *zfs_case_pool; uu_list_t *zfs_cases; #define ZFS_MAKE_RSRC(type) \ FM_RSRC_CLASS "." ZFS_ERROR_CLASS "." type #define ZFS_MAKE_EREPORT(type) \ FM_EREPORT_CLASS "." ZFS_ERROR_CLASS "." type /* * Write out the persistent representation of an active case. */ static void zfs_case_serialize(fmd_hdl_t *hdl, zfs_case_t *zcp) { /* * Always update cases to the latest version, even if they were the * previous version when unserialized. */ zcp->zc_data.zc_version = CASE_DATA_VERSION_SERD; fmd_buf_write(hdl, zcp->zc_case, CASE_DATA, &zcp->zc_data, sizeof (zcp->zc_data)); if (zcp->zc_fru != NULL) fmd_buf_write(hdl, zcp->zc_case, CASE_FRU, zcp->zc_fru, strlen(zcp->zc_fru)); } /* * Read back the persistent representation of an active case. */ static zfs_case_t * zfs_case_unserialize(fmd_hdl_t *hdl, fmd_case_t *cp) { zfs_case_t *zcp; size_t frulen; zcp = fmd_hdl_zalloc(hdl, sizeof (zfs_case_t), FMD_SLEEP); zcp->zc_case = cp; fmd_buf_read(hdl, cp, CASE_DATA, &zcp->zc_data, sizeof (zcp->zc_data)); if (zcp->zc_data.zc_version > CASE_DATA_VERSION_SERD) { fmd_hdl_free(hdl, zcp, sizeof (zfs_case_t)); return (NULL); } if ((frulen = fmd_buf_size(hdl, zcp->zc_case, CASE_FRU)) > 0) { zcp->zc_fru = fmd_hdl_alloc(hdl, frulen + 1, FMD_SLEEP); fmd_buf_read(hdl, zcp->zc_case, CASE_FRU, zcp->zc_fru, frulen); zcp->zc_fru[frulen] = '\0'; } /* * fmd_buf_read() will have already zeroed out the remainder of the * buffer, so we don't have to do anything special if the version * doesn't include the SERD engine name. */ if (zcp->zc_data.zc_has_remove_timer) zcp->zc_remove_timer = fmd_timer_install(hdl, zcp, NULL, zfs_remove_timeout); (void) uu_list_insert_before(zfs_cases, NULL, zcp); fmd_case_setspecific(hdl, cp, zcp); return (zcp); } /* * Iterate over any active cases. If any cases are associated with a pool or * vdev which is no longer present on the system, close the associated case. */ static void zfs_mark_vdev(uint64_t pool_guid, nvlist_t *vd, er_timeval_t *loaded) { uint64_t vdev_guid; uint_t c, children; nvlist_t **child; zfs_case_t *zcp; int ret; ret = nvlist_lookup_uint64(vd, ZPOOL_CONFIG_GUID, &vdev_guid); assert(ret == 0); /* * Mark any cases associated with this (pool, vdev) pair. */ for (zcp = uu_list_first(zfs_cases); zcp != NULL; zcp = uu_list_next(zfs_cases, zcp)) { if (zcp->zc_data.zc_pool_guid == pool_guid && zcp->zc_data.zc_vdev_guid == vdev_guid) { zcp->zc_present = B_TRUE; zcp->zc_when = *loaded; } } /* * Iterate over all children. */ if (nvlist_lookup_nvlist_array(vd, ZPOOL_CONFIG_CHILDREN, &child, &children) == 0) { for (c = 0; c < children; c++) zfs_mark_vdev(pool_guid, child[c], loaded); } if (nvlist_lookup_nvlist_array(vd, ZPOOL_CONFIG_L2CACHE, &child, &children) == 0) { for (c = 0; c < children; c++) zfs_mark_vdev(pool_guid, child[c], loaded); } if (nvlist_lookup_nvlist_array(vd, ZPOOL_CONFIG_SPARES, &child, &children) == 0) { for (c = 0; c < children; c++) zfs_mark_vdev(pool_guid, child[c], loaded); } } /*ARGSUSED*/ static int zfs_mark_pool(zpool_handle_t *zhp, void *unused) { zfs_case_t *zcp; uint64_t pool_guid; uint64_t *tod; er_timeval_t loaded = { 0 }; nvlist_t *config, *vd; uint_t nelem = 0; int ret; pool_guid = zpool_get_prop_int(zhp, ZPOOL_PROP_GUID, NULL); /* * Mark any cases associated with just this pool. */ for (zcp = uu_list_first(zfs_cases); zcp != NULL; zcp = uu_list_next(zfs_cases, zcp)) { if (zcp->zc_data.zc_pool_guid == pool_guid && zcp->zc_data.zc_vdev_guid == 0) zcp->zc_present = B_TRUE; } if ((config = zpool_get_config(zhp, NULL)) == NULL) { zpool_close(zhp); return (-1); } (void) nvlist_lookup_uint64_array(config, ZPOOL_CONFIG_LOADED_TIME, &tod, &nelem); if (nelem == 2) { loaded.ertv_sec = tod[0]; loaded.ertv_nsec = tod[1]; for (zcp = uu_list_first(zfs_cases); zcp != NULL; zcp = uu_list_next(zfs_cases, zcp)) { if (zcp->zc_data.zc_pool_guid == pool_guid && zcp->zc_data.zc_vdev_guid == 0) { zcp->zc_when = loaded; } } } ret = nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &vd); assert(ret == 0); zfs_mark_vdev(pool_guid, vd, &loaded); zpool_close(zhp); return (0); } /* * Find a pool with a matching GUID. */ typedef struct find_cbdata { uint64_t cb_guid; zpool_handle_t *cb_zhp; } find_cbdata_t; static int find_pool(zpool_handle_t *zhp, void *data) { find_cbdata_t *cbp = data; if (cbp->cb_guid == zpool_get_prop_int(zhp, ZPOOL_PROP_GUID, NULL)) { cbp->cb_zhp = zhp; return (0); } zpool_close(zhp); return (0); } struct load_time_arg { uint64_t lt_guid; er_timeval_t *lt_time; boolean_t lt_found; }; static int zpool_find_load_time(zpool_handle_t *zhp, void *arg) { struct load_time_arg *lta = arg; uint64_t pool_guid; uint64_t *tod; nvlist_t *config; uint_t nelem; if (lta->lt_found) { zpool_close(zhp); return (0); } pool_guid = zpool_get_prop_int(zhp, ZPOOL_PROP_GUID, NULL); if (pool_guid != lta->lt_guid) { zpool_close(zhp); return (0); } if ((config = zpool_get_config(zhp, NULL)) == NULL) { zpool_close(zhp); return (-1); } if (nvlist_lookup_uint64_array(config, ZPOOL_CONFIG_LOADED_TIME, &tod, &nelem) == 0 && nelem == 2) { lta->lt_found = B_TRUE; lta->lt_time->ertv_sec = tod[0]; lta->lt_time->ertv_nsec = tod[1]; } zpool_close(zhp); return (0); } static void zfs_purge_cases(fmd_hdl_t *hdl) { zfs_case_t *zcp; uu_list_walk_t *walk; libzfs_handle_t *zhdl = fmd_hdl_getspecific(hdl); /* * There is no way to open a pool by GUID, or lookup a vdev by GUID. No * matter what we do, we're going to have to stomach a O(vdevs * cases) * algorithm. In reality, both quantities are likely so small that * neither will matter. Given that iterating over pools is more * expensive than iterating over the in-memory case list, we opt for a * 'present' flag in each case that starts off cleared. We then iterate * over all pools, marking those that are still present, and removing * those that aren't found. * * Note that we could also construct an FMRI and rely on * fmd_nvl_fmri_present(), but this would end up doing the same search. */ /* * Mark the cases an not present. */ for (zcp = uu_list_first(zfs_cases); zcp != NULL; zcp = uu_list_next(zfs_cases, zcp)) zcp->zc_present = B_FALSE; /* * Iterate over all pools and mark the pools and vdevs found. If this * fails (most probably because we're out of memory), then don't close * any of the cases and we cannot be sure they are accurate. */ if (zpool_iter(zhdl, zfs_mark_pool, NULL) != 0) return; /* * Remove those cases which were not found. */ walk = uu_list_walk_start(zfs_cases, UU_WALK_ROBUST); while ((zcp = uu_list_walk_next(walk)) != NULL) { if (!zcp->zc_present) fmd_case_close(hdl, zcp->zc_case); } uu_list_walk_end(walk); } /* * Construct the name of a serd engine given the pool/vdev GUID and type (io, * checksum, or probe). */ static void zfs_serd_name(char *buf, uint64_t pool_guid, uint64_t vdev_guid, const char *type) { (void) snprintf(buf, MAX_SERDLEN, "zfs_%llx_%llx_%s", pool_guid, vdev_guid, type); } /* * Solve a given ZFS case. This first checks to make sure the diagnosis is * still valid, as well as cleaning up any pending timer associated with the * case. */ static void zfs_case_solve(fmd_hdl_t *hdl, zfs_case_t *zcp, const char *faultname, boolean_t checkunusable) { libzfs_handle_t *zhdl = fmd_hdl_getspecific(hdl); nvlist_t *detector, *fault; boolean_t serialize; nvlist_t *fmri, *fru; topo_hdl_t *thp; int err; /* * Construct the detector from the case data. The detector is in the * ZFS scheme, and is either the pool or the vdev, depending on whether * this is a vdev or pool fault. */ detector = fmd_nvl_alloc(hdl, FMD_SLEEP); (void) nvlist_add_uint8(detector, FM_VERSION, ZFS_SCHEME_VERSION0); (void) nvlist_add_string(detector, FM_FMRI_SCHEME, FM_FMRI_SCHEME_ZFS); (void) nvlist_add_uint64(detector, FM_FMRI_ZFS_POOL, zcp->zc_data.zc_pool_guid); if (zcp->zc_data.zc_vdev_guid != 0) { (void) nvlist_add_uint64(detector, FM_FMRI_ZFS_VDEV, zcp->zc_data.zc_vdev_guid); } /* * We also want to make sure that the detector (pool or vdev) properly * reflects the diagnosed state, when the fault corresponds to internal * ZFS state (i.e. not checksum or I/O error-induced). Otherwise, a * device which was unavailable early in boot (because the driver/file * wasn't available) and is now healthy will be mis-diagnosed. */ if (!fmd_nvl_fmri_present(hdl, detector) || (checkunusable && !fmd_nvl_fmri_unusable(hdl, detector))) { fmd_case_close(hdl, zcp->zc_case); nvlist_free(detector); return; } fru = NULL; if (zcp->zc_fru != NULL && (thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION)) != NULL) { /* * If the vdev had an associated FRU, then get the FRU nvlist * from the topo handle and use that in the suspect list. We * explicitly lookup the FRU because the fmri reported from the * kernel may not have up to date details about the disk itself * (serial, part, etc). */ if (topo_fmri_str2nvl(thp, zcp->zc_fru, &fmri, &err) == 0) { /* * If the disk is part of the system chassis, but the * FRU indicates a different chassis ID than our * current system, then ignore the error. This * indicates that the device was part of another * cluster head, and for obvious reasons cannot be * imported on this system. */ if (libzfs_fru_notself(zhdl, zcp->zc_fru)) { fmd_case_close(hdl, zcp->zc_case); nvlist_free(fmri); fmd_hdl_topo_rele(hdl, thp); nvlist_free(detector); return; } /* * If the device is no longer present on the system, or * topo_fmri_fru() fails for other reasons, then fall * back to the fmri specified in the vdev. */ if (topo_fmri_fru(thp, fmri, &fru, &err) != 0) fru = fmd_nvl_dup(hdl, fmri, FMD_SLEEP); nvlist_free(fmri); } fmd_hdl_topo_rele(hdl, thp); } fault = fmd_nvl_create_fault(hdl, faultname, 100, detector, fru, detector); fmd_case_add_suspect(hdl, zcp->zc_case, fault); nvlist_free(fru); fmd_case_solve(hdl, zcp->zc_case); serialize = B_FALSE; if (zcp->zc_data.zc_has_remove_timer) { fmd_timer_remove(hdl, zcp->zc_remove_timer); zcp->zc_data.zc_has_remove_timer = 0; serialize = B_TRUE; } if (serialize) zfs_case_serialize(hdl, zcp); nvlist_free(detector); } /* * This #define and function access a private interface of the FMA * framework. Ereports include a time-of-day upper bound. * We want to look at that so we can compare it to when pools get * loaded. */ #define FMD_EVN_TOD "__tod" static boolean_t timeval_earlier(er_timeval_t *a, er_timeval_t *b) { return (a->ertv_sec < b->ertv_sec || (a->ertv_sec == b->ertv_sec && a->ertv_nsec < b->ertv_nsec)); } /*ARGSUSED*/ static void zfs_ereport_when(fmd_hdl_t *hdl, nvlist_t *nvl, er_timeval_t *when) { uint64_t *tod; uint_t nelem; if (nvlist_lookup_uint64_array(nvl, FMD_EVN_TOD, &tod, &nelem) == 0 && nelem == 2) { when->ertv_sec = tod[0]; when->ertv_nsec = tod[1]; } else { when->ertv_sec = when->ertv_nsec = UINT64_MAX; } } /* * Main fmd entry point. */ /*ARGSUSED*/ static void zfs_fm_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { zfs_case_t *zcp, *dcp; libzfs_handle_t *zhdl; zpool_handle_t *zhp; int32_t pool_state; uint64_t ena, pool_guid, vdev_guid; er_timeval_t pool_load; er_timeval_t er_when; nvlist_t *detector; boolean_t pool_found = B_FALSE; boolean_t isresource; boolean_t is_inactive_spare, islog, iscache; nvlist_t *vd_nvl = NULL; char *fru, *type, *vdg; find_cbdata_t cb; /* * We subscribe to notifications for vdev or pool removal. In these * cases, there may be cases that no longer apply. Purge any cases * that no longer apply. */ if (fmd_nvl_class_match(hdl, nvl, "resource.sysevent.EC_zfs.*")) { zfs_purge_cases(hdl); zfs_stats.resource_drops.fmds_value.ui64++; return; } isresource = fmd_nvl_class_match(hdl, nvl, "resource.fs.zfs.*"); if (isresource) { /* * For resources, we don't have a normal payload. */ if (nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID, &vdev_guid) != 0) pool_state = SPA_LOAD_OPEN; else pool_state = SPA_LOAD_NONE; detector = NULL; } else { (void) nvlist_lookup_nvlist(nvl, FM_EREPORT_DETECTOR, &detector); (void) nvlist_lookup_int32(nvl, FM_EREPORT_PAYLOAD_ZFS_POOL_CONTEXT, &pool_state); } /* * We also ignore all ereports generated during an import of a pool, * since the only possible fault (.pool) would result in import failure, * and hence no persistent fault. Some day we may want to do something * with these ereports, so we continue generating them internally. */ if (pool_state == SPA_LOAD_IMPORT) { zfs_stats.import_drops.fmds_value.ui64++; return; } /* * Device I/O errors are ignored during pool open. */ if (pool_state == SPA_LOAD_OPEN && (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_CHECKSUM)) || fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO)) || fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_PROBE_FAILURE)))) { zfs_stats.dev_drops.fmds_value.ui64++; return; } /* * We ignore ereports for anything except disks and files. */ if (nvlist_lookup_string(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_TYPE, &type) == 0) { if (strcmp(type, VDEV_TYPE_DISK) != 0 && strcmp(type, VDEV_TYPE_FILE) != 0) { zfs_stats.vdev_drops.fmds_value.ui64++; return; } } /* * Determine if this ereport corresponds to an open case. Previous * incarnations of this DE used the ENA to chain events together as * part of the same case. The problem with this is that we rely on * global uniqueness of cases based on (pool_guid, vdev_guid) pair when * generating SERD engines. Instead, we have a case for each vdev or * pool, regardless of the ENA. */ (void) nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_POOL_GUID, &pool_guid); if (nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID, &vdev_guid) != 0) vdev_guid = 0; if (nvlist_lookup_uint64(nvl, FM_EREPORT_ENA, &ena) != 0) ena = 0; zfs_ereport_when(hdl, nvl, &er_when); for (zcp = uu_list_first(zfs_cases); zcp != NULL; zcp = uu_list_next(zfs_cases, zcp)) { if (zcp->zc_data.zc_pool_guid == pool_guid) { pool_found = B_TRUE; pool_load = zcp->zc_when; } if (zcp->zc_data.zc_vdev_guid == vdev_guid && zcp->zc_data.zc_pool_guid == pool_guid) break; } if (pool_found) { fmd_hdl_debug(hdl, "pool %llx, " "ereport time %lld.%lld, pool load time = %lld.%lld\n", pool_guid, er_when.ertv_sec, er_when.ertv_nsec, pool_load.ertv_sec, pool_load.ertv_nsec); } /* * Avoid falsely accusing a pool of being faulty. Do so by * not replaying ereports that were generated prior to the * current import. If the failure that generated them was * transient because the device was actually removed but we * didn't receive the normal asynchronous notification, we * don't want to mark it as faulted and potentially panic. If * there is still a problem we'd expect not to be able to * import the pool, or that new ereports will be generated * once the pool is used. */ if (pool_found && timeval_earlier(&er_when, &pool_load)) { zfs_stats.old_drops.fmds_value.ui64++; return; } if (!pool_found) { /* * Haven't yet seen this pool, but same situation * may apply. */ libzfs_handle_t *zhdl = fmd_hdl_getspecific(hdl); struct load_time_arg la; la.lt_guid = pool_guid; la.lt_time = &pool_load; la.lt_found = B_FALSE; if (zhdl != NULL && zpool_iter(zhdl, zpool_find_load_time, &la) == 0 && la.lt_found == B_TRUE) { pool_found = B_TRUE; fmd_hdl_debug(hdl, "pool %llx, " "ereport time %lld.%lld, " "pool load time = %lld.%lld\n", pool_guid, er_when.ertv_sec, er_when.ertv_nsec, pool_load.ertv_sec, pool_load.ertv_nsec); if (timeval_earlier(&er_when, &pool_load)) { zfs_stats.old_drops.fmds_value.ui64++; return; } } } if (zcp == NULL) { fmd_case_t *cs; zfs_case_data_t data = { 0 }; /* * If this is one of our 'fake' resource ereports, and there is * no case open, simply discard it. */ if (isresource) { zfs_stats.resource_drops.fmds_value.ui64++; return; } /* * Open a new case. */ cs = fmd_case_open(hdl, NULL); /* * Initialize the case buffer. To commonize code, we actually * create the buffer with existing data, and then call * zfs_case_unserialize() to instantiate the in-core structure. */ fmd_buf_create(hdl, cs, CASE_DATA, sizeof (zfs_case_data_t)); data.zc_version = CASE_DATA_VERSION_SERD; data.zc_ena = ena; data.zc_pool_guid = pool_guid; data.zc_vdev_guid = vdev_guid; data.zc_pool_state = (int)pool_state; fmd_buf_write(hdl, cs, CASE_DATA, &data, sizeof (data)); zcp = zfs_case_unserialize(hdl, cs); assert(zcp != NULL); if (pool_found) zcp->zc_when = pool_load; } /* * If this is an ereport for a case with an associated vdev FRU, make * sure it is accurate and up to date. */ if (nvlist_lookup_string(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_FRU, &fru) == 0) { topo_hdl_t *thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if (zcp->zc_fru == NULL || !topo_fmri_strcmp(thp, zcp->zc_fru, fru)) { if (zcp->zc_fru != NULL) { fmd_hdl_strfree(hdl, zcp->zc_fru); fmd_buf_destroy(hdl, zcp->zc_case, CASE_FRU); } zcp->zc_fru = fmd_hdl_strdup(hdl, fru, FMD_SLEEP); zfs_case_serialize(hdl, zcp); } fmd_hdl_topo_rele(hdl, thp); } if (isresource) { if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_RSRC(FM_RESOURCE_AUTOREPLACE))) { /* * The 'resource.fs.zfs.autoreplace' event indicates * that the pool was loaded with the 'autoreplace' * property set. In this case, any pending device * failures should be ignored, as the asynchronous * autoreplace handling will take care of them. */ fmd_case_close(hdl, zcp->zc_case); } else if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_RSRC(FM_RESOURCE_REMOVED))) { /* * The 'resource.fs.zfs.removed' event indicates that * device removal was detected, and the device was * closed asynchronously. If this is the case, we * assume that any recent I/O errors were due to the * device removal, not any fault of the device itself. * We reset the SERD engine, and cancel any pending * timers. */ if (zcp->zc_data.zc_has_remove_timer) { fmd_timer_remove(hdl, zcp->zc_remove_timer); zcp->zc_data.zc_has_remove_timer = 0; zfs_case_serialize(hdl, zcp); } if (zcp->zc_data.zc_serd_io[0] != '\0') fmd_serd_reset(hdl, zcp->zc_data.zc_serd_io); if (zcp->zc_data.zc_serd_checksum[0] != '\0') fmd_serd_reset(hdl, zcp->zc_data.zc_serd_checksum); if (zcp->zc_data.zc_serd_probe[0] != '\0') fmd_serd_reset(hdl, zcp->zc_data.zc_serd_probe); } zfs_stats.resource_drops.fmds_value.ui64++; return; } /* * Associate the ereport with this case. */ fmd_case_add_ereport(hdl, zcp->zc_case, ep); /* * Don't do anything else if this case is already solved. */ if (fmd_case_solved(hdl, zcp->zc_case)) return; zhdl = fmd_hdl_getspecific(hdl); /* * Find the corresponding pool. */ cb.cb_guid = pool_guid; cb.cb_zhp = NULL; if (zhdl != NULL && zpool_iter(zhdl, find_pool, &cb) != 0) { zfs_stats.pool_drops.fmds_value.ui64++; return; } zhp = cb.cb_zhp; /* NULL if pool was not found. */ if (zhp != NULL) { /* * The libzfs API takes a string representation of a base-10 * guid here instead of a number, likely because the primary * libzfs consumers are the CLI tools. */ vdg = fmd_hdl_zalloc(hdl, MAX_ULL_STR, FMD_SLEEP); (void) snprintf(vdg, MAX_ULL_STR, "%" PRIx64, vdev_guid); /* * According to libzfs the 'spare' bit is set when the spare is * unused, and unset when in use. * * We don't really care about the returned nvlist. We're only * interested in the boolean flags. */ if ((vd_nvl = zpool_find_vdev(zhp, vdg, &is_inactive_spare, &islog, &iscache)) != NULL) { nvlist_free(vd_nvl); } fmd_hdl_free(hdl, vdg, MAX_ULL_STR); } /* * Determine if we should solve the case and generate a fault. We solve * a case if: * * a. A pool failed to open (ereport.fs.zfs.pool) * b. A device failed to open (ereport.fs.zfs.pool) while a pool * was up and running. * * We may see a series of ereports associated with a pool open, all * chained together by the same ENA. If the pool open succeeds, then * we'll see no further ereports. To detect when a pool open has * succeeded, we associate a timer with the event. When it expires, we * close the case. */ if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_POOL))) { /* * Pool level fault. Before solving the case, go through and * close any open device cases that may be pending. */ for (dcp = uu_list_first(zfs_cases); dcp != NULL; dcp = uu_list_next(zfs_cases, dcp)) { if (dcp->zc_data.zc_pool_guid == zcp->zc_data.zc_pool_guid && dcp->zc_data.zc_vdev_guid != 0) fmd_case_close(hdl, dcp->zc_case); } zfs_case_solve(hdl, zcp, "fault.fs.zfs.pool", B_TRUE); } else if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_LOG_REPLAY))) { /* * Pool level fault for reading the intent logs. */ zfs_case_solve(hdl, zcp, "fault.fs.zfs.log_replay", B_TRUE); } else if (fmd_nvl_class_match(hdl, nvl, "ereport.fs.zfs.vdev.*")) { /* * Device fault. */ zfs_case_solve(hdl, zcp, "fault.fs.zfs.device", B_TRUE); } else if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO)) || fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_CHECKSUM)) || fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO_FAILURE)) || fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_PROBE_FAILURE))) { char *failmode = NULL; boolean_t checkremove = B_FALSE; /* * If this is a checksum, I/O, or probe error, then toss it into * the appropriate SERD engine and check to see if it has fired. * Ideally, we want to do something more sophisticated, * (persistent errors for a single data block, etc). For now, * a single SERD engine is sufficient. */ if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO))) { if (zcp->zc_data.zc_serd_io[0] == '\0') { zfs_serd_name(zcp->zc_data.zc_serd_io, pool_guid, vdev_guid, "io"); fmd_serd_create(hdl, zcp->zc_data.zc_serd_io, fmd_prop_get_int32(hdl, "io_N"), fmd_prop_get_int64(hdl, "io_T")); zfs_case_serialize(hdl, zcp); } if (fmd_serd_record(hdl, zcp->zc_data.zc_serd_io, ep)) checkremove = B_TRUE; } else if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_CHECKSUM))) { if (zcp->zc_data.zc_serd_checksum[0] == '\0') { zfs_serd_name(zcp->zc_data.zc_serd_checksum, pool_guid, vdev_guid, "checksum"); fmd_serd_create(hdl, zcp->zc_data.zc_serd_checksum, fmd_prop_get_int32(hdl, "checksum_N"), fmd_prop_get_int64(hdl, "checksum_T")); zfs_case_serialize(hdl, zcp); } if (fmd_serd_record(hdl, zcp->zc_data.zc_serd_checksum, ep)) { zfs_case_solve(hdl, zcp, "fault.fs.zfs.vdev.checksum", B_FALSE); } } else if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_IO_FAILURE)) && (nvlist_lookup_string(nvl, FM_EREPORT_PAYLOAD_ZFS_POOL_FAILMODE, &failmode) == 0) && failmode != NULL) { if (strncmp(failmode, FM_EREPORT_FAILMODE_CONTINUE, strlen(FM_EREPORT_FAILMODE_CONTINUE)) == 0) { zfs_case_solve(hdl, zcp, "fault.fs.zfs.io_failure_continue", B_FALSE); } else if (strncmp(failmode, FM_EREPORT_FAILMODE_WAIT, strlen(FM_EREPORT_FAILMODE_WAIT)) == 0) { zfs_case_solve(hdl, zcp, "fault.fs.zfs.io_failure_wait", B_FALSE); } } else if (fmd_nvl_class_match(hdl, nvl, ZFS_MAKE_EREPORT(FM_EREPORT_ZFS_PROBE_FAILURE))) { if (zcp->zc_data.zc_serd_probe[0] == '\0') { zfs_serd_name(zcp->zc_data.zc_serd_probe, pool_guid, vdev_guid, "probe"); fmd_serd_create(hdl, zcp->zc_data.zc_serd_probe, fmd_prop_get_int32(hdl, "probe_N"), fmd_prop_get_int64(hdl, "probe_T")); zfs_case_serialize(hdl, zcp); } /* * We only want to wait for SERD triggers for spare * vdevs. Normal pool vdevs should be diagnosed * immediately if a probe failure is received. */ if (!is_inactive_spare || fmd_serd_record(hdl, zcp->zc_data.zc_serd_probe, ep)) { checkremove = B_TRUE; } } /* * Because I/O errors may be due to device removal, we postpone * any diagnosis until we're sure that we aren't about to * receive a 'resource.fs.zfs.removed' event. */ if (checkremove) { if (zcp->zc_data.zc_has_remove_timer) fmd_timer_remove(hdl, zcp->zc_remove_timer); zcp->zc_remove_timer = fmd_timer_install(hdl, zcp, NULL, zfs_remove_timeout); if (!zcp->zc_data.zc_has_remove_timer) { zcp->zc_data.zc_has_remove_timer = 1; zfs_case_serialize(hdl, zcp); } } } } /* * The timeout is fired when we diagnosed an I/O error, and it was not due to * device removal (which would cause the timeout to be cancelled). */ /* ARGSUSED */ static void zfs_fm_timeout(fmd_hdl_t *hdl, id_t id, void *data) { zfs_case_t *zcp = data; if (id == zcp->zc_remove_timer) zfs_case_solve(hdl, zcp, "fault.fs.zfs.vdev.io", B_FALSE); } static void zfs_fm_close(fmd_hdl_t *hdl, fmd_case_t *cs) { zfs_case_t *zcp = fmd_case_getspecific(hdl, cs); if (zcp->zc_data.zc_serd_checksum[0] != '\0') fmd_serd_destroy(hdl, zcp->zc_data.zc_serd_checksum); if (zcp->zc_data.zc_serd_io[0] != '\0') fmd_serd_destroy(hdl, zcp->zc_data.zc_serd_io); if (zcp->zc_data.zc_serd_probe[0] != '\0') fmd_serd_destroy(hdl, zcp->zc_data.zc_serd_probe); if (zcp->zc_data.zc_has_remove_timer) fmd_timer_remove(hdl, zcp->zc_remove_timer); uu_list_remove(zfs_cases, zcp); fmd_hdl_free(hdl, zcp, sizeof (zfs_case_t)); } /* * We use the fmd gc entry point to look for old cases that no longer apply. * This allows us to keep our set of case data small in a long running system. */ static void zfs_fm_gc(fmd_hdl_t *hdl) { zfs_purge_cases(hdl); } static const fmd_hdl_ops_t fmd_ops = { zfs_fm_recv, /* fmdo_recv */ zfs_fm_timeout, /* fmdo_timeout */ zfs_fm_close, /* fmdo_close */ NULL, /* fmdo_stats */ zfs_fm_gc, /* fmdo_gc */ }; static const fmd_prop_t fmd_props[] = { { "checksum_N", FMD_TYPE_UINT32, "10" }, { "checksum_T", FMD_TYPE_TIME, "10min" }, { "io_N", FMD_TYPE_UINT32, "10" }, { "io_T", FMD_TYPE_TIME, "10min" }, { "probe_N", FMD_TYPE_UINT32, "5" }, { "probe_T", FMD_TYPE_TIME, "24hour" }, { "remove_timeout", FMD_TYPE_TIME, "15sec" }, { NULL, 0, NULL } }; static const fmd_hdl_info_t fmd_info = { "ZFS Diagnosis Engine", "1.0", &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { fmd_case_t *cp; libzfs_handle_t *zhdl; if ((zhdl = libzfs_init()) == NULL) return; if ((zfs_case_pool = uu_list_pool_create("zfs_case_pool", sizeof (zfs_case_t), offsetof(zfs_case_t, zc_node), NULL, 0)) == NULL) { libzfs_fini(zhdl); return; } if ((zfs_cases = uu_list_create(zfs_case_pool, NULL, 0)) == NULL) { uu_list_pool_destroy(zfs_case_pool); libzfs_fini(zhdl); return; } if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) { uu_list_destroy(zfs_cases); uu_list_pool_destroy(zfs_case_pool); libzfs_fini(zhdl); return; } fmd_hdl_setspecific(hdl, zhdl); (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC, sizeof (zfs_stats) / sizeof (fmd_stat_t), (fmd_stat_t *)&zfs_stats); /* * Iterate over all active cases and unserialize the associated buffers, * adding them to our list of open cases. */ for (cp = fmd_case_next(hdl, NULL); cp != NULL; cp = fmd_case_next(hdl, cp)) (void) zfs_case_unserialize(hdl, cp); /* * Clear out any old cases that are no longer valid. */ zfs_purge_cases(hdl); zfs_remove_timeout = fmd_prop_get_int64(hdl, "remove_timeout"); } void _fmd_fini(fmd_hdl_t *hdl) { zfs_case_t *zcp; uu_list_walk_t *walk; libzfs_handle_t *zhdl; /* * Remove all active cases. */ walk = uu_list_walk_start(zfs_cases, UU_WALK_ROBUST); while ((zcp = uu_list_walk_next(walk)) != NULL) { uu_list_remove(zfs_cases, zcp); fmd_hdl_free(hdl, zcp, sizeof (zfs_case_t)); } uu_list_walk_end(walk); uu_list_destroy(zfs_cases); uu_list_pool_destroy(zfs_case_pool); zhdl = fmd_hdl_getspecific(hdl); libzfs_fini(zhdl); } # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # MODULE = zfs-retire CLASS = common SRCS = zfs_retire.c include ../../Makefile.plugin LDLIBS += -lzfs -L$(ROOTLIB)/fm -ltopo LDFLAGS += -R/usr/lib/fm # # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # # fmd configuration file for the zfs retire agent. # # To enable automated retire for SSM wearout faults uncomment the line below: # setprop ssm_wearout_skip_retire false # subscribe fault.fs.zfs.* subscribe fault.io.* subscribe resource.fs.zfs.removed subscribe resource.fs.zfs.statechange subscribe resource.sysevent.EC_zfs.ESC_ZFS_vdev_remove /* * 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) 2006, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright 2019 Joyent, Inc. * Copyright 2024 OmniOS Community Edition (OmniOSce) Association. */ /* * The ZFS retire agent is responsible for managing hot spares across all pools. * When we see a device fault or a device removal, we try to open the associated * pool and look for any hot spares. We iterate over any available hot spares * and attempt a 'zpool replace' for each one. * * For vdevs diagnosed as faulty, the agent is also responsible for proactively * marking the vdev FAULTY (for I/O errors) or DEGRADED (for checksum errors). */ #include #include #include #include #include #include #include typedef struct zfs_retire_repaired { struct zfs_retire_repaired *zrr_next; uint64_t zrr_pool; uint64_t zrr_vdev; } zfs_retire_repaired_t; typedef struct zfs_retire_data { libzfs_handle_t *zrd_hdl; zfs_retire_repaired_t *zrd_repaired; } zfs_retire_data_t; static void zfs_retire_clear_data(fmd_hdl_t *hdl, zfs_retire_data_t *zdp) { zfs_retire_repaired_t *zrp; while ((zrp = zdp->zrd_repaired) != NULL) { zdp->zrd_repaired = zrp->zrr_next; fmd_hdl_free(hdl, zrp, sizeof (zfs_retire_repaired_t)); } } /* * Find a pool with a matching GUID. */ typedef struct find_cbdata { uint64_t cb_guid; const char *cb_fru; zpool_handle_t *cb_zhp; nvlist_t *cb_vdev; } find_cbdata_t; static int find_pool(zpool_handle_t *zhp, void *data) { find_cbdata_t *cbp = data; if (cbp->cb_guid == zpool_get_prop_int(zhp, ZPOOL_PROP_GUID, NULL)) { cbp->cb_zhp = zhp; return (1); } zpool_close(zhp); return (0); } /* * Find a vdev within a tree with a matching GUID. */ static nvlist_t * find_vdev(libzfs_handle_t *zhdl, nvlist_t *nv, const char *search_fru, uint64_t search_guid) { uint64_t guid; nvlist_t **child; uint_t c, children; nvlist_t *ret; char *fru; if (search_fru != NULL) { if (nvlist_lookup_string(nv, ZPOOL_CONFIG_FRU, &fru) == 0 && libzfs_fru_compare(zhdl, fru, search_fru)) return (nv); } else { if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &guid) == 0 && guid == search_guid) return (nv); } if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, &child, &children) != 0) return (NULL); for (c = 0; c < children; c++) { if ((ret = find_vdev(zhdl, child[c], search_fru, search_guid)) != NULL) return (ret); } if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_L2CACHE, &child, &children) == 0) { for (c = 0; c < children; c++) { if ((ret = find_vdev(zhdl, child[c], search_fru, search_guid)) != NULL) return (ret); } } if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_SPARES, &child, &children) == 0) { for (c = 0; c < children; c++) { if ((ret = find_vdev(zhdl, child[c], search_fru, search_guid)) != NULL) return (ret); } } return (NULL); } /* * Given a (pool, vdev) GUID pair, find the matching pool and vdev. */ static zpool_handle_t * find_by_guid(libzfs_handle_t *zhdl, uint64_t pool_guid, uint64_t vdev_guid, nvlist_t **vdevp) { find_cbdata_t cb; zpool_handle_t *zhp; nvlist_t *config, *nvroot; /* * Find the corresponding pool and make sure the vdev still exists. */ cb.cb_guid = pool_guid; if (zpool_iter(zhdl, find_pool, &cb) != 1) return (NULL); zhp = cb.cb_zhp; config = zpool_get_config(zhp, NULL); if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &nvroot) != 0) { zpool_close(zhp); return (NULL); } if (vdev_guid != 0) { if ((*vdevp = find_vdev(zhdl, nvroot, NULL, vdev_guid)) == NULL) { zpool_close(zhp); return (NULL); } } return (zhp); } static int search_pool(zpool_handle_t *zhp, void *data) { find_cbdata_t *cbp = data; nvlist_t *config; nvlist_t *nvroot; config = zpool_get_config(zhp, NULL); if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &nvroot) != 0) { zpool_close(zhp); return (0); } if ((cbp->cb_vdev = find_vdev(zpool_get_handle(zhp), nvroot, cbp->cb_fru, 0)) != NULL) { cbp->cb_zhp = zhp; return (1); } zpool_close(zhp); return (0); } /* * Given a FRU FMRI, find the matching pool and vdev. */ static zpool_handle_t * find_by_fru(libzfs_handle_t *zhdl, const char *fru, nvlist_t **vdevp) { find_cbdata_t cb; cb.cb_fru = fru; cb.cb_zhp = NULL; if (zpool_iter(zhdl, search_pool, &cb) != 1) return (NULL); *vdevp = cb.cb_vdev; return (cb.cb_zhp); } /* * Callback for sorting spares by increasing size. */ static int sort_spares_by_size(const void *ap, const void *bp) { nvlist_t *a = *(nvlist_t **)ap; nvlist_t *b = *(nvlist_t **)bp; vdev_stat_t *vsa, *vsb; vdev_stat_t v0 = { 0 }; uint_t c; if (nvlist_lookup_uint64_array(a, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vsa, &c) != 0) { vsa = &v0; } if (nvlist_lookup_uint64_array(b, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vsb, &c) != 0) { vsb = &v0; } if (vsa->vs_rsize > vsb->vs_rsize) return (1); if (vsa->vs_rsize < vsb->vs_rsize) return (-1); return (0); } /* * Given a vdev, attempt to replace it with every known spare until one * succeeds. The spares are first sorted by increasing size so that the * smallest possible replacement is used. */ static void replace_with_spare(fmd_hdl_t *hdl, zpool_handle_t *zhp, nvlist_t *vdev) { nvlist_t *config, *nvroot, *replacement; nvlist_t **spares, **sorted_spares; uint_t s, nspares; char *dev_name; zprop_source_t source; int ashift; zfs_retire_data_t *zdp = fmd_hdl_getspecific(hdl); libzfs_handle_t *zhdl = zdp->zrd_hdl; config = zpool_get_config(zhp, NULL); if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &nvroot) != 0) { return; } /* * Find out if there are any hot spares available in the pool. */ if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_SPARES, &spares, &nspares) != 0) { return; } /* * look up "ashift" pool property, we may need it for the replacement */ ashift = zpool_get_prop_int(zhp, ZPOOL_PROP_ASHIFT, &source); replacement = fmd_nvl_alloc(hdl, FMD_SLEEP); (void) nvlist_add_string(replacement, ZPOOL_CONFIG_TYPE, VDEV_TYPE_ROOT); dev_name = zpool_vdev_name(zhdl, zhp, vdev, B_FALSE); /* * Try to replace each spare, starting with the smallest and ending * when we successfully replace it. */ sorted_spares = fmd_hdl_alloc(hdl, nspares * sizeof (nvlist_t *), FMD_SLEEP); for (s = 0; s < nspares; s++) sorted_spares[s] = spares[s]; qsort((void *)sorted_spares, nspares, sizeof (nvlist_t *), sort_spares_by_size); for (s = 0; s < nspares; s++) { nvlist_t *spare = sorted_spares[s]; char *spare_name; if (nvlist_lookup_string(spare, ZPOOL_CONFIG_PATH, &spare_name) != 0) { continue; } /* if set, add the "ashift" pool property to the spare nvlist */ if (source != ZPROP_SRC_DEFAULT) { (void) nvlist_add_uint64(spare, ZPOOL_CONFIG_ASHIFT, ashift); } (void) nvlist_add_nvlist_array(replacement, ZPOOL_CONFIG_CHILDREN, &spare, 1); if (zpool_vdev_attach(zhp, dev_name, spare_name, replacement, B_TRUE) == 0) { break; } } fmd_hdl_free(hdl, sorted_spares, nspares * sizeof (nvlist_t *)); free(dev_name); nvlist_free(replacement); } /* * Repair this vdev if we had diagnosed a 'fault.fs.zfs.device' and * ASRU is now usable. ZFS has found the device to be present and * functioning. */ /*ARGSUSED*/ void zfs_vdev_repair(fmd_hdl_t *hdl, nvlist_t *nvl) { zfs_retire_data_t *zdp = fmd_hdl_getspecific(hdl); zfs_retire_repaired_t *zrp; uint64_t pool_guid, vdev_guid; nvlist_t *asru; if (nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_POOL_GUID, &pool_guid) != 0 || nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID, &vdev_guid) != 0) return; /* * Before checking the state of the ASRU, go through and see if we've * already made an attempt to repair this ASRU. This list is cleared * whenever we receive any kind of list event, and is designed to * prevent us from generating a feedback loop when we attempt repairs * against a faulted pool. The problem is that checking the unusable * state of the ASRU can involve opening the pool, which can post * statechange events but otherwise leave the pool in the faulted * state. This list allows us to detect when a statechange event is * due to our own request. */ for (zrp = zdp->zrd_repaired; zrp != NULL; zrp = zrp->zrr_next) { if (zrp->zrr_pool == pool_guid && zrp->zrr_vdev == vdev_guid) return; } asru = fmd_nvl_alloc(hdl, FMD_SLEEP); (void) nvlist_add_uint8(asru, FM_VERSION, ZFS_SCHEME_VERSION0); (void) nvlist_add_string(asru, FM_FMRI_SCHEME, FM_FMRI_SCHEME_ZFS); (void) nvlist_add_uint64(asru, FM_FMRI_ZFS_POOL, pool_guid); (void) nvlist_add_uint64(asru, FM_FMRI_ZFS_VDEV, vdev_guid); /* * We explicitly check for the unusable state here to make sure we * aren't responding to a transient state change. As part of opening a * vdev, it's possible to see the 'statechange' event, only to be * followed by a vdev failure later. If we don't check the current * state of the vdev (or pool) before marking it repaired, then we risk * generating spurious repair events followed immediately by the same * diagnosis. * * This assumes that the ZFS scheme code associated unusable (i.e. * isolated) with its own definition of faulty state. In the case of a * DEGRADED leaf vdev (due to checksum errors), this is not the case. * This works, however, because the transient state change is not * posted in this case. This could be made more explicit by not * relying on the scheme's unusable callback and instead directly * checking the vdev state, where we could correctly account for * DEGRADED state. */ if (!fmd_nvl_fmri_unusable(hdl, asru) && fmd_nvl_fmri_has_fault(hdl, asru, FMD_HAS_FAULT_ASRU, NULL)) { topo_hdl_t *thp; char *fmri = NULL; int err; thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if (topo_fmri_nvl2str(thp, asru, &fmri, &err) == 0) (void) fmd_repair_asru(hdl, fmri); fmd_hdl_topo_rele(hdl, thp); topo_hdl_strfree(thp, fmri); } nvlist_free(asru); zrp = fmd_hdl_alloc(hdl, sizeof (zfs_retire_repaired_t), FMD_SLEEP); zrp->zrr_next = zdp->zrd_repaired; zrp->zrr_pool = pool_guid; zrp->zrr_vdev = vdev_guid; zdp->zrd_repaired = zrp; } /*ARGSUSED*/ static void zfs_retire_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class) { uint64_t pool_guid, vdev_guid; zpool_handle_t *zhp; nvlist_t *resource, *fault, *fru; nvlist_t **faults; uint_t f, nfaults; zfs_retire_data_t *zdp = fmd_hdl_getspecific(hdl); libzfs_handle_t *zhdl = zdp->zrd_hdl; boolean_t fault_device, degrade_device; boolean_t is_repair; char *scheme, *fmri; nvlist_t *vdev; char *uuid; int repair_done = 0; boolean_t retire; boolean_t is_disk; vdev_aux_t aux; topo_hdl_t *thp; int err; /* * If this is a resource notifying us of device removal, then simply * check for an available spare and continue. */ if (strcmp(class, "resource.fs.zfs.removed") == 0) { if (nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_POOL_GUID, &pool_guid) != 0 || nvlist_lookup_uint64(nvl, FM_EREPORT_PAYLOAD_ZFS_VDEV_GUID, &vdev_guid) != 0) return; if ((zhp = find_by_guid(zhdl, pool_guid, vdev_guid, &vdev)) == NULL) return; if (fmd_prop_get_int32(hdl, "spare_on_remove")) replace_with_spare(hdl, zhp, vdev); zpool_close(zhp); return; } if (strcmp(class, FM_LIST_RESOLVED_CLASS) == 0) return; if (strcmp(class, "resource.fs.zfs.statechange") == 0 || strcmp(class, "resource.sysevent.EC_zfs.ESC_ZFS_vdev_remove") == 0) { zfs_vdev_repair(hdl, nvl); return; } zfs_retire_clear_data(hdl, zdp); if (strcmp(class, FM_LIST_REPAIRED_CLASS) == 0) is_repair = B_TRUE; else is_repair = B_FALSE; /* * We subscribe to zfs faults as well as all repair events. */ if (nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST, &faults, &nfaults) != 0) return; for (f = 0; f < nfaults; f++) { fault = faults[f]; fault_device = B_FALSE; degrade_device = B_FALSE; is_disk = B_FALSE; if (nvlist_lookup_boolean_value(fault, FM_SUSPECT_RETIRE, &retire) == 0 && retire == 0) continue; if (fmd_nvl_class_match(hdl, fault, "fault.io.disk.ssm-wearout") && fmd_prop_get_int32(hdl, "ssm_wearout_skip_retire") == FMD_B_TRUE) { fmd_hdl_debug(hdl, "zfs-retire: ignoring SSM fault"); continue; } /* * While we subscribe to fault.fs.zfs.*, we only take action * for faults targeting a specific vdev (open failure or SERD * failure). We also subscribe to fault.io.* events, so that * faulty disks will be faulted in the ZFS configuration. */ if (fmd_nvl_class_match(hdl, fault, "fault.fs.zfs.vdev.io")) { fault_device = B_TRUE; } else if (fmd_nvl_class_match(hdl, fault, "fault.fs.zfs.vdev.checksum")) { degrade_device = B_TRUE; } else if (fmd_nvl_class_match(hdl, fault, "fault.fs.zfs.device")) { fault_device = B_FALSE; } else if (fmd_nvl_class_match(hdl, fault, "fault.io.*")) { is_disk = B_TRUE; fault_device = B_TRUE; } else { continue; } if (is_disk) { /* * This is a disk fault. Lookup the FRU, convert it to * an FMRI string, and attempt to find a matching vdev. */ if (nvlist_lookup_nvlist(fault, FM_FAULT_FRU, &fru) != 0 || nvlist_lookup_string(fru, FM_FMRI_SCHEME, &scheme) != 0) continue; if (strcmp(scheme, FM_FMRI_SCHEME_HC) != 0) continue; thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION); if (topo_fmri_nvl2str(thp, fru, &fmri, &err) != 0) { fmd_hdl_topo_rele(hdl, thp); continue; } zhp = find_by_fru(zhdl, fmri, &vdev); topo_hdl_strfree(thp, fmri); fmd_hdl_topo_rele(hdl, thp); if (zhp == NULL) continue; (void) nvlist_lookup_uint64(vdev, ZPOOL_CONFIG_GUID, &vdev_guid); aux = VDEV_AUX_EXTERNAL; } else { /* * This is a ZFS fault. Lookup the resource, and * attempt to find the matching vdev. */ if (nvlist_lookup_nvlist(fault, FM_FAULT_RESOURCE, &resource) != 0 || nvlist_lookup_string(resource, FM_FMRI_SCHEME, &scheme) != 0) continue; if (strcmp(scheme, FM_FMRI_SCHEME_ZFS) != 0) continue; if (nvlist_lookup_uint64(resource, FM_FMRI_ZFS_POOL, &pool_guid) != 0) continue; if (nvlist_lookup_uint64(resource, FM_FMRI_ZFS_VDEV, &vdev_guid) != 0) { if (is_repair) vdev_guid = 0; else continue; } if ((zhp = find_by_guid(zhdl, pool_guid, vdev_guid, &vdev)) == NULL) continue; aux = VDEV_AUX_ERR_EXCEEDED; } if (vdev_guid == 0) { /* * For pool-level repair events, clear the entire pool. */ (void) zpool_clear(zhp, NULL, NULL); zpool_close(zhp); continue; } /* * If this is a repair event, then mark the vdev as repaired and * continue. */ if (is_repair) { repair_done = 1; (void) zpool_vdev_clear(zhp, vdev_guid); zpool_close(zhp); continue; } /* * Actively fault the device if needed. */ if (fault_device) (void) zpool_vdev_fault(zhp, vdev_guid, aux); if (degrade_device) (void) zpool_vdev_degrade(zhp, vdev_guid, aux); /* * Attempt to substitute a hot spare. */ replace_with_spare(hdl, zhp, vdev); zpool_close(zhp); } if (strcmp(class, FM_LIST_REPAIRED_CLASS) == 0 && repair_done && nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) == 0) fmd_case_uuresolved(hdl, uuid); } static const fmd_hdl_ops_t fmd_ops = { zfs_retire_recv, /* fmdo_recv */ NULL, /* fmdo_timeout */ NULL, /* fmdo_close */ NULL, /* fmdo_stats */ NULL, /* fmdo_gc */ }; static const fmd_prop_t fmd_props[] = { { "spare_on_remove", FMD_TYPE_BOOL, "true" }, { "ssm_wearout_skip_retire", FMD_TYPE_BOOL, "true"}, { NULL, 0, NULL } }; static const fmd_hdl_info_t fmd_info = { "ZFS Retire Agent", "1.0", &fmd_ops, fmd_props }; void _fmd_init(fmd_hdl_t *hdl) { zfs_retire_data_t *zdp; libzfs_handle_t *zhdl; if ((zhdl = libzfs_init()) == NULL) return; if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) { libzfs_fini(zhdl); return; } zdp = fmd_hdl_zalloc(hdl, sizeof (zfs_retire_data_t), FMD_SLEEP); zdp->zrd_hdl = zhdl; fmd_hdl_setspecific(hdl, zdp); } void _fmd_fini(fmd_hdl_t *hdl) { zfs_retire_data_t *zdp = fmd_hdl_getspecific(hdl); if (zdp != NULL) { zfs_retire_clear_data(hdl, zdp); libzfs_fini(zdp->zrd_hdl); fmd_hdl_free(hdl, zdp, sizeof (zfs_retire_data_t)); } }