xref: /freebsd/tests/sys/ses/nondestructive.c (revision 4d846d26)
1 /*-
2  * Copyright (C) 2021 Axcient, Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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
23  * SUCH DAMAGE.
24  *
25  * $FreeBSD$
26  */
27 
28 /* Basic smoke test of the ioctl interface */
29 
30 #include <sys/types.h>
31 #include <sys/ioctl.h>
32 
33 #include <atf-c.h>
34 #include <fcntl.h>
35 #include <glob.h>
36 #include <regex.h>
37 #include <stdint.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 
41 #include <cam/scsi/scsi_enc.h>
42 
43 #include "common.h"
44 
45 static bool
46 do_getelmdesc(const char *devname, int fd)
47 {
48 	regex_t re;
49 	FILE *pipe;
50 	char cmd[256];
51 	char line[256];
52 	char *actual;
53 	unsigned nobj;
54 	unsigned elm_idx = 0;
55 	int r;
56 
57 	actual = calloc(UINT16_MAX, sizeof(char));
58 	ATF_REQUIRE(actual != NULL);
59 	r = regcomp(&re, "(Overall|Element [0-9]+) descriptor: ", REG_EXTENDED);
60 	ATF_REQUIRE_EQ(r, 0);
61 
62 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
63 	ATF_REQUIRE_EQ(r, 0);
64 
65 	snprintf(cmd, sizeof(cmd), "sg_ses -p7 %s", devname);
66 	pipe = popen(cmd, "r");
67 	ATF_REQUIRE(pipe != NULL);
68 	while(NULL != fgets(line, sizeof(line), pipe)) {
69 		regmatch_t matches[1];
70 		encioc_elm_desc_t e_desc;
71 		char *expected;
72 		size_t elen;
73 
74 		if (regexec(&re, line, 1, matches, 0) == REG_NOMATCH) {
75 			continue;
76 		}
77 
78 		expected = &line[matches[0].rm_eo];
79 		/* Remove trailing newline */
80 		elen = strnlen(expected, sizeof(line) - matches[0].rm_eo);
81 		expected[elen - 1] = '\0';
82 		/*
83 		 * Zero the result string.  XXX we wouldn't have to do this if
84 		 * the kernel would nul-terminate the result.
85 		 */
86 		memset(actual, 0, UINT16_MAX);
87 		e_desc.elm_idx = elm_idx;
88 		e_desc.elm_desc_len = UINT16_MAX;
89 		e_desc.elm_desc_str = actual;
90 		r = ioctl(fd, ENCIOC_GETELMDESC, (caddr_t) &e_desc);
91 		ATF_REQUIRE_EQ(r, 0);
92 		if (0 == strcmp("<empty>", expected)) {
93 			/* sg_ses replaces "" with "<empty>" */
94 			ATF_CHECK_STREQ("", actual);
95 		} else
96 			ATF_CHECK_STREQ(expected, actual);
97 		elm_idx++;
98 	}
99 
100 	r = pclose(pipe);
101 	regfree(&re);
102 	free(actual);
103 	if (r != 0) {
104 		/* Probably an SGPIO device */
105 
106 		return (false);
107 	} else {
108 		ATF_CHECK_EQ_MSG(nobj, elm_idx,
109 				"Did not find the expected number of element "
110 				"descriptors in sg_ses's output");
111 		return (true);
112 	}
113 }
114 
115 ATF_TC(getelmdesc);
116 ATF_TC_HEAD(getelmdesc, tc)
117 {
118 	atf_tc_set_md_var(tc, "descr",
119 	    "Compare ENCIOC_GETELMDESC's output to sg3_utils'");
120 	atf_tc_set_md_var(tc, "require.user", "root");
121 	atf_tc_set_md_var(tc, "require.progs", "sg_ses");
122 }
123 ATF_TC_BODY(getelmdesc, tc)
124 {
125 	if (!has_ses())
126 		atf_tc_skip("No ses devices found");
127 	for_each_ses_dev(do_getelmdesc, O_RDONLY);
128 }
129 
130 static bool
131 do_getelmdevnames(const char *devname __unused, int fd)
132 {
133 	encioc_element_t *map;
134 	unsigned nobj;
135 	const size_t namesize = 128;
136 	int r, status;
137 	char *namebuf;
138 	unsigned elm_idx;
139 
140 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
141 	ATF_REQUIRE_EQ(r, 0);
142 
143 	namebuf = calloc(namesize, sizeof(char));
144 	ATF_REQUIRE(namebuf != NULL);
145 	map = calloc(nobj, sizeof(encioc_element_t));
146 	ATF_REQUIRE(map != NULL);
147 	r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
148 	ATF_REQUIRE_EQ(r, 0);
149 
150 	for (elm_idx = 0; elm_idx < nobj; elm_idx++) {
151 		/*
152 		 * devnames should be present if:
153 		 * * The element is of type Device Slot or Array Device Slot
154 		 * * It isn't an Overall Element
155 		 * * The element's status is not "Not Installed"
156 		 */
157 		encioc_elm_status_t e_status;
158 		encioc_elm_devnames_t elmdn;
159 
160 		memset(&e_status, 0, sizeof(e_status));
161 		e_status.elm_idx = elm_idx;
162 		r = ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t)&e_status);
163 		ATF_REQUIRE_EQ(r, 0);
164 
165 		memset(&elmdn, 0, sizeof(elmdn));
166 		elmdn.elm_idx = elm_idx;
167 		elmdn.elm_names_size = namesize;
168 		elmdn.elm_devnames = namebuf;
169 		namebuf[0] = '\0';
170 		r = ioctl(fd, ENCIOC_GETELMDEVNAMES, (caddr_t) &elmdn);
171 		status = ses_status_common_get_element_status_code(
172 			(struct ses_status_common*)&e_status.cstat[0]);
173 		if (status != SES_OBJSTAT_UNSUPPORTED &&
174 		    status != SES_OBJSTAT_NOTINSTALLED &&
175 		    (map[elm_idx].elm_type == ELMTYP_DEVICE ||
176 		     map[elm_idx].elm_type == ELMTYP_ARRAY_DEV))
177 		{
178 			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 		} else {
180 			ATF_CHECK(r != 0);
181 		}
182 
183 		if (r == 0) {
184 			size_t z = 0;
185 			int da = 0, ada = 0, pass = 0, nda = 0, unknown = 0;
186 
187 			while(elmdn.elm_devnames[z] != '\0') {
188 				size_t e;
189 				char *s;
190 
191 				if (elmdn.elm_devnames[z] == ',')
192 					z++;	/* Skip the comma */
193 				s = elmdn.elm_devnames + z;
194 				e = strcspn(s, "0123456789");
195 				if (0 == strncmp("da", s, e))
196 					da++;
197 				else if (0 == strncmp("ada", s, e))
198 					ada++;
199 				else if (0 == strncmp("pass", s, e))
200 					pass++;
201 				else if (0 == strncmp("nda", s, e))
202 					nda++;
203 				else
204 					unknown++;
205 				z += strcspn(elmdn.elm_devnames + z, ",");
206 			}
207 			/* There should be one pass dev for each non-pass dev */
208 			ATF_CHECK_EQ(pass, da + ada + nda);
209 			ATF_CHECK_EQ_MSG(0, unknown,
210 			    "Unknown device names %s", elmdn.elm_devnames);
211 		}
212 	}
213 	free(map);
214 	free(namebuf);
215 
216 	return (true);
217 }
218 
219 ATF_TC(getelmdevnames);
220 ATF_TC_HEAD(getelmdevnames, tc)
221 {
222 	atf_tc_set_md_var(tc, "descr",
223 	    "Compare ENCIOC_GETELMDEVNAMES's output to sg3_utils'");
224 	atf_tc_set_md_var(tc, "require.user", "root");
225 	atf_tc_set_md_var(tc, "require.progs", "sg_ses");
226 }
227 ATF_TC_BODY(getelmdevnames, tc)
228 {
229 	if (!has_ses())
230 		atf_tc_skip("No ses devices found");
231 	for_each_ses_dev(do_getelmdevnames, O_RDONLY);
232 }
233 
234 static int
235 elm_type_name2int(const char *name)
236 {
237 	const char *elm_type_names[] = ELM_TYPE_NAMES;
238 	int i;
239 
240 	for (i = 0; i <= ELMTYP_LAST; i++) {
241 		/* sg_ses uses different case than ses(4) */
242 		if (0 == strcasecmp(name, elm_type_names[i]))
243 			return i;
244 	}
245 	return (-1);
246 }
247 
248 static bool
249 do_getelmmap(const char *devname, int fd)
250 {
251 	encioc_element_t *map;
252 	FILE *pipe;
253 	char cmd[256];
254 	char line[256];
255 	unsigned elm_idx = 0;
256 	unsigned nobj, subenc_id;
257 	int r, elm_type;
258 
259 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
260 	ATF_REQUIRE_EQ(r, 0);
261 
262 	map = calloc(nobj, sizeof(encioc_element_t));
263 	ATF_REQUIRE(map != NULL);
264 	r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
265 	ATF_REQUIRE_EQ(r, 0);
266 
267 	snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
268 	pipe = popen(cmd, "r");
269 	ATF_REQUIRE(pipe != NULL);
270 	while(NULL != fgets(line, sizeof(line), pipe)) {
271 		char elm_type_name[80];
272 		int i, num_elm;
273 
274 		r = sscanf(line,
275 		    "    Element type: %[a-zA-Z0-9_ /], subenclosure id: %d",
276 		    elm_type_name, &subenc_id);
277 		if (r == 2) {
278 			elm_type = elm_type_name2int(elm_type_name);
279 			continue;
280 		} else {
281 			r = sscanf(line,
282 			    "    Element type: vendor specific [0x%x], subenclosure id: %d",
283 			    &elm_type, &subenc_id);
284 			if (r == 2)
285 				continue;
286 		}
287 		r = sscanf(line, "      number of possible elements: %d",
288 		    &num_elm);
289 		if (r != 1)
290 			continue;
291 
292 		/* Skip the Overall elements */
293 		elm_idx++;
294 		for (i = 0; i < num_elm; i++, elm_idx++) {
295 			ATF_CHECK_EQ(map[elm_idx].elm_idx, elm_idx);
296 			ATF_CHECK_EQ(map[elm_idx].elm_subenc_id, subenc_id);
297 			ATF_CHECK_EQ((int)map[elm_idx].elm_type, elm_type);
298 		}
299 	}
300 
301 	free(map);
302 	r = pclose(pipe);
303 	if (r != 0) {
304 		/* Probably an SGPIO device */
305 		return (false);
306 	} else {
307 		ATF_CHECK_EQ_MSG(nobj, elm_idx,
308 				"Did not find the expected number of element "
309 				"descriptors in sg_ses's output");
310 		return (true);
311 	}
312 }
313 
314 ATF_TC(getelmmap);
315 ATF_TC_HEAD(getelmmap, tc)
316 {
317 	atf_tc_set_md_var(tc, "descr",
318 	    "Compare ENCIOC_GETELMMAP's output to sg3_utils'");
319 	atf_tc_set_md_var(tc, "require.user", "root");
320 	atf_tc_set_md_var(tc, "require.progs", "sg_ses");
321 }
322 ATF_TC_BODY(getelmmap, tc)
323 {
324 	if (!has_ses())
325 		atf_tc_skip("No ses devices found");
326 	for_each_ses_dev(do_getelmmap, O_RDONLY);
327 }
328 
329 static bool
330 do_getelmstat(const char *devname, int fd)
331 {
332 	encioc_element_t *map;
333 	unsigned elm_idx;
334 	unsigned nobj;
335 	int r, elm_subidx;
336 	elm_type_t last_elm_type = -1;
337 
338 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
339 	ATF_REQUIRE_EQ(r, 0);
340 
341 	map = calloc(nobj, sizeof(encioc_element_t));
342 	ATF_REQUIRE(map != NULL);
343 	r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
344 
345 	for (elm_idx = 0; elm_idx < nobj; elm_subidx++, elm_idx++) {
346 		encioc_elm_status_t e_status;
347 		FILE *pipe;
348 		char cmd[256];
349 		uint32_t status;
350 		int pr;
351 
352 		if (last_elm_type != map[elm_idx].elm_type)
353 			elm_subidx = -1;
354 		last_elm_type = map[elm_idx].elm_type;
355 
356 		snprintf(cmd, sizeof(cmd),
357 		    "sg_ses -Hp2 --index=_%d,%d --get=0:7:32 %s",
358 		    map[elm_idx].elm_type, elm_subidx, devname);
359 		pipe = popen(cmd, "r");
360 		ATF_REQUIRE(pipe != NULL);
361 		r = fscanf(pipe, "0x%x", &status);
362 		pr = pclose(pipe);
363 		if (pr != 0) {
364 			/* Probably an SGPIO device */
365 			free(map);
366 			return (false);
367 		}
368 		ATF_REQUIRE_EQ(r, 1);
369 
370 		memset(&e_status, 0, sizeof(e_status));
371 		e_status.elm_idx = map[elm_idx].elm_idx;
372 		r = ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t)&e_status);
373 		ATF_REQUIRE_EQ(r, 0);
374 
375 		// Compare the common status field
376 		ATF_CHECK_EQ(e_status.cstat[0], status >> 24);
377 		/*
378 		 * Ignore the other fields, because some have values that can
379 		 * change frequently (voltage, temperature, etc)
380 		 */
381 	}
382 	free(map);
383 
384 	return (true);
385 }
386 
387 ATF_TC(getelmstat);
388 ATF_TC_HEAD(getelmstat, tc)
389 {
390 	atf_tc_set_md_var(tc, "descr",
391 	    "Compare ENCIOC_GETELMSTAT's output to sg3_utils'");
392 	atf_tc_set_md_var(tc, "require.user", "root");
393 	atf_tc_set_md_var(tc, "require.progs", "sg_ses");
394 }
395 ATF_TC_BODY(getelmstat, tc)
396 {
397 	if (!has_ses())
398 		atf_tc_skip("No ses devices found");
399 	for_each_ses_dev(do_getelmstat, O_RDONLY);
400 }
401 
402 static bool
403 do_getencid(const char *devname, int fd)
404 {
405 	encioc_string_t stri;
406 	FILE *pipe;
407 	char cmd[256];
408 	char encid[32];
409 	char line[256];
410 	char sg_encid[32];
411 	int r, sg_ses_r;
412 
413 	snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
414 	pipe = popen(cmd, "r");
415 	ATF_REQUIRE(pipe != NULL);
416 	sg_encid[0] = '\0';
417 	while(NULL != fgets(line, sizeof(line), pipe)) {
418 		const char *f = "      enclosure logical identifier (hex): %s";
419 
420 		if (1 == fscanf(pipe, f, sg_encid))
421 			break;
422 	}
423 	sg_ses_r = pclose(pipe);
424 
425 	stri.bufsiz = sizeof(encid);
426 	stri.buf = &encid[0];
427 	r = ioctl(fd, ENCIOC_GETENCID, (caddr_t) &stri);
428 	ATF_REQUIRE_EQ(r, 0);
429 	if (sg_ses_r == 0) {
430 		ATF_REQUIRE(sg_encid[0] != '\0');
431 		ATF_CHECK_STREQ(sg_encid, (char*)stri.buf);
432 		return (true);
433 	} else {
434 		/* Probably SGPIO; sg_ses unsupported */
435 		return (false);
436 	}
437 }
438 
439 ATF_TC(getencid);
440 ATF_TC_HEAD(getencid, tc)
441 {
442 	atf_tc_set_md_var(tc, "descr",
443 	    "Compare ENCIOC_GETENCID's output to sg3_utils'");
444 	atf_tc_set_md_var(tc, "require.user", "root");
445 	atf_tc_set_md_var(tc, "require.progs", "sg_ses");
446 }
447 ATF_TC_BODY(getencid, tc)
448 {
449 	if (!has_ses())
450 		atf_tc_skip("No ses devices found");
451 	for_each_ses_dev(do_getencid, O_RDONLY);
452 }
453 
454 static bool
455 do_getencname(const char *devname, int fd)
456 {
457 	encioc_string_t stri;
458 	FILE *pipe;
459 	char cmd[256];
460 	char encname[32];
461 	char line[256];
462 	int r;
463 
464 	snprintf(cmd, sizeof(cmd), "sg_inq -o %s | awk '"
465 		"/Vendor identification/ {vi=$NF} "
466 		"/Product identification/ {pi=$NF} "
467 		"/Product revision level/ {prl=$NF} "
468 		"END {printf(vi \" \" pi \" \" prl)}'", devname);
469 	pipe = popen(cmd, "r");
470 	ATF_REQUIRE(pipe != NULL);
471 	ATF_REQUIRE(NULL != fgets(line, sizeof(line), pipe));
472 	pclose(pipe);
473 
474 	stri.bufsiz = sizeof(encname);
475 	stri.buf = &encname[0];
476 	r = ioctl(fd, ENCIOC_GETENCNAME, (caddr_t) &stri);
477 	ATF_REQUIRE_EQ(r, 0);
478 	if (strlen(line) < 3) {
479 		// Probably an SGPIO device, INQUIRY unsupported
480 		return (false);
481 	} else {
482 		ATF_CHECK_STREQ(line, (char*)stri.buf);
483 		return (true);
484 	}
485 }
486 
487 ATF_TC(getencname);
488 ATF_TC_HEAD(getencname, tc)
489 {
490 	atf_tc_set_md_var(tc, "descr",
491 	    "Compare ENCIOC_GETENCNAME's output to sg3_utils'");
492 	atf_tc_set_md_var(tc, "require.user", "root");
493 	atf_tc_set_md_var(tc, "require.progs", "sg_inq");
494 }
495 ATF_TC_BODY(getencname, tc)
496 {
497 	if (!has_ses())
498 		atf_tc_skip("No ses devices found");
499 	for_each_ses_dev(do_getencname, O_RDONLY);
500 }
501 
502 static bool
503 do_getencstat(const char *devname, int fd)
504 {
505 	FILE *pipe;
506 	char cmd[256];
507 	unsigned char e, estat, invop, info, noncrit, crit, unrecov;
508 	int r;
509 
510 	snprintf(cmd, sizeof(cmd), "sg_ses -p2 %s "
511 		"| grep 'INVOP='",
512 		devname);
513 	pipe = popen(cmd, "r");
514 	ATF_REQUIRE(pipe != NULL);
515 	r = fscanf(pipe,
516 	    "  INVOP=%hhu, INFO=%hhu, NON-CRIT=%hhu, CRIT=%hhu, UNRECOV=%hhu",
517 	    &invop, &info, &noncrit, &crit, &unrecov);
518 	pclose(pipe);
519 	if (r != 5) {
520 		/* Probably on SGPIO device */
521 		return (false);
522 	} else {
523 		r = ioctl(fd, ENCIOC_GETENCSTAT, (caddr_t) &estat);
524 		ATF_REQUIRE_EQ(r, 0);
525 		/* Exclude the info bit because it changes frequently */
526 		e = (invop << 4) | (noncrit << 2) | (crit << 1) | unrecov;
527 		ATF_CHECK_EQ(estat & ~0x08, e);
528 		return (true);
529 	}
530 }
531 
532 ATF_TC(getencstat);
533 ATF_TC_HEAD(getencstat, tc)
534 {
535 	atf_tc_set_md_var(tc, "descr",
536 	    "Compare ENCIOC_GETENCSTAT's output to sg3_utils'");
537 	atf_tc_set_md_var(tc, "require.user", "root");
538 	atf_tc_set_md_var(tc, "require.progs", "sg_ses");
539 }
540 ATF_TC_BODY(getencstat, tc)
541 {
542 	if (!has_ses())
543 		atf_tc_skip("No ses devices found");
544 	for_each_ses_dev(do_getencstat, O_RDONLY);
545 }
546 
547 static bool
548 do_getnelm(const char *devname, int fd)
549 {
550 	FILE *pipe;
551 	char cmd[256];
552 	char line[256];
553 	unsigned nobj, expected = 0;
554 	int r, sg_ses_r;
555 
556 	snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
557 	pipe = popen(cmd, "r");
558 	ATF_REQUIRE(pipe != NULL);
559 
560 	while(NULL != fgets(line, sizeof(line), pipe)) {
561 		unsigned nelm;
562 
563 		if (1 == fscanf(pipe, "      number of possible elements: %u",
564 		    &nelm))
565 		{
566 			expected += 1 + nelm;	// +1 for the Overall element
567 		}
568 	}
569 	sg_ses_r = pclose(pipe);
570 
571 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
572 	ATF_REQUIRE_EQ(r, 0);
573 	if (sg_ses_r == 0) {
574 		ATF_CHECK_EQ(expected, nobj);
575 		return (true);
576 	} else {
577 		/* Probably SGPIO, sg_ses unsupported */
578 		return (false);
579 	}
580 }
581 
582 ATF_TC(getnelm);
583 ATF_TC_HEAD(getnelm, tc)
584 {
585 	atf_tc_set_md_var(tc, "descr",
586 	    "Compare ENCIOC_GETNELM's output to sg3_utils'");
587 	atf_tc_set_md_var(tc, "require.user", "root");
588 	atf_tc_set_md_var(tc, "require.progs", "sg_ses");
589 }
590 ATF_TC_BODY(getnelm, tc)
591 {
592 	if (!has_ses())
593 		atf_tc_skip("No ses devices found");
594 	for_each_ses_dev(do_getnelm, O_RDONLY);
595 }
596 
597 static bool
598 do_getstring(const char *devname, int fd)
599 {
600 	FILE *pipe;
601 	char cmd[256];
602 	char *sg_ses_buf, *ses_buf;
603 	ssize_t sg_ses_count;
604 	encioc_string_t str_in;
605 	int r;
606 
607 	sg_ses_buf = malloc(65535);
608 	ATF_REQUIRE(sg_ses_buf != NULL);
609 	ses_buf = malloc(65535);
610 	ATF_REQUIRE(ses_buf != NULL);
611 
612 	snprintf(cmd, sizeof(cmd), "sg_ses -p4 -rr %s", devname);
613 	pipe = popen(cmd, "r");
614 	ATF_REQUIRE(pipe != NULL);
615 	sg_ses_count = fread(sg_ses_buf, 1, 65535, pipe);
616 	r = pclose(pipe);
617 	if (r != 0) {
618 		// This SES device does not support the STRINGIN diagnostic page
619 		return (false);
620 	}
621 	ATF_REQUIRE(sg_ses_count > 0);
622 
623 	str_in.bufsiz = 65535;
624 	str_in.buf = ses_buf;
625 	r = ioctl(fd, ENCIOC_GETSTRING, (caddr_t) &str_in);
626 	ATF_REQUIRE_EQ(r, 0);
627 	ATF_CHECK_EQ(sg_ses_count, (ssize_t)str_in.bufsiz);
628 	ATF_CHECK_EQ(0, memcmp(sg_ses_buf, ses_buf, str_in.bufsiz));
629 
630 	free(ses_buf);
631 	free(sg_ses_buf);
632 
633 	return (true);
634 }
635 
636 ATF_TC(getstring);
637 ATF_TC_HEAD(getstring, tc)
638 {
639 	atf_tc_set_md_var(tc, "descr",
640 	    "Compare ENCIOC_GETSTRING's output to sg3_utils'");
641 	atf_tc_set_md_var(tc, "require.user", "root");
642 	atf_tc_set_md_var(tc, "require.progs", "sg_ses");
643 }
644 ATF_TC_BODY(getstring, tc)
645 {
646 	if (!has_ses())
647 		atf_tc_skip("No ses devices found");
648 	atf_tc_expect_fail("Bug 258188 ENCIO_GETSTRING does not set the string's returned size");
649 	for_each_ses_dev(do_getstring, O_RDWR);
650 }
651 
652 ATF_TP_ADD_TCS(tp)
653 {
654 
655 	/*
656 	 * Untested ioctls:
657 	 *
658 	 * * ENCIOC_GETTEXT because it was never implemented
659 	 *
660 	 */
661 	ATF_TP_ADD_TC(tp, getelmdesc);
662 	ATF_TP_ADD_TC(tp, getelmdevnames);
663 	ATF_TP_ADD_TC(tp, getelmmap);
664 	ATF_TP_ADD_TC(tp, getelmstat);
665 	ATF_TP_ADD_TC(tp, getencid);
666 	ATF_TP_ADD_TC(tp, getencname);
667 	ATF_TP_ADD_TC(tp, getencstat);
668 	ATF_TP_ADD_TC(tp, getnelm);
669 	ATF_TP_ADD_TC(tp, getstring);
670 
671 	return (atf_no_error());
672 }
673