2c449a4c5a
glob(3) returns GLOB_NOMATCH if GLOB_NOCHECK or GLOB_NOMAGIC flag is not passed so ATF_REQUIRE_EQ(r, 0) will cause a precondition check failure if no /dev/ses* exists. Remove calling of atf_tc_skip() in ATF_TC_CLEANUP() because it would let the clean up procedure unfinish. While here, fix a set-but-not-used warning. Reviewed by: asomers Differential Revision: https://reviews.freebsd.org/D34056
297 lines
7.6 KiB
C
297 lines
7.6 KiB
C
/*-
|
|
* 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_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());
|
|
}
|