xref: /freebsd/tests/sys/ses/nondestructive.c (revision 1323ec571215a77ddd21294f0871979d5ad6b992)
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 do_getelmdesc(const char *devname, int fd) {
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 do_getelmdevnames(const char *devname __unused, int fd) {
129 	encioc_element_t *map;
130 	unsigned nobj;
131 	const size_t namesize = 128;
132 	int r;
133 	char *namebuf;
134 	unsigned elm_idx;
135 
136 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
137 	ATF_REQUIRE_EQ(r, 0);
138 
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);
145 
146 	for (elm_idx = 0; elm_idx < nobj; elm_idx++) {
147 		/*
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"
152 		 */
153 		encioc_elm_status_t e_status;
154 		encioc_elm_devnames_t elmdn;
155 
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);
160 
161 		memset(&elmdn, 0, sizeof(elmdn));
162 		elmdn.elm_idx = elm_idx;
163 		elmdn.elm_names_size = namesize;
164 		elmdn.elm_devnames = namebuf;
165 		namebuf[0] = '\0';
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))
171 		{
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");
173 		} else {
174 			ATF_CHECK(r != 0);
175 		}
176 
177 		if (r == 0) {
178 			size_t z = 0;
179 			int da = 0, ada = 0, pass = 0, nvd = 0;
180 			int nvme = 0, unknown = 0;
181 
182 			while(elmdn.elm_devnames[z] != '\0') {
183 				size_t e;
184 				char *s;
185 
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))
191 					da++;
192 				else if (0 == strncmp("ada", s, e))
193 					ada++;
194 				else if (0 == strncmp("pass", s, e))
195 					pass++;
196 				else if (0 == strncmp("nvd", s, e))
197 					nvd++;
198 				else if (0 == strncmp("nvme", s, e))
199 					nvme++;
200 				else
201 					unknown++;
202 				z += strcspn(elmdn.elm_devnames + z, ",");
203 			}
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);
208 		}
209 	}
210 	free(map);
211 	free(namebuf);
212 
213 	return (true);
214 }
215 
216 ATF_TC(getelmdevnames);
217 ATF_TC_HEAD(getelmdevnames, tc)
218 {
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");
223 }
224 ATF_TC_BODY(getelmdevnames, tc)
225 {
226 	if (!has_ses())
227 		atf_tc_skip("No ses devices found");
228 	for_each_ses_dev(do_getelmdevnames, O_RDONLY);
229 }
230 
231 static int
232 elm_type_name2int(const char *name) {
233 	const char *elm_type_names[] = ELM_TYPE_NAMES;
234 	int i;
235 
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]))
239 			return i;
240 	}
241 	return (-1);
242 }
243 
244 static bool do_getelmmap(const char *devname, int fd) {
245 	encioc_element_t *map;
246 	FILE *pipe;
247 	char cmd[256];
248 	char line[256];
249 	unsigned elm_idx = 0;
250 	unsigned nobj, subenc_id;
251 	int r, elm_type;
252 
253 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
254 	ATF_REQUIRE_EQ(r, 0);
255 
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);
260 
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];
266 		int i, num_elm;
267 
268 		r = sscanf(line,
269 		    "    Element type: %[a-zA-Z0-9_ /], subenclosure id: %d",
270 		    elm_type_name, &subenc_id);
271 		if (r == 2) {
272 			elm_type = elm_type_name2int(elm_type_name);
273 			continue;
274 		} else {
275 			r = sscanf(line,
276 			    "    Element type: vendor specific [0x%x], subenclosure id: %d",
277 			    &elm_type, &subenc_id);
278 			if (r == 2)
279 				continue;
280 		}
281 		r = sscanf(line, "      number of possible elements: %d",
282 		    &num_elm);
283 		if (r != 1)
284 			continue;
285 
286 		/* Skip the Overall elements */
287 		elm_idx++;
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);
292 		}
293 	}
294 
295 	free(map);
296 	r = pclose(pipe);
297 	if (r != 0) {
298 		/* Probably an SGPIO device */
299 		return (false);
300 	} else {
301 		ATF_CHECK_EQ_MSG(nobj, elm_idx,
302 				"Did not find the expected number of element "
303 				"descriptors in sg_ses's output");
304 		return (true);
305 	}
306 }
307 
308 ATF_TC(getelmmap);
309 ATF_TC_HEAD(getelmmap, tc)
310 {
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");
315 }
316 ATF_TC_BODY(getelmmap, tc)
317 {
318 	if (!has_ses())
319 		atf_tc_skip("No ses devices found");
320 	for_each_ses_dev(do_getelmmap, O_RDONLY);
321 }
322 
323 static bool do_getelmstat(const char *devname, int fd) {
324 	encioc_element_t *map;
325 	unsigned elm_idx;
326 	unsigned nobj;
327 	int r, elm_subidx;
328 	elm_type_t last_elm_type = -1;
329 
330 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
331 	ATF_REQUIRE_EQ(r, 0);
332 
333 	map = calloc(nobj, sizeof(encioc_element_t));
334 	ATF_REQUIRE(map != NULL);
335 	r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
336 
337 	for (elm_idx = 0; elm_idx < nobj; elm_subidx++, elm_idx++) {
338 		encioc_elm_status_t e_status;
339 		FILE *pipe;
340 		char cmd[256];
341 		uint32_t status;
342 		int pr;
343 
344 		if (last_elm_type != map[elm_idx].elm_type)
345 			elm_subidx = -1;
346 		last_elm_type = map[elm_idx].elm_type;
347 
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);
354 		pr = pclose(pipe);
355 		if (pr != 0) {
356 			/* Probably an SGPIO device */
357 			free(map);
358 			return (false);
359 		}
360 		ATF_REQUIRE_EQ(r, 1);
361 
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);
366 
367 		// Compare the common status field
368 		ATF_CHECK_EQ(e_status.cstat[0], status >> 24);
369 		/*
370 		 * Ignore the other fields, because some have values that can
371 		 * change frequently (voltage, temperature, etc)
372 		 */
373 	}
374 	free(map);
375 
376 	return (true);
377 }
378 
379 ATF_TC(getelmstat);
380 ATF_TC_HEAD(getelmstat, tc)
381 {
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");
386 }
387 ATF_TC_BODY(getelmstat, tc)
388 {
389 	if (!has_ses())
390 		atf_tc_skip("No ses devices found");
391 	for_each_ses_dev(do_getelmstat, O_RDONLY);
392 }
393 
394 static bool do_getencid(const char *devname, int fd) {
395 	encioc_string_t stri;
396 	FILE *pipe;
397 	char cmd[256];
398 	char encid[32];
399 	char line[256];
400 	char sg_encid[32];
401 	int r, sg_ses_r;
402 
403 	snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
404 	pipe = popen(cmd, "r");
405 	ATF_REQUIRE(pipe != NULL);
406 	sg_encid[0] = '\0';
407 	while(NULL != fgets(line, sizeof(line), pipe)) {
408 		const char *f = "      enclosure logical identifier (hex): %s";
409 
410 		if (1 == fscanf(pipe, f, sg_encid))
411 			break;
412 	}
413 	sg_ses_r = pclose(pipe);
414 
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);
419 	if (sg_ses_r == 0) {
420 		ATF_REQUIRE(sg_encid[0] != '\0');
421 		ATF_CHECK_STREQ(sg_encid, (char*)stri.buf);
422 		return (true);
423 	} else {
424 		/* Probably SGPIO; sg_ses unsupported */
425 		return (false);
426 	}
427 }
428 
429 ATF_TC(getencid);
430 ATF_TC_HEAD(getencid, tc)
431 {
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");
436 }
437 ATF_TC_BODY(getencid, tc)
438 {
439 	if (!has_ses())
440 		atf_tc_skip("No ses devices found");
441 	for_each_ses_dev(do_getencid, O_RDONLY);
442 }
443 
444 static bool do_getencname(const char *devname, int fd) {
445 	encioc_string_t stri;
446 	FILE *pipe;
447 	char cmd[256];
448 	char encname[32];
449 	char line[256];
450 	int r;
451 
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));
460 	pclose(pipe);
461 
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
468 		return (false);
469 	} else {
470 		ATF_CHECK_STREQ(line, (char*)stri.buf);
471 		return (true);
472 	}
473 }
474 
475 ATF_TC(getencname);
476 ATF_TC_HEAD(getencname, tc)
477 {
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");
482 }
483 ATF_TC_BODY(getencname, tc)
484 {
485 	if (!has_ses())
486 		atf_tc_skip("No ses devices found");
487 	for_each_ses_dev(do_getencname, O_RDONLY);
488 }
489 
490 static bool do_getencstat(const char *devname, int fd) {
491 	FILE *pipe;
492 	char cmd[256];
493 	unsigned char e, estat, invop, info, noncrit, crit, unrecov;
494 	int r;
495 
496 	snprintf(cmd, sizeof(cmd), "sg_ses -p2 %s "
497 		"| grep 'INVOP='",
498 		devname);
499 	pipe = popen(cmd, "r");
500 	ATF_REQUIRE(pipe != NULL);
501 	r = fscanf(pipe,
502 	    "  INVOP=%hhu, INFO=%hhu, NON-CRIT=%hhu, CRIT=%hhu, UNRECOV=%hhu",
503 	    &invop, &info, &noncrit, &crit, &unrecov);
504 	pclose(pipe);
505 	if (r != 5) {
506 		/* Probably on SGPIO device */
507 		return (false);
508 	} else {
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);
514 		return (true);
515 	}
516 }
517 
518 ATF_TC(getencstat);
519 ATF_TC_HEAD(getencstat, tc)
520 {
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");
525 }
526 ATF_TC_BODY(getencstat, tc)
527 {
528 	if (!has_ses())
529 		atf_tc_skip("No ses devices found");
530 	for_each_ses_dev(do_getencstat, O_RDONLY);
531 }
532 
533 static bool do_getnelm(const char *devname, int fd) {
534 	FILE *pipe;
535 	char cmd[256];
536 	char line[256];
537 	unsigned nobj, expected = 0;
538 	int r, sg_ses_r;
539 
540 	snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
541 	pipe = popen(cmd, "r");
542 	ATF_REQUIRE(pipe != NULL);
543 
544 	while(NULL != fgets(line, sizeof(line), pipe)) {
545 		unsigned nelm;
546 
547 		if (1 == fscanf(pipe, "      number of possible elements: %u",
548 		    &nelm))
549 		{
550 			expected += 1 + nelm;	// +1 for the Overall element
551 		}
552 	}
553 	sg_ses_r = pclose(pipe);
554 
555 	r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
556 	ATF_REQUIRE_EQ(r, 0);
557 	if (sg_ses_r == 0) {
558 		ATF_CHECK_EQ(expected, nobj);
559 		return (true);
560 	} else {
561 		/* Probably SGPIO, sg_ses unsupported */
562 		return (false);
563 	}
564 }
565 
566 ATF_TC(getnelm);
567 ATF_TC_HEAD(getnelm, tc)
568 {
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");
573 }
574 ATF_TC_BODY(getnelm, tc)
575 {
576 	if (!has_ses())
577 		atf_tc_skip("No ses devices found");
578 	for_each_ses_dev(do_getnelm, O_RDONLY);
579 }
580 
581 static bool do_getstring(const char *devname, int fd) {
582 	FILE *pipe;
583 	char cmd[256];
584 	char *sg_ses_buf, *ses_buf;
585 	ssize_t sg_ses_count;
586 	encioc_string_t str_in;
587 	int r;
588 
589 	sg_ses_buf = malloc(65535);
590 	ATF_REQUIRE(sg_ses_buf != NULL);
591 	ses_buf = malloc(65535);
592 	ATF_REQUIRE(ses_buf != NULL);
593 
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);
598 	r = pclose(pipe);
599 	if (r != 0) {
600 		// This SES device does not support the STRINGIN diagnostic page
601 		return (false);
602 	}
603 	ATF_REQUIRE(sg_ses_count > 0);
604 
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));
611 
612 	free(ses_buf);
613 	free(sg_ses_buf);
614 
615 	return (true);
616 }
617 
618 ATF_TC(getstring);
619 ATF_TC_HEAD(getstring, tc)
620 {
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");
625 }
626 ATF_TC_BODY(getstring, tc)
627 {
628 	if (!has_ses())
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);
632 }
633 
634 ATF_TP_ADD_TCS(tp)
635 {
636 
637 	/*
638 	 * Untested ioctls:
639 	 *
640 	 * * ENCIOC_GETTEXT because it was never implemented
641 	 *
642 	 */
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);
652 
653 	return (atf_no_error());
654 }
655