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

Added boundary tests for strings in SCSI structs.

Added stack trace support for exceptions (with C++ name demandling).
Added more anaysis of the past code in documentation.
parent 1c09ecbf
......@@ -35,9 +35,12 @@ More details regarding the Generic SCSI driver can be found in this web site:
The section on the SG\_IO ioctl, \href{\_io.html}{\_io.html} details the usage of the
simples ioctl for the generic SCSI driver, which allows the invocation of a SCSI command and the collection of the
simplest ioctl for the generic SCSI driver, which allows the invocation of a SCSI command and the collection of the
result in a single system call.
This ioctl is provided in the middle layer of the SCSI subsystem of Linux. In practice for us, it means that this
ioctl is available also to the st driver device files. That means there is no need to open the matching generic SCSI.
\subsection{Unsorted CASTOR docs}
There is a collection of links to various documentations written in the past on this page:
......@@ -52,7 +55,7 @@ There is a collection of links to various documentations written in the past on
\item{}Google Mock/Google test (GTest is provided in EPEL repository for SLC.
GMock requires recompilation (TODO: link to source)
\item{}Valgrind (Basic SLC version)
\item{}\LaTeX (Basic SLC version)
\item{}\LaTeX (Basic SLC version) to compile this document
\item{}Doxygen for code documentation (Basic SLC version)
......@@ -62,15 +65,80 @@ There is a collection of links to various documentations written in the past on
\item{}NetBeans as an IDE, including for remote development\
\section{Classes layout}
\section{Software layout}
\subsection{The Tape::Drive object}
This first deliverable is a tape drive object. This tape drive object abstracts all
SCSI and technical details and provides a high level interface, to be used by the
file structure layer.
It will provide as much data safety as possible by blocking writes in situations
where they are not safe (to be defined in details, but the most obvious is right
after positioning, as we should double check where we are before writing).
The SCSI commands and st driver's functions used in previous software (CASTOR's taped/rtcpd) are:
\item Individual SCSI commands sent using generic SCSI.
\item Read status (inquiry SCSI command in ????)
\item Read serial number (inquiry SCSI command, asking for vital product data page 0x80)
\item Locate (locate(10) SCSI command: 32 bits logical object identifiers)
\footnote{There is also a locate(16) command allowing 64 bis addresses.
This might become necessary as tapes grow. Discounting the per-file overhead,
with 256kB block, it still takes 1PB to get $2^{32}$ blocks.}
\item Read position (read position SCSI command -- short form): get the current logical object
location (a.k.a. block ID).
\item Log select (for clearing compression stats page. The function clear\_compression\_stats
actually does a blanket reset of all statistics. It sets the PCR/SP/PC combination
to 1/0/3, which is strange as the spec indicates 1/0/xx (xx for don't care).
\item Log sense, to read the compression pages. This is device dependant. The code covers
5 blocks of device types: DAT, DLT-SDLT-LTO, IBM(3490, 3590, 3592), StorageTek RedWood(SD3),
StorageTek(9840, 9940, T10000).
\item Log sense for page 0x2E (???) on all modern tape drives to detect tape alerts (TBD, see SSC-3).
\item st driver's commands, leading to internal variables setting or SCSI actions
\item Get internal driver state via the MTIOGET ioctl (for drivre ready, write protection,
get some error condition, when MTIOSENSE failed, to get the EOD, BOT bits (readlbl)).
\item Try and get the sense data for the last-ish command with MTIOSENSE. This can
relies on a CERN-made patch.
\item Setup the driver's buffers (MTIOCTOP/MTSETDRVBUFFER) for setting on or off
buffered writes and asynchronous writes (in confdrive, a child of taped).
\item Jump to end of media (before rewinding, as a mean to rebuild the MIR) (MTIOCTOP/MTEOM,
with some MTIOCTOP/MTSETDRVBUFFER before, in repairbadmir). TODO: find undelying SCSI.
\item Rewind (MTIOCTOP/MTREW, in rwndtape).
\item Skip to end of data (MTIOCTOP/MTEOM, in skip2eod, without the trick of repairbadmir).
\item Skip n files backwards (MTIOCTOP/MTBSF, in skiptpfb).
\item Skip n files forward (MTIOCTOP/MTFSF, in skiptpff).
\item Skip n files forward (MTIOCTOP/MTFSF, in skiptpfff). skiptpfff and skiptpff differ only
by error reporting. Both functions exists since CASTOR has been put in SVN (20/07/1999)
\item Skip n blocks backwards (MTIOCTOP/MTBSR, in skiptprb).
\item Skip n blocks forward (MTIOCTOP/MTFSR, in skiptprf).
\item Unload the tape (MTIOCTOP/MTOFFL, in unldtape).
\item Write synchronous file mark(s) (tape marks in CASTOR jargon) (MTIOCTOP/MTWEOF, in wrttpmrk).
\item Write immediate (asynchronous file marks (MTIOCTOP/MTWEOFI, also in wrttpmrk).
\item Clear the EOT condition by calling MTIOGET. This is done in wrttrllbl, 3 times.
TODO: investigate the effect in st driver.
\item Write is used in 2 places only : twrite and writelbl (which is a specialized
function to write 80 bytes blocks). twrite is not checking the size of blocks,
which is determined in the calling functions.
\item Read is used in tread, which is used in a single place of TapeToMemory. It is
also used in readlbl. The latter uses a trick to detect that a tape is blank.
This could be turned into a specialized function.
The interface is shown in \ref{drive_if}.
\begin{lstlisting}[caption=Code example,label=code1]
namespace Test {
class Test {
\begin{lstlisting}[caption=Tape::Drive interface,label=drive_if]
namespace Tape {
class Drive {
Test() {}
} // namespace Test
} // namespace Tape
......@@ -28,6 +28,14 @@
#include <sstream>
#include <iosfwd>
#include <sstream>
#include <execinfo.h>
#include <cxxabi.h>
const char * Tape::Exception::what() const throw () {
std::stringstream w;
w << m_what << std::endl << std::string(backtrace);
return w.str().c_str();
Tape::Exceptions::Errnum::Errnum(std::string what):Exception(what) {
m_errnum = errno;
......@@ -49,3 +57,40 @@ Tape::Exceptions::Errnum::Errnum(std::string what):Exception(what) {
m_what += " ";
m_what += w2.str();
Tape::Exceptions::Backtrace::Backtrace() {
void * array[200];
size_t depth = ::backtrace(array, sizeof(array)/sizeof(void*));
char ** strings = ::backtrace_symbols(array, depth);
if (!strings)
m_trace = "";
else {
std::stringstream trc;
for (int i=0; i<depth; i++) {
std::string line(strings[i]);
/* Demangle the c++, if possible. We expect the c++ function name's to live
* between a '(' and a +
* line format: /usr/lib/ [0x12345] */
if ((std::string::npos != line.find("(")) && (std::string::npos != line.find("+"))) {
std::string before, theFunc, after;
before = line.substr(0, line.find("(")+1);
theFunc = line.substr(line.find("(")+1, line.find("+") - (line.find("(") + 1));
after = line.substr(line.find("+"), std::string::npos);
int status(-1);
char demangled[200];
size_t length(sizeof(demangled));
abi::__cxa_demangle(theFunc.c_str(), demangled, &length, &status);
if (0 == status)
trc << before << demangled << after << " (C++ demangled)" << std::endl;
trc << strings[i] << std::endl;
} else {
trc << strings[i] << std::endl;
free (strings);
m_trace = trc.str();
......@@ -26,17 +26,27 @@
#include <string>
namespace Tape {
namespace Exceptions {
class Backtrace {
operator std::string() const { return m_trace; }
std::string m_trace;
class Exception: public std::exception {
Exception(const std::string& what): m_what(what) {};
virtual ~Exception() throw() {};
virtual const char * what() const throw() { return m_what.c_str(); }
virtual const char * what() const throw();
Tape::Exceptions::Backtrace backtrace;
std::string m_what;
namespace Tape {
namespace Exceptions {
class Errnum: public Tape::Exception {
......@@ -44,7 +54,8 @@ namespace Tape {
virtual ~Errnum() throw() {};
int ErrorNumber() { return m_errnum; }
std::string strError() { return m_strerror; }
virtual const char * what() const throw() { return m_what.c_str(); }
/* We rely on base version:
* virtual const char * what() const throw() */
int m_errnum;
std::string m_strerror;
// ----------------------------------------------------------------------
// File: Exception/
// 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 *
* 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 "Exception.hh"
#include <errno.h>
#include <gtest/gtest.h>
#include <gmock/gmock-cardinalities.h>
namespace UnitTests {
class Nested {
void f1();
void f2();
/* Prevent inlining: it makes this test fail! */
void __attribute__((noinline)) Nested::f1() {
throw Tape::Exception("");
/* Prevent inlining: it makes this test fail!
* Even with that, f2 does not show up in the trace */
void __attribute__((noinline)) Nested::f2() {
/* Prevent inlining: it makes this test fail! */
__attribute__((noinline)) Nested::Nested() {
TEST(Exceptions, stacktrace_with_demangling) {
try {
Nested x;
} catch (Tape::Exception & e) {
std::string bt = e.backtrace;
ASSERT_NE(std::string::npos, bt.find("Nested::f1"));
ASSERT_NE(std::string::npos, bt.find("Tape::Exceptions::Backtrace::Backtrace"));
......@@ -32,7 +32,7 @@ using ::testing::Return;
using ::testing::_;
namespace UnitTests {
TEST(SCSIStructures, inquiryData_t_and_multi_byte_numbers) {
TEST(SCSI_Structures, inquiryData_t_multi_byte_numbers_strings) {
/* Validate the bit field behavior of the struct inquiryData_t,
which represents the standard INQUIRY data format as defined in
SPC-4. This test also validates the handling of multi-bytes numbers,
......@@ -61,7 +61,7 @@ namespace UnitTests {
inqBuff[7] |= (0x1 & 0x1) << 4;
ASSERT_EQ(1, inq.sync);
/* Test of strings */
/* Test of strings: empty/full/boundary effect with next record */
ASSERT_EQ("", SCSI::Structures::toString(inq.T10Vendor));
inqBuff[8] = 'V';
inqBuff[9] = 'i';
......@@ -70,9 +70,27 @@ namespace UnitTests {
inqBuff[12] = 'u';
inqBuff[13] = 'a';
inqBuff[14] = 'l';
inqBuff[15] = '\0';
ASSERT_EQ("Virtual", SCSI::Structures::toString(inq.T10Vendor));
inqBuff[15] = 's';
ASSERT_EQ("Virtuals", SCSI::Structures::toString(inq.T10Vendor));
/* Check there is no side effect from next record */
inqBuff[16] = 'X';
inqBuff[17] = 'X';
inqBuff[18] = 'X';
ASSERT_EQ("Virtuals", SCSI::Structures::toString(inq.T10Vendor));
ASSERT_EQ("XXX", SCSI::Structures::toString(inq.prodId));
/* Check that non-full record does not yield too long a string */
inqBuff[8] = 'T';
inqBuff[9] = 'a';
inqBuff[10] = 'p';
inqBuff[11] = 'e';
inqBuff[12] = 's';
inqBuff[13] = '\0';
inqBuff[14] = '\0';
inqBuff[15] = '\0';
ASSERT_EQ("Tapes", SCSI::Structures::toString(inq.T10Vendor));
/* Test of endian conversion */
ASSERT_EQ(0, SCSI::Structures::toU16(inq.versionDescriptor[7]));
inqBuff[72] = 0xCA;
......@@ -3,6 +3,7 @@ add_executable(unitTest
target_link_libraries(unitTest Exception SCSI System Utils ${GTEST_LIBRARY} gmock pthread)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment