Use C++ filesystem library/iostreams for I/O, added unit tests (#911) (#934)

* Use C++ filesystem library/iostreams for I/O (issue #911)

* Added unit tests
This commit is contained in:
Uwe Seimet 2022-10-25 10:29:57 +02:00 committed by GitHub
parent ea8bc3970d
commit 6bbaa956ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 824 additions and 734 deletions

View File

@ -92,9 +92,7 @@ SRC_SHARED = \
protobuf_serializer.cpp
SRC_RASCSI_CORE = \
bus.cpp \
filepath.cpp \
fileio.cpp
bus.cpp
SRC_RASCSI_CORE += $(shell find ./rascsi -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./controllers -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./devices -name '*.cpp')
@ -116,8 +114,7 @@ SRC_RASCTL = rasctl.cpp
SRC_RASDUMP = \
rasdump.cpp \
bus.cpp \
filepath.cpp \
fileio.cpp \
rasdump_fileio.cpp \
rascsi_version.cpp
SRC_RASDUMP += $(shell find ./hal -name '*.cpp')

View File

@ -27,6 +27,8 @@
using namespace scsi_defs;
const int ScsiController::LUN_MAX = 32;
ScsiController::ScsiController(shared_ptr<BUS> bus, int target_id) : AbstractController(bus, target_id, LUN_MAX)
{
// The initial buffer size will default to either the default buffer size OR

View File

@ -52,7 +52,7 @@ class ScsiController : public AbstractController
public:
// Maximum number of logical units
static const int LUN_MAX = 32;
static const int LUN_MAX;
ScsiController(shared_ptr<BUS>, int);
~ScsiController() override = default;

View File

@ -30,7 +30,7 @@ void CDTrack::Init(int track, uint32_t first, uint32_t last)
last_lba = last;
}
void CDTrack::SetPath(bool cdda, const Filepath& path)
void CDTrack::SetPath(bool cdda, string_view path)
{
assert(valid);
@ -41,12 +41,11 @@ void CDTrack::SetPath(bool cdda, const Filepath& path)
imgpath = path;
}
void CDTrack::GetPath(Filepath& path) const
string CDTrack::GetPath() const
{
assert(valid);
// Return the path (by reference)
path = imgpath;
return imgpath;
}
//---------------------------------------------------------------------------

View File

@ -14,7 +14,9 @@
#pragma once
#include "filepath.h"
#include <string>
using namespace std;
class CDTrack final
{
@ -26,8 +28,8 @@ public:
void Init(int track, uint32_t first, uint32_t last);
// Properties
void SetPath(bool cdda, const Filepath& path); // Set the path
void GetPath(Filepath& path) const; // Get the path
void SetPath(bool, string_view); // Set the path
string GetPath() const; // Get the path
uint32_t GetFirst() const; // Get the start LBA
uint32_t GetLast() const; // Get the last LBA
uint32_t GetBlocks() const; // Get the number of blocks
@ -41,5 +43,6 @@ private:
uint32_t first_lba = 0; // First LBA
uint32_t last_lba = 0; // Last LBA
bool audio = false; // Audio track flag
Filepath imgpath; // Image file path
string imgpath; // Image file path
};

View File

@ -19,7 +19,6 @@
#include "os.h"
#include "log.h"
#include "filepath.h"
#include "cfilesystem.h"
#include <dirent.h>
#include <iconv.h>

View File

@ -13,6 +13,10 @@
#pragma once
using TCHAR = char;
static const int FILEPATH_MAX = 260;
//---------------------------------------------------------------------------
//
// Status code definitions

View File

@ -373,20 +373,20 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
#endif
}
void CTapDriver::OpenDump(const Filepath& path) {
void CTapDriver::OpenDump(const string& path) {
if (m_pcap == nullptr) {
m_pcap = pcap_open_dead(DLT_EN10MB, 65535);
}
if (m_pcap_dumper != nullptr) {
pcap_dump_close(m_pcap_dumper);
}
m_pcap_dumper = pcap_dump_open(m_pcap, path.GetPath());
m_pcap_dumper = pcap_dump_open(m_pcap, path.c_str());
if (m_pcap_dumper == nullptr) {
LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap))
throw io_exception("Can't open pcap file");
}
LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.GetPath())
LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.c_str())
}
bool CTapDriver::Enable() const

View File

@ -13,7 +13,6 @@
#include <pcap/pcap.h>
#include <net/ethernet.h>
#include "filepath.h"
#include <unordered_map>
#include <list>
#include <string>
@ -33,7 +32,7 @@ public:
CTapDriver& operator=(const CTapDriver&) = default;
bool Init(const unordered_map<string, string>&);
void OpenDump(const Filepath& path); // Capture packets
void OpenDump(const string& path); // Capture packets
void GetMacAddr(BYTE *mac) const;
int Receive(BYTE *buf);
int Send(const BYTE *buf, int len);

View File

@ -14,7 +14,6 @@
//
//---------------------------------------------------------------------------
#include "fileio.h"
#include "rascsi_exceptions.h"
#include "dispatcher.h"
#include "scsi_command_util.h"
@ -79,17 +78,13 @@ bool Disk::Dispatch(scsi_command cmd)
void Disk::SetUpCache(off_t image_offset, bool raw)
{
Filepath path;
path.SetPath(GetFilename().c_str());
cache = make_unique<DiskCache>(path, size_shift_count, (uint32_t)GetBlockCount(), image_offset);
cache = make_unique<DiskCache>(GetFilename(), size_shift_count, (uint32_t)GetBlockCount(), image_offset);
cache->SetRawMode(raw);
}
void Disk::ResizeCache(const string& filename, bool raw)
void Disk::ResizeCache(const string& path, bool raw)
{
Filepath path;
path.SetPath(filename.c_str());
cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)GetBlockCount()));
cache.reset(new DiskCache(path, size_shift_count, (uint32_t)GetBlockCount()));
cache->SetRawMode(raw);
}
@ -501,8 +496,7 @@ int Disk::Read(const vector<int>&, vector<BYTE>& buf, uint64_t block)
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT);
}
// Success
return 1 << size_shift_count;
return GetSectorSizeInBytes();
}
int Disk::WriteCheck(uint64_t block)
@ -519,18 +513,14 @@ int Disk::WriteCheck(uint64_t block)
throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED);
}
// Success
return 1 << size_shift_count;
return GetSectorSizeInBytes();
}
void Disk::Write(const vector<int>&, const vector<BYTE>& buf, uint64_t block)
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
// Error if not ready
if (!IsReady()) {
throw scsi_exception(sense_key::NOT_READY);
}
CheckReady();
// Error if the total number of blocks is exceeded
if (block >= GetBlockCount()) {

View File

@ -14,18 +14,17 @@
//
//---------------------------------------------------------------------------
#include "os.h"
#include "disk_track.h"
#include "disk_cache.h"
#include <cstdlib>
#include <cassert>
DiskCache::DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff)
: sec_size(size), sec_blocks(blocks), imgoffset(imgoff)
DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff)
: sec_path(path), sec_size(size), sec_blocks(blocks), imgoffset(imgoff)
{
assert(blocks > 0);
assert(imgoff >= 0);
sec_path = path;
}
bool DiskCache::Save() const

View File

@ -15,9 +15,9 @@
#pragma once
#include "filepath.h"
#include <array>
#include <memory>
#include <string>
using namespace std;
@ -34,7 +34,7 @@ public:
uint32_t serial; // Serial
};
DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0);
DiskCache(const string&, int, uint32_t, off_t = 0);
~DiskCache() = default;
void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting
@ -55,7 +55,7 @@ private:
// Internal data
array<cache_t, CACHE_MAX> cache = {}; // Cache management
uint32_t serial = 0; // Last serial number
Filepath sec_path; // Path
string sec_path; // Path
int sec_size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096)
int sec_blocks; // Blocks per sector
bool cd_raw = false; // CD-ROM RAW mode

View File

@ -15,8 +15,8 @@
//---------------------------------------------------------------------------
#include "log.h"
#include "fileio.h"
#include "disk_track.h"
#include <fstream>
DiskTrack::~DiskTrack()
{
@ -46,7 +46,7 @@ void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff)
dt.imgoffset = imgoff;
}
bool DiskTrack::Load(const Filepath& path)
bool DiskTrack::Load(const string& path)
{
// Not needed if already loaded
if (dt.init) {
@ -97,24 +97,21 @@ bool DiskTrack::Load(const Filepath& path)
dt.changemap.resize(dt.sectors);
fill(dt.changemap.begin(), dt.changemap.end(), false);
// Read from File
Fileio fio;
if (!fio.OpenDIO(path, Fileio::OpenMode::ReadOnly)) {
ifstream in(path, ios::binary);
if (in.fail()) {
return false;
}
if (dt.raw) {
// Split Reading
for (int i = 0; i < dt.sectors; i++) {
// Seek
if (!fio.Seek(offset)) {
fio.Close();
in.seekg(offset);
if (in.fail()) {
return false;
}
// Read
if (!fio.Read(&dt.buffer[i << dt.size], 1 << dt.size)) {
fio.Close();
in.read((char *)&dt.buffer[i << dt.size], 1 << dt.size);
if (in.fail()) {
return false;
}
@ -123,16 +120,15 @@ bool DiskTrack::Load(const Filepath& path)
}
} else {
// Continuous reading
if (!fio.Seek(offset)) {
fio.Close();
in.seekg(offset);
if (in.fail()) {
return false;
}
if (!fio.Read(dt.buffer, length)) {
fio.Close();
in.read((char *)dt.buffer, length);
if (in.fail()) {
return false;
}
}
fio.Close();
// Set a flag and end normally
dt.init = true;
@ -140,7 +136,7 @@ bool DiskTrack::Load(const Filepath& path)
return true;
}
bool DiskTrack::Save(const Filepath& path)
bool DiskTrack::Save(const string& path)
{
// Not needed if not initialized
if (!dt.init) {
@ -169,9 +165,8 @@ bool DiskTrack::Save(const Filepath& path)
// Calculate length per sector
const int length = 1 << dt.size;
// Open file
Fileio fio;
if (!fio.Open(path, Fileio::OpenMode::ReadWrite)) {
ofstream out(path, ios::in | ios::out | ios::binary);
if (out.fail()) {
return false;
}
@ -183,9 +178,8 @@ bool DiskTrack::Save(const Filepath& path)
// Initialize write size
total = 0;
// Seek
if (!fio.Seek(offset + ((off_t)i << dt.size))) {
fio.Close();
out.seekp(offset + ((off_t)i << dt.size));
if (out.fail()) {
return false;
}
@ -201,9 +195,8 @@ bool DiskTrack::Save(const Filepath& path)
total += length;
}
// Write
if (!fio.Write(&dt.buffer[i << dt.size], total)) {
fio.Close();
out.write((const char *)&dt.buffer[i << dt.size], total);
if (out.fail()) {
return false;
}
@ -215,8 +208,6 @@ bool DiskTrack::Save(const Filepath& path)
}
}
fio.Close();
// Drop the change flag and exit
fill(dt.changemap.begin(), dt.changemap.end(), false);
dt.changed = false;

View File

@ -15,9 +15,10 @@
#pragma once
#include "filepath.h"
#include "os.h"
#include <cstdlib>
#include <vector>
#include <string>
using namespace std;
@ -31,7 +32,7 @@ class DiskTrack
BYTE *buffer; // Data buffer
bool init; // Is it initilized?
bool changed; // Changed flag
std::vector<bool> changemap; // Changed map
vector<bool> changemap; // Changed map
bool raw; // RAW mode flag
off_t imgoffset; // Offset to actual data
} dt = {};
@ -48,12 +49,12 @@ private:
friend class DiskCache;
void Init(int track, int size, int sectors, bool raw = false, off_t imgoff = 0);
bool Load(const Filepath& path);
bool Save(const Filepath& path);
bool Load(const string& path);
bool Save(const string& path);
// Read / Write
bool ReadSector(vector<BYTE>&, int) const; // Sector Read
bool WriteSector(const vector<BYTE>& buf, int); // Sector Write
bool WriteSector(const vector<BYTE>& buf, int); // Sector Write
int GetTrack() const { return dt.track; } // Get track
};

View File

@ -91,9 +91,7 @@ bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
void SCSIDaynaPort::Open()
{
Filepath path;
path.SetPath(GetFilename().c_str());
m_tap.OpenDump(path);
m_tap.OpenDump(GetFilename().c_str());
}
vector<byte> SCSIDaynaPort::InquiryInternal() const

View File

@ -39,7 +39,7 @@ public:
bool Dispatch(scsi_command) override;
// TODO Remove as soon as SCSIBR is not a subclass of Disk anymore
void Open() override { super::ValidateFile(GetFilename()); }
void Open() override { super::ValidateFile(); }
// Commands
vector<byte> InquiryInternal() const override;

View File

@ -17,6 +17,7 @@
// 2. The client triggers printing with SYNCHRONIZE BUFFER. Each SYNCHRONIZE BUFFER results in
// the print command for this printer (see below) to be called for the data not yet printed.
//
// It is recommended to reserve the printer device before printing and to release it afterwards.
// The command to be used for printing can be set with the "cmd" property when attaching the device.
// By default the data to be printed are sent to the printer unmodified, using "lp -oraw %f". This
// requires that the client uses a printer driver compatible with the respective printer, or that the
@ -33,8 +34,10 @@
#include "scsi_command_util.h"
#include "dispatcher.h"
#include "scsi_printer.h"
#include <filesystem>
using namespace std;
using namespace filesystem;
using namespace scsi_defs;
using namespace scsi_command_util;
@ -43,13 +46,18 @@ SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun)
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIPrinter::TestUnitReady);
dispatcher.Add(scsi_command::eCmdPrint, "Print", &SCSIPrinter::Print);
dispatcher.Add(scsi_command::eCmdSynchronizeBuffer, "SynchronizeBuffer", &SCSIPrinter::SynchronizeBuffer);
dispatcher.Add(scsi_command::eCmdStopPrint, "StopPrint", &SCSIPrinter::StopPrint);
// STOP PRINT is identical with TEST UNIT READY, it just returns the status
dispatcher.Add(scsi_command::eCmdStopPrint, "StopPrint", &SCSIPrinter::TestUnitReady);
// Required also in this class in order to fulfill the ScsiPrinterCommands interface contract
dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit);
dispatcher.Add(scsi_command::eCmdRelease6, "ReleaseUnit", &SCSIPrinter::ReleaseUnit);
dispatcher.Add(scsi_command::eCmdSendDiag, "SendDiagnostic", &SCSIPrinter::SendDiagnostic);
error_code error;
file_template = temp_directory_path(error); //NOSONAR Publicly writable directory is fine here
file_template += PRINTER_FILE_PATTERN;
SupportsParams(true);
SetReady(true);
}
@ -109,70 +117,75 @@ void SCSIPrinter::Print()
void SCSIPrinter::SynchronizeBuffer()
{
if (fd == -1) {
LOGWARN("Missing printer output file")
if (!out.is_open()) {
LOGWARN("Nothing to print")
throw scsi_exception(sense_key::ABORTED_COMMAND);
}
struct stat st;
fstat(fd, &st);
close(fd);
fd = -1;
string cmd = GetParam("cmd");
const size_t file_position = cmd.find("%f");
assert(file_position != string::npos);
cmd.replace(file_position, 2, filename);
LOGTRACE("%s", string("Printing file with size of " + to_string(st.st_size) +" byte(s)").c_str())
error_code error;
LOGTRACE("Printing file '%s' with %s byte(s)", filename.c_str(), to_string(file_size(path(filename), error)).c_str())
LOGDEBUG("Executing '%s'", cmd.c_str())
if (system(cmd.c_str())) {
LOGERROR("Printing failed, the printing system might not be configured")
LOGERROR("Printing file '%s' failed, the printing system might not be configured", filename.c_str())
unlink(filename);
Cleanup();
throw scsi_exception(sense_key::ABORTED_COMMAND);
}
unlink(filename);
Cleanup();
EnterStatusPhase();
}
void SCSIPrinter::StopPrint()
{
// Command implementations are identical
TestUnitReady();
}
bool SCSIPrinter::WriteByteSequence(vector<BYTE>& buf, uint32_t length)
{
if (fd == -1) {
strcpy(filename, TMP_FILE_PATTERN); //NOSONAR Using strcpy is safe here
fd = mkstemp(filename);
if (!out.is_open()) {
vector<char> f(file_template.begin(), file_template.end());
f.push_back(0);
// There is no C++ API that generates a file with a unique name
const int fd = mkstemp(f.data());
if (fd == -1) {
LOGERROR("Can't create printer output file '%s': %s", filename, strerror(errno))
LOGERROR("Can't create printer output file for pattern '%s': %s", filename.c_str(), strerror(errno))
return false;
}
close(fd);
LOGTRACE("Created printer output file '%s'", filename)
filename = f.data();
out.open(filename, ios::binary);
if (out.fail()) {
throw scsi_exception(sense_key::ABORTED_COMMAND);
}
LOGTRACE("Created printer output file '%s'", filename.c_str())
}
LOGTRACE("Appending %d byte(s) to printer output file '%s'", length, filename)
LOGTRACE("Appending %d byte(s) to printer output file '%s'", length, filename.c_str())
return (uint32_t)write(fd, buf.data(), length) == length;
out.write((const char*)buf.data(), length);
return !out.fail();
}
void SCSIPrinter::Cleanup()
{
if (fd != -1) {
close(fd);
fd = -1;
if (out.is_open()) {
out.close();
unlink(filename);
error_code error;
remove(path(filename), error);
filename = "";
}
}

View File

@ -12,16 +12,18 @@
#include "interfaces/scsi_printer_commands.h"
#include "primary_device.h"
#include <fstream>
#include <string>
#include <unordered_map>
using namespace std;
class SCSIPrinter : public PrimaryDevice, ScsiPrinterCommands //NOSONAR Custom destructor cannot be removed
{
static constexpr const char *TMP_FILE_PATTERN = "/tmp/rascsi_sclp-XXXXXX"; //NOSONAR Using /tmp is safe
static const int TMP_FILENAME_LENGTH = string_view(TMP_FILE_PATTERN).size();
static const int NOT_RESERVED = -2;
static constexpr const char *PRINTER_FILE_PATTERN = "/rascsi_sclp-XXXXXX";
public:
explicit SCSIPrinter(int);
@ -39,7 +41,6 @@ public:
void SendDiagnostic() override { PrimaryDevice::SendDiagnostic(); }
void Print() override;
void SynchronizeBuffer();
void StopPrint();
bool WriteByteSequence(vector<BYTE>&, uint32_t) override;
@ -49,6 +50,9 @@ private:
Dispatcher<SCSIPrinter> dispatcher;
char filename[TMP_FILENAME_LENGTH + 1]; //NOSONAR mkstemp() requires a modifiable string
int fd = -1;
string file_template;
string filename;
ofstream out;
};

View File

@ -14,12 +14,12 @@
//
//---------------------------------------------------------------------------
#include "fileio.h"
#include "rascsi_exceptions.h"
#include "scsi_command_util.h"
#include "dispatcher.h"
#include "scsicd.h"
#include <array>
#include <fstream>
using namespace scsi_defs;
using namespace scsi_command_util;
@ -50,48 +50,29 @@ void SCSICD::Open()
rawfile = false;
ClearTrack();
// Open as read-only
Filepath path;
path.SetPath(GetFilename().c_str());
Fileio fio;
if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) {
throw file_not_found_exception("Can't open CD-ROM file '" + GetFilename() + "'");
}
// Default sector size is 2048 bytes
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 2048);
// Close and transfer for physical CD access
if (path.GetPath()[0] == '\\') {
// Close
fio.Close();
// Open physical CD
if (GetFilename()[0] == '\\') {
OpenPhysical();
} else {
if (GetFileSize() < 4) {
fio.Close();
throw io_exception("CD-ROM file size must be at least 4 bytes");
// Judge whether it is a CUE sheet or an ISO file
array<char, 4> cue;
ifstream in(GetFilename(), ios::binary);
in.read(cue.data(), cue.size());
if (!in.good()) {
throw io_exception("Can't read header of CD-ROM file '" + GetFilename() + "'");
}
// Judge whether it is a CUE sheet or an ISO file
array<TCHAR, 5> file;
fio.Read((BYTE *)file.data(), 4);
file[4] = '\0';
fio.Close();
// If it starts with FILE, consider it as a CUE sheet
if (!strcasecmp(file.data(), "FILE")) {
throw io_exception("Opening CUE CD-ROM files is not supported");
// If it starts with FILE consider it a CUE sheet
if (!strncasecmp(cue.data(), "FILE", cue.size())) {
throw io_exception("CUE CD-ROM files are not supported");
} else {
OpenIso();
}
}
// Successful opening
assert(GetBlockCount() > 0);
super::ValidateFile(GetFilename());
super::ValidateFile();
SetUpCache(0, rawfile);
@ -106,80 +87,56 @@ void SCSICD::Open()
void SCSICD::OpenIso()
{
// Open as read-only
Fileio fio;
if (!fio.Open(GetFilename().c_str(), Fileio::OpenMode::ReadOnly)) {
throw io_exception("Can't open ISO CD-ROM file");
}
// Get file size
const off_t size = GetFileSize();
if (size < 0x800) {
fio.Close();
if (size < 2048) {
throw io_exception("ISO CD-ROM file size must be at least 2048 bytes");
}
// Read the first 12 bytes and close
array<BYTE, 12> header;
if (!fio.Read(header.data(), header.size())) {
fio.Close();
// Validate header
array<char, 16> header;
ifstream in(GetFilename(), ios::binary);
in.read(header.data(), header.size());
if (!in.good()) {
throw io_exception("Can't read header of ISO CD-ROM file");
}
// Check if it is RAW format
array<BYTE, 12> sync;
sync.fill(0xff);
sync[0] = 0x00;
sync[11] = 0x00;
// Check if it is in RAW format
array<char, 12> sync = {};
// 00,FFx10,00 is presumed to be RAW format
fill_n(sync.begin() + 1, 10, 0xff);
rawfile = false;
if (memcmp(header.data(), sync.data(), sync.size()) == 0) {
// 00,FFx10,00, so it is presumed to be RAW format
if (!fio.Read(header.data(), 4)) {
fio.Close();
throw io_exception("Can't read header of raw ISO CD-ROM file");
}
if (memcmp(header.data(), sync.data(), sync.size()) == 0) {
// Supports MODE1/2048 or MODE1/2352 only
if (header[3] != 0x01) {
if (header[15] != 0x01) {
// Different mode
fio.Close();
throw io_exception("Illegal raw ISO CD-ROM file header");
}
// Set to RAW file
rawfile = true;
}
fio.Close();
if (rawfile) {
// Size must be a multiple of 2536
if (size % 2536) {
throw io_exception("Raw ISO CD-ROM file size must be a multiple of 2536 bytes but is "
+ to_string(size) + " bytes");
}
// Set the number of blocks
SetBlockCount((uint32_t)(size / 0x930));
SetBlockCount((uint32_t)(size / 2352));
} else {
// Set the number of blocks
SetBlockCount((uint32_t)(size >> GetSectorSizeShiftCount()));
}
CreateDataTrack();
}
// TODO This code is only executed if the filename starts with a `\`, but fails to open files starting with `\`.
void SCSICD::OpenPhysical()
{
// Open as read-only
Fileio fio;
if (!fio.Open(GetFilename().c_str(), Fileio::OpenMode::ReadOnly)) {
throw file_not_found_exception("Can't open CD-ROM file '" + GetFilename() + "'");
}
fio.Close();
// Get size
off_t size = GetFileSize();
if (size < 0x800) {
if (size < 2048) {
throw io_exception("CD-ROM file size must be at least 2048 bytes");
}
@ -198,9 +155,7 @@ void SCSICD::CreateDataTrack()
assert(!tracks.size());
auto track = make_unique<CDTrack>();
track->Init(1, 0, (int)GetBlockCount() - 1);
Filepath path;
path.SetPath(GetFilename().c_str());
track->SetPath(false, path);
track->SetPath(false, GetFilename());
tracks.push_back(move(track));
dataindex = 0;
}
@ -287,12 +242,8 @@ int SCSICD::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t block)
SetBlockCount(tracks[index]->GetBlocks());
assert(GetBlockCount() > 0);
// Recreate the disk cache
Filepath path;
tracks[index]->GetPath(path);
// Re-assign disk cache (no need to save)
ResizeCache(path.GetPath(), rawfile);
ResizeCache(tracks[index]->GetPath(), rawfile);
// Reset data index
dataindex = index;

View File

@ -5,17 +5,15 @@
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI hard disk ]
//
//---------------------------------------------------------------------------
#include "scsihd.h"
#include "fileio.h"
#include "rascsi_exceptions.h"
#include "scsi_command_util.h"
@ -56,23 +54,15 @@ string SCSIHD::GetProductData() const
return DEFAULT_PRODUCT + " " + to_string(capacity) + " " + unit;
}
void SCSIHD::FinalizeSetup(off_t size, off_t image_offset)
void SCSIHD::FinalizeSetup(off_t image_offset)
{
// Effective size must be a multiple of the sector size
size = (size / GetSectorSizeInBytes()) * GetSectorSizeInBytes();
// 2 TiB is the current maximum
if (size > 2LL * 1024 * 1024 * 1024 * 1024) {
throw io_exception("Drive capacity cannot exceed 2 TiB");
}
super::ValidateFile();
// For non-removable media drives set the default product name based on the drive capacity
if (!IsRemovable()) {
SetProduct(GetProductData(), false);
}
super::ValidateFile(GetFilename());
SetUpCache(image_offset);
}
@ -86,7 +76,7 @@ void SCSIHD::Open()
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
SetBlockCount((uint32_t)(size >> GetSectorSizeShiftCount()));
FinalizeSetup(size);
FinalizeSetup(0);
}
vector<byte> SCSIHD::InquiryInternal() const

View File

@ -5,19 +5,21 @@
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI hard disk ]
//
//---------------------------------------------------------------------------
#pragma once
#include "scsi.h"
#include "disk.h"
#include <string>
#include <vector>
#include <map>
class SCSIHD : public Disk
{
@ -28,7 +30,7 @@ public:
SCSIHD(int, const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2);
~SCSIHD() override = default;
void FinalizeSetup(off_t, off_t = 0);
void FinalizeSetup(off_t);
void Open() override;

View File

@ -14,11 +14,13 @@
//
//---------------------------------------------------------------------------
#include "scsihd_nec.h"
#include "fileio.h"
#include "rascsi_exceptions.h"
#include "rasutil.h"
#include "scsi_command_util.h"
#include "scsihd_nec.h"
#include <fstream>
using namespace ras_util;
using namespace scsi_command_util;
const unordered_set<uint32_t> SCSIHD_NEC::sector_sizes = { 512 };
@ -27,64 +29,38 @@ void SCSIHD_NEC::Open()
{
assert(!IsReady());
// Open as read-only
Filepath path;
path.SetPath(GetFilename().c_str());
Fileio fio;
if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) {
throw file_not_found_exception("Can't open hard disk file '" + GetFilename() + '"');
}
off_t size = GetFileSize();
// NEC root sector
array<BYTE, 512> root_sector;
if (size < (off_t)root_sector.size() || !fio.Read(root_sector.data(), root_sector.size())) {
fio.Close();
array<char, 512> root_sector;
ifstream in(GetFilename(), ios::binary);
in.read(root_sector.data(), root_sector.size());
if (!in.good() || size < (off_t)root_sector.size()) {
throw io_exception("Can't read NEC hard disk file root sector");
}
fio.Close();
// Effective size must be a multiple of 512
size = (size / 512) * 512;
// Determine parameters by extension
const auto [image_size, sector_size] = SetParameters(path.GetFileExt(), root_sector, (int)size);
const auto [image_size, sector_size] = SetParameters(root_sector, (int)size);
if (sector_size == 0) {
throw io_exception("Invalid NEC drive sector size");
}
// Image size consistency check
if (image_offset + image_size > size || image_size % sector_size != 0) {
throw io_exception("Image size consistency check failed");
}
// Calculate sector size
for (size = 16; size > 0; --size) {
if ((1 << size) == sector_size)
break;
}
if (size <= 0 || size > 16) {
throw io_exception("Invalid NEC disk size");
}
SetSectorSizeShiftCount((uint32_t)size);
SetBlockCount(image_size >> GetSectorSizeShiftCount());
FinalizeSetup(size, image_offset);
FinalizeSetup(image_offset);
}
pair<int, int> SCSIHD_NEC::SetParameters(const string& extension, const array<BYTE, 512>& root_sector, int size)
pair<int, int> SCSIHD_NEC::SetParameters(const array<char, 512>& data, int size)
{
string ext = extension;
transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
array<BYTE, 512> root_sector = {};
memcpy(root_sector.data(), data.data(), root_sector.size());
int image_size;
int sector_size;
// PC-9801-55 NEC compatible?
if (ext == ".hdn") {
if (const string ext = GetExtensionLowerCase(GetFilename()); ext == "hdn") {
// Assuming sector size 512, number of sectors 25, number of heads 8 as default settings
image_offset = 0;
image_size = size;
@ -96,7 +72,7 @@ pair<int, int> SCSIHD_NEC::SetParameters(const string& extension, const array<BY
cylinders /= 25;
}
// Anex86 HD image?
else if (ext == ".hdi") {
else if (ext == "hdi") {
image_offset = GetInt32LittleEndian(&root_sector[8]);
image_size = GetInt32LittleEndian(&root_sector[12]);
sector_size = GetInt32LittleEndian(&root_sector[16]);
@ -105,7 +81,7 @@ pair<int, int> SCSIHD_NEC::SetParameters(const string& extension, const array<BY
cylinders = GetInt32LittleEndian(&root_sector[28]);
}
// T98Next HD image?
else if (ext == ".nhd") {
else if (ext == "nhd") {
if (!memcmp(root_sector.data(), "T98HDDIMAGE.R0\0", 15)) {
image_offset = GetInt32LittleEndian(&root_sector[0x110]);
cylinders = GetInt32LittleEndian(&root_sector[0x114]);
@ -122,6 +98,19 @@ pair<int, int> SCSIHD_NEC::SetParameters(const string& extension, const array<BY
throw io_exception("Invalid NEC image file extension");
}
if (sector_size == 0) {
throw io_exception("Invalid NEC sector size 0");
}
// Image size consistency check
if (image_offset + image_size > size) {
throw io_exception("NEC image offset/size consistency check failed");
}
if (CalculateShiftCount(sector_size) == 0) {
throw io_exception("Invalid NEC sector size of " + to_string(sector_size) + " byte(s)");
}
return make_pair(image_size, sector_size);
}

View File

@ -47,14 +47,14 @@ protected:
private:
pair<int, int> SetParameters(const string&, const array<BYTE, 512>&, int);
pair<int, int> SetParameters(const array<char, 512>&, int);
static int GetInt16LittleEndian(const BYTE *);
static int GetInt32LittleEndian(const BYTE *);
static const unordered_set<uint32_t> sector_sizes;
// Image file offset (NEC only)
// Image file offset
off_t image_offset = 0;
// Geometry data

View File

@ -12,7 +12,6 @@
//
//---------------------------------------------------------------------------
#include "fileio.h"
#include "rascsi_exceptions.h"
#include "scsi_command_util.h"
#include "scsimo.h"
@ -43,21 +42,14 @@ void SCSIMO::Open()
{
assert(!IsReady());
off_t size = GetFileSize();
// 2 TiB is the current maximum
if (size > 2LL * 1024 * 1024 * 1024 * 1024) {
throw io_exception("Drive capacity cannot exceed 2 TiB");
}
// For some capacities there are hard-coded, well-defined sector sizes and block counts
if (!SetGeometryForCapacity(size)) {
if (const off_t size = GetFileSize(); !SetGeometryForCapacity(size)) {
// Sector size (default 512 bytes) and number of blocks
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
SetBlockCount(size >> GetSectorSizeShiftCount());
}
super::ValidateFile(GetFilename());
super::ValidateFile();
SetUpCache(0);

View File

@ -15,6 +15,8 @@
#pragma once
#include "disk.h"
#include <vector>
#include <map>
using Geometry = pair<uint32_t, uint32_t>;

View File

@ -23,14 +23,22 @@ StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type,
SetStoppable(true);
}
void StorageDevice::ValidateFile(const string& file)
void StorageDevice::ValidateFile()
{
if (blocks == 0) {
throw io_exception(string(GetTypeString()) + " device has 0 blocks");
}
if (!exists(path(filename))) {
throw file_not_found_exception("Image file '" + filename + "' for " + GetTypeString() + " device does not exist");
}
if (GetFileSize() > 2LL * 1024 * 1024 * 1024 * 1024) {
throw io_exception("Drive capacity cannot exceed 2 TiB");
}
// TODO Check for duplicate handling of these properties (-> rascsi_executor.cpp)
if (access(file.c_str(), W_OK)) {
if (access(filename.c_str(), W_OK)) {
// Permanently write-protected
SetReadOnly(true);
SetProtectable(false);
@ -65,16 +73,13 @@ void StorageDevice::UnreserveFile()
filename = "";
}
bool StorageDevice::GetIdsForReservedFile(const string& file, int& id, int& lun)
pair<int, int> StorageDevice::GetIdsForReservedFile(const string& file)
{
if (const auto& it = reserved_files.find(file); it != reserved_files.end()) {
id = it->second.first;
lun = it->second.second;
return true;
return make_pair(it->second.first, it->second.second);
}
return false;
return make_pair(-1, -1);
}
void StorageDevice::UnreserveAll()
@ -94,7 +99,7 @@ bool StorageDevice::IsReadOnlyFile() const
off_t StorageDevice::GetFileSize() const
{
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handled more than 2 GiB
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
if (struct stat st; !stat(filename.c_str(), &st)) {
return st.st_size;
}

View File

@ -28,7 +28,6 @@ public:
virtual void Open() = 0;
void ValidateFile(const string&);
string GetFilename() const { return filename; }
void SetFilename(string_view f) { filename = f; }
@ -45,10 +44,12 @@ public:
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; }
static void SetReservedFiles(const unordered_map<string, id_set>& r) { reserved_files = r; }
static bool GetIdsForReservedFile(const string&, int&, int&);
static pair<int, int> GetIdsForReservedFile(const string&);
protected:
void ValidateFile();
bool IsMediumChanged() const { return medium_changed; }
void SetMediumChanged(bool b) { medium_changed = b; }

View File

@ -1,101 +0,0 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2012-2020 GIMONS
// [ File path (subset) ]
//
//---------------------------------------------------------------------------
#include "filepath.h"
#include <libgen.h>
#include <cstdlib>
#include <cstring>
#include <cassert>
Filepath& Filepath::operator=(const Filepath& path)
{
// Set path (split internally)
SetPath(path.GetPath());
return *this;
}
//---------------------------------------------------------------------------
//
// File settings (user) for MBCS
//
//---------------------------------------------------------------------------
void Filepath::SetPath(const char *path)
{
assert(path);
assert(strlen(path) < _MAX_PATH);
// Copy pathname
strcpy(m_szPath, path);
// Split
Split();
}
//---------------------------------------------------------------------------
//
// Split paths
//
//---------------------------------------------------------------------------
void Filepath::Split()
{
// Initialize the parts
m_szDir[0] = '\0';
m_szFile[0] = '\0';
m_szExt[0] = '\0';
// Split
char *pDir = strdup(m_szPath);
const char *pDirName = dirname(pDir);
char *pBase = strdup(m_szPath);
const char *pBaseName = basename(pBase);
const char *pExtName = strrchr(pBaseName, '.');
// Transmit
if (pDirName) {
strcpy(m_szDir, pDirName);
strcat(m_szDir, "/");
}
if (pExtName) {
strcpy(m_szExt, pExtName);
}
if (pBaseName) {
strcpy(m_szFile, pBaseName);
}
// Release
free(pDir);
free(pBase);
}
//---------------------------------------------------------------------------
//
// File name + extension acquisition
// The returned pointer is temporary. Copy immediately.
//
//---------------------------------------------------------------------------
const char *Filepath::GetFileExt() const
{
// Merge into static buffer
strcpy(FileExt, m_szExt);
// Return as LPCTSTR
return (const char *)FileExt;
}
//---------------------------------------------------------------------------
//
// Filename and extension
//
//---------------------------------------------------------------------------
TCHAR Filepath::FileExt[_MAX_FNAME + _MAX_DIR];

View File

@ -1,51 +0,0 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2012-2020 GIMONS
// [ File path (subset) ]
//
//---------------------------------------------------------------------------
#pragma once
#include "os.h"
using TCHAR = char;
static const int _MAX_EXT = 256;
static const int _MAX_DIR = 256;
static const int _MAX_PATH = 260;
static const int _MAX_FNAME = 256;
static const int FILEPATH_MAX = _MAX_PATH;
//===========================================================================
//
// File path
// Assignment operators are prepared here
//
//===========================================================================
class Filepath
{
public:
Filepath() = default;
~Filepath() = default;
Filepath(Filepath&) = default;
Filepath& operator=(const Filepath&);
void SetPath(const char *); // File settings (user) for MBCS
const char *GetPath() const { return m_szPath; } // Get path name
const char *GetFileExt() const; // Get short name (LPCTSTR)
private:
void Split(); // Split the path
TCHAR m_szPath[_MAX_PATH] = {}; // File path
TCHAR m_szDir[_MAX_DIR] = {}; // Directory
TCHAR m_szFile[_MAX_FNAME] = {}; // File
TCHAR m_szExt[_MAX_EXT] = {}; // Extension
static TCHAR FileExt[_MAX_FNAME + _MAX_DIR]; // Short name (TCHAR)
};

View File

@ -98,22 +98,22 @@ void Banner(int argc, char* argv[])
bool InitBus()
{
#ifdef USE_SEL_EVENT_ENABLE
SBC_Version::Init();
#endif
// GPIOBUS creation
bus = GPIOBUS_Factory::Create();
controller_manager = make_shared<ControllerManager>(bus);
rascsi_response = make_shared<RascsiResponse>(device_factory, *controller_manager, ScsiController::LUN_MAX);
executor = make_shared<RascsiExecutor>(*rascsi_response, rascsi_image, device_factory, *controller_manager);
// GPIO Initialization
if (!bus->Init()) {
return false;
}
// Bus Reset
bus->Reset();
return true;
@ -125,7 +125,6 @@ void Cleanup()
service.Cleanup();
// Clean up and discard the bus
bus->Cleanup();
}
@ -149,7 +148,7 @@ bool ReadAccessToken(const char *filename)
return false;
}
ifstream token_file(filename, ifstream::in);
ifstream token_file(filename);
if (token_file.fail()) {
cerr << "Can't open access token file '" << optarg << "'" << endl;
return false;
@ -157,19 +156,15 @@ bool ReadAccessToken(const char *filename)
getline(token_file, access_token);
if (token_file.fail()) {
token_file.close();
cerr << "Can't read access token file '" << optarg << "'" << endl;
return false;
}
if (access_token.empty()) {
token_file.close();
cerr << "Access token file '" << optarg << "' must not be empty" << endl;
return false;
}
token_file.close();
return true;
}

View File

@ -397,6 +397,7 @@ bool RascsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit
bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr<PrimaryDevice> device, bool dryRun) const
{
cerr << "AAA " << device->GetId() << endl;
auto controller = controller_manager.FindController(device->GetId());
if (controller == nullptr) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
@ -408,6 +409,9 @@ bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr<PrimaryDev
}
if (!dryRun) {
// Remember the ID before it gets invalid when removing the device
const int id = device->GetId();
if (!controller->RemoveDevice(device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
}
@ -421,8 +425,8 @@ bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr<PrimaryDev
storage_device->UnreserveFile();
}
LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID "
+ to_string(device->GetId()) + ", unit " + to_string(device->GetLun())).c_str())
LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + to_string(id)
+ ", unit " + to_string(device->GetLun())).c_str())
}
return true;
@ -544,11 +548,9 @@ bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr
return true;
}
int id;
int lun;
if (StorageDevice::GetIdsForReservedFile(filename, id, lun)) {
if (const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile(filename); id1 != -1 || lun1 != -1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id), to_string(lun));
to_string(id1), to_string(lun1));
}
string effective_filename = filename;
@ -557,9 +559,9 @@ bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr
// If the file does not exist search for it in the default image folder
effective_filename = rascsi_image.GetDefaultFolder() + "/" + filename;
if (StorageDevice::GetIdsForReservedFile(effective_filename, id, lun)) {
if (const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile(effective_filename); id2 != -1 || lun2 != -1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id), to_string(lun));
to_string(id2), to_string(lun2));
}
if (!StorageDevice::FileExists(effective_filename)) {

View File

@ -7,21 +7,20 @@
//
//---------------------------------------------------------------------------
#include <unistd.h>
#include <pwd.h>
#include "log.h"
#include "devices/disk.h"
#include "protobuf_util.h"
#include "command_context.h"
#include "rascsi_image.h"
#include <unistd.h>
#include <pwd.h>
#include <fstream>
#include <string>
#include <array>
#include <filesystem>
#ifdef __linux__
#include <sys/sendfile.h>
#endif
using namespace std;
using namespace filesystem;
using namespace rascsi_interface;
using namespace protobuf_util;
@ -39,16 +38,20 @@ bool RascsiImage::CheckDepth(string_view filename) const
bool RascsiImage::CreateImageFolder(const CommandContext& context, const string& filename) const
{
if (const size_t filename_start = filename.rfind('/'); filename_start != string::npos) {
const string folder = filename.substr(0, filename_start);
const auto folder = path(filename.substr(0, filename_start));
// Checking for existence first prevents an error if the top-level folder is a softlink
if (struct stat st; stat(folder.c_str(), &st)) {
std::error_code error;
filesystem::create_directories(folder, error);
if (error) {
context.ReturnStatus(false, "Can't create image folder '" + folder + "': " + strerror(errno));
return false;
}
if (error_code error; exists(folder, error)) {
return true;
}
try {
create_directories(folder);
return ChangeOwner(context, folder, false);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't create image folder '" + string(folder) + "': " + e.what());
}
}
@ -63,9 +66,9 @@ string RascsiImage::SetDefaultFolder(const string& f)
string folder = f;
// If a relative path is specified the path is assumed to be relative to the user's home directory
// If a relative path is specified, the path is assumed to be relative to the user's home directory
if (folder[0] != '/') {
folder = GetHomeDir() + "/" + f;
folder = GetHomeDir() + "/" + folder;
}
else {
if (folder.find("/home/") != 0) {
@ -73,13 +76,17 @@ string RascsiImage::SetDefaultFolder(const string& f)
}
}
struct stat info;
stat(folder.c_str(), &info);
if (!S_ISDIR(info.st_mode) || access(folder.c_str(), F_OK) == -1) {
return "Folder '" + f + "' does not exist or is not accessible";
// Resolve a potential symlink
auto p = path(folder);
if (error_code error; is_symlink(p, error)) {
p = read_symlink(p);
}
default_folder = folder;
if (error_code error; !is_directory(p, error)) {
return "'" + string(p) + "' is not a valid folder";
}
default_folder = string(p);
LOGINFO("Default image folder set to '%s'", default_folder.c_str())
@ -104,7 +111,7 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
const string size = GetParam(command, "size");
if (size.empty()) {
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing image size");
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing file size");
}
off_t len;
@ -125,38 +132,30 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
return false;
}
const string permission = GetParam(command, "read_only");
// Since rascsi is running as root ensure that others can access the file
const int permissions = !strcasecmp(permission.c_str(), "true") ?
S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
const bool read_only = GetParam(command, "read_only") == "true";
const int image_fd = open(full_filename.c_str(), O_CREAT|O_WRONLY, permissions);
if (image_fd == -1) {
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + string(strerror(errno)));
error_code error;
path file(full_filename);
try {
ofstream s(file);
s.close();
if (!ChangeOwner(context, file, read_only)) {
return false;
}
resize_file(file, len);
}
catch(const filesystem_error& e) {
remove(file, error);
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + e.what());
}
#ifndef __linux__
close(image_fd);
unlink(full_filename.c_str());
return false;
#else
if (fallocate(image_fd, 0, 0, len)) {
close(image_fd);
unlink(full_filename.c_str());
return context.ReturnStatus(false, "Can't allocate space for image file '" + full_filename + "': " + string(strerror(errno)));
}
close(image_fd);
LOGINFO("%s", string("Created " + string(permissions & S_IWUSR ? "": "read-only ") + "image file '" + full_filename +
LOGINFO("%s", string("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename +
"' with a size of " + to_string(len) + " bytes").c_str())
return context.ReturnStatus();
#endif
}
bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& command) const
@ -172,28 +171,35 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
const string full_filename = GetFullName(filename);
int id;
if (int lun; StorageDevice::GetIdsForReservedFile(full_filename, id, lun)) {
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(full_filename);
if (id == -1 || lun == -1) {
return context.ReturnStatus(false, "Can't delete image file '" + full_filename +
"', it is currently being used by device ID " + to_string(id) + ", unit " + to_string(lun));
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
}
if (remove(full_filename.c_str())) {
return context.ReturnStatus(false, "Can't delete image file '" + full_filename + "': " + string(strerror(errno)));
try {
remove(path(full_filename));
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't delete image file '" + full_filename + "': " + e.what());
}
// Delete empty subfolders
size_t last_slash = filename.rfind('/');
while (last_slash != string::npos) {
string folder = filename.substr(0, last_slash);
string full_folder = GetFullName(folder);
const string folder = filename.substr(0, last_slash);
const auto full_folder = path(GetFullName(folder));
if (error_code error; !filesystem::is_empty(full_folder, error) || error) {
break;
}
if (remove(full_folder.c_str())) {
return context.ReturnStatus(false, "Can't delete empty image folder '" + full_folder + "'");
try {
remove(full_folder);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't delete empty image folder '" + string(full_folder)
+ "': " + e.what());
}
last_slash = folder.rfind('/');
@ -216,8 +222,11 @@ bool RascsiImage::RenameImage(const CommandContext& context, const PbCommand& co
return false;
}
if (rename(from.c_str(), to.c_str())) {
return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + string(strerror(errno)));
try {
rename(path(from), path(to));
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + e.what());
}
LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str())
@ -233,69 +242,46 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
return false;
}
struct stat st;
if (lstat(from.c_str(), &st)) {
return context.ReturnStatus(false, "Can't access source image file '" + from + "': " + string(strerror(errno)));
if (access(from.c_str(), R_OK)) {
return context.ReturnStatus(false, "Can't read source image file '" + from + "'");
}
if (!CreateImageFolder(context, to)) {
return false;
}
path f(from);
path t(to);
// Symbolic links need a special handling
if ((st.st_mode & S_IFMT) == S_IFLNK) {
if (symlink(filesystem::read_symlink(from).c_str(), to.c_str())) {
return context.ReturnStatus(false, "Can't copy symlink '" + from + "': " + string(strerror(errno)));
if (error_code error; is_symlink(f, error)) {
try {
copy_symlink(f, t);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't copy image file symlink '" + from + "': " + e.what());
}
LOGINFO("Copied symlink '%s' to '%s'", from.c_str(), to.c_str())
LOGINFO("Copied image file symlink '%s' to '%s'", from.c_str(), to.c_str())
return context.ReturnStatus();
}
const int fd_src = open(from.c_str(), O_RDONLY, 0);
if (fd_src == -1) {
return context.ReturnStatus(false, "Can't open source image file '" + from + "': " + string(strerror(errno)));
try {
copy_file(f, t);
permissions(t, GetParam(command, "read_only") == "true" ?
perms::owner_read | perms::group_read | perms::others_read :
perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write);
}
const string permission = GetParam(command, "read_only");
// Since rascsi is running as root ensure that others can access the file
const int permissions = !strcasecmp(permission.c_str(), "true") ?
S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
const int fd_dst = open(to.c_str(), O_WRONLY | O_CREAT, permissions);
if (fd_dst == -1) {
close(fd_src);
return context.ReturnStatus(false, "Can't open destination image file '" + to + "': " + string(strerror(errno)));
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + e.what());
}
#ifndef __linux__
close(fd_dst);
close(fd_src);
unlink(to.c_str());
LOGWARN("Copying image files is only supported under Linux")
return false;
#else
if (sendfile(fd_dst, fd_src, nullptr, st.st_size) == -1) {
close(fd_dst);
close(fd_src);
unlink(to.c_str());
return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + string(strerror(errno)));
}
close(fd_dst);
close(fd_src);
LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str())
return context.ReturnStatus();
#endif
}
bool RascsiImage::SetImagePermissions(const CommandContext& context, const PbCommand& command) const
@ -316,10 +302,15 @@ bool RascsiImage::SetImagePermissions(const CommandContext& context, const PbCom
const bool protect = command.operation() == PROTECT_IMAGE;
if (const int permissions = protect ? S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
chmod(filename.c_str(), permissions) == -1) {
return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" + filename + "': " +
strerror(errno));
try {
permissions(path(filename), protect ?
perms::owner_read | perms::group_read | perms::others_read :
perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" +
filename + "': " + e.what());
}
if (protect) {
@ -369,23 +360,45 @@ bool RascsiImage::ValidateParams(const CommandContext& context, const PbCommand&
bool RascsiImage::IsValidSrcFilename(const string& filename)
{
// Source file must exist and must be a regular file or a symlink
struct stat st;
return !stat(filename.c_str(), &st) && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode));
path file(filename);
return is_regular_file(file) || is_symlink(file);
}
bool RascsiImage::IsValidDstFilename(const string& filename)
{
// Destination file must not yet exist
struct stat st;
return stat(filename.c_str(), &st);
try {
return !exists(path(filename));
}
catch(const filesystem_error&) {
return true;
}
}
bool RascsiImage::ChangeOwner(const CommandContext& context, const path& filename, bool read_only)
{
const auto [uid, gid] = GetUidAndGid();
if (chown(filename.c_str(), uid, gid)) {
// Remember the current error before the next filesystem operation
const int e = errno;
error_code error;
remove(filename, error);
return context.ReturnStatus(false, "Can't change ownership of '" + string(filename) + "': " + strerror(e));
}
permissions(filename, read_only ?
perms::owner_read | perms::group_read | perms::others_read :
perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write);
return true;
}
string RascsiImage::GetHomeDir()
{
int uid = getuid();
if (const char *sudo_user = getenv("SUDO_UID"); sudo_user != nullptr) {
uid = stoi(sudo_user);
}
const auto [uid, gid] = GetUidAndGid();
passwd pwd = {};
passwd *p_pwd;
@ -398,3 +411,22 @@ string RascsiImage::GetHomeDir()
return "/home/pi";
}
}
pair<int, int> RascsiImage::GetUidAndGid()
{
int uid = getuid();
if (const char *sudo_user = getenv("SUDO_UID"); sudo_user != nullptr) {
uid = stoi(sudo_user);
}
passwd pwd = {};
passwd *p_pwd;
array<char, 256> pwbuf;
int gid = -1;
if (!getpwuid_r(uid, &pwd, pwbuf.data(), pwbuf.size(), &p_pwd)) {
gid = pwd.pw_gid;
}
return make_pair(uid, gid);
}

View File

@ -12,8 +12,10 @@
#include "rascsi_interface.pb.h"
#include "command_context.h"
#include <string>
#include <filesystem>
using namespace std;
using namespace filesystem;
using namespace rascsi_interface;
class RascsiImage
@ -42,7 +44,9 @@ private:
static bool IsValidSrcFilename(const string&);
static bool IsValidDstFilename(const string&);
static bool ChangeOwner(const CommandContext&, const path&, bool);
static string GetHomeDir();
static pair<int, int> GetUidAndGid();
string default_folder;

View File

@ -14,7 +14,10 @@
#include "rascsi_version.h"
#include "rascsi_interface.pb.h"
#include "rascsi_response.h"
#include <filesystem>
using namespace std;
using namespace filesystem;
using namespace rascsi_interface;
using namespace protobuf_util;
@ -112,7 +115,7 @@ bool RascsiResponse::GetImageFile(PbImageFile& image_file, const string& default
image_file.set_read_only(access(f.c_str(), W_OK));
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handled more than 2 GiB
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) {
image_file.set_size(st.st_size);
return true;
@ -140,6 +143,7 @@ void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, cons
return;
}
// C++ filesystem cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
const dirent *dir;
while ((dir = readdir(d))) {
string filename = GetNextImageFile(dir, folder);
@ -526,10 +530,11 @@ string RascsiResponse::GetNextImageFile(const dirent *dir, const string& folder)
const string filename = folder + "/" + dir->d_name;
const bool file_exists = exists(path(filename));
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
struct stat st;
const bool file_exists = !stat(filename.c_str(), &st);
stat(filename.c_str(), &st);
if (dir->d_type == DT_REG && file_exists && !st.st_size) {
LOGWARN("File '%s' in image folder '%s' is empty", dir->d_name, folder.c_str())
return "";

View File

@ -13,8 +13,7 @@
#include <csignal>
#include <unistd.h>
#include "os.h"
#include "fileio.h"
#include "filepath.h"
#include "rasdump_fileio.h"
#include "hal/gpiobus.h"
#include "hal/gpiobus_factory.h"
#include "hal/systimer.h"
@ -40,7 +39,7 @@ static const int BUFSIZE = 1024 * 64; // Buffer size of about 64KB
unique_ptr<GPIOBUS> bus; // GPIO Bus // Bus
int targetid; // Target ID
int boardid; // Board ID (own ID)
Filepath hdsfile; // HDS file
string hdsfile; // HDS file
bool restore; // Restore flag
BYTE buffer[BUFSIZE]; // Work Buffer
int result; // Result Code
@ -207,7 +206,7 @@ bool ParseArgument(int argc, char* argv[])
return false;
}
hdsfile.SetPath(file);
hdsfile = file;
return true;
}
@ -837,7 +836,7 @@ int main(int argc, char* argv[])
} else {
omode = Fileio::OpenMode::WriteOnly;
}
if (!fio.Open(hdsfile.GetPath(), omode)) {
if (!fio.Open(hdsfile.c_str(), omode)) {
fprintf(stderr, "Error : Can't open hds file\n");
// Cleanup

View File

@ -8,7 +8,7 @@
//
//---------------------------------------------------------------------------
#include "fileio.h"
#include "rasdump_fileio.h"
#include <fcntl.h>
#include <unistd.h>
#include <cassert>
@ -60,27 +60,6 @@ bool Fileio::Open(const char *fname, OpenMode mode)
return Open(fname, mode, false);
}
bool Fileio::Open(const Filepath& path, OpenMode mode)
{
return Open(path.GetPath(), mode);
}
bool Fileio::OpenDIO(const char *fname, OpenMode mode)
{
// Open with included O_DIRECT
if (!Open(fname, mode, true)) {
// Normal mode retry (tmpfs etc.)
return Open(fname, mode, false);
}
return true;
}
bool Fileio::OpenDIO(const Filepath& path, OpenMode mode)
{
return OpenDIO(path.GetPath(), mode);
}
bool Fileio::Read(BYTE *buffer, int size) const
{
assert(buffer);
@ -99,14 +78,6 @@ bool Fileio::Write(const BYTE *buffer, int size) const
return write(handle, buffer, size) == size;
}
bool Fileio::Seek(off_t offset) const
{
assert(handle >= 0);
assert(offset >= 0);
return lseek(handle, offset, SEEK_SET) == offset;
}
off_t Fileio::GetFileSize() const
{
assert(handle >= 0);
@ -118,7 +89,7 @@ off_t Fileio::GetFileSize() const
const off_t end = lseek(handle, 0, SEEK_END);
// Return to start position
Seek(cur);
lseek(handle, cur, SEEK_SET);
return end;
}

View File

@ -10,7 +10,7 @@
#pragma once
#include "filepath.h"
#include "os.h"
#include <cstdlib>
class Fileio
@ -29,9 +29,6 @@ public:
Fileio& operator=(const Fileio&) = default;
bool Open(const char *fname, OpenMode mode);
bool Open(const Filepath& path, OpenMode mode);
bool OpenDIO(const Filepath& path, OpenMode mode);
bool Seek(off_t offset) const;
bool Read(BYTE *buffer, int size) const;
bool Write(const BYTE *buffer, int size) const;
off_t GetFileSize() const;
@ -40,7 +37,6 @@ public:
private:
bool Open(const char *fname, OpenMode mode, bool directIO);
bool OpenDIO(const char *fname, OpenMode mode);
int handle = -1;
};

View File

@ -14,6 +14,12 @@
using namespace std;
void HostServices_SetUpModePages(map<int, vector<byte>>& pages)
{
EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(10, pages[32].size());
}
TEST(HostServicesTest, Dispatch)
{
TestDispatch(SCHS);
@ -146,7 +152,12 @@ TEST(HostServicesTest, SetUpModePages)
MockHostServices services(0, controller_manager);
map<int, vector<byte>> pages;
// Non changeable
services.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(10, pages[32].size());
HostServices_SetUpModePages(pages);
// Changeable
pages.clear();
services.SetUpModePages(pages, 0x3f, true);
HostServices_SetUpModePages(pages);
}

View File

@ -331,6 +331,7 @@ class MockSCSIHD : public SCSIHD //NOSONAR Ignore inheritance hierarchy depth in
FRIEND_TEST(DiskTest, ConfiguredSectorSize);
FRIEND_TEST(ScsiHdTest, SupportsSaveParameters);
FRIEND_TEST(ScsiHdTest, FinalizeSetup);
FRIEND_TEST(ScsiHdTest, GetProductData);
FRIEND_TEST(ScsiHdTest, SetUpModePages);
FRIEND_TEST(RascsiExecutorTest, SetSectorSize);
FRIEND_TEST(ScsiHdTest, ModeSelect);

View File

@ -11,7 +11,9 @@
#include "rascsi_exceptions.h"
#include "rascsi_interface.pb.h"
#include "protobuf_serializer.h"
#include <filesystem>
using namespace filesystem;
using namespace rascsi_interface;
TEST(ProtobufSerializerTest, SerializeMessage)
@ -37,28 +39,25 @@ TEST(ProtobufSerializerTest, DeserializeMessage)
EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Reading the message header must fail";
close(fd);
string filename;
fd = OpenTempFile(filename);
EXPECT_NE(-1, fd);
auto [fd1, filename1] = OpenTempFile();
// Data size -1
buf = { byte{0xff}, byte{0xff}, byte{0xff}, byte{0xff} };
EXPECT_EQ(buf.size(), write(fd, buf.data(), buf.size()));
close(fd);
fd = open(filename.c_str(), O_RDONLY);
EXPECT_NE(-1, fd);
EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Invalid header was not rejected";
unlink(filename.c_str());
EXPECT_EQ(buf.size(), write(fd1, buf.data(), buf.size()));
close(fd1);
fd1 = open(filename1.c_str(), O_RDONLY);
EXPECT_NE(-1, fd1);
EXPECT_THROW(serializer.DeserializeMessage(fd1, result), io_exception) << "Invalid header was not rejected";
remove(filename1);
fd = OpenTempFile(filename);
EXPECT_NE(-1, fd);
auto [fd2, filename2] = OpenTempFile();
// Data size 2
buf = { byte{0x02}, byte{0x00}, byte{0x00}, byte{0x00} };
EXPECT_EQ(buf.size(), write(fd, buf.data(), buf.size()));
close(fd);
fd = open(filename.c_str(), O_RDONLY);
EXPECT_NE(-1, fd);
EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Invalid data were not rejected";
unlink(filename.c_str());
EXPECT_EQ(buf.size(), write(fd2, buf.data(), buf.size()));
close(fd2);
fd2 = open(filename2.c_str(), O_RDONLY);
EXPECT_NE(-1, fd2);
EXPECT_THROW(serializer.DeserializeMessage(fd2, result), io_exception) << "Invalid data were not rejected";
remove(filename2);
}
TEST(ProtobufSerializerTest, SerializeDeserializeMessage)
@ -67,8 +66,7 @@ TEST(ProtobufSerializerTest, SerializeDeserializeMessage)
result.set_status(true);
ProtobufSerializer serializer;
string filename;
int fd = OpenTempFile(filename);
auto [fd, filename] = OpenTempFile();
EXPECT_NE(-1, fd);
serializer.SerializeMessage(fd, result);
close(fd);
@ -78,7 +76,7 @@ TEST(ProtobufSerializerTest, SerializeDeserializeMessage)
EXPECT_NE(-1, fd);
serializer.DeserializeMessage(fd, result);
close(fd);
unlink(filename.c_str());
remove(filename);
EXPECT_TRUE(result.status());
}

View File

@ -18,8 +18,9 @@
#include "rascsi/rascsi_response.h"
#include "rascsi/rascsi_image.h"
#include "rascsi/rascsi_executor.h"
#include <unistd.h>
#include <filesystem>
using namespace filesystem;
using namespace rascsi_interface;
using namespace protobuf_util;
@ -279,39 +280,39 @@ TEST_F(RascsiExecutorTest, Attach)
SetParam(definition, "file", "/non_existing_file");
EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with non-existing image file not rejected";
string filename = CreateTempFile(1);
SetParam(definition, "file", filename);
path filename = CreateTempFile(1);
SetParam(definition, "file", filename.c_str());
EXPECT_THROW(executor.Attach(context, definition, false), io_exception) << "Too small image file not rejected";
unlink(filename.c_str());
remove(filename);
filename = CreateTempFile(512);
SetParam(definition, "file", filename);
SetParam(definition, "file", filename.c_str());
bool result = executor.Attach(context, definition, false);
unlink(filename.c_str());
remove(filename);
EXPECT_TRUE(result);
controller_manager.DeleteAllControllers();
filename = CreateTempFile(513);
SetParam(definition, "file", filename);
SetParam(definition, "file", filename.c_str());
result = executor.Attach(context, definition, false);
unlink(filename.c_str());
remove(filename);
EXPECT_TRUE(result);
definition.set_type(PbDeviceType::SCCD);
definition.set_unit(LUN + 1);
filename = CreateTempFile(2048);
SetParam(definition, "file", filename);
SetParam(definition, "file", filename.c_str());
result = executor.Attach(context, definition, false);
unlink(filename.c_str());
remove(filename);
EXPECT_TRUE(result);
definition.set_type(PbDeviceType::SCMO);
definition.set_unit(LUN + 2);
SetParam(definition, "read_only", "true");
filename = CreateTempFile(4096);
SetParam(definition, "file", filename);
SetParam(definition, "file", filename.c_str());
result = executor.Attach(context, definition, false);
unlink(filename.c_str());
remove(filename);
EXPECT_TRUE(result);
controller_manager.DeleteAllControllers();
@ -360,16 +361,16 @@ TEST_F(RascsiExecutorTest, Insert)
SetParam(definition, "file", "/non_existing_file");
EXPECT_FALSE(executor.Insert(context, definition, device, false));
string filename = CreateTempFile(1);
SetParam(definition, "file", filename);
path filename = CreateTempFile(1);
SetParam(definition, "file", filename.c_str());
EXPECT_THROW(executor.Insert(context, definition, device, false), io_exception)
<< "Too small image file not rejected";
unlink(filename.c_str());
remove(filename);
filename = CreateTempFile(512);
SetParam(definition, "file", filename);
SetParam(definition, "file", filename.c_str());
const bool result = executor.Insert(context, definition, device, false);
unlink(filename.c_str());
remove(filename);
EXPECT_TRUE(result);
}

View File

@ -52,7 +52,7 @@ TEST(RascsiImageTest, CreateImage)
EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid";
SetParam(command, "size", "513");
EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid";
EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as not a multiple of 512";
// Further tests would modify the filesystem
}
@ -68,6 +68,11 @@ TEST(RascsiImageTest, DeleteImage)
SetParam(command, "file", "/a/b/c/filename");
EXPECT_FALSE(image.DeleteImage(context, command)) << "Depth must be reported as invalid";
MockStorageDevice device;
device.ReserveFile("filename", 0, 0);
SetParam(command, "file", "filename");
EXPECT_FALSE(image.DeleteImage(context, command)) << "File must be reported as in use";
// Further testing would modify the filesystem
}
@ -77,17 +82,14 @@ TEST(RascsiImageTest, RenameImage)
PbCommand command;
RascsiImage image;
EXPECT_FALSE(image.RenameImage(context, command)) << "Filenames must be reported as missing";
SetParam(command, "to", "/a/b/c/filename_to");
EXPECT_FALSE(image.RenameImage(context, command)) << "Depth must be reported as invalid";
SetParam(command, "to", "filename_to");
EXPECT_FALSE(image.RenameImage(context, command)) << "Source filename must be reported as missing";
SetParam(command, "from", "/a/b/c/filename_from");
EXPECT_FALSE(image.RenameImage(context, command)) << "Depth must be reported as invalid";
SetParam(command, "from", "filename_from");
EXPECT_FALSE(image.RenameImage(context, command)) << "Source file must be reported as missing";
// Further testing would modify the filesystem
}
@ -97,17 +99,14 @@ TEST(RascsiImageTest, CopyImage)
PbCommand command;
RascsiImage image;
EXPECT_FALSE(image.CopyImage(context, command)) << "Filenames must be reported as missing";
SetParam(command, "to", "/a/b/c/filename_to");
EXPECT_FALSE(image.CopyImage(context, command)) << "Depth must be reported as invalid";
SetParam(command, "to", "filename_to");
EXPECT_FALSE(image.CopyImage(context, command)) << "Source filename must be reported as missing";
SetParam(command, "from", "/a/b/c/filename_from");
EXPECT_FALSE(image.CopyImage(context, command)) << "Depth must be reported as invalid";
SetParam(command, "from", "filename_from");
EXPECT_FALSE(image.CopyImage(context, command)) << "Source file must be reported as missing";
// Further testing would modify the filesystem
}

View File

@ -104,6 +104,16 @@ TEST(ScsiPrinterTest, StopPrint)
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(ScsiPrinterTest, SynchronizeBuffer)
{
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
auto printer = CreateDevice(SCLP, controller);
EXPECT_THROW(printer->Dispatch(scsi_command::eCmdSynchronizeBuffer), scsi_exception) << "Nothing to print";
// Further testing would use the printing system
}
TEST(ScsiPrinterTest, WriteByteSequence)
{
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);

View File

@ -9,6 +9,23 @@
#include "mocks.h"
#include "rascsi_exceptions.h"
#include <filesystem>
#include <fstream>
using namespace std;
using namespace filesystem;
void ScsiCdTest_SetUpModePages(map<int, vector<byte>>& pages)
{
EXPECT_EQ(7, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(8, pages[13].size());
EXPECT_EQ(16, pages[14].size());
EXPECT_EQ(30, pages[48].size());
}
TEST(ScsiCdTest, Inquiry)
{
@ -26,15 +43,76 @@ TEST(ScsiCdTest, SetUpModePages)
const unordered_set<uint32_t> sector_sizes;
MockSCSICD cd(0, sector_sizes);
// Non changeable
cd.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(7, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(8, pages[13].size());
EXPECT_EQ(16, pages[14].size());
EXPECT_EQ(30, pages[48].size());
ScsiCdTest_SetUpModePages(pages);
// Changeable
pages.clear();
cd.SetUpModePages(pages, 0x3f, true);
ScsiCdTest_SetUpModePages(pages);
}
TEST(ScsiCdTest, Open)
{
const unordered_set<uint32_t> sector_sizes;
MockSCSICD cd_iso(0, sector_sizes);
MockSCSICD cd_cue(0, sector_sizes);
MockSCSICD cd_raw(0, sector_sizes);
MockSCSICD cd_physical(0, sector_sizes);
EXPECT_THROW(cd_iso.Open(), io_exception) << "Missing filename";
path filename = CreateTempFile(2047);
cd_iso.SetFilename(string(filename));
EXPECT_THROW(cd_iso.Open(), io_exception) << "ISO CD-ROM image file size too small";
remove(filename);
filename = CreateTempFile(2* 2048);
cd_iso.SetFilename(string(filename));
cd_iso.Open();
EXPECT_EQ(2, cd_iso.GetBlockCount());
remove(filename);
filename = CreateTempFile(0);
ofstream out;
out.open(filename);
array<char, 4> cue = { 'F', 'I', 'L', 'E' };
out.write(cue.data(), cue.size());
out.close();
resize_file(filename, 2 * 2048);
cd_cue.SetFilename(string(filename));
EXPECT_THROW(cd_cue.Open(), io_exception) << "CUE CD-ROM files are not supported";
filename = CreateTempFile(0);
out.open(filename);
array<char, 16> header;
header.fill(0xff);
header[0] = 0;
header[11] = 0;
out.write(header.data(), header.size());
out.close();
resize_file(filename, 2 * 2535);
cd_raw.SetFilename(string(filename));
EXPECT_THROW(cd_raw.Open(), io_exception) << "Illegal raw ISO CD-ROM header";
header[15] = 0x01;
filename = CreateTempFile(0);
out.open(filename);
out.write(header.data(), header.size());
out.close();
resize_file(filename, 2 * 2535);
cd_raw.SetFilename(string(filename));
EXPECT_THROW(cd_raw.Open(), io_exception) << "Raw ISO CD-ROM image file size must be a multiple of 2536";
resize_file(filename, 2 * 2536);
cd_raw.Open();
EXPECT_EQ(2, cd_raw.GetBlockCount());
remove(filename);
filename = CreateTempFile(2* 2048);
cd_physical.SetFilename("\\" + string(filename));
// The respective code in SCSICD appears to be broken, see https://github.com/akuker/RASCSI/issues/919
EXPECT_THROW(cd_physical.Open(), io_exception) << "Invalid physical CD-ROM file";
remove(filename);
}
TEST(ScsiCdTest, ReadToc)

View File

@ -8,10 +8,24 @@
//---------------------------------------------------------------------------
#include "mocks.h"
#include "rascsi_exceptions.h"
#include "controllers/controller_manager.h"
#include "devices/scsihd_nec.h"
#include <filesystem>
#include <fstream>
using namespace std;
using namespace filesystem;
void ScsiHdNecTest_SetUpModePages(map<int, vector<byte>>& pages)
{
EXPECT_EQ(5, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(20, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(30, pages[48].size());
}
TEST(ScsiHdNecTest, Inquiry)
{
@ -23,13 +37,14 @@ TEST(ScsiHdNecTest, SetUpModePages)
map<int, vector<byte>> pages;
MockSCSIHD_NEC hd(0);
// Non changeable
hd.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(5, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(20, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(30, pages[48].size());
ScsiHdNecTest_SetUpModePages(pages);
// Changeable
pages.clear();
hd.SetUpModePages(pages, 0x3f, true);
ScsiHdNecTest_SetUpModePages(pages);
}
TEST(ScsiHdNecTest, TestAddFormatPage)
@ -46,10 +61,13 @@ TEST(ScsiHdNecTest, TestAddFormatPage)
EXPECT_EQ(0, (int)page_3[20]);
hd.SetRemovable(true);
// Non changeable
hd.SetUpModePages(pages, 0x03, false);
page_3 = pages[3];
EXPECT_EQ(0x20, (int)page_3[20]);
pages.clear();
// Changeable
hd.SetUpModePages(pages, 0x03, true);
EXPECT_EQ(0xffff, GetInt16(page_3, 12));
}
@ -61,6 +79,140 @@ TEST(ScsiHdNecTest, TestAddDrivePage)
hd.SetBlockCount(0x1234);
hd.SetReady(true);
// Non changeable
hd.SetUpModePages(pages, 0x04, false);
EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages";
pages.clear();
// Changeable
hd.SetUpModePages(pages, 0x04, true);
EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages";
}
TEST(ScsiHdNecTest, SetParameters)
{
MockSCSIHD_NEC hd_hdn(0);
MockSCSIHD_NEC hd_hdi(0);
MockSCSIHD_NEC hd_nhd(0);
EXPECT_THROW(hd_hdn.Open(), io_exception) << "Missing filename";
path tmp = CreateTempFile(511);
hd_hdn.SetFilename(string(tmp));
EXPECT_THROW(hd_hdn.Open(), io_exception) << "Root sector file is too small";
remove(tmp);
tmp = CreateTempFile(512);
hd_hdn.SetFilename(string(tmp));
EXPECT_THROW(hd_hdn.Open(), io_exception) << "Invalid file extension";
const auto hdn = path((string)tmp + ".HDN");
rename(tmp, hdn);
hd_hdn.SetFilename(string(hdn));
hd_hdn.Open();
remove(hdn);
tmp = CreateTempFile(512);
const auto hdi = path((string)tmp + ".hdi");
rename(tmp, hdi);
hd_hdi.SetFilename(string(hdi));
EXPECT_THROW(hd_hdi.Open(), io_exception) << "Invalid sector size";
ofstream out;
out.open(hdi);
const array<char, 4> cylinders1 = { 1, 0, 0, 0 };
out.seekp(28);
out.write(cylinders1.data(), cylinders1.size());
const array<char, 4> heads1 = { 1, 0, 0, 0 };
out.seekp(24);
out.write(heads1.data(), heads1.size());
const array<char, 4> sectors1 = { 1, 0, 0, 0 };
out.seekp(20);
out.write(sectors1.data(), sectors1.size());
const array<char, 4> sector_size1 = { 0, 2, 0, 0 };
out.seekp(16);
out.write(sector_size1.data(), sector_size1.size());
const array<char, 4> image_size = { 0, 2, 0, 0 };
out.seekp(12);
out.write(image_size.data(), image_size.size());
out.close();
resize_file(hdi, 512);
hd_hdi.Open();
remove(hdi);
tmp = CreateTempFile(512);
const auto nhd = path((string)tmp + ".nhd");
rename(tmp, nhd);
hd_nhd.SetFilename(string(nhd));
EXPECT_THROW(hd_nhd.Open(), io_exception) << "Invalid file format";
out.open(nhd);
out << "T98HDDIMAGE.R0";
out.close();
resize_file(nhd, 512);
EXPECT_THROW(hd_nhd.Open(), io_exception) << "Invalid sector size";
out.open(nhd);
out << "T98HDDIMAGE.R0";
// 512 bytes per sector
array<char, 2> sector_size2 = { 0, 2 };
out.seekp(0x11c);
out.write(sector_size2.data(), sector_size2.size());
out.close();
resize_file(nhd, 512);
EXPECT_THROW(hd_nhd.Open(), io_exception) << "Drive has 0 blocks";
out.open(nhd);
out << "T98HDDIMAGE.R0";
const array<char, 2> cylinders2 = { 1, 0 };
out.seekp(0x114);
out.write(cylinders2.data(), cylinders2.size());
const array<char, 2> heads2 = { 1, 0 };
out.seekp(0x118);
out.write(heads2.data(), heads2.size());
const array<char, 2> sectors2 = { 1, 0 };
out.seekp(0x11a);
out.write(sectors2.data(), sectors2.size());
out.seekp(0x11c);
out.write(sector_size2.data(), sector_size2.size());
const array<char, 4> image_offset = { 1, 0, 0, 0 };
out.seekp(0x110);
out.write(image_offset.data(), image_offset.size());
out.close();
resize_file(nhd, 512);
EXPECT_THROW(hd_nhd.Open(), io_exception) << "Invalid image offset/size";
out.open(nhd);
out << "T98HDDIMAGE.R0";
out.seekp(0x114);
out.write(cylinders2.data(), cylinders2.size());
out.seekp(0x118);
out.write(heads2.data(), heads2.size());
out.seekp(0x11a);
out.write(sectors2.data(), sectors2.size());
// 1 byte per sector
sector_size2 = { 1, 0 };
out.seekp(0x11c);
out.write(sector_size2.data(), sector_size2.size());
out.close();
resize_file(nhd, 512);
EXPECT_THROW(hd_nhd.Open(), io_exception) << "Invalid NEC disk/sector size";
out.open(nhd);
out << "T98HDDIMAGE.R0";
out.seekp(0x114);
out.write(cylinders2.data(), cylinders2.size());
out.seekp(0x118);
out.write(heads2.data(), heads2.size());
out.seekp(0x11a);
out.write(sectors2.data(), sectors2.size());
sector_size2 = { 0, 2 };
out.seekp(0x11c);
out.write(sector_size2.data(), sector_size2.size());
out.close();
resize_file(nhd, 512);
hd_nhd.Open();
remove(nhd);
}

View File

@ -11,15 +11,25 @@
#include "rascsi_exceptions.h"
#include "devices/scsihd.h"
void ScsiHdTest_SetUpModePages(map<int, vector<byte>>& pages)
{
EXPECT_EQ(5, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(30, pages[48].size());
}
TEST(ScsiHdTest, Inquiry)
{
TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, "RaSCSI ", 0x1f, false);
TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "RaSCSI ", 0x1f, false, ".hd1");
}
TEST(ScsiHdTest, SupportsSaveParameters)
{
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIHD hd(0, sector_sizes, false);
@ -28,19 +38,42 @@ TEST(ScsiHdTest, SupportsSaveParameters)
TEST(ScsiHdTest, FinalizeSetup)
{
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIHD hd(0, sector_sizes, false);
hd.SetSectorSizeInBytes(1024);
EXPECT_THROW(hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024 + hd.GetSectorSizeInBytes(), 0), io_exception)
<< "Unsupported drive capacity";
EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks";
}
hd.SetBlockCount(1);
hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024);
hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024 + hd.GetSectorSizeInBytes() - 1);
TEST(ScsiHdTest, GetProductData)
{
const unordered_set<uint32_t> sector_sizes;
MockSCSIHD hd_kb(0, sector_sizes, false);
MockSCSIHD hd_mb(0, sector_sizes, false);
MockSCSIHD hd_gb(0, sector_sizes, false);
const path filename = CreateTempFile(1);
hd_kb.SetFilename(string(filename));
hd_kb.SetSectorSizeInBytes(1024);
hd_kb.SetBlockCount(1);
hd_kb.FinalizeSetup(0);
string s = hd_kb.GetProduct();
EXPECT_NE(string::npos, s.find("1 KiB"));
hd_mb.SetFilename(string(filename));
hd_mb.SetSectorSizeInBytes(1024);
hd_mb.SetBlockCount(1'048'576 / 1024);
hd_mb.FinalizeSetup(0);
s = hd_mb.GetProduct();
EXPECT_NE(string::npos, s.find("1 MiB"));
hd_gb.SetFilename(string(filename));
hd_gb.SetSectorSizeInBytes(1024);
hd_gb.SetBlockCount(1'099'511'627'776 / 1024);
hd_gb.FinalizeSetup(0);
s = hd_gb.GetProduct();
EXPECT_NE(string::npos, s.find("1 GiB"));
remove(filename);
}
TEST(ScsiHdTest, SetUpModePages)
@ -49,14 +82,14 @@ TEST(ScsiHdTest, SetUpModePages)
const unordered_set<uint32_t> sector_sizes;
MockSCSIHD hd(0, sector_sizes, false);
hd.SetReady(false);
// Non changeable
hd.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(5, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(30, pages[48].size());
ScsiHdTest_SetUpModePages(pages);
// Changeable
pages.clear();
hd.SetUpModePages(pages, 0x3f, true);
ScsiHdTest_SetUpModePages(pages);
}
TEST(ScsiHdTest, ModeSelect)

View File

@ -9,6 +9,17 @@
#include "mocks.h"
void ScsiMo_SetUpModePages(map<int, vector<byte>>& pages)
{
EXPECT_EQ(6, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(4, pages[6].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(12, pages[32].size());
}
TEST(ScsiMoTest, Inquiry)
{
TestInquiry(SCMO, device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, "RaSCSI SCSI MO ", 0x1f, true);
@ -29,15 +40,14 @@ TEST(ScsiMoTest, SetUpModePages)
const unordered_set<uint32_t> sector_sizes;
MockSCSIMO mo(0, sector_sizes);
mo.SetReady(false);
// Non changeable
mo.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(6, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(4, pages[6].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(12, pages[32].size());
ScsiMo_SetUpModePages(pages);
// Changeable
pages.clear();
mo.SetUpModePages(pages, 0x3f, true);
ScsiMo_SetUpModePages(pages);
}
TEST(ScsiMoTest, TestAddVendorPage)

View File

@ -10,7 +10,9 @@
#include "mocks.h"
#include "rascsi_exceptions.h"
#include "devices/storage_device.h"
#include <unistd.h>
#include <filesystem>
using namespace filesystem;
TEST(StorageDeviceTest, Filename)
{
@ -25,26 +27,35 @@ TEST(StorageDeviceTest, ValidateFile)
MockStorageDevice device;
device.SetBlockCount(0);
EXPECT_THROW(device.ValidateFile("/non_existing_file"), io_exception);
device.SetFilename("/non_existing_file");
EXPECT_THROW(device.ValidateFile(), io_exception);
device.SetReadOnly(false);
device.SetProtectable(true);
device.SetBlockCount(1);
device.ValidateFile("/non_existing_file");
EXPECT_TRUE(device.IsReadOnly());
EXPECT_FALSE(device.IsProtectable());
EXPECT_FALSE(device.IsStopped());
EXPECT_FALSE(device.IsRemoved());
EXPECT_FALSE(device.IsLocked());
EXPECT_THROW(device.ValidateFile(), io_exception);
const path filename = CreateTempFile(1);
device.SetFilename(string(filename));
device.SetReadOnly(false);
device.SetProtectable(true);
device.ValidateFile("/dev/null");
device.ValidateFile();
EXPECT_FALSE(device.IsReadOnly());
EXPECT_TRUE(device.IsProtectable());
EXPECT_FALSE(device.IsStopped());
EXPECT_FALSE(device.IsRemoved());
EXPECT_FALSE(device.IsLocked());
permissions(filename, perms::owner_read);
device.SetReadOnly(false);
device.SetProtectable(true);
device.ValidateFile();
EXPECT_TRUE(device.IsReadOnly());
EXPECT_FALSE(device.IsProtectable());
EXPECT_FALSE(device.IsProtected());
EXPECT_FALSE(device.IsStopped());
EXPECT_FALSE(device.IsRemoved());
EXPECT_FALSE(device.IsLocked());
remove(filename);
}
TEST(StorageDeviceTest, MediumChanged)
@ -62,21 +73,24 @@ TEST(StorageDeviceTest, GetIdsForReservedFile)
{
const int ID = 1;
const int LUN = 2;
StorageDevice::UnreserveAll();
MockStorageDevice device;
device.SetFilename("filename");
int id;
int lun;
EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun));
const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile("filename");
EXPECT_EQ(-1, id1);
EXPECT_EQ(-1, lun1);
device.ReserveFile("filename", ID, LUN);
EXPECT_TRUE(StorageDevice::GetIdsForReservedFile("filename", id, lun));
EXPECT_EQ(ID, id);
EXPECT_EQ(LUN, lun);
const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile("filename");
EXPECT_EQ(ID, id2);
EXPECT_EQ(LUN, lun2);
device.UnreserveFile();
EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun));
const auto [id3, lun3] = StorageDevice::GetIdsForReservedFile("filename");
EXPECT_EQ(-1, id3);
EXPECT_EQ(-1, lun3);
}
TEST(StorageDeviceTest, UnreserveAll)
@ -85,9 +99,9 @@ TEST(StorageDeviceTest, UnreserveAll)
device.ReserveFile("filename", 2, 31);
StorageDevice::UnreserveAll();
int id;
int lun;
EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun));
const auto [id, lun] = StorageDevice::GetIdsForReservedFile("filename");
EXPECT_EQ(-1, id);
EXPECT_EQ(-1, lun);
}
TEST(StorageDeviceTest, GetSetReservedFiles)
@ -128,10 +142,10 @@ TEST(StorageDeviceTest, GetFileSize)
{
MockStorageDevice device;
const string filename = CreateTempFile(512);
device.SetFilename(filename);
const path filename = CreateTempFile(512);
device.SetFilename(filename.c_str());
const off_t size = device.GetFileSize();
unlink(filename.c_str());
remove(filename);
EXPECT_EQ(512, size);
device.SetFilename("/non_existing_file");

View File

@ -14,8 +14,10 @@
#include <unistd.h>
#include <vector>
#include <sstream>
#include <filesystem>
using namespace std;
using namespace filesystem;
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType type, MockAbstractController& controller, const string& extension)
{
@ -65,33 +67,31 @@ void TestDispatch(PbDeviceType type)
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
auto device = CreateDevice(type, controller);
EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class";
EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class";
}
int OpenTempFile(string& file)
pair<int, path> OpenTempFile()
{
char filename[] = "/tmp/rascsi_test-XXXXXX"; //NOSONAR mkstemp() requires a modifiable string
const string filename = string(temp_directory_path()) + "/rascsi_test-XXXXXX"; //NOSONAR Publicly writable directory is fine here
vector<char> f(filename.begin(), filename.end());
f.push_back(0);
const int fd = mkstemp(filename);
EXPECT_NE(-1, fd) << "Couldn't create temporary file '" << filename << "'";
const int fd = mkstemp(f.data());
EXPECT_NE(-1, fd) << "Couldn't create temporary file '" << f.data() << "'";
file = filename;
return fd;
return make_pair(fd, path(f.data()));
}
string CreateTempFile(int size)
path CreateTempFile(int size)
{
char filename[] = "/tmp/rascsi_test-XXXXXX"; //NOSONAR mkstemp() requires a modifiable string
const auto [fd, filename] = OpenTempFile();
vector<char> data(size);
const int fd = mkstemp(filename);
const size_t count = write(fd, data.data(), data.size());
close(fd);
EXPECT_EQ(count, data.size()) << "Couldn't create temporary file '" << string(filename) << "'";
return filename;
return path(filename);
}
int GetInt16(const vector<byte>& buf, int offset)
@ -106,6 +106,5 @@ uint32_t GetInt32(const vector<byte>& buf, int offset)
assert(buf.size() > (size_t)offset + 3);
return ((uint32_t)buf[offset] << 24) | ((uint32_t)buf[offset + 1] << 16) |
((uint32_t)buf[offset + 2] << 8) | (uint32_t)buf[offset + 3];
((uint32_t)buf[offset + 2] << 8) | (uint32_t)buf[offset + 3];
}

View File

@ -13,8 +13,10 @@
#include "rascsi_interface.pb.h"
#include <string>
#include <memory>
#include <filesystem>
using namespace std;
using namespace filesystem;
using namespace rascsi_interface;
class PrimaryDevice;
@ -27,9 +29,8 @@ void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, co
void TestDispatch(PbDeviceType);
int OpenTempFile(string&);
string CreateTempFile(int);
pair<int, path> OpenTempFile();
path CreateTempFile(int);
int GetInt16(const vector<byte>&, int);
uint32_t GetInt32(const vector<byte>&, int);