xref: /freebsd/usr.sbin/mfiutil/mfi_evt.c (revision c6ec7d31830ab1c80edae95ad5e4b9dba10c47ac)
1 /*-
2  * Copyright (c) 2008, 2009 Yahoo!, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. The names of the authors may not be used to endorse or promote
14  *    products derived from this software without specific prior written
15  *    permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $FreeBSD$
30  */
31 
32 #include <sys/types.h>
33 #include <sys/errno.h>
34 #include <err.h>
35 #include <fcntl.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <strings.h>
39 #include <time.h>
40 #include <unistd.h>
41 #include "mfiutil.h"
42 
43 static int
44 mfi_event_get_info(int fd, struct mfi_evt_log_state *info, uint8_t *statusp)
45 {
46 
47 	return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_EVENT_GETINFO, info,
48 	    sizeof(struct mfi_evt_log_state), NULL, 0, statusp));
49 }
50 
51 static int
52 mfi_get_events(int fd, struct mfi_evt_list *list, int num_events,
53     union mfi_evt filter, uint32_t start_seq, uint8_t *statusp)
54 {
55 	uint32_t mbox[2];
56 	size_t size;
57 
58 	mbox[0] = start_seq;
59 	mbox[1] = filter.word;
60 	size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) *
61 	    (num_events - 1);
62 	return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_EVENT_GET, list, size,
63 	    (uint8_t *)&mbox, sizeof(mbox), statusp));
64 }
65 
66 static int
67 show_logstate(int ac, char **av __unused)
68 {
69 	struct mfi_evt_log_state info;
70 	int error, fd;
71 
72 	if (ac != 1) {
73 		warnx("show logstate: extra arguments");
74 		return (EINVAL);
75 	}
76 
77 	fd = mfi_open(mfi_unit, O_RDWR);
78 	if (fd < 0) {
79 		error = errno;
80 		warn("mfi_open");
81 		return (error);
82 	}
83 
84 	if (mfi_event_get_info(fd, &info, NULL) < 0) {
85 		error = errno;
86 		warn("Failed to get event log info");
87 		close(fd);
88 		return (error);
89 	}
90 
91 	printf("mfi%d Event Log Sequence Numbers:\n", mfi_unit);
92 	printf("  Newest Seq #: %u\n", info.newest_seq_num);
93 	printf("  Oldest Seq #: %u\n", info.oldest_seq_num);
94 	printf("   Clear Seq #: %u\n", info.clear_seq_num);
95 	printf("Shutdown Seq #: %u\n", info.shutdown_seq_num);
96 	printf("    Boot Seq #: %u\n", info.boot_seq_num);
97 
98 	close(fd);
99 
100 	return (0);
101 }
102 MFI_COMMAND(show, logstate, show_logstate);
103 
104 static int
105 parse_seq(struct mfi_evt_log_state *info, char *arg, uint32_t *seq)
106 {
107 	char *cp;
108 	long val;
109 
110 	if (strcasecmp(arg, "newest") == 0) {
111 		*seq = info->newest_seq_num;
112 		return (0);
113 	}
114 	if (strcasecmp(arg, "oldest") == 0) {
115 		*seq = info->oldest_seq_num;
116 		return (0);
117 	}
118 	if (strcasecmp(arg, "clear") == 0) {
119 		*seq = info->clear_seq_num;
120 		return (0);
121 	}
122 	if (strcasecmp(arg, "shutdown") == 0) {
123 		*seq = info->shutdown_seq_num;
124 		return (0);
125 	}
126 	if (strcasecmp(arg, "boot") == 0) {
127 		*seq = info->boot_seq_num;
128 		return (0);
129 	}
130 	val = strtol(arg, &cp, 0);
131 	if (*cp != '\0' || val < 0) {
132 		errno = EINVAL;
133 		return (-1);
134 	}
135 	*seq = val;
136 	return (0);
137 }
138 
139 static int
140 parse_locale(char *arg, uint16_t *locale)
141 {
142 	char *cp;
143 	long val;
144 
145 	if (strncasecmp(arg, "vol", 3) == 0 || strcasecmp(arg, "ld") == 0) {
146 		*locale = MFI_EVT_LOCALE_LD;
147 		return (0);
148 	}
149 	if (strncasecmp(arg, "drive", 5) == 0 || strcasecmp(arg, "pd") == 0) {
150 		*locale = MFI_EVT_LOCALE_PD;
151 		return (0);
152 	}
153 	if (strncasecmp(arg, "encl", 4) == 0) {
154 		*locale = MFI_EVT_LOCALE_ENCL;
155 		return (0);
156 	}
157 	if (strncasecmp(arg, "batt", 4) == 0 ||
158 	    strncasecmp(arg, "bbu", 3) == 0) {
159 		*locale = MFI_EVT_LOCALE_BBU;
160 		return (0);
161 	}
162 	if (strcasecmp(arg, "sas") == 0) {
163 		*locale = MFI_EVT_LOCALE_SAS;
164 		return (0);
165 	}
166 	if (strcasecmp(arg, "ctrl") == 0 || strncasecmp(arg, "cont", 4) == 0) {
167 		*locale = MFI_EVT_LOCALE_CTRL;
168 		return (0);
169 	}
170 	if (strcasecmp(arg, "config") == 0) {
171 		*locale = MFI_EVT_LOCALE_CONFIG;
172 		return (0);
173 	}
174 	if (strcasecmp(arg, "cluster") == 0) {
175 		*locale = MFI_EVT_LOCALE_CLUSTER;
176 		return (0);
177 	}
178 	if (strcasecmp(arg, "all") == 0) {
179 		*locale = MFI_EVT_LOCALE_ALL;
180 		return (0);
181 	}
182 	val = strtol(arg, &cp, 0);
183 	if (*cp != '\0' || val < 0 || val > 0xffff) {
184 		errno = EINVAL;
185 		return (-1);
186 	}
187 	*locale = val;
188 	return (0);
189 }
190 
191 static int
192 parse_class(char *arg, int8_t *class)
193 {
194 	char *cp;
195 	long val;
196 
197 	if (strcasecmp(arg, "debug") == 0) {
198 		*class = MFI_EVT_CLASS_DEBUG;
199 		return (0);
200 	}
201 	if (strncasecmp(arg, "prog", 4) == 0) {
202 		*class = MFI_EVT_CLASS_PROGRESS;
203 		return (0);
204 	}
205 	if (strncasecmp(arg, "info", 4) == 0) {
206 		*class = MFI_EVT_CLASS_INFO;
207 		return (0);
208 	}
209 	if (strncasecmp(arg, "warn", 4) == 0) {
210 		*class = MFI_EVT_CLASS_WARNING;
211 		return (0);
212 	}
213 	if (strncasecmp(arg, "crit", 4) == 0) {
214 		*class = MFI_EVT_CLASS_CRITICAL;
215 		return (0);
216 	}
217 	if (strcasecmp(arg, "fatal") == 0) {
218 		*class = MFI_EVT_CLASS_FATAL;
219 		return (0);
220 	}
221 	if (strcasecmp(arg, "dead") == 0) {
222 		*class = MFI_EVT_CLASS_DEAD;
223 		return (0);
224 	}
225 	val = strtol(arg, &cp, 0);
226 	if (*cp != '\0' || val < -128 || val > 127) {
227 		errno = EINVAL;
228 		return (-1);
229 	}
230 	*class = val;
231 	return (0);
232 }
233 
234 /*
235  * The timestamp is the number of seconds since 00:00 Jan 1, 2000.  If
236  * the bits in 24-31 are all set, then it is the number of seconds since
237  * boot.
238  */
239 static const char *
240 format_timestamp(uint32_t timestamp)
241 {
242 	static char buffer[32];
243 	static time_t base;
244 	time_t t;
245 	struct tm tm;
246 
247 	if ((timestamp & 0xff000000) == 0xff000000) {
248 		snprintf(buffer, sizeof(buffer), "boot + %us", timestamp &
249 		    0x00ffffff);
250 		return (buffer);
251 	}
252 
253 	if (base == 0) {
254 		/* Compute 00:00 Jan 1, 2000 offset. */
255 		bzero(&tm, sizeof(tm));
256 		tm.tm_mday = 1;
257 		tm.tm_year = (2000 - 1900);
258 		base = mktime(&tm);
259 	}
260 	if (base == -1) {
261 		snprintf(buffer, sizeof(buffer), "%us", timestamp);
262 		return (buffer);
263 	}
264 	t = base + timestamp;
265 	strftime(buffer, sizeof(buffer), "%+", localtime(&t));
266 	return (buffer);
267 }
268 
269 static const char *
270 format_locale(uint16_t locale)
271 {
272 	static char buffer[8];
273 
274 	switch (locale) {
275 	case MFI_EVT_LOCALE_LD:
276 		return ("VOLUME");
277 	case MFI_EVT_LOCALE_PD:
278 		return ("DRIVE");
279 	case MFI_EVT_LOCALE_ENCL:
280 		return ("ENCL");
281 	case MFI_EVT_LOCALE_BBU:
282 		return ("BATTERY");
283 	case MFI_EVT_LOCALE_SAS:
284 		return ("SAS");
285 	case MFI_EVT_LOCALE_CTRL:
286 		return ("CTRL");
287 	case MFI_EVT_LOCALE_CONFIG:
288 		return ("CONFIG");
289 	case MFI_EVT_LOCALE_CLUSTER:
290 		return ("CLUSTER");
291 	case MFI_EVT_LOCALE_ALL:
292 		return ("ALL");
293 	default:
294 		snprintf(buffer, sizeof(buffer), "0x%04x", locale);
295 		return (buffer);
296 	}
297 }
298 
299 static const char *
300 format_class(int8_t class)
301 {
302 	static char buffer[6];
303 
304 	switch (class) {
305 	case MFI_EVT_CLASS_DEBUG:
306 		return ("debug");
307 	case MFI_EVT_CLASS_PROGRESS:
308 		return ("progress");
309 	case MFI_EVT_CLASS_INFO:
310 		return ("info");
311 	case MFI_EVT_CLASS_WARNING:
312 		return ("WARN");
313 	case MFI_EVT_CLASS_CRITICAL:
314 		return ("CRIT");
315 	case MFI_EVT_CLASS_FATAL:
316 		return ("FATAL");
317 	case MFI_EVT_CLASS_DEAD:
318 		return ("DEAD");
319 	default:
320 		snprintf(buffer, sizeof(buffer), "%d", class);
321 		return (buffer);
322 	}
323 }
324 
325 /* Simulates %D from kernel printf(9). */
326 static void
327 simple_hex(void *ptr, size_t length, const char *separator)
328 {
329 	unsigned char *cp;
330 	u_int i;
331 
332 	if (length == 0)
333 		return;
334 	cp = ptr;
335 	printf("%02x", cp[0]);
336 	for (i = 1; i < length; i++)
337 		printf("%s%02x", separator, cp[i]);
338 }
339 
340 static const char *
341 pdrive_location(struct mfi_evt_pd *pd)
342 {
343 	static char buffer[16];
344 
345 	if (pd->enclosure_index == 0)
346 		snprintf(buffer, sizeof(buffer), "%02d(s%d)", pd->device_id,
347 		    pd->slot_number);
348 	else
349 		snprintf(buffer, sizeof(buffer), "%02d(e%d/s%d)", pd->device_id,
350 		    pd->enclosure_index, pd->slot_number);
351 	return (buffer);
352 }
353 
354 static const char *
355 volume_name(int fd, struct mfi_evt_ld *ld)
356 {
357 
358 	return (mfi_volume_name(fd, ld->target_id));
359 }
360 
361 /* Ripped from sys/dev/mfi/mfi.c. */
362 static void
363 mfi_decode_evt(int fd, struct mfi_evt_detail *detail, int verbose)
364 {
365 
366 	printf("%5d (%s/%s/%s) - ", detail->seq, format_timestamp(detail->time),
367 	    format_locale(detail->evt_class.members.locale),
368 	    format_class(detail->evt_class.members.evt_class));
369 	switch (detail->arg_type) {
370 	case MR_EVT_ARGS_NONE:
371 		break;
372 	case MR_EVT_ARGS_CDB_SENSE:
373 		if (verbose) {
374 			printf("PD %s CDB ",
375 			    pdrive_location(&detail->args.cdb_sense.pd)
376 			    );
377 			simple_hex(detail->args.cdb_sense.cdb,
378 			    detail->args.cdb_sense.cdb_len, ":");
379 			printf(" Sense ");
380 			simple_hex(detail->args.cdb_sense.sense,
381 			    detail->args.cdb_sense.sense_len, ":");
382 			printf(":\n ");
383 		}
384 		break;
385 	case MR_EVT_ARGS_LD:
386 		printf("VOL %s event: ", volume_name(fd, &detail->args.ld));
387 		break;
388 	case MR_EVT_ARGS_LD_COUNT:
389 		printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
390 		if (verbose) {
391 			printf(" count %lld: ",
392 			    (long long)detail->args.ld_count.count);
393 		}
394 		printf(": ");
395 		break;
396 	case MR_EVT_ARGS_LD_LBA:
397 		printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
398 		if (verbose) {
399 			printf(" lba %lld",
400 			    (long long)detail->args.ld_lba.lba);
401 		}
402 		printf(": ");
403 		break;
404 	case MR_EVT_ARGS_LD_OWNER:
405 		printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
406 		if (verbose) {
407 			printf(" owner changed: prior %d, new %d",
408 			    detail->args.ld_owner.pre_owner,
409 			    detail->args.ld_owner.new_owner);
410 		}
411 		printf(": ");
412 		break;
413 	case MR_EVT_ARGS_LD_LBA_PD_LBA:
414 		printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
415 		if (verbose) {
416 			printf(" lba %lld, physical drive PD %s lba %lld",
417 			    (long long)detail->args.ld_lba_pd_lba.ld_lba,
418 			    pdrive_location(&detail->args.ld_lba_pd_lba.pd),
419 			    (long long)detail->args.ld_lba_pd_lba.pd_lba);
420 		}
421 		printf(": ");
422 		break;
423 	case MR_EVT_ARGS_LD_PROG:
424 		printf("VOL %s", volume_name(fd, &detail->args.ld_prog.ld));
425 		if (verbose) {
426 			printf(" progress %d%% in %ds",
427 			    detail->args.ld_prog.prog.progress/655,
428 			    detail->args.ld_prog.prog.elapsed_seconds);
429 		}
430 		printf(": ");
431 		break;
432 	case MR_EVT_ARGS_LD_STATE:
433 		printf("VOL %s", volume_name(fd, &detail->args.ld_prog.ld));
434 		if (verbose) {
435 			printf(" state prior %s new %s",
436 			    mfi_ldstate(detail->args.ld_state.prev_state),
437 			    mfi_ldstate(detail->args.ld_state.new_state));
438 		}
439 		printf(": ");
440 		break;
441 	case MR_EVT_ARGS_LD_STRIP:
442 		printf("VOL %s", volume_name(fd, &detail->args.ld_strip.ld));
443 		if (verbose) {
444 			printf(" strip %lld",
445 			    (long long)detail->args.ld_strip.strip);
446 		}
447 		printf(": ");
448 		break;
449 	case MR_EVT_ARGS_PD:
450 		if (verbose) {
451 			printf("PD %s event: ",
452 			    pdrive_location(&detail->args.pd));
453 		}
454 		break;
455 	case MR_EVT_ARGS_PD_ERR:
456 		if (verbose) {
457 			printf("PD %s err %d: ",
458 			    pdrive_location(&detail->args.pd_err.pd),
459 			    detail->args.pd_err.err);
460 		}
461 		break;
462 	case MR_EVT_ARGS_PD_LBA:
463 		if (verbose) {
464 			printf("PD %s lba %lld: ",
465 			    pdrive_location(&detail->args.pd_lba.pd),
466 			    (long long)detail->args.pd_lba.lba);
467 		}
468 		break;
469 	case MR_EVT_ARGS_PD_LBA_LD:
470 		if (verbose) {
471 			printf("PD %s lba %lld VOL %s: ",
472 			    pdrive_location(&detail->args.pd_lba_ld.pd),
473 			    (long long)detail->args.pd_lba.lba,
474 			    volume_name(fd, &detail->args.pd_lba_ld.ld));
475 		}
476 		break;
477 	case MR_EVT_ARGS_PD_PROG:
478 		if (verbose) {
479 			printf("PD %s progress %d%% seconds %ds: ",
480 			    pdrive_location(&detail->args.pd_prog.pd),
481 			    detail->args.pd_prog.prog.progress/655,
482 			    detail->args.pd_prog.prog.elapsed_seconds);
483 		}
484 		break;
485 	case MR_EVT_ARGS_PD_STATE:
486 		if (verbose) {
487 			printf("PD %s state prior %s new %s: ",
488 			    pdrive_location(&detail->args.pd_prog.pd),
489 			    mfi_pdstate(detail->args.pd_state.prev_state),
490 			    mfi_pdstate(detail->args.pd_state.new_state));
491 		}
492 		break;
493 	case MR_EVT_ARGS_PCI:
494 		if (verbose) {
495 			printf("PCI 0x%04x 0x%04x 0x%04x 0x%04x: ",
496 			    detail->args.pci.venderId,
497 			    detail->args.pci.deviceId,
498 			    detail->args.pci.subVenderId,
499 			    detail->args.pci.subDeviceId);
500 		}
501 		break;
502 	case MR_EVT_ARGS_RATE:
503 		if (verbose) {
504 			printf("Rebuild rate %d: ", detail->args.rate);
505 		}
506 		break;
507 	case MR_EVT_ARGS_TIME:
508 		if (verbose) {
509 			printf("Adapter time %s; %d seconds since power on: ",
510 			    format_timestamp(detail->args.time.rtc),
511 			    detail->args.time.elapsedSeconds);
512 		}
513 		break;
514 	case MR_EVT_ARGS_ECC:
515 		if (verbose) {
516 			printf("Adapter ECC %x,%x: %s: ",
517 			    detail->args.ecc.ecar,
518 			    detail->args.ecc.elog,
519 			    detail->args.ecc.str);
520 		}
521 		break;
522 	default:
523 		if (verbose) {
524 			printf("Type %d: ", detail->arg_type);
525 		}
526 		break;
527 	}
528 	printf("%s\n", detail->description);
529 }
530 
531 static int
532 show_events(int ac, char **av)
533 {
534 	struct mfi_evt_log_state info;
535 	struct mfi_evt_list *list;
536 	union mfi_evt filter;
537 	long val;
538 	char *cp;
539 	ssize_t size;
540 	uint32_t seq, start, stop;
541 	uint8_t status;
542 	int ch, error, fd, num_events, verbose;
543 	u_int i;
544 
545 	fd = mfi_open(mfi_unit, O_RDWR);
546 	if (fd < 0) {
547 		error = errno;
548 		warn("mfi_open");
549 		return (error);
550 	}
551 
552 	if (mfi_event_get_info(fd, &info, NULL) < 0) {
553 		error = errno;
554 		warn("Failed to get event log info");
555 		close(fd);
556 		return (error);
557 	}
558 
559 	/* Default settings. */
560 	num_events = 15;
561 	filter.members.reserved = 0;
562 	filter.members.locale = MFI_EVT_LOCALE_ALL;
563 	filter.members.evt_class = MFI_EVT_CLASS_WARNING;
564 	start = info.boot_seq_num;
565 	stop = info.newest_seq_num;
566 	verbose = 0;
567 
568 	/* Parse any options. */
569 	optind = 1;
570 	while ((ch = getopt(ac, av, "c:l:n:v")) != -1) {
571 		switch (ch) {
572 		case 'c':
573 			if (parse_class(optarg, &filter.members.evt_class) < 0) {
574 				error = errno;
575 				warn("Error parsing event class");
576 				close(fd);
577 				return (error);
578 			}
579 			break;
580 		case 'l':
581 			if (parse_locale(optarg, &filter.members.locale) < 0) {
582 				error = errno;
583 				warn("Error parsing event locale");
584 				close(fd);
585 				return (error);
586 			}
587 			break;
588 		case 'n':
589 			val = strtol(optarg, &cp, 0);
590 			if (*cp != '\0' || val <= 0) {
591 				warnx("Invalid event count");
592 				close(fd);
593 				return (EINVAL);
594 			}
595 			num_events = val;
596 			break;
597 		case 'v':
598 			verbose = 1;
599 			break;
600 		case '?':
601 		default:
602 			close(fd);
603 			return (EINVAL);
604 		}
605 	}
606 	ac -= optind;
607 	av += optind;
608 
609 	/* Determine buffer size and validate it. */
610 	size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) *
611 	    (num_events - 1);
612 	if (size > getpagesize()) {
613 		warnx("Event count is too high");
614 		close(fd);
615 		return (EINVAL);
616 	}
617 
618 	/* Handle optional start and stop sequence numbers. */
619 	if (ac > 2) {
620 		warnx("show events: extra arguments");
621 		close(fd);
622 		return (EINVAL);
623 	}
624 	if (ac > 0 && parse_seq(&info, av[0], &start) < 0) {
625 		error = errno;
626 		warn("Error parsing starting sequence number");
627 		close(fd);
628 		return (error);
629 	}
630 	if (ac > 1 && parse_seq(&info, av[1], &stop) < 0) {
631 		error = errno;
632 		warn("Error parsing ending sequence number");
633 		close(fd);
634 		return (error);
635 	}
636 
637 	list = malloc(size);
638 	if (list == NULL) {
639 		warnx("malloc failed");
640 		close(fd);
641 		return (ENOMEM);
642 	}
643 	for (seq = start;;) {
644 		if (mfi_get_events(fd, list, num_events, filter, seq,
645 		    &status) < 0) {
646 			error = errno;
647 			warn("Failed to fetch events");
648 			free(list);
649 			close(fd);
650 			return (error);
651 		}
652 		if (status == MFI_STAT_NOT_FOUND) {
653 			if (seq == start)
654 				warnx("No matching events found");
655 			break;
656 		}
657 		if (status != MFI_STAT_OK) {
658 			warnx("Error fetching events: %s", mfi_status(status));
659 			free(list);
660 			close(fd);
661 			return (EIO);
662 		}
663 
664 		for (i = 0; i < list->count; i++) {
665 			/*
666 			 * If this event is newer than 'stop_seq' then
667 			 * break out of the loop.  Note that the log
668 			 * is a circular buffer so we have to handle
669 			 * the case that our stop point is earlier in
670 			 * the buffer than our start point.
671 			 */
672 			if (list->event[i].seq >= stop) {
673 				if (start <= stop)
674 					break;
675 				else if (list->event[i].seq < start)
676 					break;
677 			}
678 			mfi_decode_evt(fd, &list->event[i], verbose);
679 		}
680 
681 		/*
682 		 * XXX: If the event's seq # is the end of the buffer
683 		 * then this probably won't do the right thing.  We
684 		 * need to know the size of the buffer somehow.
685 		 */
686 		seq = list->event[list->count - 1].seq + 1;
687 
688 	}
689 
690 	free(list);
691 	close(fd);
692 
693 	return (0);
694 }
695 MFI_COMMAND(show, events, show_events);
696