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