mirror of
https://github.com/akuker/RASCSI.git
synced 2024-11-23 08:31:05 +00:00
703 lines
19 KiB
C++
703 lines
19 KiB
C++
//---------------------------------------------------------------------------
|
|
//
|
|
// SCSI Target Emulator PiSCSI
|
|
// for Raspberry Pi
|
|
//
|
|
// Powered by XM6 TypeG Technology.
|
|
// Copyright (C) 2016-2020 GIMONS
|
|
// Copyright (C) 2020-2023 Contributors to the PiSCSI project
|
|
// Copyright (C) 2023 Uwe Seimet
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#include "shared/config.h"
|
|
#include "shared/piscsi_util.h"
|
|
#include "shared/protobuf_util.h"
|
|
#include "shared/piscsi_exceptions.h"
|
|
#include "shared/piscsi_version.h"
|
|
#include "controllers/scsi_controller.h"
|
|
#include "devices/device_logger.h"
|
|
#include "devices/storage_device.h"
|
|
#include "hal/gpiobus_factory.h"
|
|
#include "hal/gpiobus.h"
|
|
#include "piscsi/piscsi_core.h"
|
|
#include <spdlog/spdlog.h>
|
|
#include <netinet/in.h>
|
|
#include <csignal>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <vector>
|
|
#include <chrono>
|
|
|
|
using namespace std;
|
|
using namespace filesystem;
|
|
using namespace spdlog;
|
|
using namespace piscsi_interface;
|
|
using namespace piscsi_util;
|
|
using namespace protobuf_util;
|
|
using namespace scsi_defs;
|
|
|
|
void Piscsi::Banner(span<char *> args) const
|
|
{
|
|
cout << piscsi_util::Banner("(Backend Service)");
|
|
cout << "Connection type: " << CONNECT_DESC << '\n' << flush;
|
|
|
|
if ((args.size() > 1 && strcmp(args[1], "-h") == 0) || (args.size() > 1 && strcmp(args[1], "--help") == 0)){
|
|
cout << "\nUsage: " << args[0] << " [-idID[:LUN] FILE] ...\n\n";
|
|
cout << " ID is SCSI device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n";
|
|
cout << " LUN is the optional logical unit (0-" << (ControllerManager::GetScsiLunMax() - 1) <<").\n";
|
|
cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n";
|
|
cout << " Image type is detected based on file extension if no explicit type is specified.\n";
|
|
cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n";
|
|
cout << " hds : SCSI HD image (Non-removable generic SCSI HD image)\n";
|
|
cout << " hdr : SCSI HD image (Removable generic HD image)\n";
|
|
cout << " hda : SCSI HD image (Apple compatible image)\n";
|
|
cout << " hdn : SCSI HD image (NEC compatible image)\n";
|
|
cout << " hdi : SCSI HD image (Anex86 HD image)\n";
|
|
cout << " nhd : SCSI HD image (T98Next HD image)\n";
|
|
cout << " mos : SCSI MO image (MO image)\n";
|
|
cout << " iso : SCSI CD image (ISO 9660 image)\n";
|
|
cout << " is1 : SCSI CD image (ISO 9660 image, SCSI-1)\n" << flush;
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
}
|
|
|
|
bool Piscsi::InitBus()
|
|
{
|
|
bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET);
|
|
if (bus == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
executor = make_unique<PiscsiExecutor>(*bus, controller_manager);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Piscsi::CleanUp()
|
|
{
|
|
if (service.IsRunning()) {
|
|
service.Stop();
|
|
}
|
|
|
|
executor->DetachAll();
|
|
|
|
// TODO Check why there are rare cases where bus is NULL on a remote interface shutdown
|
|
// even though it is never set to NULL anywhere
|
|
assert(bus);
|
|
if (bus) {
|
|
bus->Cleanup();
|
|
}
|
|
}
|
|
|
|
void Piscsi::ReadAccessToken(const path& filename)
|
|
{
|
|
if (error_code error; !is_regular_file(filename, error)) {
|
|
throw parser_exception("Access token file '" + filename.string() + "' must be a regular file");
|
|
}
|
|
|
|
if (struct stat st; stat(filename.c_str(), &st) || st.st_uid || st.st_gid) {
|
|
throw parser_exception("Access token file '" + filename.string() + "' must be owned by root");
|
|
}
|
|
|
|
if (const auto perms = filesystem::status(filename).permissions();
|
|
(perms & perms::group_read) != perms::none || (perms & perms::others_read) != perms::none ||
|
|
(perms & perms::group_write) != perms::none || (perms & perms::others_write) != perms::none) {
|
|
throw parser_exception("Access token file '" + filename.string() + "' must be readable by root only");
|
|
}
|
|
|
|
ifstream token_file(filename);
|
|
if (token_file.fail()) {
|
|
throw parser_exception("Can't open access token file '" + filename.string() + "'");
|
|
}
|
|
|
|
getline(token_file, access_token);
|
|
if (token_file.fail()) {
|
|
throw parser_exception("Can't read access token file '" + filename.string() + "'");
|
|
}
|
|
|
|
if (access_token.empty()) {
|
|
throw parser_exception("Access token file '" + filename.string() + "' must not be empty");
|
|
}
|
|
}
|
|
|
|
void Piscsi::LogDevices(string_view devices) const
|
|
{
|
|
stringstream ss(devices.data());
|
|
string line;
|
|
|
|
while (getline(ss, line, '\n')) {
|
|
spdlog::info(line);
|
|
}
|
|
}
|
|
|
|
void Piscsi::TerminationHandler(int)
|
|
{
|
|
instance->CleanUp();
|
|
|
|
// Process will terminate automatically
|
|
}
|
|
|
|
string Piscsi::ParseArguments(span<char *> args, PbCommand& command, int& port, string& reserved_ids)
|
|
{
|
|
string log_level = "info";
|
|
PbDeviceType type = UNDEFINED;
|
|
int block_size = 0;
|
|
string name;
|
|
string id_and_lun;
|
|
|
|
string locale = GetLocale();
|
|
|
|
// Avoid duplicate messages while parsing
|
|
set_level(level::off);
|
|
|
|
opterr = 1;
|
|
int opt;
|
|
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "-Iib:d:n:p:r:t:z:D:F:L:P:R:C:v")) != -1) {
|
|
switch (opt) {
|
|
// The two options below are kind of a compound option with two letters
|
|
case 'i':
|
|
case 'I':
|
|
continue;
|
|
|
|
case 'd':
|
|
case 'D':
|
|
id_and_lun = optarg;
|
|
continue;
|
|
|
|
case 'b':
|
|
if (!GetAsUnsignedInt(optarg, block_size)) {
|
|
throw parser_exception("Invalid block size " + string(optarg));
|
|
}
|
|
continue;
|
|
|
|
case 'z':
|
|
locale = optarg;
|
|
continue;
|
|
|
|
case 'F':
|
|
if (const string error = piscsi_image.SetDefaultFolder(optarg); !error.empty()) {
|
|
throw parser_exception(error);
|
|
}
|
|
continue;
|
|
|
|
case 'L':
|
|
log_level = optarg;
|
|
continue;
|
|
|
|
case 'R':
|
|
int depth;
|
|
if (!GetAsUnsignedInt(optarg, depth)) {
|
|
throw parser_exception("Invalid image file scan depth " + string(optarg));
|
|
}
|
|
piscsi_image.SetDepth(depth);
|
|
continue;
|
|
|
|
case 'n':
|
|
name = optarg;
|
|
continue;
|
|
|
|
case 'p':
|
|
if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) {
|
|
throw parser_exception("Invalid port " + string(optarg) + ", port must be between 1 and 65535");
|
|
}
|
|
continue;
|
|
|
|
case 'P':
|
|
ReadAccessToken(optarg);
|
|
continue;
|
|
|
|
case 'r':
|
|
reserved_ids = optarg;
|
|
continue;
|
|
|
|
case 't':
|
|
type = ParseDeviceType(optarg);
|
|
continue;
|
|
|
|
case 1:
|
|
// Encountered filename
|
|
break;
|
|
|
|
default:
|
|
throw parser_exception("Parser error");
|
|
}
|
|
|
|
if (optopt) {
|
|
throw parser_exception("Parser error");
|
|
}
|
|
|
|
// Set up the device data
|
|
|
|
auto device = command.add_devices();
|
|
|
|
if (!id_and_lun.empty()) {
|
|
if (const string error = SetIdAndLun(*device, id_and_lun); !error.empty()) {
|
|
throw parser_exception(error);
|
|
}
|
|
}
|
|
|
|
device->set_type(type);
|
|
device->set_block_size(block_size);
|
|
|
|
ParseParameters(*device, optarg);
|
|
|
|
SetProductData(*device, name);
|
|
|
|
type = UNDEFINED;
|
|
block_size = 0;
|
|
name = "";
|
|
id_and_lun = "";
|
|
}
|
|
|
|
if (!SetLogLevel(log_level)) {
|
|
throw parser_exception("Invalid log level '" + log_level + "'");
|
|
}
|
|
|
|
return locale;
|
|
}
|
|
|
|
PbDeviceType Piscsi::ParseDeviceType(const string& value)
|
|
{
|
|
string t;
|
|
ranges::transform(value, back_inserter(t), ::toupper);
|
|
if (PbDeviceType type; PbDeviceType_Parse(t, &type)) {
|
|
return type;
|
|
}
|
|
|
|
throw parser_exception("Illegal device type '" + value + "'");
|
|
}
|
|
|
|
bool Piscsi::SetLogLevel(const string& log_level) const
|
|
{
|
|
int id = -1;
|
|
int lun = -1;
|
|
string level = log_level;
|
|
|
|
if (const auto& components = Split(log_level, COMPONENT_SEPARATOR, 2); !components.empty()) {
|
|
level = components[0];
|
|
|
|
if (components.size() > 1) {
|
|
if (const string error = ProcessId(components[1], id, lun); !error.empty()) {
|
|
spdlog::warn("Error setting log level: " + error);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const level::level_enum l = level::from_str(level);
|
|
// Compensate for spdlog using 'off' for unknown levels
|
|
if (to_string_view(l) != level) {
|
|
spdlog::warn("Invalid log level '" + level + "'");
|
|
return false;
|
|
}
|
|
|
|
set_level(l);
|
|
DeviceLogger::SetLogIdAndLun(id, lun);
|
|
|
|
if (id != -1) {
|
|
if (lun == -1) {
|
|
spdlog::info("Set log level for device " + to_string(id) + " to '" + level + "'");
|
|
}
|
|
else {
|
|
spdlog::info("Set log level for device " + to_string(id) + ":" + to_string(lun) + " to '" + level + "'");
|
|
}
|
|
}
|
|
else {
|
|
spdlog::info("Set log level to '" + level + "'");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Piscsi::ExecuteCommand(const CommandContext& context)
|
|
{
|
|
const PbCommand& command = context.GetCommand();
|
|
const PbOperation operation = command.operation();
|
|
|
|
if (!access_token.empty() && access_token != GetParam(command, "token")) {
|
|
return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED);
|
|
}
|
|
|
|
if (!PbOperation_IsValid(operation)) {
|
|
spdlog::trace("Ignored unknown command with operation opcode " + to_string(operation));
|
|
|
|
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION, to_string(operation));
|
|
}
|
|
|
|
spdlog::trace("Received " + PbOperation_Name(operation) + " command");
|
|
|
|
PbResult result;
|
|
|
|
switch(operation) {
|
|
case LOG_LEVEL:
|
|
if (const string log_level = GetParam(command, "level"); !SetLogLevel(log_level)) {
|
|
context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level);
|
|
}
|
|
else {
|
|
context.ReturnSuccessStatus();
|
|
}
|
|
break;
|
|
|
|
case DEFAULT_FOLDER:
|
|
if (const string error = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !error.empty()) {
|
|
context.ReturnErrorStatus(error);
|
|
}
|
|
else {
|
|
context.ReturnSuccessStatus();
|
|
}
|
|
break;
|
|
|
|
case DEVICES_INFO:
|
|
response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder());
|
|
context.WriteResult(result);
|
|
break;
|
|
|
|
case DEVICE_TYPES_INFO:
|
|
response.GetDeviceTypesInfo(*result.mutable_device_types_info());
|
|
return context.WriteSuccessResult(result);
|
|
|
|
case SERVER_INFO:
|
|
response.GetServerInfo(*result.mutable_server_info(), command, controller_manager.GetAllDevices(),
|
|
executor->GetReservedIds(), piscsi_image.GetDefaultFolder(), piscsi_image.GetDepth());
|
|
context.WriteSuccessResult(result);
|
|
break;
|
|
|
|
case VERSION_INFO:
|
|
response.GetVersionInfo(*result.mutable_version_info());
|
|
return context.WriteSuccessResult(result);
|
|
|
|
case LOG_LEVEL_INFO:
|
|
response.GetLogLevelInfo(*result.mutable_log_level_info());
|
|
return context.WriteSuccessResult(result);
|
|
|
|
case DEFAULT_IMAGE_FILES_INFO:
|
|
response.GetImageFilesInfo(*result.mutable_image_files_info(), piscsi_image.GetDefaultFolder(),
|
|
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth());
|
|
return context.WriteSuccessResult(result);
|
|
|
|
case IMAGE_FILE_INFO:
|
|
if (string filename = GetParam(command, "file"); filename.empty()) {
|
|
context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME);
|
|
}
|
|
else {
|
|
auto image_file = make_unique<PbImageFile>();
|
|
const bool status = response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(),
|
|
filename);
|
|
if (status) {
|
|
result.set_allocated_image_file_info(image_file.get());
|
|
result.set_status(true);
|
|
context.WriteResult(result);
|
|
}
|
|
else {
|
|
context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NETWORK_INTERFACES_INFO:
|
|
response.GetNetworkInterfacesInfo(*result.mutable_network_interfaces_info());
|
|
return context.WriteSuccessResult(result);
|
|
|
|
case MAPPING_INFO:
|
|
response.GetMappingInfo(*result.mutable_mapping_info());
|
|
return context.WriteSuccessResult(result);
|
|
|
|
case STATISTICS_INFO:
|
|
response.GetStatisticsInfo(*result.mutable_statistics_info(), controller_manager.GetAllDevices());
|
|
context.WriteSuccessResult(result);
|
|
break;
|
|
|
|
case OPERATION_INFO:
|
|
response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth());
|
|
return context.WriteSuccessResult(result);
|
|
|
|
case RESERVED_IDS_INFO:
|
|
response.GetReservedIds(*result.mutable_reserved_ids_info(), executor->GetReservedIds());
|
|
return context.WriteSuccessResult(result);
|
|
|
|
case SHUT_DOWN:
|
|
return ShutDown(context, GetParam(command, "mode"));
|
|
|
|
case NO_OPERATION:
|
|
return context.ReturnSuccessStatus();
|
|
|
|
case CREATE_IMAGE:
|
|
return piscsi_image.CreateImage(context);
|
|
|
|
case DELETE_IMAGE:
|
|
return piscsi_image.DeleteImage(context);
|
|
|
|
case RENAME_IMAGE:
|
|
return piscsi_image.RenameImage(context);
|
|
|
|
case COPY_IMAGE:
|
|
return piscsi_image.CopyImage(context);
|
|
|
|
case PROTECT_IMAGE:
|
|
case UNPROTECT_IMAGE:
|
|
return piscsi_image.SetImagePermissions(context);
|
|
|
|
case RESERVE_IDS:
|
|
return executor->ProcessCmd(context);
|
|
|
|
default:
|
|
// The remaining commands may only be executed when the target is idle
|
|
if (!ExecuteWithLock(context)) {
|
|
return false;
|
|
}
|
|
|
|
return HandleDeviceListChange(context, operation);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Piscsi::ExecuteWithLock(const CommandContext& context)
|
|
{
|
|
scoped_lock<mutex> lock(execution_locker);
|
|
return executor->ProcessCmd(context);
|
|
}
|
|
|
|
bool Piscsi::HandleDeviceListChange(const CommandContext& context, PbOperation operation) const
|
|
{
|
|
// ATTACH and DETACH return the resulting device list
|
|
if (operation == ATTACH || operation == DETACH) {
|
|
// A command with an empty device list is required here in order to return data for all devices
|
|
PbCommand command;
|
|
PbResult result;
|
|
response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder());
|
|
context.WriteResult(result);
|
|
return result.status();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int Piscsi::run(span<char *> args)
|
|
{
|
|
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
|
|
|
Banner(args);
|
|
|
|
// The -v option shall result in no other action except displaying the version
|
|
if (ranges::find_if(args, [] (const char *arg) { return !strcasecmp(arg, "-v"); } ) != args.end()) {
|
|
cout << piscsi_get_version_string() << '\n';
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
PbCommand command;
|
|
string locale;
|
|
string reserved_ids;
|
|
int port = DEFAULT_PORT;
|
|
try {
|
|
locale = ParseArguments(args, command, port, reserved_ids);
|
|
}
|
|
catch(const parser_exception& e) {
|
|
cerr << "Error: " << e.what() << endl;
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (!InitBus()) {
|
|
cerr << "Error: Can't initialize bus" << endl;
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (const string error = service.Init([this] (CommandContext& context) {
|
|
context.SetDefaultFolder(piscsi_image.GetDefaultFolder());
|
|
return ExecuteCommand(context);
|
|
}, port); !error.empty()) {
|
|
cerr << "Error: " << error << endl;
|
|
|
|
CleanUp();
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (const string error = executor->SetReservedIds(reserved_ids); !error.empty()) {
|
|
cerr << "Error: " << error << endl;
|
|
|
|
CleanUp();
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (command.devices_size()) {
|
|
// Attach all specified devices
|
|
command.set_operation(ATTACH);
|
|
|
|
if (const CommandContext context(command, piscsi_image.GetDefaultFolder(), locale); !executor->ProcessCmd(context)) {
|
|
cerr << "Error: Can't attach devices" << endl;
|
|
|
|
CleanUp();
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
// Display and log the device list
|
|
PbServerInfo server_info;
|
|
response.GetDevices(controller_manager.GetAllDevices(), server_info, piscsi_image.GetDefaultFolder());
|
|
const vector<PbDevice>& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
|
|
const string device_list = ListDevices(devices);
|
|
LogDevices(device_list);
|
|
cout << device_list << flush;
|
|
|
|
instance = this;
|
|
// Signal handler to detach all devices on a KILL or TERM signal
|
|
struct sigaction termination_handler;
|
|
termination_handler.sa_handler = TerminationHandler;
|
|
sigemptyset(&termination_handler.sa_mask);
|
|
termination_handler.sa_flags = 0;
|
|
sigaction(SIGINT, &termination_handler, nullptr);
|
|
sigaction(SIGTERM, &termination_handler, nullptr);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
// Set the affinity to a specific processor core
|
|
FixCpu(3);
|
|
|
|
service.Start();
|
|
|
|
Process();
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
void Piscsi::Process()
|
|
{
|
|
#ifdef USE_SEL_EVENT_ENABLE
|
|
// Scheduling policy setting (highest priority)
|
|
// TODO Check whether this results in any performance gain
|
|
sched_param schparam;
|
|
schparam.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
|
sched_setscheduler(0, SCHED_FIFO, &schparam);
|
|
#else
|
|
cout << "Note: No PiSCSI hardware support, only client interface calls are supported" << endl;
|
|
#endif
|
|
|
|
// Main Loop
|
|
while (service.IsRunning()) {
|
|
#ifdef USE_SEL_EVENT_ENABLE
|
|
// SEL signal polling
|
|
if (!bus->PollSelectEvent()) {
|
|
// Stop on interrupt
|
|
if (errno == EINTR) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Get the bus
|
|
bus->Acquire();
|
|
#else
|
|
bus->Acquire();
|
|
if (!bus->GetSEL()) {
|
|
const timespec ts = { .tv_sec = 0, .tv_nsec = 0};
|
|
nanosleep(&ts, nullptr);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// Only process the SCSI command if the bus is not busy and no other device responded
|
|
if (IsNotBusy() && bus->GetSEL()) {
|
|
scoped_lock<mutex> lock(execution_locker);
|
|
|
|
// Process command on the responsible controller based on the current initiator and target ID
|
|
if (const auto shutdown_mode = controller_manager.ProcessOnController(bus->GetDAT());
|
|
shutdown_mode != AbstractController::piscsi_shutdown_mode::NONE) {
|
|
// When the bus is free PiSCSI or the Pi may be shut down.
|
|
ShutDown(shutdown_mode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shutdown on a remote interface command
|
|
bool Piscsi::ShutDown(const CommandContext& context, const string& m) {
|
|
if (m.empty()) {
|
|
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING);
|
|
}
|
|
|
|
AbstractController::piscsi_shutdown_mode mode = AbstractController::piscsi_shutdown_mode::NONE;
|
|
if (m == "rascsi") {
|
|
mode = AbstractController::piscsi_shutdown_mode::STOP_PISCSI;
|
|
}
|
|
else if (m == "system") {
|
|
mode = AbstractController::piscsi_shutdown_mode::STOP_PI;
|
|
}
|
|
else if (m == "reboot") {
|
|
mode = AbstractController::piscsi_shutdown_mode::RESTART_PI;
|
|
}
|
|
else {
|
|
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID, m);
|
|
}
|
|
|
|
// Shutdown modes other than rascsi require root permissions
|
|
if (mode != AbstractController::piscsi_shutdown_mode::STOP_PISCSI && getuid()) {
|
|
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_PERMISSION);
|
|
}
|
|
|
|
// Report success now because after a shutdown nothing can be reported anymore
|
|
PbResult result;
|
|
context.WriteSuccessResult(result);
|
|
|
|
return ShutDown(mode);
|
|
}
|
|
|
|
// Shutdown on a SCSI command
|
|
bool Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode)
|
|
{
|
|
switch(shutdown_mode) {
|
|
case AbstractController::piscsi_shutdown_mode::STOP_PISCSI:
|
|
spdlog::info("PiSCSI shutdown requested");
|
|
CleanUp();
|
|
return true;
|
|
|
|
case AbstractController::piscsi_shutdown_mode::STOP_PI:
|
|
spdlog::info("Raspberry Pi shutdown requested");
|
|
CleanUp();
|
|
if (system("init 0") == -1) {
|
|
spdlog::error("Raspberry Pi shutdown failed");
|
|
}
|
|
break;
|
|
|
|
case AbstractController::piscsi_shutdown_mode::RESTART_PI:
|
|
spdlog::info("Raspberry Pi restart requested");
|
|
CleanUp();
|
|
if (system("init 6") == -1) {
|
|
spdlog::error("Raspberry Pi restart failed");
|
|
}
|
|
break;
|
|
|
|
case AbstractController::piscsi_shutdown_mode::NONE:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Piscsi::IsNotBusy() const
|
|
{
|
|
// Wait until BSY is released as there is a possibility for the
|
|
// initiator to assert it while setting the ID (for up to 3 seconds)
|
|
if (bus->GetBSY()) {
|
|
const auto now = chrono::steady_clock::now();
|
|
while ((chrono::duration_cast<chrono::seconds>(chrono::steady_clock::now() - now).count()) < 3) {
|
|
bus->Acquire();
|
|
|
|
if (!bus->GetBSY()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|