diff --git a/sw/src/userspace/Makefile b/sw/src/userspace/Makefile index adbd31a..7bf2979 100644 --- a/sw/src/userspace/Makefile +++ b/sw/src/userspace/Makefile @@ -13,12 +13,13 @@ all: puzzlecmd remotectl puzzlecmd: puzzlecmd.o puzzlefw.o $(CXX) -o $@ $(LDFLAGS) $^ -remotectl: remotectl.o puzzlefw.o +remotectl: remotectl.o puzzlefw.o subproc.o $(CXX) -o $@ $(LDFLAGS) $^ puzzlecmd.o: puzzlecmd.cpp logging.hpp puzzlefw.hpp interrupt_manager.hpp data_server.hpp -remotectl.o: remotectl.cpp logging.hpp puzzlefw.hpp interrupt_manager.hpp data_server.hpp version.hpp +remotectl.o: remotectl.cpp logging.hpp puzzlefw.hpp interrupt_manager.hpp data_server.hpp subproc.hpp version.hpp puzzlefw.o: puzzlefw.cpp puzzlefw.hpp +subproc.o: subproc.cpp subproc.hpp .PHONY: clean clean: diff --git a/sw/src/userspace/remotectl.cpp b/sw/src/userspace/remotectl.cpp index f6a2e97..bd763b1 100644 --- a/sw/src/userspace/remotectl.cpp +++ b/sw/src/userspace/remotectl.cpp @@ -29,6 +29,7 @@ #include "logging.hpp" #include "interrupt_manager.hpp" #include "data_server.hpp" +#include "subproc.hpp" #include "version.hpp" using namespace puzzlefw; @@ -119,39 +120,53 @@ struct NetworkConfig { /** Read network configuration from file. */ -bool read_network_config(const char *filename, NetworkConfig& ipcfg) +bool read_network_config(const std::string& filename, NetworkConfig& ipcfg) { ipcfg = NetworkConfig(); std::ifstream is(filename); if (!is) { - log(LOG_ERROR, "Can not read %s", filename); + log(LOG_ERROR, "Can not read %s", filename.c_str()); return false; } std::string line; while (std::getline(is, line)) { - boost::system::error_code ec{}; + size_t p = line.find_last_not_of(" \t\n\v\f\r"); if (p != line.npos) { line.erase(p + 1); } - if (line == "MODE=dhcp") { - ipcfg.mode = NETCFG_DHCP; + + p = line.find('='); + if (p == line.npos) { + continue; } - if (line == "MODE=static") { - ipcfg.mode = NETCFG_STATIC; + + std::string label = line.substr(0, p); + std::string value = line.substr(p + 1); + + boost::system::error_code ec{}; + + if (label == "MODE") { + if (value == "dhcp") { + ipcfg.mode = NETCFG_DHCP; + } + if (value == "static") { + ipcfg.mode = NETCFG_STATIC; + } } - if (line.compare(0, 7, "IPADDR=") == 0) { - ipcfg.ipaddr = asio::ip::make_address_v4(line.substr(7), ec); + if (label == "IPADDR" && (! value.empty())) { + ipcfg.ipaddr = asio::ip::make_address_v4(value, ec); } - if (line.compare(0, 8, "NETMASK=") == 0) { - ipcfg.netmask = asio::ip::make_address_v4(line.substr(8), ec); + if (label == "NETMASK" && (! value.empty())) { + ipcfg.netmask = asio::ip::make_address_v4(value, ec); } - if (line.compare(0, 8, "GATEWAY=") == 0 && line.size() > 8) { - ipcfg.gateway = asio::ip::make_address_v4(line.substr(8), ec); + if (label == "GATEWAY" && (! value.empty())) { + ipcfg.gateway = asio::ip::make_address_v4(value, ec); } + if (ec) { return false; } @@ -240,7 +255,7 @@ struct Calibration { /** Read calibration from file. */ -void read_calibration_file(const char *filename, Calibration& cal) +void read_calibration_file(const std::string& filename, Calibration& cal) { // Set defaults in case calibration is missing or incomplete. for (unsigned int channel = 0; channel < 4; channel++) { @@ -253,7 +268,7 @@ void read_calibration_file(const char *filename, Calibration& cal) std::ifstream is(filename); if (!is) { - log(LOG_ERROR, "Can not read calibration file"); + log(LOG_ERROR, "Can not read %s", filename.c_str()); return; } @@ -310,11 +325,11 @@ void read_calibration_file(const char *filename, Calibration& cal) /** Write calibration to file. */ -bool write_calibration_file(const char *filename, const Calibration& cal) +bool write_calibration_file(const std::string& filename, const Calibration& cal) { std::ofstream os(filename); if (!os) { - log(LOG_ERROR, "Can not write calibration file"); + log(LOG_ERROR, "Can not write %s", filename.c_str()); return false; } @@ -342,7 +357,7 @@ bool write_calibration_file(const char *filename, const Calibration& cal) } if (!os) { - log(LOG_ERROR, "Can not write calibration file"); + log(LOG_ERROR, "Error while writing %s", filename.c_str()); return false; } @@ -350,6 +365,43 @@ bool write_calibration_file(const char *filename, const Calibration& cal) } +/* ******** Run subprograms ******** */ + +/** Run script to activate or save network configuration. */ +bool run_ipcfg_script(const NetworkConfig& ipcfg, bool save) +{ + std::vector args; + args.push_back(save ? "save" : "config"); + if (ipcfg.mode == NETCFG_STATIC) { + args.push_back("--mode"); + args.push_back("static"); + args.push_back("--ipaddr"); + args.push_back(ipcfg.ipaddr.to_string()); + args.push_back("--netmask"); + args.push_back(ipcfg.netmask.to_string()); + args.push_back("--gateway"); + if (ipcfg.gateway.is_unspecified()) { + args.push_back(""); + } else { + args.push_back(ipcfg.gateway.to_string()); + } + } else { + args.push_back("--mode"); + args.push_back("dhcp"); + } + int status = run_subprocess("/opt/puzzlefw/bin/puzzle-ipcfg", args); + return (status == 0); +} + + +/** Run script to copy calibration file to SD card. */ +bool run_calibration_script() +{ + std::vector args = { "save" }; + int status = run_subprocess("/opt/puzzlefw/bin/puzzle-calibration", args); + return (status == 0); +} + /* ******** class CommandHandler ******** */ @@ -1200,13 +1252,16 @@ private: /** Handle command AIN:MINMAX:CLEAR */ std::string cmd_cal_save(CommandEnvironment env) { - if (! write_calibration_file(CFG_FILE_CALIBRATION, m_calibration)) { + std::string filename = std::string(CFG_FILE_CALIBRATION) + ".new"; + if (! write_calibration_file(filename, m_calibration)) { return "ERROR Can not write calibration"; } - // TODO - invoke script to save to SD card + if (! run_calibration_script()) { + return "ERROR Saving calibration failed"; + } - return "ERROR"; + return "OK"; } /** Handle command IPCFG */ @@ -1218,8 +1273,30 @@ private: if (! parse_network_config(args, ipcfg, errmsg)) { return "ERROR " + errmsg; } - // TODO -- activate or save address, restart networking if needed - return "ERROR"; + + if (env.saved_flag) { + + if (! run_ipcfg_script(ipcfg, true)) { + return "ERROR Saving network configuration failed"; + } + + return "OK"; + + } else { + + // Shut down server sockets; then change IP address and resume. + m_shutting_down = true; + stop_server( + [this,ipcfg]() { + run_ipcfg_script(ipcfg, false); + m_shutting_down = false; + start_server(); + }); + + // No response while shutting down. + // Response delivery would not be reliable while the socket is closing. + return std::string(); + } } static const inline std::map< diff --git a/sw/src/userspace/subproc.cpp b/sw/src/userspace/subproc.cpp new file mode 100644 index 0000000..61006c0 --- /dev/null +++ b/sw/src/userspace/subproc.cpp @@ -0,0 +1,82 @@ +/* + * subproc.cpp + * + * Run program as a subprocess. + * + * Joris van Rantwijk 2024 + */ + +#include +#include +#include +#include +#include + +#include "subproc.hpp" + + +/** Run the specified program in a subprocess. */ +int puzzlefw::run_subprocess(std::string path, std::vector args) +{ + // Prepare argv array. + std::vector argv_vec; + argv_vec.push_back(path.data()); + for (std::string& arg : args) { + argv_vec.push_back(arg.data()); + } + argv_vec.push_back(nullptr); + + char** argv = argv_vec.data(); + + int max_file_descriptor = sysconf(_SC_OPEN_MAX); + + // Block SIGCHLD. + sigset_t block_sigmask, saved_sigmask; + sigemptyset(&block_sigmask); + sigaddset(&block_sigmask, SIGCHLD); + pthread_sigmask(SIG_BLOCK, &block_sigmask, &saved_sigmask); + + pid_t pid = fork(); + if (pid == -1) { + pthread_sigmask(SIG_SETMASK, &saved_sigmask, nullptr); + throw std::system_error( + std::error_code(errno, std::system_category()), + std::string("Can not run subprocess")); + } + + if (pid == 0) { + // This code runs in the subprocess. + + // Restore signal mask. + sigprocmask(SIG_SETMASK, &saved_sigmask, nullptr); + + // Close non-standard file descriptors. + for (int i = 3; i < max_file_descriptor; i++) { + close(i); + } + + // Execute program. + execv(argv[0], argv); + _exit(127); + } + + // Wait until the subprocess ends. + int wstatus; + while (1) { + if (waitpid(pid, &wstatus, 0) < 0) { + if (errno == EINTR) { + continue; + } + pthread_sigmask(SIG_SETMASK, &saved_sigmask, nullptr); + throw std::system_error( + std::error_code(errno, std::system_category()), + std::string("Can not wait for subprocess")); + } + break; + } + + // Restore signal mask. + pthread_sigmask(SIG_SETMASK, &saved_sigmask, nullptr); + + return wstatus; +} diff --git a/sw/src/userspace/subproc.hpp b/sw/src/userspace/subproc.hpp new file mode 100644 index 0000000..911016c --- /dev/null +++ b/sw/src/userspace/subproc.hpp @@ -0,0 +1,37 @@ +/* + * subproc.hpp + * + * Run program as a subprocess. + * + * Joris van Rantwijk 2024 + */ + +#ifndef PUZZLEFW_SUBPROC_H_ +#define PUZZLEFW_SUBPROC_H_ + +#include +#include + + +namespace puzzlefw { + +/** + * Run the specified program in a subprocess. + * + * The argument "path" specifies the full path of the target program. + * This string also becomes the value of "argv[0]". + * + * The argument "args" specifies a list of optional additional arguments + * ("argv[1]" and further). + * + * The subprocess closes non-standard file descriptors before executing + * the program. + * + * This function waits (blocks) until the subprocess ends. + * It returns the termination status of the subprocess. + */ +int run_subprocess(std::string path, std::vector args); + +} // namespace puzzlefw + +#endif // PUZZLEFW_SUBPROC_H_