sesutil.c revision 292121
1/*-
2 * Copyright (c) 2015 Baptiste Daroussin <bapt@FreeBSD.org>
3 * Copyright (c) 2015 Allan Jude <allanjude@FreeBSD.org>
4 * Copyright (c) 2000 by Matthew Jacob
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 *    in this position and unchanged.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD: head/usr.sbin/sesutil/sesutil.c 292121 2015-12-11 20:45:39Z bapt $");
31
32#include <sys/param.h>
33#include <sys/ioctl.h>
34#include <sys/types.h>
35#include <sys/sbuf.h>
36
37#include <err.h>
38#include <errno.h>
39#include <fcntl.h>
40#include <getopt.h>
41#include <glob.h>
42#include <stdbool.h>
43#include <stddef.h>
44#include <stdint.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49
50#include <cam/scsi/scsi_all.h>
51#include <cam/scsi/scsi_enc.h>
52
53#include "eltsub.h"
54
55static int encstatus(int argc, char **argv);
56static int fault(int argc, char **argv);
57static int locate(int argc, char **argv);
58static int objmap(int argc, char **argv);
59static int sesled(int argc, char **argv, bool fault);
60
61static struct command {
62	const char *name;
63	const char *param;
64	const char *desc;
65	int (*exec)(int argc, char **argv);
66} cmds[] = {
67	{ "fault",
68	    "(<disk>|<sesid>|all) (on|off)",
69	    "Change the state of the fault LED associated with a disk",
70	    fault },
71	{ "locate",
72	    "(<disk>|<sesid>|all) (on|off)",
73	    "Change the state of the locate LED associated with a disk",
74	    locate },
75	{ "map", "",
76	    "Print a map of the devices managed by the enclosure", objmap } ,
77	{ "status", "", "Print the status of the enclosure",
78	    encstatus },
79};
80
81static const int nbcmds = nitems(cmds);
82static const char *uflag;
83
84static void
85usage(FILE *out, const char *subcmd)
86{
87	int i;
88
89	if (subcmd == NULL) {
90		fprintf(out, "Usage: %s [-u /dev/ses<N>] <command> [options]\n",
91		    getprogname());
92		fprintf(out, "Commands supported:\n");
93	}
94	for (i = 0; i < nbcmds; i++) {
95		if (subcmd != NULL) {
96			if (strcmp(subcmd, cmds[i].name) == 0) {
97				fprintf(out, "Usage: %s %s [-u /dev/ses<N>] "
98				    "%s\n\t%s\n", getprogname(), subcmd,
99				    cmds[i].param, cmds[i].desc);
100				break;
101			}
102			continue;
103		}
104		fprintf(out, "    %-12s%s\n\t\t%s\n\n", cmds[i].name,
105		    cmds[i].param, cmds[i].desc);
106	}
107
108	exit(EXIT_FAILURE);
109}
110
111static void
112do_led(int fd, unsigned int idx, bool onoff, bool setfault)
113{
114	encioc_elm_status_t o;
115
116	o.elm_idx = idx;
117	if (ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t) &o) < 0) {
118		close(fd);
119		err(EXIT_FAILURE, "ENCIOC_GETELMSTAT");
120	}
121	o.cstat[0] |= 0x80;
122	if (onoff) {
123		o.cstat[2] |= (setfault ? 0x20 : 0x02);
124	} else {
125		o.cstat[2] &= (setfault ? 0xdf : 0xfd);
126	}
127
128	if (ioctl(fd, ENCIOC_SETELMSTAT, (caddr_t) &o) < 0) {
129		close(fd);
130		err(EXIT_FAILURE, "ENCIOC_SETELMSTAT");
131	}
132}
133
134static bool
135disk_match(const char *devnames, const char *disk, size_t len)
136{
137	const char *dname;
138
139	dname = devnames;
140	while ((dname = strstr(dname, disk)) != NULL) {
141		if (dname[len] == '\0' || dname[len] == ',') {
142			return (true);
143		}
144		dname++;
145	}
146
147	return (false);
148}
149
150static int
151sesled(int argc, char **argv, bool setfault)
152{
153	encioc_elm_devnames_t objdn;
154	encioc_element_t *objp;
155	glob_t g;
156	char *disk, *endptr;
157	size_t len, i, ndisks;
158	int fd;
159	unsigned int nobj, j, sesid;
160	bool all, isses, onoff;
161
162	isses = false;
163	all = false;
164	onoff = false;
165
166	if (argc != 3) {
167		usage(stderr, (setfault ? "fault" : "locate"));
168	}
169
170	disk = argv[1];
171
172	sesid = strtoul(disk, &endptr, 10);
173	if (*endptr == '\0') {
174		endptr = strrchr(uflag, '*');
175		if (*endptr == '*') {
176			warnx("Must specifying a SES device (-u) to use a SES "
177			    "id# to identify a disk");
178			usage(stderr, (setfault ? "fault" : "locate"));
179		}
180		isses = true;
181	}
182
183	if (strcmp(argv[2], "on") == 0) {
184		onoff = true;
185	} else if (strcmp(argv[2], "off") == 0) {
186		onoff = false;
187	} else {
188		usage(stderr, (setfault ? "fault" : "locate"));
189	}
190
191	if (strcmp(disk, "all") == 0) {
192		all = true;
193	}
194	len = strlen(disk);
195
196	/* Get the list of ses devices */
197	if (glob((uflag != NULL ? uflag : "/dev/ses[0-9]*"), 0, NULL, &g) ==
198	    GLOB_NOMATCH) {
199		globfree(&g);
200		errx(EXIT_FAILURE, "No SES devices found");
201	}
202
203	ndisks = 0;
204	for (i = 0; i < g.gl_pathc; i++) {
205		/* ensure we only got numbers after ses */
206		if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
207		    strlen(g.gl_pathv[i] + 8)) {
208			continue;
209		}
210		if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
211			/*
212			 * Don't treat non-access errors as critical if we are
213			 * accessing all devices
214			 */
215			if (errno == EACCES && g.gl_pathc > 1) {
216				err(EXIT_FAILURE, "unable to access SES device");
217			}
218			warn("unable to access SES device: %s", g.gl_pathv[i]);
219			continue;
220		}
221
222		if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) {
223			close(fd);
224			err(EXIT_FAILURE, "ENCIOC_GETNELM");
225		}
226
227		objp = calloc(nobj, sizeof(encioc_element_t));
228		if (objp == NULL) {
229			close(fd);
230			err(EXIT_FAILURE, "calloc()");
231		}
232
233		if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) objp) < 0) {
234			close(fd);
235			err(EXIT_FAILURE, "ENCIOC_GETELMMAP");
236		}
237
238		if (isses) {
239			if (sesid > nobj) {
240				close(fd);
241				errx(EXIT_FAILURE,
242				     "Requested SES ID does not exist");
243			}
244			do_led(fd, sesid, onoff, setfault);
245			ndisks++;
246			close(fd);
247			break;
248		}
249		for (j = 0; j < nobj; j++) {
250			memset(&objdn, 0, sizeof(objdn));
251			objdn.elm_idx = objp[j].elm_idx;
252			objdn.elm_names_size = 128;
253			objdn.elm_devnames = calloc(128, sizeof(char));
254			if (objdn.elm_devnames == NULL) {
255				close(fd);
256				err(EXIT_FAILURE, "calloc()");
257			}
258			if (ioctl(fd, ENCIOC_GETELMDEVNAMES,
259			    (caddr_t) &objdn) <0) {
260				continue;
261			}
262			if (objdn.elm_names_len > 0) {
263				if (all) {
264					do_led(fd, objdn.elm_idx,
265					    onoff, setfault);
266					continue;
267				}
268				if (disk_match(objdn.elm_devnames, disk, len)) {
269					do_led(fd, objdn.elm_idx,
270					    onoff, setfault);
271					ndisks++;
272					break;
273				}
274			}
275		}
276		close(fd);
277	}
278	globfree(&g);
279	if (ndisks == 0 && all == false) {
280		errx(EXIT_FAILURE, "Count not find the SES id of device '%s'",
281		    disk);
282	}
283
284	return (EXIT_SUCCESS);
285}
286
287static int
288locate(int argc, char **argv)
289{
290
291	return (sesled(argc, argv, false));
292}
293
294static int
295fault(int argc, char **argv)
296{
297
298	return (sesled(argc, argv, true));
299}
300
301static int
302objmap(int argc, char **argv __unused)
303{
304	struct sbuf *extra;
305	encioc_elm_devnames_t e_devname;
306	encioc_elm_status_t e_status;
307	encioc_elm_desc_t e_desc;
308	encioc_element_t *e_ptr;
309	glob_t g;
310	int fd;
311	unsigned int j, nobj;
312	size_t i;
313
314	if (argc != 1) {
315		usage(stderr, "map");
316	}
317
318	/* Get the list of ses devices */
319	if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) {
320		globfree(&g);
321		errx(EXIT_FAILURE, "No SES devices found");
322	}
323	for (i = 0; i < g.gl_pathc; i++) {
324		/* ensure we only got numbers after ses */
325		if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
326		    strlen(g.gl_pathv[i] + 8)) {
327			continue;
328		}
329		if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
330			/*
331			 * Don't treat non-access errors as critical if we are
332			 * accessing all devices
333			 */
334			if (errno == EACCES && g.gl_pathc > 1) {
335				err(EXIT_FAILURE, "unable to access SES device");
336			}
337			warn("unable to access SES device: %s", g.gl_pathv[i]);
338			continue;
339		}
340
341		if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) {
342			close(fd);
343			err(EXIT_FAILURE, "ENCIOC_GETNELM");
344		}
345
346		e_ptr = calloc(nobj, sizeof(encioc_element_t));
347		if (e_ptr == NULL) {
348			close(fd);
349			err(EXIT_FAILURE, "calloc()");
350		}
351
352		if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) e_ptr) < 0) {
353			close(fd);
354			err(EXIT_FAILURE, "ENCIOC_GETELMMAP");
355		}
356
357		printf("%s:\n", g.gl_pathv[i] + 5);
358		for (j = 0; j < nobj; j++) {
359			/* Get the status of the element */
360			memset(&e_status, 0, sizeof(e_status));
361			e_status.elm_idx = e_ptr[j].elm_idx;
362			if (ioctl(fd, ENCIOC_GETELMSTAT,
363			    (caddr_t) &e_status) < 0) {
364				close(fd);
365				err(EXIT_FAILURE, "ENCIOC_GETELMSTAT");
366			}
367			/* Get the description of the element */
368			memset(&e_desc, 0, sizeof(e_desc));
369			e_desc.elm_idx = e_ptr[j].elm_idx;
370			e_desc.elm_desc_len = UINT16_MAX;
371			e_desc.elm_desc_str = calloc(UINT16_MAX, sizeof(char));
372			if (e_desc.elm_desc_str == NULL) {
373				close(fd);
374				err(EXIT_FAILURE, "calloc()");
375			}
376			if (ioctl(fd, ENCIOC_GETELMDESC,
377			    (caddr_t) &e_desc) < 0) {
378				close(fd);
379				err(EXIT_FAILURE, "ENCIOC_GETELMDESC");
380			}
381			/* Get the device name(s) of the element */
382			memset(&e_devname, 0, sizeof(e_devname));
383			e_devname.elm_idx = e_ptr[j].elm_idx;
384			e_devname.elm_names_size = 128;
385			e_devname.elm_devnames = calloc(128, sizeof(char));
386			if (e_devname.elm_devnames == NULL) {
387				close(fd);
388				err(EXIT_FAILURE, "calloc()");
389			}
390			if (ioctl(fd, ENCIOC_GETELMDEVNAMES,
391			    (caddr_t) &e_devname) <0) {
392				/* We don't care if this fails */
393				e_devname.elm_devnames[0] = '\0';
394			}
395			printf("\tElement %u, Type: %s\n", e_ptr[j].elm_idx,
396			    geteltnm(e_ptr[j].elm_type));
397			printf("\t\tStatus: %s (0x%02x 0x%02x 0x%02x 0x%02x)\n",
398			    scode2ascii(e_status.cstat[0]), e_status.cstat[0],
399			    e_status.cstat[1], e_status.cstat[2],
400			    e_status.cstat[3]);
401			if (e_desc.elm_desc_len > 0) {
402				printf("\t\tDescription: %s\n",
403				    e_desc.elm_desc_str);
404			}
405			if (e_devname.elm_names_len > 0) {
406				printf("\t\tDevice Names: %s\n",
407				    e_devname.elm_devnames);
408			}
409			extra = stat2sbuf(e_ptr[j].elm_type, e_status.cstat);
410			if (sbuf_len(extra) > 0) {
411				printf("\t\tExtra status:\n%s",
412				   sbuf_data(extra));
413			}
414			sbuf_delete(extra);
415			free(e_devname.elm_devnames);
416		}
417		close(fd);
418	}
419	globfree(&g);
420
421	return (EXIT_SUCCESS);
422}
423
424static int
425encstatus(int argc, char **argv __unused)
426{
427	glob_t g;
428	int fd, status;
429	size_t i, e;
430	u_char estat;
431
432	status = 0;
433	if (argc != 1) {
434		usage(stderr, "status");
435	}
436
437	/* Get the list of ses devices */
438	if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) {
439		globfree(&g);
440		errx(EXIT_FAILURE, "No SES devices found");
441	}
442	for (i = 0; i < g.gl_pathc; i++) {
443		/* ensure we only got numbers after ses */
444		if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
445		    strlen(g.gl_pathv[i] + 8)) {
446			continue;
447		}
448		if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
449			/*
450			 * Don't treat non-access errors as critical if we are
451			 * accessing all devices
452			 */
453			if (errno == EACCES && g.gl_pathc > 1) {
454				err(EXIT_FAILURE, "unable to access SES device");
455			}
456			warn("unable to access SES device: %s", g.gl_pathv[i]);
457			continue;
458		}
459
460		if (ioctl(fd, ENCIOC_GETENCSTAT, (caddr_t) &estat) < 0) {
461			close(fd);
462			err(EXIT_FAILURE, "ENCIOC_GETENCSTAT");
463		}
464
465		printf("%s: ", g.gl_pathv[i] + 5);
466		e = 0;
467		if (estat == 0) {
468			if (status == 0) {
469				status = 1;
470			}
471			printf("OK");
472		} else {
473			if (estat & SES_ENCSTAT_INFO) {
474				printf("INFO");
475				e++;
476			}
477			if (estat & SES_ENCSTAT_NONCRITICAL) {
478				if (e)
479					printf(",");
480				printf("NONCRITICAL");
481				e++;
482			}
483			if (estat & SES_ENCSTAT_CRITICAL) {
484				if (e)
485					printf(",");
486				printf("CRITICAL");
487				e++;
488				status = -1;
489			}
490			if (estat & SES_ENCSTAT_UNRECOV) {
491				if (e)
492					printf(",");
493				printf("UNRECOV");
494				e++;
495				status = -1;
496			}
497		}
498		printf("\n");
499
500		close(fd);
501	}
502	globfree(&g);
503
504	if (status == 1) {
505		return (EXIT_SUCCESS);
506	} else {
507		return (EXIT_FAILURE);
508	}
509}
510
511int
512main(int argc, char **argv)
513{
514	int i, ch;
515	struct command *cmd = NULL;
516
517	uflag = "/dev/ses[0-9]*";
518	while ((ch = getopt_long(argc, argv, "u:", NULL, NULL)) != -1) {
519		switch (ch) {
520		case 'u':
521			uflag = optarg;
522			break;
523		case '?':
524		default:
525			usage(stderr, NULL);
526		}
527	}
528	argc -= optind;
529	argv += optind;
530
531	if (argc < 1) {
532		warnx("Missing command");
533		usage(stderr, NULL);
534	}
535
536	for (i = 0; i < nbcmds; i++) {
537		if (strcmp(argv[0], cmds[i].name) == 0) {
538			cmd = &cmds[i];
539			break;
540		}
541	}
542
543	if (cmd == NULL) {
544		warnx("unknown command %s", argv[0]);
545		usage(stderr, NULL);
546	}
547
548	return (cmd->exec(argc, argv));
549}
550