diff --git a/doc/rasctl.1 b/doc/rasctl.1
index cae88f3c..3ae1290a 100644
--- a/doc/rasctl.1
+++ b/doc/rasctl.1
@@ -28,8 +28,11 @@ Note: The command and type arguments are case insensitive. Only the first letter
.SH OPTIONS
.TP
+.BR \-a\fI " "\fIFILENAME:FILESIZE
+Create a disk image file with the specified name and size in bytes.
+.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'.
diff --git a/doc/rasctl_man_page.txt b/doc/rasctl_man_page.txt
index d69dd84e..171e8072 100644
--- a/doc/rasctl_man_page.txt
+++ b/doc/rasctl_man_page.txt
@@ -1,32 +1,39 @@
!! ------ 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 | [-g LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS]
+ [-v] -i ID [-c CMD] [-f FILE] [-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 a disk image file with the specified name and size in
+ bytes.
+
-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.
-p PORT
The rascsi port to connect to, default is 6868.
@@ -34,7 +41,8 @@ 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.
@@ -45,21 +53,28 @@ 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.
+ Path to the disk image file. See the rascsi(1) man page for al‐
+ lowable 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 +83,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 +106,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:
+ Full documentation is available at:
+
- rascsi(1)
+ rascsi(1)
diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp
index 35c92fb8..cf6eed94 100644
--- a/src/raspberrypi/rascsi.cpp
+++ b/src/raspberrypi/rascsi.cpp
@@ -705,6 +705,55 @@ string SetReservedIds(const list& ids_to_reserve)
return "";
}
+bool CreateImage(int fd, const PbCommand& command)
+{
+ if (command.params().size() < 2 || command.params().Get(0).empty() || command.params().Get(1).empty()) {
+ return ReturnStatus(fd, false, "Can't create image file: Missing filename or file size");
+ }
+
+ string filename = command.params().Get(0);
+ if (filename[0] != '/') {
+ filename = default_image_folder + "/" + filename;
+ }
+
+ off_t len;
+ try {
+ len = stoul(command.params().Get(1));
+ }
+ catch(const invalid_argument& e) {
+ return ReturnStatus(fd, false, "Invalid image file size " + command.params().Get(1));
+ }
+ catch(const out_of_range& e) {
+ return ReturnStatus(fd, false, "Invalid image file size " + command.params().Get(1));
+ }
+ if (len < 256) {
+ 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, "Image file '" + filename + "' 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, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ 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);
+
+ return ReturnStatus(fd);
+}
+
void DetachAll()
{
Device *map[devices.size()];
@@ -1146,11 +1195,14 @@ bool ProcessCmd(const int fd, const PbCommand& command)
const list 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));
+ return ReturnStatus(fd, false, "Invalid ID " + invalid_id + " for " + PbOperation_Name(RESERVE));
}
return ReturnStatus(fd);
}
+ else if (command.operation() == CREATE_IMAGE) {
+ return CreateImage(fd, command);
+ }
const vector params = { command.params().begin(), command.params().end() };
diff --git a/src/raspberrypi/rascsi_interface.proto b/src/raspberrypi/rascsi_interface.proto
index ee7e0e37..666f1471 100644
--- a/src/raspberrypi/rascsi_interface.proto
+++ b/src/raspberrypi/rascsi_interface.proto
@@ -59,6 +59,10 @@ enum PbOperation {
// 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.
RESERVE = 14;
+ // Create an image file. The image file must not yet exist.
+ // PbCommand.params(0) contains the filename, PbCommand.params(1) contains the file size in bytes.
+ // If the filename is relative (does not start with a slash) the file is created in the default image folder.
+ CREATE_IMAGE = 15;
}
// The properties supported by a device, helping clients to offer a good user experience
diff --git a/src/raspberrypi/rasctl.cpp b/src/raspberrypi/rasctl.cpp
index e251a800..7d317ff2 100644
--- a/src/raspberrypi/rasctl.cpp
+++ b/src/raspberrypi/rasctl.cpp
@@ -206,6 +206,21 @@ void CommandReserve(const string&hostname, int port, const string& reserved_ids)
SendCommand(hostname.c_str(), port, command, result);
}
+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) {
+ command.add_params(image_params.substr(0, separatorPos));
+ command.add_params(image_params.substr(separatorPos + 1));
+ }
+
+ PbResult result;
+ SendCommand(hostname.c_str(), port, command, result);
+}
+
void CommandDefaultImageFolder(const string& hostname, int port, const string& folder)
{
PbCommand command;
@@ -502,11 +517,12 @@ int main(int argc, char* argv[])
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:n:p:r:t:u:lsv")) != -1) {
switch (opt) {
case 'i':
device->set_id(optarg[0] - '0');
@@ -516,6 +532,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)) {
@@ -632,6 +653,10 @@ 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 DEVICE_INFO:
CommandDeviceInfo(hostname, port, command);
exit(EXIT_SUCCESS);