/*- * 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 #include #include #include #include #include #include #include #include #include #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_NOCHECK | GLOB_NOSORT, NULL, &g); ATF_REQUIRE_EQ(r, 0); if (g.gl_matchc == 0) return; 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; 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]; 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) { if (!has_ses()) atf_tc_skip("No ses devices found"); for_one_ses_dev(do_setelmstat); } ATF_TC_CLEANUP(setelmstat, tc) { if (!has_ses()) return; 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) { if (!has_ses()) atf_tc_skip("No ses devices found"); 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()); }