mirror of
https://github.com/akuker/RASCSI.git
synced 2024-12-24 12:30:20 +00:00
Merge pull request #240 from akuker/feature_image_creation
rascsi support for creating/deleting/renaming image files in the default image folder
This commit is contained in:
commit
5161a83999
24
doc/rasctl.1
24
doc/rasctl.1
@ -5,6 +5,7 @@ rasctl \- Sends management commands to the rascsi process
|
||||
.B rasctl
|
||||
\fB\-l\fR |
|
||||
\fB\-s\fR |
|
||||
[\fB\-d\fR \fIIMAGE_FOLDER\fR]
|
||||
[\fB\-g\fR \fILOG_LEVEL\fR]
|
||||
[\fB\-h\fR \fIHOST\fR]
|
||||
[\fB\-p\fR \fIPORT\fR]
|
||||
@ -12,7 +13,7 @@ rasctl \- Sends management commands to the rascsi process
|
||||
[\fB\-v\fR]
|
||||
\fB\-i\fR \fIID\fR
|
||||
[\fB\-c\fR \fICMD\fR]
|
||||
[\fB\-f\fR \fIFILE\fR]
|
||||
[\fB\-f\fR \fIFILE|PARAM\fR]
|
||||
[\fB\-n\fR \fINAME\fR]
|
||||
[\fB\-t\fR \fITYPE\fR]
|
||||
[\fB\-u\fR \fIUNIT\fR]
|
||||
@ -28,8 +29,14 @@ Note: The command and type arguments are case insensitive. Only the first letter
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-a\fI " "\fIFILENAME:FILESIZE
|
||||
Create an image file in the default image folder with the specified name and size in bytes.
|
||||
.TP
|
||||
.BR \-g\fI " "\fIIMAGE_FOLDER
|
||||
Set the default image folder.
|
||||
.TP
|
||||
.BR \-g\fI " "\fILOG_LEVEL
|
||||
The rascsi log level to set (trace, debug, info, warn, err, critical, off)
|
||||
Set the rascsi log level (trace, debug, info, warn, err, critical, off).
|
||||
.TP
|
||||
.BR \-h\fI " " \fIHOST
|
||||
The rascsi host to connect to, default is 'localhost'.
|
||||
@ -37,6 +44,9 @@ The rascsi host to connect to, default is 'localhost'.
|
||||
.BR \-l\fI
|
||||
List all of the devices that are currently being emulated by RaSCSI, as well as their current status.
|
||||
.TP
|
||||
.BR \-m\fI " "\fICURRENT_NAME:NEW_NAME
|
||||
Rename an image file in the default image folder.
|
||||
.TP
|
||||
.BR \-p\fI " " \fIPORT
|
||||
The rascsi port to connect to, default is 6868.
|
||||
.TP
|
||||
@ -49,6 +59,12 @@ Display server-side settings like available images or supported device types.
|
||||
.BR \-v\fI " " \fI
|
||||
Display the rascsi version.
|
||||
.TP
|
||||
.BR \-w\fI " "\fIFILENAME
|
||||
Delete an image file in the default image folder.
|
||||
.TP
|
||||
.BR \-x\fI " "\fICURRENT_NAME:NEW_NAME
|
||||
Copy an image file in the default image folder.
|
||||
.TP
|
||||
.BR \-i\fI " " \fIID
|
||||
ID is the SCSI ID that you want to control. (0-7)
|
||||
.TP
|
||||
@ -67,8 +83,8 @@ eject, protect and unprotect are idempotent.
|
||||
.BR \-b\fI " " \fIBLOCK_SIZE
|
||||
The optional block size. For SCSI drives 512, 1024, 2048 or 4096 bytes, default size is 512 bytes. For SASI drives 256 or 1024 bytes, default is 256 bytes.
|
||||
.TP
|
||||
.BR \-f\fI " " \fIFILE
|
||||
Path to the disk image file. See the rascsi(1) man page for allowable file types.
|
||||
.BR \-f\fI " " \fIFILE|PARAM
|
||||
Device-specific: Either a path to a disk image file, or a parameter for a non-disk device. See the rascsi(1) man page for permitted file types.
|
||||
.TP
|
||||
.BR \-t\fI " " \fITYPE
|
||||
Specifies the device type. This type overrides the type derived from the file extension of the specified image. See the rascsi(1) man page for the available device types. For some types there are shortcuts (only the first letter is required):
|
||||
|
@ -1,32 +1,46 @@
|
||||
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
|
||||
!! ------ The native file is rasctl.1. Re-run 'make docs' after updating
|
||||
|
||||
|
||||
!! ------ The native file is rasctl.1. Re-run 'make docs' after updating\n\n
|
||||
rascsi(1) General Commands Manual rascsi(1)
|
||||
|
||||
NAME
|
||||
rasctl - Sends management commands to the rascsi process
|
||||
|
||||
SYNOPSIS
|
||||
rasctl -l | -s | [-g LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] [-v] -i ID [-c CMD] [-f FILE] [-n NAME] [-t TYPE] [-u UNIT]
|
||||
rasctl -l | -s | [-d IMAGE_FOLDER] [-g LOG_LEVEL] [-h HOST] [-p PORT]
|
||||
[-r RESERVED_IDS] [-v] -i ID [-c CMD] [-f FILE|PARAM] [-n NAME] [-t
|
||||
TYPE] [-u UNIT]
|
||||
|
||||
DESCRIPTION
|
||||
rasctl Sends commands to the rascsi process to make configuration adjustments at runtime or to check the status of the devices.
|
||||
rasctl Sends commands to the rascsi process to make configuration ad‐
|
||||
justments at runtime or to check the status of the devices.
|
||||
|
||||
Either the -i or -l option should be specified at one time. Not both.
|
||||
|
||||
You do NOT need root privileges to use rasctl.
|
||||
|
||||
Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool.
|
||||
Note: The command and type arguments are case insensitive. Only the
|
||||
first letter of the command/type is evaluated by the tool.
|
||||
|
||||
OPTIONS
|
||||
-a FILENAME:FILESIZE
|
||||
Create an image file in the default image folder with the speci‐
|
||||
fied name and size in bytes.
|
||||
|
||||
-g IMAGE_FOLDER
|
||||
Set the default image folder.
|
||||
|
||||
-g LOG_LEVEL
|
||||
The rascsi log level to set (trace, debug, info, warn, err, critical, off)
|
||||
Set the rascsi log level (trace, debug, info, warn, err, criti‐
|
||||
cal, off).
|
||||
|
||||
-h HOST
|
||||
The rascsi host to connect to, default is 'localhost'.
|
||||
|
||||
-l List all of the devices that are currently being emulated by RaSCSI, as well as their current status.
|
||||
-l List all of the devices that are currently being emulated by
|
||||
RaSCSI, as well as their current status.
|
||||
|
||||
-m CURRENT_NAME:NEW_NAME
|
||||
Rename an image file in the default image folder.
|
||||
|
||||
-p PORT
|
||||
The rascsi port to connect to, default is 6868.
|
||||
@ -34,10 +48,17 @@ OPTIONS
|
||||
-r RESERVED_IDS
|
||||
Comma-separated list of IDs to reserve.
|
||||
|
||||
-s Display server-side settings like available images or supported device types.
|
||||
-s Display server-side settings like available images or supported
|
||||
device types.
|
||||
|
||||
-v Display the rascsi version.
|
||||
|
||||
-w FILENAME
|
||||
Delete an image file in the default image folder.
|
||||
|
||||
-x CURRENT_NAME:NEW_NAME
|
||||
Copy an image file in the default image folder.
|
||||
|
||||
-i ID ID is the SCSI ID that you want to control. (0-7)
|
||||
|
||||
-c CMD Command is the operation being requested. Options are:
|
||||
@ -45,21 +66,29 @@ OPTIONS
|
||||
d(etach): Detach disk
|
||||
i(nsert): Insert media (removable media devices only)
|
||||
e(ject): Eject media (removable media devices only)
|
||||
p(rotect): Write protect the medium (not for CD-ROMs, which are always read-only)
|
||||
u(nprotect): Remove write protection from the medium (not for CD-ROMs, which are always read-only)
|
||||
p(rotect): Write protect the medium (not for CD-ROMs, which
|
||||
are always read-only)
|
||||
u(nprotect): Remove write protection from the medium (not for
|
||||
CD-ROMs, which are always read-only)
|
||||
s(how): Display device information
|
||||
|
||||
eject, protect and unprotect are idempotent.
|
||||
|
||||
-b BLOCK_SIZE
|
||||
The optional block size. For SCSI drives 512, 1024, 2048 or 4096 bytes, default size is 512 bytes. For SASI drives 256 or 1024 bytes, default is 256 bytes.
|
||||
The optional block size. For SCSI drives 512, 1024, 2048 or 4096
|
||||
bytes, default size is 512 bytes. For SASI drives 256 or 1024
|
||||
bytes, default is 256 bytes.
|
||||
|
||||
-f FILE
|
||||
Path to the disk image file. See the rascsi(1) man page for allowable file types.
|
||||
-f FILE|PARAM
|
||||
Device-specific: Either a path to a disk image file, or a param‐
|
||||
eter for a non-disk device. See the rascsi(1) man page for per‐
|
||||
mitted file types.
|
||||
|
||||
-t TYPE
|
||||
Specifies the device type. This type overrides the type derived from the file extension of the specified image. See the rascsi(1) man page for the available device types.
|
||||
For some types there are shortcuts (only the first letter is required):
|
||||
Specifies the device type. This type overrides the type derived
|
||||
from the file extension of the specified image. See the
|
||||
rascsi(1) man page for the available device types. For some
|
||||
types there are shortcuts (only the first letter is required):
|
||||
hd: SCSI hard disk drive
|
||||
rm: SCSI removable media drive
|
||||
cd: CD-ROM
|
||||
@ -68,11 +97,17 @@ OPTIONS
|
||||
daynaport: DaynaPORT network adapter
|
||||
|
||||
-n VENDOR:PRODUCT:REVISION
|
||||
The vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name components must be provided. VENDOR may have up to 8, PRODUCT
|
||||
up to 16, REVISION up to 4 characters. Padding with blanks to the maxium length is automatically applied. Once set the name of a device cannot be changed.
|
||||
The vendor, product and revision for the device, to be returned
|
||||
with the INQUIRY data. A complete set of name components must be
|
||||
provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up
|
||||
to 4 characters. Padding with blanks to the maxium length is au‐
|
||||
tomatically applied. Once set the name of a device cannot be
|
||||
changed.
|
||||
|
||||
-u UNIT
|
||||
Unit number (0 or 1). This will default to 0. This option is only used when there are multiple SCSI devices on a shared SCSI controller. (This is not common)
|
||||
Unit number (0 or 1). This will default to 0. This option is
|
||||
only used when there are multiple SCSI devices on a shared SCSI
|
||||
controller. (This is not common)
|
||||
|
||||
EXAMPLES
|
||||
Show a listing of all of the SCSI devices and their current status.
|
||||
@ -85,12 +120,14 @@ EXAMPLES
|
||||
| 0 | 1 | SCHD | /home/pi/harddisk.hda
|
||||
+----+----+------+-------------------------------------
|
||||
|
||||
Request the RaSCSI process to attach a disk (assumed) to SCSI ID 0 with the contents of the file system image "HDIIMAGE0.HDS".
|
||||
Request the RaSCSI process to attach a disk (assumed) to SCSI ID 0 with
|
||||
the contents of the file system image "HDIIMAGE0.HDS".
|
||||
rasctl -i 0 -f HDIIMAGE0.HDS
|
||||
|
||||
SEE ALSO
|
||||
rascsi(1) scsimon(1)
|
||||
|
||||
Full documentation is available at: <https://www.github.com/akuker/RASCSI/wiki/>
|
||||
Full documentation is available at:
|
||||
<https://www.github.com/akuker/RASCSI/wiki/>
|
||||
|
||||
rascsi(1)
|
||||
|
@ -108,6 +108,11 @@ const string Device::GetPaddedName() const
|
||||
return name;
|
||||
}
|
||||
|
||||
const string Device::GetParam(const string& key)
|
||||
{
|
||||
return params.find(key) != params.end() ? params[key] : "";
|
||||
}
|
||||
|
||||
void Device::SetStatusCode(int status_code)
|
||||
{
|
||||
if (status_code) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
@ -96,10 +96,10 @@ private:
|
||||
string revision;
|
||||
|
||||
// The parameters the device was created with
|
||||
list<string> params;
|
||||
map<string, string> params;
|
||||
|
||||
// The default parameters
|
||||
list<string> default_params;
|
||||
map<string, string> default_params;
|
||||
|
||||
// Sense Key, ASC and ASCQ
|
||||
int status_code;
|
||||
@ -110,7 +110,7 @@ public:
|
||||
virtual ~Device() {};
|
||||
|
||||
// Override for device specific initializations, to be called after all device properties have been set
|
||||
virtual bool Init(const list<string>&) { return true; };
|
||||
virtual bool Init(const map<string, string>&) { return true; };
|
||||
|
||||
virtual bool Dispatch(SCSIDEV *) = 0;
|
||||
|
||||
@ -163,13 +163,14 @@ public:
|
||||
|
||||
bool SupportsParams() const { return supports_params; }
|
||||
void SupportsParams(bool supports_paams) { this->supports_params = supports_paams; }
|
||||
const list<string> GetParams() const { return params; }
|
||||
void SetParams(const list<string>& params) { this->params = params; }
|
||||
const list<string> GetDefaultParams() const { return default_params; }
|
||||
void SetDefaultParams(const list<string>& default_params) { this->default_params = default_params; }
|
||||
const map<string, string> GetParams() const { return params; }
|
||||
const string GetParam(const string&);
|
||||
void SetParams(const map<string, string>& params) { this->params = params; }
|
||||
const map<string, string> GetDefaultParams() const { return default_params; }
|
||||
void SetDefaultParams(const map<string, string>& default_params) { this->default_params = default_params; }
|
||||
|
||||
int GetStatusCode() const { return status_code; }
|
||||
void SetStatusCode(int status_code);
|
||||
void SetStatusCode(int);
|
||||
|
||||
bool Start();
|
||||
void Stop();
|
||||
|
@ -53,8 +53,8 @@ DeviceFactory::DeviceFactory()
|
||||
default_params[SCRM] = {};
|
||||
default_params[SCMO] = {};
|
||||
default_params[SCCD] = {};
|
||||
default_params[SCBR] = { "eth0,wlan0" };
|
||||
default_params[SCDP] = { "eth0,wlan0" };
|
||||
default_params[SCBR]["interfaces"] = "eth0,wlan0";
|
||||
default_params[SCDP]["interfaces"] = "eth0,wlan0";
|
||||
}
|
||||
|
||||
DeviceFactory& DeviceFactory::instance()
|
||||
|
@ -36,7 +36,7 @@ public:
|
||||
const set<uint32_t>& GetSectorSizes(PbDeviceType type) { return sector_sizes[type]; }
|
||||
const set<uint32_t>& GetSectorSizes(const string&);
|
||||
const set<uint64_t> GetCapacities(PbDeviceType);
|
||||
const list<string>& GetDefaultParams(PbDeviceType type) { return default_params[type]; }
|
||||
const map<string, string>& GetDefaultParams(PbDeviceType type) { return default_params[type]; }
|
||||
|
||||
Device *CreateDevice(PbDeviceType type, const string& filename, const string& ext);
|
||||
|
||||
@ -46,5 +46,5 @@ private:
|
||||
|
||||
map<PbDeviceType, map<uint64_t, Geometry>> geometries;
|
||||
|
||||
map<PbDeviceType, list<string>> default_params;
|
||||
map<PbDeviceType, map<string, string>> default_params;
|
||||
};
|
||||
|
@ -25,7 +25,7 @@ void FileSupport::UnreserveFile()
|
||||
reserved_files.erase(diskpath.GetPath());
|
||||
}
|
||||
|
||||
bool FileSupport::GetIdsForReservedFile(const Filepath& path, int& id, int& unit) const
|
||||
bool FileSupport::GetIdsForReservedFile(const Filepath& path, int& id, int& unit)
|
||||
{
|
||||
if (reserved_files.find(path.GetPath()) != reserved_files.end()) {
|
||||
const id_set ids = reserved_files[path.GetPath()];
|
||||
|
@ -38,8 +38,8 @@ public:
|
||||
static void SetReservedFiles(const map<string, id_set>& files_in_use) { FileSupport::reserved_files = files_in_use; }
|
||||
void ReserveFile(const Filepath&, int, int);
|
||||
void UnreserveFile();
|
||||
bool GetIdsForReservedFile(const Filepath&, int&, int&) const;
|
||||
|
||||
static bool GetIdsForReservedFile(const Filepath&, int&, int&);
|
||||
static void UnreserveAll();
|
||||
|
||||
virtual void Open(const Filepath&) = 0;
|
||||
|
@ -85,13 +85,13 @@ bool SCSIDaynaPort::Dispatch(SCSIDEV *controller)
|
||||
return Disk::Dispatch(controller);
|
||||
}
|
||||
|
||||
bool SCSIDaynaPort::Init(const list<string>& params)
|
||||
bool SCSIDaynaPort::Init(const map<string, string>& params)
|
||||
{
|
||||
SetParams(params.empty() ? GetDefaultParams() : params);
|
||||
|
||||
#ifdef __linux__
|
||||
// TAP Driver Generation
|
||||
m_tap = new CTapDriver(GetParams().front());
|
||||
m_tap = new CTapDriver(GetParam("interfaces"));
|
||||
m_bTapEnable = m_tap->Init();
|
||||
if(!m_bTapEnable){
|
||||
LOGERROR("Unable to open the TAP interface");
|
||||
|
@ -60,7 +60,7 @@ public:
|
||||
SCSIDaynaPort();
|
||||
~SCSIDaynaPort();
|
||||
|
||||
bool Init(const list<string>&) override;
|
||||
bool Init(const map<string, string>&) override;
|
||||
void Open(const Filepath& path) override;
|
||||
|
||||
// Commands
|
||||
|
@ -63,14 +63,14 @@ SCSIBR::~SCSIBR()
|
||||
}
|
||||
}
|
||||
|
||||
bool SCSIBR::Init(const list<string>& params)
|
||||
bool SCSIBR::Init(const map<string, string>& params)
|
||||
{
|
||||
// Use default parameters if no parameters were provided
|
||||
SetParams(params.empty() ? GetDefaultParams() : params);
|
||||
|
||||
#ifdef __linux__
|
||||
// TAP Driver Generation
|
||||
tap = new CTapDriver(GetParams().front());
|
||||
tap = new CTapDriver(GetParam("interfaces"));
|
||||
m_bTapEnable = tap->Init();
|
||||
|
||||
// Generate MAC Address
|
||||
|
@ -50,7 +50,7 @@ public:
|
||||
SCSIBR();
|
||||
~SCSIBR();
|
||||
|
||||
bool Init(const list<string>&) override;
|
||||
bool Init(const map<string, string>&) override;
|
||||
bool Dispatch(SCSIDEV *) override;
|
||||
|
||||
// Commands
|
||||
|
@ -8,7 +8,6 @@
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sstream>
|
||||
#include "rascsi_interface.pb.h"
|
||||
#include "exceptions.h"
|
||||
#include "protobuf_util.h"
|
||||
@ -16,6 +15,43 @@
|
||||
using namespace std;
|
||||
using namespace rascsi_interface;
|
||||
|
||||
|
||||
const string GetParam(const PbCommand& command, const string& key)
|
||||
{
|
||||
auto map = command.params();
|
||||
return map[key];
|
||||
}
|
||||
|
||||
const string GetParam(const PbDeviceDefinition& device, const string& key)
|
||||
{
|
||||
auto map = device.params();
|
||||
return map[key];
|
||||
}
|
||||
|
||||
void AddParam(PbCommand& command, const string& key, const string& value)
|
||||
{
|
||||
if (!key.empty() && !value.empty()) {
|
||||
auto& map = *command.mutable_params();
|
||||
map[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void AddParam(PbDevice& device, const string& key, const string& value)
|
||||
{
|
||||
if (!key.empty() && !value.empty()) {
|
||||
auto& map = *device.mutable_params();
|
||||
map[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void AddParam(PbDeviceDefinition& device, const string& key, const string& value)
|
||||
{
|
||||
if (!key.empty() && !value.empty()) {
|
||||
auto& map = *device.mutable_params();
|
||||
map[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Serialize/Deserialize protobuf message: Length followed by the actual data.
|
||||
|
@ -14,6 +14,13 @@
|
||||
#include "google/protobuf/message.h"
|
||||
#include "rascsi_interface.pb.h"
|
||||
|
||||
using namespace rascsi_interface;
|
||||
|
||||
const string GetParam(const PbCommand&, const string&);
|
||||
const string GetParam(const PbDeviceDefinition&, const string&);
|
||||
void AddParam(PbCommand&, const string&, const string&);
|
||||
void AddParam(PbDevice&, const string&, const string&);
|
||||
void AddParam(PbDeviceDefinition&, const string&, const string&);
|
||||
void SerializeMessage(int, const google::protobuf::Message&);
|
||||
void DeserializeMessage(int, google::protobuf::Message&);
|
||||
int ReadNBytes(int, uint8_t *, int);
|
||||
|
@ -31,11 +31,13 @@
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include <spdlog/async.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std;
|
||||
@ -132,7 +134,7 @@ bool InitService(int port)
|
||||
{
|
||||
int result = pthread_mutex_init(&ctrl_mutex,NULL);
|
||||
if (result != EXIT_SUCCESS){
|
||||
LOGERROR("Unable to create a mutex. Err code: %d", result);
|
||||
LOGERROR("Unable to create a mutex. Error code: %d", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -155,7 +157,7 @@ bool InitService(int port)
|
||||
// Bind
|
||||
if (bind(monsocket, (struct sockaddr *)&server,
|
||||
sizeof(struct sockaddr_in)) < 0) {
|
||||
FPRT(stderr, "Error : Already running?\n");
|
||||
FPRT(stderr, "Error: Already running?\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -488,7 +490,8 @@ PbDeviceProperties *GetDeviceProperties(const Device *device)
|
||||
|
||||
if (device->SupportsParams()) {
|
||||
for (const auto& param : device_factory.GetDefaultParams(t)) {
|
||||
properties->add_default_params(param);
|
||||
auto& map = *properties->mutable_default_params();
|
||||
map[param.first] = param.second;
|
||||
}
|
||||
}
|
||||
|
||||
@ -526,8 +529,8 @@ void GetAllDeviceTypeProperties(PbServerInfo& server_info)
|
||||
void GetAvailableImages(PbServerInfo& server_info)
|
||||
{
|
||||
if (!access(default_image_folder.c_str(), F_OK)) {
|
||||
for (const auto& entry : filesystem::directory_iterator(default_image_folder)) {
|
||||
if (entry.is_regular_file()) {
|
||||
for (const auto& entry : filesystem::directory_iterator(default_image_folder, filesystem::directory_options::skip_permission_denied)) {
|
||||
if (entry.is_regular_file() && entry.file_size() && !(entry.file_size() & 0x1ff)) {
|
||||
GetImageFile(server_info.add_image_files(), entry.path().filename());
|
||||
}
|
||||
}
|
||||
@ -556,8 +559,8 @@ void GetDevice(const Device *device, PbDevice *pb_device)
|
||||
status->set_locked(device->IsLocked());
|
||||
|
||||
if (device->SupportsParams()) {
|
||||
for (const string& param : device->GetParams()) {
|
||||
pb_device->add_params(param);
|
||||
for (const auto& param : device->GetParams()) {
|
||||
AddParam(*pb_device, param.first, param.second);
|
||||
}
|
||||
}
|
||||
|
||||
@ -669,8 +672,15 @@ bool SetDefaultImageFolder(const string& f)
|
||||
return true;
|
||||
}
|
||||
|
||||
string SetReservedIds(const list<string>& ids_to_reserve)
|
||||
string SetReservedIds(const string& ids)
|
||||
{
|
||||
list<string> ids_to_reserve;
|
||||
stringstream ss(ids);
|
||||
string id;
|
||||
while (getline(ss, id, ',')) {
|
||||
ids_to_reserve.push_back(id);
|
||||
}
|
||||
|
||||
set<int> reserved;
|
||||
for (string id_to_reserve : ids_to_reserve) {
|
||||
int id;
|
||||
@ -692,8 +702,8 @@ string SetReservedIds(const list<string>& ids_to_reserve)
|
||||
if (!isFirst) {
|
||||
s << ", ";
|
||||
}
|
||||
s << id;
|
||||
isFirst = false;
|
||||
s << id;
|
||||
}
|
||||
|
||||
LOGINFO("Reserved IDs set to: %s", s.str().c_str());
|
||||
@ -705,6 +715,272 @@ string SetReservedIds(const list<string>& ids_to_reserve)
|
||||
return "";
|
||||
}
|
||||
|
||||
bool IsValidFilename(const string& filename)
|
||||
{
|
||||
struct stat st;
|
||||
return stat(filename.c_str(), &st) || !S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
bool CreateImage(int fd, const PbCommand& command)
|
||||
{
|
||||
string filename = GetParam(command, "file");
|
||||
if (filename.empty()) {
|
||||
return ReturnStatus(fd, false, "Can't create image file: Missing image filename");
|
||||
}
|
||||
|
||||
if (!IsValidFilename(filename)) {
|
||||
return ReturnStatus(fd, false, "Can't create image file: '" + filename + "': Invalid filename");
|
||||
}
|
||||
|
||||
string size = GetParam(command, "size");
|
||||
if (size.empty()) {
|
||||
return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Missing image size");
|
||||
}
|
||||
|
||||
string permission = GetParam(command, "read_only");
|
||||
if (permission.empty()) {
|
||||
return ReturnStatus(fd, false, "Can't create image file'" + filename + "': Missing read-only flag");
|
||||
}
|
||||
|
||||
if (strcasecmp(permission.c_str(), "true") && strcasecmp(permission.c_str(), "false")) {
|
||||
return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Invalid read-only flag '" + permission + "'");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (filename.find('/') != string::npos) {
|
||||
return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Filename must not contain a path");
|
||||
}
|
||||
|
||||
filename = default_image_folder + "/" + filename;
|
||||
|
||||
off_t len;
|
||||
try {
|
||||
len = stoul(size);
|
||||
}
|
||||
catch(const invalid_argument& e) {
|
||||
return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Invalid file size " + size);
|
||||
}
|
||||
catch(const out_of_range& e) {
|
||||
return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Invalid file size " + size);
|
||||
}
|
||||
if (len < 512 || (len & 0x1ff)) {
|
||||
ostringstream error;
|
||||
error << "Invalid image file size " << len;
|
||||
return ReturnStatus(fd, false, error.str());
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (!stat(filename.c_str(), &st)) {
|
||||
return ReturnStatus(fd, false, "Can't create image file '" + filename + "': File already exists");
|
||||
}
|
||||
|
||||
// Since rascsi is running as root ensure that others can access the file
|
||||
int image_fd = open(filename.c_str(), O_CREAT|O_WRONLY, permissions);
|
||||
if (image_fd == -1) {
|
||||
return ReturnStatus(fd, false, "Can't create image file '" + filename + "': " + string(strerror(errno)));
|
||||
}
|
||||
|
||||
if (fallocate(image_fd, 0, 0, len) == -1) {
|
||||
close(image_fd);
|
||||
|
||||
return ReturnStatus(fd, false, "Can't allocate space for image file '" + filename + "': " + string(strerror(errno)));
|
||||
}
|
||||
|
||||
close(image_fd);
|
||||
|
||||
ostringstream msg;
|
||||
msg << "Created " << (permissions & S_IWUSR ? "": "read-only ") << "image file '" << filename + "' with a size of " << len << " bytes";
|
||||
LOGINFO("%s", msg.str().c_str());
|
||||
|
||||
return ReturnStatus(fd);
|
||||
}
|
||||
|
||||
bool DeleteImage(int fd, const PbCommand& command)
|
||||
{
|
||||
string filename = GetParam(command, "file");
|
||||
if (filename.empty()) {
|
||||
return ReturnStatus(fd, false, "Missing image filename");
|
||||
}
|
||||
|
||||
if (!IsValidFilename(filename)) {
|
||||
return ReturnStatus(fd, false, "Can't delete image file '" + filename + "': Invalid filename");
|
||||
}
|
||||
|
||||
if (filename.find('/') != string::npos) {
|
||||
return ReturnStatus(fd, false, "The image filename '" + filename + "' must not contain a path");
|
||||
}
|
||||
|
||||
filename = default_image_folder + "/" + filename;
|
||||
|
||||
int id;
|
||||
int unit;
|
||||
Filepath filepath;
|
||||
filepath.SetPath(filename.c_str());
|
||||
if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) {
|
||||
ostringstream msg;
|
||||
msg << "Can't delete image file '" << filename << "', it is used by device ID " << id << ", unit " << unit;
|
||||
return ReturnStatus(fd, false, msg.str());
|
||||
}
|
||||
|
||||
if (unlink(filename.c_str())) {
|
||||
return ReturnStatus(fd, false, "Can't delete image file '" + filename + "': " + string(strerror(errno)));
|
||||
}
|
||||
|
||||
LOGINFO("%s", string("Deleted image file '" + filename + "'").c_str());
|
||||
|
||||
return ReturnStatus(fd);
|
||||
}
|
||||
|
||||
bool RenameImage(int fd, const PbCommand& command)
|
||||
{
|
||||
string from = GetParam(command, "from");
|
||||
if (from.empty()) {
|
||||
return ReturnStatus(fd, false, "Can't rename image file: Missing source filename");
|
||||
}
|
||||
|
||||
string to = GetParam(command, "to");
|
||||
if (to.empty()) {
|
||||
return ReturnStatus(fd, false, "Can't rename image file '" + from + "': Missing destination filename");
|
||||
}
|
||||
|
||||
if (!IsValidFilename(from)) {
|
||||
return ReturnStatus(fd, false, "Can't rename image file: '" + from + "': Invalid filename");
|
||||
}
|
||||
|
||||
if (!IsValidFilename(to)) {
|
||||
return ReturnStatus(fd, false, "Can't rename image file '" + from + "' to '" + to + "': Invalid filename");
|
||||
}
|
||||
|
||||
if (from.find('/') != string::npos) {
|
||||
return ReturnStatus(fd, false, "The source filename '" + from + "' must not contain a path");
|
||||
}
|
||||
if (to.find('/') != string::npos) {
|
||||
return ReturnStatus(fd, false, "The destination filename '" + to + "' must not contain a path");
|
||||
}
|
||||
|
||||
from = default_image_folder + "/" + from;
|
||||
to = default_image_folder + "/" + to;
|
||||
|
||||
struct stat st;
|
||||
if (!stat(to.c_str(), &st)) {
|
||||
return ReturnStatus(fd, false, "Image file '" + to + "' already exists");
|
||||
}
|
||||
|
||||
if (rename(from.c_str(), to.c_str())) {
|
||||
return ReturnStatus(fd, false, "Can't rename image file '" + from + "' to '" + to + "': " + string(strerror(errno)));
|
||||
}
|
||||
|
||||
LOGINFO("%s", string("Renamed image file '" + from + "' to '" + to + "'").c_str());
|
||||
|
||||
return ReturnStatus(fd);
|
||||
}
|
||||
|
||||
bool CopyImage(int fd, const PbCommand& command)
|
||||
{
|
||||
string from = GetParam(command, "from");
|
||||
if (from.empty()) {
|
||||
return ReturnStatus(fd, false, "Can't copy image file: Missing source filename");
|
||||
}
|
||||
|
||||
string to = GetParam(command, "to");
|
||||
if (to.empty()) {
|
||||
return ReturnStatus(fd, false, "Can't copy image file '" + from + "': Missing destination filename");
|
||||
}
|
||||
|
||||
if (!IsValidFilename(from)) {
|
||||
return ReturnStatus(fd, false, "Can't copy image file: '" + from + "': Invalid filename");
|
||||
}
|
||||
|
||||
if (!IsValidFilename(to)) {
|
||||
return ReturnStatus(fd, false, "Can't copy image file '" + from + "' to '" + to + "': Invalid filename");
|
||||
}
|
||||
|
||||
if (from.find('/') != string::npos) {
|
||||
return ReturnStatus(fd, false, "The source filename '" + from + "' must not contain a path");
|
||||
}
|
||||
if (to.find('/') != string::npos) {
|
||||
return ReturnStatus(fd, false, "The destination filename '" + to + "' must not contain a path");
|
||||
}
|
||||
|
||||
from = default_image_folder + "/" + from;
|
||||
to = default_image_folder + "/" + to;
|
||||
|
||||
struct stat st;
|
||||
if (!stat(to.c_str(), &st)) {
|
||||
return ReturnStatus(fd, false, "Image file '" + to + "' already exists");
|
||||
}
|
||||
|
||||
int fd_src = open(from.c_str(), O_RDONLY, 0);
|
||||
if (fd_src == -1) {
|
||||
return ReturnStatus(fd, false, "Can't open source image file '" + from + "': " + string(strerror(errno)));
|
||||
}
|
||||
|
||||
struct stat st_src;
|
||||
if (fstat(fd_src, &st_src) == -1) {
|
||||
return ReturnStatus(fd, false, "Can't read source image file '" + from + "': " + string(strerror(errno)));
|
||||
}
|
||||
|
||||
int fd_dst = open(to.c_str(), O_WRONLY | O_CREAT, st_src.st_mode);
|
||||
if (fd_dst == -1) {
|
||||
close (fd_dst);
|
||||
|
||||
return ReturnStatus(fd, false, "Can't open destination image file '" + to + "': " + string(strerror(errno)));
|
||||
}
|
||||
|
||||
if (sendfile(fd_dst, fd_src, 0, st_src.st_size) == -1) {
|
||||
close(fd_dst);
|
||||
close(fd_src);
|
||||
|
||||
return ReturnStatus(fd, false, "Can't copy image file '" + from + "' to '" + to + "': " + string(strerror(errno)));
|
||||
}
|
||||
|
||||
close(fd_dst);
|
||||
close(fd_src);
|
||||
|
||||
LOGINFO("%s", string("Copied image file '" + from + "' to '" + to + "'").c_str());
|
||||
|
||||
return ReturnStatus(fd);
|
||||
}
|
||||
|
||||
bool SetImagePermissions(int fd, const PbCommand& command)
|
||||
{
|
||||
string filename = GetParam(command, "file");
|
||||
if (filename.empty()) {
|
||||
return ReturnStatus(fd, false, "Missing image filename");
|
||||
}
|
||||
|
||||
if (!IsValidFilename(filename)) {
|
||||
return ReturnStatus(fd, false, "Can't modify image file '" + filename + "': Invalid filename");
|
||||
}
|
||||
|
||||
if (filename.find('/') != string::npos) {
|
||||
return ReturnStatus(fd, false, "The image filename '" + filename + "' must not contain a path");
|
||||
}
|
||||
|
||||
filename = default_image_folder + "/" + filename;
|
||||
|
||||
bool protect = command.operation() == PROTECT_IMAGE;
|
||||
|
||||
int permissions = protect ? S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
|
||||
if (chmod(filename.c_str(), permissions) == -1) {
|
||||
ostringstream error;
|
||||
error << "Can't " << (protect ? "protect" : "unprotect") << " image file '" << filename << "': " << strerror(errno);
|
||||
return ReturnStatus(fd, false, error.str());
|
||||
}
|
||||
|
||||
if (protect) {
|
||||
LOGINFO("%s", string("Protected image file '" + filename + "'").c_str());
|
||||
}
|
||||
else {
|
||||
LOGINFO("%s", string("Unprotected image file '" + filename + "'").c_str());
|
||||
}
|
||||
|
||||
return ReturnStatus(fd);
|
||||
}
|
||||
|
||||
void DetachAll()
|
||||
{
|
||||
Device *map[devices.size()];
|
||||
@ -731,7 +1007,7 @@ bool Attach(int fd, const PbDeviceDefinition& pb_device, Device *map[], bool dry
|
||||
return ReturnStatus(fd, false, error);
|
||||
}
|
||||
|
||||
string filename = pb_device.params_size() > 0 ? pb_device.params().Get(0) : "";
|
||||
string filename = GetParam(pb_device, "file");
|
||||
string ext;
|
||||
size_t separator = filename.rfind('.');
|
||||
if (separator != string::npos) {
|
||||
@ -824,7 +1100,7 @@ bool Attach(int fd, const PbDeviceDefinition& pb_device, Device *map[], bool dry
|
||||
|
||||
int id;
|
||||
int unit;
|
||||
if (file_support->GetIdsForReservedFile(filepath, id, unit)) {
|
||||
if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) {
|
||||
delete device;
|
||||
|
||||
error << "Image file '" << filename << "' is already used by ID " << id << ", unit " << unit;
|
||||
@ -841,7 +1117,7 @@ bool Attach(int fd, const PbDeviceDefinition& pb_device, Device *map[], bool dry
|
||||
return true;
|
||||
}
|
||||
|
||||
const list<string> params = { pb_device.params().begin(), pb_device.params().end() };
|
||||
std::map<string, string> params = { pb_device.params().begin(), pb_device.params().end() };
|
||||
if (!device->Init(params)) {
|
||||
error << "Initialization of " << device->GetType() << " device, ID " << id << ", unit " << unit << " failed";
|
||||
|
||||
@ -911,7 +1187,7 @@ bool Insert(int fd, const PbDeviceDefinition& pb_device, Device *device, bool dr
|
||||
return ReturnStatus(fd, false, "Once set the device name cannot be changed anymore");
|
||||
}
|
||||
|
||||
string filename = pb_device.params_size() > 0 ? pb_device.params().Get(0): "";
|
||||
string filename = GetParam(pb_device, "file");
|
||||
if (filename.empty()) {
|
||||
return ReturnStatus(fd, false, "Missing filename for " + PbOperation_Name(INSERT));
|
||||
}
|
||||
@ -928,8 +1204,7 @@ bool Insert(int fd, const PbDeviceDefinition& pb_device, Device *device, bool dr
|
||||
Filepath filepath;
|
||||
filepath.SetPath(filename.c_str());
|
||||
string initial_filename = filepath.GetPath();
|
||||
FileSupport *file_support = dynamic_cast<FileSupport *>(device);
|
||||
if (file_support->GetIdsForReservedFile(filepath, id, unit)) {
|
||||
if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) {
|
||||
ostringstream error;
|
||||
error << "Image file '" << filename << "' is already used by ID " << id << ", unit " << unit;
|
||||
return ReturnStatus(fd, false, error);
|
||||
@ -949,6 +1224,8 @@ bool Insert(int fd, const PbDeviceDefinition& pb_device, Device *device, bool dr
|
||||
}
|
||||
}
|
||||
|
||||
FileSupport *file_support = dynamic_cast<FileSupport *>(device);
|
||||
|
||||
try {
|
||||
try {
|
||||
file_support->Open(filepath);
|
||||
@ -974,13 +1251,15 @@ bool Insert(int fd, const PbDeviceDefinition& pb_device, Device *device, bool dr
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
bool ProcessCmd(int fd, const PbDeviceDefinition& pb_device, const PbOperation operation, const vector<string>& params, bool dryRun)
|
||||
bool ProcessCmd(int fd, const PbDeviceDefinition& pb_device, const PbCommand& command, bool dryRun)
|
||||
{
|
||||
ostringstream error;
|
||||
|
||||
const int id = pb_device.id();
|
||||
const int unit = pb_device.unit();
|
||||
const PbDeviceType type = pb_device.type();
|
||||
const PbOperation operation = command.operation();
|
||||
const map<string, string> params = { command.params().begin(), command.params().end() };
|
||||
|
||||
ostringstream s;
|
||||
s << (dryRun ? "Validating: " : "Executing: ");
|
||||
@ -988,11 +1267,13 @@ bool ProcessCmd(int fd, const PbDeviceDefinition& pb_device, const PbOperation o
|
||||
|
||||
if (!params.empty()) {
|
||||
s << ", command params=";
|
||||
for (size_t i = 0; i < params.size(); i++) {
|
||||
if (i) {
|
||||
bool isFirst = true;
|
||||
for (const auto& param: params) {
|
||||
if (!isFirst) {
|
||||
s << ", ";
|
||||
}
|
||||
s << "'" << params[i] << "'";
|
||||
isFirst = false;
|
||||
s << "'" << param.first << "=" << param.second << "'";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1000,11 +1281,13 @@ bool ProcessCmd(int fd, const PbDeviceDefinition& pb_device, const PbOperation o
|
||||
|
||||
if (pb_device.params_size()) {
|
||||
s << ", device params=";
|
||||
for (int i = 0; i < pb_device.params_size(); i++) {
|
||||
if (i) {
|
||||
bool isFirst = true;
|
||||
for (const auto& param: pb_device.params()) {
|
||||
if (!isFirst) {
|
||||
s << ", ";
|
||||
}
|
||||
s << "'" << pb_device.params().Get(i) << "'";
|
||||
isFirst = false;
|
||||
s << "'" << param.first << "=" << param.second << "'";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1138,26 +1421,46 @@ bool ProcessCmd(int fd, const PbDeviceDefinition& pb_device, const PbOperation o
|
||||
|
||||
bool ProcessCmd(const int fd, const PbCommand& command)
|
||||
{
|
||||
if (command.operation() == DETACH_ALL) {
|
||||
switch (command.operation()) {
|
||||
case DETACH_ALL:
|
||||
DetachAll();
|
||||
return ReturnStatus(fd);
|
||||
}
|
||||
else if (command.operation() == RESERVE) {
|
||||
const list<string> ids = { command.params().begin(), command.params().end() };
|
||||
|
||||
case RESERVE: {
|
||||
const string ids = GetParam(command, "ids");
|
||||
string invalid_id = SetReservedIds(ids);
|
||||
if (!invalid_id.empty()) {
|
||||
return ReturnStatus(fd, false,"Invalid ID " + invalid_id + " for " + PbOperation_Name(RESERVE));
|
||||
return ReturnStatus(fd, false, "Invalid ID " + invalid_id + " for " + PbOperation_Name(RESERVE));
|
||||
}
|
||||
|
||||
return ReturnStatus(fd);
|
||||
}
|
||||
|
||||
const vector<string> params = { command.params().begin(), command.params().end() };
|
||||
case CREATE_IMAGE:
|
||||
return CreateImage(fd, command);
|
||||
|
||||
case DELETE_IMAGE:
|
||||
return DeleteImage(fd, command);
|
||||
|
||||
case RENAME_IMAGE:
|
||||
return RenameImage(fd, command);
|
||||
|
||||
case COPY_IMAGE:
|
||||
return CopyImage(fd, command);
|
||||
|
||||
case PROTECT_IMAGE:
|
||||
case UNPROTECT_IMAGE:
|
||||
return SetImagePermissions(fd, command);
|
||||
|
||||
default:
|
||||
// This is a device-specific command handled below
|
||||
break;
|
||||
}
|
||||
|
||||
// Remember the list of reserved files, than run the dry run
|
||||
const auto reserved_files = FileSupport::GetReservedFiles();
|
||||
for (const auto& device : command.devices()) {
|
||||
if (!ProcessCmd(fd, device, command.operation(), params, true)) {
|
||||
if (!ProcessCmd(fd, device, command, true)) {
|
||||
// Dry run failed, restore the file list
|
||||
FileSupport::SetReservedFiles(reserved_files);
|
||||
return false;
|
||||
@ -1167,7 +1470,7 @@ bool ProcessCmd(const int fd, const PbCommand& command)
|
||||
// Restore list of reserved files, then execute the command
|
||||
FileSupport::SetReservedFiles(reserved_files);
|
||||
for (const auto& device : command.devices()) {
|
||||
if (!ProcessCmd(fd, device, command.operation(), params, false)) {
|
||||
if (!ProcessCmd(fd, device, command, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1248,15 +1551,7 @@ bool ParseArgument(int argc, char* argv[], int& port)
|
||||
continue;
|
||||
|
||||
case 'r': {
|
||||
stringstream ss(optarg);
|
||||
string id;
|
||||
|
||||
list<string> ids;
|
||||
while (getline(ss, id, ',')) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
|
||||
string invalid_id = SetReservedIds(ids);
|
||||
string invalid_id = SetReservedIds(optarg);
|
||||
if (!invalid_id.empty()) {
|
||||
cerr << "Invalid ID " << invalid_id << " for " << PbOperation_Name(RESERVE);
|
||||
return false;
|
||||
@ -1298,9 +1593,7 @@ bool ParseArgument(int argc, char* argv[], int& port)
|
||||
device->set_unit(unit);
|
||||
device->set_type(type);
|
||||
device->set_block_size(block_size);
|
||||
if (strlen(optarg)) {
|
||||
device->add_params(optarg);
|
||||
}
|
||||
AddParam(*device, "file", optarg);
|
||||
|
||||
size_t separatorPos = name.find(':');
|
||||
if (separatorPos != string::npos) {
|
||||
@ -1412,7 +1705,7 @@ static void *MonThread(void *param)
|
||||
case LOG_LEVEL: {
|
||||
LOGTRACE(string("Received " + PbOperation_Name(LOG_LEVEL) + " command").c_str());
|
||||
|
||||
string log_level = command.params_size() > 0 ? command.params().Get(0) : "";
|
||||
string log_level = GetParam(command, "level");
|
||||
bool status = SetLogLevel(log_level);
|
||||
if (!status) {
|
||||
ReturnStatus(fd, false, "Invalid log level: " + log_level);
|
||||
@ -1426,7 +1719,7 @@ static void *MonThread(void *param)
|
||||
case DEFAULT_FOLDER: {
|
||||
LOGTRACE(string("Received " + PbOperation_Name(DEFAULT_FOLDER) + " command").c_str());
|
||||
|
||||
string folder = command.params_size() > 0 ? command.params().Get(0) : "";
|
||||
string folder = GetParam(command, "folder");
|
||||
if (folder.empty()) {
|
||||
ReturnStatus(fd, false, "Can't set default image folder: Missing folder name");
|
||||
}
|
||||
|
@ -27,41 +27,101 @@ enum PbDeviceType {
|
||||
SCDP = 7;
|
||||
}
|
||||
|
||||
// rascsi remote operations, returns PbResult
|
||||
// rascsi remote operations, returning PbResult
|
||||
enum PbOperation {
|
||||
NONE = 0;
|
||||
// Gets the server information
|
||||
SERVER_INFO = 1;
|
||||
// Gets information for a list of attached devices. Returns data for all attached devices if empty.
|
||||
DEVICE_INFO = 2;
|
||||
// Set the default folder for image files. PbCommand.params contains the folder name.
|
||||
DEFAULT_FOLDER = 3;
|
||||
// Set server log level. PbCommand.params contains the log level.
|
||||
LOG_LEVEL = 4;
|
||||
|
||||
// Attach devices
|
||||
ATTACH = 5;
|
||||
// Parameters (mutually exclusive):
|
||||
// "file": The filename relative to the default image folder. It must not contain a slash.
|
||||
// "interfaces": A prioritized comma-separated list of interfaces to create a network bridge for.
|
||||
ATTACH = 1;
|
||||
|
||||
// Detach devices
|
||||
DETACH = 6;
|
||||
DETACH = 2;
|
||||
|
||||
// Detach all devices, does not require a device list
|
||||
DETACH_ALL = 7;
|
||||
DETACH_ALL = 3;
|
||||
|
||||
// Start device
|
||||
START = 8;
|
||||
START = 4;
|
||||
|
||||
// Stop device, e.g. park drive
|
||||
STOP = 9;
|
||||
STOP = 5;
|
||||
|
||||
// Insert medium
|
||||
INSERT = 10;
|
||||
// Parameters:
|
||||
// "file": The filename, relative to the default image folder. It must not contain a slash.
|
||||
INSERT = 6;
|
||||
|
||||
// Eject medium
|
||||
EJECT = 11;
|
||||
EJECT = 7;
|
||||
|
||||
// Write-protect medium (not possible for read-only media)
|
||||
PROTECT = 12;
|
||||
PROTECT = 8;
|
||||
|
||||
// Make medium writable (not possible for read-only media)
|
||||
UNPROTECT = 13;
|
||||
// IDs blocked from being used, usually the IDs of the initiators (computers) in the SCSI chain.
|
||||
// PbCommand.params contains the list of IDs to reserve, or is empty in order not to reserve any ID.
|
||||
UNPROTECT = 9;
|
||||
|
||||
// Gets the server information
|
||||
SERVER_INFO = 10;
|
||||
|
||||
// Gets information for a list of attached devices. Returns data for all attached devices if empty.
|
||||
DEVICE_INFO = 11;
|
||||
|
||||
// Set the default folder for image files.
|
||||
// Parameters:
|
||||
// "folder": The default folder name.
|
||||
DEFAULT_FOLDER = 12;
|
||||
|
||||
// Set server log level.
|
||||
// Parameters:
|
||||
// "level": The new log level
|
||||
LOG_LEVEL = 13;
|
||||
|
||||
// Block IDs from being used, usually the IDs of the initiators (computers) in the SCSI chain.
|
||||
// Parameters:
|
||||
// "ids": A comma-separated list of IDs to reserve, or an empty string in order not to reserve any ID.
|
||||
RESERVE = 14;
|
||||
|
||||
// Create an image file. The image file must not yet exist.
|
||||
// Parameters:
|
||||
// "file": The filename, relative to the default image folder. It must not contain a slash.
|
||||
// "size": The file size in bytes, must be a multiple of 512
|
||||
// "read_only": "true" (case-insensitive) in order to create a read-only file, otherwise "false"
|
||||
CREATE_IMAGE = 15;
|
||||
|
||||
// Delete an image file.
|
||||
// Parameters:
|
||||
// "file": The filename, relative to the default image folder. It must not contain a slash.
|
||||
DELETE_IMAGE = 16;
|
||||
|
||||
// Rename an image file.
|
||||
// Parameters:
|
||||
// "from": The old filename, relative to the default image folder. It must not contain a slash.
|
||||
// "to": The new filename, relative to the default image folder. It must not contain a slash.
|
||||
// The new filename must not yet exist.
|
||||
RENAME_IMAGE = 17;
|
||||
|
||||
// Copy an image file.
|
||||
// Parameters:
|
||||
// "from": The source filename, relative to the default image folder. It must not contain a slash.
|
||||
// "to": The destination filename, relative to the default image folder. It must not contain a slash.
|
||||
// The destination filename must not yet exist.
|
||||
COPY_IMAGE = 18;
|
||||
|
||||
// Write-protect an image file.
|
||||
// Parameters:
|
||||
// "file": The filename, relative to the default image folder. It must not contain a slash.
|
||||
PROTECT_IMAGE = 19;
|
||||
|
||||
// Make an image file writable.
|
||||
// Parameters:
|
||||
// "file": The filename, relative to the default image folder. It must not contain a slash.
|
||||
UNPROTECT_IMAGE = 20;
|
||||
}
|
||||
|
||||
// The properties supported by a device, helping clients to offer a good user experience
|
||||
// The properties supported by a device
|
||||
message PbDeviceProperties {
|
||||
// Read-only media (e.g. CD-ROMs) are not protectable but permanently read-only
|
||||
bool read_only = 1;
|
||||
@ -77,8 +137,8 @@ message PbDeviceProperties {
|
||||
bool supports_file = 6;
|
||||
// Device supports parameters other than a filename
|
||||
bool supports_params = 7;
|
||||
// Ordered list of default parameters, if any (requires supports_params to be true)
|
||||
repeated string default_params = 8;
|
||||
// List of default parameters, if any (requires supports_params to be true)
|
||||
map<string, string> default_params = 8;
|
||||
// Number of supported LUNs, at least 1 (for LUN 0)
|
||||
uint32 luns = 9;
|
||||
// Unordered list of permitted block sizes in bytes, empty if the block size is not configurable
|
||||
@ -117,8 +177,8 @@ message PbDeviceDefinition {
|
||||
int32 id = 1;
|
||||
int32 unit = 2;
|
||||
PbDeviceType type = 3;
|
||||
// Optional device specific parameters, e.g. the name of an image file
|
||||
repeated string params = 4;
|
||||
// Device specific named parameters, e.g. the name of an image file
|
||||
map<string, string> params = 4;
|
||||
// The optional block size in bytes per sector, must be one of the supported block sizes for SASI/SCSI
|
||||
int32 block_size = 5;
|
||||
// The device name components
|
||||
@ -138,8 +198,8 @@ message PbDevice {
|
||||
PbDeviceStatus status = 5;
|
||||
// Image file information, if the device supports image files
|
||||
PbImageFile file = 6;
|
||||
// Ordered list of effective parameters the device was created with
|
||||
repeated string params = 7;
|
||||
// Effective parameters the device was created with
|
||||
map<string, string> params = 7;
|
||||
string vendor = 8;
|
||||
string product = 9;
|
||||
string revision = 10;
|
||||
@ -158,8 +218,8 @@ message PbCommand {
|
||||
PbOperation operation = 1;
|
||||
// The non-empty list of devices for this command
|
||||
repeated PbDeviceDefinition devices = 2;
|
||||
// The optional parameters depending on the operation, e.g. a filename, or a network interface list
|
||||
repeated string params = 3;
|
||||
// The named parameters for the operation, e.g. a filename, or a network interface list
|
||||
map<string, string> params = 3;
|
||||
}
|
||||
|
||||
// The result of a command
|
||||
|
@ -142,10 +142,13 @@ void DisplayDeviceInfo(const PbDevice& pb_device)
|
||||
cout << " ";
|
||||
}
|
||||
|
||||
if (pb_device.params_size()) {
|
||||
for (const string param : pb_device.params()) {
|
||||
cout << param << " ";
|
||||
bool isFirst = true;
|
||||
for (const auto& param : pb_device.params()) {
|
||||
if (!isFirst) {
|
||||
cout << " ";
|
||||
}
|
||||
isFirst = false;
|
||||
cout << param.first << "=" << param.second;
|
||||
}
|
||||
|
||||
cout << endl;
|
||||
@ -184,7 +187,7 @@ void CommandLogLevel(const string& hostname, int port, const string& log_level)
|
||||
{
|
||||
PbCommand command;
|
||||
command.set_operation(LOG_LEVEL);
|
||||
command.add_params(log_level);
|
||||
AddParam(command, "level", log_level);
|
||||
|
||||
PbResult result;
|
||||
SendCommand(hostname.c_str(), port, command, result);
|
||||
@ -194,12 +197,75 @@ void CommandReserve(const string&hostname, int port, const string& reserved_ids)
|
||||
{
|
||||
PbCommand command;
|
||||
command.set_operation(RESERVE);
|
||||
AddParam(command, "ids", reserved_ids);
|
||||
|
||||
stringstream ss(reserved_ids);
|
||||
string reserved_id;
|
||||
PbResult result;
|
||||
SendCommand(hostname.c_str(), port, command, result);
|
||||
}
|
||||
|
||||
while (getline(ss, reserved_id, ',')) {
|
||||
command.add_params(reserved_id);
|
||||
void CommandCreateImage(const string&hostname, int port, const string& image_params)
|
||||
{
|
||||
PbCommand command;
|
||||
command.set_operation(CREATE_IMAGE);
|
||||
|
||||
size_t separatorPos = image_params.find(COMPONENT_SEPARATOR);
|
||||
if (separatorPos != string::npos) {
|
||||
AddParam(command, "file", image_params.substr(0, separatorPos));
|
||||
AddParam(command, "size", image_params.substr(separatorPos + 1));
|
||||
}
|
||||
else {
|
||||
cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
AddParam(command, "read_only", "false");
|
||||
|
||||
PbResult result;
|
||||
SendCommand(hostname.c_str(), port, command, result);
|
||||
}
|
||||
|
||||
void CommandDeleteImage(const string&hostname, int port, const string& filename)
|
||||
{
|
||||
PbCommand command;
|
||||
command.set_operation(DELETE_IMAGE);
|
||||
AddParam(command, "file", filename);
|
||||
|
||||
PbResult result;
|
||||
SendCommand(hostname.c_str(), port, command, result);
|
||||
}
|
||||
|
||||
void CommandRenameImage(const string&hostname, int port, const string& image_params)
|
||||
{
|
||||
PbCommand command;
|
||||
command.set_operation(RENAME_IMAGE);
|
||||
|
||||
size_t separatorPos = image_params.find(COMPONENT_SEPARATOR);
|
||||
if (separatorPos != string::npos) {
|
||||
AddParam(command, "from", image_params.substr(0, separatorPos));
|
||||
AddParam(command, "to", image_params.substr(separatorPos + 1));
|
||||
}
|
||||
else {
|
||||
cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
PbResult result;
|
||||
SendCommand(hostname.c_str(), port, command, result);
|
||||
}
|
||||
|
||||
void CommandCopyImage(const string&hostname, int port, const string& image_params)
|
||||
{
|
||||
PbCommand command;
|
||||
command.set_operation(COPY_IMAGE);
|
||||
|
||||
size_t separatorPos = image_params.find(COMPONENT_SEPARATOR);
|
||||
if (separatorPos != string::npos) {
|
||||
AddParam(command, "from", image_params.substr(0, separatorPos));
|
||||
AddParam(command, "to", image_params.substr(separatorPos + 1));
|
||||
}
|
||||
else {
|
||||
cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
PbResult result;
|
||||
@ -210,7 +276,7 @@ void CommandDefaultImageFolder(const string& hostname, int port, const string& f
|
||||
{
|
||||
PbCommand command;
|
||||
command.set_operation(DEFAULT_FOLDER);
|
||||
command.add_params(folder);
|
||||
AddParam(command, "folder", folder);
|
||||
|
||||
PbResult result;
|
||||
SendCommand(hostname.c_str(), port, command, result);
|
||||
@ -317,8 +383,7 @@ void CommandServerInfo(const string& hostname, int port)
|
||||
}
|
||||
|
||||
if (properties.supports_params() && properties.default_params_size()) {
|
||||
list<string> params = { properties.default_params().begin(), properties.default_params().end() };
|
||||
params.sort([](const auto& a, const auto& b) { return a < b; });
|
||||
map<string, string> params = { properties.default_params().begin(), properties.default_params().end() };
|
||||
|
||||
cout << " Default parameters: ";
|
||||
|
||||
@ -327,7 +392,7 @@ void CommandServerInfo(const string& hostname, int port)
|
||||
if (!isFirst) {
|
||||
cout << ", ";
|
||||
}
|
||||
cout << param;
|
||||
cout << param.first << "=" << param.second;
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
@ -471,20 +536,22 @@ int main(int argc, char* argv[])
|
||||
if (argc < 2) {
|
||||
cerr << "SCSI Target Emulator RaSCSI Controller" << endl;
|
||||
cerr << "version " << rascsi_get_version_string() << " (" << __DATE__ << ", " << __TIME__ << ")" << endl;
|
||||
cerr << "Usage: " << argv[0] << " -i ID [-u UNIT] [-c CMD] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE] ";
|
||||
cerr << "[-d DEFAULT_IMAGE_FOLDER] [-g LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] [-l] [-v]" << endl;
|
||||
cerr << " where ID := {0|1|2|3|4|5|6|7}" << endl;
|
||||
cerr << " UNIT := {0|1}, default setting is 0." << endl;
|
||||
cerr << "Usage: " << argv[0] << " -i ID [-u UNIT] [-c CMD] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] ";
|
||||
cerr << "[-d IMAGE_FOLDER] [-g LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] ";
|
||||
cerr << "[-a FILENAME:FILESIZE] [-w FILENAME] [-m CURRENT_NAME:NEW_NAME] [-x CURRENT_NAME:NEW_NAME] ";
|
||||
cerr << "[-l] [-v]" << endl;
|
||||
cerr << " where ID := {0-7}" << endl;
|
||||
cerr << " UNIT := {0|1}, default is 0" << endl;
|
||||
cerr << " CMD := {attach|detach|insert|eject|protect|unprotect|show}" << endl;
|
||||
cerr << " TYPE := {sahd|schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}" << endl;
|
||||
cerr << " BLOCK_SIZE := {256|512|1024|2048|4096) bytes per hard disk drive block" << endl;
|
||||
cerr << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)" << endl;
|
||||
cerr << " FILE := image file path" << endl;
|
||||
cerr << " DEFAULT_IMAGE_FOLDER := default location for image files, default is '~/images'" << endl;
|
||||
cerr << " FILE|PARAM := image file path or device-specific parameter" << endl;
|
||||
cerr << " IMAGE_FOLDER := default location for image files, default is '~/images'" << endl;
|
||||
cerr << " HOST := rascsi host to connect to, default is 'localhost'" << endl;
|
||||
cerr << " PORT := rascsi port to connect to, default is 6868" << endl;
|
||||
cerr << " RESERVED_IDS := comma-separated list of IDs to reserve" << endl;
|
||||
cerr << " LOG_LEVEL := log level {trace|debug|info|warn|err|critical|off}, default is 'trace'" << endl;
|
||||
cerr << " LOG_LEVEL := log level {trace|debug|info|warn|err|critical|off}, default is 'info'" << endl;
|
||||
cerr << " If CMD is 'attach' or 'insert' the FILE parameter is required." << endl;
|
||||
cerr << "Usage: " << argv[0] << " -l" << endl;
|
||||
cerr << " Print device list." << endl;
|
||||
@ -499,14 +566,16 @@ int main(int argc, char* argv[])
|
||||
device->set_id(-1);
|
||||
const char *hostname = "localhost";
|
||||
int port = 6868;
|
||||
string param;
|
||||
string log_level;
|
||||
string default_folder;
|
||||
string reserved_ids;
|
||||
string image_params;
|
||||
bool list = false;
|
||||
|
||||
opterr = 1;
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "b:c:d:f:g:h:i:n:p:r:t:u:lsv")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "a:b:c:d:f:g:h:i:m:n:p:r:t:u:x:w:lsv")) != -1) {
|
||||
switch (opt) {
|
||||
case 'i':
|
||||
device->set_id(optarg[0] - '0');
|
||||
@ -516,6 +585,11 @@ int main(int argc, char* argv[])
|
||||
device->set_unit(optarg[0] - '0');
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
command.set_operation(CREATE_IMAGE);
|
||||
image_params = optarg;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
int block_size;
|
||||
if (!GetAsInt(optarg, block_size)) {
|
||||
@ -539,7 +613,7 @@ int main(int argc, char* argv[])
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
device->add_params(optarg);
|
||||
param = optarg;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
@ -563,6 +637,11 @@ int main(int argc, char* argv[])
|
||||
list = true;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
command.set_operation(RENAME_IMAGE);
|
||||
image_params = optarg;
|
||||
break;
|
||||
|
||||
case 'n': {
|
||||
string vendor;
|
||||
string product;
|
||||
@ -612,6 +691,16 @@ int main(int argc, char* argv[])
|
||||
cout << rascsi_get_version_string() << endl;
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
command.set_operation(COPY_IMAGE);
|
||||
image_params = optarg;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
command.set_operation(DELETE_IMAGE);
|
||||
image_params = optarg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -632,6 +721,22 @@ int main(int argc, char* argv[])
|
||||
CommandReserve(hostname, port, reserved_ids);
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
case CREATE_IMAGE:
|
||||
CommandCreateImage(hostname, port, image_params);
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
case DELETE_IMAGE:
|
||||
CommandDeleteImage(hostname, port, image_params);
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
case RENAME_IMAGE:
|
||||
CommandRenameImage(hostname, port, image_params);
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
case COPY_IMAGE:
|
||||
CommandCopyImage(hostname, port, image_params);
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
case DEVICE_INFO:
|
||||
CommandDeviceInfo(hostname, port, command);
|
||||
exit(EXIT_SUCCESS);
|
||||
@ -649,6 +754,15 @@ int main(int argc, char* argv[])
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
if (!param.empty()) {
|
||||
if (device->type() == SCBR || device->type() == SCDP) {
|
||||
AddParam(*device, "interfaces", param);
|
||||
}
|
||||
else {
|
||||
AddParam(*device, "file", param);
|
||||
}
|
||||
}
|
||||
|
||||
PbResult result;
|
||||
SendCommand(hostname, port, command, result);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user