]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - usr.sbin/mfiutil/mfi_evt.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / usr.sbin / mfiutil / mfi_evt.c
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);