Skip to content
Snippets Groups Projects
Commit 89632b72 authored by Eric Cano's avatar Eric Cano
Browse files

Added exceptions

Completed a first test harness based on a system wrapper, simulating sysfs and 3 generic scsi devices.
Created test using the harness.
Created SCSI::DeviceVector class, listing the devices on the system.
parent 5f5a2605
Branches
Tags
No related merge requests found
......@@ -57,6 +57,8 @@ set(CTEST_OUTPUT_ON_FAILURE 1)
# Add parts first in dependency order
add_subdirectory(SCSI)
add_subdirectory(Drive)
add_subdirectory(system)
add_subdirectory(Exception)
# .. and then tools, tests, and daemon
# add_subdirectory(tools)
......@@ -65,6 +67,12 @@ add_subdirectory(Drive)
# .. and of course, the tests
add_subdirectory(test)
###########################################################################
# compiler options
###########################################################################
set (CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -Wall -Werror -pedantic”)
###########################################################################
# documentation
###########################################################################
......
......@@ -21,6 +21,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#pragma once
/**
* @file Drive.hh
*
......
......@@ -21,6 +21,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#pragma once
#include <vector>
......
add_library(Exception Exception.cc)
// ----------------------------------------------------------------------
// File: Exception/Exception.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 "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>
#include <sstream>
Tape::Exceptions::Errnum::Errnum(std::string what):Exception(what) {
m_errnum = errno;
char s[1000];
if (::strerror_r(m_errnum, s, sizeof(s))) {
int new_errno = errno;
std::stringstream w;
w << "Errno=" << m_errnum << ". In addition, failed to read the corresponding error string (with errno="
<< new_errno << ")";
m_strerror = w.str();
} else {
m_strerror = std::string(s);
}
std::stringstream w2;
w2 << "Errno=" << m_errnum << ": " << m_strerror;
if (m_what.size())
m_what += " ";
m_what += w2.str();
}
// ----------------------------------------------------------------------
// File: Exception/Exception.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 <exception>
#include <string>
namespace Tape {
class Exception: public std::exception {
public:
Exception(const std::string& what): m_what(what) {};
virtual ~Exception() throw() {};
virtual const char * what() { return m_what.c_str(); }
protected:
std::string m_what;
};
}
namespace Tape {
namespace Exceptions {
class Errnum: public Tape::Exception {
public:
Errnum(std::string what = "");
virtual ~Errnum() throw() {};
int ErrorNumber() { return m_errnum; }
std::string strError() { return m_strerror; }
virtual const char * what() { return m_what.c_str(); }
protected:
int m_errnum;
std::string m_strerror;
};
}
}
add_library(SCSI Device.cc)
\ No newline at end of file
add_library(SCSI Device.cc)
#add_executable(SCSIDumpTest DumpTest.cc)
#target_link_libraries(SCSIDumpTest SCSI)
\ No newline at end of file
......@@ -21,26 +21,34 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#pragma once
#include <string>
#include <vector>
#include <sys/types.h>
#include <dirent.h>
#include "../system/Wrapper.hh"
#include "../Exception/Exception.hh"
#include <scsi/scsi.h>
namespace SCSI {
/**
/**
* Bare-bones representation of a SCSI device
*/
struct Device {
std::string sg_path;
struct DeviceInfo {
std::string sysfs_entry;
int type;
std::string sg_dev;
};
/**
* Automatic lister of the system's SCSI devices
* @param sysWrapper
*/
template<class sysWrapperClass>
class DeviceList : public std::vector<Device> {
class DeviceVector : public std::vector<DeviceInfo> {
public:
/**
* Fill up the array that the device list is with all the system's
......@@ -48,15 +56,83 @@ namespace SCSI {
*
* (all code using templates must be in the header file)
*/
DeviceList(sysWrapperClass & sysWrapper) {
/**
*
* @param sysWrapper
*/
DeviceVector(sysWrapperClass & sysWrapper) : m_sysWrapper(sysWrapper) {
std::string sysDevsPath = "/sys/bus/scsi/devices";
DIR* dirp = sysWrapper.opendir(sysDevsPath.c_str());
if (!dirp) return; /* TODO: throw exception? */
while (struct dirent * dent = sysWrapper.readdir(dirp)) {
struct Device dev;
dev.sg_path = sysDevsPath + "/" + dent->d_name;
DIR* dirp = m_sysWrapper.opendir(sysDevsPath.c_str());
if (!dirp) throw Tape::Exceptions::Errnum("Error opening sysfs scsi devs");
while (struct dirent * dent = m_sysWrapper.readdir(dirp)) {
std::string fullpath = sysDevsPath + "/" + std::string(dent->d_name);
/* 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();
this->push_back(getDeviceInfo(rp));
}
sysWrapper.closedir(dirp);
}
};
private:
#undef ConvenientCoding
#ifdef ConvenientCoding
synthax error here!!!; /* So we don't compile in this configuration */
/* This makes code completion more efficient in editors */
Tape::System::fakeWrapper & m_sysWrapper;
#else
sysWrapperClass & m_sysWrapper;
#endif
static const size_t readfileBlockSize = 1024;
std::string readfile(std::string path) {
int fd = m_sysWrapper.open(path.c_str(), 0);
if (-1 == fd) {
throw Tape::Exceptions::Errnum();
}
char buf[readfileBlockSize];
std::string ret;
while (ssize_t sread = m_sysWrapper.read(fd, buf, readfileBlockSize)) {
if (-1 == sread) throw Tape::Exceptions::Errnum();
ret.append(buf, sread);
}
if (m_sysWrapper.close(fd)) throw Tape::Exceptions::Errnum();
return ret;
}
/**
* Extract information from sysfs about a SCSI device.
* @param path Path to the directory with information about
* @return
*/
DeviceInfo getDeviceInfo(const char * path) {
DeviceInfo ret;
ret.sysfs_entry = path;
std::string buf;
/* Get device type */
{
buf = readfile(ret.sysfs_entry + "/type");
if (!sscanf(buf.c_str(), "%d", &ret.type))
throw Tape::Exception(std::string("Could not parse file: ") + ret.sysfs_entry + "/type");
}
/* Get name of sg device */
{
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();
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);
}
return ret;
}
}; /* class DeviceVector */
};
......@@ -40,5 +40,36 @@ TEST(DeviceList, TriesToFind) {
EXPECT_CALL(sysWrapper, readdir(sysWrapper.m_DIR)).Times(1);
EXPECT_CALL(sysWrapper, closedir(sysWrapper.m_DIR)).Times(1);
SCSI::DeviceList<Tape::System::mockWrapper> dl(sysWrapper);
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, closedir(_)).Times(1);
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);
/* 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].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");
}
// ----------------------------------------------------------------------
// File: SCSI/DumpTest.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/>.*
************************************************************************/
/**
* Test main program. For development use.
*/
#include "../system/Wrapper.hh"
#include "Device.hh"
#include <iostream>
int main ()
{
Tape::System::realWrapper sWrapper;
SCSI::DeviceVector<Tape::System::realWrapper> dl(sWrapper);
for(SCSI::DeviceVector<Tape::System::realWrapper>::iterator i = dl.begin();
i != dl.end(); i++) {
SCSI::DeviceInfo & dev = (*i);
std::cout << dev.sg_path << std::endl;
}
}
add_library(System Wrapper.cc)
\ No newline at end of file
// ----------------------------------------------------------------------
// File: System/Wrapper.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/>.*
************************************************************************/
#include "Wrapper.hh"
#include <errno.h>
#include <limits.h>
#include <stdexcept>
using ::testing::_;
using ::testing::Invoke;
DIR* Tape::System::fakeWrapper::opendir(const char* name) {
/* Manage absence of directory */
if (m_directories.end() == m_directories.find(std::string(name))) {
errno = ENOENT;
return NULL;
}
/* Dirty pointer gymnastics. Good enough for a test harness */
ourDIR * dir = new ourDIR;
dir->nextIdx = 0;
dir->path = name;
return (DIR*) dir;
}
int Tape::System::fakeWrapper::closedir(DIR* dirp) {
delete ((ourDIR *) dirp);
return 0;
}
struct dirent * Tape::System::fakeWrapper::readdir(DIR* dirp) {
/* Dirty pointer gymnastics. Good enough for a test harness */
ourDIR & dir = *((ourDIR *) dirp);
/* Check we did not reach end of directory. This will create a new
* entry in the map if it does not exist, but we should be protected by
* opendir.
*/
if (dir.nextIdx + 1 > m_directories[dir.path].size())
return NULL;
dir.dent_name = m_directories[dir.path][dir.nextIdx++];
strncpy(dir.dent.d_name, dir.dent_name.c_str(), NAME_MAX);
return & (dir.dent);
}
int Tape::System::fakeWrapper::readlink(const char* path, char* buf, size_t len) {
/*
* Mimic readlink. see man 3 readlink.
*/
if (m_links.end() == m_links.find(std::string(path))) {
errno = ENOENT;
return -1;
}
const std::string & link = m_links[std::string(path)];
strncpy(buf, link.c_str(), len);
return len > link.size() ? link.size() : len;
}
char * Tape::System::fakeWrapper::realpath(const char* name, char* resolved) {
/*
* Mimic realpath. see man 3 realpath.
*/
if (m_realpathes.end() == m_realpathes.find(std::string(name))) {
errno = ENOENT;
return NULL;
}
strncpy(resolved, m_realpathes[std::string(name)].c_str(), PATH_MAX);
return resolved;
}
int Tape::System::fakeWrapper::open(const char* file, int oflag) {
/*
* Mimic open. See man 2 open.
* We only allow read for the moment.
*/
if (oflag & (O_APPEND | O_CREAT)) {
errno = EACCES;
return -1;
}
if (m_files.end() == m_files.find(std::string(file))) {
errno = ENOENT;
return -1;
}
int ret = m_nextFD++;
m_openFiles[ret].data = m_files[std::string(file)];
m_openFiles[ret].read_pointer = 0;
return ret;
}
ssize_t Tape::System::fakeWrapper::read(int fd, void* buf, size_t nbytes) {
/*
* Mimic read. See man 2 read
*/
if (m_openFiles.end() == m_openFiles.find(fd)) {
errno = EBADF;
return -1;
}
/*
* Read. std::string::copy nicely does the heavy lifting.
*/
try {
size_t ret;
ret = m_openFiles[fd].data.copy((char *) buf, nbytes, m_openFiles[fd].read_pointer);
m_openFiles[fd].read_pointer += ret;
return ret;
} catch (std::out_of_range & e) {
return 0;
}
}
int Tape::System::fakeWrapper::close(int fd) {
/*
* Mimic close. See man 2 close
*/
if (m_openFiles.end() == m_openFiles.find(fd)) {
errno = EBADF;
return -1;
}
m_openFiles.erase(fd);
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));
ON_CALL(*this, closedir(_)).WillByDefault(Invoke(&fake, &fakeWrapper::closedir));
ON_CALL(*this, readlink(_, _, _)).WillByDefault(Invoke(&fake, &fakeWrapper::readlink));
ON_CALL(*this, realpath(_, _)).WillByDefault(Invoke(&fake, &fakeWrapper::realpath));
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));
}
void Tape::System::fakeWrapper::setupSLC5() {
/*
* Setup an tree similar to what we'll find in
* and SLC5 system with mvhtl library (one media exchanger, 2 drives)
*/
/*
* First of, the description of all devices in sysfs.
* In SLC5, sysfs is mounted on /sys/. If other mount point appear in the
* future, we'll have to provide /proc/mounts (and use it).
* SLC6 is similar, so this is not necessary at the time of writing.
*/
m_directories["/sys/bus/scsi/devices"].push_back("3:0:0:0");
m_directories["/sys/bus/scsi/devices"].push_back("3:0:1:0");
m_directories["/sys/bus/scsi/devices"].push_back("3:0:2:0");
m_realpathes["/sys/bus/scsi/devices/3:0:0:0"]
= "/sys/devices/pseudo_0/adapter0/host3/target3:0:0/3:0:0:0";
m_realpathes["/sys/bus/scsi/devices/3:0:1:0"]
= "/sys/devices/pseudo_0/adapter0/host3/target3:0:1/3:0:1:0";
m_realpathes["/sys/bus/scsi/devices/3:0:2:0"]
= "/sys/devices/pseudo_0/adapter0/host3/target3:0:2/3:0:2:0";
m_files["/sys/devices/pseudo_0/adapter0/host3/target3:0:0/3:0:0:0/type"] = "8\n";
m_files["/sys/devices/pseudo_0/adapter0/host3/target3:0:1/3:0:1:0/type"] = "1\n";
m_files["/sys/devices/pseudo_0/adapter0/host3/target3:0:2/3:0:2:0/type"] = "1\n";
m_links["/sys/devices/pseudo_0/adapter0/host3/target3:0:0/3:0:0:0/generic"]
= "../../../../../../class/scsi_generic/sg2";
m_links["/sys/devices/pseudo_0/adapter0/host3/target3:0:1/3:0:1:0/generic"]
= "../../../../../../class/scsi_generic/sg0";
m_links["/sys/devices/pseudo_0/adapter0/host3/target3:0:2/3:0:2:0/generic"]
= "../../../../../../class/scsi_generic/sg1";
}
// ----------------------------------------------------------------------
// File: System/realWrapper.hh
// File: System/Wrapper.hh
// Author: Eric Cano - CERN
// ----------------------------------------------------------------------
......@@ -21,17 +21,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#pragma once
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <gmock/gmock.h>
#include <map>
#include <vector>
#include <string>
#include <fstream>
namespace Tape {
namespace System {
/**
* Wrapper class the all system calls used, allowing writing of test harnesses
* for unit testing.
* The member function are purposedly non-virtual, allowing full
* The member functions are purposedly non-virtual, allowing full
* performance with inline member functions.
*/
class realWrapper{
......@@ -39,22 +46,87 @@ namespace System {
DIR* opendir(const char *name) { return ::opendir(name); }
struct dirent * readdir(DIR* dirp) { return ::readdir(dirp); }
int closedir(DIR* dirp) { return ::closedir(dirp); }
int readlink(const char* path, char* buf, size_t len) { return ::readlink(path, buf, len); }
char * realpath(const char* name, char* resolved) { return ::realpath(name, resolved); }
int open(const char* file, int oflag) { return ::open(file, oflag); }
ssize_t read(int fd, void* buf, size_t nbytes) { return ::read( fd, buf, nbytes); }
int close(int fd) { return ::close(fd); }
};
/**
* Intermediate class definition, allowing common ancestor between
* mockWrapper and fakeWrapper (pure virtual)
*/
class virtualWrapper {
public:
virtual DIR* opendir(const char *name) = 0;
virtual struct dirent * readdir(DIR* dirp) = 0;
virtual int closedir(DIR* dirp) = 0;
virtual int readlink(const char* path, char* buf, size_t len) = 0;
virtual char * realpath(const char* name, char* resolved) = 0;
virtual int open(const char* file, int oflag) = 0;
virtual ssize_t read(int fd, void* buf, size_t nbytes) = 0;
virtual int close(int fd) = 0;
};
/**
* Fake class for system wrapper. Allows recording of pre-cooked filesystem elements,
* once for each call separately.
* Each test can then delegate (from mock) and configure
*/
class fakeWrapper: public virtualWrapper {
public:
fakeWrapper(): m_nextFD(0) {};
virtual DIR* opendir(const char *name);
virtual struct dirent * readdir(DIR* dirp);
virtual int closedir(DIR* dirp);
virtual int readlink(const char* path, char* buf, size_t len);
virtual char * realpath(const char* name, char* resolved);
virtual int open(const char* file, int oflag);
virtual ssize_t read(int fd, void* buf, size_t nbytes);
virtual int close(int fd);
std::map<std::string, std::vector<std::string> > m_directories;
std::map<std::string, std::string> m_links;
std::map<std::string, std::string> m_realpathes;
std::map<std::string, std::string> m_files;
void setupSLC5();
private:
struct ourDIR {
std::string path;
int nextIdx;
struct dirent dent;
std::string dent_name;
};
struct outFD {
std::string data;
size_t read_pointer;
};
std::map<int, outFD> m_openFiles;
int m_nextFD;
};
/**
* Mock class for system wrapper, used to develop tests.
*/
class mockWrapper {
class mockWrapper: public virtualWrapper {
public:
mockWrapper(): m_DIR((DIR*)&m_DIRfake) {
ON_CALL(*this, opendir(::testing::_))
.WillByDefault(::testing::Return(m_DIR));
}
MOCK_METHOD1(opendir, DIR*(const char *name));
MOCK_METHOD1(readdir, struct dirent*(DIR* dirp));
MOCK_METHOD1(readdir, dirent*(DIR* dirp));
MOCK_METHOD1(closedir, int(DIR* dirp));
int m_DIRfake;
MOCK_METHOD3(readlink, int(const char* path, char* buf, size_t len));
MOCK_METHOD2(realpath, char *(const char* name, char* resolved));
MOCK_METHOD2(open, int(const char* file, int oflag));
MOCK_METHOD3(read, ssize_t(int fd, void* buf, size_t nbytes));
MOCK_METHOD1(close, int(int fd));
DIR* m_DIR;
int m_DIRfake;
void delegateToFake();
fakeWrapper fake;
};
} // namespace System
} // namespace Tape
add_executable(unitTest unitTest.cc ../SCSI/DeviceTest.cc)
target_link_libraries(unitTest SCSI ${GTEST_LIBRARY} gmock pthread)
target_link_libraries(unitTest SCSI System Exception ${GTEST_LIBRARY} gmock pthread)
install(TARGETS unitTest
RUNTIME DESTINATION bin)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment