Exemplo n.º 1
0
/*
 * Look up the diagcode for this case and cache it in ci_code.  If no suspects
 * were defined for this case or if the lookup fails, the event dictionary or
 * module code is broken, and we set the event code to a precomputed default.
 */
static const char *
fmd_case_mkcode(fmd_case_t *cp)
{
	fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;
	fmd_case_susp_t *cis;

	char **keys, **keyp;
	const char *s;

	ASSERT(MUTEX_HELD(&cip->ci_lock));
	ASSERT(cip->ci_state >= FMD_CASE_SOLVED);

	fmd_free(cip->ci_code, cip->ci_codelen);
	cip->ci_codelen = cip->ci_mod->mod_codelen;
	cip->ci_code = fmd_zalloc(cip->ci_codelen, FMD_SLEEP);
	keys = keyp = alloca(sizeof (char *) * (cip->ci_nsuspects + 1));

	for (cis = cip->ci_suspects; cis != NULL; cis = cis->cis_next) {
		if (nvlist_lookup_string(cis->cis_nvl, FM_CLASS, keyp) == 0)
			keyp++;
	}

	*keyp = NULL; /* mark end of keys[] array for libdiagcode */

	if (cip->ci_nsuspects == 0 || fmd_module_dc_key2code(
	    cip->ci_mod, keys, cip->ci_code, cip->ci_codelen) != 0) {
		(void) fmd_conf_getprop(fmd.d_conf, "nodiagcode", &s);
		fmd_free(cip->ci_code, cip->ci_codelen);
		cip->ci_codelen = strlen(s) + 1;
		cip->ci_code = fmd_zalloc(cip->ci_codelen, FMD_SLEEP);
		(void) strcpy(cip->ci_code, s);
	}

	return (cip->ci_code);
}
Exemplo n.º 2
0
/*
 * Take a snapshot of the case hash by placing an additional hold on each
 * member in an auxiliary array, and then call 'func' for each case.
 */
void
fmd_case_hash_apply(fmd_case_hash_t *chp,
    void (*func)(fmd_case_t *, void *), void *arg)
{
	fmd_case_impl_t *cp, **cps, **cpp;
	uint_t cpc, i;

	(void) pthread_rwlock_rdlock(&chp->ch_lock);

	cps = cpp = fmd_alloc(chp->ch_count * sizeof (fmd_case_t *), FMD_SLEEP);
	cpc = chp->ch_count;

	for (i = 0; i < chp->ch_hashlen; i++) {
		for (cp = chp->ch_hash[i]; cp != NULL; cp = cp->ci_next) {
			fmd_case_hold((fmd_case_t *)cp);
			*cpp++ = cp;
		}
	}

	ASSERT(cpp == cps + cpc);
	(void) pthread_rwlock_unlock(&chp->ch_lock);

	for (i = 0; i < cpc; i++) {
		func((fmd_case_t *)cps[i], arg);
		fmd_case_rele((fmd_case_t *)cps[i]);
	}

	fmd_free(cps, cpc * sizeof (fmd_case_t *));
}
Exemplo n.º 3
0
static void
fmd_ckpt_destroy(fmd_ckpt_t *ckp)
{
	if (ckp->ckp_buf != NULL)
		fmd_free(ckp->ckp_buf, ckp->ckp_size);
	if (ckp->ckp_fd >= 0)
		(void) close(ckp->ckp_fd);
}
Exemplo n.º 4
0
static void
fmd_ckpt_save_module(fmd_ckpt_t *ckp, fmd_module_t *mp)
{
	fcf_secidx_t bufsec = FCF_SECIDX_NONE;
	fcf_module_t fcfm;
	fmd_case_t *cp;
	uint_t n;

	for (cp = fmd_list_next(&mp->mod_cases); cp; cp = fmd_list_next(cp))
		fmd_ckpt_save_case(ckp, cp);

	if ((n = fmd_serd_hash_count(&mp->mod_serds)) != 0) {
		size_t size = sizeof (fcf_serd_t) * n;
		fcf_serd_t *serds = ckp->ckp_arg = fmd_alloc(size, FMD_SLEEP);

		fmd_serd_hash_apply(&mp->mod_serds,
		    (fmd_serd_eng_f *)fmd_ckpt_save_serd, ckp);

		(void) fmd_ckpt_section(ckp, serds, FCF_SECT_SERD, size);
		fmd_free(serds, size);
	}

	if ((n = fmd_buf_hash_count(&mp->mod_bufs)) != 0) {
		size_t size = sizeof (fcf_buf_t) * n;
		fcf_buf_t *bufs = ckp->ckp_arg = fmd_alloc(size, FMD_SLEEP);

		fmd_buf_hash_apply(&mp->mod_bufs,
		    (fmd_buf_f *)fmd_ckpt_save_buf, ckp);

		bufsec = fmd_ckpt_section(ckp, bufs, FCF_SECT_BUFS, size);
		fmd_free(bufs, size);
	}

	fcfm.fcfm_name = fmd_ckpt_string(ckp, mp->mod_name);
	fcfm.fcfm_path = fmd_ckpt_string(ckp, mp->mod_path);
	fcfm.fcfm_desc = fmd_ckpt_string(ckp, mp->mod_info->fmdi_desc);
	fcfm.fcfm_vers = fmd_ckpt_string(ckp, mp->mod_info->fmdi_vers);
	fcfm.fcfm_bufs = bufsec;

	(void) fmd_ckpt_section(ckp, &fcfm,
	    FCF_SECT_MODULE, sizeof (fcf_module_t));
}
Exemplo n.º 5
0
/*PRINTFLIKE2*/
static int
fmd_ckpt_inval(fmd_ckpt_t *ckp, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	fmd_verror(EFMD_CKPT_INVAL, format, ap);
	va_end(ap);

	fmd_free(ckp->ckp_buf, ckp->ckp_size);
	return (fmd_set_errno(EFMD_CKPT_INVAL));
}
Exemplo n.º 6
0
void
fmd_thread_destroy(fmd_thread_t *tp, int flag)
{
	if (flag == FMD_THREAD_JOIN && tp->thr_tid != pthread_self() &&
	    pthread_join(tp->thr_tid, NULL) != 0) {
		fmd_error(EFMD_MOD_JOIN, "failed to join thread for module "
		    "%s (tid %u)\n", tp->thr_mod->mod_name, tp->thr_tid);
	}

	(void) pthread_mutex_lock(&fmd.d_thr_lock);
	fmd_list_delete(&fmd.d_thr_list, tp);
	(void) pthread_mutex_unlock(&fmd.d_thr_lock);

	fmd_trace_destroy(tp->thr_trdata);
	fmd_free(tp, sizeof (fmd_thread_t));
}
Exemplo n.º 7
0
static fmd_thread_t *
fmd_thread_create_cmn(fmd_module_t *mp, fmd_thread_f *func, void *arg,
    int isdoor)
{
	fmd_thread_t *tp = fmd_alloc(sizeof (fmd_thread_t), FMD_SLEEP);
	sigset_t oset, nset;
	int err;

	tp->thr_mod = mp;
	tp->thr_func = func;
	tp->thr_arg = arg;
	tp->thr_trdata = fmd_trace_create();
	tp->thr_trfunc = (fmd_tracebuf_f *)fmd.d_thr_trace;
	tp->thr_errdepth = 0;
	tp->thr_isdoor = isdoor;

	(void) sigfillset(&nset);
	(void) sigdelset(&nset, SIGABRT); /* always unblocked for fmd_panic() */
	if (!isdoor)
		(void) sigdelset(&nset, fmd.d_thr_sig); /* fmd_thr_signal() */

	(void) pthread_sigmask(SIG_SETMASK, &nset, &oset);
	err = pthread_create(&tp->thr_tid, NULL, fmd_thread_start, tp);
	(void) pthread_sigmask(SIG_SETMASK, &oset, NULL);

	if (err != 0) {
		fmd_free(tp, sizeof (fmd_thread_t));
		return (NULL);
	}

	(void) pthread_mutex_lock(&fmd.d_thr_lock);
	fmd_list_append(&fmd.d_thr_list, tp);
	(void) pthread_mutex_unlock(&fmd.d_thr_lock);

	return (tp);
}
Exemplo n.º 8
0
void
fmd_strfree(char *s)
{
	if (s != NULL)
		fmd_free(s, strlen(s) + 1);
}
Exemplo n.º 9
0
/*
 * Destroy the case hash.  Unlike most of our hash tables, no active references
 * are kept by the case hash itself; all references come from other subsystems.
 * The hash must be destroyed after all modules are unloaded; if anything was
 * present in the hash it would be by definition a reference count leak.
 */
void
fmd_case_hash_destroy(fmd_case_hash_t *chp)
{
	fmd_free(chp->ch_hash, sizeof (void *) * chp->ch_hashlen);
	fmd_free(chp, sizeof (fmd_case_hash_t));
}
Exemplo n.º 10
0
void
fmd_destroy(fmd_t *dp)
{
	fmd_module_t *mp;
	fmd_case_t *cp;
	int core;

	(void) fmd_conf_getprop(fmd.d_conf, "core", &core);

	fmd_rpc_fini();
	fmd_dr_fini();

	if (dp->d_xprt_ids != NULL)
		fmd_xprt_suspend_all();

	/*
	 * Unload the self-diagnosis module first.  This ensures that it does
	 * not get confused as we start unloading other modules, etc.  We must
	 * hold the dispq lock as a writer while doing so since it uses d_self.
	 */
	if (dp->d_self != NULL) {
		fmd_module_t *self;

		(void) pthread_rwlock_wrlock(&dp->d_disp->dq_lock);
		self = dp->d_self;
		dp->d_self = NULL;
		(void) pthread_rwlock_unlock(&dp->d_disp->dq_lock);

		fmd_module_unload(self);
		fmd_module_rele(self);
	}

	/*
	 * Unload modules in reverse order *except* for the root module, which
	 * is first in the list.  This allows it to keep its thread and trace.
	 */
	for (mp = fmd_list_prev(&dp->d_mod_list); mp != dp->d_rmod; ) {
		fmd_module_unload(mp);
		mp = fmd_list_prev(mp);
	}

	if (dp->d_mod_hash != NULL) {
		fmd_modhash_destroy(dp->d_mod_hash);
		dp->d_mod_hash = NULL;
	}

	/*
	 * Close both log files now that modules are no longer active.  We must
	 * set these pointers to NULL in case any subsequent errors occur.
	 */
	if (dp->d_errlog != NULL) {
		fmd_log_rele(dp->d_errlog);
		dp->d_errlog = NULL;
	}

	if (dp->d_fltlog != NULL) {
		fmd_log_rele(dp->d_fltlog);
		dp->d_fltlog = NULL;
	}

	/*
	 * Now destroy the resource cache: each ASRU contains a case reference,
	 * which may in turn contain a pointer to a referenced owning module.
	 */
	if (dp->d_asrus != NULL) {
		fmd_asru_hash_destroy(dp->d_asrus);
		dp->d_asrus = NULL;
	}

	/*
	 * Now that all data structures that refer to modules are torn down,
	 * no modules should be remaining on the module list except for d_rmod.
	 * If we trip one of these assertions, we're missing a rele somewhere.
	 */
	ASSERT(fmd_list_prev(&dp->d_mod_list) == dp->d_rmod);
	ASSERT(fmd_list_next(&dp->d_mod_list) == dp->d_rmod);

	/*
	 * Now destroy the root module.  We clear its thread key first so any
	 * calls to fmd_trace() inside of the module code will be ignored.
	 */
	(void) pthread_setspecific(dp->d_key, NULL);
	fmd_module_lock(dp->d_rmod);

	while ((cp = fmd_list_next(&dp->d_rmod->mod_cases)) != NULL)
		fmd_case_discard(cp);

	fmd_module_unlock(dp->d_rmod);
	fmd_free(dp->d_rmod->mod_stats, sizeof (fmd_modstat_t));
	dp->d_rmod->mod_stats = NULL;

	(void) pthread_mutex_lock(&dp->d_rmod->mod_lock);
	dp->d_rmod->mod_flags |= FMD_MOD_FINI;
	(void) pthread_mutex_unlock(&dp->d_rmod->mod_lock);

	fmd_module_rele(dp->d_rmod);
	ASSERT(fmd_list_next(&dp->d_mod_list) == NULL);

	/*
	 * Now destroy the remaining global data structures.  If 'core' was
	 * set to true, force a core dump so we can check for memory leaks.
	 */
	if (dp->d_cases != NULL)
		fmd_case_hash_destroy(dp->d_cases);
	if (dp->d_disp != NULL)
		fmd_dispq_destroy(dp->d_disp);
	if (dp->d_timers != NULL)
		fmd_timerq_destroy(dp->d_timers);
	if (dp->d_schemes != NULL)
		fmd_scheme_hash_destroy(dp->d_schemes);
	if (dp->d_xprt_ids != NULL)
		fmd_idspace_destroy(dp->d_xprt_ids);

	if (dp->d_errstats != NULL) {
		fmd_free(dp->d_errstats,
		    sizeof (fmd_stat_t) * (EFMD_END - EFMD_UNKNOWN));
	}

	if (dp->d_conf != NULL)
		fmd_conf_close(dp->d_conf);

	if (dp->d_topo != NULL)
		topo_close(dp->d_topo);

	nvlist_free(dp->d_auth);
	(void) nv_alloc_fini(&dp->d_nva);
	dp->d_clockops->fto_fini(dp->d_clockptr);

	(void) pthread_key_delete(dp->d_key);
	bzero(dp, sizeof (fmd_t));

	if (core)
		fmd_panic("forcing core dump at user request\n");
}
Exemplo n.º 11
0
void
fmd_trace_destroy(fmd_tracebuf_t *tbp)
{
	fmd_free(tbp->tb_buf, tbp->tb_size * tbp->tb_recs);
	fmd_free(tbp, sizeof (fmd_tracebuf_t));
}
Exemplo n.º 12
0
static void
fmd_ckpt_save_case(fmd_ckpt_t *ckp, fmd_case_t *cp)
{
	fmd_case_impl_t *cip = (fmd_case_impl_t *)cp;

	fmd_case_item_t *cit;
	fmd_case_susp_t *cis;
	fcf_case_t fcfc;
	uint_t n;

	fcf_secidx_t bufsec = FCF_SECIDX_NONE;
	fcf_secidx_t evsec = FCF_SECIDX_NONE;
	fcf_secidx_t nvsec = FCF_SECIDX_NONE;
	fcf_secidx_t prsec = FCF_SECIDX_NONE;

	if (cip->ci_xprt != NULL)
		return; /* do not checkpoint cases from remote transports */

	if ((n = fmd_buf_hash_count(&cip->ci_bufs)) != 0) {
		size_t size = sizeof (fcf_buf_t) * n;
		fcf_buf_t *bufs = ckp->ckp_arg = fmd_alloc(size, FMD_SLEEP);

		fmd_buf_hash_apply(&cip->ci_bufs,
		    (fmd_buf_f *)fmd_ckpt_save_buf, ckp);

		bufsec = fmd_ckpt_section(ckp, bufs, FCF_SECT_BUFS, size);
		fmd_free(bufs, size);
	}

	if (cip->ci_principal != NULL) {
		prsec = fmd_ckpt_section(ckp, NULL, FCF_SECT_EVENTS,
		    sizeof (fcf_event_t));

		fmd_ckpt_save_event(ckp, cip->ci_principal);
	}

	if (cip->ci_nitems != 0) {
		evsec = fmd_ckpt_section(ckp, NULL, FCF_SECT_EVENTS,
		    sizeof (fcf_event_t) * cip->ci_nitems);

		for (cit = cip->ci_items; cit != NULL; cit = cit->cit_next)
			fmd_ckpt_save_event(ckp, cit->cit_event);
	}

	if (cip->ci_nsuspects != 0) {
		nvsec = fmd_ckpt_section(ckp, NULL,
		    FCF_SECT_NVLISTS, cip->ci_nvsz);

		for (cis = cip->ci_suspects; cis != NULL; cis = cis->cis_next)
			fmd_ckpt_save_nvlist(ckp, cis->cis_nvl);
	}

	fcfc.fcfc_uuid = fmd_ckpt_string(ckp, cip->ci_uuid);
	fcfc.fcfc_bufs = bufsec;
	fcfc.fcfc_principal = prsec;
	fcfc.fcfc_events = evsec;
	fcfc.fcfc_suspects = nvsec;

	switch (cip->ci_state) {
	case FMD_CASE_UNSOLVED:
		fcfc.fcfc_state = FCF_CASE_UNSOLVED;
		break;
	case FMD_CASE_SOLVED:
		fcfc.fcfc_state = FCF_CASE_SOLVED;
		break;
	case FMD_CASE_CLOSE_WAIT:
		fcfc.fcfc_state = FCF_CASE_CLOSE_WAIT;
		break;
	default:
		fmd_panic("case %p (%s) has invalid state %u",
		    (void *)cp, cip->ci_uuid, cip->ci_state);
	}

	(void) fmd_ckpt_section(ckp, &fcfc, FCF_SECT_CASE, sizeof (fcf_case_t));
}
Exemplo n.º 13
0
static int
fmd_ckpt_open(fmd_ckpt_t *ckp, fmd_module_t *mp)
{
	struct stat64 st;
	uint64_t seclen;
	uint_t i;
	int err;

	bzero(ckp, sizeof (fmd_ckpt_t));
	ckp->ckp_mp = mp;

	(void) snprintf(ckp->ckp_src, PATH_MAX, "%s/%s",
	    mp->mod_ckpt, mp->mod_name);

	if ((ckp->ckp_fd = open(ckp->ckp_src, O_RDONLY)) == -1)
		return (-1); /* failed to open checkpoint file */

	if (fstat64(ckp->ckp_fd, &st) == -1) {
		err = errno;
		(void) close(ckp->ckp_fd);
		return (fmd_set_errno(err));
	}

	ckp->ckp_buf = fmd_alloc(st.st_size, FMD_SLEEP);
	ckp->ckp_hdr = (void *)ckp->ckp_buf;
	ckp->ckp_size = read(ckp->ckp_fd, ckp->ckp_buf, st.st_size);

	if (ckp->ckp_size != st.st_size || ckp->ckp_size < sizeof (fcf_hdr_t) ||
	    ckp->ckp_size != ckp->ckp_hdr->fcfh_filesz) {
		err = ckp->ckp_size == (size_t)-1L ? errno : EFMD_CKPT_SHORT;
		fmd_free(ckp->ckp_buf, st.st_size);
		(void) close(ckp->ckp_fd);
		return (fmd_set_errno(err));
	}

	(void) close(ckp->ckp_fd);
	ckp->ckp_fd = -1;

	/*
	 * Once we've read in a consistent copy of the FCF file and we're sure
	 * the header can be accessed, go through it and make sure everything
	 * is valid.  We also check that unused bits are zero so we can expand
	 * to use them safely in the future and support old files if needed.
	 */
	if (bcmp(&ckp->ckp_hdr->fcfh_ident[FCF_ID_MAG0],
	    FCF_MAG_STRING, FCF_MAG_STRLEN) != 0)
		return (fmd_ckpt_inval(ckp, "bad checkpoint magic string\n"));

	if (ckp->ckp_hdr->fcfh_ident[FCF_ID_MODEL] != FCF_MODEL_NATIVE)
		return (fmd_ckpt_inval(ckp, "bad checkpoint data model\n"));

	if (ckp->ckp_hdr->fcfh_ident[FCF_ID_ENCODING] != FCF_ENCODE_NATIVE)
		return (fmd_ckpt_inval(ckp, "bad checkpoint data encoding\n"));

	if (ckp->ckp_hdr->fcfh_ident[FCF_ID_VERSION] != FCF_VERSION_1) {
		return (fmd_ckpt_inval(ckp, "bad checkpoint version %u\n",
		    ckp->ckp_hdr->fcfh_ident[FCF_ID_VERSION]));
	}

	for (i = FCF_ID_PAD; i < FCF_ID_SIZE; i++) {
		if (ckp->ckp_hdr->fcfh_ident[i] != 0) {
			return (fmd_ckpt_inval(ckp,
			    "bad checkpoint padding at id[%d]", i));
		}
	}

	if (ckp->ckp_hdr->fcfh_flags & ~FCF_FL_VALID)
		return (fmd_ckpt_inval(ckp, "bad checkpoint flags\n"));

	if (ckp->ckp_hdr->fcfh_pad != 0)
		return (fmd_ckpt_inval(ckp, "reserved field in use\n"));

	if (ckp->ckp_hdr->fcfh_hdrsize < sizeof (fcf_hdr_t) ||
	    ckp->ckp_hdr->fcfh_secsize < sizeof (fcf_sec_t)) {
		return (fmd_ckpt_inval(ckp,
		    "bad header and/or section size\n"));
	}

	seclen = (uint64_t)ckp->ckp_hdr->fcfh_secnum *
	    (uint64_t)ckp->ckp_hdr->fcfh_secsize;

	if (ckp->ckp_hdr->fcfh_secoff > ckp->ckp_size ||
	    seclen > ckp->ckp_size ||
	    ckp->ckp_hdr->fcfh_secoff + seclen > ckp->ckp_size ||
	    ckp->ckp_hdr->fcfh_secoff + seclen < ckp->ckp_hdr->fcfh_secoff)
		return (fmd_ckpt_inval(ckp, "truncated section headers\n"));

	if (!IS_P2ALIGNED(ckp->ckp_hdr->fcfh_secoff, sizeof (uint64_t)) ||
	    !IS_P2ALIGNED(ckp->ckp_hdr->fcfh_secsize, sizeof (uint64_t)))
		return (fmd_ckpt_inval(ckp, "misaligned section headers\n"));

	/*
	 * Once the header is validated, iterate over the section headers
	 * ensuring that each one is valid w.r.t. offset, alignment, and size.
	 * We also pick up the string table pointer during this pass.
	 */
	ckp->ckp_secp = (void *)(ckp->ckp_buf + ckp->ckp_hdr->fcfh_secoff);
	ckp->ckp_secs = ckp->ckp_hdr->fcfh_secnum;

	for (i = 0; i < ckp->ckp_secs; i++) {
		fcf_sec_t *sp = (void *)(ckp->ckp_buf +
		    ckp->ckp_hdr->fcfh_secoff + ckp->ckp_hdr->fcfh_secsize * i);

		const fmd_ckpt_desc_t *dp = &_fmd_ckpt_sections[sp->fcfs_type];

		if (sp->fcfs_flags != 0) {
			return (fmd_ckpt_inval(ckp, "section %u has invalid "
			    "section flags (0x%x)\n", i, sp->fcfs_flags));
		}

		if (sp->fcfs_align & (sp->fcfs_align - 1)) {
			return (fmd_ckpt_inval(ckp, "section %u has invalid "
			    "alignment (%u)\n", i, sp->fcfs_align));
		}

		if (sp->fcfs_offset & (sp->fcfs_align - 1)) {
			return (fmd_ckpt_inval(ckp, "section %u is not properly"
			    " aligned (offset %llu)\n", i, sp->fcfs_offset));
		}

		if (sp->fcfs_entsize != 0 &&
		    (sp->fcfs_entsize & (sp->fcfs_align - 1)) != 0) {
			return (fmd_ckpt_inval(ckp, "section %u has misaligned "
			    "entsize %u\n", i, sp->fcfs_entsize));
		}

		if (sp->fcfs_offset > ckp->ckp_size ||
		    sp->fcfs_size > ckp->ckp_size ||
		    sp->fcfs_offset + sp->fcfs_size > ckp->ckp_size ||
		    sp->fcfs_offset + sp->fcfs_size < sp->fcfs_offset) {
			return (fmd_ckpt_inval(ckp, "section %u has corrupt "
			    "size or offset\n", i));
		}

		if (sp->fcfs_type >= sizeof (_fmd_ckpt_sections) /
		    sizeof (_fmd_ckpt_sections[0])) {
			return (fmd_ckpt_inval(ckp, "section %u has unknown "
			    "section type %u\n", i, sp->fcfs_type));
		}

		if (sp->fcfs_align != dp->secd_align) {
			return (fmd_ckpt_inval(ckp, "section %u has align %u "
			    "(not %u)\n", i, sp->fcfs_align, dp->secd_align));
		}

		if (sp->fcfs_size < dp->secd_size ||
		    sp->fcfs_entsize < dp->secd_entsize) {
			return (fmd_ckpt_inval(ckp, "section %u has short "
			    "size or entsize\n", i));
		}

		switch (sp->fcfs_type) {
		case FCF_SECT_STRTAB:
			if (ckp->ckp_strs != NULL) {
				return (fmd_ckpt_inval(ckp, "multiple string "
				    "tables are present in checkpoint file\n"));
			}

			ckp->ckp_strs = (char *)ckp->ckp_buf + sp->fcfs_offset;
			ckp->ckp_strn = sp->fcfs_size;

			if (ckp->ckp_strs[ckp->ckp_strn - 1] != '\0') {
				return (fmd_ckpt_inval(ckp, "string table %u "
				    "is missing terminating nul byte\n", i));
			}
			break;

		case FCF_SECT_MODULE:
			if (ckp->ckp_modp != NULL) {
				return (fmd_ckpt_inval(ckp, "multiple module "
				    "sects are present in checkpoint file\n"));
			}
			ckp->ckp_modp = sp;
			break;
		}
	}

	/*
	 * Ensure that the first section is an empty one of type FCF_SECT_NONE.
	 * This is done to ensure that links can use index 0 as a null section.
	 */
	if (ckp->ckp_secs == 0 || ckp->ckp_secp->fcfs_type != FCF_SECT_NONE ||
	    ckp->ckp_secp->fcfs_entsize != 0 || ckp->ckp_secp->fcfs_size != 0) {
		return (fmd_ckpt_inval(ckp, "section 0 is not of the "
		    "appropriate size and/or attributes (SECT_NONE)\n"));
	}

	if (ckp->ckp_modp == NULL) {
		return (fmd_ckpt_inval(ckp,
		    "no module section found in file\n"));
	}

	return (0);
}