The branch main has been updated by asomers:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=eea7c61590ae8968b3f1f609cf0bc8633222a94f

commit eea7c61590ae8968b3f1f609cf0bc8633222a94f
Author:     Alan Somers <asom...@freebsd.org>
AuthorDate: 2021-08-27 20:38:39 +0000
Commit:     Alan Somers <asom...@freebsd.org>
CommitDate: 2022-01-19 19:43:51 +0000

    Add tests for ses(4)
    
    The tests require SES hardware.  Without it, the test cases will be
    skipped.
    
    Reviewed by:    ken
    Differential Revision: https://reviews.freebsd.org/D31809
    Sponsored by:   Axcient
    MFC after:      2 weeks
---
 tests/sys/Makefile             |   1 +
 tests/sys/ses/Makefile         |  11 +
 tests/sys/ses/common.h         |  59 ++++
 tests/sys/ses/destructive.c    | 289 +++++++++++++++++++
 tests/sys/ses/nondestructive.c | 636 +++++++++++++++++++++++++++++++++++++++++
 usr.sbin/sesutil/sesutil.c     |   1 +
 6 files changed, 997 insertions(+)

diff --git a/tests/sys/Makefile b/tests/sys/Makefile
index 2ba60f41b76c..2faf04054d7f 100644
--- a/tests/sys/Makefile
+++ b/tests/sys/Makefile
@@ -28,6 +28,7 @@ TESTS_SUBDIRS+=               netmap
 TESTS_SUBDIRS+=                netpfil
 TESTS_SUBDIRS+=                opencrypto
 TESTS_SUBDIRS+=                posixshm
+TESTS_SUBDIRS+=                ses
 TESTS_SUBDIRS+=                sys
 TESTS_SUBDIRS+=                vfs
 TESTS_SUBDIRS+=                vm
diff --git a/tests/sys/ses/Makefile b/tests/sys/ses/Makefile
new file mode 100644
index 000000000000..8a0c54a2dd52
--- /dev/null
+++ b/tests/sys/ses/Makefile
@@ -0,0 +1,11 @@
+PACKAGE=       tests
+
+TESTSDIR=      ${TESTSBASE}/sys/ses
+
+ATF_TESTS_C+=  destructive
+ATF_TESTS_C+=  nondestructive
+
+# Some tests cases alter enclosure state, so they can't run concurrently.
+TEST_METADATA.destructive+= is_exclusive=true
+
+.include <bsd.test.mk>
diff --git a/tests/sys/ses/common.h b/tests/sys/ses/common.h
new file mode 100644
index 000000000000..c4a74bd8505f
--- /dev/null
+++ b/tests/sys/ses/common.h
@@ -0,0 +1,59 @@
+/*-
+ * Copyright (C) 2021 Axcient, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+typedef bool(*ses_cb)(const char *devname, int fd);
+
+// Run a test function on every available ses device
+static void
+for_each_ses_dev(ses_cb cb, int oflags)
+{
+       glob_t g;
+       int r;
+       unsigned i;
+       bool tested = false;
+
+       g.gl_pathc = 0;
+       g.gl_pathv = NULL;
+       g.gl_offs = 0;
+
+       r = glob("/dev/ses*", GLOB_NOSORT, NULL, &g);
+       ATF_REQUIRE_EQ(r, 0);
+
+       for(i = 0; i < g.gl_pathc; i++) {
+               int fd;
+
+               fd = open(g.gl_pathv[i], oflags);
+               ATF_REQUIRE(fd >= 0);
+               tested |= cb(g.gl_pathv[i], fd);
+               close(fd);
+       }
+
+       if (!tested)
+               atf_tc_skip("No supported devices found");
+
+       globfree(&g);
+}
diff --git a/tests/sys/ses/destructive.c b/tests/sys/ses/destructive.c
new file mode 100644
index 000000000000..307b272c447f
--- /dev/null
+++ b/tests/sys/ses/destructive.c
@@ -0,0 +1,289 @@
+/*-
+ * Copyright (C) 2021 Axcient, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/* Tests that alter an enclosure's state */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <atf-c.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <cam/scsi/scsi_enc.h>
+
+#include "common.h"
+
+// Run a test function on just one ses device
+static void
+for_one_ses_dev(ses_cb cb)
+{
+       glob_t g;
+       int fd, r;
+
+       g.gl_pathc = 0;
+       g.gl_pathv = NULL;
+       g.gl_offs = 0;
+
+       r = glob("/dev/ses*", GLOB_NOSORT, NULL, &g);
+       ATF_REQUIRE_EQ(r, 0);
+       if (g.gl_pathc == 0)
+               atf_tc_skip("No ses devices found");
+
+       fd = open(g.gl_pathv[0], O_RDWR);
+       ATF_REQUIRE(fd >= 0);
+       cb(g.gl_pathv[0], fd);
+       close(fd);
+
+       globfree(&g);
+}
+
+static bool do_setelmstat(const char *devname __unused, int fd) {
+       encioc_element_t *map;
+       unsigned elm_idx;
+       unsigned nobj;
+       int r;
+       elm_type_t last_elm_type = -1;
+
+       r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
+       ATF_REQUIRE_EQ(r, 0);
+
+       map = calloc(nobj, sizeof(encioc_element_t));
+       ATF_REQUIRE(map != NULL);
+       r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
+
+       /* Set the IDENT bit for every disk slot */
+       for (elm_idx = 0; elm_idx < nobj; elm_idx++) {
+               encioc_elm_status_t elmstat;
+               struct ses_ctrl_dev_slot *cslot;
+               struct ses_status_dev_slot *sslot;
+
+               if (last_elm_type != map[elm_idx].elm_type) {
+                       /* skip overall elements */
+                       last_elm_type = map[elm_idx].elm_type;
+                       continue;
+               }
+               elmstat.elm_idx = elm_idx;
+               if (map[elm_idx].elm_type == ELMTYP_DEVICE ||
+                   map[elm_idx].elm_type == ELMTYP_ARRAY_DEV)
+               {
+                       r = ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t)&elmstat);
+                       ATF_REQUIRE_EQ(r, 0);
+
+                       cslot = (struct ses_ctrl_dev_slot*)&elmstat.cstat[0];
+                       sslot = (struct ses_status_dev_slot*)&elmstat.cstat[0];
+
+                       ses_ctrl_common_set_select(&cslot->common, 1);
+                       ses_ctrl_dev_slot_set_rqst_ident(cslot, 1);
+                       r = ioctl(fd, ENCIOC_SETELMSTAT, (caddr_t)&elmstat);
+                       ATF_REQUIRE_EQ(r, 0);
+               }
+       }
+
+       /* Check the IDENT bit for every disk slot */
+       last_elm_type = -1;
+       for (elm_idx = 0; elm_idx < nobj; elm_idx++) {
+               encioc_elm_status_t elmstat;
+               struct ses_status_dev_slot *sslot;
+
+               if (last_elm_type != map[elm_idx].elm_type) {
+                       /* skip overall elements */
+                       last_elm_type = map[elm_idx].elm_type;
+                       continue;
+               }
+               elmstat.elm_idx = elm_idx;
+               if (map[elm_idx].elm_type == ELMTYP_DEVICE ||
+                   map[elm_idx].elm_type == ELMTYP_ARRAY_DEV)
+               {
+                       int i;
+
+                       r = ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t)&elmstat);
+                       ATF_REQUIRE_EQ(r, 0);
+
+                       sslot = (struct ses_status_dev_slot*)&elmstat.cstat[0];
+
+                       for (i = 0; i < 10; i++) {
+                               r = ioctl(fd, ENCIOC_GETELMSTAT,
+                                   (caddr_t)&elmstat);
+                               ATF_REQUIRE_EQ(r, 0);
+                               if (0 == ses_status_dev_slot_get_ident(sslot)) {
+                                       /* Needs more time to take effect */
+                                       usleep(100000);
+                               }
+                       }
+                       ATF_CHECK(ses_status_dev_slot_get_ident(sslot) != 0);
+
+               }
+       }
+
+       free(map);
+       return (true);
+}
+
+/*
+ * sg_ses doesn't provide "dump and restore" functionality.  The closest is to
+ * dump status page 2, then manually edit the file to set every individual
+ * element select bit, then load the entire file.  But that is much too hard.
+ * Instead, we'll just clear every ident bit.
+ */
+static bool
+do_setelmstat_cleanup(const char *devname __unused, int fd __unused) {
+       encioc_element_t *map;
+       unsigned elm_idx;
+       unsigned nobj;
+       int r;
+       elm_type_t last_elm_type = -1;
+
+       r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
+       ATF_REQUIRE_EQ(r, 0);
+
+       map = calloc(nobj, sizeof(encioc_element_t));
+       ATF_REQUIRE(map != NULL);
+       r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
+
+       /* Clear the IDENT bit for every disk slot */
+       for (elm_idx = 0; elm_idx < nobj; elm_idx++) {
+               encioc_elm_status_t elmstat;
+               struct ses_ctrl_dev_slot *cslot;
+
+               if (last_elm_type != map[elm_idx].elm_type) {
+                       /* skip overall elements */
+                       last_elm_type = map[elm_idx].elm_type;
+                       continue;
+               }
+               elmstat.elm_idx = elm_idx;
+               if (map[elm_idx].elm_type == ELMTYP_DEVICE ||
+                   map[elm_idx].elm_type == ELMTYP_ARRAY_DEV)
+               {
+                       cslot = (struct ses_ctrl_dev_slot*)&elmstat.cstat[0];
+
+                       ses_ctrl_common_set_select(&cslot->common, 1);
+                       ses_ctrl_dev_slot_set_rqst_ident(cslot, 0);
+                       r = ioctl(fd, ENCIOC_SETELMSTAT, (caddr_t)&elmstat);
+               }
+       }
+
+       return(true);
+}
+
+
+ATF_TC_WITH_CLEANUP(setelmstat);
+ATF_TC_HEAD(setelmstat, tc)
+{
+       atf_tc_set_md_var(tc, "descr", "Exercise ENCIOC_SETELMSTAT");
+       atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(setelmstat, tc)
+{
+       for_one_ses_dev(do_setelmstat);
+}
+ATF_TC_CLEANUP(setelmstat, tc)
+{
+       for_one_ses_dev(do_setelmstat_cleanup);
+}
+
+
+static bool do_setencstat(const char *devname __unused, int fd) {
+       unsigned char encstat;
+       int r, i;
+       bool worked = false;
+
+       /*
+        * SES provides no way to read the current setting of the enclosure
+        * control page common status bits.  So we'll blindly set CRIT.
+        */
+       encstat = 1 << SES_CTRL_PAGE_CRIT_SHIFT;
+       r = ioctl(fd, ENCIOC_SETENCSTAT, (caddr_t) &encstat);
+       ATF_REQUIRE_EQ(r, 0);
+
+       /* Check that the status has changed */
+       for (i = 0; i < 10; i++) {
+               r = ioctl(fd, ENCIOC_GETENCSTAT, (caddr_t) &encstat);
+               ATF_REQUIRE_EQ(r, 0);
+               if (encstat & SES_CTRL_PAGE_CRIT_MASK) {
+                       worked = true;
+                       break;
+               }
+               usleep(100000);
+       }
+       if (!worked) {
+               /* Some enclosures don't support setting the enclosure status */
+               return (false);
+       } else
+               return (true);
+}
+
+static bool do_setencstat_cleanup(const char *devname __unused, int fd) {
+       unsigned char encstat;
+
+       /*
+        * SES provides no way to read the current setting of the enclosure
+        * control page common status bits.  So we don't know what they were
+        * set to before the test.  We'll blindly clear all bits.
+        */
+       encstat = 0;
+       ioctl(fd, ENCIOC_SETENCSTAT, (caddr_t) &encstat);
+       return (true);
+}
+
+ATF_TC_WITH_CLEANUP(setencstat);
+ATF_TC_HEAD(setencstat, tc)
+{
+       atf_tc_set_md_var(tc, "descr", "Exercise ENCIOC_SETENCSTAT");
+       atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(setencstat, tc)
+{
+       for_each_ses_dev(do_setencstat, O_RDWR);
+}
+ATF_TC_CLEANUP(setencstat, tc)
+{
+       for_each_ses_dev(do_setencstat_cleanup, O_RDWR);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+       /*
+        * Untested ioctls:
+        *
+        * * ENCIOC_INIT because SES doesn't need it and I don't have any
+        *   SAF-TE devices.
+        *
+        * * ENCIOC_SETSTRING because it's seriously unsafe!  It's normally
+        *   used for stuff like firmware updates
+        */
+       ATF_TP_ADD_TC(tp, setelmstat);
+       ATF_TP_ADD_TC(tp, setencstat);
+       // TODO ENCIOC_SETELMSTAT
+
+       return (atf_no_error());
+}
diff --git a/tests/sys/ses/nondestructive.c b/tests/sys/ses/nondestructive.c
new file mode 100644
index 000000000000..e52a38a97862
--- /dev/null
+++ b/tests/sys/ses/nondestructive.c
@@ -0,0 +1,636 @@
+/*-
+ * Copyright (C) 2021 Axcient, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/* Basic smoke test of the ioctl interface */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <atf-c.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <cam/scsi/scsi_enc.h>
+
+#include "common.h"
+
+static bool do_getelmdesc(const char *devname, int fd) {
+       regex_t re;
+       FILE *pipe;
+       char cmd[256];
+       char line[256];
+       char *actual;
+       unsigned nobj;
+       unsigned elm_idx = 0;
+       int r;
+
+       actual = calloc(UINT16_MAX, sizeof(char));
+       ATF_REQUIRE(actual != NULL);
+       r = regcomp(&re, "(Overall|Element [0-9]+) descriptor: ", REG_EXTENDED);
+       ATF_REQUIRE_EQ(r, 0);
+
+       r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
+       ATF_REQUIRE_EQ(r, 0);
+
+       snprintf(cmd, sizeof(cmd), "sg_ses -p7 %s", devname);
+       pipe = popen(cmd, "r");
+       ATF_REQUIRE(pipe != NULL);
+       while(NULL != fgets(line, sizeof(line), pipe)) {
+               regmatch_t matches[1];
+               encioc_elm_desc_t e_desc;
+               char *expected;
+               size_t elen;
+
+               if (regexec(&re, line, 1, matches, 0) == REG_NOMATCH) {
+                       continue;
+               }
+
+               expected = &line[matches[0].rm_eo];
+               /* Remove trailing newline */
+               elen = strnlen(expected, sizeof(line) - matches[0].rm_eo);
+               expected[elen - 1] = '\0';
+               /*
+                * Zero the result string.  XXX we wouldn't have to do this if
+                * the kernel would nul-terminate the result.
+                */
+               memset(actual, 0, UINT16_MAX);
+               e_desc.elm_idx = elm_idx;
+               e_desc.elm_desc_len = UINT16_MAX;
+               e_desc.elm_desc_str = actual;
+               r = ioctl(fd, ENCIOC_GETELMDESC, (caddr_t) &e_desc);
+               ATF_REQUIRE_EQ(r, 0);
+               if (0 == strcmp("<empty>", expected)) {
+                       /* sg_ses replaces "" with "<empty>" */
+                       ATF_CHECK_STREQ("", actual);
+               } else
+                       ATF_CHECK_STREQ(expected, actual);
+               elm_idx++;
+       }
+
+       r = pclose(pipe);
+       regfree(&re);
+       free(actual);
+       if (r != 0) {
+               /* Probably an SGPIO device */
+
+               return (false);
+       } else {
+               ATF_CHECK_EQ_MSG(nobj, elm_idx,
+                               "Did not find the expected number of element "
+                               "descriptors in sg_ses's output");
+               return (true);
+       }
+}
+
+ATF_TC(getelmdesc);
+ATF_TC_HEAD(getelmdesc, tc)
+{
+       atf_tc_set_md_var(tc, "descr",
+           "Compare ENCIOC_GETELMDESC's output to sg3_utils'");
+       atf_tc_set_md_var(tc, "require.user", "root");
+       atf_tc_set_md_var(tc, "require.progs", "sg_ses");
+}
+ATF_TC_BODY(getelmdesc, tc)
+{
+       for_each_ses_dev(do_getelmdesc, O_RDONLY);
+}
+
+static bool do_getelmdevnames(const char *devname __unused, int fd) {
+       encioc_element_t *map;
+       unsigned nobj;
+       const size_t namesize = 128;
+       int r;
+       char *namebuf;
+       unsigned elm_idx;
+
+       r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
+       ATF_REQUIRE_EQ(r, 0);
+
+       namebuf = calloc(namesize, sizeof(char));
+       ATF_REQUIRE(namebuf != NULL);
+       map = calloc(nobj, sizeof(encioc_element_t));
+       ATF_REQUIRE(map != NULL);
+       r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
+       ATF_REQUIRE_EQ(r, 0);
+
+       for (elm_idx = 0; elm_idx < nobj; elm_idx++) {
+               /*
+                * devnames should be present if:
+                * * The element is of type Device Slot or Array Device Slot
+                * * It isn't an Overall Element
+                * * The element's status is not "Not Installed"
+                */
+               encioc_elm_status_t e_status;
+               encioc_elm_devnames_t elmdn;
+
+               memset(&e_status, 0, sizeof(e_status));
+               e_status.elm_idx = elm_idx;
+               r = ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t)&e_status);
+               ATF_REQUIRE_EQ(r, 0);
+
+               memset(&elmdn, 0, sizeof(elmdn));
+               elmdn.elm_idx = elm_idx;
+               elmdn.elm_names_size = namesize;
+               elmdn.elm_devnames = namebuf;
+               namebuf[0] = '\0';
+               r = ioctl(fd, ENCIOC_GETELMDEVNAMES, (caddr_t) &elmdn);
+               if (e_status.cstat[0] != SES_OBJSTAT_UNSUPPORTED &&
+                   e_status.cstat[0] != SES_OBJSTAT_NOTINSTALLED &&
+                   (map[elm_idx].elm_type == ELMTYP_DEVICE ||
+                    map[elm_idx].elm_type == ELMTYP_ARRAY_DEV))
+               {
+                       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");
+               } else {
+                       ATF_CHECK(r != 0);
+               }
+
+               if (r == 0) {
+                       size_t z = 0;
+                       int da = 0, ada = 0, pass = 0, nvd = 0;
+                       int nvme = 0, unknown = 0;
+
+                       while(elmdn.elm_devnames[z] != '\0') {
+                               size_t e;
+                               char *s;
+
+                               if (elmdn.elm_devnames[z] == ',')
+                                       z++;    /* Skip the comma */
+                               s = elmdn.elm_devnames + z;
+                               e = strcspn(s, "0123456789");
+                               if (0 == strncmp("da", s, e))
+                                       da++;
+                               else if (0 == strncmp("ada", s, e))
+                                       ada++;
+                               else if (0 == strncmp("pass", s, e))
+                                       pass++;
+                               else if (0 == strncmp("nvd", s, e))
+                                       nvd++;
+                               else if (0 == strncmp("nvme", s, e))
+                                       nvme++;
+                               else
+                                       unknown++;
+                               z += strcspn(elmdn.elm_devnames + z, ",");
+                       }
+                       /* There should be one pass dev for each non-pass dev */
+                       ATF_CHECK_EQ(pass, da + ada + nvd + nvme);
+                       ATF_CHECK_EQ_MSG(0, unknown,
+                           "Unknown device names %s", elmdn.elm_devnames);
+               }
+       }
+       free(map);
+       free(namebuf);
+
+       return (true);
+}
+
+ATF_TC(getelmdevnames);
+ATF_TC_HEAD(getelmdevnames, tc)
+{
+       atf_tc_set_md_var(tc, "descr",
+           "Compare ENCIOC_GETELMDEVNAMES's output to sg3_utils'");
+       atf_tc_set_md_var(tc, "require.user", "root");
+       atf_tc_set_md_var(tc, "require.progs", "sg_ses");
+}
+ATF_TC_BODY(getelmdevnames, tc)
+{
+       for_each_ses_dev(do_getelmdevnames, O_RDONLY);
+}
+
+static int
+elm_type_name2int(const char *name) {
+       const char *elm_type_names[] = ELM_TYPE_NAMES;
+       int i;
+
+       for (i = 0; i <= ELMTYP_LAST; i++) {
+               /* sg_ses uses different case than ses(4) */
+               if (0 == strcasecmp(name, elm_type_names[i]))
+                       return i;
+       }
+       return (-1);
+}
+
+static bool do_getelmmap(const char *devname, int fd) {
+       encioc_element_t *map;
+       FILE *pipe;
+       char cmd[256];
+       char line[256];
+       unsigned elm_idx = 0;
+       unsigned nobj, subenc_id;
+       int r, elm_type;
+
+       r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
+       ATF_REQUIRE_EQ(r, 0);
+
+       map = calloc(nobj, sizeof(encioc_element_t));
+       ATF_REQUIRE(map != NULL);
+       r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
+       ATF_REQUIRE_EQ(r, 0);
+
+       snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
+       pipe = popen(cmd, "r");
+       ATF_REQUIRE(pipe != NULL);
+       while(NULL != fgets(line, sizeof(line), pipe)) {
+               char elm_type_name[80];
+               int i, num_elm;
+
+               r = sscanf(line,
+                   "    Element type: %[a-zA-Z0-9_ /], subenclosure id: %d",
+                   elm_type_name, &subenc_id);
+               if (r == 2) {
+                       elm_type = elm_type_name2int(elm_type_name);
+                       continue;
+               } else {
+                       r = sscanf(line,
+                           "    Element type: vendor specific [0x%x], 
subenclosure id: %d",
+                           &elm_type, &subenc_id);
+                       if (r == 2)
+                               continue;
+               }
+               r = sscanf(line, "      number of possible elements: %d",
+                   &num_elm);
+               if (r != 1)
+                       continue;
+
+               /* Skip the Overall elements */
+               elm_idx++;
+               for (i = 0; i < num_elm; i++, elm_idx++) {
+                       ATF_CHECK_EQ(map[elm_idx].elm_idx, elm_idx);
+                       ATF_CHECK_EQ(map[elm_idx].elm_subenc_id, subenc_id);
+                       ATF_CHECK_EQ((int)map[elm_idx].elm_type, elm_type);
+               }
+       }
+
+       free(map);
+       r = pclose(pipe);
+       if (r != 0) {
+               /* Probably an SGPIO device */
+               return (false);
+       } else {
+               ATF_CHECK_EQ_MSG(nobj, elm_idx,
+                               "Did not find the expected number of element "
+                               "descriptors in sg_ses's output");
+               return (true);
+       }
+}
+
+ATF_TC(getelmmap);
+ATF_TC_HEAD(getelmmap, tc)
+{
+       atf_tc_set_md_var(tc, "descr",
+           "Compare ENCIOC_GETELMMAP's output to sg3_utils'");
+       atf_tc_set_md_var(tc, "require.user", "root");
+       atf_tc_set_md_var(tc, "require.progs", "sg_ses");
+}
+ATF_TC_BODY(getelmmap, tc)
+{
+       for_each_ses_dev(do_getelmmap, O_RDONLY);
+}
+
+static bool do_getelmstat(const char *devname, int fd) {
+       encioc_element_t *map;
+       unsigned elm_idx;
+       unsigned nobj;
+       int r, elm_subidx;
+       elm_type_t last_elm_type = -1;
+
+       r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
+       ATF_REQUIRE_EQ(r, 0);
+
+       map = calloc(nobj, sizeof(encioc_element_t));
+       ATF_REQUIRE(map != NULL);
+       r = ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) map);
+
+       for (elm_idx = 0; elm_idx < nobj; elm_subidx++, elm_idx++) {
+               encioc_elm_status_t e_status;
+               FILE *pipe;
+               char cmd[256];
+               uint32_t status;
+               int pr;
+
+               if (last_elm_type != map[elm_idx].elm_type)
+                       elm_subidx = -1;
+               last_elm_type = map[elm_idx].elm_type;
+
+               snprintf(cmd, sizeof(cmd),
+                   "sg_ses -Hp2 --index=_%d,%d --get=0:7:32 %s",
+                   map[elm_idx].elm_type, elm_subidx, devname);
+               pipe = popen(cmd, "r");
+               ATF_REQUIRE(pipe != NULL);
+               r = fscanf(pipe, "0x%x", &status);
+               pr = pclose(pipe);
+               if (pr != 0) {
+                       /* Probably an SGPIO device */
+                       free(map);
+                       return (false);
+               }
+               ATF_REQUIRE_EQ(r, 1);
+
+               memset(&e_status, 0, sizeof(e_status));
+               e_status.elm_idx = map[elm_idx].elm_idx;
+               r = ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t)&e_status);
+               ATF_REQUIRE_EQ(r, 0);
+
+               // Compare the common status field
+               ATF_CHECK_EQ(e_status.cstat[0], status >> 24);
+               /*
+                * Ignore the other fields, because some have values that can
+                * change frequently (voltage, temperature, etc)
+                */
+       }
+       free(map);
+
+       return (true);
+}
+
+ATF_TC(getelmstat);
+ATF_TC_HEAD(getelmstat, tc)
+{
+       atf_tc_set_md_var(tc, "descr",
+           "Compare ENCIOC_GETELMSTAT's output to sg3_utils'");
+       atf_tc_set_md_var(tc, "require.user", "root");
+       atf_tc_set_md_var(tc, "require.progs", "sg_ses");
+}
+ATF_TC_BODY(getelmstat, tc)
+{
+       for_each_ses_dev(do_getelmstat, O_RDONLY);
+}
+
+static bool do_getencid(const char *devname, int fd) {
+       encioc_string_t stri;
+       FILE *pipe;
+       char cmd[256];
+       char encid[32];
+       char line[256];
+       char sg_encid[32];
+       int r, sg_ses_r;
+
+       snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
+       pipe = popen(cmd, "r");
+       ATF_REQUIRE(pipe != NULL);
+       sg_encid[0] = '\0';
+       while(NULL != fgets(line, sizeof(line), pipe)) {
+               const char *f = "      enclosure logical identifier (hex): %s";
+
+               if (1 == fscanf(pipe, f, sg_encid))
+                       break;
+       }
+       sg_ses_r = pclose(pipe);
+
+       stri.bufsiz = sizeof(encid);
+       stri.buf = &encid[0];
+       r = ioctl(fd, ENCIOC_GETENCID, (caddr_t) &stri);
+       ATF_REQUIRE_EQ(r, 0);
+       if (sg_ses_r == 0) {
+               ATF_REQUIRE(sg_encid[0] != '\0');
+               ATF_CHECK_STREQ(sg_encid, (char*)stri.buf);
+               return (true);
+       } else {
+               /* Probably SGPIO; sg_ses unsupported */
+               return (false);
+       }
+}
+
+ATF_TC(getencid);
+ATF_TC_HEAD(getencid, tc)
+{
+       atf_tc_set_md_var(tc, "descr",
+           "Compare ENCIOC_GETENCID's output to sg3_utils'");
+       atf_tc_set_md_var(tc, "require.user", "root");
+       atf_tc_set_md_var(tc, "require.progs", "sg_ses");
+}
+ATF_TC_BODY(getencid, tc)
+{
+       for_each_ses_dev(do_getencid, O_RDONLY);
+}
+
+static bool do_getencname(const char *devname, int fd) {
+       encioc_string_t stri;
+       FILE *pipe;
+       char cmd[256];
+       char encname[32];
+       char line[256];
+       int r;
+
+       snprintf(cmd, sizeof(cmd), "sg_inq -o %s | awk '"
+               "/Vendor identification/ {vi=$NF} "
+               "/Product identification/ {pi=$NF} "
+               "/Product revision level/ {prl=$NF} "
+               "END {printf(vi \" \" pi \" \" prl)}'", devname);
+       pipe = popen(cmd, "r");
+       ATF_REQUIRE(pipe != NULL);
+       ATF_REQUIRE(NULL != fgets(line, sizeof(line), pipe));
+       pclose(pipe);
+
+       stri.bufsiz = sizeof(encname);
+       stri.buf = &encname[0];
+       r = ioctl(fd, ENCIOC_GETENCNAME, (caddr_t) &stri);
+       ATF_REQUIRE_EQ(r, 0);
+       if (strlen(line) < 3) {
+               // Probably an SGPIO device, INQUIRY unsupported
+               return (false);
+       } else {
+               ATF_CHECK_STREQ(line, (char*)stri.buf);
+               return (true);
+       }
+}
+
+ATF_TC(getencname);
+ATF_TC_HEAD(getencname, tc)
+{
+       atf_tc_set_md_var(tc, "descr",
+           "Compare ENCIOC_GETENCNAME's output to sg3_utils'");
+       atf_tc_set_md_var(tc, "require.user", "root");
+       atf_tc_set_md_var(tc, "require.progs", "sg_inq");
+}
+ATF_TC_BODY(getencname, tc)
+{
+       for_each_ses_dev(do_getencname, O_RDONLY);
+}
+
+static bool do_getencstat(const char *devname, int fd) {
+       FILE *pipe;
+       char cmd[256];
+       unsigned char e, estat, invop, info, noncrit, crit, unrecov;
+       int r;
+
+       snprintf(cmd, sizeof(cmd), "sg_ses -p2 %s "
+               "| grep 'INVOP='",
+               devname);
+       pipe = popen(cmd, "r");
+       ATF_REQUIRE(pipe != NULL);
+       r = fscanf(pipe,
+           "  INVOP=%hhu, INFO=%hhu, NON-CRIT=%hhu, CRIT=%hhu, UNRECOV=%hhu",
+           &invop, &info, &noncrit, &crit, &unrecov);
+       pclose(pipe);
+       if (r != 5) {
+               /* Probably on SGPIO device */
+               return (false);
+       } else {
+               r = ioctl(fd, ENCIOC_GETENCSTAT, (caddr_t) &estat);
+               ATF_REQUIRE_EQ(r, 0);
+               /* Exclude the info bit because it changes frequently */
+               e = (invop << 4) | (noncrit << 2) | (crit << 1) | unrecov;
+               ATF_CHECK_EQ(estat & ~0x08, e);
+               return (true);
+       }
+}
+
+ATF_TC(getencstat);
+ATF_TC_HEAD(getencstat, tc)
+{
+       atf_tc_set_md_var(tc, "descr",
+           "Compare ENCIOC_GETENCSTAT's output to sg3_utils'");
+       atf_tc_set_md_var(tc, "require.user", "root");
+       atf_tc_set_md_var(tc, "require.progs", "sg_ses");
+}
+ATF_TC_BODY(getencstat, tc)
+{
+       for_each_ses_dev(do_getencstat, O_RDONLY);
+}
+
+static bool do_getnelm(const char *devname, int fd) {
+       FILE *pipe;
+       char cmd[256];
+       char line[256];
+       unsigned nobj, expected = 0;
+       int r, sg_ses_r;
+
+       snprintf(cmd, sizeof(cmd), "sg_ses -p1 %s", devname);
+       pipe = popen(cmd, "r");
+       ATF_REQUIRE(pipe != NULL);
+
+       while(NULL != fgets(line, sizeof(line), pipe)) {
+               unsigned nelm;
+
+               if (1 == fscanf(pipe, "      number of possible elements: %u",
+                   &nelm))
+               {
+                       expected += 1 + nelm;   // +1 for the Overall element
+               }
+       }
+       sg_ses_r = pclose(pipe);
+
+       r = ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj);
+       ATF_REQUIRE_EQ(r, 0);
+       if (sg_ses_r == 0) {
+               ATF_CHECK_EQ(expected, nobj);
+               return (true);
+       } else {
+               /* Probably SGPIO, sg_ses unsupported */
+               return (false);
+       }
+}
+
+ATF_TC(getnelm);
+ATF_TC_HEAD(getnelm, tc)
+{
+       atf_tc_set_md_var(tc, "descr",
+           "Compare ENCIOC_GETNELM's output to sg3_utils'");
+       atf_tc_set_md_var(tc, "require.user", "root");
+       atf_tc_set_md_var(tc, "require.progs", "sg_ses");
+}
+ATF_TC_BODY(getnelm, tc)
+{
+       for_each_ses_dev(do_getnelm, O_RDONLY);
+}
+
*** 84 LINES SKIPPED ***

Reply via email to