xref: /freebsd/usr.sbin/sesutil/sesutil.c (revision af0a81b6470aba4af4a24ae9804053722846ded4)
1 /*-
2  * Copyright (c) 2019 Klara Inc.
3  * Copyright (c) 2015 Baptiste Daroussin <bapt@FreeBSD.org>
4  * Copyright (c) 2015 Allan Jude <allanjude@FreeBSD.org>
5  * Copyright (c) 2000 by Matthew Jacob
6  * All rights reserved.
7  *
8  * Portions of this software were developed by Edward Tomasz Napierala
9  * under sponsorship from Klara Inc.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer
16  *    in this position and unchanged.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 #include <sys/endian.h>
35 #include <sys/param.h>
36 #include <sys/disk.h>
37 #include <sys/ioctl.h>
38 #include <sys/types.h>
39 
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <getopt.h>
43 #include <glob.h>
44 #include <stdbool.h>
45 #include <stddef.h>
46 #include <stdint.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <libxo/xo.h>
52 
53 #include <cam/scsi/scsi_enc.h>
54 
55 #include "eltsub.h"
56 
57 #define SESUTIL_XO_VERSION	"1"
58 
59 #define TEMPERATURE_OFFSET	20
60 
61 #define PRINT_STYLE_DASHED	0
62 #define PRINT_STYLE_DASHED_2	1
63 #define PRINT_STYLE_CSV 	2
64 #define PRINT_STYLE_CSV_2	3
65 
66 static int encstatus(int argc, char **argv);
67 static int fault(int argc, char **argv);
68 static int locate(int argc, char **argv);
69 static int objmap(int argc, char **argv);
70 static int sesled(int argc, char **argv, bool fault);
71 static int show(int argc, char **argv);
72 static void sesutil_print(int *style, const char *fmt, ...) __printflike(2,3);
73 
74 static struct command {
75 	const char *name;
76 	const char *param;
77 	const char *desc;
78 	int (*exec)(int argc, char **argv);
79 } cmds[] = {
80 	{ "fault",
81 	    "(<disk>|<sesid>|all) (on|off)",
82 	    "Change the state of the fault LED associated with a disk",
83 	    fault },
84 	{ "locate",
85 	    "(<disk>|<sesid>|all) (on|off)",
86 	    "Change the state of the locate LED associated with a disk",
87 	    locate },
88 	{ "map", "",
89 	    "Print a map of the devices managed by the enclosure", objmap } ,
90 	{ "show", "",
91 	    "Print a human-friendly summary of the enclosure", show } ,
92 	{ "status", "", "Print the status of the enclosure",
93 	    encstatus },
94 };
95 
96 static const int nbcmds = nitems(cmds);
97 static const char *uflag;
98 
99 static void
100 usage(const char *subcmd)
101 {
102 	int i;
103 
104 	if (subcmd == NULL) {
105 		xo_error("usage: %s [-u /dev/ses<N>] <command> [options]\n",
106 		    getprogname());
107 		xo_error("Commands supported:\n");
108 	}
109 	for (i = 0; i < nbcmds; i++) {
110 		if (subcmd != NULL) {
111 			if (strcmp(subcmd, cmds[i].name) == 0) {
112 				xo_error("usage: %s %s [-u /dev/ses<N>] "
113 				    "%s\n\t%s\n", getprogname(), subcmd,
114 				    cmds[i].param, cmds[i].desc);
115 				break;
116 			}
117 			continue;
118 		}
119 		xo_error("    %-12s%s\n\t\t%s\n\n", cmds[i].name,
120 		    cmds[i].param, cmds[i].desc);
121 	}
122 
123 	exit(EXIT_FAILURE);
124 }
125 
126 static void
127 do_led(int fd, unsigned int idx, elm_type_t type, bool onoff, bool setfault)
128 {
129 	int state = onoff ? 1 : 0;
130 	encioc_elm_status_t o;
131 	struct ses_ctrl_dev_slot *slot;
132 
133 	o.elm_idx = idx;
134 	if (ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t) &o) < 0) {
135 		close(fd);
136 		xo_err(EXIT_FAILURE, "ENCIOC_GETELMSTAT");
137 	}
138 	ses_status_to_ctrl(type, &o.cstat[0]);
139 	switch (type) {
140 	case ELMTYP_DEVICE:
141 	case ELMTYP_ARRAY_DEV:
142 		slot = (struct ses_ctrl_dev_slot *) &o.cstat[0];
143 		ses_ctrl_common_set_select(&slot->common, 1);
144 		if (setfault)
145 			ses_ctrl_dev_slot_set_rqst_fault(slot, state);
146 		else
147 			ses_ctrl_dev_slot_set_rqst_ident(slot, state);
148 		break;
149 	default:
150 		return;
151 	}
152 	if (ioctl(fd, ENCIOC_SETELMSTAT, (caddr_t) &o) < 0) {
153 		close(fd);
154 		xo_err(EXIT_FAILURE, "ENCIOC_SETELMSTAT");
155 	}
156 }
157 
158 static bool
159 disk_match(const char *devnames, const char *disk, size_t len)
160 {
161 	const char *dname;
162 
163 	dname = devnames;
164 	while ((dname = strstr(dname, disk)) != NULL) {
165 		if (dname[len] == '\0' || dname[len] == ',') {
166 			return (true);
167 		}
168 		dname++;
169 	}
170 
171 	return (false);
172 }
173 
174 static int
175 sesled(int argc, char **argv, bool setfault)
176 {
177 	encioc_elm_devnames_t objdn;
178 	encioc_element_t *objp;
179 	glob_t g;
180 	char *disk, *endptr;
181 	size_t len, i, ndisks;
182 	int fd;
183 	unsigned int nobj, j, sesid;
184 	bool all, isses, onoff;
185 
186 	isses = false;
187 	all = false;
188 	onoff = false;
189 
190 	if (argc != 3) {
191 		usage(setfault ? "fault" : "locate");
192 	}
193 
194 	disk = argv[1];
195 
196 	sesid = strtoul(disk, &endptr, 10);
197 	if (*endptr == '\0') {
198 		endptr = strrchr(uflag, '*');
199 		if (endptr != NULL && *endptr == '*') {
200 			xo_warnx("Must specifying a SES device (-u) to use a SES "
201 			    "id# to identify a disk");
202 			usage(setfault ? "fault" : "locate");
203 		}
204 		isses = true;
205 	}
206 
207 	if (strcmp(argv[2], "on") == 0) {
208 		onoff = true;
209 	} else if (strcmp(argv[2], "off") == 0) {
210 		onoff = false;
211 	} else {
212 		usage(setfault ? "fault" : "locate");
213 	}
214 
215 	if (strcmp(disk, "all") == 0) {
216 		all = true;
217 	}
218 	len = strlen(disk);
219 
220 	/* Get the list of ses devices */
221 	if (glob((uflag != NULL ? uflag : "/dev/ses[0-9]*"), 0, NULL, &g) ==
222 	    GLOB_NOMATCH) {
223 		globfree(&g);
224 		xo_errx(EXIT_FAILURE, "No SES devices found");
225 	}
226 
227 	ndisks = 0;
228 	for (i = 0; i < g.gl_pathc; i++) {
229 		/* ensure we only got numbers after ses */
230 		if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
231 		    strlen(g.gl_pathv[i] + 8)) {
232 			continue;
233 		}
234 		if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
235 			/*
236 			 * Don't treat non-access errors as critical if we are
237 			 * accessing all devices
238 			 */
239 			if (errno == EACCES && g.gl_pathc > 1) {
240 				xo_err(EXIT_FAILURE, "unable to access SES device");
241 			}
242 			xo_warn("unable to access SES device: %s", g.gl_pathv[i]);
243 			continue;
244 		}
245 
246 		if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) {
247 			close(fd);
248 			xo_err(EXIT_FAILURE, "ENCIOC_GETNELM");
249 		}
250 
251 		objp = calloc(nobj, sizeof(encioc_element_t));
252 		if (objp == NULL) {
253 			close(fd);
254 			xo_err(EXIT_FAILURE, "calloc()");
255 		}
256 
257 		if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) objp) < 0) {
258 			free(objp);
259 			close(fd);
260 			xo_err(EXIT_FAILURE, "ENCIOC_GETELMMAP");
261 		}
262 
263 		if (isses) {
264 			if (sesid >= nobj) {
265 				free(objp);
266 				close(fd);
267 				xo_errx(EXIT_FAILURE,
268 				     "Requested SES ID does not exist");
269 			}
270 			do_led(fd, sesid, objp[sesid].elm_type, onoff, setfault);
271 			ndisks++;
272 			free(objp);
273 			close(fd);
274 			break;
275 		}
276 		for (j = 0; j < nobj; j++) {
277 			const int devnames_size = 128;
278 			char devnames[devnames_size];
279 
280 			if (all) {
281 				encioc_elm_status_t es;
282 				memset(&es, 0, sizeof(es));
283 				es.elm_idx = objp[j].elm_idx;
284 				if (ioctl(fd, ENCIOC_GETELMSTAT, &es) < 0) {
285 					close(fd);
286 					xo_err(EXIT_FAILURE,
287 						"ENCIOC_GETELMSTAT");
288 				}
289 				if ((es.cstat[0] & 0xf) == SES_OBJSTAT_NOACCESS)
290 					continue;
291 				do_led(fd, objp[j].elm_idx, objp[j].elm_type,
292 				    onoff, setfault);
293 				continue;
294 			}
295 			memset(&objdn, 0, sizeof(objdn));
296 			memset(devnames, 0, devnames_size);
297 			objdn.elm_idx = objp[j].elm_idx;
298 			objdn.elm_names_size = devnames_size;
299 			objdn.elm_devnames = devnames;
300 			if (ioctl(fd, ENCIOC_GETELMDEVNAMES,
301 			    (caddr_t) &objdn) <0) {
302 				continue;
303 			}
304 			if (objdn.elm_names_len > 0) {
305 				if (disk_match(objdn.elm_devnames, disk, len)) {
306 					do_led(fd, objdn.elm_idx, objp[j].elm_type,
307 					    onoff, setfault);
308 					ndisks++;
309 					break;
310 				}
311 			}
312 		}
313 		free(objp);
314 		close(fd);
315 	}
316 	globfree(&g);
317 	if (ndisks == 0 && all == false) {
318 		xo_errx(EXIT_FAILURE, "Could not find the SES id of device '%s'",
319 		    disk);
320 	}
321 
322 	return (EXIT_SUCCESS);
323 }
324 
325 static int
326 locate(int argc, char **argv)
327 {
328 
329 	return (sesled(argc, argv, false));
330 }
331 
332 static int
333 fault(int argc, char **argv)
334 {
335 
336 	return (sesled(argc, argv, true));
337 }
338 
339 static void
340 sesutil_print(int *style, const char *fmt, ...)
341 {
342 	va_list args;
343 
344 	if (*style == PRINT_STYLE_DASHED) {
345 		xo_open_container("extra_status");
346 		xo_emit("\t\tExtra status:\n");
347 		*style = PRINT_STYLE_DASHED_2;
348 	} else if (*style == PRINT_STYLE_CSV) {
349 		xo_open_container("extra_status");
350 		*style = PRINT_STYLE_CSV_2;
351 	}
352 
353 	if (*style == PRINT_STYLE_DASHED_2)
354 		xo_emit("\t\t- ");
355 	else if (*style == PRINT_STYLE_CSV_2)
356 		xo_emit(", ");
357 	va_start(args, fmt);
358 	xo_emit_hv(NULL, fmt, args);
359 	va_end(args);
360 	if (*style == PRINT_STYLE_DASHED_2)
361 		xo_emit("\n");
362 }
363 
364 static void
365 print_extra_status(int eletype, u_char *cstat, int style)
366 {
367 
368 	if (cstat[0] & 0x40) {
369 		sesutil_print(&style, "{e:predicted_failure/true} Predicted Failure");
370 	}
371 	if (cstat[0] & 0x20) {
372 		sesutil_print(&style, "{e:disabled/true} Disabled");
373 	}
374 	if (cstat[0] & 0x10) {
375 		sesutil_print(&style, "{e:swapped/true} Swapped");
376 	}
377 	switch (eletype) {
378 	case ELMTYP_DEVICE:
379 	case ELMTYP_ARRAY_DEV:
380 		if (cstat[2] & 0x02) {
381 			sesutil_print(&style, "LED={q:led/locate}");
382 		}
383 		if (cstat[2] & 0x20) {
384 			sesutil_print(&style, "LED={q:led/fault}");
385 		}
386 		break;
387 	case ELMTYP_FAN:
388 		sesutil_print(&style, "Speed: {:speed/%d}{Uw:rpm}",
389 		    (((0x7 & cstat[1]) << 8) + cstat[2]) * 10);
390 		break;
391 	case ELMTYP_THERM:
392 		if (cstat[2]) {
393 			sesutil_print(&style, "Temperature: {:temperature/%d}{Uw:C}",
394 			    cstat[2] - TEMPERATURE_OFFSET);
395 		} else {
396 			sesutil_print(&style, "Temperature: -{q:temperature/reserved}");
397 		}
398 		break;
399 	case ELMTYP_VOM:
400 		sesutil_print(&style, "Voltage: {:voltage/%.2f}{Uw:V}",
401 		    be16dec(cstat + 2) / 100.0);
402 		break;
403 	}
404 	if (style) {
405 		xo_close_container("extra_status");
406 	}
407 }
408 
409 static int
410 objmap(int argc, char **argv __unused)
411 {
412 	encioc_string_t stri;
413 	encioc_elm_devnames_t e_devname;
414 	encioc_elm_status_t e_status;
415 	encioc_elm_desc_t e_desc;
416 	encioc_element_t *e_ptr;
417 	glob_t g;
418 	int fd;
419 	unsigned int j, nobj;
420 	size_t i;
421 	char str[32];
422 
423 	if (argc != 1) {
424 		usage("map");
425 	}
426 
427 	memset(&e_desc, 0, sizeof(e_desc));
428 	/* SES4r02 allows element descriptors of up to 65536 characters */
429 	e_desc.elm_desc_str = calloc(UINT16_MAX, sizeof(char));
430 	if (e_desc.elm_desc_str == NULL)
431 		xo_err(EXIT_FAILURE, "calloc()");
432 
433 	e_devname.elm_devnames = calloc(128, sizeof(char));
434 	if (e_devname.elm_devnames == NULL)
435 		xo_err(EXIT_FAILURE, "calloc()");
436 	e_devname.elm_names_size = 128;
437 
438 	/* Get the list of ses devices */
439 	if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) {
440 		globfree(&g);
441 		xo_errx(EXIT_FAILURE, "No SES devices found");
442 	}
443 	xo_set_version(SESUTIL_XO_VERSION);
444 	xo_open_container("sesutil");
445 	xo_open_list("enclosures");
446 	for (i = 0; i < g.gl_pathc; i++) {
447 		/* ensure we only got numbers after ses */
448 		if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
449 		    strlen(g.gl_pathv[i] + 8)) {
450 			continue;
451 		}
452 		if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
453 			/*
454 			 * Don't treat non-access errors as critical if we are
455 			 * accessing all devices
456 			 */
457 			if (errno == EACCES && g.gl_pathc > 1) {
458 				xo_err(EXIT_FAILURE, "unable to access SES device");
459 			}
460 			xo_warn("unable to access SES device: %s", g.gl_pathv[i]);
461 			continue;
462 		}
463 
464 		if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) {
465 			close(fd);
466 			xo_err(EXIT_FAILURE, "ENCIOC_GETNELM");
467 		}
468 
469 		e_ptr = calloc(nobj, sizeof(encioc_element_t));
470 		if (e_ptr == NULL) {
471 			close(fd);
472 			xo_err(EXIT_FAILURE, "calloc()");
473 		}
474 
475 		if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) e_ptr) < 0) {
476 			close(fd);
477 			xo_err(EXIT_FAILURE, "ENCIOC_GETELMMAP");
478 		}
479 
480 		xo_open_instance("enclosures");
481 		xo_emit("{t:enc/%s}:\n", g.gl_pathv[i] + 5);
482 		stri.bufsiz = sizeof(str);
483 		stri.buf = &str[0];
484 		if (ioctl(fd, ENCIOC_GETENCNAME, (caddr_t) &stri) == 0)
485 			xo_emit("\tEnclosure Name: {t:name/%s}\n", stri.buf);
486 		stri.bufsiz = sizeof(str);
487 		stri.buf = &str[0];
488 		if (ioctl(fd, ENCIOC_GETENCID, (caddr_t) &stri) == 0)
489 			xo_emit("\tEnclosure ID: {t:id/%s}\n", stri.buf);
490 
491 		xo_open_list("elements");
492 		for (j = 0; j < nobj; j++) {
493 			/* Get the status of the element */
494 			memset(&e_status, 0, sizeof(e_status));
495 			e_status.elm_idx = e_ptr[j].elm_idx;
496 			if (ioctl(fd, ENCIOC_GETELMSTAT,
497 			    (caddr_t) &e_status) < 0) {
498 				close(fd);
499 				xo_err(EXIT_FAILURE, "ENCIOC_GETELMSTAT");
500 			}
501 			/* Get the description of the element */
502 			e_desc.elm_idx = e_ptr[j].elm_idx;
503 			e_desc.elm_desc_len = UINT16_MAX;
504 			if (ioctl(fd, ENCIOC_GETELMDESC,
505 			    (caddr_t) &e_desc) < 0) {
506 				close(fd);
507 				xo_err(EXIT_FAILURE, "ENCIOC_GETELMDESC");
508 			}
509 			e_desc.elm_desc_str[e_desc.elm_desc_len] = '\0';
510 			/* Get the device name(s) of the element */
511 			e_devname.elm_idx = e_ptr[j].elm_idx;
512 			if (ioctl(fd, ENCIOC_GETELMDEVNAMES,
513 			    (caddr_t) &e_devname) <0) {
514 				/* Continue even if we can't look up devnames */
515 				e_devname.elm_devnames[0] = '\0';
516 			}
517 			xo_open_instance("elements");
518 			xo_emit("\tElement {:id/%u}, Type: {:type/%s}\n", e_ptr[j].elm_idx,
519 			    geteltnm(e_ptr[j].elm_type));
520 			xo_emit("\t\tStatus: {:status/%s} ({q:status_code/0x%02x 0x%02x 0x%02x 0x%02x})\n",
521 			    scode2ascii(e_status.cstat[0]), e_status.cstat[0],
522 			    e_status.cstat[1], e_status.cstat[2],
523 			    e_status.cstat[3]);
524 			if (e_desc.elm_desc_len > 0) {
525 				xo_emit("\t\tDescription: {:description/%s}\n",
526 				    e_desc.elm_desc_str);
527 			}
528 			if (e_devname.elm_names_len > 0) {
529 				xo_emit("\t\tDevice Names: {:device_names/%s}\n",
530 				    e_devname.elm_devnames);
531 			}
532 			print_extra_status(e_ptr[j].elm_type, e_status.cstat, PRINT_STYLE_DASHED);
533 			xo_close_instance("elements");
534 		}
535 		xo_close_list("elements");
536 		free(e_ptr);
537 		close(fd);
538 	}
539 	globfree(&g);
540 	free(e_devname.elm_devnames);
541 	free(e_desc.elm_desc_str);
542 	xo_close_list("enclosures");
543 	xo_close_container("sesutil");
544 	if (xo_finish() < 0)
545 		xo_err(EXIT_FAILURE, "stdout");
546 
547 	return (EXIT_SUCCESS);
548 }
549 
550 /*
551  * Get rid of the 'passN' devices, unless there's nothing else to show.
552  */
553 static void
554 skip_pass_devices(char *devnames, size_t devnameslen)
555 {
556 	char *dev, devs[128], passes[128], *tmp;
557 
558 	devs[0] = passes[0] = '\0';
559 	tmp = devnames;
560 
561 	while ((dev = strsep(&tmp, ",")) != NULL) {
562 		if (strncmp(dev, "pass", 4) == 0) {
563 			if (passes[0] != '\0')
564 				strlcat(passes, ",", sizeof(passes));
565 			strlcat(passes, dev, sizeof(passes));
566 		} else {
567 			if (devs[0] != '\0')
568 				strlcat(devs, ",", sizeof(devs));
569 			strlcat(devs, dev, sizeof(devs));
570 		}
571 	}
572 	strlcpy(devnames, devs, devnameslen);
573 	if (devnames[0] == '\0')
574 		strlcpy(devnames, passes, devnameslen);
575 }
576 
577 static void
578 fetch_device_details(char *devnames, char **model, char **serial, off_t *size)
579 {
580 	char ident[DISK_IDENT_SIZE];
581 	struct diocgattr_arg arg;
582 	char *tmp;
583 	off_t mediasize;
584 	int comma;
585 	int fd;
586 
587 	comma = (int)strcspn(devnames, ",");
588 	asprintf(&tmp, "/dev/%.*s", comma, devnames);
589 	if (tmp == NULL)
590 		xo_err(EXIT_FAILURE, "asprintf");
591 	fd = open(tmp, O_RDONLY);
592 	free(tmp);
593 	if (fd < 0) {
594 		/*
595 		 * This can happen with a disk so broken it cannot
596 		 * be probed by GEOM.
597 		 */
598 		*model = strdup("?");
599 		*serial = strdup("?");
600 		*size = -1;
601 		close(fd);
602 		return;
603 	}
604 
605 	strlcpy(arg.name, "GEOM::descr", sizeof(arg.name));
606 	arg.len = sizeof(arg.value.str);
607 	if (ioctl(fd, DIOCGATTR, &arg) == 0)
608 		*model = strdup(arg.value.str);
609 	else
610 		*model = NULL;
611 
612 	if (ioctl(fd, DIOCGIDENT, ident) == 0)
613 		*serial = strdup(ident);
614 	else
615 		*serial = NULL;
616 
617 	if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) == 0)
618 		*size = mediasize;
619 	else
620 		*size = -1;
621 	close(fd);
622 }
623 
624 static void
625 show_device(int fd, int elm_idx, encioc_elm_status_t e_status, encioc_elm_desc_t e_desc)
626 {
627 	encioc_elm_devnames_t e_devname;
628 	char *model = NULL, *serial = NULL;
629 	off_t size;
630 
631 	/* Get the device name(s) of the element */
632 	memset(&e_devname, 0, sizeof(e_devname));
633 	e_devname.elm_idx = elm_idx;
634 	e_devname.elm_names_size = 128;
635 	e_devname.elm_devnames = calloc(128, sizeof(char));
636 	if (e_devname.elm_devnames == NULL) {
637 		close(fd);
638 		xo_err(EXIT_FAILURE, "calloc()");
639 	}
640 
641 	if (ioctl(fd, ENCIOC_GETELMDEVNAMES,
642 	    (caddr_t) &e_devname) < 0) {
643 		/* We don't care if this fails */
644 		e_devname.elm_devnames[0] = '\0';
645 		size = -1;
646 	} else {
647 		skip_pass_devices(e_devname.elm_devnames, 128);
648 		fetch_device_details(e_devname.elm_devnames, &model, &serial, &size);
649 	}
650 	xo_open_instance("elements");
651 	xo_emit("{e:type/device_slot}");
652 	xo_emit("{d:description/%-15s} ", e_desc.elm_desc_len > 0 ? e_desc.elm_desc_str : "-");
653 	xo_emit("{e:description/%-15s}", e_desc.elm_desc_len > 0 ? e_desc.elm_desc_str : "");
654 	xo_emit("{d:device_names/%-7s} ", e_devname.elm_names_len > 0 ? e_devname.elm_devnames : "-");
655 	xo_emit("{e:device_names/%s}", e_devname.elm_names_len > 0 ? e_devname.elm_devnames : "");
656 	xo_emit("{d:model/%-25s} ", model ? model : "-");
657 	xo_emit("{e:model/%s}", model ? model : "");
658 	xo_emit("{d:serial/%-20s} ", serial != NULL ? serial : "-");
659 	xo_emit("{e:serial/%s}", serial != NULL ? serial : "");
660 	if ((e_status.cstat[0] & 0xf) == SES_OBJSTAT_OK && size >= 0) {
661 		xo_emit("{h,hn-1000:size/%ld}{e:status/%s}",
662 		    size, scode2ascii(e_status.cstat[0]));
663 	} else {
664 		xo_emit("{:status/%s}", scode2ascii(e_status.cstat[0]));
665 	}
666 	print_extra_status(ELMTYP_ARRAY_DEV, e_status.cstat, PRINT_STYLE_CSV);
667 	xo_emit("\n");
668 	xo_close_instance("elements");
669 	free(serial);
670 	free(model);
671 	free(e_devname.elm_devnames);
672 }
673 
674 static void
675 show_therm(encioc_elm_status_t e_status, encioc_elm_desc_t e_desc)
676 {
677 
678 	if (e_desc.elm_desc_len <= 0) {
679 		/* We don't have a label to display; might as well skip it. */
680 		return;
681 	}
682 
683 	if (e_status.cstat[2] == 0) {
684 		/* No temperature to show. */
685 		return;
686 	}
687 
688 	xo_open_instance("elements");
689 	xo_emit("{e:type/temperature_sensor}");
690 	xo_emit("{:description/%s}: {:temperature/%d}{Uw:C}",
691 	    e_desc.elm_desc_str, e_status.cstat[2] - TEMPERATURE_OFFSET);
692 	xo_close_instance("elements");
693 }
694 
695 static void
696 show_vom(encioc_elm_status_t e_status, encioc_elm_desc_t e_desc)
697 {
698 
699 	if (e_desc.elm_desc_len <= 0) {
700 		/* We don't have a label to display; might as well skip it. */
701 		return;
702 	}
703 
704 	if (e_status.cstat[2] == 0) {
705 		/* No voltage to show. */
706 		return;
707 	}
708 
709 	xo_open_instance("elements");
710 	xo_emit("{e:type/voltage_sensor}");
711 	xo_emit("{:description/%s}: {:voltage/%.2f}{Uw:V}",
712 	    e_desc.elm_desc_str, be16dec(e_status.cstat + 2) / 100.0);
713 	xo_close_instance("elements");
714 }
715 
716 static int
717 show(int argc, char **argv __unused)
718 {
719 	encioc_string_t stri;
720 	encioc_elm_status_t e_status;
721 	encioc_elm_desc_t e_desc;
722 	encioc_element_t *e_ptr;
723 	glob_t g;
724 	elm_type_t prev_type;
725 	int fd;
726 	unsigned int j, nobj;
727 	size_t i;
728 	bool first_ses;
729 	char str[32];
730 
731 	if (argc != 1) {
732 		usage("map");
733 	}
734 
735 	first_ses = true;
736 
737 	e_desc.elm_desc_str = calloc(UINT16_MAX, sizeof(char));
738 	if (e_desc.elm_desc_str == NULL)
739 		xo_err(EXIT_FAILURE, "calloc()");
740 
741 	/* Get the list of ses devices */
742 	if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) {
743 		globfree(&g);
744 		xo_errx(EXIT_FAILURE, "No SES devices found");
745 	}
746 	xo_set_version(SESUTIL_XO_VERSION);
747 	xo_open_container("sesutil");
748 	xo_open_list("enclosures");
749 	for (i = 0; i < g.gl_pathc; i++) {
750 		/* ensure we only got numbers after ses */
751 		if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
752 		    strlen(g.gl_pathv[i] + 8)) {
753 			continue;
754 		}
755 		if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
756 			/*
757 			 * Don't treat non-access errors as critical if we are
758 			 * accessing all devices
759 			 */
760 			if (errno == EACCES && g.gl_pathc > 1) {
761 				xo_err(EXIT_FAILURE, "unable to access SES device");
762 			}
763 			xo_warn("unable to access SES device: %s", g.gl_pathv[i]);
764 			continue;
765 		}
766 
767 		if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) {
768 			close(fd);
769 			xo_err(EXIT_FAILURE, "ENCIOC_GETNELM");
770 		}
771 
772 		e_ptr = calloc(nobj, sizeof(encioc_element_t));
773 		if (e_ptr == NULL) {
774 			close(fd);
775 			xo_err(EXIT_FAILURE, "calloc()");
776 		}
777 
778 		if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) e_ptr) < 0) {
779 			close(fd);
780 			xo_err(EXIT_FAILURE, "ENCIOC_GETELMMAP");
781 		}
782 
783 		xo_open_instance("enclosures");
784 
785 		if (first_ses)
786 			first_ses = false;
787 		else
788 			xo_emit("\n");
789 
790 		xo_emit("{t:enc/%s}: ", g.gl_pathv[i] + 5);
791 		stri.bufsiz = sizeof(str);
792 		stri.buf = &str[0];
793 		if (ioctl(fd, ENCIOC_GETENCNAME, (caddr_t) &stri) == 0)
794 			xo_emit("<{t:name/%s}>; ", stri.buf);
795 		stri.bufsiz = sizeof(str);
796 		stri.buf = &str[0];
797 		if (ioctl(fd, ENCIOC_GETENCID, (caddr_t) &stri) == 0)
798 			xo_emit("ID: {t:id/%s}", stri.buf);
799 		xo_emit("\n");
800 
801 		xo_open_list("elements");
802 		prev_type = -1;
803 		for (j = 0; j < nobj; j++) {
804 			/* Get the status of the element */
805 			memset(&e_status, 0, sizeof(e_status));
806 			e_status.elm_idx = e_ptr[j].elm_idx;
807 			if (ioctl(fd, ENCIOC_GETELMSTAT,
808 			    (caddr_t) &e_status) < 0) {
809 				close(fd);
810 				xo_err(EXIT_FAILURE, "ENCIOC_GETELMSTAT");
811 			}
812 
813 			/*
814 			 * Skip "Unsupported" elements; those usually precede
815 			 * the actual device entries and are not particularly
816 			 * interesting.
817 			 */
818 			if (e_status.cstat[0] == SES_OBJSTAT_UNSUPPORTED)
819 				continue;
820 
821 			/* Get the description of the element */
822 			e_desc.elm_idx = e_ptr[j].elm_idx;
823 			e_desc.elm_desc_len = UINT16_MAX;
824 			if (ioctl(fd, ENCIOC_GETELMDESC,
825 			    (caddr_t) &e_desc) < 0) {
826 				close(fd);
827 				xo_err(EXIT_FAILURE, "ENCIOC_GETELMDESC");
828 			}
829 			e_desc.elm_desc_str[e_desc.elm_desc_len] = '\0';
830 
831 			switch (e_ptr[j].elm_type) {
832 			case ELMTYP_DEVICE:
833 			case ELMTYP_ARRAY_DEV:
834 				if (e_ptr[j].elm_type != prev_type)
835 					xo_emit("Desc            Dev     Model                     Ident                Size/Status\n");
836 
837 				show_device(fd, e_ptr[j].elm_idx, e_status, e_desc);
838 				prev_type = e_ptr[j].elm_type;
839 				break;
840 			case ELMTYP_THERM:
841 				if (e_ptr[j].elm_type != prev_type)
842 					xo_emit("\nTemperatures: ");
843 				else
844 					xo_emit(", ");
845 				prev_type = e_ptr[j].elm_type;
846 				show_therm(e_status, e_desc);
847 				break;
848 			case ELMTYP_VOM:
849 				if (e_ptr[j].elm_type != prev_type)
850 					xo_emit("\nVoltages: ");
851 				else
852 					xo_emit(", ");
853 				prev_type = e_ptr[j].elm_type;
854 				show_vom(e_status, e_desc);
855 				break;
856 			default:
857 				/*
858 				 * Ignore stuff not interesting to the user.
859 				 */
860 				break;
861 			}
862 		}
863 		if (prev_type != (elm_type_t)-1 &&
864 		    prev_type != ELMTYP_DEVICE && prev_type != ELMTYP_ARRAY_DEV)
865 			xo_emit("\n");
866 		xo_close_list("elements");
867 		free(e_ptr);
868 		close(fd);
869 	}
870 	globfree(&g);
871 	free(e_desc.elm_desc_str);
872 	xo_close_list("enclosures");
873 	xo_close_container("sesutil");
874 	if (xo_finish() < 0)
875 		xo_err(EXIT_FAILURE, "stdout");
876 
877 	return (EXIT_SUCCESS);
878 }
879 
880 static int
881 encstatus(int argc, char **argv __unused)
882 {
883 	glob_t g;
884 	int fd, status;
885 	size_t i, e;
886 	u_char estat;
887 
888 	status = 0;
889 	if (argc != 1) {
890 		usage("status");
891 	}
892 
893 	/* Get the list of ses devices */
894 	if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) {
895 		globfree(&g);
896 		xo_errx(EXIT_FAILURE, "No SES devices found");
897 	}
898 
899 	xo_set_version(SESUTIL_XO_VERSION);
900 	xo_open_container("sesutil");
901 	xo_open_list("enclosures");
902 	for (i = 0; i < g.gl_pathc; i++) {
903 		/* ensure we only got numbers after ses */
904 		if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
905 		    strlen(g.gl_pathv[i] + 8)) {
906 			continue;
907 		}
908 		if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
909 			/*
910 			 * Don't treat non-access errors as critical if we are
911 			 * accessing all devices
912 			 */
913 			if (errno == EACCES && g.gl_pathc > 1) {
914 				xo_err(EXIT_FAILURE, "unable to access SES device");
915 			}
916 			xo_warn("unable to access SES device: %s", g.gl_pathv[i]);
917 			continue;
918 		}
919 
920 		if (ioctl(fd, ENCIOC_GETENCSTAT, (caddr_t) &estat) < 0) {
921 			xo_err(EXIT_FAILURE, "ENCIOC_GETENCSTAT");
922 			close(fd);
923 		}
924 
925 		xo_open_instance("enclosures");
926 		xo_emit("{:enc/%s}: ", g.gl_pathv[i] + 5);
927 		e = 0;
928 		if (estat == 0) {
929 			if (status == 0) {
930 				status = 1;
931 			}
932 			xo_emit("{q:status/OK}");
933 		} else {
934 			if (estat & SES_ENCSTAT_INFO) {
935 				xo_emit("{lq:status/INFO}");
936 				e++;
937 			}
938 			if (estat & SES_ENCSTAT_NONCRITICAL) {
939 				if (e)
940 					xo_emit(",");
941 				xo_emit("{lq:status/NONCRITICAL}");
942 				e++;
943 			}
944 			if (estat & SES_ENCSTAT_CRITICAL) {
945 				if (e)
946 					xo_emit(",");
947 				xo_emit("{lq:status/CRITICAL}");
948 				e++;
949 				status = -1;
950 			}
951 			if (estat & SES_ENCSTAT_UNRECOV) {
952 				if (e)
953 					xo_emit(",");
954 				xo_emit("{lq:status/UNRECOV}");
955 				e++;
956 				status = -1;
957 			}
958 		}
959 		xo_close_instance("enclosures");
960 		xo_emit("\n");
961 		close(fd);
962 	}
963 	globfree(&g);
964 
965 	xo_close_list("enclosures");
966 	xo_close_container("sesutil");
967 	if (xo_finish() < 0)
968 		xo_err(EXIT_FAILURE, "stdout");
969 
970 	if (status == 1) {
971 		return (EXIT_SUCCESS);
972 	} else {
973 		return (EXIT_FAILURE);
974 	}
975 }
976 
977 int
978 main(int argc, char **argv)
979 {
980 	int i, ch;
981 	struct command *cmd = NULL;
982 
983 	argc = xo_parse_args(argc, argv);
984 	if (argc < 0)
985 		exit(EXIT_FAILURE);
986 
987 	uflag = "/dev/ses[0-9]*";
988 	while ((ch = getopt_long(argc, argv, "u:", NULL, NULL)) != -1) {
989 		switch (ch) {
990 		case 'u':
991 			uflag = optarg;
992 			break;
993 		case '?':
994 		default:
995 			usage(NULL);
996 		}
997 	}
998 	argc -= optind;
999 	argv += optind;
1000 
1001 	if (argc < 1) {
1002 		xo_warnx("Missing command");
1003 		usage(NULL);
1004 	}
1005 
1006 	for (i = 0; i < nbcmds; i++) {
1007 		if (strcmp(argv[0], cmds[i].name) == 0) {
1008 			cmd = &cmds[i];
1009 			break;
1010 		}
1011 	}
1012 
1013 	if (cmd == NULL) {
1014 		xo_warnx("unknown command %s", argv[0]);
1015 		usage(NULL);
1016 	}
1017 
1018 	return (cmd->exec(argc, argv));
1019 }
1020