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