/*
* 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
* 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
*/
/*
*/
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#include <syslog.h>
#include <alloca.h>
#include <stddef.h>
#include <door.h>
#include <fmd_module.h>
#include <fmd_api.h>
#include <fmd_string.h>
#include <fmd_subr.h>
#include <fmd_error.h>
#include <fmd_event.h>
#include <fmd_eventq.h>
#include <fmd_dispq.h>
#include <fmd_timerq.h>
#include <fmd_thread.h>
#include <fmd_ustat.h>
#include <fmd_case.h>
#include <fmd_protocol.h>
#include <fmd_buf.h>
#include <fmd_asru.h>
#include <fmd_fmri.h>
#include <fmd_topo.h>
#include <fmd_ckpt.h>
#include <fmd_xprt.h>
#include <fmd.h>
/*
* Table of configuration file variable types ops-vector pointers. We use this
* to convert from the property description array specified by the module to an
* array of fmd_conf_formal_t's. The order of this array must match the order
* of #define values specified in <fmd_api.h> (i.e. FMD_TYPE_BOOL must be 0).
* For now, the fmd_conf_list and fmd_conf_path types are not supported as we
* do not believe modules need them and they would require more complexity.
*/
&fmd_conf_bool, /* FMD_TYPE_BOOL */
&fmd_conf_int32, /* FMD_TYPE_INT32 */
&fmd_conf_uint32, /* FMD_TYPE_UINT32 */
&fmd_conf_int64, /* FMD_TYPE_INT64 */
&fmd_conf_uint64, /* FMD_TYPE_UINT64 */
&fmd_conf_string, /* FMD_TYPE_STRING */
&fmd_conf_time, /* FMD_TYPE_TIME */
&fmd_conf_size, /* FMD_TYPE_SIZE */
};
/*
* fmd_api_vxerror() provides the engine underlying the fmd_hdl_[v]error() API
* calls and the fmd_api_[v]error() utility routine defined below. The routine
* formats the error, optionally associated with a particular errno code 'err',
* and logs it as an ereport associated with the calling module. Depending on
* other optional properties, we also emit a message to stderr and to syslog.
*/
static void
{
fmd_event_t *e;
char c;
/*
* fmd_api_vxerror() counts as both an error of class EFMD_MODULE
* as well as an instance of 'err' w.r.t. our internal bean counters.
*/
/*
* Format the message using vsnprintf(). As usual, if the format has a
* newline in it, it is printed alone; otherwise strerror() is added.
*/
err = 0; /* err is not relevant in the message */
if (err != 0) {
}
/*
* Create an error event corresponding to the error, insert it into the
* error log, and dispatch it to the fmd-self-diagnosis engine.
*/
if (c == '\n')
fmd_event_commit(e);
}
/*
* Similar to fmd_vdebug(), if the debugging switches are enabled we
* fmd_vdebug(), we also print to stderr if foreground mode is enabled.
* We also print the message if a built-in module is aborting before
* fmd has detached from its parent (e.g. default transport failure).
*/
}
}
}
/*PRINTFLIKE3*/
static void
{
}
/*
* fmd_api_verror() is a wrapper around fmd_api_vxerror() for API subroutines.
* It calls fmd_module_unlock() on behalf of its caller, logs the error, and
* then aborts the API call and the surrounding module entry point by doing an
* fmd_module_abort(), which longjmps to the place where we entered the module.
*/
static void
{
if (fmd_module_locked(mp))
}
/*PRINTFLIKE3*/
static void
{
}
/*
* Common code for fmd_api_module_lock() and fmd_api_transport_impl(). This
* code verifies that the handle is valid and associated with a proper thread.
*/
static fmd_module_t *
{
/*
* If our TSD is not present at all, this is either a serious bug or
* someone has created a thread behind our back and is using fmd's API.
* We can't call fmd_api_error() because we can't be sure that we can
* unwind our state back to an enclosing fmd_module_dispatch(), so we
* must panic instead. This is likely a module design or coding error.
*/
fmd_panic("fmd module api call made using "
"client handle %p from unknown thread\n", (void *)hdl);
}
/*
* If our TSD refers to the root module and is a non-private
* door server thread, then it was created asynchronously at the
* request of a module but is using now the module API as an
* auxiliary module thread. We reset tp->thr_mod to the module
* handle so it can act as a module thread.
*
* If more than one module uses non-private doors then the
* "client handle is not valid" check below can fail since
* door server threads for such doors can service *any*
* non-private door. We use non-private door for legacy sysevent
* alone.
*/
"client handle %p is not valid\n", (void *)hdl);
}
"module has experienced an unrecoverable error\n");
}
return (mp);
}
/*
* fmd_api_module_lock() is used as a wrapper around fmd_module_lock() and a
* common prologue to each fmd_api.c routine. It verifies that the handle is
* valid and owned by the current server thread, locks the handle, and then
* verifies that the caller is performing an operation on a registered handle.
* If any tests fail, the entire API call is aborted by fmd_api_error().
*/
static fmd_module_t *
{
"client handle %p has not been registered\n", (void *)hdl);
}
return (mp);
}
/*
* Utility function for API entry points that accept fmd_case_t's. We cast cp
* to fmd_case_impl_t and check to make sure the case is owned by the caller.
*/
static fmd_case_impl_t *
{
"case %p is invalid or not owned by caller\n", (void *)cip);
}
return (cip);
}
/*
* Utility function for API entry points that accept fmd_xprt_t's. We cast xp
* to fmd_transport_t and check to make sure the case is owned by the caller.
* Note that we could make this check safer by actually walking mp's transport
* list, but that requires holding the module lock and this routine needs to be
* MT-hot w.r.t. auxiliary module threads. Ultimately any loadable module can
* cause us to crash anyway, so we optimize for scalability over safety here.
*/
static fmd_xprt_impl_t *
{
"xprt %p is invalid or not owned by caller\n", (void *)xp);
}
return (xip);
}
/*
* fmd_hdl_register() is the one function which cannot use fmd_api_error() to
* report errors, because that routine causes the module to abort. Failure to
* register is instead handled by having fmd_hdl_register() return an error to
* the _fmd_init() function and then detecting no registration when it returns.
* So we use this routine for fmd_hdl_register() error paths instead.
*/
static int
{
if (fmd_module_locked(mp))
return (fmd_set_errno(err));
}
static void
fmd_hdl_nop(void)
{
/* empty function for use with unspecified module entry points */
}
int
{
int i;
/*
* First perform some sanity checks on our input. The API version must
* be supported by FMD and the handle can only be registered once by
* the module thread to which we assigned this client handle. The info
* provided for the handle must be valid and have the minimal settings.
*/
if (version > FMD_API_VERSION_5)
if (version < FMD_API_VERSION_1)
/*
* Copy the module's ops vector into a local variable to account for
* changes in the module ABI. Then if any of the optional entry points
* are NULL, set them to nop so we don't have to check before calling.
*/
if (version < FMD_API_VERSION_3)
else if (version < FMD_API_VERSION_4)
else
/*
* Make two passes through the property array to initialize the formals
* to use for processing the module's .conf file. In the first pass,
* we validate the types and count the number of properties. In the
* second pass we copy the strings and fill in the appropriate ops.
*/
sizeof (_fmd_prop_ops) / sizeof (_fmd_prop_ops[0])) {
"property %s uses invalid type %u\n",
}
}
}
/*
* If this module came from an on-disk file, compute the name of the
* corresponding .conf file and parse properties from it if it exists.
*/
(void) fmd_strdirname(buf);
}
/*
* Look up the list of the libdiagcode dictionaries associated with the
* module. If none were specified, use the value from daemon's config.
* We only fail if the module specified an explicit dictionary.
*/
}
}
/*
* Make a copy of the handle information and store it in mod_info. We
* do not need to bother copying fmdi_props since they're already read.
*/
/*
* Store a copy of module version in mp for fmd_scheme_fmd_present()
*/
/*
* Allocate an FMRI representing this module. We'll use this later
* if the module decides to publish any events (e.g. list.suspects).
*/
/*
* Any subscriptions specified in the conf file are now stored in the
* corresponding property. Add all of these to the dispatch queue.
*/
}
/*
* Unlock the module and restore any pre-existing module checkpoint.
* If the checkpoint is missing or corrupt, we just keep going.
*/
return (0);
}
/*
* If an auxiliary thread exists for the specified module at unregistration
* time, send it an asynchronous cancellation to force it to exit and then
* join with it (we expect this to either succeed quickly or return ESRCH).
* Once this is complete we can destroy the associated fmd_thread_t data.
*/
static void
{
/*
* Door service threads are not cancellable (worse - if they're
* waiting in door_return then that is interrupted, but they then spin
* endlessly!). Non-private door service threads are not tracked
* in the module thread idspace so it's only private server threads
* created via fmd_doorthr_create that we'll encounter. In most
* cases the module _fini should have tidied up (e.g., calling
* sysevent_evc_unbind which will cleanup door threads if
* sysevent_evc_xsubscribe was used). One case that does not
* clean up is sysev_fini which explicitly does not unbind the
* channel, so we must skip any remaining door threads here.
*/
if (tp->thr_isdoor) {
return;
}
}
void
{
int i;
/*
* If any transports are still open, they have send threads that are
* using the module handle: shut them down and join with these threads.
*/
/*
* If any auxiliary threads exist, they may be using our module handle,
* and therefore could cause a fault as soon as we start destroying it.
* Module writers should clean up any threads before unregistering: we
* forcibly cancel any remaining auxiliary threads before proceeding.
*/
(void (*)())fmd_module_thrcancel, mp);
/*
* Delete any cases associated with the module (UNSOLVED, SOLVED, or
* CLOSE_WAIT) as if fmdo_close() has finished processing them.
*/
}
}
}
void
{
}
void
{
FMD_PROP_SUBSCRIPTIONS, class) == 0) {
}
}
void
{
FMD_PROP_SUBSCRIPTIONS, class) == 0) {
}
}
void
{
}
void *
{
return (spec);
}
void
{
int i;
/*
* Update the dictionary property in order to preserve the list of
* pathnames and expand any % tokens in the path. Then retrieve the
* new dictionary names from cpa_argv[] and open them one at a time.
*/
"failed to open dictionary %s for module %s",
}
}
}
{
if (v != TOPO_VERSION) {
"fmd version %d != client version %d\n", TOPO_VERSION, v);
}
return (thp);
}
void
{
"topo handle: %p\n", (void *)thp);
}
static void *
{
void *data;
"bytes exceeds module memory limit (%llu)\n",
}
return (data);
}
void *
{
void *data;
return (data);
}
void *
{
return (data);
}
static void
{
}
void
{
}
char *
{
char *p;
if (s != NULL)
else
p = NULL;
if (p != NULL)
(void) strcpy(p, s);
return (p);
}
void
{
if (s != NULL)
}
void
{
}
/*PRINTFLIKE2*/
void
{
}
void
{
}
/*PRINTFLIKE2*/
void
{
}
void
{
char *msg;
char c;
if (!(fmd.d_hdl_debug)) {
return;
}
return;
}
}
}
}
/*PRINTFLIKE2*/
void
{
}
{
ops == &fmd_conf_uint32)
"property %s is not of int32 type\n", name);
} else {
"property %s is not defined\n", name);
}
return (value);
}
{
"property %s is not of int64 type\n", name);
} else {
"property %s is not defined\n", name);
}
return (value);
}
char *
{
const char *s;
if (ops == &fmd_conf_string) {
"property %s is not of string type\n", name);
} else {
"property %s is not defined\n", name);
}
return (value);
}
void
{
fmd_strfree(s);
}
{
if (flags & ~FMD_STAT_ALLOC) {
"invalid flags 0x%x passed to fmd_stat_create\n", flags);
}
}
return (sp);
}
void
{
}
void
{
}
}
{
return (cp);
}
{
"'%s'\n", uuidstr);
}
} else {
}
return (cp); /* May be NULL iff case already exists */
}
void
{
}
}
void
{
}
}
void
{
}
const char *
{
return (uuid);
}
{
} else
}
void
{
}
}
int
{
}
return (rv);
}
void
{
/*
* For a proxy, we notify the diagnosing side, and then
* wait for it to send us back a list.resolved.
*/
else
}
}
int
{
}
return (rv);
}
static int
{
return (rv);
}
int
{
}
int
{
}
void
{
}
void
{
"failed to add events from serd engine '%s'", name);
}
}
}
void
{
char *class;
int err;
}
}
&asru) == 0) {
asru);
&asru);
}
} else {
asru);
&asru);
}
fru);
&fru);
}
}
}
/*
* Try to find the location label for this resource
*/
}
}
/*
* In some cases, serial information for the resource will not be
* available at enumeration but may instead be available by invoking
* a dynamic property method on the FRU. In order to ensure the serial
* number is persisted properly in the ASRU cache, we'll fetch the
* property, if it exists, and add it to the resource and fru fmris.
* If the DE has not listed a fru in the suspect, see if we can
* retrieve the serial from the resource instead.
*/
}
}
}
void
{
}
void *
{
void *data;
return (data);
}
void
{
}
{
return (ep);
}
{
else
return (cp);
}
{
else
return (cp);
}
/*
* Utility function for fmd_buf_* routines. If a case is specified, use the
* case's ci_bufs hash; otherwise use the module's global mod_bufs hash.
*/
static fmd_buf_hash_t *
{
}
void
{
}
}
} else {
"cannot create '%s': buffer already exists\n", name);
}
else
}
void
{
else
}
}
void
{
}
}
void
{
}
}
"write to buf '%s' overflows buf size (%lu > %lu)\n",
}
else
}
{
else
size = 0;
return (size);
}
void
{
"failed to create serd engine '%s': %s\n",
}
}
void
{
}
int
{
return (rv);
}
void
{
"serd engine '%s' does not exist\n", name);
}
}
int
{
int err;
"failed to add record to serd engine '%s'", name);
}
return (err);
}
int
{
int err;
"serd engine '%s' does not exist\n", name);
}
return (err);
}
int
{
int empty;
"serd engine '%s' does not exist\n", name);
}
return (empty);
}
{
"auxiliary thread exceeds module thread limit (%u)\n",
}
"failed to create auxiliary thread");
}
return (tid);
}
void
{
int err;
if (pthread_self() == tid) {
"destroy itself (tid %u)\n", tid);
}
"destroy an invalid thread (tid %u)\n", tid);
}
/*
* Wait for the specified thread to exit and then join with it. Since
* the thread may need to make API calls in order to complete its work
* we must sleep with the module lock unheld, and then reacquire it.
*/
/*
* Since pthread_join() was called without the module lock held, if
* multiple callers attempted to destroy the same auxiliary thread
* simultaneously, one will succeed and the others will get ESRCH.
* Therefore we silently ignore ESRCH but only allow the caller who
* succeessfully joined with the auxiliary thread to destroy it.
*/
"failed to join with auxiliary thread %u\n", tid);
}
if (err == 0) {
}
}
void
{
}
}
void
{
}
}
/*ARGSUSED3*/
int
void *cookie)
{
/*
* We're called either during initial door_xcreate or during
* a depletion callback. In both cases the current thread
* is already setup so we can retrieve the fmd_thread_t.
* If not then we panic. The new thread will be associated with
* the same module as the old.
*
* If dip == NULL we're being called as part of the
* sysevent_bind_subscriber hack - see comments there.
*/
fmd_panic("fmd_doorthr_create from unrecognized thread\n");
"not attemped - at max\n",
return (0);
}
}
if (dip) {
}
}
/*ARGSUSED*/
void
{
}
{
fmd_modtimer_t *t;
if (delta < 0) {
"timer delta %lld is not a valid interval\n", delta);
}
t->mt_id = -1;
fmd_free(t, sizeof (fmd_modtimer_t));
"failed to install timer +%lld", delta);
}
return (id);
}
void
{
fmd_modtimer_t *t;
"id %ld is not a valid timer id\n", id);
}
/*
* If the timer has not fired (t != NULL), remove it from the timer
* queue. If the timer has fired (t == NULL), we could be in one of
* two situations: a) we are processing the timer callback or b)
* the timer event is on the module queue awaiting dispatch. For a),
* fmd_timerq_remove() will wait for the timer callback function
* to complete and queue an event for dispatch. For a) and b),
* we cancel the outstanding timer event from the module's dispatch
* queue.
*/
fmd_free(t, sizeof (fmd_modtimer_t));
}
static nvlist_t *
{
return (nvl);
}
nvlist_t *
{
/*
* We can't enforce that callers only specifiy classes matching
* fault.* since there are already a number of modules that
* use fmd_nvl_create_fault to create a defect event. Since
* fmd_nvl_create_{fault,defect} are equivalent, for now anyway,
* no harm is done. So call fmd_nvl_create_suspect with last
* argument B_FALSE.
*/
}
nvlist_t *
{
}
const nvlist_t *
{
return (auth);
}
const nvlist_t *
{
return (auth);
}
int
{
char *class;
int rv;
return (rv);
}
int
{
int rv;
"invalid nvlist %p\n", (void *)nvl);
}
return (rv);
}
int
{
int rv;
"invalid nvlist %p\n", (void *)nvl);
}
if (rv < 0) {
"fmd_nvl_fmri_present\n");
}
return (rv);
}
int
{
int rv;
"invalid nvlist %p\n", (void *)nvl);
}
return (rv);
}
int
{
int rv;
"invalid nvlist %p\n", (void *)nvl);
}
if (rv < 0) {
"fmd_nvl_fmri_unusable\n");
}
return (rv);
}
int
{
int rv;
"invalid nvlist %p\n", (void *)nvl);
}
return (rv);
}
int
{
int rv;
"invalid nvlist %p\n", (void *)nvl);
}
return (rv);
}
int
{
int rv;
"invalid nvlist %p\n", (void *)nvl);
}
if (rv < 0)
if (rv < 0) {
"fmd_nvl_fmri_service_state\n");
}
return (rv);
}
typedef struct {
const char *class;
int *rvp;
static void
{
char *class;
} else {
}
}
int
{
int rv = 0;
char *name;
int namelen;
"invalid nvlist %p\n", (void *)nvl);
}
"invalid nvlist: %p\n", (void *)nvl);
"invalid nvlist: %p\n", (void *)nvl);
}
if (type == FMD_HAS_FAULT_RESOURCE)
&fhf);
else if (type == FMD_HAS_FAULT_ASRU)
&fhf);
else if (type == FMD_HAS_FAULT_FRU)
&fhf);
return (rv);
}
int
{
int rv;
}
if (rv < 0) {
"fmd_nvl_fmri_contains\n");
}
return (rv);
}
nvlist_t *
{
}
return (xfmri);
}
static int
{
return (0);
}
static void *
{
}
static void *
{
}
static void
{
}
NULL,
};
NULL,
};
nvlist_t *
{
int ret;
else
if (ret != 0)
return (NULL);
else
return (nvl);
}
nvlist_t *
{
int ret;
else
if (ret != 0)
return (NULL);
else
return (nvl);
}
/*ARGSUSED*/
void
{
int err;
}
/*ARGSUSED*/
int
{
return (err);
}
int
{
"NULL parameter specified to fmd_event_local\n");
}
}
/*ARGSUSED*/
{
return (fmd_ena());
}
{
if (flags & ~FMD_XPRT_CMASK) {
"invalid transport flags 0x%x\n", flags);
}
"cannot open write-only transport\n");
}
"transport exceeds module transport limit (%u)\n",
}
return (xp);
}
void
{
/*
* Although this could be supported, it doesn't seem necessary or worth
* the trouble. For now, just detect this and trigger a module abort.
* If it is needed, transports should grow reference counts and a new
* event type will need to be enqueued for the main thread to reap it.
*/
"fmd_xprt_close() cannot be called from fmdo_send()\n");
}
}
void
{
/*
* If this event was allocated using the module-specific nvlist ops, we
* need to create a copy using the standard fmd nvlist ops. Otherwise,
* the event may persist after the module has been unloaded and we'll
* die when attempting to free the nvlist.
*/
}
/*
* fmd_xprt_recv() must block during startup waiting for fmd to globally
* clear FMD_XPRT_DSUSPENDED. As such, we can't allow it to be called
* from a module's _fmd_init() routine, because that would block
* fmd from completing initial module loading, resulting in a deadlock.
*/
"fmd_xprt_post() cannot be called from _fmd_init()\n");
}
}
void
{
/*
* fmd_xprt_recv() must block during startup waiting for fmd to globally
* clear FMD_XPRT_DSUSPENDED. As such, we can't allow it to be called
* from a module's _fmd_init() routine, because that would block
* fmd from completing initial module loading, resulting in a deadlock.
*/
"fmd_xprt_log() cannot be called from _fmd_init()\n");
}
}
void
{
}
void
{
}
int
{
}
/*
* Translate all FMRIs in the specified name-value pair list for the specified
* FMRI authority, and return a new name-value pair list for the translation.
* This function is the recursive engine used by fmd_xprt_translate(), below.
*/
static nvlist_t *
{
uint_t i, j, n;
char *name;
nvlist_t **a, **b;
nvlist_t *l, *r;
char *s;
int err;
/*
* Count up the number of name-value pairs in 'nvl' and compute the
* maximum length of a name used in this list for use below.
*/
}
/*
* Store a snapshot of the name-value pairs in 'nvl' into nvps[] so
* that we can iterate over the original pairs in the loop below while
* performing arbitrary insert and delete operations on 'nvl' itself.
*/
/*
* Now iterate over the snapshot of the name-value pairs. If we find a
* value that is of type NVLIST or NVLIST_ARRAY, we translate that
* object by either calling ourself recursively on it, or calling into
* fmd_fmri_translate() if the object is an FMRI. We then rip out the
* original name-value pair and replace it with the translated one.
*/
for (i = 0; i < nvpslen; i++) {
switch (type) {
case DATA_TYPE_NVLIST_ARRAY:
if (nvpair_value_nvlist_array(nvp, &a, &n) != 0 ||
a == NULL || n == 0)
continue; /* array is zero-sized; skip it */
/*
* If the first array nvlist element looks like an FMRI
* then assume the other elements are FMRIs as well.
* If any b[j]'s can't be translated, then EINVAL will
* be returned from nvlist_add_nvlist_array() below.
*/
if (nvlist_lookup_string(*a, FM_FMRI_SCHEME, &s) == 0) {
for (j = 0; j < n; j++)
b[j] = fmd_fmri_translate(a[j], auth);
} else {
for (j = 0; j < n; j++)
b[j] = fmd_xprt_xtranslate(a[j], auth);
}
for (j = 0; j < n; j++)
nvlist_free(b[j]);
if (err != 0) {
return (NULL);
}
break;
case DATA_TYPE_NVLIST:
if (nvpair_value_nvlist(nvp, &l) == 0 &&
nvlist_lookup_string(l, FM_FMRI_SCHEME, &s) == 0)
r = fmd_fmri_translate(l, auth);
else
r = fmd_xprt_xtranslate(l, auth);
if (r == NULL) {
return (NULL);
}
nvlist_free(r);
break;
}
}
return (nvl);
}
nvlist_t *
{
"no authority defined for transport %p\n", (void *)xp);
}
}
/*ARGSUSED*/
void
{
char *class;
return;
return;
}
}
FM_FMRI_AUTHORITY) == 0) {
(void) nvlist_add_string(nvl3,
break;
}
}
break;
}
}
}
void
{
}
void *
{
}