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