2 * Copyright (C) 2021 Axcient, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 /* Basic smoke test of the ioctl interface */
30 #include <sys/types.h>
31 #include <sys/ioctl.h>
41 #include <cam/scsi/scsi_enc.h>
45 static bool do_getelmdesc(const char *devname, int fd) {
55 actual = calloc(UINT16_MAX, sizeof(char));
56 ATF_REQUIRE(actual != NULL);
57 r = regcomp(&re, "(Overall|Element [0-9]+) descriptor: ", REG_EXTENDED);
60 r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
63 snprintf(cmd, sizeof(cmd), "sg_ses -p7 %s", devname);
64 pipe = popen(cmd, "r");
65 ATF_REQUIRE(pipe != NULL);
66 while(NULL != fgets(line, sizeof(line), pipe)) {
67 regmatch_t matches[1];
68 encioc_elm_desc_t e_desc;
72 if (regexec(&re, line, 1, matches, 0) == REG_NOMATCH) {
76 expected = &line[matches[0].rm_eo];
77 /* Remove trailing newline */
78 elen = strnlen(expected, sizeof(line) - matches[0].rm_eo);
79 expected[elen - 1] = '\0';
81 * Zero the result string. XXX we wouldn't have to do this if
82 * the kernel would nul-terminate the result.
84 memset(actual, 0, UINT16_MAX);
85 e_desc.elm_idx = elm_idx;
86 e_desc.elm_desc_len = UINT16_MAX;
87 e_desc.elm_desc_str = actual;
88 r = ioctl(fd, ENCIOC_GETELMDESC, (caddr_t) &e_desc);
90 if (0 == strcmp("<empty>", expected)) {
91 /* sg_ses replaces "" with "<empty>" */
92 ATF_CHECK_STREQ("", actual);
94 ATF_CHECK_STREQ(expected, actual);
102 /* Probably an SGPIO device */
106 ATF_CHECK_EQ_MSG(nobj, elm_idx,
107 "Did not find the expected number of element "
108 "descriptors in sg_ses's output");
114 ATF_TC_HEAD(getelmdesc, tc)
116 atf_tc_set_md_var(tc, "descr",
117 "Compare ENCIOC_GETELMDESC's output to sg3_utils'");
118 atf_tc_set_md_var(tc, "require.user", "root");
119 atf_tc_set_md_var(tc, "require.progs", "sg_ses");
121 ATF_TC_BODY(getelmdesc, tc)
124 atf_tc_skip("No ses devices found");
125 for_each_ses_dev(do_getelmdesc, O_RDONLY);
128 static bool do_getelmdevnames(const char *devname __unused, int fd) {
129 encioc_element_t *map;
131 const size_t namesize = 128;
136 r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
137 ATF_REQUIRE_EQ(r, 0);
139 namebuf = calloc(namesize, sizeof(char));
140 ATF_REQUIRE(namebuf != NULL);
141 map = calloc(nobj, sizeof(encioc_element_t));
142 ATF_REQUIRE(map != NULL);
143 r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
144 ATF_REQUIRE_EQ(r, 0);
146 for (elm_idx = 0; elm_idx < nobj; elm_idx++) {
148 * devnames should be present if:
149 * * The element is of type Device Slot or Array Device Slot
150 * * It isn't an Overall Element
151 * * The element's status is not "Not Installed"
153 encioc_elm_status_t e_status;
154 encioc_elm_devnames_t elmdn;
156 memset(&e_status, 0, sizeof(e_status));
157 e_status.elm_idx = elm_idx;
158 r = ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t)&e_status);
159 ATF_REQUIRE_EQ(r, 0);
161 memset(&elmdn, 0, sizeof(elmdn));
162 elmdn.elm_idx = elm_idx;
163 elmdn.elm_names_size = namesize;
164 elmdn.elm_devnames = namebuf;
166 r = ioctl(fd, ENCIOC_GETELMDEVNAMES, (caddr_t) &elmdn);
167 if (e_status.cstat[0] != SES_OBJSTAT_UNSUPPORTED &&
168 e_status.cstat[0] != SES_OBJSTAT_NOTINSTALLED &&
169 (map[elm_idx].elm_type == ELMTYP_DEVICE ||
170 map[elm_idx].elm_type == ELMTYP_ARRAY_DEV))
172 ATF_CHECK_EQ_MSG(r, 0, "devnames not found. This could be due to a buggy ses driver, buggy ses controller, dead HDD, or an ATA HDD in a SAS slot");
179 int da = 0, ada = 0, pass = 0, nvd = 0;
180 int nvme = 0, unknown = 0;
182 while(elmdn.elm_devnames[z] != '\0') {
186 if (elmdn.elm_devnames[z] == ',')
187 z++; /* Skip the comma */
188 s = elmdn.elm_devnames + z;
189 e = strcspn(s, "0123456789");
190 if (0 == strncmp("da", s, e))
192 else if (0 == strncmp("ada", s, e))
194 else if (0 == strncmp("pass", s, e))
196 else if (0 == strncmp("nvd", s, e))
198 else if (0 == strncmp("nvme", s, e))
202 z += strcspn(elmdn.elm_devnames + z, ",");
204 /* There should be one pass dev for each non-pass dev */
205 ATF_CHECK_EQ(pass, da + ada + nvd + nvme);
206 ATF_CHECK_EQ_MSG(0, unknown,
207 "Unknown device names %s", elmdn.elm_devnames);
216 ATF_TC(getelmdevnames);
217 ATF_TC_HEAD(getelmdevnames, tc)
219 atf_tc_set_md_var(tc, "descr",
220 "Compare ENCIOC_GETELMDEVNAMES's output to sg3_utils'");
221 atf_tc_set_md_var(tc, "require.user", "root");
222 atf_tc_set_md_var(tc, "require.progs", "sg_ses");
224 ATF_TC_BODY(getelmdevnames, tc)
227 atf_tc_skip("No ses devices found");
228 for_each_ses_dev(do_getelmdevnames, O_RDONLY);
232 elm_type_name2int(const char *name) {
233 const char *elm_type_names[] = ELM_TYPE_NAMES;
236 for (i = 0; i <= ELMTYP_LAST; i++) {
237 /* sg_ses uses different case than ses(4) */
238 if (0 == strcasecmp(name, elm_type_names[i]))
244 static bool do_getelmmap(const char *devname, int fd) {
245 encioc_element_t *map;
249 unsigned elm_idx = 0;
250 unsigned nobj, subenc_id;
253 r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
254 ATF_REQUIRE_EQ(r, 0);
256 map = calloc(nobj, sizeof(encioc_element_t));
257 ATF_REQUIRE(map != NULL);
258 r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
259 ATF_REQUIRE_EQ(r, 0);
261 snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
262 pipe = popen(cmd, "r");
263 ATF_REQUIRE(pipe != NULL);
264 while(NULL != fgets(line, sizeof(line), pipe)) {
265 char elm_type_name[80];
269 " Element type: %[a-zA-Z0-9_ /], subenclosure id: %d",
270 elm_type_name, &subenc_id);
272 elm_type = elm_type_name2int(elm_type_name);
276 " Element type: vendor specific [0x%x], subenclosure id: %d",
277 &elm_type, &subenc_id);
281 r = sscanf(line, " number of possible elements: %d",
286 /* Skip the Overall elements */
288 for (i = 0; i < num_elm; i++, elm_idx++) {
289 ATF_CHECK_EQ(map[elm_idx].elm_idx, elm_idx);
290 ATF_CHECK_EQ(map[elm_idx].elm_subenc_id, subenc_id);
291 ATF_CHECK_EQ((int)map[elm_idx].elm_type, elm_type);
298 /* Probably an SGPIO device */
301 ATF_CHECK_EQ_MSG(nobj, elm_idx,
302 "Did not find the expected number of element "
303 "descriptors in sg_ses's output");
309 ATF_TC_HEAD(getelmmap, tc)
311 atf_tc_set_md_var(tc, "descr",
312 "Compare ENCIOC_GETELMMAP's output to sg3_utils'");
313 atf_tc_set_md_var(tc, "require.user", "root");
314 atf_tc_set_md_var(tc, "require.progs", "sg_ses");
316 ATF_TC_BODY(getelmmap, tc)
319 atf_tc_skip("No ses devices found");
320 for_each_ses_dev(do_getelmmap, O_RDONLY);
323 static bool do_getelmstat(const char *devname, int fd) {
324 encioc_element_t *map;
328 elm_type_t last_elm_type = -1;
330 r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
331 ATF_REQUIRE_EQ(r, 0);
333 map = calloc(nobj, sizeof(encioc_element_t));
334 ATF_REQUIRE(map != NULL);
335 r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
337 for (elm_idx = 0; elm_idx < nobj; elm_subidx++, elm_idx++) {
338 encioc_elm_status_t e_status;
344 if (last_elm_type != map[elm_idx].elm_type)
346 last_elm_type = map[elm_idx].elm_type;
348 snprintf(cmd, sizeof(cmd),
349 "sg_ses -Hp2 --index=_%d,%d --get=0:7:32 %s",
350 map[elm_idx].elm_type, elm_subidx, devname);
351 pipe = popen(cmd, "r");
352 ATF_REQUIRE(pipe != NULL);
353 r = fscanf(pipe, "0x%x", &status);
356 /* Probably an SGPIO device */
360 ATF_REQUIRE_EQ(r, 1);
362 memset(&e_status, 0, sizeof(e_status));
363 e_status.elm_idx = map[elm_idx].elm_idx;
364 r = ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t)&e_status);
365 ATF_REQUIRE_EQ(r, 0);
367 // Compare the common status field
368 ATF_CHECK_EQ(e_status.cstat[0], status >> 24);
370 * Ignore the other fields, because some have values that can
371 * change frequently (voltage, temperature, etc)
380 ATF_TC_HEAD(getelmstat, tc)
382 atf_tc_set_md_var(tc, "descr",
383 "Compare ENCIOC_GETELMSTAT's output to sg3_utils'");
384 atf_tc_set_md_var(tc, "require.user", "root");
385 atf_tc_set_md_var(tc, "require.progs", "sg_ses");
387 ATF_TC_BODY(getelmstat, tc)
390 atf_tc_skip("No ses devices found");
391 for_each_ses_dev(do_getelmstat, O_RDONLY);
394 static bool do_getencid(const char *devname, int fd) {
395 encioc_string_t stri;
403 snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
404 pipe = popen(cmd, "r");
405 ATF_REQUIRE(pipe != NULL);
407 while(NULL != fgets(line, sizeof(line), pipe)) {
408 const char *f = " enclosure logical identifier (hex): %s";
410 if (1 == fscanf(pipe, f, sg_encid))
413 sg_ses_r = pclose(pipe);
415 stri.bufsiz = sizeof(encid);
416 stri.buf = &encid[0];
417 r = ioctl(fd, ENCIOC_GETENCID, (caddr_t) &stri);
418 ATF_REQUIRE_EQ(r, 0);
420 ATF_REQUIRE(sg_encid[0] != '\0');
421 ATF_CHECK_STREQ(sg_encid, (char*)stri.buf);
424 /* Probably SGPIO; sg_ses unsupported */
430 ATF_TC_HEAD(getencid, tc)
432 atf_tc_set_md_var(tc, "descr",
433 "Compare ENCIOC_GETENCID's output to sg3_utils'");
434 atf_tc_set_md_var(tc, "require.user", "root");
435 atf_tc_set_md_var(tc, "require.progs", "sg_ses");
437 ATF_TC_BODY(getencid, tc)
440 atf_tc_skip("No ses devices found");
441 for_each_ses_dev(do_getencid, O_RDONLY);
444 static bool do_getencname(const char *devname, int fd) {
445 encioc_string_t stri;
452 snprintf(cmd, sizeof(cmd), "sg_inq -o %s | awk '"
453 "/Vendor identification/ {vi=$NF} "
454 "/Product identification/ {pi=$NF} "
455 "/Product revision level/ {prl=$NF} "
456 "END {printf(vi \" \" pi \" \" prl)}'", devname);
457 pipe = popen(cmd, "r");
458 ATF_REQUIRE(pipe != NULL);
459 ATF_REQUIRE(NULL != fgets(line, sizeof(line), pipe));
462 stri.bufsiz = sizeof(encname);
463 stri.buf = &encname[0];
464 r = ioctl(fd, ENCIOC_GETENCNAME, (caddr_t) &stri);
465 ATF_REQUIRE_EQ(r, 0);
466 if (strlen(line) < 3) {
467 // Probably an SGPIO device, INQUIRY unsupported
470 ATF_CHECK_STREQ(line, (char*)stri.buf);
476 ATF_TC_HEAD(getencname, tc)
478 atf_tc_set_md_var(tc, "descr",
479 "Compare ENCIOC_GETENCNAME's output to sg3_utils'");
480 atf_tc_set_md_var(tc, "require.user", "root");
481 atf_tc_set_md_var(tc, "require.progs", "sg_inq");
483 ATF_TC_BODY(getencname, tc)
486 atf_tc_skip("No ses devices found");
487 for_each_ses_dev(do_getencname, O_RDONLY);
490 static bool do_getencstat(const char *devname, int fd) {
493 unsigned char e, estat, invop, info, noncrit, crit, unrecov;
496 snprintf(cmd, sizeof(cmd), "sg_ses -p2 %s "
499 pipe = popen(cmd, "r");
500 ATF_REQUIRE(pipe != NULL);
502 " INVOP=%hhu, INFO=%hhu, NON-CRIT=%hhu, CRIT=%hhu, UNRECOV=%hhu",
503 &invop, &info, &noncrit, &crit, &unrecov);
506 /* Probably on SGPIO device */
509 r = ioctl(fd, ENCIOC_GETENCSTAT, (caddr_t) &estat);
510 ATF_REQUIRE_EQ(r, 0);
511 /* Exclude the info bit because it changes frequently */
512 e = (invop << 4) | (noncrit << 2) | (crit << 1) | unrecov;
513 ATF_CHECK_EQ(estat & ~0x08, e);
519 ATF_TC_HEAD(getencstat, tc)
521 atf_tc_set_md_var(tc, "descr",
522 "Compare ENCIOC_GETENCSTAT's output to sg3_utils'");
523 atf_tc_set_md_var(tc, "require.user", "root");
524 atf_tc_set_md_var(tc, "require.progs", "sg_ses");
526 ATF_TC_BODY(getencstat, tc)
529 atf_tc_skip("No ses devices found");
530 for_each_ses_dev(do_getencstat, O_RDONLY);
533 static bool do_getnelm(const char *devname, int fd) {
537 unsigned nobj, expected = 0;
540 snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
541 pipe = popen(cmd, "r");
542 ATF_REQUIRE(pipe != NULL);
544 while(NULL != fgets(line, sizeof(line), pipe)) {
547 if (1 == fscanf(pipe, " number of possible elements: %u",
550 expected += 1 + nelm; // +1 for the Overall element
553 sg_ses_r = pclose(pipe);
555 r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
556 ATF_REQUIRE_EQ(r, 0);
558 ATF_CHECK_EQ(expected, nobj);
561 /* Probably SGPIO, sg_ses unsupported */
567 ATF_TC_HEAD(getnelm, tc)
569 atf_tc_set_md_var(tc, "descr",
570 "Compare ENCIOC_GETNELM's output to sg3_utils'");
571 atf_tc_set_md_var(tc, "require.user", "root");
572 atf_tc_set_md_var(tc, "require.progs", "sg_ses");
574 ATF_TC_BODY(getnelm, tc)
577 atf_tc_skip("No ses devices found");
578 for_each_ses_dev(do_getnelm, O_RDONLY);
581 static bool do_getstring(const char *devname, int fd) {
584 char *sg_ses_buf, *ses_buf;
585 ssize_t sg_ses_count;
586 encioc_string_t str_in;
589 sg_ses_buf = malloc(65535);
590 ATF_REQUIRE(sg_ses_buf != NULL);
591 ses_buf = malloc(65535);
592 ATF_REQUIRE(ses_buf != NULL);
594 snprintf(cmd, sizeof(cmd), "sg_ses -p4 -rr %s", devname);
595 pipe = popen(cmd, "r");
596 ATF_REQUIRE(pipe != NULL);
597 sg_ses_count = fread(sg_ses_buf, 1, 65535, pipe);
600 // This SES device does not support the STRINGIN diagnostic page
603 ATF_REQUIRE(sg_ses_count > 0);
605 str_in.bufsiz = 65535;
606 str_in.buf = ses_buf;
607 r = ioctl(fd, ENCIOC_GETSTRING, (caddr_t) &str_in);
608 ATF_REQUIRE_EQ(r, 0);
609 ATF_CHECK_EQ(sg_ses_count, (ssize_t)str_in.bufsiz);
610 ATF_CHECK_EQ(0, memcmp(sg_ses_buf, ses_buf, str_in.bufsiz));
619 ATF_TC_HEAD(getstring, tc)
621 atf_tc_set_md_var(tc, "descr",
622 "Compare ENCIOC_GETSTRING's output to sg3_utils'");
623 atf_tc_set_md_var(tc, "require.user", "root");
624 atf_tc_set_md_var(tc, "require.progs", "sg_ses");
626 ATF_TC_BODY(getstring, tc)
629 atf_tc_skip("No ses devices found");
630 atf_tc_expect_fail("Bug 258188 ENCIO_GETSTRING does not set the string's returned size");
631 for_each_ses_dev(do_getstring, O_RDWR);
640 * * ENCIOC_GETTEXT because it was never implemented
643 ATF_TP_ADD_TC(tp, getelmdesc);
644 ATF_TP_ADD_TC(tp, getelmdevnames);
645 ATF_TP_ADD_TC(tp, getelmmap);
646 ATF_TP_ADD_TC(tp, getelmstat);
647 ATF_TP_ADD_TC(tp, getencid);
648 ATF_TP_ADD_TC(tp, getencname);
649 ATF_TP_ADD_TC(tp, getencstat);
650 ATF_TP_ADD_TC(tp, getnelm);
651 ATF_TP_ADD_TC(tp, getstring);
653 return (atf_no_error());