/*
* @project The CERN Tape Archive (CTA)
* @copyright Copyright(C) 2003-2021 CERN
* @license 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 .
*/
#include
#include
#include "Device.hpp"
#include "common/exception/Errnum.hpp"
using namespace castor::tape;
/**
* 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)
*/
SCSI::DeviceVector::DeviceVector(System::virtualWrapper& sysWrapper) : m_sysWrapper(sysWrapper) {
std::string sysDevsPath = "/sys/bus/scsi/devices";
cta::utils::Regex ifFirstCharIsDigit("^[[:digit:]]");
std::vector checkResult;
DIR* dirp;
cta::exception::Errnum::throwOnNull(
dirp = m_sysWrapper.opendir(sysDevsPath.c_str()),
"Error opening sysfs scsi devs");
while (struct dirent * dent = m_sysWrapper.readdir(dirp)) {
std::string dn(dent->d_name);
if ("." == dn || ".." == dn) continue;
checkResult = ifFirstCharIsDigit.exec(dn);
if (0 == checkResult.size()) continue; /* we only use digital names like 6:0:3:0 */
std::string fullpath = sysDevsPath + "/" + std::string(dent->d_name);
/* We expect only symbolic links in this directory, */
char rp[PATH_MAX];
cta::exception::Errnum::throwOnNull(
m_sysWrapper.realpath(fullpath.c_str(), rp),
"Could not find realpath for " + fullpath);
this->push_back(getDeviceInfo(rp));
}
sysWrapper.closedir(dirp);
}
SCSI::DeviceInfo & SCSI::DeviceVector::findBySymlink(std::string path) {
struct stat sbuff;
cta::exception::Errnum::throwOnMinusOne(
m_sysWrapper.stat(path.c_str(), &sbuff),
std::string("Could not stat path: ")+path);
for(std::vector::iterator i = begin(); i!= end(); i++) {
if (i->nst == sbuff || i->st == sbuff) {
return *i;
}
}
throw SCSI::DeviceVector::NotFound(
std::string("Could not find tape device pointed to by ") + path);
}
std::string SCSI::DeviceVector::readfile(std::string path) {
int fd;
cta::exception::Errnum::throwOnMinusOne(
fd = m_sysWrapper.open(path.c_str(), 0),
std::string("Could not open file ") + path);
char buf[readfileBlockSize];
std::string ret;
while (ssize_t sread = m_sysWrapper.read(fd, buf, readfileBlockSize)) {
cta::exception::Errnum::throwOnMinusOne(sread,
std::string("Could not read from open file ") + path);
ret.append(buf, sread);
}
cta::exception::Errnum::throwOnNonZero(
m_sysWrapper.close(fd),
std::string("Error closing file ") + path);
return ret;
}
SCSI::DeviceInfo::DeviceFile SCSI::DeviceVector::readDeviceFile(std::string path) {
DeviceInfo::DeviceFile ret;
std::string file = readfile(path);
if (!::sscanf(file.c_str(), "%u:%u\n", &ret.major, &ret.minor))
throw cta::exception::Exception(std::string("Could not parse file: ") + path);
return ret;
}
SCSI::DeviceInfo::DeviceFile SCSI::DeviceVector::statDeviceFile(std::string path) {
struct stat sbuf;
cta::exception::Errnum::throwOnNonZero(
m_sysWrapper.stat(path.c_str(), &sbuf),
std::string("Could not stat file ") + path);
if (!S_ISCHR(sbuf.st_mode))
throw cta::exception::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;
}
/**
* Part factored out of getDeviceInfo: get the tape specifics
* from sysfs.
* @param devinfo
*/
void SCSI::DeviceVector::getTapeInfo(DeviceInfo & devinfo) {
/* Find the st and nst devices for this SCSI device */
DIR * dirp;
/* SLC6 default tapeDir and scsiPrefix */
std::string tapeDir="/scsi_tape"; /* The name of the tape dir inside of
* devinfo.sysfs_entry. /scsi_tape/ on SLC6.
*/
std::string scsiPrefix="^"; /* The prefix for the name for the tape
* device name for the regexp.
* scsi_tape: on SLC5.
*/
/* we try to open /scsi_tape first for SLC6 */
dirp = m_sysWrapper.opendir((devinfo.sysfs_entry+tapeDir).c_str());
if (!dirp) {
/* here we open sysfs_entry for SLC5 */
cta::exception::Errnum::throwOnNull(
dirp = m_sysWrapper.opendir(devinfo.sysfs_entry.c_str()),
std::string("Error opening tape device directory ") +
devinfo.sysfs_entry + tapeDir+" or "+devinfo.sysfs_entry);
/* we are not on SLC6 */
scsiPrefix="^scsi_tape:";
tapeDir ="";
}
cta::utils::Regex st_re((scsiPrefix+"(st[[:digit:]]+)$").c_str());
cta::utils::Regex nst_re((scsiPrefix+"(nst[[:digit:]]+)$").c_str());
while (struct dirent * dent = m_sysWrapper.readdir(dirp)) {
std::vector 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 cta::exception::Exception("Matched st device several times!");
/* Read the major and major number */
devinfo.st = readDeviceFile(devinfo.sysfs_entry + tapeDir+ "/"
+ 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 cta::exception::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 cta::exception::Exception("Matched nst device several times!");
/* Read the major and major number */
devinfo.nst = readDeviceFile(devinfo.sysfs_entry + tapeDir + "/"
+ 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 cta::exception::Exception(err.str());
}
}
}
m_sysWrapper.closedir(dirp);
}
/**
* Extract information from sysfs about a SCSI device.
* @param path Path to the directory with information about
* @return
*/
SCSI::DeviceInfo SCSI::DeviceVector::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 cta::exception::Exception(std::string("Could not parse file: ") + ret.sysfs_entry + "/type");
}
/* Get vendor (trimmed of trailing newline, not of spaces) */
{
buf = readfile(ret.sysfs_entry + "/vendor");
size_t endpos = buf.find_first_of("\n ");
ret.vendor = buf.substr(0, endpos);
}
/* Get model (trimmed of trailing newline, not of spaces) */
{
buf = readfile(ret.sysfs_entry + "/model");
size_t endpos = buf.find_first_of("\n ");
ret.product = buf.substr(0, endpos);
}
/* Get revision (trimmed of trailing newline, not of spaces) */
{
buf = readfile(ret.sysfs_entry + "/rev");
size_t endpos = buf.find_first_of("\n ");
ret.productRevisionLevel = buf.substr(0, endpos);
}
/* Get name of sg device */
{
char rl[PATH_MAX];
std::string lp = ret.sysfs_entry + "/generic";
ssize_t rlSize;
cta::exception::Errnum::throwOnMinusOne(
rlSize = m_sysWrapper.readlink(lp.c_str(), rl, sizeof (rl) - 1),
std::string("Could not read link ") + lp);
rl[rlSize] = '\0';
std::string gl(rl);
size_t pos = gl.find_last_of("/");
if (pos == std::string::npos)
throw cta::exception::Exception(std::string("Could not find last / in link: ") + gl +
" read from " + ret.sysfs_entry + "/generic");
ret.sg_dev = std::string("/dev/") + gl.substr(pos + 1);
}
/* Get the major and minor number of the device file */
ret.sg = readDeviceFile(ret.sysfs_entry + "/generic/dev");
/* Check that we have an agreement with the actual device file */
DeviceInfo::DeviceFile realFile = statDeviceFile(ret.sg_dev);
if (ret.sg != realFile) {
std::stringstream err;
err << "Mismatch between sysfs info and actual device file: "
<< ret.sysfs_entry + "/generic/dev" << " indicates "
<< ret.sg.major << ":" << ret.sg.minor
<< " while " << ret.sg_dev << " is: "
<< realFile.major << ":" << realFile.minor;
throw cta::exception::Exception(err.str());
}
/* Handle more if we have a tape device */
if (Types::tape == ret.type)
getTapeInfo(ret);
return ret;
}