mirror of
https://github.com/akuker/RASCSI.git
synced 2025-04-10 04:37:11 +00:00
* Use C++ filesystem library/iostreams for I/O (issue #911) * Added unit tests
This commit is contained in:
parent
ea8bc3970d
commit
6bbaa956ed
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
#include "os.h"
|
||||
#include "log.h"
|
||||
#include "filepath.h"
|
||||
#include "cfilesystem.h"
|
||||
#include <dirent.h>
|
||||
#include <iconv.h>
|
||||
|
@ -13,6 +13,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
using TCHAR = char;
|
||||
|
||||
static const int FILEPATH_MAX = 260;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Status code definitions
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -5,17 +5,15 @@
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(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
|
||||
|
@ -5,19 +5,21 @@
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "disk.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
using Geometry = pair<uint32_t, uint32_t>;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -1,101 +0,0 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// X68000 EMULATOR "XM6"
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(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];
|
@ -1,51 +0,0 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// X68000 EMULATOR "XM6"
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(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)
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 "";
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
};
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user