Remote saving of IP config and calibration

This commit is contained in:
Joris van Rantwijk 2024-09-29 21:48:33 +02:00
parent 2a2f97c006
commit 055c084c60
4 changed files with 222 additions and 25 deletions

View File

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

View File

@ -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<std::string> 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<std::string> 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<

View File

@ -0,0 +1,82 @@
/*
* subproc.cpp
*
* Run program as a subprocess.
*
* Joris van Rantwijk 2024
*/
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <system_error>
#include "subproc.hpp"
/** Run the specified program in a subprocess. */
int puzzlefw::run_subprocess(std::string path, std::vector<std::string> args)
{
// Prepare argv array.
std::vector<char*> 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;
}

View File

@ -0,0 +1,37 @@
/*
* subproc.hpp
*
* Run program as a subprocess.
*
* Joris van Rantwijk 2024
*/
#ifndef PUZZLEFW_SUBPROC_H_
#define PUZZLEFW_SUBPROC_H_
#include <string>
#include <vector>
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<std::string> args);
} // namespace puzzlefw
#endif // PUZZLEFW_SUBPROC_H_