/* Copyright (C) 2017-2022 Fredrik Öhrström (gpl-3.0-or-later) 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"cmdline.h" #include"meters.h" #include"util.h" #include #include using namespace std; 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; c->bin_dir = dirname(currentProcessExe()); 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"); } return parseCommandLineWithUseConfig(c, argc, argv, true); } 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")) { c->need_help = true; return shared_ptr(c); } if (!strncmp(argv[i], "--device=", 9) || // Deprecated !strncmp(argv[i], "--overridedevice=", 17)) { error("You can only use --overridedevice=xyz with --useconfig=xyz\n"); } if (!strcmp(argv[i], "--silent")) { c->silent = true; i++; continue; } if (!strcmp(argv[i], "--verbose")) { c->verbose = true; i++; continue; } if (!strcmp(argv[i], "--normal")) { c->silent = false; c->verbose = false; c->debug = false; c->trace = false; i++; continue; } if (!strcmp(argv[i], "--version")) { c->version = true; return shared_ptr(c); } if (!strcmp(argv[i], "--license")) { c->license = true; return shared_ptr(c); } if (!strcmp(argv[i], "--analyze")) { c->analyze = true; if (isatty(1)) { c->analyze_format = OutputFormat::TERMINAL; } else { c->analyze_format = OutputFormat::PLAIN; } c->analyze_driver = ""; c->analyze_key = ""; c->analyze_verbose = false; i++; continue; } if (!strncmp(argv[i], "--analyze=", 10)) { c->analyze = true; if (isatty(1)) { c->analyze_format = OutputFormat::TERMINAL; } else { c->analyze_format = OutputFormat::PLAIN; } c->analyze_driver = ""; c->analyze_key = ""; c->analyze_verbose = false; string arg = string(argv[i]+10); vector args = splitString(arg, ':'); for (auto s : args) { bool inv = false; if (isHexStringStrict(s, &inv)) { if (inv) { error("Bad key \"%s\"", s.c_str()); } c->analyze_key = s; } else if (s == "plain") c->analyze_format = OutputFormat::PLAIN; else if (s == "terminal") c->analyze_format = OutputFormat::TERMINAL; else if (s == "json") c->analyze_format = OutputFormat::JSON; else if (s == "html") c->analyze_format = OutputFormat::HTML; else if (s == "verbose") c->analyze_verbose = true; else { MeterInfo mi; mi.parse("x", s, "00000000", ""); if (mi.driver == MeterDriver::UNKNOWN && mi.driver_name.str() == "") { error("Not a valid meter driver \"%s\"\n", s.c_str()); } c->analyze_driver = s; } } i++; continue; } if (!strcmp(argv[i], "--debug")) { c->debug = true; i++; continue; } if (!strcmp(argv[i], "--trace")) { c->debug = true; c->trace = true; i++; continue; } if (!strncmp(argv[i], "--logtimestamps=", 16)) { string ts = string(argv[i]+16); debug("(cmdline) add log timestamps \"%s\"\n", ts.c_str()); if (ts == "never") { c->addtimestamps = AddLogTimestamps::Never; } else if (ts == "always") { c->addtimestamps = AddLogTimestamps::Always; } else if (ts == "important") { c->addtimestamps = AddLogTimestamps::Important; } else { error("No such timestamp setting \"%s\" possible values are: never always important\n", ts.c_str()); } i++; continue; } if (!strcmp(argv[i], "--internaltesting")) { c->internaltesting = true; i++; continue; } if (!strncmp(argv[i], "--donotprobe=", 13)) { string df = string(argv[i]+13); debug("(cmdline) do not probe \"%s\"\n", df.c_str()); c->do_not_probe_ttys.insert(df); i++; continue; } if (!strncmp(argv[i], "--listento=", 11)) { LinkModeSet lms = parseLinkModes(argv[i]+11); if (lms.empty()) { error("Unknown default link mode \"%s\"!\n", argv[i]+11); } if (!c->default_device_linkmodes.empty()) { error("You have already specified a default link mode!\n"); } c->default_device_linkmodes = lms; i++; continue; } LinkMode lm = isLinkModeOption(argv[i]); if (lm != LinkMode::UNKNOWN) { if (!c->default_device_linkmodes.empty()) { error("You have already specified a default link mode!\n"); } // Add to the empty set. c->default_device_linkmodes.addLinkMode(lm); i++; continue; } if (!strcmp(argv[i], "--logtelegrams")) { c->logtelegrams = true; i++; continue; } if (!strcmp(argv[i], "--logsummary")) { c->logsummary = true; i++; continue; } if (!strcmp(argv[i], "--ppjson")) { c->pretty_print_json = true; i++; continue; } if (!strncmp(argv[i], "--format=", 9)) { if (!strcmp(argv[i]+9, "json")) { c->json = true; c->fields = false; } else if (!strcmp(argv[i]+9, "fields")) { c->json = false; c->fields = true; c->separator = ';'; } else if (!strcmp(argv[i]+9, "hr")) { c->json = false; c->fields = false; c->separator = '\t'; } else { error("Unknown output format: \"%s\"\n", argv[i]+9); } i++; continue; } if (!strncmp(argv[i], "--addconversions=", 17)) { if (strlen(argv[i]) > 16) { string s = string(argv[i]+17); handleConversions(c, s); } else { error("You must supply conversion units.\n"); } i++; continue; } if (!strncmp(argv[i], "--selectfields=", 15)) { if (strlen(argv[i]) > 15) { string s = string(argv[i]+15); handleSelectedFields(c, s); } else { error("You must supply fields to be selected.\n"); } i++; continue; } if (!strncmp(argv[i], "--separator=", 12)) { if (!c->fields) { error("You must specify --format=fields before --separator=X\n"); } if (strlen(argv[i]) != 13) { error("You must supply a single character as the field separator.\n"); } c->separator = argv[i][12]; i++; continue; } if (!strncmp(argv[i], "--meterfilesaction", 18)) { if (strlen(argv[i]) > 18 && argv[i][18] == '=') { if (!strncmp(argv[i]+19, "overwrite", 9)) { c->meterfiles_action = MeterFileType::Overwrite; } else if (!strncmp(argv[i]+19, "append", 6)) { c->meterfiles_action = MeterFileType::Append; } else { error("No such meter file action %s\n", argv[i]+19); } } else { error("Incorrect option %s\n", argv[i]); } i++; continue; } if (!strncmp(argv[i], "--meterfilesnaming", 18)) { if (strlen(argv[i]) > 18 && argv[i][18] == '=') { if (!strncmp(argv[i]+19, "name-id", 7)) { c->meterfiles_naming = MeterFileNaming::NameId; } else if (!strncmp(argv[i]+19, "name", 4)) { c->meterfiles_naming = MeterFileNaming::Name; } else if (!strncmp(argv[i]+19, "id", 2)) { c->meterfiles_naming = MeterFileNaming::Id; } else { error("No such meter file naming %s\n", argv[i]+19); } } else { error("Incorrect option %s\n", argv[i]); } i++; continue; } if (!strncmp(argv[i], "--meterfilestimestamp", 21)) { if (strlen(argv[i]) > 22 && argv[i][21] == '=') { if (!strncmp(argv[i]+22, "day", 3)) { c->meterfiles_timestamp = MeterFileTimestamp::Day; } else if (!strncmp(argv[i]+22, "hour", 4)) { c->meterfiles_timestamp = MeterFileTimestamp::Hour; } else if (!strncmp(argv[i]+22, "minute", 6)) { c->meterfiles_timestamp = MeterFileTimestamp::Minute; } else if (!strncmp(argv[i]+22, "micros", 5)) { c->meterfiles_timestamp = MeterFileTimestamp::Micros; } else if (!strncmp(argv[i]+22, "never", 5)) { c->meterfiles_timestamp = MeterFileTimestamp::Never; } else { error("No such meter file timestamp \"%s\"\n", argv[i]+22); } } else { error("Incorrect option %s\n", argv[i]); } i++; continue; } if (!strcmp(argv[i], "--meterfiles") || (!strncmp(argv[i], "--meterfiles", 12) && strlen(argv[i]) > 12 && argv[i][12] == '=')) { c->meterfiles = true; size_t len = strlen(argv[i]); if (len > 13) { c->meterfiles_dir = string(argv[i]+13, len-13); } else { c->meterfiles_dir = "/tmp"; } if (!checkIfDirExists(c->meterfiles_dir.c_str())) { error("Cannot write meter files into dir \"%s\"\n", c->meterfiles_dir.c_str()); } i++; continue; } if (!strncmp(argv[i], "--logfile=", 10)) { c->use_logfile = true; if (strlen(argv[i]) > 10) { size_t len = strlen(argv[i])-10; if (len > 0) { c->logfile = string(argv[i]+10, len); } else { error("Not a valid log file name.\n"); } } i++; continue; } if (!strncmp(argv[i], "--usestderr", 11)) { c->use_stderr_for_log = true; i++; continue; } if (!strncmp(argv[i], "--ignoreduplicates", 18)) { if (argv[i][18] == 0) { c->ignore_duplicate_telegrams = true; } else { if (!strcmp(argv[i]+18, "=true")) { c->ignore_duplicate_telegrams = true; } else if (!strcmp(argv[i]+18, "=false")) { c->ignore_duplicate_telegrams = false; } else { error("You must specify true or false after --ignoreduplicates=\n"); } } i++; continue; } if (!strncmp(argv[i], "--usestdoutforlogging", 13)) { c->use_stderr_for_log = false; i++; continue; } if (!strncmp(argv[i], "--shell=", 8)) { string cmd = string(argv[i]+8); if (cmd == "") { error("The telegram shell command cannot be empty.\n"); } c->telegram_shells.push_back(cmd); i++; continue; } if (!strncmp(argv[i], "--alarmshell=", 13)) { string cmd = string(argv[i]+13); if (cmd == "") { error("The alarm shell command cannot be empty.\n"); } c->alarm_shells.push_back(cmd); i++; continue; } if (!strncmp(argv[i], "--json_", 7) || !strncmp(argv[i], "--field_", 8)) { // For example: --json_floor=42 // or --field_floor=42 // they are equivalent. int off = 7; if (!strncmp(argv[i], "--field_", 8)) { off = 8; } string extra_constant_field = string(argv[i]+off); if (extra_constant_field == "") { error("The extra constant field command cannot be empty.\n"); } // The extra "floor"="42" will be pushed to the json. debug("Added extra constant field %s\n", extra_constant_field.c_str()); c->extra_constant_fields.push_back(extra_constant_field); i++; continue; } if (!strncmp(argv[i], "--listenvs=", 11)) { c->list_shell_envs = true; c->list_meter = string(argv[i]+11); i++; continue; } if (!strncmp(argv[i], "--listfields=", 13)) { c->list_fields = true; c->list_meter = string(argv[i]+13); i++; continue; } if (!strncmp(argv[i], "--listmeters=", 13)) { c->list_meters = true; c->list_meters_search = string(argv[i]+13); i++; continue; } else if (!strncmp(argv[i], "--listmeters", 12)) { c->list_meters = true; c->list_meters_search = ""; i++; continue; } else if (!strcmp(argv[i], "--listunits")) { c->list_units = true; i++; continue; } if (!strcmp(argv[i], "--oneshot")) { c->oneshot = true; i++; continue; } if (!strncmp(argv[i], "--exitafter=", 12) && strlen(argv[i]) > 12) { c->exitafter = parseTime(argv[i]+12); if (c->exitafter <= 0) { error("Not a valid time to exit after. \"%s\"\n", argv[i]+12); } i++; continue; } if (!strcmp(argv[i], "--nodeviceexit")) { c->nodeviceexit = true; i++; continue; } if (!strncmp(argv[i], "--pollinterval=", 15) && strlen(argv[i]) > 15) { c->pollinterval = parseTime(argv[i]+15); if (c->pollinterval <= 0) { error("Not a valid time to regularly poll meters. \"%s\"\n", argv[i]+15); } i++; continue; } if (!strncmp(argv[i], "--resetafter=", 13) && strlen(argv[i]) > 13) { c->resetafter = parseTime(argv[i]+13); if (c->resetafter <= 0) { error("Not a valid time to regularly reset after. \"%s\"\n", argv[i]+13); } i++; continue; } if (!strncmp(argv[i], "--alarmtimeout=", 15)) { c->alarm_timeout = parseTime(argv[i]+15); if (c->alarm_timeout <= 0) { error("Not a valid alarm timeout. \"%s\"\n", argv[i]+15); } i++; continue; } if (!strncmp(argv[i], "--alarmexpectedactivity=", 24)) { string ea = string(argv[i]+24); if (!isValidTimePeriod(ea)) { error("Not a valid time period string. \"%s\"\n", ea.c_str()); } c->alarm_expected_activity = ea; i++; continue; } error("Unknown option \"%s\"\n", argv[i]); } bool found_at_least_one_device_or_hex = false; while (argv[i]) { if (!strncmp(argv[i], "--", 2)) { // We have found a device and the next parameter is an option. error("All command line options must be placed before the devices, %s is placed wrong.\n", argv[i]); } bool ok = handleDeviceOrHex(c, argv[i]); if (ok) { found_at_least_one_device_or_hex = true; } else { if (!found_at_least_one_device_or_hex) { error("At least one valid device (or hex) must be supplied!\n"); } // There are more arguments... break; } i++; } if (c->supplied_bus_devices.size() == 0 && c->use_auto_device_detect == false && !c->list_shell_envs && !c->list_fields && !c->list_meters && !c->list_units) { error("You must supply at least one device to communicate using (w)mbus.\n"); } while (argv[i] != 0 && SendBusContent::isLikely(argv[i])) { SendBusContent sbc; bool ok = sbc.parse(argv[i]); if (!ok) { error("Not a valid send bus content command.\n"); } c->send_bus_content.push_back(sbc); i++; } if ((argc-i) % 4 != 0) { error("For each meter you must supply a: name,type,id and key.\n"); } int num_meters = (argc-i)/4; for (int m=0; mpollinterval; if (mi.driver == MeterDriver::UNKNOWN && mi.driver_name.str() == "") { error("Not a valid meter driver \"%s\"\n", driver.c_str()); } LinkModeSet default_modes = toMeterLinkModeSet(mi.driver); if (default_modes.has(LinkMode::MBUS)) { // MBus primary address 0-250 // secondary hex address iiiiiiiimmmmvvmm } else { // WMBus ids are 8 hex digits iiiiiiii if (!isValidMatchExpressions(id, true)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str()); } if (!isValidKey(key, mi.driver)) error("Not a valid meter key \"%s\"\n", key.c_str()); c->meters.push_back(mi); // Check if the devices can listen to the meter link mode(s). /* Ignore this check for now until all meters have been refactored. if (!default_modes.hasAll(mi.link_modes)) { string want = mi.link_modes.hr(); string has = default_modes.hr(); error("(cmdline) cannot set link modes to: %s because meter %s only transmits on: %s\n", want.c_str(), mi.driverName().str().c_str(), has.c_str()); } string modeshr = mi.link_modes.hr(); debug("(cmdline) setting link modes to %s for meter %s\n", mi.link_modes.hr().c_str(), name.c_str()); */ } 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) || // Deprecated !strncmp(argv[i], "--overridedevice=", 17)) { 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" "--overridedevice= --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; }