From 22b6f0275210445b57b392c19b392c37f91f6ab2 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 14:03:28 +0200 Subject: [PATCH] get rid of argp dependency --- CMakeLists.txt | 12 -- README.md | 2 +- config.h.cmake | 3 - configure.ac | 8 - contrib/alpine/APKBUILD | 2 +- src/ebusd/CMakeLists.txt | 2 +- src/ebusd/datahandler.cpp | 18 +- src/ebusd/datahandler.h | 8 +- src/ebusd/knxhandler.cpp | 31 ++-- src/ebusd/knxhandler.h | 7 +- src/ebusd/main.cpp | 157 ++++++++-------- src/ebusd/mqtthandler.cpp | 56 +++--- src/ebusd/mqtthandler.h | 7 +- src/lib/utils/CMakeLists.txt | 1 + src/lib/utils/arg.cpp | 346 +++++++++++++++++++++++++++++++++++ src/lib/utils/arg.h | 97 ++++++++++ src/tools/CMakeLists.txt | 8 +- src/tools/ebusctl.cpp | 69 +++---- src/tools/ebusfeed.cpp | 94 +++++----- src/tools/ebuspicloader.cpp | 116 ++++++------ 20 files changed, 740 insertions(+), 304 deletions(-) create mode 100755 src/lib/utils/arg.cpp create mode 100755 src/lib/utils/arg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab42dca8..ac2a07cb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,18 +74,6 @@ check_function_exists(ppoll HAVE_PPOLL) check_include_file(linux/serial.h HAVE_LINUX_SERIAL -DHAVE_LINUX_SERIAL=1) check_include_file(dev/usb/uftdiio.h HAVE_FREEBSD_UFTDI -DHAVE_FREEBSD_UFTDI=1) -check_function_exists(argp_parse HAVE_ARGP_H) -if(NOT HAVE_ARGP_H) - if(NOT ARGP_HINTS) - set(ARGP_HINTS ~/argp-standalone/src) - endif(NOT ARGP_HINTS) - find_library(LIB_ARGP NAMES argp argp-standalone HINTS ARGP_HINTS) - if (NOT LIB_ARGP) - message(FATAL_ERROR "argp library not available") - endif(NOT LIB_ARGP) - set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${LIB_ARGP}") -endif(NOT HAVE_ARGP_H) - option(coverage "enable code coverage tracking." OFF) if(NOT coverage STREQUAL OFF) add_definitions(-g -O0 --coverage -Wall) diff --git a/README.md b/README.md index 15a602bc..a3523045 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Building ebusd from the source requires the following packages and/or features: * g++ with C++11 support (>=4.8.1) * make * kernel with pselect or ppoll support - * glibc with argp support or argp-standalone + * glibc with getopt_long support * libmosquitto-dev for MQTT support * libssl-dev for SSL support diff --git a/config.h.cmake b/config.h.cmake index f73c5a54..cdcadefe 100755 --- a/config.h.cmake +++ b/config.h.cmake @@ -40,9 +40,6 @@ /* Defined if syslog.h is available. */ #cmakedefine HAVE_SYSLOG_H -/* Defined if argp.h is available. */ -#cmakedefine HAVE_ARGP_H - /* The name of package. */ #cmakedefine PACKAGE "${PACKAGE_NAME}" diff --git a/configure.ac b/configure.ac index c3fbd666..df0602af 100755 --- a/configure.ac +++ b/configure.ac @@ -48,14 +48,6 @@ if test "x$with_contrib" != "xno"; then fi AC_ARG_WITH(ebusfeed, AS_HELP_STRING([--with-ebusfeed], [enable inclusion of ebusfeed tool]), [with_ebusfeed=yes], []) AM_CONDITIONAL([WITH_EBUSFEED], [test "x$with_ebusfeed" == "xyes"]) -AC_ARG_WITH(argp-lib, AS_HELP_STRING([--with-argp-lib=PATH], [path to argp libraries]), [LDFLAGS+="-L$with_argp_lib"]) -AC_ARG_WITH(argp-include, AS_HELP_STRING([--with-argp-include=PATH], [path to argp includes]), [CXXFLAGS+="-I$with_argp_include"]) -AC_CHECK_FUNC([argp_parse], [have_argp=yes], AC_CHECK_LIB([argp], [argp_parse], [have_argp=yes; LIBS="-largp $LIBS"], [have_argp=no])) -if test "x$have_argp" = "xyes"; then - AC_CHECK_HEADER([argp.h], AC_DEFINE([HAVE_ARGP_H], [1], [Defined if argp.h is available.]), AC_MSG_ERROR([argp.h not found])) -else - AC_MSG_ERROR([argp library not found, specify argp-standalone location in --with-argp-lib= and --with-argp-include= options.]) -fi AC_ARG_WITH(mqtt, AS_HELP_STRING([--without-mqtt], [disable support for MQTT handling]), [], [with_mqtt=yes]) if test "x$with_mqtt" != "xno"; then AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 4a50d8c7..63a24986 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -8,7 +8,7 @@ url="https://github.com/john30/ebusd" # Upstream only supports these archs. arch="x86 x86_64 aarch64 armhf armv7" license="GPL-3.0-only" -makedepends="argp-standalone cmake mosquitto-dev openssl-dev" +makedepends="cmake mosquitto-dev openssl-dev" source="$pkgname-$pkgver.tar.gz::https://github.com/john30/${pkgname}/archive/refs/tags/${pkgver}.tar.gz" build() { diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index 7166bd08..85b41e30 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -33,7 +33,7 @@ include_directories(../lib/ebus) include_directories(../lib/utils) add_executable(ebusd ${ebusd_SOURCES}) -target_link_libraries(ebusd utils ebus pthread rt ${LIB_ARGP} ${ebusd_LIBS}) +target_link_libraries(ebusd utils ebus pthread rt ${ebusd_LIBS}) install(TARGETS ebusd EXPORT ebusd DESTINATION usr/bin) diff --git a/src/ebusd/datahandler.cpp b/src/ebusd/datahandler.cpp index fc18ebec..37fa33bf 100755 --- a/src/ebusd/datahandler.cpp +++ b/src/ebusd/datahandler.cpp @@ -30,11 +30,11 @@ namespace ebusd { -/** the final @a argp_child structure. */ -static const struct argp_child g_last_argp_child = {nullptr, 0, nullptr, 0}; +/** the final @a argParseChildOpt structure. */ +static const argParseChildOpt g_last_arg_child = {nullptr, nullptr}; -/** the list of @a argp_child structures. */ -static struct argp_child g_argp_children[ +/** the list of @a argParseChildOpt structures. */ +static argParseChildOpt g_arg_children[ 1 #ifdef HAVE_MQTT +1 @@ -44,17 +44,17 @@ static struct argp_child g_argp_children[ #endif ]; -const struct argp_child* datahandler_getargs() { +const argParseChildOpt* datahandler_getargs() { size_t count = 0; #ifdef HAVE_MQTT - g_argp_children[count++] = *mqtthandler_getargs(); + g_arg_children[count++] = *mqtthandler_getargs(); #endif #ifdef HAVE_KNX - g_argp_children[count++] = *knxhandler_getargs(); + g_arg_children[count++] = *knxhandler_getargs(); #endif if (count > 0) { - g_argp_children[count] = g_last_argp_child; - return g_argp_children; + g_arg_children[count] = g_last_arg_child; + return g_arg_children; } return nullptr; } diff --git a/src/ebusd/datahandler.h b/src/ebusd/datahandler.h index 84a8b507..c3c03a75 100755 --- a/src/ebusd/datahandler.h +++ b/src/ebusd/datahandler.h @@ -19,12 +19,12 @@ #ifndef EBUSD_DATAHANDLER_H_ #define EBUSD_DATAHANDLER_H_ -#include #include #include #include #include "ebusd/bushandler.h" #include "lib/ebus/message.h" +#include "lib/utils/arg.h" namespace ebusd { @@ -49,10 +49,10 @@ enum scanStatus_t { /** - * Helper function for getting the argp definition for all known @a DataHandler instances. - * @return a pointer to the argp_child structure, or nullptr. + * Helper function for getting the arg definition for all known @a DataHandler instances. + * @return a pointer to the child argument options, or nullptr. */ -const struct argp_child* datahandler_getargs(); +const argParseChildOpt* datahandler_getargs(); /** * Registration function that is called once during initialization. diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index a8746bce..2f7b5284 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -52,9 +52,9 @@ using std::dec; #define O_VAR (O_INT-1) /** the definition of the KNX arguments. */ -static const struct argp_option g_knx_argp_options[] = { +static const argDef g_knx_argDefs[] = { {nullptr, 0, nullptr, 0, "KNX options:", 1 }, - {"knxurl", O_URL, "URL", OPTION_ARG_OPTIONAL, "URL to open (i.e. \"[multicast][@interface]\" for KNXnet/IP" + {"knxurl", O_URL, "URL", af_optional, "URL to open (i.e. \"[multicast][@interface]\" for KNXnet/IP" #ifdef HAVE_KNXD " or \"ip:host[:port]\" / \"local:/socketpath\" for knxd" #endif @@ -79,11 +79,11 @@ static vector* g_integrationVars = nullptr; //!< the integration settin /** * The KNX argument parsing function. - * @param key the key from @a g_knx_argp_options. + * @param key the key from @a g_knx_arg_options. * @param arg the option argument, or nullptr. * @param state the parsing state. */ -static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { +static int knx_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { result_t result; unsigned int value; switch (key) { @@ -94,12 +94,12 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { case O_AGR: // --knxrage=5 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid knxrage value"); + argParseError(parseOpt, "invalid knxrage value"); return EINVAL; } value = parseInt(arg, 10, 0, 99999999, &result); if (result != RESULT_OK) { - argp_error(state, "invalid knxrage"); + argParseError(parseOpt, "invalid knxrage"); return EINVAL; } g_maxReadAge = value; @@ -107,12 +107,12 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { case O_AGW: // --knxwage=5 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid knxwage value"); + argParseError(parseOpt, "invalid knxwage value"); return EINVAL; } value = parseInt(arg, 10, 0, 99999999, &result); if (result != RESULT_OK) { - argp_error(state, "invalid knxwage"); + argParseError(parseOpt, "invalid knxwage"); return EINVAL; } g_maxWriteAge = value; @@ -120,7 +120,7 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { case O_INT: // --knxint=/etc/ebusd/knx.cfg if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid knxint file"); + argParseError(parseOpt, "invalid knxint file"); return EINVAL; } g_integrationFile = arg; @@ -129,7 +129,7 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { case O_VAR: // --knxvar=NAME=VALUE[,NAME=VALUE]* { if (arg == nullptr || arg[0] == 0 || !strchr(arg, '=')) { - argp_error(state, "invalid knxvar"); + argParseError(parseOpt, "invalid knxvar"); return EINVAL; } if (!g_integrationVars) { @@ -143,18 +143,15 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { } default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } return 0; } -static const struct argp g_knx_argp = { g_knx_argp_options, knx_parse_opt, nullptr, nullptr, nullptr, nullptr, - nullptr }; -static const struct argp_child g_knx_argp_child = {&g_knx_argp, 0, "", 1}; +static const argParseChildOpt g_knx_arg_child = { g_knx_argDefs, knx_parse_opt }; - -const struct argp_child* knxhandler_getargs() { - return &g_knx_argp_child; +const argParseChildOpt* knxhandler_getargs() { + return &g_knx_arg_child; } bool knxhandler_register(UserInfo* userInfo, BusHandler* busHandler, MessageMap* messages, diff --git a/src/ebusd/knxhandler.h b/src/ebusd/knxhandler.h index c2ad961d..fcfcebc6 100644 --- a/src/ebusd/knxhandler.h +++ b/src/ebusd/knxhandler.h @@ -29,6 +29,7 @@ #include "lib/ebus/message.h" #include "lib/ebus/stringhelper.h" #include "lib/knx/knx.h" +#include "lib/utils/arg.h" namespace ebusd { @@ -41,10 +42,10 @@ using std::string; using std::vector; /** - * Helper function for getting the argp definition for KNX. - * @return a pointer to the argp_child structure. + * Helper function for getting the arg definition for KNX. + * @return a pointer to the child argument options, or nullptr. */ -const struct argp_child* knxhandler_getargs(); +const argParseChildOpt* knxhandler_getargs(); /** * Registration function that is called once during initialization. diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 1cfcdd48..3790a69a 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -23,7 +23,6 @@ #include "ebusd/main.h" #include #include -#include #include #include #include @@ -32,17 +31,11 @@ #include #include "ebusd/mainloop.h" #include "ebusd/network.h" +#include "lib/utils/arg.h" #include "lib/utils/log.h" #include "lib/utils/httpclient.h" #include "ebusd/scan.h" - -/** the version string of the program. */ -const char *argp_program_version = "" PACKAGE_STRING "." REVISION ""; - -/** the report bugs to address of the program. */ -const char *argp_program_bug_address = "" PACKAGE_BUGREPORT ""; - namespace ebusd { using std::dec; @@ -146,10 +139,6 @@ static string s_configPath = CONFIG_PATH; /** whether scanConfig or configPath were set by arguments. */ static bool s_scanConfigOrPathSet = false; -/** the documentation of the program. */ -static const char argpdoc[] = - "A daemon for communication with eBUS heating systems."; - #define O_INISND -2 #define O_DEVLAT (O_INISND-1) #define O_SCNRET (O_DEVLAT-1) @@ -187,7 +176,7 @@ static const char argpdoc[] = #define O_DMPFLU (O_DMPSIZ-1) /** the definition of the known program arguments. */ -static const struct argp_option argpoptions[] = { +static const argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Device options:", 1 }, {"device", 'd', "DEV", 0, "Use DEV as eBUS device (" "prefix \"ens:\" for enhanced high speed device or " @@ -203,7 +192,7 @@ static const struct argp_option argpoptions[] = { {nullptr, 0, nullptr, 0, "Message configuration options:", 2 }, {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" CONFIG_PATH "]", 0 }, - {"scanconfig", 's', "ADDR", OPTION_ARG_OPTIONAL, "Pick CSV config files matching initial scan ADDR: " + {"scanconfig", 's', "ADDR", af_optional, "Pick CSV config files matching initial scan ADDR: " "empty for broadcast ident message (default when configpath is not given), " "\"none\" for no initial scan message, " "\"full\" for full scan, " @@ -215,11 +204,11 @@ static const struct argp_option argpoptions[] = { {"configlang", O_CFGLNG, "LANG", 0, "Prefer LANG in multilingual configuration files [system default language]", 0 }, {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop", 0 }, - {"dumpconfig", O_DMPCFG, "FORMAT", OPTION_ARG_OPTIONAL, + {"dumpconfig", O_DMPCFG, "FORMAT", af_optional, "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop", 0 }, {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE", 0 }, {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]", 0 }, - {"inject", 'i', "stop", OPTION_ARG_OPTIONAL, "Inject remaining arguments as already seen messages (e.g. " + {"inject", 'i', "stop", af_optional, "Inject remaining arguments as already seen messages (e.g. " "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards", 0 }, #ifdef HAVE_SSL {"cafile", O_CAFILE, "FILE", 0, "Use CA FILE for checking certificates (uses defaults," @@ -261,7 +250,7 @@ static const struct argp_option argpoptions[] = { " [notice]", 0 }, {nullptr, 0, nullptr, 0, "Raw logging options:", 6 }, - {"lograwdata", O_RAW, "bytes", OPTION_ARG_OPTIONAL, + {"lograwdata", O_RAW, "bytes", af_optional, "Log messages or all received/sent bytes on the bus", 0 }, {"lograwdatafile", O_RAWFIL, "FILE", 0, "Write raw log to FILE [" PACKAGE_LOGFILE "]", 0 }, {"lograwdatasize", O_RAWSIZ, "SIZE", 0, "Make raw log file no larger than SIZE kB [100]", 0 }, @@ -277,12 +266,12 @@ static const struct argp_option argpoptions[] = { /** * The program argument parsing function. - * @param key the key from @a argpoptions. + * @param key the key from @a argDefs. * @param arg the option argument, or nullptr. - * @param state the parsing state. + * @param parseOpt the parse options. */ -error_t parse_opt(int key, char *arg, struct argp_state *state) { - struct options *opt = (struct options*)state->input; +static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { + struct options *opt = (struct options*)parseOpt->userArg; result_t result = RESULT_OK; unsigned int value; @@ -290,7 +279,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { // Device options: case 'd': // --device=/dev/ttyUSB0 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid device"); + argParseError(parseOpt, "invalid device"); return EINVAL; } opt->device = arg; @@ -307,7 +296,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_DEVLAT: // --latency=10 value = parseInt(arg, 10, 0, 200000, &result); // backwards compatible (micros) if (result != RESULT_OK || (value <= 1000 && value > 200)) { // backwards compatible (micros) - argp_error(state, "invalid latency"); + argParseError(parseOpt, "invalid latency"); return EINVAL; } opt->extraLatency = value > 1000 ? value/1000 : value; // backwards compatible (micros) @@ -316,7 +305,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { // Message configuration options: case 'c': // --configpath=https://cfg.ebusd.eu/ if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid configpath"); + argParseError(parseOpt, "invalid configpath"); return EINVAL; } s_configPath = arg; @@ -336,7 +325,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } else { auto address = (symbol_t)parseInt(arg, 16, 0x00, 0xff, &result); if (result != RESULT_OK || !isValidAddress(address)) { - argp_error(state, "invalid initial scan address"); + argParseError(parseOpt, "invalid initial scan address"); return EINVAL; } if (isMaster(address)) { @@ -353,7 +342,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_SCNRET: // --scanretries=10 value = parseInt(arg, 10, 0, 100, &result); if (result != RESULT_OK) { - argp_error(state, "invalid scanretries"); + argParseError(parseOpt, "invalid scanretries"); return EINVAL; } opt->scanRetries = value; @@ -371,14 +360,14 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } else if (strcmp("json", arg) == 0) { opt->dumpConfig = OF_DEFINITION | OF_NAMES | OF_UNITS | OF_COMMENTS | OF_VALUENAME | OF_ALL_ATTRS | OF_JSON; } else { - argp_error(state, "invalid dumpconfig"); + argParseError(parseOpt, "invalid dumpconfig"); return EINVAL; } opt->checkConfig = true; break; case O_DMPCTO: // --dumpconfigto=FILE if (!arg || arg[0] == 0) { - argp_error(state, "invalid dumpconfigto"); + argParseError(parseOpt, "invalid dumpconfigto"); return EINVAL; } opt->dumpConfigTo = arg; @@ -386,7 +375,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_POLINT: // --pollinterval=5 value = parseInt(arg, 10, 0, 3600, &result); if (result != RESULT_OK) { - argp_error(state, "invalid pollinterval"); + argParseError(parseOpt, "invalid pollinterval"); return EINVAL; } opt->pollInterval = value; @@ -407,7 +396,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { { auto address = (symbol_t)parseInt(arg, 16, 0, 0xff, &result); if (result != RESULT_OK || !isMaster(address)) { - argp_error(state, "invalid address"); + argParseError(parseOpt, "invalid address"); return EINVAL; } opt->address = address; @@ -419,7 +408,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_ACQTIM: // --acquiretimeout=10 value = parseInt(arg, 10, 1, 100000, &result); // backwards compatible (micros) if (result != RESULT_OK || (value <= 1000 && value > 100)) { // backwards compatible (micros) - argp_error(state, "invalid acquiretimeout"); + argParseError(parseOpt, "invalid acquiretimeout"); return EINVAL; } opt->acquireTimeout = value > 1000 ? value/1000 : value; // backwards compatible (micros) @@ -427,7 +416,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_ACQRET: // --acquireretries=3 value = parseInt(arg, 10, 0, 10, &result); if (result != RESULT_OK) { - argp_error(state, "invalid acquireretries"); + argParseError(parseOpt, "invalid acquireretries"); return EINVAL; } opt->acquireRetries = value; @@ -435,7 +424,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_SNDRET: // --sendretries=2 value = parseInt(arg, 10, 0, 10, &result); if (result != RESULT_OK) { - argp_error(state, "invalid sendretries"); + argParseError(parseOpt, "invalid sendretries"); return EINVAL; } opt->sendRetries = value; @@ -443,7 +432,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_RCVTIM: // --receivetimeout=25 value = parseInt(arg, 10, 1, 100000, &result); // backwards compatible (micros) if (result != RESULT_OK || (value <= 1000 && value > 100)) { // backwards compatible (micros) - argp_error(state, "invalid receivetimeout"); + argParseError(parseOpt, "invalid receivetimeout"); return EINVAL; } opt->receiveTimeout = value > 1000 ? value/1000 : value; // backwards compatible (micros) @@ -451,7 +440,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_MASCNT: // --numbermasters=0 value = parseInt(arg, 10, 0, 25, &result); if (result != RESULT_OK) { - argp_error(state, "invalid numbermasters"); + argParseError(parseOpt, "invalid numbermasters"); return EINVAL; } opt->masterCount = value; @@ -463,14 +452,14 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { // Daemon options: case O_ACLDEF: // --accesslevel=* if (arg == nullptr) { - argp_error(state, "invalid accesslevel"); + argParseError(parseOpt, "invalid accesslevel"); return EINVAL; } opt->accessLevel = arg; break; case O_ACLFIL: // --aclfile=/etc/ebusd/acl if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid aclfile"); + argParseError(parseOpt, "invalid aclfile"); return EINVAL; } opt->aclFile = arg; @@ -486,7 +475,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case O_PIDFIL: // --pidfile=/var/run/ebusd.pid if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid pidfile"); + argParseError(parseOpt, "invalid pidfile"); return EINVAL; } opt->pidFile = arg; @@ -494,7 +483,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 'p': // --port=8888 value = parseInt(arg, 10, 1, 65535, &result); if (result != RESULT_OK) { - argp_error(state, "invalid port"); + argParseError(parseOpt, "invalid port"); return EINVAL; } opt->port = (uint16_t)value; @@ -505,21 +494,21 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_HTTPPT: // --httpport=0 value = parseInt(arg, 10, 1, 65535, &result); if (result != RESULT_OK) { - argp_error(state, "invalid httpport"); + argParseError(parseOpt, "invalid httpport"); return EINVAL; } opt->httpPort = (uint16_t)value; break; case O_HTMLPA: // --htmlpath=/var/ebusd/html if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid htmlpath"); + argParseError(parseOpt, "invalid htmlpath"); return EINVAL; } opt->htmlPath = arg; break; case O_UPDCHK: // --updatecheck=on if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid updatecheck"); + argParseError(parseOpt, "invalid updatecheck"); return EINVAL; } if (strcmp("on", arg) == 0) { @@ -527,7 +516,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } else if (strcmp("off", arg) == 0) { opt->updateCheck = false; } else { - argp_error(state, "invalid updatecheck"); + argParseError(parseOpt, "invalid updatecheck"); return EINVAL; } break; @@ -535,7 +524,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { // Log options: case 'l': // --logfile=/var/log/ebusd.log if (arg == nullptr || strcmp("/", arg) == 0) { - argp_error(state, "invalid logfile"); + argParseError(parseOpt, "invalid logfile"); return EINVAL; } opt->logFile = arg; @@ -546,23 +535,23 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { if (pos == nullptr) { pos = strchr(arg, ' '); if (pos == nullptr) { - argp_error(state, "invalid log"); + argParseError(parseOpt, "invalid log"); return EINVAL; } } *pos = 0; int facilities = parseLogFacilities(arg); if (facilities == -1) { - argp_error(state, "invalid log: areas"); + argParseError(parseOpt, "invalid log: areas"); return EINVAL; } LogLevel level = parseLogLevel(pos + 1); if (level == ll_COUNT) { - argp_error(state, "invalid log: level"); + argParseError(parseOpt, "invalid log: level"); return EINVAL; } if (opt->logAreas != -1 || opt->logLevel != ll_COUNT) { - argp_error(state, "invalid log (combined with logareas or loglevel)"); + argParseError(parseOpt, "invalid log (combined with logareas or loglevel)"); return EINVAL; } setFacilitiesLogLevel(facilities, level); @@ -573,11 +562,11 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { { int facilities = parseLogFacilities(arg); if (facilities == -1) { - argp_error(state, "invalid logareas"); + argParseError(parseOpt, "invalid logareas"); return EINVAL; } if (opt->multiLog) { - argp_error(state, "invalid logareas (combined with log)"); + argParseError(parseOpt, "invalid logareas (combined with log)"); return EINVAL; } opt->logAreas = facilities; @@ -587,11 +576,11 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { { LogLevel logLevel = parseLogLevel(arg); if (logLevel == ll_COUNT) { - argp_error(state, "invalid loglevel"); + argParseError(parseOpt, "invalid loglevel"); return EINVAL; } if (opt->multiLog) { - argp_error(state, "invalid loglevel (combined with log)"); + argParseError(parseOpt, "invalid loglevel (combined with log)"); return EINVAL; } opt->logLevel = logLevel; @@ -604,7 +593,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case O_RAWFIL: // --lograwdatafile=/var/log/ebusd.log if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid lograwdatafile"); + argParseError(parseOpt, "invalid lograwdatafile"); return EINVAL; } opt->logRawFile = arg; @@ -612,7 +601,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_RAWSIZ: // --lograwdatasize=100 value = parseInt(arg, 10, 1, 1000000, &result); if (result != RESULT_OK) { - argp_error(state, "invalid lograwdatasize"); + argParseError(parseOpt, "invalid lograwdatasize"); return EINVAL; } opt->logRawSize = value; @@ -625,7 +614,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case O_DMPFIL: // --dumpfile=/tmp/ebusd_dump.bin if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid dumpfile"); + argParseError(parseOpt, "invalid dumpfile"); return EINVAL; } opt->dumpFile = arg; @@ -633,7 +622,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_DMPSIZ: // --dumpsize=100 value = parseInt(arg, 10, 1, 1000000, &result); if (result != RESULT_OK) { - argp_error(state, "invalid dumpsize"); + argParseError(parseOpt, "invalid dumpsize"); return EINVAL; } opt->dumpSize = value; @@ -642,33 +631,27 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { opt->dumpFlush = true; break; - case ARGP_KEY_ARG: - if (opt->injectMessages || (opt->checkConfig && opt->scanConfig)) { - return ARGP_ERR_UNKNOWN; - } - argp_error(state, "invalid arguments starting with \"%s\"", arg); - return EINVAL; default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } - // check for invalid arg combinations if (opt->readOnly && (opt->answer || opt->generateSyn || opt->initialSend || (opt->scanConfig && opt->initialScan != ESC))) { - argp_error(state, "cannot combine readonly with answer/generatesyn/initsend/scanconfig"); + argParseError(parseOpt, "cannot combine readonly with answer/generatesyn/initsend/scanconfig"); return EINVAL; } if (opt->scanConfig && opt->pollInterval == 0) { - argp_error(state, "scanconfig without polling may lead to invalid files included for certain products!"); + argParseError(parseOpt, "scanconfig without polling may lead to invalid files included for certain products!"); return EINVAL; } if (opt->injectMessages && (opt->checkConfig || opt->dumpConfig)) { - argp_error(state, "cannot combine inject with checkconfig/dumpconfig"); + argParseError(parseOpt, "cannot combine inject with checkconfig/dumpconfig"); return EINVAL; } return 0; } + void shutdown(bool error = false); void daemonize() { @@ -823,8 +806,17 @@ void signalHandler(int sig) { * @return the exit code. */ int main(int argc, char* argv[], char* envp[]) { - struct argp aargp = { argpoptions, parse_opt, nullptr, argpdoc, datahandler_getargs(), nullptr, nullptr }; - setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); + const argParseOpt parseOpt = { + argDefs, + parse_opt, + 0, + "" PACKAGE_NAME, + "[INJECT...]", + "A daemon for communication with eBUS heating systems.", + "Report bugs to " PACKAGE_BUGREPORT " .", + datahandler_getargs(), + &s_opt + }; char envname[32] = "--"; // needs to cover at least max length of any option name plus "--" char* envopt = envname+2; @@ -849,7 +841,7 @@ int main(int argc, char* argv[], char* envp[]) { // ignore those defined in Dockerfile, EBUSD_OPTS, those with final args, and interactive ones continue; } - char* envargv[] = {envname, pos+1}; + char* envargv[] = {argv[0], envname, pos+1}; int cnt = pos[1] ? 2 : 1; if (pos[1] && strlen(*env) < sizeof(envname)-3 && (strcmp(envopt, "scanconfig") == 0 || strcmp(envopt, "lograwdata") == 0)) { @@ -858,8 +850,7 @@ int main(int argc, char* argv[], char* envp[]) { strcat(envopt, pos); } int idx = -1; - s_opt.injectMessages = true; // for skipping unknown values via ARGP_ERR_UNKNOWN - error_t err = argp_parse(&aargp, cnt, envargv, ARGP_PARSE_ARGV0|ARGP_SILENT|ARGP_IN_ORDER, &idx, &s_opt); + int err = argParse(&parseOpt, 1+cnt, envargv, &idx); if (err != 0 && idx == -1) { // ignore args for non-arg boolean options if (err == ESRCH) { // special value to abort immediately logWrite(lf_main, ll_error, "invalid argument in env: %s", *env); // force logging on exit @@ -867,13 +858,27 @@ int main(int argc, char* argv[], char* envp[]) { } logWrite(lf_main, ll_error, "invalid/unknown argument in env (ignored): %s", *env); // force logging } - s_opt.injectMessages = false; // restore (was not parsed from cmdline args yet) } int arg_index = -1; - if (argp_parse(&aargp, argc, argv, ARGP_IN_ORDER, &arg_index, &s_opt) != 0) { - logWrite(lf_main, ll_error, "invalid arguments"); // force logging on exit - return EINVAL; + switch (argParse(&parseOpt, argc, argv, &arg_index)) { + case 0: // OK + break; + case '?': // help printed + return 0; + case 'V': + printf("" PACKAGE_STRING "." REVISION "\n"); + return 0; + default: + logWrite(lf_main, ll_error, "invalid arguments"); // force logging on exit + return EINVAL; + } + + if (arg_index >= 0) { + if (!s_opt.injectMessages && !(s_opt.checkConfig && s_opt.scanConfig)) { + fprintf(stderr, "invalid arguments starting with \"%s\"", argv[arg_index]); + return EINVAL; + } } if (s_opt.logAreas != -1 || s_opt.logLevel != ll_COUNT) { diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index f508f322..02014c5d 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -56,7 +56,7 @@ using std::dec; #define O_VERB (O_INSE+1) /** the definition of the MQTT arguments. */ -static const struct argp_option g_mqtt_argp_options[] = { +static const argDef g_mqtt_argDefs[] = { {nullptr, 0, nullptr, 0, "MQTT options:", 1 }, {"mqtthost", O_HOST, "HOST", 0, "Connect to MQTT broker on HOST [localhost]", 0 }, {"mqttport", O_PORT, "PORT", 0, "Connect to MQTT broker on PORT (usually 1883), 0 to disable [0]", 0 }, @@ -72,7 +72,7 @@ static const struct argp_option g_mqtt_argp_options[] = { {"mqttqos", O_PQOS, "QOS", 0, "Set the QoS value for all topics (0-2) [0]", 0 }, {"mqttint", O_INTF, "FILE", 0, "Read MQTT integration settings from FILE (no default)", 0 }, {"mqttvar", O_IVAR, "NAME=VALUE[,...]", 0, "Add variable(s) to the read MQTT integration settings", 0 }, - {"mqttjson", O_JSON, "short", OPTION_ARG_OPTIONAL, + {"mqttjson", O_JSON, "short", af_optional, "Publish in JSON format instead of strings, optionally in short (value directly below field key)", 0 }, {"mqttverbose", O_VERB, nullptr, 0, "Publish all available attributes", 0 }, #if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001) @@ -144,17 +144,17 @@ void splitFields(const string& str, vector* row); /** * The MQTT argument parsing function. - * @param key the key from @a g_mqtt_argp_options. + * @param key the key from @a g_mqtt_argDefs. * @param arg the option argument, or nullptr. * @param state the parsing state. */ -static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { +static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { result_t result = RESULT_OK; unsigned int value; switch (key) { case O_HOST: // --mqtthost=localhost if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqtthost"); + argParseError(parseOpt, "invalid mqtthost"); return EINVAL; } g_host = arg; @@ -163,7 +163,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_PORT: // --mqttport=1883 value = parseInt(arg, 10, 1, 65535, &result); if (result != RESULT_OK) { - argp_error(state, "invalid mqttport"); + argParseError(parseOpt, "invalid mqttport"); return EINVAL; } g_port = (uint16_t)value; @@ -171,7 +171,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_CLID: // --mqttclientid=clientid if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqttclientid"); + argParseError(parseOpt, "invalid mqttclientid"); return EINVAL; } g_clientId = arg; @@ -179,7 +179,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_USER: // --mqttuser=username if (arg == nullptr) { - argp_error(state, "invalid mqttuser"); + argParseError(parseOpt, "invalid mqttuser"); return EINVAL; } g_username = arg; @@ -187,7 +187,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_PASS: // --mqttpass=password if (arg == nullptr) { - argp_error(state, "invalid mqttpass"); + argParseError(parseOpt, "invalid mqttpass"); return EINVAL; } g_password = replaceSecret(arg); @@ -196,21 +196,21 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_TOPI: // --mqtttopic=ebusd { if (arg == nullptr || arg[0] == 0 || arg[0] == '/' || strchr(arg, '+') || arg[strlen(arg)-1] == '/') { - argp_error(state, "invalid mqtttopic"); + argParseError(parseOpt, "invalid mqtttopic"); return EINVAL; } char *pos = strchr(arg, '#'); if (pos && (pos == arg || pos[1])) { // allow # only at very last position (to indicate not using any default) - argp_error(state, "invalid mqtttopic"); + argParseError(parseOpt, "invalid mqtttopic"); return EINVAL; } if (g_topic) { - argp_error(state, "duplicate mqtttopic"); + argParseError(parseOpt, "duplicate mqtttopic"); return EINVAL; } StringReplacer replacer; if (!replacer.parse(arg, true)) { - argp_error(state, "malformed mqtttopic"); + argParseError(parseOpt, "malformed mqtttopic"); return ESRCH; // abort in any case due to the above potentially being destructive } g_topic = arg; @@ -219,7 +219,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_GTOP: // --mqttglobal=global/ if (arg == nullptr || strchr(arg, '+') || strchr(arg, '#')) { - argp_error(state, "invalid mqttglobal"); + argParseError(parseOpt, "invalid mqttglobal"); return EINVAL; } g_globalTopic = arg; @@ -232,7 +232,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_PQOS: // --mqttqos=0 value = parseInt(arg, 10, 0, 2, &result); if (result != RESULT_OK) { - argp_error(state, "invalid mqttqos value"); + argParseError(parseOpt, "invalid mqttqos value"); return EINVAL; } g_qos = static_cast(value); @@ -240,7 +240,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_INTF: // --mqttint=/etc/ebusd/mqttint.cfg if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid mqttint file"); + argParseError(parseOpt, "invalid mqttint file"); return EINVAL; } g_integrationFile = arg; @@ -248,7 +248,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_IVAR: // --mqttvar=NAME=VALUE[,NAME=VALUE]* if (arg == nullptr || arg[0] == 0 || !strchr(arg, '=')) { - argp_error(state, "invalid mqttvar"); + argParseError(parseOpt, "invalid mqttvar"); return EINVAL; } if (!g_integrationVars) { @@ -277,7 +277,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { #if (LIBMOSQUITTO_VERSION_NUMBER >= 1004001) case O_VERS: // --mqttversion=3.1.1 if (arg == nullptr || arg[0] == 0 || (strcmp(arg, "3.1") != 0 && strcmp(arg, "3.1.1") != 0)) { - argp_error(state, "invalid mqttversion"); + argParseError(parseOpt, "invalid mqttversion"); return EINVAL; } g_version = strcmp(arg, "3.1.1") == 0 ? MQTT_PROTOCOL_V311 : MQTT_PROTOCOL_V31; @@ -295,7 +295,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { #if (LIBMOSQUITTO_MAJOR >= 1) case O_CAFI: // --mqttca=file or --mqttca=dir/ if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqttca"); + argParseError(parseOpt, "invalid mqttca"); return EINVAL; } if (arg[strlen(arg)-1] == '/') { @@ -309,7 +309,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_CERT: // --mqttcert=CERTFILE if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqttcert"); + argParseError(parseOpt, "invalid mqttcert"); return EINVAL; } g_certfile = arg; @@ -317,7 +317,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_KEYF: // --mqttkey=KEYFILE if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqttkey"); + argParseError(parseOpt, "invalid mqttkey"); return EINVAL; } g_keyfile = arg; @@ -325,7 +325,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_KEPA: // --mqttkeypass=PASSWORD if (arg == nullptr) { - argp_error(state, "invalid mqttkeypass"); + argParseError(parseOpt, "invalid mqttkeypass"); return EINVAL; } g_keypass = replaceSecret(arg); @@ -336,18 +336,18 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { #endif default: - return ARGP_ERR_UNKNOWN; + return EINVAL; } return 0; } -static const struct argp g_mqtt_argp = { g_mqtt_argp_options, mqtt_parse_opt, nullptr, nullptr, nullptr, nullptr, - nullptr }; -static const struct argp_child g_mqtt_argp_child = {&g_mqtt_argp, 0, "", 1}; +static const argParseChildOpt g_mqtt_arg_child = { + g_mqtt_argDefs, mqtt_parse_opt +}; -const struct argp_child* mqtthandler_getargs() { - return &g_mqtt_argp_child; +const argParseChildOpt* mqtthandler_getargs() { + return &g_mqtt_arg_child; } bool check(int code, const char* method) { diff --git a/src/ebusd/mqtthandler.h b/src/ebusd/mqtthandler.h index 94301233..acef8dab 100644 --- a/src/ebusd/mqtthandler.h +++ b/src/ebusd/mqtthandler.h @@ -29,6 +29,7 @@ #include "ebusd/bushandler.h" #include "lib/ebus/message.h" #include "lib/ebus/stringhelper.h" +#include "lib/utils/arg.h" namespace ebusd { @@ -42,10 +43,10 @@ using std::string; using std::vector; /** - * Helper function for getting the argp definition for MQTT. - * @return a pointer to the argp_child structure. + * Helper function for getting the arg definition for MQTT. + * @return a pointer to the child argument options, or nullptr. */ -const struct argp_child* mqtthandler_getargs(); +const argParseChildOpt* mqtthandler_getargs(); /** * Registration function that is called once during initialization. diff --git a/src/lib/utils/CMakeLists.txt b/src/lib/utils/CMakeLists.txt index 319b5d46..5fece5f1 100755 --- a/src/lib/utils/CMakeLists.txt +++ b/src/lib/utils/CMakeLists.txt @@ -1,6 +1,7 @@ add_definitions(-Wconversion) set(libutils_a_SOURCES + arg.h arg.cpp log.h log.cpp tcpsocket.h tcpsocket.cpp thread.h thread.cpp diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp new file mode 100755 index 00000000..f6ae7712 --- /dev/null +++ b/src/lib/utils/arg.cpp @@ -0,0 +1,346 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "lib/utils/arg.h" + +#include +#include +#include +#include +#include + +namespace ebusd { + +#define isAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) + +void calcCounts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount) { + for (const argDef *arg = argDefs; arg && arg->help; arg++) { + if (!arg->name) { + continue; + } + count++; + if (!isAlpha(arg->key)) { + continue; + } + shortCharsCount++; + shortOptsCount++; + if (arg->valueName) { + shortOptsCount++; + if (arg->flags & af_optional) { + shortOptsCount++; + } + } + } +} + +void buildOpts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount, struct option *longOpts, char *shortChars, int *shortIndexes, char *shortOpts, int argDefIdx) { + struct option *opt = longOpts+count; + for (const argDef *arg = argDefs; arg && arg->help; arg++, argDefIdx++) { + if (!arg->name) { + continue; + } + opt->name = arg->name; + opt->has_arg = arg->valueName ? ((arg->flags & af_optional) ? optional_argument : required_argument) : no_argument; + opt->flag = nullptr; + opt->val = argDefIdx; + if (isAlpha(arg->key)) { + shortChars[shortCharsCount] = (char)arg->key; + shortIndexes[shortCharsCount++] = count; + shortOpts[shortOptsCount++] = (char)arg->key; + if (arg->valueName) { + shortOpts[shortOptsCount++] = ':'; + if (arg->flags & af_optional) { + shortOpts[shortOptsCount++] = ':'; + } + } + } + opt++; + count++; + } +} + +static const argDef endArgDef = {nullptr, 0, nullptr, 0, nullptr, 0 }; +static const argDef helpArgDef = {"help", '?', nullptr, 0, "Give this help list", 0 }; +static const argDef helpArgDefs[] = { + helpArgDef, + endArgDef +}; +static const argDef versionArgDef = {"version", 'V', nullptr, 0, "Print program version", 0 }; +static const argDef versionArgDefs[] = { + versionArgDef, + endArgDef +}; + +int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) { + int count = 0, shortCharsCount = 0, shortOptsCount = 0; + if (!(parseOpt->flags & af_noHelp)) { + calcCounts(helpArgDefs, count, shortCharsCount, shortOptsCount); + } + if (!(parseOpt->flags & af_noVersion)) { + calcCounts(versionArgDefs, count, shortCharsCount, shortOptsCount); + } + calcCounts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount); + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + calcCounts(child->argDefs, count, shortCharsCount, shortOptsCount); + } + struct option *longOpts = (struct option*)calloc(count+1, sizeof(struct option)); // room for EOF + char *shortChars = (char*)calloc(shortCharsCount+1, sizeof(char)); // room for \0 + int *shortIndexes = (int*)calloc(shortCharsCount, sizeof(int)); + char *shortOpts = (char*)calloc(2+shortOptsCount+1, sizeof(char)); // room for +, :, and \0 + count = 0; + shortCharsCount = 0; + shortOptsCount = 0; + shortOpts[shortOptsCount++] = '+'; // posix mode to stop at first non-option + shortOpts[shortOptsCount++] = ':'; // return ':' for missing option + if (!(parseOpt->flags & af_noHelp)) { + buildOpts(helpArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff00); + } + if (!(parseOpt->flags & af_noVersion)) { + buildOpts(versionArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff01); + } + buildOpts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0); + int children = 0; + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + buildOpts(child->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0x100*(++children)); + } + optind = 1; // setting to 0 does not work + int c = 0, longIdx = -1, ret = 0; + while ((c = getopt_long(argc, argv, shortOpts, longOpts, &longIdx)) != -1) { + if (c == '?') { + // unknown option or help + if (optopt != '?') { + ret = '!'; + fprintf(stderr, "invalid argument %s\n", argv[optind - 1]); + } else { + ret = c; + } + break; + } + if (c == ':') { + // missing option + fprintf(stderr, "missing argument to %s\n", argv[optind - 1]); + ret = c; + break; + } + if (isAlpha(c)) { + // short name + int idx = (int)(strchr(shortChars, c) - shortChars); + if (idx >= 0 && idx < shortCharsCount) { + longIdx = shortIndexes[idx]; + } else { + longIdx = -1; + } + } else if (c >= 0 && longIdx < 0) { + longIdx = c; + } + if (longIdx < 0 || longIdx >= count) { + ret = '!'; // error + break; + } + int val = longOpts[longIdx].val; + if (val == 0xff00) { // help + ret = '?'; + break; + } + if (val == 0xff01) { // version + ret = 'V'; + break; + } + const argDef *argDefs; + parse_function_t parser; + if (val & 0xff00) { + const argParseChildOpt *child = parseOpt->childOpts + ((val>>8)-1); + argDefs = child->argDefs; + parser = child->parser; + } else { + argDefs = parseOpt->argDefs; + parser = parseOpt->parser; + } + const argDef *arg = argDefs + (val & 0xff); + c = parser(arg->key, optarg, parseOpt); + if (c != 0) { + ret = c; + break; + } + } + if (ret == '?') { + argHelp(parseOpt); + } else if (argIndex && optind < argc) { + *argIndex = optind; + } + free(longOpts); + free(shortChars); + free(shortOpts); + return ret; +} + +#define MIN_INDENT 18 +#define MAX_INDENT 29 +#define MAX_BREAK 79 + +void wrap(const char* str, size_t pos, size_t indent) { + const char* end = strchr(str, 0); + const char* eol = strchr(str, '\n'); + char buf[MAX_BREAK + 1]; + bool first = true; + while (*str && str < end) { + if (!first) { + if (indent) { + printf("%*c", (int)indent, ' '); + } + pos = indent; + } + // start from max position backwards to find a break char + size_t cnt = MAX_BREAK - pos; + if (eol && eol < str) { + eol = strchr(str, '\n'); + } + if (eol && eol < str + cnt) { + // EOL is before latest possible break + cnt = eol - str; + } else if (end < str + cnt) { + cnt = end - str; + } + for (; cnt > 0; cnt--) { + char ch = str[cnt]; + if (ch == ' ' || ch == '\n' || ch == 0) { + // break found + buf[0] = 0; + strncat(buf, str, cnt); + printf("%s\n", buf); + str += cnt; + if (*str) { + str++; + } + break; // restart + } + } + if (cnt == 0 && *str) { + // final + printf("%s\n", str); + break; + } + first = false; + } +} + +size_t calcIndent(const argDef *argDefs) { + size_t indent = 0; + for (const argDef *arg = argDefs; arg && arg->help; arg++) { + if (!arg->name) { + continue; + } + // e.g. " -d, --device=DEV Use DEV..." + size_t length = 2 + 3 + 3 + strlen(arg->name) + 2; + if (arg->valueName) { + length += 1 + strlen(arg->valueName); + if (arg->flags & af_optional) { + length += 2; + } + } + if (length > indent) { + indent = length; + if (indent > MAX_INDENT) { + return indent; + } + } + } + return indent; +} + +void printArgs(const argDef *argDefs, size_t indent) { + for (const argDef *arg = argDefs; arg && arg->help; arg++) { + if (!arg->name) { + if (*arg->help) { + printf("\n %s\n", arg->help); + } else { + printf("\n"); + } + continue; + } + printf(" "); + if (isAlpha(arg->key) || arg->key == '?') { + printf("-%c,", arg->key); + } else { + printf(" "); + } + printf(" --%s", arg->name); + size_t taken = 2 + 3 + 3 + strlen(arg->name); + if (arg->valueName) { + taken += 1 + strlen(arg->valueName); + if (arg->flags & af_optional) { + printf("[=%s]", arg->valueName); + taken += 2; + } else { + printf("=%s", arg->valueName); + } + } + if (taken > indent) { + printf(" "); + wrap(arg->help, taken+1, indent); + } else { + printf("%*c", (int)(indent - taken), ' '); + wrap(arg->help, indent, indent); + } + } +} + +void argHelp(const argParseOpt *parseOpt) { + size_t indent = calcIndent(parseOpt->argDefs); + if (indent < MAX_INDENT) { + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + size_t childIndent = calcIndent(child->argDefs); + if (childIndent > indent) { + indent = childIndent; + if (indent > MAX_INDENT) { + break; + } + } + } + } + if (indent > MAX_INDENT) { + indent = MAX_INDENT; + } else if (indent < MIN_INDENT) { + indent = MIN_INDENT; + } + printf("Usage: %s [OPTION...] %s\n", + parseOpt->name, + parseOpt->positional ? parseOpt->positional : "" + ); + wrap(parseOpt->help, 0, 0); + printArgs(parseOpt->argDefs, indent); + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + printArgs(child->argDefs, indent); + } + if (!(parseOpt->flags & (af_noHelp|af_noVersion))) { + printf("\n"); + if (!(parseOpt->flags & af_noHelp)) { + printArgs(helpArgDefs, indent); + } + if (!(parseOpt->flags & af_noVersion)) { + printArgs(versionArgDefs, indent); + } + } + if (parseOpt->suffix) { + printf("\n"); + wrap(parseOpt->suffix, 0, 0); + } + fflush(stdout); +} + +} // namespace ebusd diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h new file mode 100755 index 00000000..f8abb3d9 --- /dev/null +++ b/src/lib/utils/arg.h @@ -0,0 +1,97 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIB_UTILS_ARGS_H_ +#define LIB_UTILS_ARGS_H_ + +namespace ebusd { + +/** \file lib/utils/args.h */ + +/** the available arg flags. */ +enum ArgFlag { + af_optional = 1<<0, //!< optional argument value + af_noHelp = 1<<1, //!< do not include -?/--help option + af_noVersion = 1<<2, //!< do not include -V/--version option +}; + +/** Definition of a single argument. */ +typedef struct argDef { + const char* name; //!< the (long) name of the argument, or nullptr for a group header + int key; //!< the argument key, also used as short name if alphabetic or the question mark + const char* valueName; //!< the optional argument value name + int flags; //!< flags for the argument, bit combination of @a ArgFlag + const char* help; //!< help text (mandatory) + int unused; //!< currently unused (kept for compatibility to argp) +} argDef; + +struct argParseOpt; + +/** + * Function to be called for each argument. + * @param key the argument key as defined. + * @param arg the argument value, or nullptr. + * @param parseOpt ppointer to the @a argParseOpt structure. + * @return 0 on success, non-zero otherwise. + */ +typedef int (*parse_function_t)(int key, char *arg, const struct argParseOpt *parseOpt); + +/** Options for child definitions. */ +typedef struct argParseChildOpt { + const argDef *argDefs; //!< pointer to the argument defintions (last one needs to have nullptr help as end sign) + parse_function_t parser; //!< parse function to use +} argParseChildOpt; + +/** Options to pass to @a argParse(). */ +typedef struct argParseOpt { + const argDef *argDefs; //!< pointer to the argument defintions (last one needs to have nullptr help as end sign) + parse_function_t parser; //!< parse function to use + int flags; //!< flags for the parser, bit combination of @a ArgFlag + const char* name; //!< name of the program parsed + const char* positional; //!< help text for optional positional argument + const char* help; //!< help text for the program (second line of help output) + const char* suffix; //!< optional help suffix text + const argParseChildOpt *childOpts; //!< optional child definitions + void* userArg; //!< optional user argument +} argParseOpt; + +/** + * Parse the arguments given in @a argv. + * @param parseOpt pointer to the @a argParseOpt structure. + * @param argc the argument count (including the full program name in index 0). + * @param argv the argument values (including the full program name in index 0). + * @param argIndex optional pointer for storing the index to the first non-argument found in argv. + * @return 0 on success, '!' for an invalid argument value, ':' for a missing argument value, + * '?' when "-?" was given, or the result of the parse function if non-zero. + */ +int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex); + +/** + * Print the help text. + * @param parseOpt ppointer to the @a argParseOpt structure. + */ +void argHelp(const argParseOpt *parseOpt); + +/** + * Convenience macro to print an error message to stderr. +*/ +#define argParseError(argParseOpt, message) fprintf(stderr, "%s\n", message); + +} // namespace ebusd + +#endif // LIB_UTILS_ARGS_H_ diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 7c664e73..e35b00db 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,3 +1,5 @@ +add_definitions(-Wno-unused-parameter) + set(ebusctl_SOURCES ebusctl.cpp) set(ebuspicloader_SOURCES ebuspicloader.cpp intelhex/intelhexclass.cpp) @@ -7,13 +9,13 @@ include_directories(intelhex) add_executable(ebusctl ${ebusctl_SOURCES}) add_executable(ebuspicloader ${ebuspicloader_SOURCES}) -target_link_libraries(ebusctl utils ebus ${LIB_ARGP} ${ebusctl_LIBS}) -target_link_libraries(ebuspicloader utils ${LIB_ARGP} ${ebuspicloader_LIBS}) +target_link_libraries(ebusctl utils ebus ${ebusctl_LIBS}) +target_link_libraries(ebuspicloader utils ${ebuspicloader_LIBS}) if(WITH_EBUSFEED) set(ebusfeed_SOURCES ebusfeed.cpp) add_executable(ebusfeed ${ebusfeed_SOURCES}) - target_link_libraries(ebusfeed ebus ${LIB_ARGP} ${ebusfeed_LIBS}) + target_link_libraries(ebusfeed utils ebus ${ebusfeed_LIBS}) endif(WITH_EBUSFEED) install(TARGETS ebusctl ebuspicloader EXPORT ebusd DESTINATION usr/bin) diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index 6f581c7e..755c4bca 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -20,7 +20,6 @@ # include #endif -#include #include #ifdef HAVE_PPOLL # include @@ -30,6 +29,7 @@ #include #include #include +#include "lib/utils/arg.h" #include "lib/utils/tcpsocket.h" namespace ebusd { @@ -62,24 +62,8 @@ static struct options opt = { 0 // argCount }; -/** the version string of the program. */ -const char *argp_program_version = "ebusctl of """ PACKAGE_STRING ""; - -/** the report bugs to address of the program. */ -const char *argp_program_bug_address = "" PACKAGE_BUGREPORT ""; - -/** the documentation of the program. */ -static const char argpdoc[] = - "Client for acessing " PACKAGE " via TCP.\n" - "\v" - "If given, send COMMAND together with CMDOPT options to " PACKAGE ".\n" - "Use 'help' as COMMAND for help on available " PACKAGE " commands."; - -/** the description of the accepted arguments. */ -static char argpargsdoc[] = "\nCOMMAND [CMDOPT...]"; - /** the definition of the known program arguments. */ -static const struct argp_option argpoptions[] = { +static const argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Options:", 1 }, {"server", 's', "HOST", 0, "Connect to " PACKAGE " on HOST (name or IP) [localhost]", 0 }, {"port", 'p', "PORT", 0, "Connect to " PACKAGE " on PORT [8888]", 0 }, @@ -92,19 +76,19 @@ static const struct argp_option argpoptions[] = { /** * The program argument parsing function. - * @param key the key from @a argpoptions. + * @param key the key from @a argDefs. * @param arg the option argument, or nullptr. - * @param state the parsing state. + * @param parseOpt the parse options. */ -error_t parse_opt(int key, char *arg, struct argp_state *state) { - struct options *opt = (struct options*)state->input; +static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { + struct options *opt = (struct options*)parseOpt->userArg; char* strEnd = nullptr; unsigned int value; switch (key) { // Device settings: case 's': // --server=localhost if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid server"); + argParseError(parseOpt, "invalid server"); return EINVAL; } opt->server = arg; @@ -112,7 +96,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 'p': // --port=8888 value = strtoul(arg, &strEnd, 10); if (strEnd == nullptr || strEnd == arg || *strEnd != 0 || value < 1 || value > 65535) { - argp_error(state, "invalid port"); + argParseError(parseOpt, "invalid port"); return EINVAL; } opt->port = (uint16_t)value; @@ -120,7 +104,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 't': // --timeout=10 value = strtoul(arg, &strEnd, 10); if (strEnd == nullptr || strEnd == arg || *strEnd != 0 || value > 3600) { - argp_error(state, "invalid timeout"); + argParseError(parseOpt, "invalid timeout"); return EINVAL; } opt->timeout = (uint16_t)value; @@ -128,12 +112,8 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 'e': // --error opt->errorResponse = true; break; - case ARGP_KEY_ARGS: - opt->args = state->argv + state->next; - opt->argCount = state->argc - state->next; - break; default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } return 0; } @@ -362,11 +342,32 @@ bool connect(const char* host, uint16_t port, uint16_t timeout, char* const *arg * @return the exit code. */ int main(int argc, char* argv[]) { - struct argp argp = { argpoptions, parse_opt, argpargsdoc, argpdoc, nullptr, nullptr, nullptr }; - setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); - if (argp_parse(&argp, argc, argv, ARGP_IN_ORDER, nullptr, &opt) != 0) { - return EINVAL; + argParseOpt parseOpt = { + argDefs, + parse_opt, + af_noVersion, + "ebusctl", + "[COMMAND [CMDOPT...]]", + "Client for accessing " PACKAGE " via TCP.", + "If given, send COMMAND together with CMDOPT options to " PACKAGE ".\n" + "Use 'help' as COMMAND for help on available " PACKAGE " commands.", + nullptr, + &opt + }; + int arg_index = -1; + switch (argParse(&parseOpt, argc, argv, &arg_index)) { + case 0: // OK + break; + case '?': // help printed + return 0; + default: + return EINVAL; } + if (arg_index >= 0) { + opt.args = argv + arg_index; + opt.argCount = argc - arg_index; + } + bool success = connect(opt.server, opt.port, opt.timeout, opt.args, opt.argCount); exit(success ? EXIT_SUCCESS : EXIT_FAILURE); diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index 5939184d..40fd2ca9 100755 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -20,7 +20,6 @@ # include #endif -#include #include #include #include @@ -29,6 +28,7 @@ #include #include "lib/ebus/device.h" #include "lib/ebus/result.h" +#include "lib/utils/arg.h" namespace ebusd { @@ -58,31 +58,8 @@ static struct options opt = { "/tmp/ebus_dump.bin", // dumpFile }; -/** the version string of the program. */ -const char *argp_program_version = "ebusfeed of """ PACKAGE_STRING ""; - -/** the report bugs to address of the program. */ -const char *argp_program_bug_address = "" PACKAGE_BUGREPORT ""; - -/** the documentation of the program. */ -static const char argpdoc[] = - "Feed data from an " PACKAGE " DUMPFILE to a serial device.\n" - "\v" - "With no DUMPFILE, /tmp/ebus_dump.bin is used.\n" - "\n" - "Example for setting up two pseudo terminals with 'socat':\n" - " 1. 'socat -d -d pty,raw,echo=0 pty,raw,echo=0'\n" - " 2. create symbol links to appropriate devices, e.g.\n" - " 'ln -s /dev/pts/2 /dev/ttyUSB60'\n" - " 'ln -s /dev/pts/3 /dev/ttyUSB20'\n" - " 3. start " PACKAGE ": '" PACKAGE " -f -d /dev/ttyUSB20 --nodevicecheck'\n" - " 4. start ebusfeed: 'ebusfeed /path/to/ebus_dump.bin'\n"; - -/** the description of the accepted arguments. */ -static char argpargsdoc[] = "[DUMPFILE]"; - /** the definition of the known program arguments. */ -static const struct argp_option argpoptions[] = { +static const ebusd::argDef argDefs[] = { {"device", 'd', "DEV", 0, "Write to DEV (serial device) [/dev/ttyUSB60]", 0 }, {"time", 't', "USEC", 0, "Delay each byte by USEC us [10000]", 0 }, @@ -91,18 +68,18 @@ static const struct argp_option argpoptions[] = { /** * The program argument parsing function. - * @param key the key from @a argpoptions. + * @param key the key from @a argDefs. * @param arg the option argument, or nullptr. - * @param state the parsing state. + * @param parseOpt the parse options. */ -error_t parse_opt(int key, char *arg, struct argp_state *state) { - struct options *opt = (struct options*)state->input; +static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { + struct options *opt = (struct options*)parseOpt->userArg; char* strEnd = nullptr; switch (key) { // Device settings: case 'd': // --device=/dev/ttyUSB60 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid device"); + argParseError(parseOpt, "invalid device"); return EINVAL; } opt->device = arg; @@ -110,23 +87,12 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 't': // --time=10000 opt->time = (unsigned int)strtoul(arg, &strEnd, 10); if (strEnd == nullptr || strEnd == arg || *strEnd != 0 || opt->time < 1000 || opt->time > 100000000) { - argp_error(state, "invalid time"); + argParseError(parseOpt, "invalid time"); return EINVAL; } break; - case ARGP_KEY_ARG: - if (state->arg_num == 0) { - if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid dumpfile"); - return EINVAL; - } - opt->dumpFile = arg; - } else { - return ARGP_ERR_UNKNOWN; - } - break; default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } return 0; } @@ -139,11 +105,45 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { * @return the exit code. */ int main(int argc, char* argv[]) { - struct argp argp = { argpoptions, parse_opt, argpargsdoc, argpdoc, nullptr, nullptr, nullptr }; - setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); - if (argp_parse(&argp, argc, argv, ARGP_IN_ORDER, nullptr, &opt) != 0) { - return EINVAL; + ebusd::argParseOpt parseOpt = { + argDefs, + parse_opt, + "ebusfeed", + "[DUMPFILE]", + "Feed data from an " PACKAGE " DUMPFILE to a serial device.", + "With no DUMPFILE, /tmp/ebus_dump.bin is used.\n" + "\n" + "Example for setting up two pseudo terminals with 'socat':\n" + " 1. 'socat -d -d pty,raw,echo=0 pty,raw,echo=0'\n" + " 2. create symbol links to appropriate devices, e.g.\n" + " 'ln -s /dev/pts/2 /dev/ttyUSB60'\n" + " 'ln -s /dev/pts/3 /dev/ttyUSB20'\n" + " 3. start " PACKAGE ": '" PACKAGE " -f -d /dev/ttyUSB20 --nodevicecheck'\n" + " 4. start ebusfeed: 'ebusfeed /path/to/ebus_dump.bin'", + &opt + }; + int arg_index = -1; + switch (ebusd::argParse(&parseOpt, argc, argv, &arg_index)) { + case 0: // OK + break; + case '?': // help printed + return 0; + default: + return EINVAL; } + if (arg_index >= 0) { + if (argv[arg_index][0] == 0 || strcmp("/", argv[arg_index]) == 0) { + argParseError(parseOpt, "invalid dumpfile"); + return EINVAL; + } + if (arg_index != argc -1) { + // more than one arg + argParseError(parseOpt, "multiple dumpfile"); + return EINVAL; + } + opt.dumpFile = argv[arg_index]; + } + Device* device = Device::create(opt.device, false, false, false); if (device == nullptr) { cout << "unable to create device " << opt.device << endl; diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index a6829faa..5ce3cc72 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -37,29 +36,13 @@ #include #include #include "intelhex/intelhexclass.h" +#include "lib/utils/arg.h" #include "lib/utils/tcpsocket.h" using ebusd::socketConnect; -/** the version string of the program. */ -const char *argp_program_version = "eBUS adapter PIC firmware loader"; - -/** the documentation of the program. */ -static const char argpdoc[] = - "A tool for loading firmware to the eBUS adapter PIC and configure adjustable settings." - "\vPORT is either the serial port to use (e.g. " -#ifdef __CYGWIN__ - "/dev/ttyS0 for COM1 on Windows" -#else - "/dev/ttyUSB0" -#endif - ") that also supports a trailing wildcard '*' for testing" - " multiple ports, or a network port as \"ip:port\" for use with e.g. socat or ebusd-esp in PIC pass-through mode."; - -static const char argpargsdoc[] = "PORT"; - /** the definition of the known program arguments. */ -static const struct argp_option argpoptions[] = { +static const ebusd::argDef argDefs[] = { {nullptr, 0, nullptr, 0, "IP options:", 1 }, {"dhcp", 'd', nullptr, 0, "set dynamic IP address via DHCP (default)", 0 }, {"ip", 'i', "IP", 0, "set fix IP address (e.g. 192.168.0.10)", 0 }, @@ -82,7 +65,7 @@ static const struct argp_option argpoptions[] = { {nullptr, 0, nullptr, 0, "Tool options:", 9 }, {"verbose", 'v', nullptr, 0, "enable verbose output", 0 }, {"slow", 's', nullptr, 0, "low speed mode for transfer (115kBd instead of 921kBd)", 0 }, - {nullptr, 0, nullptr, 0, nullptr, 0 }, + {nullptr, 0, nullptr, 0, nullptr, 0 }, }; static bool verbose = false; @@ -136,7 +119,13 @@ bool parseShort(const char *arg, uint16_t minValue, uint16_t maxValue, uint16_t return true; } -error_t parse_opt(int key, char *arg, struct argp_state *state) { +/** + * The program argument parsing function. + * @param key the key from @a argDefs. + * @param arg the option argument, or nullptr. + * @param parseOpt the parse options. + */ +static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { char *ip = nullptr, *part = nullptr; int pos = 0, sum = 0; struct stat st; @@ -147,22 +136,22 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case 'd': // --dhcp if (setIp || setMask || setGateway) { - argp_error(state, "either DHCP or IP address is needed"); + argParseError(parseOpt, "either DHCP or IP address is needed"); return EINVAL; } setDhcp = true; break; case 'i': // --ip=192.168.0.10 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid IP address"); + argParseError(parseOpt, "invalid IP address"); return EINVAL; } if (setDhcp) { - argp_error(state, "either DHCP or IP address is needed"); + argParseError(parseOpt, "either DHCP or IP address is needed"); return EINVAL; } if (setIp) { - argp_error(state, "IP address was specified twice"); + argParseError(parseOpt, "IP address was specified twice"); return EINVAL; } ip = strdup(arg); @@ -177,41 +166,41 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } free(ip); if (pos != 4 || part || sum == 0) { - argp_error(state, "invalid IP address"); + argParseError(parseOpt, "invalid IP address"); return EINVAL; } setIp = true; break; case 'm': // --mask=24 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid IP mask"); + argParseError(parseOpt, "invalid IP mask"); return EINVAL; } if (setDhcp) { - argp_error(state, "either DHCP or IP address is needed"); + argParseError(parseOpt, "either DHCP or IP address is needed"); return EINVAL; } if (setMask) { - argp_error(state, "mask was specified twice"); + argParseError(parseOpt, "mask was specified twice"); return EINVAL; } if (!parseByte(arg, 1, 0x1e, &setMaskLen)) { - argp_error(state, "invalid IP mask"); + argParseError(parseOpt, "invalid IP mask"); return EINVAL; } setMask = true; break; case 'g': // --gateway=192.168.0.11 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid gateway"); + argParseError(parseOpt, "invalid gateway"); return EINVAL; } if (setDhcp) { - argp_error(state, "either DHCP or IP address is needed"); + argParseError(parseOpt, "either DHCP or IP address is needed"); return EINVAL; } if (!setIp || !setMask) { - argp_error(state, "IP and mask need to be specified before gateway"); + argParseError(parseOpt, "IP and mask need to be specified before gateway"); return EINVAL; } ip = strdup(arg); @@ -228,7 +217,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { uint8_t maskRemain = setMaskLen-pos*8; uint8_t mask = maskRemain >= 8 ? 255 : maskRemain == 0 ? 0 : (255^((1 << (8 - maskRemain)) - 1)); if ((address & mask) != (setIpAddress[pos] & mask)) { - argp_error(state, "invalid gateway (different network)"); + argParseError(parseOpt, "invalid gateway (different network)"); free(ip); return EINVAL; } @@ -237,15 +226,15 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } free(ip); if (pos != 4 || part || sum == 0 || setGatewayBits == 0) { - argp_error(state, "invalid gateway"); + argParseError(parseOpt, "invalid gateway"); return EINVAL; } if (setGatewayBits == hostBits) { - argp_error(state, "invalid gateway (same as address)"); + argParseError(parseOpt, "invalid gateway (same as address)"); return EINVAL; } if (!setGatewayBits || setGatewayBits == ((1 << (32 - setMaskLen)) - 1)) { - argp_error(state, "invalid gateway (net or broadcast address)"); + argParseError(parseOpt, "invalid gateway (net or broadcast address)"); return EINVAL; } if (setGatewayBits == 1) { // default @@ -260,7 +249,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } if (!(setGatewayBits >> 5)) { if (!(setGatewayBits & 0x1f)) { - argp_error(state, "invalid gateway (net address)"); + argParseError(parseOpt, "invalid gateway (net address)"); return EINVAL; } // fine: host part above max gateway adjustable bits is the same and remainder non-zero @@ -274,7 +263,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { setGateway = true; break; } - argp_error(state, "invalid gateway (out of possible range of first/last 31 hosts in subnet)"); + argParseError(parseOpt, "invalid gateway (out of possible range of first/last 31 hosts in subnet)"); return EINVAL; case 'M': // --macip setMacFromIp = true; @@ -286,11 +275,11 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case 'a': // --arbdel=1000 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid arbitration delay"); + argParseError(parseOpt, "invalid arbitration delay"); return EINVAL; } if (!parseShort(arg, 0, 620, &setArbitrationDelayMicros)) { - argp_error(state, "invalid arbitration delay"); + argParseError(parseOpt, "invalid arbitration delay"); return EINVAL; } setArbitrationDelay = true; @@ -305,7 +294,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case -3: // --variant=U|W|E|F|N|u|w|e|f|n if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid variant"); + argParseError(parseOpt, "invalid variant"); return EINVAL; } if (arg[0] == 'u' || arg[0] == 'U') { @@ -319,7 +308,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } else if (arg[0] == 'n' || arg[0] == 'N') { setVariantValue = 0; } else { - argp_error(state, "invalid variant"); + argParseError(parseOpt, "invalid variant"); return EINVAL; } setVariantForced = arg[0]<'a'; @@ -327,7 +316,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case 'f': // --flash=firmware.hex if (arg == nullptr || arg[0] == 0 || stat(arg, &st) != 0 || !S_ISREG(st.st_mode)) { - argp_error(state, "invalid flash file"); + argParseError(parseOpt, "invalid flash file"); return EINVAL; } flashFile = arg; @@ -339,7 +328,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { lowSpeed = true; break; default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } return 0; } @@ -1094,7 +1083,7 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { } else { std::cout << "off" << std::endl; } - std::cout << "Variant: "; // since firmware 20221206 + std::cout << "Variant: "; // since firmware 20221206 switch (configData[5]&0x03) { case 3: std::cout << "USB/RPI (high-speed)"; @@ -1156,7 +1145,7 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { if (setVariant) { configData[5] = (configData[5]&0x38) | (setVariantForced?0:0x04) | (setVariantValue&0x03); if (setVariantValue==0) { - configData[1] = (configData[1]&~0x1f); // set mask=0 to disable Ethernet + configData[1] = (configData[1]&~0x1f); // set mask=0 to disable Ethernet } } if (writeConfig(fd, 0x0000, 8, configData) != 0) { @@ -1170,13 +1159,32 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { int run(int fd); int main(int argc, char* argv[]) { - struct argp aargp = { argpoptions, parse_opt, argpargsdoc, argpdoc, nullptr, nullptr, nullptr }; + ebusd::argParseOpt parseOpt = { + argDefs, + parse_opt, + ebusd::af_noVersion, + "ebuspicloader", + "[PORT]", + "A tool for loading firmware to the eBUS adapter PIC and configure adjustable settings.", + "PORT is either the serial port to use (e.g. " +#ifdef __CYGWIN__ + "/dev/ttyS0 for COM1 on Windows" +#else + "/dev/ttyUSB0" +#endif + ") that also supports a trailing wildcard '*' for testing" + " multiple ports, or a network port as \"ip:port\" for use with e.g. socat or ebusd-esp in PIC pass-through mode.", + nullptr, + nullptr + }; int arg_index = -1; - setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); - - if (argp_parse(&aargp, argc, argv, ARGP_IN_ORDER, &arg_index, nullptr) != 0) { - std::cerr << "invalid arguments" << std::endl; - exit(EXIT_FAILURE); + switch (ebusd::argParse(&parseOpt, argc, argv, &arg_index)) { + case 0: // OK + break; + case '?': // help printed + return 0; + default: + return EINVAL; } if (setIp != setMask || (setMacFromIp && !setIp)) { @@ -1188,7 +1196,7 @@ int main(int argc, char* argv[]) { printFileChecksum(); exit(EXIT_SUCCESS); } else { - argp_help(&aargp, stderr, ARGP_HELP_STD_ERR, const_cast("ebuspicloader")); + ebusd::argHelp(&parseOpt); exit(EXIT_FAILURE); } }