Commit fddd64d7 authored by Eric Cano's avatar Eric Cano
Browse files

Added many cross checks to identify the devices files. Unit test created accordingly.

Unit test does not pass yet.
parent 89632b72
......@@ -54,17 +54,22 @@ set(CTEST_OUTPUT_ON_FAILURE 1)
# main part
###########################################################################
# Prepare tests file list
#set(TEST_FILES "")
#set(TEST_LIBS "")
# Add parts first in dependency order
add_subdirectory(SCSI)
add_subdirectory(Drive)
add_subdirectory(system)
add_subdirectory(Exception)
add_subdirectory(Utils)
# .. and then tools, tests, and daemon
# add_subdirectory(tools)
# add_subdirectory(deamon)
# .. and of course, the tests
# .. and of course, the tests (last to use the variable definition)
add_subdirectory(test)
###########################################################################
......
add_library(Exception Exception.cc)
......@@ -20,11 +20,10 @@
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#define _XOPEN_SOURCE 600
#include "Exception.hh"
#include <errno.h>
/* We want the thread safe (and portable) version of strerror */
#define _XOPEN_SOURCE 600
#include <string.h>
#include <sstream>
#include <iosfwd>
......@@ -33,14 +32,16 @@
Tape::Exceptions::Errnum::Errnum(std::string what):Exception(what) {
m_errnum = errno;
char s[1000];
if (::strerror_r(m_errnum, s, sizeof(s))) {
/* _XOPEN_SOURCE seems not to work. */
char * errorStr = ::strerror_r(m_errnum, s, sizeof(s));
if (!errorStr) {
int new_errno = errno;
std::stringstream w;
w << "Errno=" << m_errnum << ". In addition, failed to read the corresponding error string (with errno="
w << "Errno=" << m_errnum << ". In addition, failed to read the corresponding error string (strerror gave errno="
<< new_errno << ")";
m_strerror = w.str();
} else {
m_strerror = std::string(s);
m_strerror = std::string(errorStr);
}
std::stringstream w2;
w2 << "Errno=" << m_errnum << ": " << m_strerror;
......
......@@ -30,7 +30,7 @@ namespace Tape {
public:
Exception(const std::string& what): m_what(what) {};
virtual ~Exception() throw() {};
virtual const char * what() { return m_what.c_str(); }
virtual const char * what() const throw() { return m_what.c_str(); }
protected:
std::string m_what;
};
......@@ -44,7 +44,7 @@ namespace Tape {
virtual ~Errnum() throw() {};
int ErrorNumber() { return m_errnum; }
std::string strError() { return m_strerror; }
virtual const char * what() { return m_what.c_str(); }
virtual const char * what() const throw() { return m_what.c_str(); }
protected:
int m_errnum;
std::string m_strerror;
......
add_library(SCSI Device.cc)
#add_executable(SCSIDumpTest DumpTest.cc)
#target_link_libraries(SCSIDumpTest SCSI)
\ No newline at end of file
#target_link_libraries(SCSIDumpTest SCSI)
......@@ -24,16 +24,39 @@
#pragma once
#include <string>
#include <vector>
#include <regex.h>
#include <sys/types.h>
#include <dirent.h>
#include "../system/Wrapper.hh"
#include "../Exception/Exception.hh"
#include <scsi/scsi.h>
#include "../Utils/Regex.hh"
namespace SCSI {
/* Extracted from linux kernel's include/scsi/scsi.h. System-level include
is less complete */
class Types {
public:
enum {
disk = 0x00,
tape = 0x01,
printer = 0x02,
processor = 0x03, /* HP scanners use this */
worm = 0x04, /* Treated as ROM by our system */
rom = 0x05,
scanner = 0x06,
mod = 0x07, /* Magneto-optical disk -
* - treated as TYPE_DISK */
mediumChanger = 0x08,
comm = 0x09, /* Communications device */
raid = 0x0c,
enclosure = 0x0d, /* Enclosure Services Device */
rbc = 0x0e,
noLun = 0x7f
};
};
/**
* Bare-bones representation of a SCSI device
*/
......@@ -41,26 +64,42 @@ namespace SCSI {
std::string sysfs_entry;
int type;
std::string sg_dev;
std::string st_dev;
std::string nst_dev;
class DeviceFile {
public:
/* We avoid being hit by the macros major() and minor() by using a longer syntax */
DeviceFile() {
major = -1;
minor = -1;
}
int major;
int minor;
bool operator !=(const DeviceFile& b) const {
return major != b.major || minor != b.minor;
}
};
DeviceFile sg;
DeviceFile st;
DeviceFile nst;
};
/**
* Automatic lister of the system's SCSI devices
* @param sysWrapper
*/
template<class sysWrapperClass>
class DeviceVector : public std::vector<DeviceInfo> {
public:
/**
* Fill up the array that the device list is with all the system's
* SCSI devices information.
*
* (all code using templates must be in the header file)
*/
/**
*
* @param sysWrapper
*/
DeviceVector(sysWrapperClass & sysWrapper) : m_sysWrapper(sysWrapper) {
std::string sysDevsPath = "/sys/bus/scsi/devices";
DIR* dirp = m_sysWrapper.opendir(sysDevsPath.c_str());
......@@ -70,7 +109,7 @@ namespace SCSI {
/* We expect only symbolic links in this directory, */
char rp[PATH_MAX];
if (NULL == m_sysWrapper.realpath(fullpath.c_str(), rp))
throw Tape::Exceptions::Errnum();
throw Tape::Exceptions::Errnum("Could not find realpath for " + fullpath);
this->push_back(getDeviceInfo(rp));
}
sysWrapper.closedir(dirp);
......@@ -78,7 +117,7 @@ namespace SCSI {
private:
#undef ConvenientCoding
#ifdef ConvenientCoding
synthax error here!!!; /* So we don't compile in this configuration */
syntax error here!!!; /* So we don't compile in this configuration */
/* This makes code completion more efficient in editors */
Tape::System::fakeWrapper & m_sysWrapper;
#else
......@@ -91,18 +130,76 @@ namespace SCSI {
std::string readfile(std::string path) {
int fd = m_sysWrapper.open(path.c_str(), 0);
if (-1 == fd) {
throw Tape::Exceptions::Errnum();
throw Tape::Exceptions::Errnum("Could not open file " + path);
}
char buf[readfileBlockSize];
std::string ret;
while (ssize_t sread = m_sysWrapper.read(fd, buf, readfileBlockSize)) {
if (-1 == sread) throw Tape::Exceptions::Errnum();
if (-1 == sread)
throw Tape::Exceptions::Errnum("Could not read from open file " + path);
ret.append(buf, sread);
}
if (m_sysWrapper.close(fd)) throw Tape::Exceptions::Errnum();
if (m_sysWrapper.close(fd))
throw Tape::Exceptions::Errnum("Error closing file " + path);
return ret;
}
void getTapeInfo(DeviceInfo & devinfo) {
/* Find the st and nst devices for this SCSI device */
Tape::Utils::regex st_re("^scsi_tape:(st[[:digit:]]+)$");
Tape::Utils::regex nst_re("^scsi_tape:(nst[[:digit:]]+)$");
DIR * dirp = m_sysWrapper.opendir(devinfo.sysfs_entry.c_str());
if (!dirp) throw Tape::Exceptions::Errnum(std::string("Error opening device directory ") +
devinfo.sysfs_entry);
while (struct dirent * dent = m_sysWrapper.readdir(dirp)) {
std::vector<std::string> res;
/* Check if it's the st information */
res = st_re.exec(dent->d_name);
if (res.size()) {
if (!devinfo.st_dev.size()) {
devinfo.st_dev = std::string("/dev/") + res[1];
} else
throw Tape::Exception("Matched st device several times!");
/* Read the major and major number */
devinfo.st = readDeviceFile(devinfo.sysfs_entry + "/"
+ std::string(dent->d_name) + "/dev");
/* Check the actual device file */
DeviceInfo::DeviceFile realFile = statDeviceFile(devinfo.st_dev);
if (devinfo.st != realFile) {
std::stringstream err;
err << "Mismatch between sysfs info and actual device file: "
<< devinfo.sysfs_entry + "/" + dent->d_name << " indicates "
<< devinfo.st.major << ":" << devinfo.st.minor
<< " while " << devinfo.st_dev << " is: "
<< realFile.major << ":" << realFile.minor;
throw Tape::Exception(err.str());
}
}
/* Check if it's the nst information */
res = nst_re.exec(dent->d_name);
if (res.size()) {
if (!devinfo.nst_dev.size()) {
devinfo.nst_dev = std::string("/dev/") + res[1];
} else
throw Tape::Exception("Matched nst device several times!");
/* Read the major and major number */
devinfo.nst = readDeviceFile(devinfo.sysfs_entry + "/"
+ std::string(dent->d_name) + "/dev");
/* Check the actual device file */
DeviceInfo::DeviceFile realFile = statDeviceFile(devinfo.nst_dev);
if (devinfo.nst != realFile) {
std::stringstream err;
err << "Mismatch between sysfs info and actual device file: "
<< devinfo.sysfs_entry + "/" + dent->d_name << " indicates "
<< devinfo.nst.major << ":" << devinfo.nst.minor
<< " while " << devinfo.st_dev << " is: "
<< realFile.major << ":" << realFile.minor;
throw Tape::Exception(err.str());
}
}
}
}
/**
* Extract information from sysfs about a SCSI device.
* @param path Path to the directory with information about
......@@ -123,14 +220,39 @@ namespace SCSI {
char rl[PATH_MAX];
std::string lp = ret.sysfs_entry + "/generic";
if (-1 == m_sysWrapper.readlink(lp.c_str(), rl, sizeof (rl)))
throw Tape::Exceptions::Errnum();
throw Tape::Exceptions::Errnum("Could not read link " + lp);
std::string gl(rl);
size_t pos = gl.find_last_of("/");
if (pos == std::string::npos)
throw Tape::Exception(std::string("Could not find last / in link: ") + gl +
" read from " + ret.sysfs_entry + "/generic");
ret.sg_dev = gl.substr(pos + 1);
ret.sg_dev = std::string("/dev/") + gl.substr(pos + 1);
}
/* Get the major and minor number of the device file */
/* TODO */
/* Handle more if we have a tape device */
if (Types::tape == ret.type)
getTapeInfo(ret);
return ret;
}
DeviceInfo::DeviceFile readDeviceFile(std::string path) {
DeviceInfo::DeviceFile ret;
std::string file = readfile(path);
if (!::sscanf(file.c_str(), "%d:%d\n", &ret.major, &ret.minor))
throw Tape::Exception(std::string("Could not parse file: ") + path);
return ret;
}
DeviceInfo::DeviceFile statDeviceFile(std::string path) {
struct stat sbuf;
if (m_sysWrapper.stat(path.c_str(), &sbuf))
throw Tape::Exceptions::Errnum("Could not stat file " + path);
if (!S_ISCHR(sbuf.st_mode))
throw Tape::Exception("Device file " + path + " is not a character device");
DeviceInfo::DeviceFile ret;
ret.major = major(sbuf.st_rdev);
ret.minor = minor(sbuf.st_rdev);
return ret;
}
......
......@@ -30,46 +30,71 @@ using ::testing::AtLeast;
using ::testing::Return;
using ::testing::_;
Tape::System::mockWrapper sysWrapper;
TEST(DeviceList, TriesToFind) {
Tape::System::mockWrapper sysWrapper;
/* Give minimal service output from mock system calls:
* at least pretend there is a directory to scan */
/* _ means anything goes */
EXPECT_CALL(sysWrapper, opendir(_)).Times(1);
EXPECT_CALL(sysWrapper, readdir(sysWrapper.m_DIR)).Times(1);
EXPECT_CALL(sysWrapper, closedir(sysWrapper.m_DIR)).Times(1);
SCSI::DeviceVector<Tape::System::virtualWrapper> dl(sysWrapper);
}
TEST(DeviceList, ScansCorrectly) {
Tape::System::mockWrapper sysWrapper;
/* Configure the mock to use fake */
sysWrapper.delegateToFake();
/* Populate the test harness */
sysWrapper.fake.setupSLC5();
/* We expect the following calls: */
EXPECT_CALL(sysWrapper, opendir(_)).Times(1);
EXPECT_CALL(sysWrapper, readdir(_)).Times(4);
EXPECT_CALL(sysWrapper, opendir(_)).Times(3);
EXPECT_CALL(sysWrapper, readdir(_)).Times(AtLeast(30));
EXPECT_CALL(sysWrapper, closedir(_)).Times(1);
EXPECT_CALL(sysWrapper, realpath(_,_)).Times(3);
EXPECT_CALL(sysWrapper, open(_,_)).Times(3);
EXPECT_CALL(sysWrapper, read(_,_,_)).Times(6);
EXPECT_CALL(sysWrapper, realpath(_, _)).Times(3);
EXPECT_CALL(sysWrapper, open(_, _)).Times(3);
EXPECT_CALL(sysWrapper, read(_, _, _)).Times(6);
EXPECT_CALL(sysWrapper, close(_)).Times(3);
EXPECT_CALL(sysWrapper, readlink(_,_,_)).Times(3);
EXPECT_CALL(sysWrapper, readlink(_, _, _)).Times(3);
/* Everything should have been found correctly */
/* Everything should have called correctly */
SCSI::DeviceVector<Tape::System::virtualWrapper> dl(sysWrapper);
ASSERT_EQ(dl.size(), 3);
ASSERT_EQ(dl[0].type, 8);
ASSERT_EQ(dl[1].type, 1);
ASSERT_EQ(dl[2].type, 1);
ASSERT_EQ(dl[0].sg_dev, "sg2");
ASSERT_EQ(dl[1].sg_dev, "sg0");
ASSERT_EQ(dl[2].sg_dev, "sg1");
ASSERT_EQ(dl[0].type, SCSI::Types::mediumChanger);
ASSERT_EQ(dl[1].type, SCSI::Types::tape);
ASSERT_EQ(dl[2].type, SCSI::Types::tape);
ASSERT_EQ(dl[0].sg_dev, "/dev/sg2");
ASSERT_EQ(dl[1].sg_dev, "/dev/sg0");
ASSERT_EQ(dl[2].sg_dev, "/dev/sg1");
ASSERT_EQ(dl[0].st_dev, "");
ASSERT_EQ(dl[1].st_dev, "/dev/st0");
ASSERT_EQ(dl[2].st_dev, "/dev/st1");
ASSERT_EQ(dl[0].nst_dev, "");
ASSERT_EQ(dl[1].nst_dev, "/dev/nst0");
ASSERT_EQ(dl[2].nst_dev, "/dev/nst1");
ASSERT_EQ(dl[0].sysfs_entry, "/sys/devices/pseudo_0/adapter0/host3/target3:0:0/3:0:0:0");
ASSERT_EQ(dl[1].sysfs_entry, "/sys/devices/pseudo_0/adapter0/host3/target3:0:1/3:0:1:0");
ASSERT_EQ(dl[2].sysfs_entry, "/sys/devices/pseudo_0/adapter0/host3/target3:0:2/3:0:2:0");
ASSERT_EQ(dl[2].sysfs_entry, "/sys/devices/pseudo_0/adapter0/host3/target3:0:2/3:0:2:0");
ASSERT_EQ(dl[0].sg.major, 21);
ASSERT_EQ(dl[0].sg.minor, 2);
ASSERT_EQ(dl[1].sg.major, 21);
ASSERT_EQ(dl[1].sg.minor, 0);
ASSERT_EQ(dl[2].sg.major, 21);
ASSERT_EQ(dl[2].sg.minor, 1);
ASSERT_EQ(dl[0].st.major, -1);
ASSERT_EQ(dl[0].st.minor, -1);
ASSERT_EQ(dl[1].st.major, 9);
ASSERT_EQ(dl[1].st.minor, 0);
ASSERT_EQ(dl[2].st.major, 9);
ASSERT_EQ(dl[2].st.minor, 1);
ASSERT_EQ(dl[0].nst.major, -1);
ASSERT_EQ(dl[0].nst.minor, -1);
ASSERT_EQ(dl[1].nst.major, 9);
ASSERT_EQ(dl[1].nst.minor, 128);
ASSERT_EQ(dl[2].nst.major, 9);
ASSERT_EQ(dl[2].nst.minor, 129);
}
add_library(Utils Regex.cc)
\ No newline at end of file
// ----------------------------------------------------------------------
// File: Utils/Regex.cc
// Author: Eric Cano - CERN
// ----------------------------------------------------------------------
/************************************************************************
* Tape Server *
* Copyright (C) 2013 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#include "Regex.hh"
#include "../Exception/Exception.hh"
#include <regex.h>
Tape::Utils::regex::regex(const char * re_str) : m_set(false) {
if (int rc = ::regcomp(&m_re, re_str, REG_EXTENDED)) {
std::string error("Could not compile regular expression: \"");
error += re_str;
error += "\"";
char re_err[1024];
if (::regerror(rc, &m_re, re_err, sizeof (re_err))) {
error += ": ";
error += re_err;
}
throw Tape::Exception(error);
}
m_set = true;
}
Tape::Utils::regex::~regex() {
if (m_set)
::regfree(&m_re);
}
std::vector<std::string> Tape::Utils::regex::exec(const std::string &s) {
regmatch_t matches[100];
if (REG_NOMATCH != ::regexec(&m_re, s.c_str(), 100, matches, 0)) {
std::vector<std::string> ret;
for (int i = 0; i < 100; i++) {
if (matches[i].rm_so != -1) {
ret.push_back(s.substr(matches[i].rm_so, matches[i].rm_eo - matches[i].rm_so + 1));
} else
break;
}
return ret;
}
return std::vector<std::string>();
}
// ----------------------------------------------------------------------
// File: Utils/Regex.hh
// Author: Eric Cano - CERN
// ----------------------------------------------------------------------
/************************************************************************
* Tape Server *
* Copyright (C) 2013 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#pragma once
#include <vector>
#include <string>
#include <regex.h>
/**
* Semi-trivial wrapper to regex library, mostly
* useful for its destructor which will allow
* RAII.
*/
namespace Tape {
namespace Utils {
class regex {
public:
regex(const char * re_str);
virtual ~regex();
std::vector<std::string> exec(const std::string &s);
private:
regex_t m_re;
bool m_set;
}; /* class regex */
}; /* namespace Utils */
}; /* namespace Tape */
// ----------------------------------------------------------------------
// File: Utils/RegexTest.cc
// Author: Eric Cano - CERN
// ----------------------------------------------------------------------
/************************************************************************
* Tape Server *
* Copyright (C) 2013 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gmock/gmock-cardinalities.h>
#include "Regex.hh"
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::_;
TEST(Regex, BasicFunctionality) {
Tape::Utils::regex re("a(b)");
std::vector<std::string> ret1, ret2;
ret1 = re.exec("1abc");
ret2 = re.exec("xyz");
ASSERT_EQ(ret1.size(), 2);
ASSERT_EQ(ret1[0], "ab");
ASSERT_EQ(ret1[1], "b");
ASSERT_EQ(ret2.size(), 0);
}
TEST(Regex, OperationalTest) {
Tape::Utils::regex re("^scsi_tape:(st[[:digit:]]+)$");
std::vector<std::string> ret1, ret2, ret3;
ret1 = re.exec("scsi_tape:st1");
ret2 = re.exec("scsi_tape:st124");
ret3 = re.exec("scsi_tape:st1a");
ASSERT_EQ(ret1.size(), 2);
ASSERT_EQ(ret1[0], "scsi_tape:st1");
ASSERT_EQ(ret1[1], "st1");
ASSERT_EQ(ret2.size(), 2);
ASSERT_EQ(ret2[0], "scsi_tape:st124");
ASSERT_EQ(ret2[1], "st124");
ASSERT_EQ(ret3.size(), 0);
}
add_library(System Wrapper.cc)
\ No newline at end of file
add_library(System Wrapper.cc)
......@@ -138,6 +138,18 @@ int Tape::System::fakeWrapper::close(int fd) {
return 0;
}
int Tape::System::fakeWrapper::stat(const char* path, struct stat* buf) {
/*
* Mimic stat. See man 2 stat
*/
if (m_stats.end() == m_stats.find(std::string(path))) {
errno = ENOENT;
return -1;
}
*buf = m_stats[std::string(path)];
return 0;
}
void Tape::System::mockWrapper::delegateToFake() {
ON_CALL(*this, opendir(_)).WillByDefault(Invoke(&fake, &fakeWrapper::opendir));
ON_CALL(*this, readdir(_)).WillByDefault(Invoke(&fake, &fakeWrapper::readdir));
......@@ -147,6 +159,7 @@ void Tape::System::mockWrapper::delegateToFake() {
ON_CALL(*this, open(_, _)).WillByDefault(Invoke(&fake, &fakeWrapper::open));
ON_CALL(*this, read(_, _, _)).WillByDefault(Invoke(&fake, &fakeWrapper::read));
ON_CALL(*this, close(_)).WillByDefault(Invoke(&fake, &fakeWrapper::close));
ON_CALL(*this, stat(_, _)).WillByDefault(Invoke(&fake, &fakeWrapper::stat));
}