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