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: stable/11/usr.sbin/mfiutil/mfi_evt.c 330449 2018-03-05 07:26:05Z eadler $
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
46static int
47mfi_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
54static int
55mfi_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
69static int
70show_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}
105MFI_COMMAND(show, logstate, show_logstate);
106
107static int
108parse_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
142static int
143parse_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
194static int
195parse_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 */
242static const char *
243format_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
272static const char *
273format_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
302static const char *
303format_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). */
329static void
330simple_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
343static const char *
344pdrive_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
357static const char *
358volume_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. */
365static void
366mfi_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
534static int
535show_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	}
696finish:
697	if (first)
698		warnx("No matching events found");
699
700	free(list);
701	close(fd);
702
703	return (0);
704}
705MFI_COMMAND(show, events, show_events);
706