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:
Daniel Markstedt 2021-09-18 11:28:51 -07:00 committed by GitHub
commit 5161a83999
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 719 additions and 150 deletions

View File

@ -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):

View File

@ -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
rascsi(1) General Commands Manual rascsi(1)
!! ------ 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)
rascsi(1)

View File

@ -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) {

View File

@ -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();

View File

@ -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()

View File

@ -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;
};

View File

@ -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()];

View File

@ -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;

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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,9 +672,16 @@ bool SetDefaultImageFolder(const string& f)
return true;
}
string SetReservedIds(const list<string>& ids_to_reserve)
string SetReservedIds(const string& ids)
{
set<int> reserved;
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;
if (!GetAsInt(id_to_reserve, 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) {
DetachAll();
return ReturnStatus(fd);
}
else if (command.operation() == RESERVE) {
const list<string> ids = { command.params().begin(), command.params().end() };
string invalid_id = SetReservedIds(ids);
if (!invalid_id.empty()) {
return ReturnStatus(fd, false,"Invalid ID " + invalid_id + " for " + PbOperation_Name(RESERVE));
switch (command.operation()) {
case DETACH_ALL:
DetachAll();
return ReturnStatus(fd);
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);
}
return ReturnStatus(fd);
}
case CREATE_IMAGE:
return CreateImage(fd, command);
const vector<string> params = { command.params().begin(), command.params().end() };
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");
}

View File

@ -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

View File

@ -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);