Merge pull request #1167 from slsdetectorgroup/dev/multirxr_proper_cleanup_on_ctrlc
All checks were successful
Build on RHEL9 / build (push) Successful in 3m41s
Build on RHEL8 / build (push) Successful in 5m4s

Dev/multirxr proper cleanup on ctrl + c and versioning
This commit is contained in:
2025-07-11 11:12:23 +02:00
committed by GitHub
14 changed files with 997 additions and 434 deletions

View File

@ -152,12 +152,12 @@ int main(int argc, char *argv[]) {
strcpy(version, APIXILINXCTB);
#endif
LOG(logINFO, ("SLS Detector Server Version: %s\n", version));
exit(EXIT_SUCCESS);
return EXIT_SUCCESS;
case 'p':
if (sscanf(optarg, "%d", &portno) != 1) {
LOG(logERROR, ("Cannot scan port argument\n%s", helpMessage));
exit(EXIT_FAILURE);
return EXIT_FAILURE;
}
LOG(logINFO, ("Detected port: %d\n", portno));
break;
@ -188,7 +188,7 @@ int main(int argc, char *argv[]) {
ignoreConfigFileFlag = 1;
#else
LOG(logERROR, ("No server config files for this detector\n"));
exit(EXIT_FAILURE);
return EXIT_FAILURE;
#endif
break;
@ -196,11 +196,11 @@ int main(int argc, char *argv[]) {
#if !defined(VIRTUAL) && !defined(EIGERD)
LOG(logERROR, ("Cannot set master via the detector server for this "
"detector\n"));
exit(EXIT_FAILURE);
return EXIT_FAILURE;
#elif defined(GOTTHARD2D) || defined(EIGERD) || defined(MYTHEN3D)
if (sscanf(optarg, "%d", &masterCommandLine) != 1) {
LOG(logERROR, ("Cannot scan master argument\n%s", helpMessage));
exit(EXIT_FAILURE);
return EXIT_FAILURE;
}
if (masterCommandLine == 1) {
LOG(logINFO, ("Detector Master mode\n"));
@ -209,7 +209,7 @@ int main(int argc, char *argv[]) {
}
#else
LOG(logERROR, ("No master implemented for this detector server\n"));
exit(EXIT_FAILURE);
return EXIT_FAILURE;
#endif
break;
@ -217,7 +217,7 @@ int main(int argc, char *argv[]) {
#ifdef EIGERD
if (sscanf(optarg, "%d", &topCommandLine) != 1) {
LOG(logERROR, ("Cannot scan top argument\n%s", helpMessage));
exit(EXIT_FAILURE);
return EXIT_FAILURE;
}
if (topCommandLine == 1) {
LOG(logINFO, ("Detector Top mode\n"));
@ -226,16 +226,16 @@ int main(int argc, char *argv[]) {
}
#else
LOG(logERROR, ("No top implemented for this detector server\n"));
exit(EXIT_FAILURE);
return EXIT_FAILURE;
#endif
break;
case 'h':
printf("%s", helpMessage);
exit(EXIT_SUCCESS);
return EXIT_SUCCESS;
default:
printf("\n%s", helpMessage);
exit(EXIT_FAILURE);
return EXIT_FAILURE;
}
}
@ -376,5 +376,5 @@ int main(int argc, char *argv[]) {
#endif
}
LOG(logINFO, ("Goodbye!\n"));
return 0;
return EXIT_SUCCESS;
}

View File

@ -84,7 +84,7 @@ int main(int argc, char *argv[]) {
c.call(parser.command(), parser.arguments(), parser.detector_id(),
action, std::cout, parser.receiver_id());
} catch (sls::RuntimeError &e) {
exit(EXIT_FAILURE);
return EXIT_FAILURE;
}
exit(EXIT_SUCCESS);
return EXIT_SUCCESS;
}

View File

@ -13,6 +13,7 @@ set(SOURCES
src/Arping.cpp
src/MasterAttributes.cpp
src/MasterFileUtility.cpp
src/CommandLineOptions.cpp
)
set(PUBLICHEADERS
@ -51,6 +52,10 @@ target_link_libraries(slsReceiverObject
slsProjectWarnings #don't propagate warnigns
)
target_compile_definitions(slsReceiverObject
PRIVATE $<$<BOOL:${SLS_USE_TESTS}>:SLS_USE_TESTS>
)
# HDF5
if (SLS_USE_HDF5)
if (HDF5_FOUND)
@ -86,6 +91,7 @@ set_target_properties(slsReceiverStatic PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
PUBLIC_HEADER "${PUBLICHEADERS}"
)
list(APPEND RECEIVER_LIBRARY_TARGETS slsReceiverStatic)

View File

@ -13,22 +13,12 @@ class Receiver : private virtual slsDetectorDefs {
public:
/**
* Constructor
* Starts up a Receiver server. Reads configuration file, options, and
* assembles a Receiver using TCP and UDP detector interfaces
* Starts up a Receiver server.
* Assembles a Receiver using TCP and UDP detector interfaces
* throws an exception in case of failure
* @param argc from command line
* @param argv from command line
* @param port TCP/IP port number
*/
Receiver(int argc, char *argv[]);
/**
* Constructor
* Starts up a Receiver server. Reads configuration file, options, and
* assembles a Receiver using TCP and UDP detector interfaces
* throws an exception in case of failure
* @param tcpip_port_no TCP/IP port number
*/
Receiver(uint16_t tcpip_port_no = 1954);
Receiver(uint16_t port = 1954);
~Receiver();

View File

@ -0,0 +1,347 @@
// SPDX-License-Identifier: LGPL-3.0-or-other
// Copyright (C) 2021 Contributors to the SLS Detector Package
#include "CommandLineOptions.h"
#include "sls/ToString.h"
#include "sls/logger.h"
#include "sls/sls_detector_defs.h"
#include "sls/versionAPI.h"
#include <cstring>
#include <unistd.h>
CommandLineOptions::CommandLineOptions(AppType app)
: appType_(app), optString_(buildOptString()),
longOptions_(buildOptionList()) {}
/** for testing */
ParsedOptions CommandLineOptions::parse(const std::vector<std::string> &args) {
std::vector<char *> argv;
argv.reserve(args.size());
for (const auto &arg : args) {
argv.push_back(const_cast<char *>(arg.c_str()));
}
int argc = static_cast<int>(argv.size());
return parse(argc, argv.data());
}
ParsedOptions CommandLineOptions::parse(int argc, char *argv[]) {
CommonOptions base;
MultiReceiverOptions multi;
FrameSyncOptions frame;
base.port = DEFAULT_TCP_RX_PORTNO;
optind = 0; // reset getopt
int opt, option_index = 0;
while ((opt = getopt_long(argc, argv, optString_.c_str(),
longOptions_.data(), &option_index)) != -1) {
switch (opt) {
case 'v':
case 'h':
handleCommonOption(opt, optarg, base);
return base; // exit after version/help
case 'p':
case 'u':
handleCommonOption(opt, optarg, base);
break;
case 'c':
case 'n':
case 't':
handleAppSpecificOption(opt, optarg, base, multi, frame);
break;
default:
throw sls::RuntimeError("Invalid arguments." + getHelpMessage());
}
}
// remaining arguments
if (optind < argc) {
// deprecated and current options => invalid
if (base.port != DEFAULT_TCP_RX_PORTNO || multi.numReceivers != 1 ||
frame.numReceivers != 1 || multi.callbackEnabled != false ||
frame.printHeaders != false) {
LOG(sls::logWARNING) << "Cannot use both deprecated options and "
"the valid options simultaneously. Please "
"move away from the deprecated options.\n";
}
// unsupported deprecated arguments
if (appType_ == AppType::SingleReceiver) {
throw sls::RuntimeError("Invalid arguments." + getHelpMessage());
}
// parse deprecated arguments
std::vector<std::string> args(argv, argv + argc);
auto [p, n, o] = ParseDeprecated(args);
// set options
base.port = p;
if (appType_ == AppType::MultiReceiver) {
multi.numReceivers = n;
multi.callbackEnabled = o;
} else if (appType_ == AppType::FrameSynchronizer) {
frame.numReceivers = n;
frame.printHeaders = o;
}
}
// Logging
LOG(sls::logINFO) << "TCP Port: " << base.port;
if (appType_ == AppType::MultiReceiver) {
LOG(sls::logINFO) << "Number of receivers: " << multi.numReceivers;
LOG(sls::logINFO) << "Callback enabled: " << multi.callbackEnabled;
} else if (appType_ == AppType::FrameSynchronizer) {
LOG(sls::logINFO) << "Number of receivers: " << frame.numReceivers;
LOG(sls::logINFO) << "Print headers: " << frame.printHeaders;
}
switch (appType_) {
case AppType::SingleReceiver:
return base;
case AppType::MultiReceiver:
static_cast<CommonOptions &>(multi) = base;
return multi;
case AppType::FrameSynchronizer:
static_cast<CommonOptions &>(frame) = base;
return frame;
default:
throw sls::RuntimeError("Unknown AppType in CommandLineOptions::parse");
}
}
std::vector<option> CommandLineOptions::buildOptionList() const {
std::vector<option> opts = {
{"version", no_argument, nullptr, 'v'},
{"help", no_argument, nullptr, 'h'},
{"port", required_argument, nullptr, 'p'},
{"uid", required_argument, nullptr, 'u'},
};
switch (appType_) {
case AppType::SingleReceiver:
opts.push_back({"rx_tcpport", required_argument, nullptr, 't'});
break;
case AppType::MultiReceiver:
opts.push_back({"num-receivers", required_argument, nullptr, 'n'});
opts.push_back({"callback", no_argument, nullptr, 'c'});
break;
case AppType::FrameSynchronizer:
opts.push_back({"num-receivers", required_argument, nullptr, 'n'});
opts.push_back({"print-headers", no_argument, nullptr, 'c'});
break;
}
opts.push_back({nullptr, 0, nullptr, 0}); // null-terminator for getopt
return opts;
}
std::string CommandLineOptions::buildOptString() const {
std::string optstr = "vhp:u:";
if (appType_ == AppType::MultiReceiver ||
appType_ == AppType::FrameSynchronizer)
optstr += "cn:";
if (appType_ == AppType::SingleReceiver)
optstr += "t:";
return optstr;
}
uint16_t CommandLineOptions::parsePort(const char *optarg) {
uint16_t val = 0;
try {
val = sls::StringTo<uint16_t>(optarg);
} catch (...) {
throw sls::RuntimeError("Could not parse port number " +
std::string(optarg));
}
if (val < 1024) {
throw sls::RuntimeError(
"Invalid/ privileged port number parsed. Min: 1024.");
}
return val;
}
uint16_t CommandLineOptions::parseNumReceivers(const char *optarg) {
uint16_t val = 0;
try {
val = sls::StringTo<uint16_t>(optarg);
} catch (...) {
throw sls::RuntimeError("Could not parse number of receivers " +
std::string(optarg));
}
if (val == 0 || val > MAX_RECEIVERS) {
throw sls::RuntimeError(
"Invalid number of receivers parsed. Options: 1 - " +
std::to_string(MAX_RECEIVERS));
}
return val;
}
uid_t CommandLineOptions::parseUID(const char *optarg) {
uid_t val = -1;
try {
val = sls::StringTo<uid_t>(optarg);
} catch (...) {
throw sls::RuntimeError("Could not parse UID " + std::string(optarg));
}
if (val == static_cast<uid_t>(-1)) {
throw sls::RuntimeError(
"Could not parse UID. Expected a valid user ID." +
std::string(optarg));
}
return val;
}
void CommandLineOptions::handleCommonOption(int opt, const char *optarg,
CommonOptions &base) {
switch (opt) {
case 'v':
base.versionRequested = true;
std::cout << getVersion() << std::endl;
break;
case 'h':
base.helpRequested = true;
std::cout << getHelpMessage() << std::endl;
break;
case 'p':
base.port = parsePort(optarg);
break;
case 'u':
base.userid = parseUID(optarg);
setEffectiveUID(base.userid);
break;
}
}
void CommandLineOptions::handleAppSpecificOption(int opt, const char *optarg,
CommonOptions &base,
MultiReceiverOptions &multi,
FrameSyncOptions &frame) {
switch (opt) {
case 'c':
if (appType_ == AppType::MultiReceiver)
multi.callbackEnabled = true;
else if (appType_ == AppType::FrameSynchronizer)
frame.printHeaders = true;
break;
case 'n': {
auto val = parseNumReceivers(optarg);
if (appType_ == AppType::MultiReceiver)
multi.numReceivers = val;
else if (appType_ == AppType::FrameSynchronizer)
frame.numReceivers = val;
break;
}
case 't':
LOG(sls::logWARNING) << "Deprecated option '-t' and '--rx_tcport'. Use "
"'p' or '--port' instead.";
base.port = parsePort(optarg);
break;
}
}
/* maintain backward compatibility of [start port] [num receivers] [optional
* arg] */
std::tuple<uint16_t, uint16_t, bool>
CommandLineOptions::ParseDeprecated(const std::vector<std::string> &args) {
size_t nargs = args.size();
if (nargs != 1 && nargs != 3 && nargs != 4) {
throw sls::RuntimeError("Invalid number of arguments.");
}
LOG(sls::logWARNING)
<< "Deprecated options will be removed in future versions. "
"Please use the new options.\n";
// default deprecated values
if (nargs == 1) {
return std::make_tuple(DEFAULT_TCP_RX_PORTNO, 1, false);
}
// parse deprecated arguments
uint16_t p = parsePort(args[1].c_str());
uint16_t n = parseNumReceivers(args[2].c_str());
bool o = false;
if (nargs == 4) {
try {
o = sls::StringTo<bool>(args[3].c_str());
} catch (...) {
throw sls::RuntimeError("Invalid optional argument "
"parsed. Expected 1 (true) or "
"0 (false).");
}
}
return std::make_tuple(p, n, o);
}
std::string CommandLineOptions::getTypeString() const {
switch (appType_) {
case AppType::SingleReceiver:
return "slsReceiver";
case AppType::MultiReceiver:
return "slsMultiReceiver";
case AppType::FrameSynchronizer:
return "slsFrameSynchronizer";
default:
return "Unknown";
}
}
std::string CommandLineOptions::getVersion() const {
return getTypeString() + " Version: " + APIRECEIVER;
}
std::string CommandLineOptions::getHelpMessage() const {
switch (appType_) {
case AppType::SingleReceiver:
return std::string("\nUsage: ") + getTypeString() + " Options:\n" +
"\t-v, --version : Version.\n" +
"\t-p, --port : TCP port to communicate with client "
"for "
"configuration. Non-zero and 16 bit.\n" +
"\t-u, --uid : Set effective user id if receiver "
"started "
"with privileges. \n\n";
case AppType::MultiReceiver:
return std::string("\nUsage: " + getTypeString() + " Options:\n") +
"\t-v, --version : Version.\n" +
"\t-n, --num-receivers : Number of receivers.\n" +
"\t-p, --port : TCP port to communicate with client "
"for "
"configuration. Non-zero and 16 bit.\n" +
"\t-c, --callback : Enable dummy callbacks for debugging. "
"Disabled by default. \n" +
"\t-u, --uid : Set effective user id if receiver "
"started "
"with privileges. \n\n";
case AppType::FrameSynchronizer:
return std::string("\nUsage: " + getTypeString() + " Options:\n") +
"\t-v, --version : Version.\n" +
"\t-n, --num-receivers : Number of receivers.\n" +
"\t-p, --port : TCP port to communicate with client "
"for "
"configuration. Non-zero and 16 bit.\n" +
"\t-c, --print-headers : Print callback headers for debugging. "
"Disabled by default.\n" +
"\t-u, --uid : Set effective user id if receiver "
"started "
"with privileges. \n\n";
}
throw sls::RuntimeError("Unknown AppType for help message");
}
void CommandLineOptions::setEffectiveUID(uid_t uid) {
if (geteuid() == uid) {
LOG(sls::logINFO) << "Process already has the same Effective UID "
<< uid;
} else {
if (seteuid(uid) != 0 || geteuid() != uid) {
throw sls::RuntimeError("Could not set Effective UID");
}
LOG(sls::logINFO) << "Process Effective UID changed to " << uid;
}
}

View File

@ -0,0 +1,63 @@
// SPDX-License-Identifier: LGPL-3.0-or-other
// Copyright (C) 2021 Contributors to the SLS Detector Package
#pragma once
#include <cstdint>
#include <getopt.h>
#include <string>
#include <tuple>
#include <variant>
#include <vector>
enum class AppType { MultiReceiver, SingleReceiver, FrameSynchronizer };
struct CommonOptions {
uint16_t port = -1;
uid_t userid = -1;
bool versionRequested = false;
bool helpRequested = false;
};
struct MultiReceiverOptions : CommonOptions {
uint16_t numReceivers = 1;
bool callbackEnabled = false;
};
struct FrameSyncOptions : CommonOptions {
uint16_t numReceivers = 1;
bool printHeaders = false;
};
using ParsedOptions =
std::variant<CommonOptions, MultiReceiverOptions, FrameSyncOptions>;
class CommandLineOptions {
public:
explicit CommandLineOptions(AppType app);
ParsedOptions parse(const std::vector<std::string> &args); // for testing
ParsedOptions parse(int argc, char *argv[]);
std::string getTypeString() const;
std::string getVersion() const;
std::string getHelpMessage() const;
static void setEffectiveUID(uid_t uid);
static std::tuple<uint16_t, uint16_t, bool>
ParseDeprecated(const std::vector<std::string> &args);
private:
AppType appType_;
std::string optString_;
std::vector<option> longOptions_;
std::vector<option> buildOptionList() const;
std::string buildOptString() const;
static uint16_t parsePort(const char *optarg);
static uint16_t parseNumReceivers(const char *optarg);
static uid_t parseUID(const char *optarg);
void handleCommonOption(int opt, const char *optarg, CommonOptions &base);
void handleAppSpecificOption(int opt, const char *optarg,
CommonOptions &base,
MultiReceiverOptions &multi,
FrameSyncOptions &frame);
static constexpr uint16_t MAX_RECEIVERS = 1000;
};

View File

@ -5,16 +5,17 @@
* reconstructing image. Sample python script for pull socket for this combiner
* in python/scripts folder. TODO: Not handling empty frames from one socket
*/
#include "CommandLineOptions.h"
#include "sls/Receiver.h"
#include "sls/ToString.h"
#include "sls/container_utils.h"
#include "sls/logger.h"
#include "sls/network_utils.h"
#include "sls/sls_detector_defs.h"
#include <csignal> //SIGINT
#include <cstdio>
#include <cstring>
#include <iostream>
#include <mutex>
#include <ostream>
#include <semaphore.h>
@ -28,9 +29,14 @@
#include <zmq.h>
std::vector<std::thread> threads;
std::vector<sem_t *> semaphores;
sls::TLogLevel printHeadersLevel = sls::logDEBUG;
// gettid added in glibc 2.30
#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30
#include <sys/syscall.h>
#define gettid() syscall(SYS_gettid)
#endif
/** Define Colors to print data call back in different colors for different
* recievers */
#define PRINT_IN_COLOR(c, f, ...) \
@ -57,16 +63,6 @@ struct FrameStatus {
};
FrameStatus *global_frame_status = nullptr;
/**
* Control+C Interrupt Handler
* to let all the processes know to exit properly
*/
void sigInterruptHandler(int p) {
for (size_t i = 0; i != semaphores.size(); ++i) {
sem_post(semaphores[i]);
}
}
void cleanup() {
if (global_frame_status) {
std::lock_guard<std::mutex> lock(global_frame_status->mtx);
@ -86,19 +82,6 @@ void cleanup() {
}
}
/**
* prints usage of this example program
*/
std::string getHelpMessage() {
std::ostringstream os;
os << "\nUsage:\n"
"./slsFrameSynchronizer [start tcp port] [num recevers] [print "
"callback headers (optional)]\n"
<< "\t - tcp port has to be non-zero and 16 bit\n"
<< "\t - print callback headers option is 0 (disabled) by default\n";
return os.str();
}
void zmq_free(void *data, void *hint) { delete[] static_cast<char *>(data); }
void print_frames(const PortFrameMap &frame_port_map) {
@ -512,83 +495,46 @@ void GetDataCallback(slsDetectorDefs::sls_receiver_header &header,
sem_post(&stat->available);
}
std::vector<sem_t> semaphores;
/**
* Example of main program using the Receiver class
*
* - Defines in file for:
* - Default Number of receivers is 1
* - Default Start TCP port is 1954
* Control+C Interrupt Handler
* to let all the processes know to exit properly
* Only the main thread will call this handler
*/
void sigInterruptHandler(int p) {
(void)signal; // suppress unused warning if needed
for (auto &s : semaphores) {
sem_post(&s);
}
}
int main(int argc, char *argv[]) {
/** - set default values */
int numReceivers = 1;
uint16_t startTCPPort = DEFAULT_TCP_RX_PORTNO;
bool printHeaders = false;
/** - get number of receivers and start tcp port from command line
* arguments */
if (argc > 1) {
try {
if (argc == 3 || argc == 4) {
startTCPPort = sls::StringTo<uint16_t>(argv[1]);
if (startTCPPort == 0) {
throw std::runtime_error("Invalid start tcp port");
}
numReceivers = std::stoi(argv[2]);
if (numReceivers > 1024) {
cprintf(RED,
"Did you mix up the order of the arguments?\n%s\n",
getHelpMessage().c_str());
return EXIT_FAILURE;
}
if (numReceivers == 0) {
cprintf(RED, "Invalid number of receivers.\n%s\n",
getHelpMessage().c_str());
return EXIT_FAILURE;
}
if (argc == 4) {
printHeaders = sls::StringTo<bool>(argv[3]);
if (printHeaders) {
printHeadersLevel = sls::logINFOBLUE;
}
}
} else
throw std::runtime_error("Invalid number of arguments");
} catch (const std::exception &e) {
cprintf(RED, "Error: %s\n%s\n", e.what(), getHelpMessage().c_str());
return EXIT_FAILURE;
}
CommandLineOptions cli(AppType::FrameSynchronizer);
ParsedOptions opts;
try {
opts = cli.parse(argc, argv);
} catch (sls::RuntimeError &e) {
return EXIT_FAILURE;
}
auto &f = std::get<FrameSyncOptions>(opts);
if (f.versionRequested || f.helpRequested) {
return EXIT_SUCCESS;
}
cprintf(RESET, "Number of Receivers: %d\n", numReceivers);
cprintf(RESET, "Start TCP Port: %hu\n", startTCPPort);
cprintf(RESET, "Print Callback Headers: %s\n\n",
(printHeaders ? "Enabled" : "Disabled"));
LOG(sls::logINFOBLUE) << "Current Process [ Tid: " << gettid() << ']';
/** - Catch signal SIGINT to close files and call destructors properly */
struct sigaction sa;
sa.sa_flags = 0; // no flags
sa.sa_handler = sigInterruptHandler; // handler function
sigemptyset(&sa.sa_mask); // dont block additional signals during invocation
// of handler
if (sigaction(SIGINT, &sa, nullptr) == -1) {
cprintf(RED, "Could not set handler function for SIGINT\n");
// close files on ctrl+c
sls::setupSignalHandler(SIGINT, sigInterruptHandler);
// handle locally on socket crash
sls::setupSignalHandler(SIGPIPE, SIG_IGN);
semaphores.resize(f.numReceivers);
for (auto &s : semaphores) {
sem_init(&s, 0, 0);
}
/** - Ignore SIG_PIPE, prevents global signal handler, handle locally,
instead of a server crashing due to client crash when writing, it just
gives error */
struct sigaction asa;
asa.sa_flags = 0; // no flags
asa.sa_handler = SIG_IGN; // handler function
sigemptyset(&asa.sa_mask); // dont block additional signals during
// invocation of handler
if (sigaction(SIGPIPE, &asa, nullptr) == -1) {
cprintf(RED, "Could not set handler function for SIGPIPE\n");
}
FrameStatus stat{true, false, numReceivers};
FrameStatus stat{true, false, f.numReceivers};
// store pointer for signal handler
global_frame_status = &stat;
@ -596,34 +542,44 @@ int main(int argc, char *argv[]) {
void *user_data = static_cast<void *>(&stat);
std::thread combinerThread(Correlate, &stat);
for (int i = 0; i != numReceivers; ++i) {
sem_t *semaphore = new sem_t;
sem_init(semaphore, 1, 0);
semaphores.push_back(semaphore);
std::exception_ptr threadException = nullptr;
for (int i = 0; i != f.numReceivers; ++i) {
uint16_t port = f.port + i;
sem_t *semaphore = &semaphores[i];
threads.emplace_back(
[i, semaphore, port, user_data, &threadException]() {
LOG(sls::logINFOBLUE)
<< "Thread " << i << " [ Tid: " << gettid() << ']';
try {
sls::Receiver receiver(port);
receiver.registerCallBackStartAcquisition(
StartAcquisitionCallback, user_data);
receiver.registerCallBackAcquisitionFinished(
AcquisitionFinishedCallback, user_data);
receiver.registerCallBackRawDataReady(GetDataCallback,
user_data);
uint16_t port = startTCPPort + i;
threads.emplace_back([i, semaphore, port, user_data]() {
sls::Receiver receiver(port);
receiver.registerCallBackStartAcquisition(StartAcquisitionCallback,
user_data);
receiver.registerCallBackAcquisitionFinished(
AcquisitionFinishedCallback, user_data);
receiver.registerCallBackRawDataReady(GetDataCallback, user_data);
/** - as long as no Ctrl+C */
sem_wait(semaphore);
sem_destroy(semaphore);
delete semaphore;
// clean up frames
if (i == 0)
cleanup();
});
/** - as long as no Ctrl+C */
// each child shares the common semaphore
sem_wait(semaphore);
} catch (...) {
// capture exception and raise SIGINT to exit gracefully
threadException = std::current_exception();
raise(SIGINT);
}
LOG(sls::logINFOBLUE)
<< "Exiting Thread " << i << " [ Tid: " << gettid() << " ]";
});
}
for (auto &thread : threads) {
thread.join();
for (auto &t : threads) {
t.join();
}
for (auto &s : semaphores)
sem_destroy(&s);
cleanup();
{
std::lock_guard<std::mutex> lock(stat.mtx);
stat.terminate = true;
@ -632,6 +588,19 @@ int main(int argc, char *argv[]) {
combinerThread.join();
sem_destroy(&stat.available);
if (threadException) {
try {
std::rethrow_exception(threadException);
} catch (const std::exception &e) {
LOG(sls::logERROR)
<< "Unhandled exception from thread: " << e.what();
return EXIT_FAILURE;
} catch (...) {
LOG(sls::logERROR) << "Unknown exception occurred in thread";
return EXIT_FAILURE;
}
}
LOG(sls::logINFOBLUE) << "Goodbye!";
return EXIT_SUCCESS;
}

View File

@ -2,15 +2,16 @@
// Copyright (C) 2021 Contributors to the SLS Detector Package
/* Creates the slsMultiReceiver for running multiple receivers form a single
* binary */
#include "CommandLineOptions.h"
#include "sls/Receiver.h"
#include "sls/ToString.h"
#include "sls/container_utils.h"
#include "sls/logger.h"
#include "sls/network_utils.h"
#include "sls/sls_detector_defs.h"
#include <csignal> //SIGINT
#include <cstring>
#include <iostream>
#include <semaphore.h>
#include <sys/wait.h> //wait
#include <unistd.h>
@ -26,28 +27,6 @@
#define PRINT_IN_COLOR(c, f, ...) \
printf("\033[%dm" f RESET, 30 + c + 1, ##__VA_ARGS__)
sem_t semaphore;
/**
* Control+C Interrupt Handler
* to let all the processes know to exit properly
*/
void sigInterruptHandler(int p) { sem_post(&semaphore); }
/**
* prints usage of this example program
*/
std::string getHelpMessage() {
std::ostringstream os;
os << "\nUsage:\n"
<< "./slsMultiReceiver [start tcp port] [num recevers] [call back "
"option (optional)]\n"
<< "\t - tcp port has to be non-zero and 16 bit\n"
<< "\t - call back option is 0 (disabled) by default, 1 prints frame "
"header for debugging\n";
return os.str();
}
/**
* Start Acquisition Call back (slsMultiReceiver writes data if file write
* enabled) if registerCallBackRawDataReady or
@ -146,99 +125,48 @@ void GetData(slsDetectorDefs::sls_receiver_header &header,
// header->packetsMask.to_string().c_str(),
((uint8_t)(*((uint8_t *)(dataPointer)))), imageSize);
// // example of how to use roi or modify data that is later written to file
// slsDetectorDefs::ROI roi{0, 10, 0, 20};
// int width = roi.xmax - roi.xmin;
// int height = roi.ymax - roi.ymin;
// uint8_t *destPtr = (uint8_t *)dataPointer;
// for (int irow = roi.ymin; irow < roi.ymax; ++irow) {
// memcpy(destPtr,
// ((uint8_t *)(dataPointer + irow * callbackHeader.shape.x +
// roi.xmin)),
// width);
// destPtr += width;
// }
// memcpy((uint8_t*)dataPointer, (uint8_t*)dataPointer
// // setting roi for eg. changes size
// imageSize = width * height;
// if data is modified, can affect size
// only reduction in size allowed, not increase
// imageSize = 26000;
}
sem_t semaphore;
/**
* Example of main program using the Receiver class
*
* - Defines in file for:
* - Default Number of receivers is 1
* - Default Start TCP port is 1954
* Control+C Interrupt Handler
* to let all the processes know to exit properly
* All child processes will call the handler (parent process set to ignore)
*/
void sigInterruptHandler(int signal) {
(void)signal; // suppress unused warning if needed
sem_post(&semaphore);
}
int main(int argc, char *argv[]) {
/** - set default values */
int numReceivers = 1;
uint16_t startTCPPort = DEFAULT_TCP_RX_PORTNO;
int withCallback = 0;
CommandLineOptions cli(AppType::MultiReceiver);
ParsedOptions opts;
try {
opts = cli.parse(argc, argv);
} catch (sls::RuntimeError &e) {
return EXIT_FAILURE;
}
auto &m = std::get<MultiReceiverOptions>(opts);
if (m.versionRequested || m.helpRequested) {
return EXIT_SUCCESS;
}
LOG(sls::logINFOBLUE) << "Current Process [ Tid: " << gettid() << ']';
// close files on ctrl+c
sls::setupSignalHandler(SIGINT, sigInterruptHandler);
// handle locally on socket crash
sls::setupSignalHandler(SIGPIPE, SIG_IGN);
sem_init(&semaphore, 1, 0);
/** - get number of receivers and start tcp port from command line
* arguments */
if (argc > 1) {
try {
if (argc == 3 || argc == 4) {
startTCPPort = sls::StringTo<uint16_t>(argv[1]);
if (startTCPPort == 0) {
throw std::runtime_error("Invalid start tcp port");
}
numReceivers = std::stoi(argv[2]);
if (numReceivers > 1024) {
cprintf(RED,
"Did you mix up the order of the arguments?\n%s\n",
getHelpMessage().c_str());
return EXIT_FAILURE;
}
if (numReceivers == 0) {
cprintf(RED, "Invalid number of receivers.\n%s\n",
getHelpMessage().c_str());
return EXIT_FAILURE;
}
if (argc == 4) {
withCallback = std::stoi(argv[3]);
}
} else
throw std::runtime_error("Invalid number of arguments");
} catch (const std::exception &e) {
cprintf(RED, "Error: %s\n%s\n", e.what(), getHelpMessage().c_str());
return EXIT_FAILURE;
}
}
cprintf(BLUE, "Parent Process Created [ Tid: %ld ]\n", (long)gettid());
cprintf(RESET, "Number of Receivers: %d\n", numReceivers);
cprintf(RESET, "Start TCP Port: %hu\n", startTCPPort);
cprintf(RESET, "Callback Enable: %d\n", withCallback);
/** - Catch signal SIGINT to close files and call destructors properly */
struct sigaction sa;
sa.sa_flags = 0; // no flags
sa.sa_handler = sigInterruptHandler; // handler function
sigemptyset(&sa.sa_mask); // dont block additional signals during invocation
// of handler
if (sigaction(SIGINT, &sa, nullptr) == -1) {
cprintf(RED, "Could not set handler function for SIGINT\n");
}
/** - Ignore SIG_PIPE, prevents global signal handler, handle locally,
instead of a server crashing due to client crash when writing, it just
gives error */
struct sigaction asa;
asa.sa_flags = 0; // no flags
asa.sa_handler = SIG_IGN; // handler function
sigemptyset(&asa.sa_mask); // dont block additional signals during
// invocation of handler
if (sigaction(SIGPIPE, &asa, nullptr) == -1) {
cprintf(RED, "Could not set handler function for SIGPIPE\n");
}
/** - loop over number of receivers */
for (int i = 0; i < numReceivers; ++i) {
/** - loop over receivers */
for (int i = 0; i < m.numReceivers; ++i) {
/** - fork process to create child process */
pid_t pid = fork();
@ -246,86 +174,89 @@ int main(int argc, char *argv[]) {
/** - if fork failed, raise SIGINT and properly destroy all child
* processes */
if (pid < 0) {
cprintf(RED, "fork() failed. Killing all the receiver objects\n");
LOG(sls::logERROR)
<< "fork() failed. Killing all the receiver objects";
raise(SIGINT);
}
/** - if child process */
else if (pid == 0) {
cprintf(BLUE, "Child process %d [ Tid: %ld ]\n", i, (long)gettid());
LOG(sls::logINFOBLUE)
<< "Child process " << i << " [ Tid: " << gettid() << ']';
std::unique_ptr<sls::Receiver> receiver = nullptr;
try {
receiver = sls::make_unique<sls::Receiver>(startTCPPort + i);
uint16_t port = m.port + i;
sls::Receiver receiver(port);
/** - register callbacks. remember to set file write enable
* to 0 (using the client) if we should not write files and you
* will write data using the callbacks */
if (m.callbackEnabled) {
/** - Call back for start acquisition */
LOG(sls::logINFOBLUE) << "Registering StartAcq()";
receiver.registerCallBackStartAcquisition(StartAcq,
nullptr);
/** - Call back for acquisition finished */
LOG(sls::logINFOBLUE)
<< "Registering AcquisitionFinished()";
receiver.registerCallBackAcquisitionFinished(
AcquisitionFinished, nullptr);
/* - Call back for raw data */
LOG(sls::logINFOBLUE) << "Registering GetData()";
receiver.registerCallBackRawDataReady(GetData, nullptr);
}
/** - as long as no Ctrl+C */
// each child process gets a copy of the semaphore
sem_wait(&semaphore);
sem_destroy(&semaphore);
LOG(sls::logINFOBLUE)
<< "Exiting Child Process [ Tid: " << gettid() << ']';
exit(EXIT_SUCCESS);
} catch (...) {
sem_destroy(&semaphore);
LOG(sls::logINFOBLUE)
<< "Exiting Child Process [ Tid: " << gettid() << " ]";
throw;
exit(EXIT_FAILURE);
}
/** - register callbacks. remember to set file write enable to 0
* (using the client) if we should not write files and you will
* write data using the callbacks */
if (withCallback) {
/** - Call back for start acquisition */
cprintf(BLUE, "Registering StartAcq()\n");
receiver->registerCallBackStartAcquisition(StartAcq, nullptr);
/** - Call back for acquisition finished */
cprintf(BLUE, "Registering AcquisitionFinished()\n");
receiver->registerCallBackAcquisitionFinished(
AcquisitionFinished, nullptr);
/* - Call back for raw data */
cprintf(BLUE, "Registering GetData() \n");
receiver->registerCallBackRawDataReady(GetData, nullptr);
}
/** - as long as no Ctrl+C */
sem_wait(&semaphore);
sem_destroy(&semaphore);
cprintf(BLUE, "Exiting Child Process [ Tid: %ld ]\n",
(long)gettid());
exit(EXIT_SUCCESS);
break;
}
}
/** - Parent process ignores SIGINT (exits only when all child process
* exits) */
sa.sa_flags = 0; // no flags
sa.sa_handler = SIG_IGN; // handler function
sigemptyset(&sa.sa_mask); // dont block additional signals during invocation
// of handler
if (sigaction(SIGINT, &sa, nullptr) == -1) {
cprintf(RED, "Could not set handler function for SIGINT\n");
}
/** - Parent process ignores SIGINT and waits for all the child processes to
* handle the signal */
sls::setupSignalHandler(SIGINT, SIG_IGN);
/** - Print Ready and Instructions how to exit */
std::cout << "Ready ... \n";
cprintf(RESET, "\n[ Press \'Ctrl+c\' to exit ]\n");
LOG(sls::logINFO) << "\n[ Press \'Ctrl+c\' to exit ]";
/** - Parent process waits for all child processes to exit */
for (;;) {
pid_t childPid = waitpid(-1, nullptr, 0);
int status;
pid_t childPid = waitpid(-1, &status, 0);
// no child closed
if (childPid == -1) {
if (errno == ECHILD) {
cprintf(GREEN, "All Child Processes have been closed\n");
LOG(sls::logINFOGREEN)
<< "All Child Processes have been closed";
break;
} else {
cprintf(RED, "Unexpected error from waitpid(): (%s)\n",
strerror(errno));
LOG(sls::logERROR)
<< "Unexpected error from waitpid(): " << strerror(errno);
break;
}
}
// child closed
cprintf(BLUE, "Exiting Child Process [ Tid: %ld ]\n",
(long int)childPid);
if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
std::cerr << "Child " << childPid << " failed\n";
kill(0, SIGINT); // signal other children to exit
}
}
std::cout << "Goodbye!\n";
return 0;
return EXIT_SUCCESS;
}

View File

@ -27,106 +27,14 @@ namespace sls {
Receiver::~Receiver() = default;
Receiver::Receiver(int argc, char *argv[]) : tcpipInterface(nullptr) {
// options
uint16_t tcpip_port_no = 1954;
uid_t userid = -1;
// parse command line for config
static struct option long_options[] = {
// These options set a flag.
//{"verbose", no_argument, &verbose_flag, 1},
// These options dont set a flag. We distinguish them by their indices.
{"rx_tcpport", required_argument, nullptr,
't'}, // TODO change or backward compatible to "port, p"?
{"uid", required_argument, nullptr, 'u'},
{"version", no_argument, nullptr, 'v'},
{"help", no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0}};
// initialize global optind variable (required when instantiating multiple
// receivers in the same process)
optind = 1;
// getopt_long stores the option index here.
int option_index = 0;
int c = 0;
std::string help_message =
"\nUsage: " + std::string(argv[0]) + " [arguments]\n" +
"Possible arguments are:\n" +
"\t-t, --rx_tcpport <port> : TCP Communication Port with "
"client. Non-zero and 16 bit.\n" +
"\t-u, --uid <user id> : Set effective user id if receiver "
"\n" +
"\t started with privileges. \n\n";
while (c != -1) {
c = getopt_long(argc, argv, "hvf:t:u:", long_options, &option_index);
// Detect the end of the options.
if (c == -1)
break;
switch (c) {
case 't':
try {
tcpip_port_no = sls::StringTo<uint16_t>(optarg);
validatePortNumber(tcpip_port_no);
} catch (...) {
throw RuntimeError("Could not scan TCP port number." +
help_message);
}
break;
case 'u':
if (sscanf(optarg, "%u", &userid) != 1) {
throw RuntimeError("Could not scan uid" + help_message);
}
break;
case 'v':
std::cout << "SLS Receiver Version: " << APIRECEIVER << std::endl;
LOG(logINFOBLUE) << "Exiting [ Tid: " << gettid() << " ]";
exit(EXIT_SUCCESS);
case 'h':
std::cout << help_message << std::endl;
exit(EXIT_SUCCESS);
default:
throw RuntimeError(help_message);
}
Receiver::Receiver(uint16_t port) {
validatePortNumber(port);
#ifdef SLS_USE_TESTS
if (port == 65535) {
throw sls::RuntimeError("Throwing for testing purposes. ");
}
// set effective id if provided
if (userid != static_cast<uid_t>(-1)) {
if (geteuid() == userid) {
LOG(logINFO) << "Process already has the same Effective UID "
<< userid;
} else {
if (seteuid(userid) != 0) {
std::ostringstream oss;
oss << "Could not set Effective UID to " << userid;
throw RuntimeError(oss.str());
}
if (geteuid() != userid) {
std::ostringstream oss;
oss << "Could not set Effective UID to " << userid << ". Got "
<< geteuid();
throw RuntimeError(oss.str());
}
LOG(logINFO) << "Process Effective UID changed to " << userid;
}
}
// might throw an exception
tcpipInterface = make_unique<ClientInterface>(tcpip_port_no);
}
Receiver::Receiver(uint16_t tcpip_port_no) {
// might throw an exception
tcpipInterface = make_unique<ClientInterface>(tcpip_port_no);
#endif
tcpipInterface = make_unique<ClientInterface>(port);
}
std::string Receiver::getReceiverVersion() {

View File

@ -1,9 +1,12 @@
// SPDX-License-Identifier: LGPL-3.0-or-other
// Copyright (C) 2021 Contributors to the SLS Detector Package
/* slsReceiver */
#include "CommandLineOptions.h"
#include "sls/Receiver.h"
#include "sls/ToString.h"
#include "sls/container_utils.h"
#include "sls/logger.h"
#include "sls/network_utils.h"
#include "sls/sls_detector_defs.h"
#include <csignal> //SIGINT
@ -18,44 +21,49 @@
sem_t semaphore;
void sigInterruptHandler(int p) { sem_post(&semaphore); }
/**
* Control+C Interrupt Handler
* to let all the other process know to exit properly
*/
void sigInterruptHandler(int signal) {
(void)signal; // suppress unused warning if needed
sem_post(&semaphore);
}
int main(int argc, char *argv[]) {
CommandLineOptions cli(AppType::SingleReceiver);
ParsedOptions opts;
try {
opts = cli.parse(argc, argv);
} catch (sls::RuntimeError &e) {
return EXIT_FAILURE;
}
auto &o = std::get<CommonOptions>(opts);
if (o.versionRequested || o.helpRequested) {
return EXIT_SUCCESS;
}
LOG(sls::logINFOBLUE) << "Current Process [ Tid: " << gettid() << " ]";
// close files on ctrl+c
sls::setupSignalHandler(SIGINT, sigInterruptHandler);
// handle locally on socket crash
sls::setupSignalHandler(SIGPIPE, SIG_IGN);
sem_init(&semaphore, 1, 0);
LOG(sls::logINFOBLUE) << "Created [ Tid: " << gettid() << " ]";
// Catch signal SIGINT to close files and call destructors properly
struct sigaction sa;
sa.sa_flags = 0; // no flags
sa.sa_handler = sigInterruptHandler; // handler function
sigemptyset(&sa.sa_mask); // dont block additional signals during invocation
// of handler
if (sigaction(SIGINT, &sa, nullptr) == -1) {
LOG(sls::logERROR) << "Could not set handler function for SIGINT";
}
// if socket crash, ignores SISPIPE, prevents global signal handler
// subsequent read/write to socket gives error - must handle locally
struct sigaction asa;
asa.sa_flags = 0; // no flags
asa.sa_handler = SIG_IGN; // handler function
sigemptyset(&asa.sa_mask); // dont block additional signals during
// invocation of handler
if (sigaction(SIGPIPE, &asa, nullptr) == -1) {
LOG(sls::logERROR) << "Could not set handler function for SIGPIPE";
}
try {
sls::Receiver r(argc, argv);
sls::Receiver r(o.port);
LOG(sls::logINFO) << "[ Press \'Ctrl+c\' to exit ]";
sem_wait(&semaphore);
sem_destroy(&semaphore);
} catch (...) {
// pass
sem_destroy(&semaphore);
LOG(sls::logINFOBLUE) << "Exiting [ Tid: " << gettid() << " ]";
throw;
}
LOG(sls::logINFOBLUE) << "Exiting [ Tid: " << gettid() << " ]";
LOG(sls::logINFO) << "Exiting Receiver";
return 0;
return EXIT_SUCCESS;
}

View File

@ -4,6 +4,7 @@ target_sources(tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/test-GeneralData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test-CircularFifo.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test-ArrangeDataBasedOnBitList.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test-Apps.cpp
)
target_include_directories(tests PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../src>")

View File

@ -0,0 +1,328 @@
// SPDX-License-Identifier: LGPL-3.0-or-other
// Copyright (C) 2021 Contributors to the SLS Detector Package
#include "CommandLineOptions.h"
#include "catch.hpp"
#include "sls/versionAPI.h"
#include <unistd.h>
namespace sls {
template <typename T, typename U> constexpr bool is_type() {
return std::is_same_v<std::decay_t<U>, T>;
}
TEST_CASE("CommandLineOption construction", "[.rxcmdcall]") {
CommandLineOptions s(AppType::SingleReceiver);
REQUIRE(s.getTypeString() == "slsReceiver");
REQUIRE(s.getVersion() ==
std::string("slsReceiver Version: ") + APIRECEIVER);
REQUIRE_NOTHROW(s.getHelpMessage());
CommandLineOptions m(AppType::MultiReceiver);
REQUIRE(m.getTypeString() == "slsMultiReceiver");
REQUIRE(m.getVersion() ==
std::string("slsMultiReceiver Version: ") + APIRECEIVER);
REQUIRE_NOTHROW(m.getHelpMessage());
CommandLineOptions f(AppType::FrameSynchronizer);
REQUIRE(f.getTypeString() == "slsFrameSynchronizer");
REQUIRE(f.getVersion() ==
std::string("slsFrameSynchronizer Version: ") + APIRECEIVER);
REQUIRE_NOTHROW(f.getHelpMessage());
}
TEST_CASE("Validate common options", "[.rxcmdcall]") {
std::string uidStr = std::to_string(getuid());
for (auto app : {AppType::SingleReceiver, AppType::MultiReceiver,
AppType::FrameSynchronizer}) {
CommandLineOptions s(app);
REQUIRE_NOTHROW(s.parse({}));
REQUIRE_NOTHROW(s.parse({"", "-v"}));
REQUIRE_NOTHROW(s.parse({"", "-h"}));
REQUIRE_NOTHROW(s.parse({"", "-h", "gdfg"})); // ignored extra args
REQUIRE_NOTHROW(s.parse({"", "-p", "1955"}));
REQUIRE_NOTHROW(s.parse({"", "-u", uidStr}));
REQUIRE_NOTHROW(s.parse({"", "-p", "1234", "-u", uidStr}));
}
}
TEST_CASE("Validate specific options", "[.rxcmdcall]") {
std::string uidStr = std::to_string(getuid());
CommandLineOptions s(AppType::SingleReceiver);
REQUIRE_NOTHROW(s.parse({"", "-t", "1955"}));
REQUIRE_THROWS(s.parse({"", "-c"}));
REQUIRE_THROWS(s.parse({"", "-n", "2"}));
REQUIRE_THROWS(s.parse({"", "-m", "2"}));
for (auto app : {AppType::MultiReceiver, AppType::FrameSynchronizer}) {
CommandLineOptions m(app);
REQUIRE_NOTHROW(m.parse({"", "-c"}));
REQUIRE_NOTHROW(m.parse({"", "-n", "2"}));
REQUIRE_NOTHROW(
m.parse({"", "-p", "1234", "-u", uidStr, "-c", "-n", "2"}));
REQUIRE_THROWS(m.parse({"", "-t", "1955"}));
REQUIRE_THROWS(m.parse({"", "-m", "2"}));
}
}
TEST_CASE("Parse version and help", "[.rxcmdcall]") {
for (auto app : {AppType::SingleReceiver, AppType::MultiReceiver,
AppType::FrameSynchronizer}) {
CommandLineOptions s(app);
auto opts = s.parse({});
std::visit(
[](const auto &o) {
REQUIRE(o.versionRequested == false); // default
REQUIRE(o.helpRequested == false); // default
},
opts);
opts = s.parse({"", "-v"});
std::visit(
[](const auto &o) {
REQUIRE(o.versionRequested == true);
REQUIRE(o.helpRequested == false);
},
opts);
opts = s.parse({"", "-h"});
std::visit(
[](const auto &o) {
REQUIRE(o.versionRequested == false);
REQUIRE(o.helpRequested == true);
},
opts);
opts = s.parse({"", "-h", "-v"});
std::visit(
[](const auto &o) {
REQUIRE(o.versionRequested == false); // exits after help
REQUIRE(o.helpRequested == true);
},
opts);
opts = s.parse({"", "-v", "-h"});
std::visit(
[](const auto &o) {
REQUIRE(o.helpRequested == false); // exits after version
REQUIRE(o.versionRequested == true);
},
opts);
opts = s.parse({"", "-v", "-h", "sdfsf"}); // ignores extra args
std::visit(
[](const auto &o) {
REQUIRE(o.helpRequested == false); // exits after version
REQUIRE(o.versionRequested == true);
},
opts);
}
}
TEST_CASE("Parse port and uid", "[.rxcmdcall]") {
uid_t uid = getuid();
std::string uidStr = std::to_string(uid);
uid_t invalidUid = uid + 1000;
std::string invalidUidStr = std::to_string(invalidUid);
for (auto app : {AppType::SingleReceiver, AppType::MultiReceiver,
AppType::FrameSynchronizer}) {
CommandLineOptions s(app);
REQUIRE_THROWS(
s.parse({"", "-p", "1234", "-u", invalidUidStr})); // invalid uid
REQUIRE_THROWS(s.parse({"", "-p", "500"})); // invalid port
auto opts = s.parse({"", "-p", "1234", "-u", uidStr});
std::visit(
[&](const auto &o) {
REQUIRE(o.port == 1234);
REQUIRE(o.userid == uid);
},
opts);
opts = s.parse({"", "-p", "5678"});
std::visit(
[](const auto &o) {
REQUIRE(o.port == 5678);
REQUIRE(o.userid == static_cast<uid_t>(-1)); // default
},
opts);
opts = s.parse({});
std::visit(
[](const auto &o) {
REQUIRE(o.port == 1954); // default
REQUIRE(o.userid == static_cast<uid_t>(-1)); // default
},
opts);
}
}
TEST_CASE("Parse num receivers and opt arg (Specific opt)", "[.rxcmdcall]") {
for (auto app : {AppType::MultiReceiver, AppType::FrameSynchronizer}) {
CommandLineOptions s(app);
REQUIRE_THROWS(s.parse({"", "-n", "0"})); // invalid number of receivers
REQUIRE_THROWS(s.parse({"", "-n", "1001"})); // exceeds max receivers
REQUIRE_NOTHROW(s.parse({"", "-n", "10"})); // valid
auto opts = s.parse({""});
std::visit(
[](const auto &o) {
using T = decltype(o);
if constexpr (is_type<MultiReceiverOptions, T>()) {
REQUIRE(o.numReceivers == 1); // default
REQUIRE(o.callbackEnabled == false);
} else if constexpr (is_type<FrameSyncOptions, T>()) {
REQUIRE(o.numReceivers == 1); // default
REQUIRE(o.printHeaders == false);
}
},
opts);
opts = s.parse({"", "-n", "5"});
std::visit(
[](const auto &o) {
using T = decltype(o);
if constexpr (is_type<MultiReceiverOptions, T>()) {
REQUIRE(o.numReceivers == 5);
REQUIRE(o.callbackEnabled == false); // default
} else if constexpr (is_type<FrameSyncOptions, T>()) {
REQUIRE(o.numReceivers == 5);
REQUIRE(o.printHeaders == false); // default
}
},
opts);
opts = s.parse({"", "-c", "-n", "3"});
std::visit(
[](const auto &o) {
using T = decltype(o);
if constexpr (is_type<MultiReceiverOptions, T>()) {
REQUIRE(o.numReceivers == 3);
REQUIRE(o.callbackEnabled == true);
} else if constexpr (is_type<FrameSyncOptions, T>()) {
REQUIRE(o.numReceivers == 3);
REQUIRE(o.printHeaders == true);
}
},
opts);
}
}
TEST_CASE("Parse deprecated options", "[.rxcmdcall]") {
for (auto app : {AppType::SingleReceiver, AppType::MultiReceiver,
AppType::FrameSynchronizer}) {
CommandLineOptions s(app);
// argc 3 or 4, invalid
REQUIRE_THROWS(s.parse({"", "1954"}));
REQUIRE_THROWS(s.parse({
"",
"1954",
}));
// argc 3 or 4
if (app == AppType::SingleReceiver) {
REQUIRE_THROWS(
s.parse({"", "1954", "1"})); // deprecated unsupported
} else {
REQUIRE_THROWS(s.parse({"", "1954", "1", "1", "-p",
"1954"})); // mix deprecated and current
REQUIRE_THROWS(
s.parse({"", "1954", "1", "-c"})); // mix deprecated and current
REQUIRE_THROWS(s.parse(
{"", "1954", "1", "-n", "34"})); // mix deprecated and current
REQUIRE_THROWS(s.parse({"", "110", "1954"})); // mix order
REQUIRE_THROWS(s.parse({"", "1023", "10"})); // privileged port
REQUIRE_THROWS(s.parse({"", "2000", "0"})); // invalid num receivers
REQUIRE_THROWS(
s.parse({"", "2000", "1001"})); // invalid num receivers
REQUIRE_THROWS(s.parse({"", "1954", "1", "2"})); // invalid 3rd opt
REQUIRE_NOTHROW(s.parse({""}));
REQUIRE_NOTHROW(s.parse({"", "1954", "1"}));
REQUIRE_NOTHROW(s.parse({"", "1954", "1", "0"}));
REQUIRE_NOTHROW(s.parse({"", "1954", "1", "1"}));
// default
auto opts = s.parse({""});
std::visit(
[](const auto &o) {
using T = decltype(o);
if constexpr (is_type<MultiReceiverOptions, T>()) {
REQUIRE(o.port == 1954);
REQUIRE(o.numReceivers == 1);
REQUIRE(o.callbackEnabled == false);
} else if constexpr (is_type<FrameSyncOptions, T>()) {
REQUIRE(o.port == 1954);
REQUIRE(o.numReceivers == 1);
REQUIRE(o.printHeaders == false); // default
}
},
opts);
opts = s.parse({"", "1958", "10"});
std::visit(
[](const auto &o) {
using T = decltype(o);
if constexpr (is_type<MultiReceiverOptions, T>()) {
REQUIRE(o.port == 1958);
REQUIRE(o.numReceivers == 10);
REQUIRE(o.callbackEnabled == false); // default
} else if constexpr (is_type<FrameSyncOptions, T>()) {
REQUIRE(o.port == 1958);
REQUIRE(o.numReceivers == 10);
REQUIRE(o.printHeaders == false); // default
}
},
opts);
opts = s.parse({"", "1958", "10", "1"});
std::visit(
[](const auto &o) {
using T = decltype(o);
if constexpr (is_type<MultiReceiverOptions, T>()) {
REQUIRE(o.port == 1958);
REQUIRE(o.numReceivers == 10);
REQUIRE(o.callbackEnabled == true); // default
} else if constexpr (is_type<FrameSyncOptions, T>()) {
REQUIRE(o.port == 1958);
REQUIRE(o.numReceivers == 10);
REQUIRE(o.printHeaders == true); // default
}
},
opts);
}
}
// test function directly
// nargs can be 1, 3 or 4
REQUIRE_THROWS(CommandLineOptions::ParseDeprecated({"", ""}));
REQUIRE_THROWS(CommandLineOptions::ParseDeprecated({"", "", "", "", ""}));
// default
auto [p, n, o] = CommandLineOptions::ParseDeprecated({""});
REQUIRE(p == 1954);
REQUIRE(n == 1);
REQUIRE(o == false);
std::tie(p, n, o) = CommandLineOptions::ParseDeprecated({"", "1955", "6"});
REQUIRE(p == 1955);
REQUIRE(n == 6);
REQUIRE(o == false);
std::tie(p, n, o) =
CommandLineOptions::ParseDeprecated({"", "1955", "6", "1"});
REQUIRE(p == 1955);
REQUIRE(n == 6);
REQUIRE(o == true);
}
} // namespace sls

View File

@ -90,4 +90,5 @@ MacAddr InterfaceNameToMac(const std::string &inf);
IpAddr InterfaceNameToIp(const std::string &ifn);
void validatePortNumber(uint16_t port);
void validatePortRange(uint16_t startPort, int numPorts);
void setupSignalHandler(int signal, void (*handler)(int));
} // namespace sls

View File

@ -1,11 +1,12 @@
// SPDX-License-Identifier: LGPL-3.0-or-other
// Copyright (C) 2021 Contributors to the SLS Detector Package
#include "sls/network_utils.h"
#include "sls/sls_detector_exceptions.h"
#include "sls/network_utils.h"
#include <algorithm>
#include <arpa/inet.h>
#include <cassert>
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <ifaddrs.h>
@ -205,17 +206,27 @@ MacAddr InterfaceNameToMac(const std::string &inf) {
}
void validatePortNumber(uint16_t port) {
// random local port. might work if internal = bad practise
if (port == 0) {
throw RuntimeError("Invalid port number. Must be between 1 - 65535.");
if (port < 1024 || port > std::numeric_limits<uint16_t>::max()) {
throw RuntimeError(std::string("Invalid port number ") +
std::to_string(port) +
". Must be between 1024 - 65535.");
}
}
void validatePortRange(uint16_t startPort, int numPorts) {
validatePortNumber(startPort);
if ((startPort + numPorts) > std::numeric_limits<uint16_t>::max()) {
throw RuntimeError("Invalid port range. Must be between 1 - 65535.");
}
validatePortNumber(startPort + numPorts - 1);
}
void setupSignalHandler(int signal, void (*handler)(int)) {
// Catch signal SIGINT to close files and call destructors properly
struct sigaction sa {};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask); // dont block additional signals
sa.sa_flags = 0;
if (sigaction(signal, &sa, nullptr) == -1) {
throw RuntimeError("Could not set handler for " +
std::string(strsignal(signal)));
}
}
} // namespace sls