diff --git a/simulations/simulation_aes.msg b/simulations/serial_aes.msg similarity index 100% rename from simulations/simulation_aes.msg rename to simulations/serial_aes.msg diff --git a/src/cmdline.cc b/src/cmdline.cc index 541b53d..4270f90 100644 --- a/src/cmdline.cc +++ b/src/cmdline.cc @@ -24,61 +24,43 @@ using namespace std; -shared_ptr parseCommandLine(int argc, char **argv) { +static bool isDaemon(int argc, char **argv); +static bool checkIfUseConfig(int argc, char **argv); +static shared_ptr parseNormalCommandLine(Configuration *c, int argc, char **argv); +static shared_ptr parseCommandLineWithUseConfig(Configuration *c, int argc, char **argv, bool pid_file_expected); +shared_ptr parseCommandLine(int argc, char **argv) +{ Configuration * c = new Configuration; - int i=1; - const char *filename = strrchr(argv[0], '/'); - if (filename) - { - filename++; - } - else - { - filename = argv[0]; - } c->bin_dir = dirname(currentProcessExe()); - if (!strcmp(filename, "wmbusmetersd")) + + if (isDaemon(argc, argv)) { c->daemon = true; if (argc < 2) { - error("Usage error: wmbusmetersd must have at least a single argument to the pid file.\n" - "But you can also supply --device= and --listento= to override the config files.\n"); + error("Usage error: wmbusmetersd must have at least a single argument to the pid file.\n"); } - int i = 1; - bool pid_file_found = false; - for (;;) - { - if (argv[i] == NULL) break; - if (!strncmp(argv[i], "--device=", 9)) - { - c->device_override = string(argv[i]+9); - debug("(daemon) device override \"%s\"\n", c->device_override.c_str()); - i++; - continue; - } - if (!strncmp(argv[i], "--listento=", 11)) - { - c->listento_override = string(argv[i]+11); - debug("(daemon) listento override \"%s\"\n", c->listento_override.c_str()); - i++; - continue; - } - c->pid_file = argv[i]; - pid_file_found = true; - break; - } - if (!pid_file_found) - { - error("Usage error: you must supply the pid file as the argument to wmbusmetersd.\n"); - } - return shared_ptr(c); + return parseCommandLineWithUseConfig(c, argc, argv, true); } - if (argc < 2) { + + if (argc < 2) + { c->need_help = true; return shared_ptr(c); } + + if (checkIfUseConfig(argc, argv)) + { + return parseCommandLineWithUseConfig(c, argc, argv, false); + } + + return parseNormalCommandLine(c, argc, argv); +} + +static shared_ptr parseNormalCommandLine(Configuration *c, int argc, char **argv) +{ + int i = 1; while (argv[i] && argv[i][0] == '-') { if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) { @@ -205,52 +187,6 @@ shared_ptr parseCommandLine(int argc, char **argv) { i++; continue; } - if (!strncmp(argv[i], "--useconfig", 11)) { - if (strlen(argv[i]) == 11) - { - c->useconfig = true; - c->config_root = ""; - return shared_ptr(c); - } - else if (strlen(argv[i]) > 12 && argv[i][11] == '=') - { - size_t len = strlen(argv[i]) - 12; - c->useconfig = true; - c->config_root = string(argv[i]+12, len); - if (c->config_root == "/") { - c->config_root = ""; - } - } - else - { - error("You must supply a directory to --useconfig=dir\n"); - } - i++; - for (;;) - { - if (argv[i] == NULL) break; - if (!strncmp(argv[i], "--device=", 9)) - { - c->device_override = string(argv[i]+9); - debug("(useconfig) device override \"%s\"\n", c->device_override.c_str()); - i++; - continue; - } - if (!strncmp(argv[i], "--listento=", 11)) - { - c->listento_override = string(argv[i]+11); - debug("(useconfig) listento override \"%s\"\n", c->listento_override.c_str()); - i++; - continue; - } - break; - } - if (i+1 < argc) { - error("Usage error: --useconfig can only be followed by --device= and --listento=\n"); - } - return shared_ptr(c); - continue; - } if (!strncmp(argv[i], "--format=", 9)) { if (!strcmp(argv[i]+9, "json")) @@ -406,7 +342,7 @@ shared_ptr parseCommandLine(int argc, char **argv) { if (len > 0) { c->logfile = string(argv[i]+10, len); } else { - error("Not a valid log file name."); + error("Not a valid log file name.\n"); } } i++; @@ -661,3 +597,155 @@ shared_ptr parseCommandLine(int argc, char **argv) { return shared_ptr(c); } + +shared_ptr parseCommandLineWithUseConfig(Configuration *c, int argc, char **argv, bool pid_file_expected) +{ + int i = 1; + + while (argv[i] && argv[i][0] == '-') + { + if (!strncmp(argv[i], "--useconfig", 11)) + { + if (strlen(argv[i]) == 11) + { + c->useconfig = true; + c->config_root = ""; + } + else if (strlen(argv[i]) > 12 && argv[i][11] == '=') + { + size_t len = strlen(argv[i]) - 12; + c->useconfig = true; + c->config_root = string(argv[i]+12, len); + if (c->config_root == "/") { + c->config_root = ""; + } + } + else + { + error("You must supply a directory to --useconfig=dir\n"); + } + i++; + continue; + } + if (!strncmp(argv[i], "--verbose", 9)) + { + c->overrides.loglevel_override = "verbose"; + debug("(useconfig) loglevel override \"%s\"\n", c->overrides.loglevel_override.c_str()); + i++; + continue; + } + if (!strncmp(argv[i], "--normal", 8)) + { + c->overrides.loglevel_override = "normal"; + debug("(useconfig) loglevel override \"%s\"\n", c->overrides.loglevel_override.c_str()); + i++; + continue; + } + if (!strncmp(argv[i], "--silent", 8)) + { + c->overrides.loglevel_override = "silent"; + debug("(useconfig) loglevel override \"%s\"\n", c->overrides.loglevel_override.c_str()); + i++; + continue; + } + if (!strncmp(argv[i], "--debug", 7)) + { + c->overrides.loglevel_override = "debug"; + debug("(useconfig) loglevel override \"%s\"\n", c->overrides.loglevel_override.c_str()); + i++; + continue; + } + if (!strncmp(argv[i], "--trace", 7)) + { + c->overrides.loglevel_override = "trace"; + debug("(useconfig) loglevel override \"%s\"\n", c->overrides.loglevel_override.c_str()); + i++; + continue; + } + if (!strncmp(argv[i], "--device=", 9)) + { + c->overrides.device_override = string(argv[i]+9); + debug("(useconfig) device override \"%s\"\n", c->overrides.device_override.c_str()); + i++; + continue; + } + if (!strncmp(argv[i], "--listento=", 11)) + { + c->overrides.listento_override = string(argv[i]+11); + debug("(useconfig) listento override \"%s\"\n", c->overrides.listento_override.c_str()); + i++; + continue; + } + if (!strncmp(argv[i], "--exitafter=", 12) && strlen(argv[i]) > 12) { + int s = parseTime(argv[i]+12); + if (s <= 0) { + error("Not a valid time to exit after. \"%s\"\n", argv[i]+12); + } + c->overrides.exitafter_override = argv[i]+12; + i++; + continue; + } + if (!strcmp(argv[i], "--oneshot")) { + c->overrides.oneshot_override = "true"; + i++; + continue; + } + if (!strncmp(argv[i], "--logfile=", 10)) { + size_t len = strlen(argv[i])-10; + if (len > 0) { + c->overrides.logfile_override = string(argv[i]+10, len); + } else { + error("Not a valid log file name.\n"); + } + i++; + continue; + } + + error("Usage error: --useconfig=... can only be used in combination with:\n" + "--device= --listento= --exitafter= --oneshot= --logfile= --silent --normal --verbose --debug --trace\n"); + break; + } + + if (pid_file_expected) + { + if (!argv[i]) + { + error("Usage error: you must supply the pid file as the last argument to wmbusmetersd.\n"); + } + c->pid_file = argv[i]; + i++; + } + + if (i+1 < argc) + { + error("Usage error: you must supply the pid file as the last argument to wmbusmetersd.\n"); + } + return shared_ptr(c); +} + +static bool isDaemon(int argc, char **argv) +{ + const char *filename = strrchr(argv[0], '/'); + + if (filename) + { + filename++; + } + else + { + filename = argv[0]; + } + + return !strcmp(filename, "wmbusmetersd"); +} + +static bool checkIfUseConfig(int argc, char **argv) +{ + while (*argv != NULL) + { + if (!strncmp(*argv, "--useconfig", 11)) return true; + argv++; + } + + return false; +} diff --git a/src/config.cc b/src/config.cc index 5008e27..f00bdc6 100644 --- a/src/config.cc +++ b/src/config.cc @@ -177,22 +177,48 @@ void parseMeterConfig(Configuration *c, vector &buf, string file) void handleLoglevel(Configuration *c, string loglevel) { - if (loglevel == "verbose") { c->verbose = true; } + if (loglevel == "verbose") + { + c->silent = false; + c->verbose = true; + c->debug = false; + c->trace = false; + verboseEnabled(c->verbose); + } else if (loglevel == "debug") { + c->silent = false; + c->verbose = false; c->debug = true; + c->trace = false; // Kick in debug immediately. debugEnabled(c->debug); } else if (loglevel == "trace") { + c->silent = false; + c->verbose = false; + c->debug = false; c->trace = true; // Kick in trace immediately. traceEnabled(c->trace); } - else if (loglevel == "silent") { c->silent = true; } - else if (loglevel == "normal") { } - else { + else if (loglevel == "silent") + { + c->silent = true; + c->verbose = false; + c->debug = false; + c->trace = false; + } + else if (loglevel == "normal") + { + c->silent = false; + c->verbose = false; + c->debug = false; + c->trace = false; + } + else + { warning("No such log level: \"%s\"\n", loglevel.c_str()); } } @@ -358,6 +384,26 @@ void handleListenTo(Configuration *c, string mode) c->default_device_linkmodes = lms; } +void handleExitAfter(Configuration *c, string after) +{ + if (c->exitafter > 0) + { + error("You have already specified an exit after time!\n"); + } + + c->exitafter = parseTime(after); + + if (c->exitafter == 0) + { + error("Exit after time must be non-zero \"%s\"!\n", after.c_str()); + } +} + +void handleOneshot(Configuration *c) +{ + c->oneshot = true; +} + void handleLogtelegrams(Configuration *c, string logtelegrams) { if (logtelegrams == "true") { c->logtelegrams = true; } @@ -579,7 +625,7 @@ void handleExtraConstantField(Configuration *c, string field) c->extra_constant_fields.push_back(field); } -shared_ptr loadConfiguration(string root, string device_override, string listento_override) +shared_ptr loadConfiguration(string root, ConfigOverrides overrides) { Configuration *c = new Configuration; @@ -587,7 +633,23 @@ shared_ptr loadConfiguration(string root, string device_override, c->json = true; vector global_conf; + + // --useconfig=/ will work to find /etc/wmbusmeters.conf and /etc/wmbusmeters.d + // --useconfig=/etc will also work to find /etc/wmbusmeters.conf and /etc/wmbusmeters.d + // The second one is preferable but for backward compatibility the first one is tested first. + // If there is no /+etc/wmbusmeters.conf then it will look for /+wmbusmeters.conf + + string conf_dir = root; string conf_file = root+"/etc/wmbusmeters.conf"; + string conf_meter_dir = root+"/etc/wmbusmeters.d"; + + if (!checkFileExists(conf_file.c_str())) + { + conf_dir = root+"/etc"; + conf_file = root+"/wmbusmeters.conf"; + conf_meter_dir = root+"/wmbusmeters.d"; + } + debug("(config) loading %s\n", conf_file.c_str()); bool ok = loadFile(conf_file, &global_conf); global_conf.push_back('\n'); @@ -609,6 +671,8 @@ shared_ptr loadConfiguration(string root, string device_override, else if (p.first == "device") handleDeviceOrHex(c, p.second); else if (p.first == "donotprobe") handleDoNotProbe(c, p.second); else if (p.first == "listento") handleListenTo(c, p.second); + else if (p.first == "exitafter") handleExitAfter(c, p.second); + else if (p.first == "oneshot") handleOneshot(c); else if (p.first == "logtelegrams") handleLogtelegrams(c, p.second); else if (p.first == "meterfiles") handleMeterfiles(c, p.second); else if (p.first == "meterfilesaction") handleMeterfilesAction(c, p.second); @@ -641,36 +705,60 @@ shared_ptr loadConfiguration(string root, string device_override, } vector meters; - listFiles(root+"/etc/wmbusmeters.d", &meters); + listFiles(conf_meter_dir, &meters); for (auto& f : meters) { vector meter_conf; - string file = root+"/etc/wmbusmeters.d/"+f; + string file = conf_meter_dir+"/"+f; loadFile(file.c_str(), &meter_conf); meter_conf.push_back('\n'); parseMeterConfig(c, meter_conf, file); } - if (device_override != "") + if (overrides.device_override != "") { // There is an override, therefore we // drop any already loaded devices from the config file. c->use_auto_device_detect = false; c->supplied_bus_devices.clear(); - if (startsWith(device_override, "/dev/rtlsdr")) + if (startsWith(overrides.device_override, "/dev/rtlsdr")) { - debug("(config) use rtlwmbus instead of raw device %s\n", device_override.c_str()); - device_override = "rtlwmbus"; + debug("(config) use rtlwmbus instead of raw device %s\n", overrides.device_override.c_str()); + overrides.device_override = "rtlwmbus"; } - debug("(config) overriding device with \"%s\"\n", device_override.c_str()); - handleDeviceOrHex(c, device_override); + debug("(config) overriding device with \"%s\"\n", overrides.device_override.c_str()); + handleDeviceOrHex(c, overrides.device_override); } - if (listento_override != "") + if (overrides.listento_override != "") { - debug("(config) overriding listento with \"%s\"\n", listento_override.c_str()); - handleListenTo(c, listento_override); + debug("(config) overriding listento with \"%s\"\n", overrides.listento_override.c_str()); + handleListenTo(c, overrides.listento_override); + } + + if (overrides.exitafter_override != "") + { + debug("(config) overriding exitafter with \"%s\"\n", overrides.exitafter_override.c_str()); + handleExitAfter(c, overrides.exitafter_override); + } + + if (overrides.oneshot_override != "") + { + debug("(config) overriding oneshot with true\n"); + handleOneshot(c); + } + + if (overrides.loglevel_override != "") + { + debug("(config) overriding loglevel with %s\n", overrides.loglevel_override.c_str()); + handleLoglevel(c, overrides.loglevel_override); + } + + if (overrides.logfile_override != "") + { + debug("(config) overriding logfile with %s\n", overrides.logfile_override.c_str()); + handleLogfile(c, overrides.logfile_override); } return shared_ptr(c); diff --git a/src/config.h b/src/config.h index 3588976..df06de1 100644 --- a/src/config.h +++ b/src/config.h @@ -42,6 +42,17 @@ enum class MeterFileTimestamp Never, Day, Hour, Minute, Micros }; +// These values can be overridden from the command line. +struct ConfigOverrides +{ + std::string loglevel_override; + std::string device_override; + std::string listento_override; + std::string exitafter_override; + std::string oneshot_override; + std::string logfile_override; +}; + struct Configuration { string bin_dir {}; // The wmbusmeters binary executed is located here. @@ -49,8 +60,7 @@ struct Configuration // inside the same directory. bool daemon {}; std::string pid_file; - std::string device_override; - std::string listento_override; + ConfigOverrides overrides; bool useconfig {}; std::string config_root; bool need_help {}; @@ -121,7 +131,7 @@ struct Configuration ~Configuration() = default; }; -shared_ptr loadConfiguration(string root, string device_override, string listento_override); +shared_ptr loadConfiguration(string root, ConfigOverrides overrides); void parseMeterConfig(Configuration *c, vector &buf, string file); void handleConversions(Configuration *c, string s); diff --git a/src/main.cc b/src/main.cc index 12fe85e..b2ca807 100644 --- a/src/main.cc +++ b/src/main.cc @@ -55,8 +55,10 @@ void log_start_information(Configuration *config); void oneshot_check(Configuration *config, Telegram *t, Meter *meter); void regular_checkup(Configuration *config); bool start(Configuration *config); -void start_using_config_files(string root, bool is_daemon, string device_override, string listento_override); -void start_daemon(string pid_file, string device_override, string listento_override); // Will use config files. +void start_using_config_files(string root, bool is_daemon, ConfigOverrides overrides); + +void start_daemon(string pid_file, ConfigOverrides overrides); + void setup_log_file(Configuration *config); void setup_meters(Configuration *config, MeterManager *manager); void write_pid(string pid_file, int pid); @@ -150,13 +152,13 @@ provided you with this binary. Read the full license for all details. if (config->daemon) { - start_daemon(config->pid_file, config->device_override, config->listento_override); + start_daemon(config->pid_file, config->overrides); exit(0); } if (config->useconfig) { - start_using_config_files(config->config_root, false, config->device_override, config->listento_override); + start_using_config_files(config->config_root, false, config->overrides); exit(0); } else @@ -412,9 +414,9 @@ void setup_log_file(Configuration *config) bool ok = enableLogfile(config->logfile, config->daemon); if (!ok) { if (config->daemon) { - warning("Could not open log file, will use syslog instead.\n"); + warning("Could not open log file %s will use syslog instead.\n", config->logfile.c_str()); } else { - error("Could not open log file.\n"); + error("Could not open log file %s\n", config->logfile.c_str()); } } } @@ -462,8 +464,9 @@ bool start(Configuration *config) // Configure settings. silentLogging(config->silent); verboseEnabled(config->verbose); - logTelegramsEnabled(config->logtelegrams); debugEnabled(config->debug); + traceEnabled(config->trace); + logTelegramsEnabled(config->logtelegrams); if (config->addtimestamps == AddLogTimestamps::NotSet) { @@ -472,7 +475,7 @@ bool start(Configuration *config) } setLogTimestamps(config->addtimestamps); internalTestingEnabled(config->internaltesting); - traceEnabled(config->trace); + stderrEnabled(config->use_stderr_for_log); setAlarmShells(config->alarm_shells); setIgnoreDuplicateTelegrams(config->ignore_duplicate_telegrams); @@ -605,7 +608,7 @@ bool start(Configuration *config) return gotHupped(); } -void start_daemon(string pid_file, string device_override, string listento_override) +void start_daemon(string pid_file, ConfigOverrides overrides) { setlogmask(LOG_UPTO (LOG_INFO)); openlog("wmbusmetersd", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1); @@ -655,15 +658,15 @@ void start_daemon(string pid_file, string device_override, string listento_overr if (open("/dev/null", O_RDWR) == -1) { error("Failed to reopen stderr while daemonising (errno=%d)",errno); } - start_using_config_files("", true, device_override, listento_override); + start_using_config_files("", true, overrides); } -void start_using_config_files(string root, bool is_daemon, string device_override, string listento_override) +void start_using_config_files(string root, bool is_daemon, ConfigOverrides overrides) { bool restart = false; do { - shared_ptr config = loadConfiguration(root, device_override, listento_override); + shared_ptr config = loadConfiguration(root, overrides); config->daemon = is_daemon; restart = start(config.get()); if (restart) diff --git a/test.sh b/test.sh index 55d2121..3f096c3 100755 --- a/test.sh +++ b/test.sh @@ -158,6 +158,9 @@ echo Slower tests... tests/test_pipe.sh $PROG if [ "$?" != "0" ]; then RC="1"; fi +tests/test_config_overrides.sh $PROG +if [ "$?" != "0" ]; then RC="1"; fi + if [ "$(uname)" = "Linux" ] then tests/test_alarm.sh $PROG diff --git a/tests/test_aes.sh b/tests/test_aes.sh index 07aab86..44f5dc2 100755 --- a/tests/test_aes.sh +++ b/tests/test_aes.sh @@ -10,8 +10,8 @@ mkdir -p $TEST TESTNAME="Test aes encrypted telegrams" TESTRESULT="ERROR" -cat simulations/simulation_aes.msg | grep '^{' | tr -d '#' > $TEST/test_expected.txt -cat simulations/simulation_aes.msg | grep '^[CT]' | tr -d '#' > $TEST/test_input.txt +cat simulations/serial_aes.msg | grep '^{' | tr -d '#' > $TEST/test_expected.txt +cat simulations/serial_aes.msg | grep '^[CT]' | tr -d '#' > $TEST/test_input.txt cat $TEST/test_input.txt | $PROG --format=json "stdin:rtlwmbus" \ ApWater apator162 88888888 00000000000000000000000000000000 \ Vatten multical21 76348799 28F64A24988064A079AA2C807D6102AE \ diff --git a/tests/test_log_timestamps.sh b/tests/test_log_timestamps.sh index 320eac7..ce1bace 100755 --- a/tests/test_log_timestamps.sh +++ b/tests/test_log_timestamps.sh @@ -9,7 +9,7 @@ mkdir -p $TEST TESTNAME="Test log timestamps" -cat simulations/simulation_aes.msg | grep '^[CT]' | grep 76348799 | tr -d '#' > $TEST/test_input.txt +cat simulations/serial_aes.msg | grep '^[CT]' | grep 76348799 | tr -d '#' > $TEST/test_input.txt cat $TEST/test_input.txt | $PROG --format=json --logtimestamps=always --verbose "stdin:rtlwmbus" \ Vatten multical21 76348799 28F64A24988064A079AA2C807D6102AE > $TEST/test_output.txt 2> $TEST/test_stderr.txt diff --git a/tests/test_wrongkeys.sh b/tests/test_wrongkeys.sh index 622672a..b9e930e 100755 --- a/tests/test_wrongkeys.sh +++ b/tests/test_wrongkeys.sh @@ -10,8 +10,8 @@ mkdir -p $TEST TESTNAME="Test wrong keys" TESTRESULT="ERROR" -cat simulations/simulation_aes.msg | grep '^{' | tr -d '#' > $TEST/test_expected.txt -cat simulations/simulation_aes.msg | grep '^[CT]' | tr -d '#' > $TEST/test_input.txt +cat simulations/serial_aes.msg | grep '^{' | tr -d '#' > $TEST/test_expected.txt +cat simulations/serial_aes.msg | grep '^[CT]' | tr -d '#' > $TEST/test_input.txt cat $TEST/test_input.txt | $PROG --format=json "stdin:rtlwmbus" \ ApWater apator162 88888888 00000000000000000000000000000001 \ Vatten multical21 76348799 28F64A24988064A079AA2C807D6102AF \