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