diff --git a/.gitignore b/.gitignore index 79412200cf..baaaff8353 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ tags /VERSION_FORCED /VERSION_FORCED_SEMVER /install-sh +/.libs-dev-PATH /libtool /ltmain.sh /missing diff --git a/Makefile.am b/Makefile.am index eb0738a36b..8f464d6ecc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -162,8 +162,8 @@ SUBDIR_MAKE_VERBOSE = default # Run the standard build if going sequential (or with unknown MAKEFLAGS), # or fanout if parallel (presuming GNU/BSD/Sun make at least): -CLEANFILES += include/.all.nut_version-generated.timestamp clients/.all.libupsclient_version-generated.timestamp -all-fanout-maybe: @dotMAKE@ +CLEANFILES += .libs-dev-PATH include/.all.nut_version-generated.timestamp clients/.all.libupsclient_version-generated.timestamp +all-fanout-maybe: .libs-dev-PATH @dotMAKE@ @rm -f include/.all.nut_version-generated.timestamp \ clients/.all.libupsclient_version-generated.timestamp +@if [ x"$(NUT_MAKE_SKIP_FANOUT)" = xtrue ] ; then \ @@ -190,6 +190,27 @@ all-fanout-maybe: @dotMAKE@ ;; \ esac +.libs-dev-PATH: Makefile + echo '# Sourcable script for developers to run built progs with their shared libs:' > '$@' + echo 'for D in common clients drivers tools/nut-scanner ; do \ + for S in "" "/.libs" ; do \ + DN="$(abs_top_builddir)/$${D}$${S}" ; \ + case "$${PATH}" in \ + "$${DN}"|*:"$${DN}}"|"$${DN}":*|*:"$${DN}":*) ;; \ + "") PATH="$${DN}" ;; \ + *) PATH="$${DN}:$${PATH}" ;; \ + esac ; \ + case "$${LD_LIBRARY_PATH}" in \ + "$${DN}"|*:"$${DN}}"|"$${DN}":*|*:"$${DN}":*) ;; \ + "") LD_LIBRARY_PATH="$${DN}" ;; \ + *) LD_LIBRARY_PATH="$${DN}:$${LD_LIBRARY_PATH}" ;; \ + esac ; \ + done ; \ + done ; \ + export PATH ; \ + export LD_LIBRARY_PATH ; \ + ' >> '$@' + # We start with a pass to `make all` in `common` dir because our wild recipes # (with other subdirs ensuring the libraries they need have been built) can # sometimes cause parallel compilation and library generation for same files @@ -208,7 +229,7 @@ all-fanout-staged: @dotMAKE@ all-fanout-subdirs: $(SUBDIRS_ALL_RECURSIVE) -all-fanout-libs all-libs-local: $(SUBDIRS_ALL_LIBS_LOCAL) +all-fanout-libs all-libs-local: $(SUBDIRS_ALL_LIBS_LOCAL) .libs-dev-PATH #all all-am-local all-local: @dotMAKE@ # +@cd common && $(MAKE) $(AM_MAKEFLAGS) all @@ -855,9 +876,21 @@ SET_PARMAKES_OPT = \ ;; \ esac +all-quick: @dotMAKE@ + +@$(SET_PARMAKES_OPT); \ + $(MAKE) $(AM_MAKEFLAGS) -k -s $${PARMAKES_OPT} all + +install-quick: @dotMAKE@ + +@$(SET_PARMAKES_OPT); \ + $(MAKE) $(AM_MAKEFLAGS) -k -s $${PARMAKES_OPT} install + +check-quick: @dotMAKE@ + +@$(SET_PARMAKES_OPT); \ + $(MAKE) $(AM_MAKEFLAGS) -k -s $${PARMAKES_OPT} check + spellcheck-quick: @dotMAKE@ +@$(SET_PARMAKES_OPT); \ - $(MAKE) $(AM_MAKEFLAGS) -k -s ${PARMAKES_OPT} spellcheck + $(MAKE) $(AM_MAKEFLAGS) -k -s $${PARMAKES_OPT} spellcheck # Run auto-parallel recipe, and if something fails - re-run interactively: spellcheck-interactive-quick: @dotMAKE@ diff --git a/NEWS.adoc b/NEWS.adoc index d9de4084e0..d90ac46958 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -137,6 +137,12 @@ but the `nutshutdown` script would bail out quickly and quietly. [PR #3008] * Ported C client library `libupsclient` support logic for SSL connections into C++ client library `libnutclient` and added tests for it. [issues #1599, #1711, PR #3353] + * Added support for command-line debugging with `-D` option into `upsc`, + `upscmd` and `upsrw` command-line clients, and the `nutconf` tool. + Revised handling of `-D` and/or `NUT_DEBUG_LEVEL` environment variable + in other programs. Extended public API of libupsclient and libnutscan + to diligently arrange debug-logging when at run-time those libraries + actually have different copies of the methods and data involved. [#3378] - NUT for Windows specific updates: * Revised detection of (relative) paths to program and configuration files @@ -395,7 +401,8 @@ several `FSD` notifications into one executed action. [PR #3097] - `upsc` has now optional JSON output [issue #3172, PR #3178] - - `upsset` should now recognize `RANGE NUMBER` and `NUMBER` types. [#3164] + - `upsset` CGI tool should now recognize `RANGE NUMBER` and `NUMBER` types. + [#3164] - `upsstats` CGI tool updates: * Now has JSON output mode via `?json` (or `&json`) CGI query parameter. @@ -540,6 +547,9 @@ several `FSD` notifications into one executed action. [PR #3097] which should now not re-check source texts that were okay with the previous dictionary contents, in case some new terms have to be added. [PRs #3186, #2871] + * Added `make .libs-dev-PATH` recipe to generate a file which developers + or CI scripts can source to prefer freshly built NUT libraries and some + tools in their `LD_LIBRARY_PATH` and `PATH`. [#3379] * Introduced `make distcheck-completeness` goal to verify that our distribution archives can exactly reproduce themselves. [#2829] * Added a GitHub Actions CI job to generate, upload and recycle `make dist` diff --git a/clients/Makefile.am b/clients/Makefile.am index 4af2e01732..47b11baee7 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -219,7 +219,7 @@ endif WITH_SSL # for the run-time dynamic linker resolution? For now the shared-library # builds are "exotic", but it makes sense to deprecate this export in a # future release. -libupsclient_la_LDFLAGS = -version-info 7:0:0 +libupsclient_la_LDFLAGS = -version-info 8:0:1 libupsclient_la_LDFLAGS += -export-symbols-regex '^(upscli_|nut_debug_level)' #|s_upsdebug|fatalx|fatal_with_errno|xcalloc|xbasename|print_banner_once)' if HAVE_WINDOWS diff --git a/clients/upsc.c b/clients/upsc.c index 91a746612d..c2c002a3b0 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -35,30 +35,18 @@ /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" +/* name-swap in libupsclient consumer to simplify the look of code base */ +#define builtin_setproctag(x) setproctag(x) +#define setproctag(x) do { builtin_setproctag(x); upscli_upslog_setproctag(x, nut_common_cookie()); } while(0) + static char *upsname = NULL, *hostname = NULL; static UPSCONN_t *ups = NULL; static int output_json = 0; -static void fatalx_error_json_simple(int msg_is_simple, const char *msg) - __attribute__((noreturn)); - -static void fatalx_error_json_simple(int msg_is_simple, const char *msg) { - /* To be used in simpler cases, where the possible JSON - * message is not embedded into other lists/objects */ - if (output_json) { - if (msg_is_simple) { - /* Caller knows there is nothing to escape here, pass through */ - printf("{\"error\": \"%s\"}\n", NUT_STRARG(msg)); - } else { - printf("{\"error\": \""); - json_print_esc(msg); - printf("\"}\n"); - } - } - fatalx(EXIT_FAILURE, "Error: %s", NUT_STRARG(msg)); -} +/* For getopt loops below: */ +static const char optstring[] = "+DhlLcVW:j"; -static void usage(const char *prog) +static void help(const char *prog) { print_banner_once(prog, 2); printf("NUT read-only client program to display UPS variables.\n"); @@ -88,6 +76,7 @@ static void usage(const char *prog) printf(" -V - display the version of this software\n"); printf(" -W - network timeout for initial connections (default: %s)\n", UPSCLI_DEFAULT_CONNECT_TIMEOUT); + printf(" -D - raise debugging level\n"); printf(" -h - display this help text\n"); nut_report_config_flags(); @@ -96,6 +85,25 @@ static void usage(const char *prog) printf("\n%s", suggest_doc_links(prog, NULL)); } +static void fatalx_error_json_simple(int msg_is_simple, const char *msg) + __attribute__((noreturn)); + +static void fatalx_error_json_simple(int msg_is_simple, const char *msg) { + /* To be used in simpler cases, where the possible JSON + * message is not embedded into other lists/objects */ + if (output_json) { + if (msg_is_simple) { + /* Caller knows there is nothing to escape here, pass through */ + printf("{\"error\": \"%s\"}\n", NUT_STRARG(msg)); + } else { + printf("{\"error\": \""); + json_print_esc(msg); + printf("\"}\n"); + } + } + fatalx(EXIT_FAILURE, "Error: %s", NUT_STRARG(msg)); +} + static void printvar(const char *var) { int ret; @@ -381,41 +389,72 @@ static void clean_exit(void) free(hostname); free(ups); - /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ + upscli_cleanup(); + upsdebugx(1, "%s: finished, exiting", __func__); - setproctag(NULL); } int main(int argc, char **argv) { - int i = 0; + /* Make sure all related logs (copies of code that may + * be spread in different NUT common libs) start on the + * same note; execute this call before everything else, + * at the cost of a temporary otherwise useless variable. */ + const struct timeval *upslog_start_tmp = upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + int opt_ret = 0; uint16_t port; int varlist = 0, clientlist = 0, verbose = 0; - const char *prog = xbasename(argv[0]); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsc"); const char *net_connect_timeout = NULL; - char *s = NULL; - setproctag(prog); - /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc - * and NUT methods called from it. This line aims to just initialize - * the subsystem, and set initial timestamp. Debugging the client is - * primarily of use to developers, so is not exposed via `-D` args. + NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + + /* NOTE: Debugging the client is primarily of use to developers, so + * it was not at all exposed via `-D[D...]` args until NUT v2.8.5. + * Since earlier 2.8.x releases, caller could `export NUT_DEBUG_LEVEL` + * to see debugs for the client and for NUT methods called from it. */ - s = getenv("NUT_DEBUG_LEVEL"); - if (s && str_to_int(s, &i, 10) && i > 0) { - nut_debug_level = i; - upscli_set_debug_level(nut_debug_level); + + /* Parse command line options -- First loop: only get debug level */ + /* Suppress error messages, for now -- leave them to the second loop. */ + opterr = 0; + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + if (opt_ret == 'D') + nut_debug_level++; } + + if (!nut_debug_level) { + char *s = getenv("NUT_DEBUG_LEVEL"); + int l; + if (s && str_to_int(s, &l, 10) && l > 0) { + nut_debug_level = l; + upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " + "since none was requested by command-line options", l); + } /* else follow -D settings */ + } + + /* These lines aim to just initialize the logging subsystem, and set + * initial timestamp, for the eventuality that debugs would be printed: + */ + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); + setproctag(prog); upsdebugx(1, "Starting NUT client: %s", prog); #if (defined NUT_PLATFORM_AIX) && (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, "+hlLcVW:j")) != -1) { + /* Parse command line options -- Second loop: everything else */ + /* Restore error messages... */ + opterr = 1; + /* ...and index of the item to be processed by getopt(). */ + optind = 1; + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { - switch (i) + switch (opt_ret) { + case 'D': break; /* See nut_debug_level handled above */ case 'L': verbose = 1; goto fallthrough_case_l; @@ -445,7 +484,7 @@ int main(int argc, char **argv) case 'h': default: - usage(prog); + help(prog); exit(EXIT_SUCCESS); } } @@ -456,6 +495,8 @@ int main(int argc, char **argv) fatalx_error_json_simple(0, msg); } + /* Simplify offset numbering to look at command-line + * arguments (if any) after the options checked above */ argc -= optind; argv += optind; @@ -471,7 +512,7 @@ int main(int argc, char **argv) fatalx_error_json_simple(0, "invalid UPS definition.\nRequired format: upsname[@hostname[:port]]"); } } - setproctag(argv[0]); + setproctag(argv[0]); /* ups[@host[:port]] */ upsdebugx(1, "upsname='%s' hostname='%s' port='%" PRIu16 "'", NUT_STRARG(upsname), NUT_STRARG(hostname), port); @@ -484,15 +525,13 @@ int main(int argc, char **argv) if (varlist) { upsdebugx(1, "Calling list_upses()"); list_upses(verbose); - exit(EXIT_SUCCESS); } - + else if (clientlist) { upsdebugx(1, "Calling list_clients()"); list_clients(upsname); - exit(EXIT_SUCCESS); } - + else if (argc > 1) { upsdebugx(1, "Calling printvar(%s)", argv[1]); printvar(argv[1]); @@ -501,6 +540,8 @@ int main(int argc, char **argv) list_vars(); } + /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ + setproctag(prog); exit(EXIT_SUCCESS); } diff --git a/clients/upsclient.c b/clients/upsclient.c index 6af58cb62e..22f05f95f2 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -2272,12 +2272,59 @@ int upscli_str_add_unique_token(char *tgt, size_t tgtsize, const char *token, * active memory, and upsdebugx() calls suffer if the library's copy * is never changed from zero. */ -void upscli_set_debug_level(int lvl) + +/* privately exported from common.c for internal libs */ +const char *setproctag_lib_once(const char *val); + +const void *upscli_upslog_cookie(void) +{ + return nut_common_cookie(); +} + +void upscli_upslog_set_debug_level(int lvl, const void *cookie) { nut_debug_level = lvl; + + if (cookie == upscli_upslog_cookie()) + return; + + setproctag_lib_once("libupsclient"); } -int upscli_get_debug_level(void) +int upscli_upslog_get_debug_level(void) { return nut_debug_level; } + +/* Avoid re-querying /proc or equivalent and logging about it, + * if the caller is a NUT program that already knows its name: + * see getmyprocname() in NUT common library */ +void upscli_upslog_setprocname(const char *pn, const void *cookie) +{ + if (cookie != upscli_upslog_cookie()) + setproctag_lib_once("libupsclient"); + + setmyprocname(pn); +} + +void upscli_upslog_setproctag(const char *tag, const void *cookie) +{ + if (cookie != upscli_upslog_cookie()) + setproctag_lib_once("libupsclient"); + + setproctag(tag); +} + +const char *upscli_upslog_getproctag(void) +{ + return getproctag(); +} + +struct timeval *upscli_upslog_start_sync(struct timeval *tv, const void *cookie) +{ + if (cookie != upscli_upslog_cookie()) + setproctag_lib_once("libupsclient"); + + /* No-op if internal tv equals passed tv */ + return upslog_start_sync(tv); +} diff --git a/clients/upsclient.h b/clients/upsclient.h index ea80550bc6..84889fbeb7 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -104,10 +104,33 @@ const char *upscli_strerror(UPSCONN_t *ups); * programs using both libraries as dynamically-linked shared code, * the nut_debug_level setting is backed by independent variables in * active memory, and upsdebugx() calls suffer if the library's copy - * is never changed from zero. + * is never changed from zero. It can get even more confusing with + * libnutprivate-common being a shared dynamically loaded library + * instance behind both the program and libupsclient, hence the cookies: + * direct NUT-common consumers like NUT in-tree clients can use their + * nut_common_cookie() to pass into methods here. */ -void upscli_set_debug_level(int lvl); -int upscli_get_debug_level(void); +const void *upscli_upslog_cookie(void); +void upscli_upslog_set_debug_level(int lvl, const void *cookie); +int upscli_upslog_get_debug_level(void); + +/* Similarly for sub-process tags that help with troubleshooting */ +void upscli_upslog_setprocname(const char *pn, const void *cookie); +void upscli_upslog_setproctag(const char *tag, const void *cookie); +const char *upscli_upslog_getproctag(void); + +/* The NUT common library code is included in several other + * libraries, often with their private copies of variables, + * so we want to synchronize them. + * If internal `upslog_start` value is not yet set, we set + * it from *tv (or current time if tv==NULL), otherwise the + * method is no-op (keep and report the original setting). + * Returns the pointer to the currently set value, so it + * can be propagated or used in difftime() computations. + * NOTE: In WIN32 builds also enforces line-buffering for + * stdout and stderr streams. + */ +struct timeval *upscli_upslog_start_sync(struct timeval *tv, const void *cookie); /* NOTE: effectively only runs once; re-runs quickly skip out */ int upscli_init(int certverify, const char *certpath, const char *certname, const char *certpasswd); diff --git a/clients/upscmd.c b/clients/upscmd.c index 05fc13d1d7..8cb7e50f07 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -3,6 +3,7 @@ Copyright (C) 2000 Russell Kroll 2019 EATON (author: Arnaud Quette ) + 2020-2026 Jim Klimov 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 @@ -35,6 +36,10 @@ #include "nut_stdint.h" #include "upsclient.h" +/* name-swap in libupsclient consumer to simplify the look of code base */ +#define builtin_setproctag(x) setproctag(x) +#define setproctag(x) do { builtin_setproctag(x); upscli_upslog_setproctag(x, nut_common_cookie()); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" @@ -48,7 +53,10 @@ struct list_t { struct list_t *next; }; -static void usage(const char *prog) +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+Dlhu:p:t:wVW:"; + +static void help(const char *prog) { print_banner_once(prog, 2); printf("NUT administration client program to initiate instant commands on UPS hardware.\n"); @@ -71,6 +79,7 @@ static void usage(const char *prog) printf(" -V - display the version of this software\n"); printf(" -W - network timeout for initial connections (default: %s)\n", UPSCLI_DEFAULT_CONNECT_TIMEOUT); + printf(" -D - raise debugging level\n"); printf(" -h - display this help text\n"); nut_report_config_flags(); @@ -287,42 +296,74 @@ static void clean_exit(void) free(hostname); free(ups); - /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ + upscli_cleanup(); + upsdebugx(1, "%s: finished, exiting", __func__); - setproctag(NULL); } int main(int argc, char **argv) { - int i; + /* Make sure all related logs (copies of code that may + * be spread in different NUT common libs) start on the + * same note; execute this call before everything else, + * at the cost of a temporary otherwise useless variable. */ + const struct timeval *upslog_start_tmp = upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + int opt_ret = 0; uint16_t port; ssize_t ret; int have_un = 0, have_pw = 0, cmdlist = 0; - char buf[SMALLBUF * 2], username[SMALLBUF], password[SMALLBUF], *s = NULL; - const char *prog = xbasename(argv[0]); + char buf[SMALLBUF * 2], username[SMALLBUF], password[SMALLBUF]; + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upscmd"); const char *net_connect_timeout = NULL; - setproctag(prog); - /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc - * and NUT methods called from it. This line aims to just initialize - * the subsystem, and set initial timestamp. Debugging the client is - * primarily of use to developers, so is not exposed via `-D` args. + NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + + /* NOTE: Debugging the client is primarily of use to developers, so + * it was not at all exposed via `-D[D...]` args until NUT v2.8.5. + * Since earlier 2.8.x releases, caller could `export NUT_DEBUG_LEVEL` + * to see debugs for the client and for NUT methods called from it. */ - s = getenv("NUT_DEBUG_LEVEL"); - if (s && str_to_int(s, &i, 10) && i > 0) { - nut_debug_level = i; - upscli_set_debug_level(nut_debug_level); + + /* Parse command line options -- First loop: only get debug level */ + /* Suppress error messages, for now -- leave them to the second loop. */ + opterr = 0; + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + if (opt_ret == 'D') + nut_debug_level++; } + + if (!nut_debug_level) { + char *s = getenv("NUT_DEBUG_LEVEL"); + int l; + if (s && str_to_int(s, &l, 10) && l > 0) { + nut_debug_level = l; + upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " + "since none was requested by command-line options", l); + } /* else follow -D settings */ + } + + /* These lines aim to just initialize the logging subsystem, and set + * initial timestamp, for the eventuality that debugs would be printed: + */ + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); + setproctag(prog); upsdebugx(1, "Starting NUT client: %s", prog); #if (defined NUT_PLATFORM_AIX) && (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, "+lhu:p:t:wVW:")) != -1) { + /* Parse command line options -- Second loop: everything else */ + /* Restore error messages... */ + opterr = 1; + /* ...and index of the item to be processed by getopt(). */ + optind = 1; + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { - switch (i) + switch (opt_ret) { + case 'D': break; /* See nut_debug_level handled above */ case 'l': cmdlist = 1; break; @@ -359,7 +400,7 @@ int main(int argc, char **argv) case 'h': default: - usage(prog); + help(prog); exit(EXIT_SUCCESS); } } @@ -369,11 +410,13 @@ int main(int argc, char **argv) net_connect_timeout); } + /* Simplify offset numbering to look at command-line + * arguments (if any) after the options checked above */ argc -= optind; argv += optind; if (argc < 1) { - usage(prog); + help(prog); exit(EXIT_SUCCESS); } @@ -383,7 +426,7 @@ int main(int argc, char **argv) if (upscli_splitname(argv[0], &upsname, &hostname, &port) != 0) { fatalx(EXIT_FAILURE, "Error: invalid UPS definition. Required format: upsname[@hostname[:port]]"); } - setproctag(argv[0]); + setproctag(argv[0]); /* ups[@host[:port]] */ ups = (UPSCONN_t *)xcalloc(1, sizeof(*ups)); @@ -397,7 +440,7 @@ int main(int argc, char **argv) } if (argc < 2) { - usage(prog); + help(prog); exit(EXIT_SUCCESS); } @@ -496,6 +539,8 @@ int main(int argc, char **argv) do_cmd(&argv[1], argc - 1); + /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ + setproctag(prog); exit(EXIT_SUCCESS); } diff --git a/clients/upsimage.c b/clients/upsimage.c index d3399d938d..f9adbe8890 100644 --- a/clients/upsimage.c +++ b/clients/upsimage.c @@ -49,6 +49,10 @@ #define MAX_CGI_STRLEN 64 +/* name-swap in libupsclient consumer to simplify the look of code base */ +#define builtin_setproctag(x) setproctag(x) +#define setproctag(x) do { builtin_setproctag(x); upscli_upslog_setproctag(x, nut_common_cookie()); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" @@ -613,6 +617,13 @@ static int get_var(const char *var, char *buf, size_t buflen) return 1; } +static void clean_exit(void) +{ + upscli_cleanup(); + + upsdebugx(1, "%s: finished, exiting", __func__); +} + int main(int argc, char **argv) { char str[SMALLBUF], *s; @@ -634,8 +645,9 @@ int main(int argc, char **argv) setmode(STDOUT_FILENO, O_BINARY); #endif - NUT_UNUSED_VARIABLE(argc); - NUT_UNUSED_VARIABLE(argv); + upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsimage(CGI)"); /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc * and NUT methods called from it. This line aims to just initialize @@ -645,7 +657,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); } #ifdef NUT_CGI_DEBUG_UPSIMAGE @@ -655,7 +667,7 @@ int main(int argc, char **argv) # endif /* Un-comment via make flags when developer-troubleshooting: */ nut_debug_level = NUT_CGI_DEBUG_UPSIMAGE; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); #endif if (nut_debug_level > 0) { @@ -669,6 +681,7 @@ int main(int argc, char **argv) extractcgiargs(); upscli_init_default_connect_timeout(NULL, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT); + atexit(clean_exit); /* no 'host=' or 'display=' given */ if ((!monhost) || (!cmd)) diff --git a/clients/upslog.c b/clients/upslog.c index f7f879d570..80977a0179 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -43,6 +43,10 @@ #include "upslog.h" #include "str.h" +/* name-swap in libupsclient consumer to simplify the look of code base */ +#define builtin_setproctag(x) setproctag(x) +#define setproctag(x) do { builtin_setproctag(x); upscli_upslog_setproctag(x, nut_common_cookie()); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" @@ -190,6 +194,9 @@ static void setup_signals(void) static void help(const char *prog) __attribute__((noreturn)); +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+hDs:l:i:d:Nf:u:Vp:FBm:W:"; + static void help(const char *prog) { print_banner_once(prog, 2); @@ -508,9 +515,14 @@ static void run_flist(const struct monhost_ups_t *monhost_ups_print) int main(int argc, char **argv) { - int interval = 30, i, foreground = -1, prefix_UPSHOST = 0, logformat_allocated = 0; + /* Make sure all related logs (copies of code that may + * be spread in different NUT common libs) start on the + * same note; execute this call before everything else, + * at the cost of a temporary otherwise useless variable. */ + const struct timeval *upslog_start_tmp = upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + int interval = 30, opt_ret = 0, i, foreground = -1, prefix_UPSHOST = 0, logformat_allocated = 0; size_t monhost_len = 0, loop_count = 0; - const char *prog = xbasename(argv[0]); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upslog"); const char *net_connect_timeout = NULL; time_t now, nextpoll = 0; const char *user = NULL; @@ -522,26 +534,60 @@ int main(int argc, char **argv) logformat = DEFAULT_LOGFORMAT; user = RUN_AS_USER; + NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + + /* NOTE: Debugging the client is primarily of use to developers, so + * it was not at all exposed via `-D[D...]` args until NUT v2.8.5. + * Since earlier 2.8.x releases, caller could `export NUT_DEBUG_LEVEL` + * to see debugs for the client and for NUT methods called from it. + */ + + /* Parse command line options -- First loop: only get debug level */ + /* Suppress error messages, for now -- leave them to the second loop. */ + opterr = 0; + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + if (opt_ret == 'D') + nut_debug_level++; + } + + if (!nut_debug_level) { + char *s = getenv("NUT_DEBUG_LEVEL"); + int l; + if (s && str_to_int(s, &l, 10) && l > 0) { + nut_debug_level = l; + upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " + "since none was requested by command-line options", l); + } /* else follow -D settings */ + } + #if (defined NUT_PLATFORM_AIX) && (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS callback_upsconf_args = do_upsconf_args; #endif + /* These lines aim to just initialize the logging subsystem, and set + * initial timestamp, for the eventuality that debugs would be printed: + */ + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); setproctag(prog); print_banner_once(prog, 0); - while ((i = getopt(argc, argv, "+hDs:l:i:d:Nf:u:Vp:FBm:W:")) != -1) { - switch(i) { + /* Parse command line options -- Second loop: everything else */ + /* Restore error messages... */ + opterr = 1; + /* ...and index of the item to be processed by getopt(). */ + optind = 1; + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + + switch (opt_ret) + { + case 'D': break; /* See nut_debug_level handled above */ case 'h': help(prog); #ifndef HAVE___ATTRIBUTE__NORETURN break; #endif - case 'D': - nut_debug_level++; - upscli_set_debug_level(nut_debug_level); - break; - case 'm': { /* var scope */ char *m_arg, *s; @@ -654,7 +700,7 @@ int main(int argc, char **argv) default: fatalx(EXIT_FAILURE, "Error: unknown option -%c. Try -h for help.", - (char)i); + (char)opt_ret); } } @@ -1013,6 +1059,9 @@ int main(int argc, char **argv) } } + /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ + setproctag(prog); + upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); upsnotify(NOTIFY_STATE_STOPPING, "Signal %d: exiting", exit_flag); @@ -1038,6 +1087,7 @@ int main(int argc, char **argv) logformat = NULL; } + upscli_cleanup(); exit(EXIT_SUCCESS); } diff --git a/clients/upsmon.c b/clients/upsmon.c index fea5cb4577..b17fb5c715 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -43,6 +43,10 @@ # include #endif +/* name-swap in libupsclient consumer to simplify the look of code base */ +#define builtin_setproctag(x) setproctag(x) +#define setproctag(x) do { builtin_setproctag(x); upscli_upslog_setproctag(x, nut_common_cookie()); } while(0) + static char *shutdowncmd = NULL, *notifycmd = NULL; static char *powerdownflag = NULL, *configfile = NULL; @@ -2670,7 +2674,7 @@ static void loadconfig(void) nut_debug_level_args); nut_debug_level = nut_debug_level_args; } - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); if (pollfail_log_throttle_max >= 0) { upslogx(LOG_INFO, @@ -3393,6 +3397,9 @@ static int check_pdflag(void) static void help(const char *arg_progname) __attribute__((noreturn)); +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+DFBhic:P:f:pu:VK46W:"; + static void help(const char *arg_progname) { int old_debug_level = nut_debug_level; @@ -3408,14 +3415,14 @@ static void help(const char *arg_progname) nut_debug_level = -2; nut_debug_level_args = -2; nut_debug_level_global = -2; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); loadconfig(); nut_debug_level = old_debug_level; nut_debug_level_args = old_debug_level_args; nut_debug_level_global = old_debug_level_global; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); /* Separate from logs emitted by loadconfig() */ /* printf("\n"); */ @@ -3785,9 +3792,14 @@ static void init_Inhibitor(const char *prog) int main(int argc, char *argv[]) { - const char *prog = xbasename(argv[0]); + /* Make sure all related logs (copies of code that may + * be spread in different NUT common libs) start on the + * same note; execute this call before everything else, + * at the cost of a temporary otherwise useless variable. */ + const struct timeval *upslog_start_tmp = upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsmon"); const char *net_connect_timeout = NULL; - int i, cmdret = -1, checking_flag = 0, foreground = -1; + int opt_ret = 0, cmdret = -1, checking_flag = 0, foreground = -1; struct timeval prevstart; #ifndef WIN32 @@ -3800,23 +3812,11 @@ int main(int argc, char *argv[]) HANDLE handles[MAXIMUM_WAIT_OBJECTS]; int maxhandle = 0; pipe_conn_t *conn; - - /* remove trailing .exe */ - char * drv_name; - drv_name = (char *)xbasename(argv[0]); - char * name = strrchr(drv_name,'.'); - if( name != NULL ) { - if(strcasecmp(name, ".exe") == 0 ) { - prog = strdup(drv_name); - char * t = strrchr(prog,'.'); - *t = 0; - } - } - else { - prog = drv_name; - } #endif /* WIN32 */ + NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + print_banner_once(prog, 0); /* if no configuration file is specified on the command line, use default */ @@ -3826,8 +3826,8 @@ int main(int argc, char *argv[]) run_as_user = xstrdup(RUN_AS_USER); - while ((i = getopt(argc, argv, "+DFBhic:P:f:pu:VK46W:")) != -1) { - switch (i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch (opt_ret) { case 'c': if (!strncmp(optarg, "fsd", strlen(optarg))) { cmd = SIGCMD_FSD; @@ -3841,18 +3841,18 @@ int main(int argc, char *argv[]) /* bad command name given */ if (cmd == 0) - help(argv[0]); + help(prog); break; #ifndef WIN32 case 'P': if ((oldpid = parsepid(optarg)) < 0) - help(argv[0]); + help(prog); break; #endif /* !WIN32 */ case 'D': nut_debug_level++; nut_debug_level_args++; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); break; case 'F': foreground = 1; @@ -3865,7 +3865,7 @@ int main(int argc, char *argv[]) configfile = xstrdup(optarg); break; case 'h': - help(argv[0]); + help(prog); #ifndef HAVE___ATTRIBUTE__NORETURN break; #endif @@ -3895,7 +3895,7 @@ int main(int argc, char *argv[]) net_connect_timeout = optarg; break; default: - help(argv[0]); + help(prog); #ifndef HAVE___ATTRIBUTE__NORETURN break; #endif @@ -3921,7 +3921,7 @@ int main(int argc, char *argv[]) nut_debug_level_args = l; } /* else follow -D settings */ } /* else nothing to bother about */ - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); } if (cmd) { @@ -4073,7 +4073,7 @@ int main(int argc, char *argv[]) * from loadconfig() for just checking the killpower flag */ if (nut_debug_level == 0) { nut_debug_level = -2; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); } } @@ -4085,7 +4085,7 @@ int main(int argc, char *argv[]) */ if (nut_debug_level_global > nut_debug_level) { nut_debug_level = nut_debug_level_global; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); } upsdebugx(1, "debug level is '%d'", nut_debug_level); diff --git a/clients/upsrw.c b/clients/upsrw.c index 662e816036..a861c43211 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -3,6 +3,7 @@ Copyright (C) 1999 Russell Kroll 2019 EATON (author: Arnaud Quette ) + 2020-2026 Jim Klimov 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 @@ -35,6 +36,10 @@ #include "upsclient.h" #include "extstate.h" +/* name-swap in libupsclient consumer to simplify the look of code base */ +#define builtin_setproctag(x) setproctag(x) +#define setproctag(x) do { builtin_setproctag(x); upscli_upslog_setproctag(x, nut_common_cookie()); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" @@ -48,7 +53,10 @@ struct list_t { struct list_t *next; }; -static void usage(const char *prog) +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+Dhls:p:t:u:wVW:"; + +static void help(const char *prog) { print_banner_once(prog, 2); printf("NUT administration client program to set variables within UPS hardware.\n"); @@ -70,6 +78,7 @@ static void usage(const char *prog) printf(" -V - display the version of this software\n"); printf(" -W - network timeout for initial connections (default: %s)\n", UPSCLI_DEFAULT_CONNECT_TIMEOUT); + printf(" -D - raise debugging level\n"); printf(" -h - display this help text\n"); printf("\n"); printf("Call without -s to show all possible read/write variables (same as -l).\n"); @@ -90,9 +99,9 @@ static void clean_exit(void) free(hostname); free(ups); - /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ + upscli_cleanup(); + upsdebugx(1, "%s: finished, exiting", __func__); - setproctag(NULL); } #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) ) @@ -654,32 +663,65 @@ static void print_rwlist(void) int main(int argc, char **argv) { - int i; + /* Make sure all related logs (copies of code that may + * be spread in different NUT common libs) start on the + * same note; execute this call before everything else, + * at the cost of a temporary otherwise useless variable. */ + const struct timeval *upslog_start_tmp = upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + int opt_ret = 0; uint16_t port; - const char *prog = xbasename(argv[0]); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsrw"); const char *net_connect_timeout = NULL; - char *password = NULL, *username = NULL, *setvar = NULL, *s = NULL; + char *password = NULL, *username = NULL, *setvar = NULL; - setproctag(prog); - /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc - * and NUT methods called from it. This line aims to just initialize - * the subsystem, and set initial timestamp. Debugging the client is - * primarily of use to developers, so is not exposed via `-D` args. + NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + + /* NOTE: Debugging the client is primarily of use to developers, so + * it was not at all exposed via `-D[D...]` args until NUT v2.8.5. + * Since earlier 2.8.x releases, caller could `export NUT_DEBUG_LEVEL` + * to see debugs for the client and for NUT methods called from it. */ - s = getenv("NUT_DEBUG_LEVEL"); - if (s && str_to_int(s, &i, 10) && i > 0) { - nut_debug_level = i; - upscli_set_debug_level(nut_debug_level); + + /* Parse command line options -- First loop: only get debug level */ + /* Suppress error messages, for now -- leave them to the second loop. */ + opterr = 0; + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + if (opt_ret == 'D') + nut_debug_level++; } + + if (!nut_debug_level) { + char *s = getenv("NUT_DEBUG_LEVEL"); + int l; + if (s && str_to_int(s, &l, 10) && l > 0) { + nut_debug_level = l; + upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " + "since none was requested by command-line options", l); + } /* else follow -D settings */ + } + + /* These lines aim to just initialize the logging subsystem, and set + * initial timestamp, for the eventuality that debugs would be printed: + */ + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); + setproctag(prog); upsdebugx(1, "Starting NUT client: %s", prog); #if (defined NUT_PLATFORM_AIX) && (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, "+hls:p:t:u:wVW:")) != -1) { - switch (i) + /* Parse command line options -- Second loop: everything else */ + /* Restore error messages... */ + opterr = 1; + /* ...and index of the item to be processed by getopt(). */ + optind = 1; + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + + switch (opt_ret) { + case 'D': break; /* See nut_debug_level handled above */ case 's': setvar = optarg; break; @@ -713,7 +755,7 @@ int main(int argc, char **argv) break; case 'h': default: - usage(prog); + help(prog); exit(EXIT_SUCCESS); } } @@ -723,11 +765,13 @@ int main(int argc, char **argv) net_connect_timeout); } + /* Simplify offset numbering to look at command-line + * arguments (if any) after the options checked above */ argc -= optind; argv += optind; if (argc < 1) { - usage(prog); + help(prog); exit(EXIT_SUCCESS); } @@ -737,7 +781,7 @@ int main(int argc, char **argv) if (upscli_splitname(argv[0], &upsname, &hostname, &port) != 0) { fatalx(EXIT_FAILURE, "Error: invalid UPS definition. Required format: upsname[@hostname[:port]]"); } - setproctag(argv[0]); + setproctag(argv[0]); /* ups[@host[:port]] */ ups = (UPSCONN_t *)xcalloc(1, sizeof(*ups)); @@ -753,6 +797,8 @@ int main(int argc, char **argv) print_rwlist(); } + /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ + setproctag(prog); exit(EXIT_SUCCESS); } diff --git a/clients/upssched.c b/clients/upssched.c index b201d82c1d..49843dd71f 100644 --- a/clients/upssched.c +++ b/clients/upssched.c @@ -7,7 +7,7 @@ 2006-2019 Arjen de Korte 2006-2007 Peter Selinger 2010-2012 Frederic BOHE - 2020-2025 Jim Klimov + 2020-2026 Jim Klimov 2022 Dimitris Economou This program is free software; you can redistribute it and/or modify @@ -2170,6 +2170,9 @@ static void checkconf(void) static void help(const char *arg_progname) __attribute__((noreturn)); +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+DVhl"; + static void help(const char *arg_progname) { printf("upssched: upsmon's scheduling helper for offset timers\n"); @@ -2190,22 +2193,20 @@ static void help(const char *arg_progname) int main(int argc, char **argv) { - int i, argn = 0; + int opt_ret, argn = 0; - if (argc > 0) - prog = xbasename(argv[0]); - if (!prog) - prog = "upssched"; + /* Here this is a global variable, used also in start_daemon() */ + prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upssched"); - while ((i = getopt(argc, argv, "+DVhl")) != -1) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { argn++; - switch (i) { + switch (opt_ret) { case 'D': nut_debug_level_args++; break; case 'h': - help(argv[0]); + help(prog); #ifndef HAVE___ATTRIBUTE__NORETURN break; #endif @@ -2222,7 +2223,7 @@ int main(int argc, char **argv) default: fatalx(EXIT_FAILURE, "Error: unknown option -%c. Try -h for help.", - (char)i); + (char)opt_ret); } } diff --git a/clients/upsset.c b/clients/upsset.c index 36d231f7d3..20f70bf8b6 100644 --- a/clients/upsset.c +++ b/clients/upsset.c @@ -45,6 +45,10 @@ struct list_t { #define HARD_UPSVAR_LIMIT_NUM 64 #define HARD_UPSVAR_LIMIT_LEN 256 +/* name-swap in libupsclient consumer to simplify the look of code base */ +#define builtin_setproctag(x) setproctag(x) +#define setproctag(x) do { builtin_setproctag(x); upscli_upslog_setproctag(x, nut_common_cookie()); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" @@ -1111,6 +1115,13 @@ static void check_conf(void) exit(EXIT_FAILURE); } +static void clean_exit(void) +{ + upscli_cleanup(); + + upsdebugx(1, "%s: finished, exiting", __func__); +} + int main(int argc, char **argv) { char *s; @@ -1133,8 +1144,10 @@ int main(int argc, char **argv) setmode(STDIN_FILENO, O_BINARY); #endif - NUT_UNUSED_VARIABLE(argc); - NUT_UNUSED_VARIABLE(argv); + upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsset(CGI)"); + username = password = function = monups = NULL; printf("Content-type: text/html\n"); @@ -1149,7 +1162,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); } #ifdef NUT_CGI_DEBUG_UPSSET @@ -1159,7 +1172,7 @@ int main(int argc, char **argv) # endif /* Un-comment via make flags when developer-troubleshooting: */ nut_debug_level = NUT_CGI_DEBUG_UPSSET; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); #endif if (nut_debug_level > 0) { @@ -1171,6 +1184,7 @@ int main(int argc, char **argv) check_conf(); upscli_init_default_connect_timeout(NULL, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT); + atexit(clean_exit); extractpostargs(); diff --git a/clients/upsstats.c b/clients/upsstats.c index 55ad9b8cf4..ab14107d51 100644 --- a/clients/upsstats.c +++ b/clients/upsstats.c @@ -33,6 +33,10 @@ #define MAX_CGI_STRLEN 128 #define MAX_PARSE_ARGS 16 +/* name-swap in libupsclient consumer to simplify the look of code base */ +#define builtin_setproctag(x) setproctag(x) +#define setproctag(x) do { builtin_setproctag(x); upscli_upslog_setproctag(x, nut_common_cookie()); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" @@ -1677,6 +1681,11 @@ static void display_json(void) /* --- END: NEW JSON FUNCTION ---------------------------------- */ /* ------------------------------------------------------------- */ +static void clean_exit(void) +{ + upscli_cleanup(); + upsdebugx(1, "%s: finished, exiting", __func__); +} int main(int argc, char **argv) { @@ -1698,8 +1707,9 @@ int main(int argc, char **argv) setmode(STDOUT_FILENO, O_BINARY); #endif - NUT_UNUSED_VARIABLE(argc); - NUT_UNUSED_VARIABLE(argv); + upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsstats(CGI)"); /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc * and NUT methods called from it. This line aims to just initialize @@ -1709,7 +1719,7 @@ int main(int argc, char **argv) s = getenv("NUT_DEBUG_LEVEL"); if (s && str_to_int(s, &i, 10) && i > 0) { nut_debug_level = i; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); } @@ -1720,7 +1730,7 @@ int main(int argc, char **argv) # endif /* Un-comment via make flags when developer-troubleshooting: */ nut_debug_level = NUT_CGI_DEBUG_UPSSTATS; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); #endif if (nut_debug_level > 0) { @@ -1738,6 +1748,7 @@ int main(int argc, char **argv) extractcgiargs(); upscli_init_default_connect_timeout(NULL, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT); + atexit(clean_exit); /* * If json is in the query, bypass all HTML and call display_json() diff --git a/common/common.c b/common/common.c index 71312a5e26..6ebb5c0b27 100644 --- a/common/common.c +++ b/common/common.c @@ -47,8 +47,102 @@ # include #endif -static const char * getmyprocname(void); -static const char * getmyprocbasename(void); +/* Just yield a unique value - e.g. address of a statically allocated variable + * which would be different if several copies of NUT-common object code are + * loaded in memory, e.g. as part of a monolithic NUT client (no libnutprivate + * parts) AND as part of libupsclient dynamically linked with it at run-time). */ +const void *nut_common_cookie(void) +{ + static char cookie = 0x42; + return &cookie; +} + +/* Consistently handle atexit() for internal data of this common library */ +static void nut_free_search_paths(void); +#if (defined WITH_LIBSYSTEMD_INHIBITOR) && (defined WITH_LIBSYSTEMD && WITH_LIBSYSTEMD) && (defined WITH_LIBSYSTEMD_INHIBITOR && WITH_LIBSYSTEMD_INHIBITOR) && !(defined(WITHOUT_LIBSYSTEMD) && (WITHOUT_LIBSYSTEMD)) +static void close_sdbus_once(void); +#endif +#ifdef WIN32 +static void win_PREFIX_cleanup(void); +#endif +static void cleanup_progname_argv0_default(void); +static void proctag_cleanup(void); +static void procname_cleanup(void); + +static struct { + void (*func)(void); + char registered; +} nut_common_atexit_handlers[] = { + /* Listed in order of desired clean-up execution, if activated */ + { nut_free_search_paths, 0 }, +#if (defined WITH_LIBSYSTEMD_INHIBITOR) && (defined WITH_LIBSYSTEMD && WITH_LIBSYSTEMD) && (defined WITH_LIBSYSTEMD_INHIBITOR && WITH_LIBSYSTEMD_INHIBITOR) && !(defined(WITHOUT_LIBSYSTEMD) && (WITHOUT_LIBSYSTEMD)) + { close_sdbus_once, 0 }, +#endif +#ifdef WIN32 + { win_PREFIX_cleanup, 0 }, +#endif + { cleanup_progname_argv0_default, 0 }, + + /* These should be last due to debug-trace logging. + * And actually any one of them actually runs (nested). */ + { proctag_cleanup, 0 }, + { procname_cleanup, 0 }, + + /* Sentinel */ + { NULL, 0 } +}; + +static void nut_common_atexit_cleanup(void) +{ + size_t i; + int iPN = -1, iPT = -1; + + /* Monkey-patch */ + for (i = 0; nut_common_atexit_handlers[i].func; i++) { + if (nut_common_atexit_handlers[i].func == proctag_cleanup) { + iPT = i; + } + if (nut_common_atexit_handlers[i].func == procname_cleanup) { + iPN = i; + } + } + + if (iPN >= 0 && iPT >= 0) { + if (nut_common_atexit_handlers[iPN].registered + && nut_common_atexit_handlers[iPT].registered + ) { + /* Only call procname_cleanup(), and it calls + * proctag_cleanup() at the right moment for + * sensible logging */ + nut_common_atexit_handlers[iPT].registered = 0; + } + } + + for (i = 0; nut_common_atexit_handlers[i].func; i++) { + if (nut_common_atexit_handlers[i].registered) { + (*(nut_common_atexit_handlers[i].func))(); + nut_common_atexit_handlers[i].registered = 0; + } + } +} + +static void nut_common_atexit(void (*func)(void)) +{ + static char registered = 0; + size_t i; + + for (i = 0; nut_common_atexit_handlers[i].func; i++) { + if (nut_common_atexit_handlers[i].func == func) { + nut_common_atexit_handlers[i].registered = 1; + break; + } + } + + if (!registered) { + atexit(nut_common_atexit_cleanup); + registered = 1; + } +} #if (defined WITH_LIBSYSTEMD_INHIBITOR) && (defined WITH_LIBSYSTEMD && WITH_LIBSYSTEMD) && (defined WITH_LIBSYSTEMD_INHIBITOR && WITH_LIBSYSTEMD_INHIBITOR) && !(defined(WITHOUT_LIBSYSTEMD) && (WITHOUT_LIBSYSTEMD)) # ifdef HAVE_SYSTEMD_SD_BUS_H @@ -137,7 +231,8 @@ static int open_sdbus_once(const char *caller) { if (systemd_bus && !openedOnce) { openedOnce = 1; - atexit(close_sdbus_once); + nut_common_atexit(close_sdbus_once); + upsdebugx(5, "%s: registered nut_common_atexit(close_sdbus_once)", __func__); } if (systemd_bus) { @@ -548,6 +643,39 @@ pid_t get_max_pid_t(void) struct timeval upslog_start = { 0, 0 }; +/* The NUT common library code is included in several other + * libraries, often with their private copies of variables, + * so we want to synchronize them. + * If internal `upslog_start` value is not yet set, we set + * it from *tv (or current time if tv==NULL), otherwise the + * method is no-op (keep the original setting). + * Returns the currently set value so it can be propagated. + */ +struct timeval *upslog_start_sync(struct timeval *tv) { + if (tv == &upslog_start) + return tv; + + if (upslog_start.tv_sec == 0 || upslog_start.tv_usec == 0) { + if (tv && (tv->tv_sec > 0 || tv->tv_usec > 0)) { + upslog_start = *tv; + } else { + struct timeval now; + + gettimeofday(&now, NULL); + upslog_start = now; + +#ifdef WIN32 + /* Ensure line buffering for sane logs on Windows console + * especially when many threads/daemons write there. */ + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); + /* Also stdout (some messages go there) for good measure: */ + setvbuf(stdout, NULL, _IOLBF, BUFSIZ); +#endif + } + } + return &upslog_start; +} + static void xbit_set(int *val, int flag) { *val |= flag; @@ -900,8 +1028,34 @@ void chroot_start(const char *path) /* In forking, assume process name does not change (PID might); cache it */ static char *myProcName = NULL; +static int procname_cleanup_registered = 0; static const char *myProcBaseName = NULL; + +/* We also keep a buffer with prefixed colon for debug printouts. + * Var/method used in procname_cleanup(), implemented further in the file */ +static char *proctag = NULL, *proctag_for_upsdebug = NULL, + *proctag_lib = NULL; +static int proctag_cleanup_registered = 0; + static void procname_cleanup(void) { + char *myBN, *myPN, *myPT, *myLT, *myPTU; + + if (procname_cleanup_registered < 0) + return; /* already ran */ + + myBN = (myProcBaseName ? xstrdup(myProcBaseName) : NULL); + myPN = (myProcName ? xstrdup(myProcName) : NULL); + myPT = (proctag ? xstrdup(proctag) : NULL); + myLT = (proctag_lib ? xstrdup(proctag_lib) : NULL); + myPTU = (proctag_for_upsdebug ? xstrdup(proctag_for_upsdebug) : NULL); + + upsdebugx(3, "%s: starting for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + __func__, NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + + if (proctag_cleanup_registered > 0) { + proctag_cleanup(); /* calls getmyprocname() */ + } + if (myProcBaseName) { /* points to inside of myProcName */ myProcBaseName = NULL; @@ -910,23 +1064,57 @@ static void procname_cleanup(void) { free(myProcName); myProcName = NULL; } + + procname_cleanup_registered = -1; + + if (myPTU || myLT) { + upsdebugx(5, "{%s}: %s: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + myPTU ? myPTU : myLT, __func__, NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + } else { + upsdebugx(5, "%s: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + __func__, NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + } + + if (myBN) free(myBN); + if (myPN) free(myPN); + if (myPT) free(myPT); + if (myLT) free(myLT); + if (myPTU) free(myPTU); } -static const char * getmyprocname(void) +/* Exported for internal use between NUT libraries + * Gets caller-allocated string which this method frees if not NULL (in atexit()) + */ +void setmyprocname(const char *s) { - if (myProcName) - return (const char *)myProcName; + if (s) { + if (myProcName) + free(myProcName); + /* NOTE: Reference (to free() later), not copy! */ + myProcName = (char *)s; + } - myProcName = getprocname(getpid()); /* no xstrdup, we own and free this later */ if (myProcName) { myProcBaseName = xbasename(myProcName); /* substring inside myProcName */ - atexit(procname_cleanup); + if (procname_cleanup_registered < 1) { + nut_common_atexit(procname_cleanup); + upsdebugx(5, "%s: registered nut_common_atexit(procname_cleanup)", __func__); + } + procname_cleanup_registered = 1; } +} + +const char * getmyprocname(void) +{ + if (myProcName) + return (const char *)myProcName; + + setmyprocname(getprocname(getpid())); /* no xstrdup, we own that return value and free this memory later */ return (const char *)myProcName; } -static const char * getmyprocbasename(void) +const char * getmyprocbasename(void) { getmyprocname(); return myProcBaseName; @@ -1864,7 +2052,8 @@ char * getfullpath(char * relative_path) if (win_PREFIX == NULL) { win_PREFIX = xstrdup(PREFIX); - atexit(win_PREFIX_cleanup); + nut_common_atexit(win_PREFIX_cleanup); + upsdebugx(5, "%s: registered nut_common_atexit(win_PREFIX_cleanup)", __func__); last_slash = win_PREFIX; while ( (last_slash = strchr(last_slash, '/')) ) { *last_slash = '\\'; @@ -2614,7 +2803,7 @@ const char *xbasename(const char *file) const char *p = strrchr(file, '\\'); const char *r = strrchr(file, '/'); /* if not found, try '/' */ - if( r > p ) { + if (r > p) { p = r; } #endif /* WIN32 */ @@ -2624,6 +2813,155 @@ const char *xbasename(const char *file) return p + 1; } +char *xbasename_no_ext(const char *file) +{ + const char *cs; + char *bn = NULL; + static char *exeext = NULL; + static size_t exeext_len = 0; + + if (!file || !*file) + return NULL; + + cs = xbasename(file); + if (!cs || !*cs) + return NULL; + +#ifdef WIN32 + /* Special handling for a known outlier (for man pages, etc.) */ + if (!strcasecmp(cs, "nut.exe")) + return xstrdup(cs); + + if (!strcasecmp(cs, "nut")) + return xstrdup("nut.exe"); +#endif + + /* Some compilers detect that conditions are not changing at run-time: */ +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + if (!exeext) { +#ifdef EXEEXT + exeext = EXEEXT; +#endif +#ifdef WIN32 + if (!exeext || !*exeext) + exeext = ".exe"; +#endif + + exeext_len = strlen(exeext); + } + + if (exeext_len > 0) { + size_t cs_len = strlen(cs), + bn_len = (cs_len > exeext_len ? cs_len - exeext_len : 0); + + if (bn_len) { + /* TOTHINK: Generalize provided-if-missing strcasestr()? + * One implementation is currently tucked away in + * libusb0.c because net-snmp may provide another... + */ + char *s = strstr(cs, exeext); + if (s && (bn_len == (size_t)(s - cs))) { + /* s points to first character that matches exeext, + * this character is what we already do not want */ + bn = (char*)xmalloc(bn_len + 1); + /* Extract first bn_len characters, add '\0' in the end */ + snprintf(bn, bn_len, "%s", cs); + } + } + } + + if (!bn) { + bn = xstrdup(cs); + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic pop +#endif + + return bn; +} + +char *xbasename_no_ext_default(const char *file, const char *fallback) { + char *bn = xbasename_no_ext(file); + + if (bn && *bn) + return bn; + + if (!fallback || !*fallback) { + return xstrdup(fallback); + } + + return xstrdup(fallback); +} + +/* Keep tabs on the value from setprogname_argv0_default() + * to possibly free() in the end */ +static char *progname_argv0_default = NULL; +static void cleanup_progname_argv0_default(void) { + if (progname_argv0_default) { + free(progname_argv0_default); + progname_argv0_default = NULL; + } +} + +/* Take the caller-allocated string and keep to free() later */ +static const char *reset_progname_argv0_default(char *arg) { + static int atexit_hooked = 0; + + if (!atexit_hooked) { + /* First time here */ + nut_common_atexit(cleanup_progname_argv0_default); + atexit_hooked = 1; + upsdebugx(5, "%s: registered nut_common_atexit(cleanup_progname_argv0_default)", __func__); + } + + cleanup_progname_argv0_default(); + progname_argv0_default = arg; + return (const char *)progname_argv0_default; +} + +const char *getprogname_argv0_default(const char *file, const char *fallback) { + if (!file || !*file) { + if (!fallback || !*fallback) { + if (progname_argv0_default && *progname_argv0_default) { + return (const char *)progname_argv0_default; + } + return reset_progname_argv0_default(xstrdup("UNDEFINED")); + } + return fallback; + } else { + const char *cs = xbasename(file); + if (!cs || !*cs) { + if (!fallback || !*fallback) { + return reset_progname_argv0_default(xstrdup("UNDEFINED")); + } + return fallback; + } else { + char *bn = xbasename_no_ext(cs); /* New allocation with non-trivial text or NULL */ + if (bn) { + if (!strcmp(bn, cs)) { + free(bn); + return cs; + } + return reset_progname_argv0_default(bn); + } + return cs; + } + } +} + /* Based on https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html * modified for a syntax similar to difftime() */ @@ -3842,6 +4180,8 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) #ifdef HAVE_VA_COPY_VARIANT va_list va_snprintf; + errno = 0; + /* va_copy() to avoid mangling on re-use (see issue #2948), * this lets us retry safely vsnprintf() with the VA copies */ va_copy(va_snprintf, va); @@ -3853,6 +4193,22 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) ret = vsnprintf(buf, bufsize, fmt, va); #endif + /* NOTE: we may have ret<0 due to other errors, e.g. format + * string errors/typos leading to log reports like: + * 84 => Invalid or incomplete multibyte or wide character + * but implementations of *printf() methods are not required + * to say why they failed (e.g. set a specific errno); some + * may do so though. */ + if (ret < 0 && errno == EILSEQ) { + if (nut_debug_level > 0) { + fprintf(stderr, "[D0] WARNING: vupslog: " + "vsnprintf could not handle the " + "inputs: %d (%d => %s)\n", + ret, errno, strerror(errno)); + } + break; + } + if ((ret < 0) || ((uintmax_t)ret >= (uintmax_t)bufsize)) { /* Not building the below block on systems without va_copy(), * otherwise consumed VAs would get re-used & mangled on retries. @@ -3882,8 +4238,10 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) newbufsize = (size_t)ret + LARGEBUF; } /* else: errno, e.g. ERANGE printing: * "...(34 => Result too large)" */ - if (nut_debug_level > 0) { - fprintf(stderr, "WARNING: vupslog: " + if (nut_debug_level > 0 + && (nut_debug_level > 5 || bufsize > LARGEBUF) + ) { + fprintf(stderr, "[D0] WARNING: vupslog: " "vsnprintf needed more than %" PRIuSIZE " bytes: %d (%d => %s)," " extending to %" PRIuSIZE "\n", @@ -3958,21 +4316,9 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) } /* Note: nowadays debug level can be changed during run-time, - * so mark the starting point whenever we first try to log */ - if (upslog_start.tv_sec == 0) { - struct timeval now; - - gettimeofday(&now, NULL); - upslog_start = now; - -#ifdef WIN32 - /* Ensure line buffering for sane logs on Windows console - * especially when many threads/daemons write there. */ - setvbuf(stderr, NULL, _IOLBF, BUFSIZ); - /* Also stdout (some messages go there) for good measure: */ - setvbuf(stdout, NULL, _IOLBF, BUFSIZ); -#endif - } + * so mark the starting point (if not yet set) whenever we + * first try to log */ + upslog_start_sync(NULL); if (xbit_test(upslog_flags, UPSLOG_STDERR) || xbit_test(upslog_flags, UPSLOG_STDOUT)) { if (nut_debug_level > 0) { @@ -4241,55 +4587,44 @@ void upslogx(int priority, const char *fmt, ...) va_end(va); } -/* also keep a buffer with prefixed colon for debug printouts */ -static char *proctag = NULL, *proctag_for_upsdebug = NULL, - proctag_cleanup_registered = 0; - static void proctag_cleanup(void) { - if (proctag) { - char *pn = xstrdup(getmyprocbasename()); - char *tn = xstrdup(proctag); + char *myBN, *myPN, *myPT, *myLT, *myPTU; -#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -#pragma GCC diagnostic push -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -#pragma GCC diagnostic ignored "-Wunreachable-code" -#endif -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - if (strlen(EXEEXT) > 0) { - /* TOTHINK: Generalize provided-if-missing strcasestr()? */ - char *s; - if (pn) { - s = strstr(pn, EXEEXT); - if (s) *s='\0'; - } + if (proctag_cleanup_registered < 0) + return; /* already ran */ - if (tn) { - s = strstr(tn, EXEEXT); - if (s) *s='\0'; - } - } -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -#pragma GCC diagnostic pop -#endif + myBN = (myProcBaseName ? xstrdup(myProcBaseName) : NULL); + myPN = (myProcName ? xstrdup(myProcName) : NULL); + myPT = (proctag ? xstrdup(proctag) : NULL); + myLT = (proctag_lib ? xstrdup(proctag_lib) : NULL); + myPTU = (proctag_for_upsdebug ? xstrdup(proctag_for_upsdebug) : NULL); + + upsdebugx(3, "%s: starting for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + __func__, NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + + if (proctag) { + char *pn = xbasename_no_ext(getmyprocbasename()); + char *tn = xbasename_no_ext(proctag); if (pn && tn && !strcmp(pn, tn)) { /* Avoid reporting this line as misleading "sub-process" * after a plain singular setproctag(progname) call in * a NUT program: */ - upsdebugx(2, "a process (%s) is exiting now", pn); + if (myLT) { + upsdebugx(2, "library (%s) used by a process (%s) is exiting now", myLT, pn); + } else { + upsdebugx(2, "a process (%s) is exiting now", pn); + } } else { /* Some ptr not set, or strings not equal */ - upsdebugx(2, "a %s sub-process (%s) is exiting now", - NUT_STRARG(pn), proctag); + if (myLT) { + upsdebugx(2, "library (%s) used by a %s sub-process (%s) is exiting now", + myLT, NUT_STRARG(pn), proctag); + } else { + upsdebugx(2, "a %s sub-process (%s) is exiting now", + NUT_STRARG(pn), proctag); + } } if (pn) @@ -4298,7 +4633,49 @@ static void proctag_cleanup(void) free(tn); } + /* Free some global vars */ + if (proctag_lib) { + free(proctag_lib); + proctag_lib = NULL; + } + + /* Frees the remaining global vars */ setproctag(NULL); + + proctag_cleanup_registered = -1; + + if (myPTU || myLT) { + upsdebugx(5, "{%s}: %s: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + myPTU ? myPTU : myLT, __func__, NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + } else { + upsdebugx(5, "%s: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + __func__, NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + } + + if (myBN) free(myBN); + if (myPN) free(myPN); + if (myPT) free(myPT); + if (myLT) free(myLT); + if (myPTU) free(myPTU); +} + +/* privately exported for internal libs for their quiet init without + * debug log noise like getmyprocname() below - in fact, help identify it */ +const char *setproctag_lib_once(const char *val); +const char *setproctag_lib_once(const char *val) { + if (val && !proctag_lib) { + size_t proctag_lib_buflen = strlen(val) + 2; + proctag_lib = (char *)xcalloc(proctag_lib_buflen, sizeof(char)); + snprintf(proctag_lib, proctag_lib_buflen, ":%s", val); + + if (proctag_cleanup_registered < 1) { + nut_common_atexit(proctag_cleanup); + proctag_cleanup_registered = 1; + /* NOISY? // upsdebugx(8, "%s: registered nut_common_atexit(proctag_cleanup)", __func__); */ + } + } + + return (const char *)proctag_lib; } const char *getproctag(void) @@ -4309,7 +4686,26 @@ const char *getproctag(void) void setproctag(const char *tag) { size_t proctag_for_upsdebug_buflen = 0; - if (proctag) { + + upsdebugx(6, "%s: starting for '%s'...", __func__, NUT_STRARG(tag)); + if (proctag_cleanup_registered < 2 && tag) { + /* We would use this anyway in exit handler (probably many times + * for forked children), so better get it over with quickly. + * In libraries proctag_for_upsdebug may be pre-initialized + * and can show up here. + */ + upsdebugx(3, "%s: starting first tagging as '%s'...", __func__, NUT_STRARG(tag)); + getmyprocname(); + + if (proctag_cleanup_registered < 1) { + nut_common_atexit(proctag_cleanup); + upsdebugx(5, "%s: registered nut_common_atexit(proctag_cleanup)", __func__); + } + proctag_cleanup_registered = 2; + } + + if (proctag && proctag != tag) { + /* Take care to not free the caller's copy, or not too soon */ free(proctag); proctag = NULL; } @@ -4320,85 +4716,67 @@ void setproctag(const char *tag) } if (!tag) { - /* wipe */ + /* only wipe */ return; } - if (!proctag_cleanup_registered) { - /* We would use this anyway in exit handler (probably many times - * for forked children), so better get it over with quickly */ - getmyprocname(); - - atexit(proctag_cleanup); - proctag_cleanup_registered = 1; - } - /* let the caller's copy be freed */ - proctag = xstrdup(tag); + /* TOTHINK: */ + if (proctag != tag) + proctag = xstrdup(tag); proctag_for_upsdebug_buflen = strlen(tag) + 2; + if (proctag_lib) + proctag_for_upsdebug_buflen += strlen(proctag_lib) + 2; proctag_for_upsdebug = (char *)xcalloc(proctag_for_upsdebug_buflen, sizeof(char)); if (proctag_for_upsdebug) { - char *pn = xstrdup(getmyprocbasename()); - char *tn = xstrdup(tag); + char *pn = xbasename_no_ext(getmyprocbasename()); + char *tn = xbasename_no_ext(tag); int tagged = 0; -#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -#pragma GCC diagnostic push -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -#pragma GCC diagnostic ignored "-Wunreachable-code" -#endif -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - if (strlen(EXEEXT) > 0) { - /* TOTHINK: Generalize provided-if-missing strcasestr()? - * One implementation is currently tucked away in - * libusb0.c because net-snmp may provide another... - */ - char *s; - if (pn) { - s = strstr(pn, EXEEXT); - if (s) *s='\0'; - } - - if (tn) { - s = strstr(tn, EXEEXT); - if (s) *s='\0'; - } - } -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -#pragma GCC diagnostic pop -#endif - - if (pn && tn && getenv("NUT_DEBUG_PROCNAME") != NULL && strcmp(pn, tn)) { - /* Only add the process name if asked for and substantially - * different from tag value -- e.g. do not duplicate text - * when callers initialize with settagname(progname) */ - char *s = NULL; - proctag_for_upsdebug_buflen += strlen(pn) + 1; - s = (char *)xcalloc(proctag_for_upsdebug_buflen, sizeof(char)); - if (s) { - snprintf(s, proctag_for_upsdebug_buflen, ":%s:%s", pn, tag); - free(proctag_for_upsdebug); - proctag_for_upsdebug = s; + if (pn && tn) { + if (strcmp(pn, tn)) { + /* Only add the process name if asked for and substantially + * different from tag value -- e.g. do not duplicate text + * when callers initialize with setproctag(progname) */ + if (getenv("NUT_DEBUG_PROCNAME") != NULL) { + char *s = NULL; + proctag_for_upsdebug_buflen += strlen(pn) + 1; + s = (char *)xcalloc(proctag_for_upsdebug_buflen, sizeof(char)); + if (s) { + snprintf(s, proctag_for_upsdebug_buflen, ":%s%s:%s", pn, proctag_lib ? proctag_lib : "", tag); + free(proctag_for_upsdebug); + proctag_for_upsdebug = s; + tagged = 1; + } /* else alloc error */ + } + } else { + /* tn == pn, print procname before libname + * (don't care about envvar, this is an + * explicit tag too) */ + snprintf(proctag_for_upsdebug, proctag_for_upsdebug_buflen, ":%s%s", pn, proctag_lib ? proctag_lib : ""); tagged = 1; } } if (!tagged) { - snprintf(proctag_for_upsdebug, proctag_for_upsdebug_buflen, ":%s", tag); + snprintf(proctag_for_upsdebug, proctag_for_upsdebug_buflen, "%s:%s", proctag_lib ? proctag_lib : "", tag); } + upsdebugx(6, "%s: constructed proctag_for_upsdebug[%" PRIuSIZE "]='%s' from pn='%s' tn='%s' tl='%s' tag='%s'", + __func__, proctag_for_upsdebug_buflen, NUT_STRARG(proctag_for_upsdebug), + NUT_STRARG(pn), NUT_STRARG(tn), NUT_STRARG(proctag_lib), NUT_STRARG(tag)); + if (pn) free(pn); if (tn) free(tn); + } else { + /* alloc error, we'll print no proctag + * (but maybe libname, see vupslog) */ + upsdebugx(6, "%s: could not allocate proctag_for_upsdebug[%" PRIuSIZE "] from tl='%s' tag='%s'", + __func__, proctag_for_upsdebug_buflen, + NUT_STRARG(proctag_lib), NUT_STRARG(tag)); } } @@ -4432,12 +4810,12 @@ void s_upsdebug_with_errno(int level, const char *fmt, ...) * change during the run-time (forking etc.) */ ret = snprintf(fmt2, sizeof(fmt2), "[D%d:%" PRIiMAX "%s] %s", level, (intmax_t)getpid(), - proctag_for_upsdebug ? proctag_for_upsdebug : "", + proctag_for_upsdebug ? proctag_for_upsdebug : (proctag_lib ? proctag_lib : ""), fmt); } else { ret = snprintf(fmt2, sizeof(fmt2), "[D%d%s] %s", level, - proctag_for_upsdebug ? proctag_for_upsdebug : "", + proctag_for_upsdebug ? proctag_for_upsdebug : (proctag_lib ? proctag_lib : ""), fmt); } if ((ret < 0) || (ret >= (int) sizeof(fmt2))) { @@ -4496,12 +4874,12 @@ void s_upsdebugx(int level, const char *fmt, ...) * change during the run-time (forking etc.) */ ret = snprintf(fmt2, sizeof(fmt2), "[D%d:%" PRIiMAX "%s] %s", level, (intmax_t)getpid(), - proctag_for_upsdebug ? proctag_for_upsdebug : "", + proctag_for_upsdebug ? proctag_for_upsdebug : (proctag_lib ? proctag_lib : ""), fmt); } else { ret = snprintf(fmt2, sizeof(fmt2), "[D%d%s] %s", level, - proctag_for_upsdebug ? proctag_for_upsdebug : "", + proctag_for_upsdebug ? proctag_for_upsdebug : (proctag_lib ? proctag_lib : ""), fmt); } @@ -5111,8 +5489,9 @@ void nut_prepare_search_paths(void) { search_paths = filtered_search_paths; if (!atexit_hooked) { - atexit(nut_free_search_paths); + nut_common_atexit(nut_free_search_paths); atexit_hooked = 1; + upsdebugx(5, "%s: registered nut_common_atexit(nut_free_search_paths)", __func__); } } diff --git a/common/nutstream.cpp b/common/nutstream.cpp index d203eddb68..42bb7b646d 100644 --- a/common/nutstream.cpp +++ b/common/nutstream.cpp @@ -906,12 +906,44 @@ NutSocket::Address::Address(const std::vector & bytes, uint16_t p NutSocket::Address::Address(const Address & orig): m_sock_addr(nullptr), m_length(orig.m_length) { - void * copy = ::malloc(m_length); + /* Some libs define socklen_t as int, others as uint_32 etc. */ +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#pragma clang diagnostic ignored "-Wtautological-compare" +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif + if (m_length < 0 || static_cast(m_length) > SIZE_MAX) + throw std::bad_alloc(); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif + + void * copy = ::malloc(static_cast(m_length)); if (nullptr == copy) throw std::bad_alloc(); - ::memcpy(copy, orig.m_sock_addr, m_length); + ::memcpy(copy, orig.m_sock_addr, static_cast(m_length)); m_sock_addr = reinterpret_cast(copy); } diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 278aaec68e..8f48835884 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -546,13 +546,13 @@ SRC_DEV_PAGES = \ upscli_readline.txt \ upscli_report_build_details.txt \ upscli_sendline.txt \ - upscli_set_debug_level.txt \ upscli_splitaddr.txt \ upscli_splitname.txt \ upscli_ssl.txt \ upscli_ssl_caps.txt \ upscli_strerror.txt \ upscli_upserror.txt \ + upscli_upslog_set_debug_level.txt \ upscli_str_add_unique_token.txt \ upscli_str_contains_token.txt \ libnutclient.txt \ @@ -587,8 +587,9 @@ SRC_DEV_PAGES = \ nutscan_free_device.txt \ nutscan_add_option_to_device.txt \ nutscan_add_device_to_device.txt \ - nutscan_init.txt \ nutscan_get_serial_ports_list.txt \ + nutscan_upslog_set_debug_level.txt \ + nutscan_init.txt \ libupsclient-config.txt \ sockdebug.txt \ skel.txt @@ -705,10 +706,15 @@ UPSCLI_SSL_CAPS_DEPS = \ $(UPSCLI_SSL_CAPS_DEPS): upscli_ssl_caps.$(MAN_SECTION_API) -UPSCLI_SET_DEBUG_LEVEL_DEPS = \ - upscli_get_debug_level.$(MAN_SECTION_API) +UPSCLI_UPSLOG_SET_DEBUG_LEVEL_DEPS = \ + upscli_upslog_cookie.$(MAN_SECTION_API) \ + upscli_upslog_get_debug_level.$(MAN_SECTION_API) \ + upscli_upslog_start_sync.$(MAN_SECTION_API) \ + upscli_upslog_setprocname.$(MAN_SECTION_API) \ + upscli_upslog_getproctag.$(MAN_SECTION_API) \ + upscli_upslog_setproctag.$(MAN_SECTION_API) -$(UPSCLI_SET_DEBUG_LEVEL_DEPS): upscli_set_debug_level.$(MAN_SECTION_API) +$(UPSCLI_UPSLOG_SET_DEBUG_LEVEL_DEPS): upscli_upslog_set_debug_level.$(MAN_SECTION_API) INST_MAN_DEV_API_PAGES = \ upsclient.$(MAN_SECTION_API) \ @@ -730,8 +736,6 @@ INST_MAN_DEV_API_PAGES = \ upscli_report_build_details.$(MAN_SECTION_API) \ upscli_sendline.$(MAN_SECTION_API) \ upscli_sendline_timeout.$(MAN_SECTION_API) \ - upscli_set_debug_level.$(MAN_SECTION_API) \ - $(UPSCLI_SET_DEBUG_LEVEL_DEPS) \ upscli_splitaddr.$(MAN_SECTION_API) \ upscli_splitname.$(MAN_SECTION_API) \ upscli_ssl.$(MAN_SECTION_API) \ @@ -739,6 +743,8 @@ INST_MAN_DEV_API_PAGES = \ $(UPSCLI_SSL_CAPS_DEPS) \ upscli_strerror.$(MAN_SECTION_API) \ upscli_upserror.$(MAN_SECTION_API) \ + upscli_upslog_set_debug_level.$(MAN_SECTION_API) \ + $(UPSCLI_UPSLOG_SET_DEBUG_LEVEL_DEPS) \ upscli_str_add_unique_token.$(MAN_SECTION_API) \ upscli_str_contains_token.$(MAN_SECTION_API) \ libnutclient.$(MAN_SECTION_API) \ @@ -781,6 +787,8 @@ INST_MAN_DEV_API_PAGES = \ nutscan_add_commented_option_to_device.$(MAN_SECTION_API) \ nutscan_add_device_to_device.$(MAN_SECTION_API) \ nutscan_get_serial_ports_list.$(MAN_SECTION_API) \ + nutscan_upslog_set_debug_level.$(MAN_SECTION_API) \ + $(NUTSCAN_UPSLOG_SET_DEBUG_LEVEL_DEPS) \ nutscan_init.$(MAN_SECTION_API) # Alias page for one text describing two commands: @@ -808,6 +816,16 @@ nutscan_scan_ip_range_ipmi.$(MAN_SECTION_API): nutscan_scan_ipmi.$(MAN_SECTION_A nutscan_add_commented_option_to_device.$(MAN_SECTION_API): nutscan_add_option_to_device.$(MAN_SECTION_API) touch $@ +NUTSCAN_UPSLOG_SET_DEBUG_LEVEL_DEPS = \ + nutscan_upslog_cookie.$(MAN_SECTION_API) \ + nutscan_upslog_get_debug_level.$(MAN_SECTION_API) \ + nutscan_upslog_start_sync.$(MAN_SECTION_API) \ + nutscan_upslog_setprocname.$(MAN_SECTION_API) \ + nutscan_upslog_getproctag.$(MAN_SECTION_API) \ + nutscan_upslog_setproctag.$(MAN_SECTION_API) + +$(NUTSCAN_UPSLOG_SET_DEBUG_LEVEL_DEPS): nutscan_upslog_set_debug_level.$(MAN_SECTION_API) + INST_MAN_DEV_CMD_USR_PAGES = \ libupsclient-config.$(MAN_SECTION_CMD_USR) @@ -849,13 +867,13 @@ INST_HTML_DEV_MANS = \ upscli_readline.html \ upscli_report_build_details.html \ upscli_sendline.html \ - upscli_set_debug_level.html \ upscli_splitaddr.html \ upscli_splitname.html \ upscli_ssl.html \ upscli_ssl_caps.html \ upscli_strerror.html \ upscli_upserror.html \ + upscli_upslog_set_debug_level.html \ upscli_str_add_unique_token.html \ upscli_str_contains_token.html \ libnutclient.html \ @@ -891,6 +909,7 @@ INST_HTML_DEV_MANS = \ nutscan_add_option_to_device.html \ nutscan_add_device_to_device.html \ nutscan_get_serial_ports_list.html \ + nutscan_upslog_set_debug_level.html \ nutscan_init.html \ libupsclient-config.html \ sockdebug.html \ diff --git a/docs/man/nutconf.txt b/docs/man/nutconf.txt index 5f5ab6602b..b3d3a7add1 100644 --- a/docs/man/nutconf.txt +++ b/docs/man/nutconf.txt @@ -31,6 +31,10 @@ OPTIONS *-h* | *-help* | *--help*:: Display the help text. +*-D*:: +Raise the debugging level. Use this option multiple times for more details. +Overrides the optional `NUT_DEBUG_LEVEL` environment variable. + *-v* | *--verbose*:: Increase output verbosity (may be used multiple times). diff --git a/docs/man/nutscan_upslog_set_debug_level.txt b/docs/man/nutscan_upslog_set_debug_level.txt new file mode 100644 index 0000000000..3f6c88b1a4 --- /dev/null +++ b/docs/man/nutscan_upslog_set_debug_level.txt @@ -0,0 +1,75 @@ +NUTSCAN_SET_DEBUG_LEVEL(3) +========================== + +NAME +---- + +nutscan_upslog_set_debug_level, nutscan_upslog_get_debug_level, +nutscan_upslog_cookie, nutscan_upslog_setprocname, +nutscan_upslog_setproctag, nutscan_upslog_getproctag, +nutscan_upslog_start_sync - manipulate +the possibly separate (identified via cookie) debugging level and +sub-process tags for NUT common code in the nutscan library; +propagate to `libupsclient` if already loaded. + +SYNOPSIS +-------- + +------ + #include + + const void *nutscan_upslog_cookie(void); + + void nutscan_set_debug_level(int level), const void *cookie; + int nutscan_get_debug_level(void); + + void nutscan_setprocname(const char *full_procname, const void *cookie); + void nutscan_setproctag(const char *tag, const void *cookie); + const char *nutscan_getproctag(void); + + struct timeval *nutscan_upslog_start_sync(struct timeval *tv, const void *cookie); +------ + +DESCRIPTION +----------- + +The NUT common library code is included in several other +libraries, often with their private copies of variables, +so we want to synchronize them. + +It can get even more confusing with libnutprivate-common being a shared +dynamically loaded library instance behind both the program and libnutscan +(and maybe further libupsclient), hence the cookies: direct NUT-common code +consumers like NUT in-tree clients can use their `nut_common_cookie()` value +to pass into methods here. Third-party clients may safely pass 'NULL'. + +The *nutscan_set_debug_level()* function sets internal debug verbosity +for common NUT code in the library and optionally into the loaded +`libupsclient` (*nutscan_init()* function must be called at least once +before this then). + +If internal `upslog_start` value is not yet set, we can set it with +*nutscan_upslog_start_sync()* from *tv (or current time if tv==NULL), +otherwise the method is no-op (keep and report the original setting). + +Returns the pointer to the currently set value, so it +can be propagated or used in difftime() computations. + +NOTE: In WIN32 builds also enforces line-buffering for +stdout and stderr streams. + + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-init.h' file. + +For legacy reasons, the `nut_debug_level` (or its copy from the NUT common +library objects linked into `libnutscan`) and `setproctag()` method are +also exposed, but should not be used directly; may be removed in later +releases. + +SEE ALSO +-------- + +linkman:nutscan_init[3], linkman:upscli_set_debug_level[3] diff --git a/docs/man/upsc.txt b/docs/man/upsc.txt index 217009ad43..215ffc25c5 100644 --- a/docs/man/upsc.txt +++ b/docs/man/upsc.txt @@ -73,6 +73,11 @@ COMMON OPTIONS Show the command-line help message. +*-D*:: + + Raise the debugging level. Use this option multiple times for more details. + Overrides the optional `NUT_DEBUG_LEVEL` environment variable. + *-V*:: Show NUT version banner. More details may be available if you also diff --git a/docs/man/upscli_set_debug_level.txt b/docs/man/upscli_set_debug_level.txt deleted file mode 100644 index f7e057283e..0000000000 --- a/docs/man/upscli_set_debug_level.txt +++ /dev/null @@ -1,51 +0,0 @@ -UPSCLI_SET_DEBUG_LEVEL(3) -========================= - -NAME ----- - -upscli_set_debug_level, upscli_get_debug_level - manipulate the possibly -separate copy of the `nut_debug_level` variable in the `libupsclient` build - -SYNOPSIS --------- - ------- - #include - - void upscli_set_debug_level(int); - int upscli_get_debug_level(void); ------- - -DESCRIPTION ------------ - -On some platforms, 'libupsclient' builds tend to get a built-in copy -of the internal code from NUT 'libcommon' library, so for NUT client -programs using both libraries as dynamically-linked shared code, -the `nut_debug_level` setting is backed by independent variables in -active memory, and `upsdebugx()` calls suffer if the library's copy -is never changed from zero. - -These methods allow to set or retrieve the value of `nut_debug_level` -setting known by the 'libupsclient' library, regardless of build mode. - -The most likely use (at least in NUT programs) is to call -`upscli_set_debug_level(nut_debug_level);` after changing the -original variable. Values of the debugging level are zero to disable -debug, may be negative for a few special cases, and generally are -positive numbers to cut off the more verbose logging attempts; -otherwise it is up to the code base and NUT style guide practices -to assign certain levels to some classes of messages. - -RETURN VALUE ------------- - -There is no return value for the setter. - -The getter returns the current value of the internal variable. - -SEE ALSO --------- - -linkman:upscli_init[3], linkman:upscli_report_build_details[3] diff --git a/docs/man/upscli_upslog_set_debug_level.txt b/docs/man/upscli_upslog_set_debug_level.txt new file mode 100644 index 0000000000..aef4ace4e0 --- /dev/null +++ b/docs/man/upscli_upslog_set_debug_level.txt @@ -0,0 +1,80 @@ +UPSCLI_UPSLOG_SET_DEBUG_LEVEL(3) +================================ + +NAME +---- + +upscli_upslog_set_debug_level, upscli_upslog_get_debug_level, +upscli_upslog_cookie, upscli_upslog_setprocname, +upscli_upslog_setproctag, upscli_upslog_getproctag, +upscli_upslog_start_sync - manipulate +the possibly separate (identified via cookie) copies of the `nut_debug_level` +and sub-process tag variables in the `libupsclient` build + +SYNOPSIS +-------- + +------ + #include + + const void *upscli_upslog_cookie(void); + + void upscli_set_debug_level(int, const void *cookie); + int upscli_get_debug_level(void); + + void upscli_setprocname(const char *full_procname, const void *cookie); + void upscli_setproctag(const char *tag, const void *cookie); + const char *upscli_getproctag(void); + + struct timeval *upscli_upslog_start_sync(struct timeval *tv, const void *cookie); +------ + +DESCRIPTION +----------- + +On some platforms, 'libupsclient' builds tend to get a built-in copy +of the internal code from NUT 'libcommon' library, so for NUT client +programs using both libraries as dynamically-linked shared code, +the `nut_debug_level` setting is backed by independent variables in +active memory, and `upsdebugx()` calls suffer if the library's copy +is never changed from zero. + +It can get even more confusing with libnutprivate-common being a shared +dynamically loaded library instance behind both the program and libupsclient, +hence the cookies: direct NUT-common code consumers like NUT in-tree clients +can use their `nut_common_cookie()` value to pass into methods here. +Third-party clients may safely pass 'NULL'. + +These methods allow to set or retrieve the value of `nut_debug_level` +setting known by the 'libupsclient' library, regardless of build mode. + +The most likely use (at least in NUT programs) is to call +`upscli_set_debug_level(nut_debug_level);` after changing the +original variable. Values of the debugging level are zero to disable +debug, may be negative for a few special cases, and generally are +positive numbers to cut off the more verbose logging attempts; +otherwise it is up to the code base and NUT style guide practices +to assign certain levels to some classes of messages. + +If internal `upslog_start` value is not yet set, we can set it with +*upscli_upslog_start_sync()* from *tv (or current time if tv==NULL), +otherwise the method is no-op (keep and report the original setting). + +Returns the pointer to the currently set value, so it +can be propagated or used in difftime() computations. + +NOTE: In WIN32 builds also enforces line-buffering for +stdout and stderr streams. + + +RETURN VALUE +------------ + +There is no return value for the setter. + +The getter returns the current value of the internal variable. + +SEE ALSO +-------- + +linkman:upscli_init[3], linkman:upscli_report_build_details[3] diff --git a/docs/man/upscmd.txt b/docs/man/upscmd.txt index a618c2237f..d8793248e0 100644 --- a/docs/man/upscmd.txt +++ b/docs/man/upscmd.txt @@ -67,6 +67,10 @@ COMMON OPTIONS *-h*:: Show the command-line help message. +*-D*:: +Raise the debugging level. Use this option multiple times for more details. +Overrides the optional `NUT_DEBUG_LEVEL` environment variable. + *-V*:: Show NUT version banner. More details may be available if you also `export NUT_DEBUG_LEVEL=1` or greater verbosity level. diff --git a/docs/man/upsrw.txt b/docs/man/upsrw.txt index 275a469588..fdb4891cfd 100644 --- a/docs/man/upsrw.txt +++ b/docs/man/upsrw.txt @@ -84,6 +84,10 @@ COMMON OPTIONS *-h*:: Show the command-line help message. +*-D*:: +Raise the debugging level. Use this option multiple times for more details. +Overrides the optional `NUT_DEBUG_LEVEL` environment variable. + *-V*:: Show NUT version banner. More details may be available if you also `export NUT_DEBUG_LEVEL=1` or greater verbosity level. diff --git a/docs/nut.dict b/docs/nut.dict index 1faf5c6983..6d2471fe4f 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3707 utf-8 +personal_ws-1.1 en 3710 utf-8 AAC AAS ABI @@ -2506,6 +2506,7 @@ libnutclientstub libnutclientsub libnutconf libnutconfig +libnutprivate libnutscan libpcre libpng @@ -2983,6 +2984,7 @@ prjconf problemMatcher probu proc +procname productid prog progname @@ -3171,6 +3173,7 @@ setfsd setgid setinfo setpci +setprocname setproctag setq setuid diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index 863740890e..ab359d987c 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -48,7 +48,7 @@ #include "dummy-ups.h" #define DRIVER_NAME "Device simulation and repeater driver" -#define DRIVER_VERSION "0.24" +#define DRIVER_VERSION "0.25" /* driver description structure */ upsdrv_info_t upsdrv_info = @@ -120,7 +120,7 @@ void upsdrv_initinfo(void) { dummy_info_t *item; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); switch (mode) { @@ -268,7 +268,7 @@ void upsdrv_updateinfo(void) { upsdebugx(1, "upsdrv_updateinfo..."); - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); sleep(1); @@ -419,26 +419,50 @@ static int instcmd(const char *cmdname, const char *extra) void upsdrv_help(void) { - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); upscli_report_build_details(); } +static void dummy_setproctag_callback(const char *tag) { + const void *cookie = nut_common_cookie(); + + if (cookie != upscli_upslog_cookie()) + upscli_upslog_setproctag(xstrdup(tag), cookie); +} + /* optionally tweak prognames[] entries */ void upsdrv_tweak_prognames(void) { + const void *cookie = nut_common_cookie(); + + /* Here we actually tweak libupsclient logging more, + * relying on this method being called early in main.c */ + upscli_upslog_start_sync(upslog_start_sync(NULL), cookie); + upscli_upslog_set_debug_level(nut_debug_level, cookie); + + /* FIXME: All other calls to setproctag() in main.c would currently + * be invisible to upscli_*() as that object file has no idea about + * the library in this one driver... should we introduce a callback? */ + if (cookie != upscli_upslog_cookie()) { + /* Send over a copy */ + upscli_upslog_setprocname(xstrdup(getmyprocname()), cookie); + upscli_upslog_setproctag(xstrdup(getproctag()), cookie); + + upsdrv_callback_setproctag = dummy_setproctag_callback; + } } void upsdrv_makevartable(void) { addvar(VAR_VALUE, "mode", "Specify mode instead of guessing it from port value (dummy = dummy-loop, dummy-once, repeater)"); /* meta */ - addvar(VAR_FLAG, "repeater_disable_strict_start", "Do not terminate the driver encountering errors when starting the repeater mode"); + addvar(VAR_FLAG, "repeater_disable_strict_start", "Do not terminate the driver encountering errors when starting the repeater mode"); } void upsdrv_initups(void) { const char *val; - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); val = dstate_getinfo("driver.parameter.mode"); if (val) { @@ -598,6 +622,9 @@ void upsdrv_cleanup(void) free(ctx); ctx = NULL; } + + upscli_cleanup(); + upsdrv_callback_setproctag = NULL; } static int setvar(const char *varname, const char *val) diff --git a/drivers/main.c b/drivers/main.c index 88590117ad..42f489d613 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -54,6 +54,12 @@ const char *upsname = NULL, *device_name = NULL; const char *prognames[MAX_PROGNAMES]; char prognames_should_free[MAX_PROGNAMES]; +/* If not NULL, will be called by upsdrv_setproctag() so the tag value can be + * propagated to third-party code (should not update the driver's tag with our + * own copy of NUT common library -- this is what upsdrv_setproctag() does); + * see dummy-ups for an example use-case */ +void (*upsdrv_callback_setproctag)(const char *tag) = NULL; + /* may be set by the driver to wake up while in dstate_poll_fds */ TYPE_FD extrafd = ERROR_FD; #ifndef DRIVERS_MAIN_WITHOUT_MAIN @@ -262,6 +268,13 @@ static void forceshutdown(void) exit(exit_flag == EF_EXIT_FAILURE ? EXIT_FAILURE : EXIT_SUCCESS); } +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+a:s:kDFBd:hx:Lqr:u:g:Vi:c:" +#ifndef WIN32 + "P:" +#endif /* WIN32 */ + ; + /* this function only prints the usage message; it does not call exit() */ static void help_msg(void) { @@ -1950,6 +1963,14 @@ void vartab_free(void) } #ifndef DRIVERS_MAIN_WITHOUT_MAIN +static void upsdrv_setproctag(const char *tag) +{ + setproctag(tag); + if (upsdrv_callback_setproctag) { + (*upsdrv_callback_setproctag)(tag); + } +} + static void exit_upsdrv_cleanup(void) { dstate_setinfo("driver.state", "cleanup.upsdrv"); @@ -1995,6 +2016,13 @@ static void exit_cleanup(void) } #endif /* WIN32 */ + /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) + * although it does bring a bit of clarity to the logs, just the wording is odd: + * [D2:1112101:dummy-ups:test] a dummy-ups sub-process (test) is exiting now + * + * //TOTHINK// upsdrv_setproctag(xstrdup(prognames[0])); + */ + for (i = 0; i < MAX_PROGNAMES; i++) { /* Some prognames[] may be allocated statically, * e.g. can be a pointer to part of argv[0]; @@ -2005,9 +2033,7 @@ static void exit_cleanup(void) } } - /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ upsdebugx(1, "%s: finished, exiting", __func__); - setproctag(NULL); } #endif /* DRIVERS_MAIN_WITHOUT_MAIN */ @@ -2154,30 +2180,21 @@ int main(int argc, char **argv) # endif { struct passwd *new_uid = NULL; - int i, do_forceshutdown = 0; + int opt_ret = 0, do_forceshutdown = 0, i; int update_count = 0; -#ifndef WIN32 +# ifndef WIN32 int cmd = 0; pid_t oldpid = -1; -#else /* WIN32 */ +# else /* WIN32 */ /* FIXME NUT_WIN32_INCOMPLETE : *actually* handle WIN32 builds too */ const char * cmd = NULL; + char drv_pipe_name[NUT_PATH_MAX + 1]; +# endif /* WIN32 */ - const char * drv_name = NULL; - char * dot = NULL; - char name[NUT_PATH_MAX + 1]; -#endif /* WIN32 */ - - const char optstring[] = "+a:s:kDFBd:hx:Lqr:u:g:Vi:c:" -#ifndef WIN32 - "P:" -#endif /* WIN32 */ - ; - -#if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS +# if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS callback_upsconf_args = do_upsconf_args; -#else +# else /* static build, symbols should be visible to main.c right away */ #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE)) # pragma GCC diagnostic push @@ -2192,14 +2209,17 @@ int main(int argc, char **argv) #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE)) # pragma GCC diagnostic pop #endif -#endif +# endif /* ENABLE_SHARED_PRIVATE_LIBS */ - /* init verbosity from default in common.c (0 probably) */ + /* init verbosity from default in common.c (0 probably) + * Note that unlike simpler programs, this is a long-running + * daemon which can change debug verbosity on the fly, so + * we track numerous variables for juggling that act. */ nut_debug_level_args = nut_debug_level; /* handle CLI-driven debug level in advance, to trace initialization if needed */ - while ((i = getopt(argc, argv, optstring)) != -1) { - switch (i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch (opt_ret) { case 'D': /* bump right here, may impact reporting of other CLI args */ nut_debug_level++; @@ -2267,26 +2287,12 @@ int main(int argc, char **argv) memset(prognames, 0, sizeof(prognames)); memset(prognames_should_free, 0, sizeof(prognames_should_free)); - prognames[0] = xbasename(argv[0]); -#ifdef WIN32 - drv_name = prognames[0]; - /* remove trailing .exe */ - dot = strrchr(drv_name,'.'); - if (dot != NULL) { - if (strcasecmp(dot, ".exe") == 0) { - char *fixed_progname = strdup(drv_name); - char *t = strrchr(fixed_progname,'.'); - *t = 0; - prognames[0] = fixed_progname; - prognames_should_free[0] = 1; - } - } - else { - prognames[0] = strdup(drv_name); - prognames_should_free[0] = 1; - } -#endif /* WIN32 */ + /* Note: "const char *" to (substring of) argv[0] itself, + * or an allocated string auto-cleaned by the NUT common + * library; either way, this program does not free() it: */ + prognames[0] = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "nutdrv"); + upsdrv_setproctag(prognames[0]); upsdrv_callbacks.upsdrv_tweak_prognames(); @@ -2304,8 +2310,8 @@ int main(int argc, char **argv) /* build the driver's extra (-x) variable table */ upsdrv_callbacks.upsdrv_makevartable(); - while ((i = getopt(argc, argv, optstring)) != -1) { - switch (i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch (opt_ret) { case 'a': if (upsname) fatalx(EXIT_FAILURE, "Error: options '-a id' and '-s id' " @@ -2364,7 +2370,8 @@ int main(int argc, char **argv) help_msg(); fatalx(EXIT_FAILURE, "Error: only one command per run can be " - "sent with option -%c. Try -h for help.", i); + "sent with option -%c. Try -h for help.", + (char)opt_ret); } if (!strncmp(optarg, "reload-or-error", strlen(optarg))) { @@ -2374,28 +2381,29 @@ int main(int argc, char **argv) if (!strncmp(optarg, "exit", strlen(optarg))) { cmd = SIGCMD_EXIT; } -#ifndef WIN32 +# ifndef WIN32 else if (!strncmp(optarg, "reload", strlen(optarg))) { cmd = SIGCMD_RELOAD; } else -# ifdef SIGCMD_RELOAD_OR_RESTART +# ifdef SIGCMD_RELOAD_OR_RESTART if (!strncmp(optarg, "reload-or-restart", strlen(optarg))) { cmd = SIGCMD_RELOAD_OR_RESTART; } else -# endif +# endif if (!strncmp(optarg, "reload-or-exit", strlen(optarg))) { cmd = SIGCMD_RELOAD_OR_EXIT; } -#endif /* WIN32 */ +# endif /* WIN32 */ /* bad command given */ if (!cmd) { help_msg(); fatalx(EXIT_FAILURE, - "Error: unknown argument to option -%c. Try -h for help.", i); + "Error: unknown argument to option -%c. Try -h for help.", + (char)opt_ret); } -#ifndef WIN32 +# ifndef WIN32 if (cmd > 0) upsdebugx(1, "Will send signal %d (%s) for command '%s' " "to already-running driver %s-%s (if any) and exit", @@ -2404,20 +2412,20 @@ int main(int argc, char **argv) upsdebugx(1, "Will send request for command '%s' (internal code %d) " "to already-running driver %s-%s (if any) and exit", optarg, cmd, progname, upsname); -#else /* WIN32 */ +# else /* WIN32 */ upsdebugx(1, "Will send request '%s' for command '%s' " "to already-running driver %s-%s (if any) and exit", cmd, optarg, progname, upsname); -#endif /* WIN32 */ +# endif /* WIN32 */ break; -#ifndef WIN32 +# ifndef WIN32 /* NOTE for FIXME above: PID-signalling is non-WIN32-only for us */ case 'P': if ((oldpid = parsepid(optarg)) < 0) help_msg(); break; -#endif /* !WIN32 */ +# endif /* !WIN32 */ case 'L': listxarg(); exit(EXIT_SUCCESS); @@ -2485,7 +2493,7 @@ int main(int argc, char **argv) default: fatalx(EXIT_FAILURE, "Error: unknown option -%c. Try -h for help.", - (char)i); + (char)opt_ret); } } @@ -2510,7 +2518,7 @@ int main(int argc, char **argv) fatalx(EXIT_FAILURE, "Error: specifying '-a id' or '-s id' is now mandatory. Try -h for help."); } - setproctag(upsname); + upsdrv_setproctag(upsname); /* we need to get the port from somewhere, unless we are just sending a signal and exiting */ if (!device_path && !cmd) { @@ -2529,7 +2537,7 @@ int main(int argc, char **argv) become_user(new_uid); -#ifndef WIN32 +# ifndef WIN32 /* We only need switch to statepath if we're not powering off * or not just dumping data (for discovery), in particular to * hold that path from getting unmounted easily while used for @@ -2555,7 +2563,7 @@ int main(int argc, char **argv) /* Setup signals to communicate with driver which is destined for a long run. */ setup_signals(); } -#endif /* !WIN32 */ +# endif /* !WIN32 */ if (do_forceshutdown) { /* First try to handle this over socket protocol @@ -2608,29 +2616,29 @@ int main(int argc, char **argv) /* Handle reload-or-error over socket protocol with * the running older driver instance */ -#ifndef WIN32 +# ifndef WIN32 if (cmd == SIGCMD_RELOAD_OR_ERROR || cmd == SIGCMD_EXIT) -#else /* WIN32 */ +# else /* WIN32 */ if (cmd && (!strcmp(cmd, SIGCMD_RELOAD_OR_ERROR) || !strcmp(cmd, SIGCMD_EXIT))) -#endif /* WIN32 */ +# endif /* WIN32 */ { /* Not a signal, but a socket protocol action */ ssize_t cmdret = -1; char buf[LARGEBUF], cmdbuf[LARGEBUF]; struct timeval tv; char *cmdname = NULL; -#ifndef WIN32 +# ifndef WIN32 if (cmd == SIGCMD_RELOAD_OR_ERROR) -#else /* WIN32 */ +# else /* WIN32 */ if (!strcmp(cmd, SIGCMD_RELOAD_OR_ERROR)) -#endif /* WIN32 */ +# endif /* WIN32 */ cmdname = "reload-or-error"; else -#ifndef WIN32 +# ifndef WIN32 if (cmd == SIGCMD_EXIT) -#else /* WIN32 */ +# else /* WIN32 */ if (!strcmp(cmd, SIGCMD_EXIT)) -#endif /* WIN32 */ +# endif /* WIN32 */ cmdname = "exit"; #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW || defined HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION) @@ -2727,11 +2735,11 @@ int main(int argc, char **argv) */ upslogx(LOG_WARNING, "Duplicate driver instance detected (local %s exists)! " "Asked the other driver nicely to self-terminate!", -#ifndef WIN32 +# ifndef WIN32 "Unix socket" -#else /* WIN32 */ +# else /* WIN32 */ "pipe" -#endif /* WIN32 */ +# endif /* WIN32 */ ); for (i = 10; i > 0; i--) { @@ -2779,7 +2787,7 @@ int main(int argc, char **argv) nut_upsdrvquery_debug_level = NUT_UPSDRVQUERY_DEBUG_LEVEL_DEFAULT; } -#ifndef WIN32 +# ifndef WIN32 /* Setup PID file to receive signals to communicate with this driver * instance once backgrounded (or staying foregrounded with `-FF`), * and to stop a competing older instance. Or to send it a signal @@ -2836,7 +2844,7 @@ int main(int argc, char **argv) * for troubleshooting, e.g. about lack of PID file */ upslogx(LOG_NOTICE, "Failed to signal the currently running daemon (if any)"); -# ifdef HAVE_SYSTEMD +# ifdef HAVE_SYSTEMD switch (cmd) { case SIGCMD_RELOAD: upslogx(LOG_NOTICE, "Try something like " @@ -2846,9 +2854,9 @@ int main(int argc, char **argv) break; case SIGCMD_RELOAD_OR_EXIT: -# ifdef SIGCMD_RELOAD_OR_RESTART +# ifdef SIGCMD_RELOAD_OR_RESTART case SIGCMD_RELOAD_OR_RESTART: -# endif +# endif upslogx(LOG_NOTICE, "Try something like " "'systemctl reload-or-restart " "nut-driver@%s.service'%s", @@ -2867,11 +2875,11 @@ int main(int argc, char **argv) * and so save the PID file for ability to manage the daemon * beside the service framework, possibly confusing things... */ -# else /* not HAVE_SYSTEMD */ +# else /* not HAVE_SYSTEMD */ if (oldpid < 0) { upslogx(LOG_NOTICE, "Try to add '-P $PID' argument"); } -# endif /* HAVE_SYSTEMD */ +# endif /* HAVE_SYSTEMD */ } /* if (cmdret != 0) */ exit((cmdret == 0) ? EXIT_SUCCESS : EXIT_FAILURE); @@ -2934,8 +2942,8 @@ int main(int argc, char **argv) writepid(pidfn); /* before backgrounding */ } } -#else /* WIN32 */ - snprintf(name, sizeof(name), "%s-%s", progname, upsname); +# else /* WIN32 */ + snprintf(drv_pipe_name, sizeof(drv_pipe_name), "%s-%s", progname, upsname); if (cmd) { /* FIXME: port event loop from upsd/upsmon to allow messaging fellow drivers in WIN32 builds */ @@ -2943,10 +2951,10 @@ int main(int argc, char **argv) fatalx(EXIT_FAILURE, "Signal support not implemented for this platform"); } - mutex = CreateMutex(NULL, TRUE, name); + mutex = CreateMutex(NULL, TRUE, drv_pipe_name); if (mutex == NULL) { if (GetLastError() != ERROR_ACCESS_DENIED) { - fatalx(EXIT_FAILURE, "Can not create mutex %s : %d.\n", name, (int)GetLastError()); + fatalx(EXIT_FAILURE, "Can not create mutex %s : %d.\n", drv_pipe_name, (int)GetLastError()); } } @@ -2954,7 +2962,7 @@ int main(int argc, char **argv) upslogx(LOG_WARNING, "Duplicate driver instance detected! Terminating other driver!"); for (i = 0; i < 10; i++) { DWORD res; - sendsignal(name, COMMAND_STOP, 1); + sendsignal(drv_pipe_name, COMMAND_STOP, 1); if (mutex != NULL) { res = WaitForSingleObject(mutex, 1000); if (res == WAIT_OBJECT_0) { @@ -2963,7 +2971,7 @@ int main(int argc, char **argv) } else { sleep(1); - mutex = CreateMutex(NULL, TRUE, name); + mutex = CreateMutex(NULL, TRUE, drv_pipe_name); if (mutex != NULL) { break; } @@ -2973,7 +2981,7 @@ int main(int argc, char **argv) fatalx(EXIT_FAILURE, "Can not terminate the previous driver.\n"); } } -#endif /* WIN32 */ +# endif /* WIN32 */ /* Restore the signal errors verbosity */ nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_DEFAULT; @@ -3060,7 +3068,7 @@ int main(int argc, char **argv) if (strcmp(group, RUN_AS_GROUP) || strcmp(user, RUN_AS_USER) ) { -#ifndef WIN32 +# ifndef WIN32 int allOk = 1; /* Use file descriptor, not name, to first check and then manipulate permissions: * https://cwe.mitre.org/data/definitions/367.html @@ -3157,10 +3165,10 @@ int main(int argc, char **argv) close(fd); fd = ERROR_FD; } -#else /* WIN32 */ +# else /* WIN32 */ /* NUT_WIN32_INCOMPLETE(); */ upsdebugx(1, "Options for alternate user/group are not implemented on this platform"); -#endif /* WIN32 */ +# endif /* WIN32 */ } free(sockname); } @@ -3215,20 +3223,20 @@ int main(int argc, char **argv) dstate_setflags("driver.flag.allow_killpower", ST_FLAG_RW | ST_FLAG_NUMBER); dstate_addcmd("driver.killpower"); -#ifndef WIN32 +# ifndef WIN32 /* TODO: Equivalent for WIN32 - see SIGCMD_RELOAD in upsd and upsmon */ dstate_addcmd("driver.reload"); dstate_addcmd("driver.reload-or-exit"); -# ifndef DRIVERS_MAIN_WITHOUT_MAIN +# ifndef DRIVERS_MAIN_WITHOUT_MAIN dstate_addcmd("driver.reload-or-error"); -# endif -# ifdef SIGCMD_RELOAD_OR_RESTART +# endif +# ifdef SIGCMD_RELOAD_OR_RESTART dstate_addcmd("driver.reload-or-restart"); -# endif -#else /* WIN32 */ +# endif +# else /* WIN32 */ /* https://github.com/networkupstools/nut/issues/1916 */ NUT_WIN32_INCOMPLETE_DETAILED("driver.reload* instant commands"); -#endif /* WIN32 */ +# endif /* WIN32 */ dstate_setinfo("driver.state", "quiet"); if (dump_data) { diff --git a/drivers/main.h b/drivers/main.h index e9ced7078e..5c56ca0069 100644 --- a/drivers/main.h +++ b/drivers/main.h @@ -259,6 +259,10 @@ typedef struct upsdrv_callback_s { } upsdrv_callback_t; void register_upsdrv_callbacks(upsdrv_callback_t *runtime_callbacks, size_t cb_struct_sz); +/* If not NULL, e.g. registered early by a driver like dummy-ups, + * will be called in upsdrv_setproctag() transitions */ +extern void (*upsdrv_callback_setproctag)(const char *tag); + /* Suite of simple calls to register driver callback method implementations * named as dictated by this header, which (being macros) can be called easily * from both static and shared builds (with libnutprivate-X_Y_Z-drivers-common). diff --git a/drivers/upsdrvctl.c b/drivers/upsdrvctl.c index dfa95f2b75..42e20ac19e 100644 --- a/drivers/upsdrvctl.c +++ b/drivers/upsdrvctl.c @@ -1445,6 +1445,9 @@ static void start_driver(const ups_t *ups) static void help(const char *arg_progname) __attribute__((noreturn)); +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+htu:r:DdFBVc:l"; + static void help(const char *arg_progname) { print_banner_once(arg_progname, 2); @@ -1688,7 +1691,7 @@ static void exit_cleanup(void) int main(int argc, char **argv) { - int i, lastarg = 0; + int opt_ret = 0, lastarg = 0; char *prog, *command_name = NULL, progdesc[LARGEBUF]; prog = argv[0]; @@ -1701,8 +1704,8 @@ int main(int argc, char **argv) snprintf(progdesc, sizeof(progdesc), "%s - UPS driver controller", xbasename(prog)); print_banner_once(progdesc, 0); - while ((i = getopt(argc, argv, "+htu:r:DdFBVc:l")) != -1) { - switch(i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch(opt_ret) { case 'r': pt_root = optarg; break; @@ -1749,7 +1752,8 @@ int main(int argc, char **argv) if (command || pt_cmd) { fatalx(EXIT_FAILURE, "Error: only one command per run can be " - "sent with option -%c. Try -h for help.", i); + "sent with option -%c. Try -h for help.", + (char)opt_ret); } command = &signal_driver; command_name = "signal"; @@ -1789,7 +1793,8 @@ int main(int argc, char **argv) /* bad command given */ if (!signal_flag) { fatalx(EXIT_FAILURE, - "Error: unknown argument to option -%c. Try -h for help.", i); + "Error: unknown argument to option -%c. Try -h for help.", + (char)opt_ret); } pt_cmd = optarg; diff --git a/include/common.h b/include/common.h index 69f8d7348a..d493ac7565 100644 --- a/include/common.h +++ b/include/common.h @@ -298,6 +298,22 @@ const char *getproctag(void); */ void setproctag(const char *tag); +/* These are exported for internal use between NUT libraries (common, + * libupsclient, libnutscan...) and not intended for arbitrary consumers, + * except maybe those that have a chance to be linked with both common + * and some of the other libraries, with or without libnutprivate-common + * as a single dynamically loaded object behind them. To make sense of + * it, every instance has a cookie so they can compare notes; it can be + * passed to relevant public libraries' methods. + */ +const void *nut_common_cookie(void); +/* Gets caller-allocated string which this method frees if not NULL (in atexit()), + * typically returned by getmyprocname() */ +void setmyprocname(const char *s); +/* Returns NULL or a string from getprocname(myPid) that the caller should free() */ +const char *getmyprocname(void); +const char *getmyprocbasename(void); + /* do this here to keep pwd/grp stuff out of the main files */ struct passwd *get_user_pwent(const char *name); @@ -445,6 +461,25 @@ int sendsignalfnaliases(const char *pidfn, const char * sig, const char **progna /* return a pointer to character inside the file that starts a basename * caller should strdup() a copy to retain beyond the lifetime of "file" */ const char *xbasename(const char *file); +/* like above, but also strip platform-specific EXEEXT if present, + * e.g. convert ".../upsd.exe" => "upsd"; returns a newly allocated + * string that the caller must free() eventually, or NULL in case + * of errors (e.g. NULL or empty input or xbasename() output. */ +char *xbasename_no_ext(const char *file); +/* like above with fallback support; always returns a new allocation, + * even if a copy of inputs or "UNDEFINED" if can not determine the + * value (and fallback is NULL) */ +char *xbasename_no_ext_default(const char *file, const char *fallback); + +/* Used in main() and similar methods to set a "const char *progname" from + * argv[0] in a way that this may be either a pointer to sub-string of + * that argv[0] or to the fallback (if not NULL) without wasting RAM for + * copies, or to a variable automatically cleaned by the NUT common + * library at exit. Uses logic similar to xbasename_no_ext_default() + * internally to strip EXEEXT on platforms that have it. + * Call with getprogname_argv0_default(NULL, NULL) would return the + * previously saved value, or "UNDEFINED" if never set yet. */ +const char *getprogname_argv0_default(const char *file, const char *fallback); /* enable writing upslog_with_errno() and upslogx() type messages to * the stdout instead of stderr, and end them with HTML
tag, @@ -545,6 +580,19 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) #define UPSNOTIFY_EXTEND_TIMEOUT_USEC_INFINITY ((uint64_t)INT64_MAX) extern uint64_t upsnotify_extend_timeout_usec_default, upsnotify_extend_timeout_usec; +/* The NUT common library code is included in several other + * libraries, often with their private copies of variables, + * so we want to synchronize them. + * If internal `upslog_start` value is not yet set, we set + * it from *tv (or current time if tv==NULL), otherwise the + * method is no-op (keep and report the original setting). + * Returns the pointer to the currently set value, so it + * can be propagated or used in difftime() computations. + * NOTE: In WIN32 builds also enforces line-buffering for + * stdout and stderr streams. + */ +struct timeval *upslog_start_sync(struct timeval *tv); + /* upslog*() messages are sent to syslog always; * their life after that is out of NUT's control */ void upslog_with_errno(int priority, const char *fmt, ...) diff --git a/scripts/Windows/wininit.c b/scripts/Windows/wininit.c index a1fc5577a1..4d175d6de7 100644 --- a/scripts/Windows/wininit.c +++ b/scripts/Windows/wininit.c @@ -3,7 +3,7 @@ Copyright (C) 2010 Frederic Bohe - 2021-2025 Jim Klimov + 2021-2026 Jim Klimov 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 @@ -888,6 +888,9 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) } } +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+IUNDVh"; + static void help(const char *arg_progname) { print_banner_once(arg_progname, 2); @@ -921,15 +924,18 @@ static void help(const char *arg_progname) int main(int argc, char **argv) { - int i, default_opterr = opterr; + int opt_ret = 0, default_opterr = opterr; + /* Note: here we do want ".exe" in the name, to reference + * the man page from suggest_doc_links() correctly later! + */ const char *progname = xbasename(argc > 0 ? argv[0] : "nut.exe"); /* TODO: Do not warn about unknown args - pass them to SvcMain() * Currently neutered because that method ignores argc/argv de-facto. * opterr = 0; */ - while ((i = getopt(argc, argv, "+IUNDVh")) != -1) { - switch (i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch (opt_ret) { case 'I': return SvcInstall(SVCNAME, NULL); case 'U': @@ -965,7 +971,7 @@ int main(int argc, char **argv) */ upsdebugx(1, "%s: unknown option ignored " "(maybe SvcMain would use it later): '%c'", - progname, i); + progname, (char)opt_ret); break; } } diff --git a/server/pipedebug.c b/server/pipedebug.c index ef1c4a4f8a..95402af935 100644 --- a/server/pipedebug.c +++ b/server/pipedebug.c @@ -128,7 +128,7 @@ DWORD WINAPI WriteThread( LPVOID lpParameter ) } int main(int argc, char **argv) { - const char *prog = xbasename(argv[0]); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "pipedebug(WIN32)"); HANDLE pipefd; HANDLE thread[2]; diff --git a/server/sockdebug.c b/server/sockdebug.c index 2db400db4b..1fd2b70667 100644 --- a/server/sockdebug.c +++ b/server/sockdebug.c @@ -132,7 +132,7 @@ static void read_sock(int fd) int main(int argc, char **argv) { - const char *prog = xbasename(argv[0]); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "sockdebug(POSIX)"); int ret, sockfd; if (argc != 2 diff --git a/server/upsd.c b/server/upsd.c index 3a370193e2..685ec45514 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -2211,6 +2211,9 @@ static void mainloop(void) static void help(const char *arg_progname) __attribute__((noreturn)); +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+h46p:qr:i:fu:Vc:P:DFB"; + static void help(const char *arg_progname) { print_banner_once(arg_progname, 2); @@ -2303,7 +2306,7 @@ void check_perms(const char *fn) int main(int argc, char **argv) { - int i, cmdret = 0, foreground = -1; + int opt_ret = 0, cmdret = 0, foreground = -1; #ifndef WIN32 int cmd = 0; pid_t oldpid = -1; @@ -2314,7 +2317,7 @@ int main(int argc, char **argv) const char *user = RUN_AS_USER; struct passwd *new_uid = NULL; - progname = xbasename(argv[0]); + progname = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsd"); setproctag(progname); #if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS @@ -2327,22 +2330,7 @@ int main(int argc, char **argv) datapath = xstrdup(NUT_DATADIR); #else /* WIN32 */ datapath = getfullpath2(NUT_DATADIR, PATH_SHARE); - /* no statepath here, we talk via named pipes */ - - /* remove trailing .exe */ - char * drv_name; - drv_name = (char *)xbasename(argv[0]); - char * name = strrchr(drv_name,'.'); - if( name != NULL ) { - if(strcasecmp(name, ".exe") == 0 ) { - progname = strdup(drv_name); - char * t = strrchr(progname,'.'); - *t = 0; - } - } - else { - progname = drv_name; - } + /* no statepath here really, we talk via named pipes */ #endif /* WIN32 */ /* set up some things for later */ @@ -2350,8 +2338,8 @@ int main(int argc, char **argv) print_banner_once(progname, 0); - while ((i = getopt(argc, argv, "+h46p:qr:i:fu:Vc:P:DFB")) != -1) { - switch (i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch (opt_ret) { case 'p': case 'i': fatalx(EXIT_FAILURE, "Specifying a listening addresses with '-i
' and '-p '\n" diff --git a/tools/nut-scanner/Makefile.am b/tools/nut-scanner/Makefile.am index eac84193e4..74e6d3719a 100644 --- a/tools/nut-scanner/Makefile.am +++ b/tools/nut-scanner/Makefile.am @@ -151,7 +151,7 @@ libnutscan_la_LDFLAGS += -version-info 5:0:1 # copies of "nut_debug_level" making fun of our debug-logging attempts. # One solution to tackle if needed for those cases would be to make some # dynamic/shared libnutcommon (etc.) -libnutscan_la_LDFLAGS += -export-symbols-regex '^(nutscan_|nut_debug_level|s_upsdebug|fatalx|fatal_with_errno|setproctag|xcalloc|xbasename|snprintfcat|snprintf_dynamic|max_threads|curr_threads|nut_report_config_flags|upsdebugx_report_search_paths|nut_prepare_search_paths|print_banner_once|suggest_doc_links)' +libnutscan_la_LDFLAGS += -export-symbols-regex '^(nutscan_|nut_debug_level|s_upsdebug|fatalx|fatal_with_errno|setproctag|xcalloc|xbasename|getprogname_argv0_default|snprintfcat|snprintf_dynamic|max_threads|curr_threads|nut_report_config_flags|upsdebugx_report_search_paths|nut_prepare_search_paths|print_banner_once|suggest_doc_links)' libnutscan_la_CFLAGS = \ -I$(top_builddir)/clients -I$(top_srcdir)/clients \ -I$(top_builddir)/include -I$(top_srcdir)/include \ diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index f07bf99018..ba28aa47a8 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -1174,7 +1174,7 @@ static void show_usage(const char *arg_progname) int main(int argc, char *argv[]) { - const char *progname = xbasename(argv[0]); + const char *progname = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "nut-scanner"); nutscan_snmp_t snmp_sec; nutscan_ipmi_t ipmi_sec; nutscan_xml_t xml_sec; @@ -1194,6 +1194,7 @@ int main(int argc, char *argv[]) int quiet = 0; /* The debugging level for certain upsdebugx() progress messages; 0 = print always, quiet==1 is to require at least one -D */ void (*display_func)(nutscan_device_t * device); int ret_code = EXIT_SUCCESS; + int _nut_debug_level = 0; #ifdef HAVE_PTHREAD # if (defined HAVE_SEMAPHORE_UNNAMED) || (defined HAVE_SEMAPHORE_NAMED) sem_t *current_sem; @@ -1201,7 +1202,17 @@ int main(int argc, char *argv[]) #endif #if (defined HAVE_PTHREAD) && ( (defined HAVE_PTHREAD_TRYJOIN) || (defined HAVE_SEMAPHORE_UNNAMED) || (defined HAVE_SEMAPHORE_NAMED) ) && (defined HAVE_SYS_RESOURCE_H) struct rlimit nofile_limit; +#endif + + /* NOTE: No hassle in this program about upslog_start_sync() + * nor nutscan_upslog_setprocname(xstrdup(getmyprocname())) + * because its provider of those data and debugging methods + * is libnutscan itself (so cookie is NULL as we do not even + * see NUT-common methods in the program source). + */ + nutscan_upslog_setproctag(progname, NULL); +#if (defined HAVE_PTHREAD) && ( (defined HAVE_PTHREAD_TRYJOIN) || (defined HAVE_SEMAPHORE_UNNAMED) || (defined HAVE_SEMAPHORE_NAMED) ) && (defined HAVE_SYS_RESOURCE_H) /* Limit the max scanning thread count by the amount of allowed open * file descriptors (which caller can change with `ulimit -n NUM`), * following practical investigation summarized at @@ -1230,8 +1241,6 @@ int main(int argc, char *argv[]) } #endif /* HAVE_PTHREAD && ( HAVE_PTHREAD_TRYJOIN || HAVE_SEMAPHORE_UNNAMED || HAVE_SEMAPHORE_NAMED ) && HAVE_SYS_RESOURCE_H */ - setproctag(progname); - memset(&snmp_sec, 0, sizeof(snmp_sec)); memset(&ipmi_sec, 0, sizeof(ipmi_sec)); memset(&xml_sec, 0, sizeof(xml_sec)); @@ -1254,14 +1263,33 @@ int main(int argc, char *argv[]) opterr = 0; while ((opt_ret = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) { if (opt_ret == 'D') - nut_debug_level++; + _nut_debug_level++; + } + + if (_nut_debug_level) { + nutscan_upslog_set_debug_level(_nut_debug_level, NULL); + } else { + char *s = getenv("NUT_DEBUG_LEVEL"); + if (s) { + int l = atol(s); + if (l > 0) { + _nut_debug_level = l; + nutscan_upslog_set_debug_level(_nut_debug_level, NULL); + upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " + "since none was requested by command-line options", l); + } + } /* else follow -D settings */ } - setproctag("init-ip-ranges"); + /* A non-trivial _nut_debug_level set above allows + * debug-tracing to troubleshoot these init methods: */ + nutscan_upslog_setproctag("init-ip-ranges", NULL); nutscan_init_ip_ranges(&ip_ranges_list); - setproctag("init-libnutscan"); + nutscan_upslog_setproctag("init-libnutscan", NULL); nutscan_init(); - setproctag(progname); + /* Re-set to cover libupsclient, if loaded: */ + nutscan_upslog_set_debug_level(_nut_debug_level, NULL); + nutscan_upslog_setproctag(progname, NULL); /* Default, see -Q/-N/-P below */ display_func = nutscan_display_ups_conf_with_sanity_check; @@ -1731,7 +1759,7 @@ int main(int argc, char *argv[]) * useful in troubleshooting. Currently it relies on a process-wide variable, * and threads are all in same process space... */ - setproctag("scanning"); + nutscan_upslog_setproctag("scanning", NULL); if (allow_usb && nutscan_avail_usb) { upsdebugx(quiet, "Scanning USB bus."); @@ -1741,7 +1769,7 @@ int main(int argc, char *argv[]) nutscan_avail_usb = 0; } #else - setproctag("usb"); + nutscan_upslog_setproctag("usb", NULL); upsdebugx(1, "USB SCAN: no pthread support, starting nutscan_scan_usb..."); dev[TYPE_USB] = run_usb(&cli_link_detail_level); #endif /* HAVE_PTHREAD */ @@ -1763,7 +1791,7 @@ int main(int argc, char *argv[]) nutscan_avail_snmp = 0; } #else - setproctag("snmp"); + nutscan_upslog_setproctag("snmp", NULL); upsdebugx(1, "SNMP SCAN: no pthread support, starting nutscan_scan_snmp..."); /* dev[TYPE_SNMP] = nutscan_scan_snmp(start_ip, end_ip, timeout, &snmp_sec); */ run_snmp(&snmp_sec); @@ -1788,7 +1816,7 @@ int main(int argc, char *argv[]) nutscan_avail_xml_http = 0; } #else - setproctag("netxml"); + nutscan_upslog_setproctag("netxml", NULL); upsdebugx(1, "XML/HTTP SCAN: no pthread support, starting nutscan_scan_xml_http_range()..."); /* dev[TYPE_XML] = nutscan_scan_xml_http_range(start_ip, end_ip, timeout, &xml_sec); */ run_xml(&xml_sec); @@ -1811,7 +1839,7 @@ int main(int argc, char *argv[]) nutscan_avail_nut = 0; } #else - setproctag("oldnut"); + nutscan_upslog_setproctag("oldnut", NULL); upsdebugx(1, "NUT bus (old) SCAN: no pthread support, starting nutscan_scan_nut..."); /*dev[TYPE_NUT] = nutscan_scan_nut(start_ip, end_ip, port, timeout);*/ run_nut_old(NULL); @@ -1830,7 +1858,7 @@ int main(int argc, char *argv[]) nutscan_avail_nut_simulation = 0; } #else - setproctag("nut_simulation"); + nutscan_upslog_setproctag("nut_simulation", NULL); upsdebugx(1, "NUT simulation devices SCAN: no pthread support, starting nutscan_scan_nut_simulation..."); /* dev[TYPE_NUT_SIMULATION] = nutscan_scan_nut_simulation(); */ run_nut_simulation(NULL); @@ -1848,7 +1876,7 @@ int main(int argc, char *argv[]) nutscan_avail_avahi = 0; } #else - setproctag("avahi"); + nutscan_upslog_setproctag("avahi", NULL); upsdebugx(1, "NUT bus (avahi) SCAN: no pthread support, starting nutscan_scan_avahi..."); /* dev[TYPE_AVAHI] = nutscan_scan_avahi(timeout); */ run_avahi(NULL); @@ -1871,7 +1899,7 @@ int main(int argc, char *argv[]) nutscan_avail_ipmi = 0; } #else - setproctag("ipmi"); + nutscan_upslog_setproctag("ipmi", NULL); upsdebugx(1, "IPMI SCAN: no pthread support, starting nutscan_scan_ipmi..."); /* dev[TYPE_IPMI] = nutscan_scan_ipmi(start_ip, end_ip, &ipmi_sec); */ run_ipmi(&ipmi_sec); @@ -1889,7 +1917,7 @@ int main(int argc, char *argv[]) nutscan_avail_upower = 0; } #else - setproctag("upower"); + nutscan_upslog_setproctag("upower", NULL); upsdebugx(1, "UPOWER SCAN: no pthread support, starting nutscan_scan_upower..."); run_upower(NULL); #endif /* HAVE_PTHREAD */ @@ -1907,7 +1935,7 @@ int main(int argc, char *argv[]) /* upsdebugx(1, "pthread_create returned an error; disabling this scan mode"); */ /* nutscan_avail_eaton_serial(?) = 0; */ #else - setproctag("serial"); + nutscan_upslog_setproctag("serial", NULL); upsdebugx(1, "SERIAL SCAN: no pthread support, starting nutscan_scan_eaton_serial..."); /* dev[TYPE_EATON_SERIAL] = nutscan_scan_eaton_serial (serial_ports); */ run_eaton_serial(serial_ports); @@ -1955,7 +1983,7 @@ int main(int argc, char *argv[]) } #endif /* HAVE_PTHREAD */ - setproctag("post-processing"); + nutscan_upslog_setproctag("post-processing", NULL); upsdebugx(1, "SCANS DONE: display results"); @@ -2004,7 +2032,7 @@ int main(int argc, char *argv[]) upsdebugx(1, "SCANS DONE: free resources: SERIAL"); nutscan_free_device(dev[TYPE_EATON_SERIAL]); - setproctag("cleanup"); + nutscan_upslog_setproctag("cleanup", NULL); #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE_UNNAMED sem_destroy(nutscan_semaphore()); @@ -2022,7 +2050,7 @@ int main(int argc, char *argv[]) nutscan_free(); /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ - setproctag(progname); + nutscan_upslog_setproctag(progname, NULL); upsdebugx(1, "SCANS DONE: EXIT_SUCCESS"); diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index cdbd2a611e..3714df3031 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -2,7 +2,7 @@ * Copyright (C) 2011 - 2024 Arnaud Quette (Design and part of implementation) * Copyright (C) 2011 - 2021 EATON * Copyright (C) 2016 - 2021 Jim Klimov - * Copyright (C) 2021 - 2024 Jim Klimov + * Copyright (C) 2021 - 2026 Jim Klimov * * 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 @@ -73,9 +73,16 @@ int nutscan_load_ipmi_library(const char *libname_path); int nutscan_unload_ipmi_library(void); int nutscan_load_upsclient_library(const char *libname_path); int nutscan_unload_upsclient_library(void); +void nutscan_upscli_set_debug_level(int level, const void *cookie); +void nutscan_upscli_setprocname(const char *pn, const void *cookie); +void nutscan_upscli_setproctag(const char *tag, const void *cookie); +struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv, const void *cookie); int nutscan_load_upower_library(const char *libname_path); int nutscan_unload_upower_library(void); +/* privately exported from common.c for internal libs */ +const char *setproctag_lib_once(const char *val); + #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE_UNNAMED /* Shared by library consumers, exposed by nutscan_semaphore() below */ @@ -149,6 +156,69 @@ void do_upsconf_args(char *confupsname, char *var, char *val) { } #endif /* WIN32 */ +const void *nutscan_upslog_cookie(void) +{ + return nut_common_cookie(); +} + +void nutscan_upslog_set_debug_level(int level, const void *cookie) { + nut_debug_level = level; + + if (cookie != nutscan_upslog_cookie()) + setproctag_lib_once("libnutscan"); + + /* Succeeds after init and if the library is loaded, else no-op */ + nutscan_upscli_set_debug_level(level, cookie ? cookie : nutscan_upslog_cookie()); + +} + +int nutscan_upslog_get_debug_level(void) +{ + return nut_debug_level; +} + +/* Avoid re-querying /proc or equivalent and logging about it, + * if the caller is a NUT program that already knows its name: + * see getmyprocname() in NUT common library */ +void nutscan_upslog_setprocname(const char *pn, const void *cookie) +{ + if (cookie != nutscan_upslog_cookie()) + setproctag_lib_once("libnutscan"); + + setmyprocname(pn); + /* Succeeds after init and if the library is loaded, else no-op */ + nutscan_upscli_setprocname(pn, cookie ? cookie : nutscan_upslog_cookie()); +} + +void nutscan_upslog_setproctag(const char *tag, const void *cookie) +{ + if (cookie != nutscan_upslog_cookie()) + setproctag_lib_once("libnutscan"); + + setproctag(tag); + /* Succeeds after init and if the library is loaded, else no-op */ + nutscan_upscli_setproctag(tag, cookie ? cookie : nutscan_upslog_cookie()); +} + +const char *nutscan_upslog_getproctag(void) +{ + return getproctag(); +} + +struct timeval *nutscan_upslog_start_sync(struct timeval *tv, const void *cookie) +{ + /* No-op if internal tv equals passed tv */ + struct timeval *nstv = upslog_start_sync(tv); + + if (cookie != nutscan_upslog_cookie()) + setproctag_lib_once("libnutscan"); + + /* Succeeds after init and if the library is loaded, else no-op */ + nutscan_upscli_upslog_start_sync(nstv, cookie ? cookie : nutscan_upslog_cookie()); + + return nstv; +} + void nutscan_init(void) { char *libname = NULL; @@ -164,6 +234,8 @@ void nutscan_init(void) } #endif /* WIN32 */ + upsdebugx(1, "%s: starting...", __func__); + /* Optional filter to not walk things twice */ nut_prepare_search_paths(); @@ -694,6 +766,8 @@ void nutscan_init(void) /* start of "NUT Simulation" - unconditional */ /* no need for additional library */ nutscan_avail_nut_simulation = 1; + + upsdebugx(1, "%s: done", __func__); } /* Return 0 on success, -1 on error e.g. "was not loaded"; @@ -750,6 +824,8 @@ int nutscan_unload_library(int *avail, lt_dlhandle *pdl_handle, char **libpath) void nutscan_free(void) { + upsdebugx(1, "%s: starting...", __func__); + nutscan_unload_usb_library(); nutscan_unload_snmp_library(); nutscan_unload_neon_library(); @@ -775,4 +851,5 @@ void nutscan_free(void) # endif #endif + upsdebugx(1, "%s: done", __func__); } diff --git a/tools/nut-scanner/nutscan-init.h b/tools/nut-scanner/nutscan-init.h index b8306c50f8..359245d193 100644 --- a/tools/nut-scanner/nutscan-init.h +++ b/tools/nut-scanner/nutscan-init.h @@ -44,6 +44,29 @@ extern int nutscan_avail_upower; void nutscan_init(void); void nutscan_free(void); +/* On some platforms, libnutscan builds tend to get a built-in copy + * of the internal code from NUT libcommon library (and maybe provide + * those methods and data to programs like nut-scanner); also there + * may be dynamic loading of libupsclient library. So for NUT client + * programs using both libraries as dynamically-linked shared code, + * the nut_debug_level setting is backed by independent variables in + * active memory, and upsdebugx() calls suffer if the library's copy + * is never changed from zero. It can get even more confusing with + * libnutprivate-common being a shared dynamically loaded library + * instance behind both the program and libnutscan/libupsclient, + * hence the cookies: direct NUT-common consumers like NUT in-tree + * clients can use their nut_common_cookie() to pass into methods here. + */ +const void *nutscan_upslog_cookie(void); +void nutscan_upslog_set_debug_level(int level, const void *cookie); +int nutscan_upslog_get_debug_level(void); + +void nutscan_upslog_setprocname(const char *pn, const void *cookie); +void nutscan_upslog_setproctag(const char *tag, const void *cookie); +const char *nutscan_upslog_getproctag(void); + +struct timeval *nutscan_upslog_start_sync(struct timeval *tv, const void *cookie); + #define DEFAULT_THREAD 512 #ifdef __cplusplus diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index f143b67a36..c22bdb7d9b 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -52,7 +52,10 @@ static int (*nut_upscli_list_next)(UPSCONN_t *ups, size_t numq, const char **query, size_t *numa, char ***answer); static int (*nut_upscli_disconnect)(UPSCONN_t *ups); static int (*nut_upscli_ssl_caps)(void); -static int (*nut_upscli_set_debug_level)(int); +static int (*nut_upscli_upslog_set_debug_level)(int level, const void *cookie); +static void (*nut_upscli_upslog_setproctag)(const char *tag, const void *cookie); +static void (*nut_upscli_upslog_setprocname)(const char *tag, const void *cookie); +static struct timeval *(*nut_upscli_upslog_start_sync)(struct timeval *tv, const void *cookie); static void (*nut_upscli_report_build_details)(void); /* This variable collects device(s) from a sequential or parallel scan, @@ -85,6 +88,42 @@ int nutscan_unload_upsclient_library(void) return nutscan_unload_library(&nutscan_avail_nut, &dl_handle, &dl_saved_libname); } +/* Visible externally */ +void nutscan_upscli_set_debug_level(int level, const void *cookie); +void nutscan_upscli_set_debug_level(int level, const void *cookie) +{ + if (nutscan_avail_nut && dl_handle && nut_upscli_upslog_set_debug_level) { + (*nut_upscli_upslog_set_debug_level)(level, cookie); + } +} + +void nutscan_upscli_setprocname(const char *pn, const void *cookie); +void nutscan_upscli_setprocname(const char *pn, const void *cookie) +{ + if (nutscan_avail_nut && dl_handle && nut_upscli_upslog_setprocname) { + (*nut_upscli_upslog_setprocname)(pn, cookie); + } +} + +void nutscan_upscli_setproctag(const char *tag, const void *cookie); +void nutscan_upscli_setproctag(const char *tag, const void *cookie) +{ + if (nutscan_avail_nut && dl_handle && nut_upscli_upslog_setproctag) { + (*nut_upscli_upslog_setproctag)(tag, cookie); + } +} + +struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv, const void *cookie); +struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv, const void *cookie) +{ + if (nutscan_avail_nut && dl_handle && nut_upscli_upslog_start_sync) { + return (*nut_upscli_upslog_start_sync)(tv, cookie); + } + + /* So far return the nutscan library's copy */ + return upslog_start_sync(NULL); +} + /* Return 0 on error; visible externally */ int nutscan_load_upsclient_library(const char *libname_path); int nutscan_load_upsclient_library(const char *libname_path) @@ -159,14 +198,52 @@ int nutscan_load_upsclient_library(const char *libname_path) __func__, symbol); } - *(void **) (&nut_upscli_set_debug_level) = lt_dlsym(dl_handle, - symbol = "upscli_set_debug_level"); + *(void **) (&nut_upscli_upslog_start_sync) = lt_dlsym(dl_handle, + symbol = "upscli_upslog_start_sync"); + if ((dl_error = lt_dlerror()) != NULL) { + nut_upscli_upslog_start_sync = NULL; + upsdebugx(1, "%s: %s() not found, using older libupsclient build?", + __func__, symbol); + } else { + /* Propagate value currently known in libnutscan into libupsclient */ + upsdebugx(1, "%s: initializing %s() from libnutscan data", __func__, symbol); + (*nut_upscli_upslog_start_sync)(upslog_start_sync(NULL), nut_common_cookie()); + } + + *(void **) (&nut_upscli_upslog_set_debug_level) = lt_dlsym(dl_handle, + symbol = "upscli_upslog_set_debug_level"); + if ((dl_error = lt_dlerror()) != NULL) { + nut_upscli_upslog_set_debug_level = NULL; + upsdebugx(1, "%s: %s() not found, using older libupsclient build?", + __func__, symbol); + } else { + /* Propagate value currently known in libnutscan into libupsclient */ + upsdebugx(1, "%s: initializing %s() from libnutscan data (%d)", __func__, symbol, nut_debug_level); + (*nut_upscli_upslog_set_debug_level)(nut_debug_level, nut_common_cookie()); + } + + *(void **) (&nut_upscli_upslog_setprocname) = lt_dlsym(dl_handle, + symbol = "upscli_upslog_setprocname"); + if ((dl_error = lt_dlerror()) != NULL) { + nut_upscli_upslog_setprocname = NULL; + upsdebugx(1, "%s: %s() not found, using older libupsclient build?", + __func__, symbol); + } else { + /* Propagate value currently known in libnutscan into libupsclient */ + upsdebugx(1, "%s: initializing %s() from libnutscan data (%s)", __func__, symbol, getmyprocname()); + (*nut_upscli_upslog_setprocname)(xstrdup(getmyprocname()), nut_common_cookie()); + } + + *(void **) (&nut_upscli_upslog_setproctag) = lt_dlsym(dl_handle, + symbol = "upscli_upslog_setproctag"); if ((dl_error = lt_dlerror()) != NULL) { - nut_upscli_set_debug_level = NULL; + nut_upscli_upslog_setproctag = NULL; upsdebugx(1, "%s: %s() not found, using older libupsclient build?", __func__, symbol); } else { - (*nut_upscli_set_debug_level)(nut_debug_level); + /* Propagate value currently known in libnutscan into libupsclient */ + upsdebugx(1, "%s: initializing %s() from libnutscan data (%s)", __func__, symbol, getproctag()); + (*nut_upscli_upslog_setproctag)(getproctag(), nut_common_cookie()); } *(void **) (&nut_upscli_report_build_details) = lt_dlsym(dl_handle, diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index f4a83c67f2..d4955811e4 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) * 2013 - EATON - * 2024-2025 - Jim Klimov + * 2024-2026 - Jim Klimov * * 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 @@ -62,10 +62,10 @@ class Usage { public: /** Print version and usage to stderr */ - static void print(const std::string & bin); + static void print(const std::string & bin, const std::string & binpath); /** Print version info to stdout */ - static void printVersion(const std::string & bin); + static void printVersion(const std::string & bin, const std::string & binpath); }; // end of class usage @@ -113,9 +113,10 @@ const char * Usage::s_text[] = { " specified multiple times to set multiple users", " --add-user Same as --set-user, but keeps existing users", " The two options are mutually exclusive", - /* FIXME: Alias as "-D"? Is this the same as nut_debug_level - * NOTE: upsdebugx() not used here directly (yet?), though we + /* NOTE: upsdebugx() not used here directly (yet?), though we * could setenv() the envvar for libnutscan perhaps? */ + " -D Raise debugging level", + /* TOCHECK: Is this the same as nut_debug_level, or tool verbosity? */ " -v", " --verbose Increase verbosity of output one level", " May be specified multiple times", @@ -185,21 +186,24 @@ const char * Usage::s_text[] = { /** * Print version info to stdout (like other NUT tools) */ -void Usage::printVersion(const std::string & bin) { +void Usage::printVersion(const std::string & bin, const std::string & binpath) { std::cout << "Network UPS Tools " << bin << " " << describe_NUT_VERSION_once() << std::endl; + + if (!binpath.empty()) std::cout + << "Located in " << binpath << std::endl; } /** * Print help text (including version info) to stderr */ -void Usage::print(const std::string & bin) { +void Usage::print(const std::string & bin, const std::string & binpath) { std::cerr << "Network UPS Tools " << bin << " " << describe_NUT_VERSION_once() << std::endl << std::endl - << "Usage: " << bin << " [OPTIONS]" << std::endl + << "Usage: " << (binpath.empty() ? bin : binpath) << " [OPTIONS]" << std::endl << std::endl << "OPTIONS:" << std::endl; @@ -715,7 +719,12 @@ class NutScanner { /** NUT scanner initialization/finalization */ struct InitFinal { /** Initialization */ - InitFinal() { nutscan_init(); } + InitFinal() { + /* Register atexit() cleanups to scope around libnutscan lifetime */ + nutscan_upslog_start_sync(upslog_start_sync(nullptr), nut_common_cookie()); + nutscan_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + nutscan_init(); + } /** Finalization */ ~InitFinal() { nutscan_free(); } @@ -1330,17 +1339,25 @@ NutConfOptions::NutConfOptions(char * const argv[], int argc): static const std::string dDash("--"); // Specify single-dashed options + // (maybe several short options after a common single dash) List list = stringsSingle(); for (List::const_iterator opt = list.begin(); opt != list.end(); ++opt) { - // Known options - if ("v" == *opt) { - ++verbose; - } - - // Unknown option - else { - m_unknown.push_back(sDash + *opt); + for (char &opt_ret : std::string(*opt)) { + // Known single-character options + switch (opt_ret) { + case 'v': + ++verbose; + break; + + case 'D': + ++nut_debug_level; + break; + + // Unknown option + default: + m_unknown.push_back(sDash + opt_ret); + } } } @@ -3123,22 +3140,43 @@ static void scanSerialDevices(const NutConfOptions & options) { * \return 0 always (exits on error) */ static int mainx(int argc, char * const argv[]) { - const char *prog = xbasename(argv[0]); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : nullptr, "nutconf"); char *s = nullptr; - // Get options + // Get options, also set nut_debug_level NutConfOptions options(argv, argc); + if (!nut_debug_level) { + int l; + + s = ::getenv("NUT_DEBUG_LEVEL"); + if (s && str_to_int(s, &l, 10) && l > 0) { + nut_debug_level = l; + upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " + "since none was requested by command-line options", l); + } /* else follow -D settings */ + } + + /* These lines aim to just initialize the logging subsystem, and set + * initial timestamp, for the eventuality that debugs would be printed: + */ +#if (defined WITH_NUTSCANNER) + nutscan_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); + nutscan_upslog_setproctag(prog, nut_common_cookie()); +#endif + setproctag(prog); + upsdebugx(1, "Starting NUT configuration tool: %s", prog); + // Usage if (options.exists("help") || options.existsSingle("h")) { - Usage::print(prog); + Usage::print(prog, ""); ::exit(0); } // Usage if (options.exists("version") || options.existsSingle("V")) { - Usage::printVersion(prog); + Usage::printVersion(prog, ""); ::exit(0); } @@ -3147,7 +3185,7 @@ static int mainx(int argc, char * const argv[]) { if (!options.valid) { options.reportInvalid(); - Usage::print(argv[0]); + Usage::print(prog, argv[0]); ::exit(1); } @@ -3254,6 +3292,7 @@ static int mainx(int argc, char * const argv[]) { } #if (defined WITH_NUTSCANNER) + upsdebugx(1, "Scanning devices (if enabled)..."); // SNMP devices scan if (options.scan_snmp_cnt) { @@ -3290,8 +3329,10 @@ static int mainx(int argc, char * const argv[]) { scanSerialDevices(options); } + nutscan_upslog_setproctag(prog, nut_common_cookie()); #endif // defined WITH_NUTSCANNER + upsdebugx(1, "Finishing NUT configuration tool: %s", prog); return 0; } @@ -3306,6 +3347,7 @@ static int mainx(int argc, char * const argv[]) { */ int main(int argc, char * const argv[]) { try { + upslog_start_sync(nullptr); return mainx(argc, argv); } catch (const std::exception & e) {