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