xref: /illumos-gate/usr/src/lib/fm/libfmd_log/common/fmd_log.c (revision 3d393ee6c37fa10ac512ed6d36109ad616dc7c1a)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/fm/protocol.h>
30 #include <sys/types.h>
31 #include <sys/mkdev.h>
32 
33 #include <alloca.h>
34 #include <unistd.h>
35 #include <limits.h>
36 #include <strings.h>
37 #include <stdlib.h>
38 #include <fcntl.h>
39 #include <errno.h>
40 #include <libgen.h>
41 #include <dirent.h>
42 #include <fmd_log_impl.h>
43 #include <fmd_log.h>
44 
45 #define	CAT_FMA_RGROUP	(EXT_GROUP | EXC_DEFAULT | EXD_GROUP_RFMA)
46 #define	CAT_FMA_GROUP	(EXT_GROUP | EXC_DEFAULT | EXD_GROUP_FMA)
47 
48 #define	CAT_FMA_LABEL	(EXT_STRING | EXC_DEFAULT | EXD_FMA_LABEL)
49 #define	CAT_FMA_VERSION	(EXT_STRING | EXC_DEFAULT | EXD_FMA_VERSION)
50 #define	CAT_FMA_OSREL	(EXT_STRING | EXC_DEFAULT | EXD_FMA_OSREL)
51 #define	CAT_FMA_OSVER	(EXT_STRING | EXC_DEFAULT | EXD_FMA_OSVER)
52 #define	CAT_FMA_PLAT	(EXT_STRING | EXC_DEFAULT | EXD_FMA_PLAT)
53 #define	CAT_FMA_UUID	(EXT_STRING | EXC_DEFAULT | EXD_FMA_UUID)
54 #define	CAT_FMA_TODSEC	(EXT_UINT64 | EXC_DEFAULT | EXD_FMA_TODSEC)
55 #define	CAT_FMA_TODNSEC	(EXT_UINT64 | EXC_DEFAULT | EXD_FMA_TODNSEC)
56 #define	CAT_FMA_NVLIST	(EXT_RAW | EXC_DEFAULT | EXD_FMA_NVLIST)
57 #define	CAT_FMA_MAJOR	(EXT_UINT32 | EXC_DEFAULT | EXD_FMA_MAJOR)
58 #define	CAT_FMA_MINOR	(EXT_UINT32 | EXC_DEFAULT | EXD_FMA_MINOR)
59 #define	CAT_FMA_INODE	(EXT_UINT64 | EXC_DEFAULT | EXD_FMA_INODE)
60 #define	CAT_FMA_OFFSET	(EXT_UINT64 | EXC_DEFAULT | EXD_FMA_OFFSET)
61 
62 static int fmd_log_load_record(fmd_log_t *, uint_t, fmd_log_record_t *);
63 static void fmd_log_free_record(fmd_log_record_t *);
64 static int fmd_log_load_xrefs(fmd_log_t *, uint_t, fmd_log_record_t *);
65 
66 static const char FMD_CREATOR[] = "fmd";
67 
68 /*
69  * fmd_log_set_errno is used as a utility function throughout the library.  It
70  * sets both lp->log_errno and errno to the specified value.  If the current
71  * error is EFDL_EXACCT, we store it internally as that value plus ea_error().
72  * If no ea_error() is present, we assume EFDL_BADTAG (catalog tag mismatch).
73  */
74 static int
75 fmd_log_set_errno(fmd_log_t *lp, int err)
76 {
77 	if (err == EFDL_EXACCT && ea_error() != EXR_OK)
78 		lp->log_errno = EFDL_EXACCT + ea_error();
79 	else if (err == EFDL_EXACCT)
80 		lp->log_errno = EFDL_BADTAG;
81 	else
82 		lp->log_errno = err;
83 
84 	errno = lp->log_errno;
85 	return (-1);
86 }
87 
88 /*PRINTFLIKE2*/
89 static void
90 fmd_log_dprintf(fmd_log_t *lp, const char *format, ...)
91 {
92 	va_list ap;
93 
94 	if (lp->log_flags & FMD_LF_DEBUG) {
95 		(void) fputs("fmd_log DEBUG: ", stderr);
96 		va_start(ap, format);
97 		(void) vfprintf(stderr, format, ap);
98 		va_end(ap);
99 	}
100 }
101 
102 /*
103  * fmd_log_load_record() is used to load the exacct object at the current file
104  * location into the specified fmd_log_record structure.  Once the caller has
105  * made use of this information, it can clean up using fmd_log_free_record().
106  */
107 static int
108 fmd_log_load_record(fmd_log_t *lp, uint_t iflags, fmd_log_record_t *rp)
109 {
110 	ea_object_t *grp, *obj;
111 	off64_t off;
112 	int err;
113 
114 	if (iflags & FMD_LOG_XITER_OFFS) {
115 		ea_clear(&lp->log_ea);
116 		off = lseek64(lp->log_fd, 0, SEEK_CUR);
117 	}
118 
119 	if ((grp = ea_get_object_tree(&lp->log_ea, 1)) == NULL)
120 		return (fmd_log_set_errno(lp, EFDL_EXACCT));
121 
122 	if (grp->eo_catalog != CAT_FMA_RGROUP &&
123 	    grp->eo_catalog != CAT_FMA_GROUP) {
124 		fmd_log_dprintf(lp, "bad catalog tag 0x%x\n", grp->eo_catalog);
125 		ea_free_object(grp, EUP_ALLOC);
126 		return (fmd_log_set_errno(lp, EFDL_EXACCT));
127 	}
128 
129 	bzero(rp, sizeof (fmd_log_record_t));
130 	rp->rec_grp = grp;
131 
132 	if (iflags & FMD_LOG_XITER_OFFS)
133 		rp->rec_off = off;
134 
135 	for (obj = grp->eo_group.eg_objs; obj != NULL; obj = obj->eo_next) {
136 		switch (obj->eo_catalog) {
137 		case CAT_FMA_NVLIST:
138 			if ((err = nvlist_unpack(obj->eo_item.ei_raw,
139 			    obj->eo_item.ei_size, &rp->rec_nvl, 0)) != 0) {
140 				fmd_log_free_record(rp);
141 				return (fmd_log_set_errno(lp, err));
142 			}
143 			break;
144 
145 		case CAT_FMA_TODSEC:
146 			rp->rec_sec = obj->eo_item.ei_uint64;
147 			break;
148 
149 		case CAT_FMA_TODNSEC:
150 			rp->rec_nsec = obj->eo_item.ei_uint64;
151 			break;
152 
153 		case CAT_FMA_GROUP:
154 			rp->rec_nrefs += obj->eo_group.eg_nobjs;
155 			break;
156 		}
157 	}
158 
159 	if (rp->rec_nvl == NULL || nvlist_lookup_string(rp->rec_nvl,
160 	    FM_CLASS, (char **)&rp->rec_class) != 0) {
161 		fmd_log_free_record(rp);
162 		return (fmd_log_set_errno(lp, EFDL_NOCLASS));
163 	}
164 
165 	if (rp->rec_nrefs != 0 && fmd_log_load_xrefs(lp, iflags, rp) != 0) {
166 		err = errno; /* errno is set for us */
167 		fmd_log_free_record(rp);
168 		return (fmd_log_set_errno(lp, err));
169 	}
170 
171 	return (0);
172 }
173 
174 /*
175  * fmd_log_free_record frees memory associated with the specified record.  If
176  * cross-references are contained in this record, we proceed recursively.
177  */
178 static void
179 fmd_log_free_record(fmd_log_record_t *rp)
180 {
181 	uint_t i;
182 
183 	if (rp->rec_xrefs != NULL) {
184 		for (i = 0; i < rp->rec_nrefs; i++)
185 			fmd_log_free_record(&rp->rec_xrefs[i]);
186 		free(rp->rec_xrefs);
187 	}
188 
189 	nvlist_free(rp->rec_nvl);
190 	ea_free_object(rp->rec_grp, EUP_ALLOC);
191 }
192 
193 /*
194  * fmd_log_load_xref loads the cross-reference represented by the specified
195  * exacct group 'grp' into the next empty slot in rp->rec_xrefs.  This function
196  * is called repeatedly by fmd_log_load_xrefs() for each embedded reference.
197  */
198 static int
199 fmd_log_load_xref(fmd_log_t *lp, uint_t iflags,
200     fmd_log_record_t *rp, ea_object_t *grp)
201 {
202 	ea_object_t *obj;
203 	fmd_log_t *xlp;
204 	dev_t dev;
205 
206 	off64_t off = (off64_t)-1L;
207 	major_t maj = (major_t)-1L;
208 	minor_t min = (minor_t)-1L;
209 	ino64_t ino = (ino64_t)-1L;
210 	char *uuid = NULL;
211 
212 	for (obj = grp->eo_group.eg_objs; obj != NULL; obj = obj->eo_next) {
213 		switch (obj->eo_catalog) {
214 		case CAT_FMA_MAJOR:
215 			maj = obj->eo_item.ei_uint32;
216 			break;
217 		case CAT_FMA_MINOR:
218 			min = obj->eo_item.ei_uint32;
219 			break;
220 		case CAT_FMA_INODE:
221 			ino = obj->eo_item.ei_uint64;
222 			break;
223 		case CAT_FMA_OFFSET:
224 			off = obj->eo_item.ei_uint64;
225 			break;
226 		case CAT_FMA_UUID:
227 			uuid = obj->eo_item.ei_string;
228 			break;
229 		}
230 	}
231 
232 	if (off == (off64_t)-1L || (uuid == NULL && (ino == (ino64_t)-1L ||
233 	    maj == (major_t)-1L || min == (minor_t)-1L)))
234 		return (fmd_log_set_errno(lp, EFDL_BADREF));
235 
236 	if (uuid == NULL && (dev = makedev(maj, min)) == NODEV)
237 		return (fmd_log_set_errno(lp, EFDL_BADDEV));
238 
239 	/*
240 	 * Search our xref list for matching (dev_t, ino64_t) or (uuid).
241 	 * If we can't find one, return silently without
242 	 * doing anything.  We expect log xrefs to be broken whenever log
243 	 * files are trimmed or removed; their only purpose is to help us
244 	 * debug diagnosis engine algorithms.
245 	 */
246 	for (xlp = lp->log_xrefs; xlp != NULL; xlp = xlp->log_xnext) {
247 		if (uuid == NULL) {
248 			if (xlp->log_stat.st_ino == ino &&
249 			    xlp->log_stat.st_dev == dev)
250 				break;
251 		} else if (xlp->log_uuid != NULL &&
252 		    strcmp(xlp->log_uuid, uuid) == 0)
253 			break;
254 	}
255 
256 	if (xlp == NULL) {
257 		if (uuid == NULL)
258 			fmd_log_dprintf(lp, "broken xref dev=%lx ino=%llx\n",
259 			    (ulong_t)dev, (u_longlong_t)ino);
260 		else
261 			fmd_log_dprintf(lp, "broken xref uuid=%s\n", uuid);
262 
263 		return (0);
264 	}
265 
266 	xlp->log_flags &= ~FMD_LF_START;
267 	ea_clear(&xlp->log_ea);
268 	(void) lseek64(xlp->log_fd, off, SEEK_SET);
269 
270 	return (fmd_log_load_record(xlp,
271 	    iflags, &rp->rec_xrefs[rp->rec_nrefs++]));
272 }
273 
274 /*
275  * fmd_log_load_xrdir is called by fmd_log_load_xrefs when the FMD_LF_XREFS bit
276  * is not yet set, indicating we haven't looked for cross-referenced files.  We
277  * open the directory associated with the specified log file and attempt to
278  * perform an fmd_log_open() on every file found there (i.e. /var/fm/fmd).  If
279  * we are successful, the files are chained on to lp->log_xrefs, where the
280  * fmd_log_load_xref() function can find them by comparing dev/ino to log_stat.
281  */
282 static void
283 fmd_log_load_xrdir(fmd_log_t *lp)
284 {
285 	fmd_log_t *xlp;
286 	char dirbuf[PATH_MAX], path[PATH_MAX], *dirpath;
287 	struct dirent *dp;
288 	DIR *dirp;
289 
290 	lp->log_flags |= FMD_LF_XREFS;
291 	(void) strlcpy(dirbuf, lp->log_path, sizeof (dirbuf));
292 	dirpath = dirname(dirbuf);
293 
294 	if ((dirp = opendir(dirpath)) == NULL)
295 		return; /* failed to open directory; just skip it */
296 
297 	while ((dp = readdir(dirp)) != NULL) {
298 		if (dp->d_name[0] == '.')
299 			continue; /* skip "." and ".." and hidden files */
300 
301 		(void) snprintf(path, sizeof (path),
302 		    "%s/%s", dirpath, dp->d_name);
303 
304 		if (strcmp(path, lp->log_path) != 0 &&
305 		    (xlp = fmd_log_open(lp->log_abi, path, NULL)) != NULL) {
306 			fmd_log_dprintf(lp, "%s loaded %s for xrefs\n",
307 			    lp->log_path, xlp->log_path);
308 			xlp->log_xnext = lp->log_xrefs;
309 			lp->log_xrefs = xlp;
310 		}
311 	}
312 }
313 
314 /*
315  * fmd_log_load_xrefs iterates again over the record's exacct group and for
316  * each cross-reference (embedded CAT_FMA_GROUP), attempts to fill in the
317  * corresponding xref.  rp->rec_nrefs is reset to the number of valid items
318  * in the finished rp->rec_xrefs array; see fmd_log_load_xref() for more info.
319  */
320 static int
321 fmd_log_load_xrefs(fmd_log_t *lp, uint_t iflags, fmd_log_record_t *rp)
322 {
323 	size_t size = sizeof (fmd_log_record_t) * rp->rec_nrefs;
324 	ea_object_t *rgrp = rp->rec_grp;
325 	ea_object_t *grp, *obj;
326 
327 	if (!(iflags & FMD_LOG_XITER_REFS))
328 		return (0); /* do not load any xrefs */
329 
330 	if (!(lp->log_flags & FMD_LF_XREFS))
331 		fmd_log_load_xrdir(lp);
332 
333 	if ((rp->rec_xrefs = malloc(size)) == NULL)
334 		return (fmd_log_set_errno(lp, EFDL_NOMEM));
335 
336 	bzero(rp->rec_xrefs, size);
337 	rp->rec_nrefs = 0;
338 
339 	/*
340 	 * Make a second pass through the record group to locate and process
341 	 * each cross-reference sub-group.  The structure of the groups is
342 	 * as follows (left-hand-side symbols named after the variables used):
343 	 *
344 	 * rgrp := CAT_FMA_TODSEC CAT_FMA_TODNSEC CAT_FMA_NVLIST grp*
345 	 * grp  := obj* (i.e. zero or more groups of xref items)
346 	 * obj  := CAT_FMA_MAJOR CAT_FMA_MINOR CAT_FMA_INODE CAT_FMA_OFFSET
347 	 *
348 	 * For each xref 'obj', we call fmd_log_load_xref() to parse the four
349 	 * xref members and then load the specified record into rp->rec_xrefs.
350 	 */
351 	for (grp = rgrp->eo_group.eg_objs; grp != NULL; grp = grp->eo_next) {
352 		if (grp->eo_catalog != CAT_FMA_GROUP)
353 			continue; /* ignore anything that isn't a group */
354 
355 		for (obj = grp->eo_group.eg_objs;
356 		    obj != NULL; obj = obj->eo_next) {
357 			if (fmd_log_load_xref(lp, iflags, rp, obj) != 0)
358 				return (-1); /* errno is set for us */
359 		}
360 	}
361 
362 	return (0);
363 }
364 
365 static fmd_log_t *
366 fmd_log_open_err(fmd_log_t *lp, int *errp, int err)
367 {
368 	if (errp != NULL)
369 		*errp = err == EFDL_EXACCT ? EFDL_EXACCT + ea_error() : err;
370 
371 	if (lp != NULL)
372 		fmd_log_close(lp);
373 
374 	return (NULL);
375 }
376 
377 fmd_log_t *
378 fmd_log_open(int abi, const char *name, int *errp)
379 {
380 	ea_object_t *grp, *obj;
381 	fmd_log_t *lp;
382 	int fd;
383 
384 	if (abi > FMD_LOG_VERSION)
385 		return (fmd_log_open_err(NULL, errp, EFDL_VERSION));
386 
387 	if ((lp = malloc(sizeof (fmd_log_t))) == NULL)
388 		return (fmd_log_open_err(NULL, errp, EFDL_NOMEM));
389 
390 	bzero(lp, sizeof (fmd_log_t));
391 
392 	if ((lp->log_path = strdup(name)) == NULL)
393 		return (fmd_log_open_err(lp, errp, EFDL_NOMEM));
394 
395 	if ((lp->log_fd = open64(name, O_RDONLY)) == -1 ||
396 	    fstat64(lp->log_fd, &lp->log_stat) == -1 ||
397 	    (fd = dup(lp->log_fd)) == -1)
398 		return (fmd_log_open_err(lp, errp, errno));
399 
400 	if (ea_fdopen(&lp->log_ea, fd, FMD_CREATOR,
401 	    EO_VALID_HDR | EO_HEAD, O_RDONLY) == -1) {
402 		(void) close(fd);
403 		return (fmd_log_open_err(lp, errp, EFDL_EXACCT));
404 	}
405 
406 	lp->log_abi = abi;
407 	lp->log_flags |= FMD_LF_EAOPEN;
408 	if (getenv("FMD_LOG_DEBUG") != NULL)
409 		lp->log_flags |= FMD_LF_DEBUG;
410 
411 	/*
412 	 * Read the first group of log meta-data: the write-once read-only
413 	 * file header.  We read all records in this group, ignoring all but
414 	 * the VERSION and LABEL, which are required and must be verified.
415 	 */
416 	if ((grp = ea_get_object_tree(&lp->log_ea, 1)) == NULL)
417 		return (fmd_log_open_err(lp, errp, EFDL_EXACCT));
418 
419 	if (grp->eo_catalog != CAT_FMA_GROUP) {
420 		ea_free_object(grp, EUP_ALLOC);
421 		return (fmd_log_open_err(lp, errp, EFDL_EXACCT));
422 	}
423 
424 	for (obj = grp->eo_group.eg_objs; obj != NULL; obj = obj->eo_next) {
425 		switch (obj->eo_catalog) {
426 		case CAT_FMA_VERSION:
427 			lp->log_version = strdup(obj->eo_item.ei_string);
428 			if (lp->log_version == NULL) {
429 				ea_free_object(grp, EUP_ALLOC);
430 				return (fmd_log_open_err(lp, errp, EFDL_NOMEM));
431 			}
432 			break;
433 		case CAT_FMA_LABEL:
434 			lp->log_label = strdup(obj->eo_item.ei_string);
435 			if (lp->log_label == NULL) {
436 				ea_free_object(grp, EUP_ALLOC);
437 				return (fmd_log_open_err(lp, errp, EFDL_NOMEM));
438 			}
439 			break;
440 		case CAT_FMA_OSREL:
441 			lp->log_osrelease = strdup(obj->eo_item.ei_string);
442 			if (lp->log_osrelease == NULL) {
443 				ea_free_object(grp, EUP_ALLOC);
444 				return (fmd_log_open_err(lp, errp, EFDL_NOMEM));
445 			}
446 			break;
447 		case CAT_FMA_OSVER:
448 			lp->log_osversion = strdup(obj->eo_item.ei_string);
449 			if (lp->log_osversion == NULL) {
450 				ea_free_object(grp, EUP_ALLOC);
451 				return (fmd_log_open_err(lp, errp, EFDL_NOMEM));
452 			}
453 			break;
454 		case CAT_FMA_PLAT:
455 			lp->log_platform = strdup(obj->eo_item.ei_string);
456 			if (lp->log_platform == NULL) {
457 				ea_free_object(grp, EUP_ALLOC);
458 				return (fmd_log_open_err(lp, errp, EFDL_NOMEM));
459 			}
460 			break;
461 		case CAT_FMA_UUID:
462 			lp->log_uuid = strdup(obj->eo_item.ei_string);
463 			if (lp->log_uuid == NULL) {
464 				ea_free_object(grp, EUP_ALLOC);
465 				return (fmd_log_open_err(lp, errp, EFDL_NOMEM));
466 			}
467 			break;
468 		}
469 	}
470 
471 	ea_free_object(grp, EUP_ALLOC);
472 
473 	if (lp->log_version == NULL || lp->log_label == NULL)
474 		return (fmd_log_open_err(lp, errp, EFDL_BADHDR));
475 
476 	/*
477 	 * Read the second group of log meta-data: the table of contents.  At
478 	 * present there are no records libfmd_log needs in here, so we just
479 	 * skip over this entire group so that fmd_log_xiter() starts after it.
480 	 */
481 	if ((grp = ea_get_object_tree(&lp->log_ea, 1)) == NULL)
482 		return (fmd_log_open_err(lp, errp, EFDL_EXACCT));
483 
484 	if (grp->eo_catalog != CAT_FMA_GROUP) {
485 		ea_free_object(grp, EUP_ALLOC);
486 		return (fmd_log_open_err(lp, errp, EFDL_EXACCT));
487 	}
488 
489 	ea_free_object(grp, EUP_ALLOC);
490 	lp->log_flags |= FMD_LF_START;
491 
492 	fmd_log_dprintf(lp, "open log %s dev=%lx ino=%llx\n", lp->log_path,
493 	    (ulong_t)lp->log_stat.st_dev, (u_longlong_t)lp->log_stat.st_ino);
494 
495 	return (lp);
496 }
497 
498 void
499 fmd_log_close(fmd_log_t *lp)
500 {
501 	fmd_log_t *xlp, *nlp;
502 
503 	if (lp == NULL)
504 		return; /* permit null lp to simply caller code */
505 
506 	for (xlp = lp->log_xrefs; xlp != NULL; xlp = nlp) {
507 		nlp = xlp->log_xnext;
508 		fmd_log_close(xlp);
509 	}
510 
511 	if (lp->log_flags & FMD_LF_EAOPEN)
512 		(void) ea_close(&lp->log_ea);
513 
514 	if (lp->log_fd >= 0)
515 		(void) close(lp->log_fd);
516 
517 	free(lp->log_path);
518 	free(lp->log_version);
519 	free(lp->log_label);
520 	free(lp->log_osrelease);
521 	free(lp->log_osversion);
522 	free(lp->log_platform);
523 	free(lp->log_uuid);
524 
525 	free(lp);
526 }
527 
528 const char *
529 fmd_log_label(fmd_log_t *lp)
530 {
531 	return (lp->log_label);
532 }
533 
534 void
535 fmd_log_header(fmd_log_t *lp, fmd_log_header_t *hp)
536 {
537 	const char *creator = ea_get_creator(&lp->log_ea);
538 	const char *hostname = ea_get_hostname(&lp->log_ea);
539 
540 	hp->log_creator = creator ? creator : "";
541 	hp->log_hostname = hostname ? hostname : "";
542 	hp->log_label = lp->log_label ? lp->log_label : "";
543 	hp->log_version = lp->log_version ? lp->log_version : "";
544 	hp->log_osrelease = lp->log_osrelease ? lp->log_osrelease : "";
545 	hp->log_osversion = lp->log_osversion ? lp->log_osversion : "";
546 	hp->log_platform = lp->log_platform ? lp->log_platform : "";
547 	if (lp->log_abi > 1)
548 		hp->log_uuid = lp->log_uuid ? lp->log_uuid : "";
549 }
550 
551 /*
552  * Note: this will be verrrry slow for big files.  If this function becomes
553  * important, we'll need to add a function to libexacct to let us rewind.
554  * Currently libexacct has no notion of seeking other than record-at-a-time.
555  */
556 int
557 fmd_log_rewind(fmd_log_t *lp)
558 {
559 	ea_object_t obj, *grp;
560 
561 	if (!(lp->log_flags & FMD_LF_START)) {
562 		while (ea_previous_object(&lp->log_ea, &obj) != EO_ERROR)
563 			continue; /* rewind until beginning of file */
564 
565 		if ((grp = ea_get_object_tree(&lp->log_ea, 1)) == NULL)
566 			return (fmd_log_set_errno(lp, EFDL_EXACCT));
567 		else
568 			ea_free_object(grp, EUP_ALLOC); /* hdr group */
569 
570 		if ((grp = ea_get_object_tree(&lp->log_ea, 1)) == NULL)
571 			return (fmd_log_set_errno(lp, EFDL_EXACCT));
572 		else
573 			ea_free_object(grp, EUP_ALLOC); /* toc group */
574 
575 		lp->log_flags |= FMD_LF_START;
576 	}
577 
578 	return (0);
579 }
580 
581 static int
582 fmd_log_xiter_filter(fmd_log_t *lp, const fmd_log_record_t *rp,
583     uint_t fac, const fmd_log_filtvec_t *fav)
584 {
585 	uint_t i, j;
586 
587 	for (i = 0; i < fac; i++) {
588 		for (j = 0; j < fav[i].filt_argc; j++) {
589 			if (fav[i].filt_argv[j].filt_func(lp, rp,
590 			    fav[i].filt_argv[j].filt_arg) != 0)
591 				break; /* logical OR of this class is true */
592 		}
593 
594 		if (j == fav[i].filt_argc)
595 			return (0); /* logical AND of filter is false */
596 	}
597 
598 	return (1); /* logical AND of filter is true */
599 }
600 
601 static int
602 fmd_log_xiter_filtcmp(const void *lp, const void *rp)
603 {
604 	return ((intptr_t)((fmd_log_filter_t *)lp)->filt_func -
605 	    (intptr_t)((fmd_log_filter_t *)rp)->filt_func);
606 }
607 
608 int
609 fmd_log_filter(fmd_log_t *lp, uint_t fc, fmd_log_filter_t *fv,
610     const fmd_log_record_t *rp)
611 {
612 	fmd_log_filtvec_t *fav = alloca(fc * sizeof (fmd_log_filtvec_t));
613 	uint_t i, fac = 0;
614 
615 	/*
616 	 * If a filter array was provided, create an array of filtvec structs
617 	 * to perform logical AND/OR processing.  See fmd_log_xiter(), below.
618 	 */
619 	bzero(fav, fc * sizeof (fmd_log_filtvec_t));
620 	qsort(fv, fc, sizeof (fmd_log_filter_t), fmd_log_xiter_filtcmp);
621 
622 	for (i = 0; i < fc; i++) {
623 		if (i == 0 || fv[i].filt_func != fv[i - 1].filt_func)
624 			fav[fac++].filt_argv = &fv[i];
625 		fav[fac - 1].filt_argc++;
626 	}
627 
628 	return (fmd_log_xiter_filter(lp, rp, fac, fav));
629 }
630 
631 int
632 fmd_log_xiter(fmd_log_t *lp, uint_t flag, uint_t fc, fmd_log_filter_t *fv,
633     fmd_log_rec_f *rfunc, fmd_log_err_f *efunc, void *private, ulong_t *rcntp)
634 {
635 	fmd_log_record_t rec;
636 	fmd_log_filtvec_t *fav = NULL;
637 	uint_t i, fac = 0;
638 	ulong_t rcnt = 0;
639 	int rv = 0;
640 
641 	if (flag & ~FMD_LOG_XITER_MASK)
642 		return (fmd_log_set_errno(lp, EINVAL));
643 
644 	/*
645 	 * If a filter array was provided, create an array of filtvec structs
646 	 * where each filtvec holds a pointer to an equivalent list of filters,
647 	 * as determined by their filt_func.  We sort the input array by func,
648 	 * and then fill in the filtvec struct array.  We can then compute the
649 	 * logical OR of equivalent filters by iterating over filt_argv, and
650 	 * we can compute the logical AND of 'fv' by iterating over filt_argc.
651 	 */
652 	if (fc != 0) {
653 		if ((fav = calloc(fc, sizeof (fmd_log_filtvec_t))) == NULL)
654 			return (fmd_log_set_errno(lp, EFDL_NOMEM));
655 
656 		qsort(fv, fc, sizeof (fmd_log_filter_t), fmd_log_xiter_filtcmp);
657 
658 		for (i = 0; i < fc; i++) {
659 			if (i == 0 || fv[i].filt_func != fv[i - 1].filt_func)
660 				fav[fac++].filt_argv = &fv[i];
661 			fav[fac - 1].filt_argc++;
662 		}
663 	}
664 
665 	lp->log_flags &= ~FMD_LF_START;
666 	ea_clear(&lp->log_ea);
667 
668 	do {
669 		if (fmd_log_load_record(lp, flag, &rec) != 0) {
670 			if (lp->log_errno == EFDL_EXACCT + EXR_EOF)
671 				break; /* end-of-file reached */
672 			rv = efunc ? efunc(lp, private) : -1;
673 			rcnt++;
674 		} else {
675 			if (fc == 0 || fmd_log_xiter_filter(lp, &rec, fac, fav))
676 				rv = rfunc(lp, &rec, private);
677 
678 			fmd_log_free_record(&rec);
679 			rcnt++;
680 		}
681 	} while (rv == 0);
682 
683 	if (fac != 0)
684 		free(fav);
685 
686 	if (rcntp != NULL)
687 		*rcntp = rcnt;
688 
689 	return (rv);
690 }
691 
692 int
693 fmd_log_iter(fmd_log_t *lp, fmd_log_rec_f *rfunc, void *private)
694 {
695 	return (fmd_log_xiter(lp, 0, 0, NULL, rfunc, NULL, private, NULL));
696 }
697 
698 int
699 fmd_log_seek(fmd_log_t *lp, off64_t off)
700 {
701 	lp->log_flags &= ~FMD_LF_START;
702 	ea_clear(&lp->log_ea);
703 
704 	if (lseek64(lp->log_fd, off, SEEK_SET) != off)
705 		return (fmd_log_set_errno(lp, errno));
706 
707 	return (0);
708 }
709 
710 static const char *const _fmd_errs[] = {
711 	"client requires newer version of libfmd_log",	/* EFDL_VERSION */
712 	"required memory allocation failed",		/* EFDL_NOMEM */
713 	"log header did not contain required field",	/* EFDL_BADHDR */
714 	"log record did not contain protocol class",	/* EFDL_NOCLASS */
715 	"log record has invalid catalog tag",		/* EFDL_BADTAG */
716 	"log record has invalid cross-reference group",	/* EFDL_BADREF */
717 	"log record has invalid cross-reference dev_t",	/* EFDL_BADDEV */
718 	"log record was not of expected type",		/* EFDL_EXACCT + OK */
719 	"log access system call failed",		/* EXR_SYSCALL_FAIL */
720 	"log file corruption detected",			/* EXR_CORRUPT_FILE */
721 	"end-of-file reached",				/* EXR_EOF */
722 	"log file does not have appropriate creator",	/* EXR_NO_CREATOR */
723 	"invalid unpack buffer specified",		/* EXR_INVALID_BUF */
724 	"invalid exacct operation for log file",	/* EXR_NOTSUPP */
725 	"log file requires newer version of libexacct",	/* EXR_UNKN_VERSION */
726 	"invalid object buffer specified",		/* EXR_INVALID_OBJ */
727 };
728 
729 static const int _fmd_nerr = sizeof (_fmd_errs) / sizeof (_fmd_errs[0]);
730 
731 /*ARGSUSED*/
732 const char *
733 fmd_log_errmsg(fmd_log_t *lp, int err)
734 {
735 	const char *msg;
736 
737 	if (err >= EFDL_BASE && err - EFDL_BASE < _fmd_nerr)
738 		msg = _fmd_errs[err - EFDL_BASE];
739 	else
740 		msg = strerror(err);
741 
742 	return (msg ? msg : "unknown error");
743 }
744 
745 int
746 fmd_log_errno(fmd_log_t *lp)
747 {
748 	return (lp->log_errno);
749 }
750