From 6ed111fe7099b4eca3436a779152ffaa26039117 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 10:25:48 +0100 Subject: [PATCH 01/65] clients/upsc.c: move usage() to top like in other clients [#3378] Signed-off-by: Jim Klimov --- clients/upsc.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index 91a746612d..0b9e02a55a 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -39,25 +39,6 @@ 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)); -} - static void usage(const char *prog) { print_banner_once(prog, 2); @@ -96,6 +77,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; From 724ba4b133697cee583a59ffb3f60c2093ff4cea Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 10:43:26 +0100 Subject: [PATCH 02/65] Clients and daemons with command-line options: converge to using getopt(...optstring) with the latter defined near the help/usage method [#3378] Signed-off-by: Jim Klimov --- clients/upsc.c | 5 ++++- clients/upscmd.c | 6 +++++- clients/upslog.c | 5 ++++- clients/upsmon.c | 5 ++++- clients/upsrw.c | 6 +++++- clients/upssched.c | 7 +++++-- drivers/upsdrvctl.c | 5 ++++- scripts/Windows/wininit.c | 7 +++++-- server/upsd.c | 5 ++++- 9 files changed, 40 insertions(+), 11 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index 0b9e02a55a..e3a1b3f322 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -39,6 +39,9 @@ static char *upsname = NULL, *hostname = NULL; static UPSCONN_t *ups = NULL; static int output_json = 0; +/* For getopt loops below: */ +static const char optstring[] = "+hlLcVW:j"; + static void usage(const char *prog) { print_banner_once(prog, 2); @@ -412,7 +415,7 @@ int main(int argc, char **argv) callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, "+hlLcVW:j")) != -1) { + while ((i = getopt(argc, argv, optstring)) != -1) { switch (i) { diff --git a/clients/upscmd.c b/clients/upscmd.c index 05fc13d1d7..7800b9fe50 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 @@ -48,6 +49,9 @@ struct list_t { struct list_t *next; }; +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+lhu:p:t:wVW:"; + static void usage(const char *prog) { print_banner_once(prog, 2); @@ -319,7 +323,7 @@ int main(int argc, char **argv) callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, "+lhu:p:t:wVW:")) != -1) { + while ((i = getopt(argc, argv, optstring)) != -1) { switch (i) { diff --git a/clients/upslog.c b/clients/upslog.c index f7f879d570..9bcbba720a 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -190,6 +190,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); @@ -529,7 +532,7 @@ int main(int argc, char **argv) setproctag(prog); print_banner_once(prog, 0); - while ((i = getopt(argc, argv, "+hDs:l:i:d:Nf:u:Vp:FBm:W:")) != -1) { + while ((i = getopt(argc, argv, optstring)) != -1) { switch(i) { case 'h': help(prog); diff --git a/clients/upsmon.c b/clients/upsmon.c index fea5cb4577..73ba04a615 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -3393,6 +3393,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; @@ -3826,7 +3829,7 @@ int main(int argc, char *argv[]) run_as_user = xstrdup(RUN_AS_USER); - while ((i = getopt(argc, argv, "+DFBhic:P:f:pu:VK46W:")) != -1) { + while ((i = getopt(argc, argv, optstring)) != -1) { switch (i) { case 'c': if (!strncmp(optarg, "fsd", strlen(optarg))) { diff --git a/clients/upsrw.c b/clients/upsrw.c index 662e816036..957f4ece6f 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 @@ -48,6 +49,9 @@ struct list_t { struct list_t *next; }; +/* For getopt loops; should match usage documented below: */ +static const char optstring[] = "+hls:p:t:u:wVW:"; + static void usage(const char *prog) { print_banner_once(prog, 2); @@ -677,7 +681,7 @@ int main(int argc, char **argv) callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, "+hls:p:t:u:wVW:")) != -1) { + while ((i = getopt(argc, argv, optstring)) != -1) { switch (i) { case 's': diff --git a/clients/upssched.c b/clients/upssched.c index b201d82c1d..5116036384 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"); @@ -2197,7 +2200,7 @@ int main(int argc, char **argv) if (!prog) prog = "upssched"; - while ((i = getopt(argc, argv, "+DVhl")) != -1) { + while ((i = getopt(argc, argv, optstring)) != -1) { argn++; switch (i) { case 'D': diff --git a/drivers/upsdrvctl.c b/drivers/upsdrvctl.c index dfa95f2b75..5fd9c07726 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); @@ -1701,7 +1704,7 @@ 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) { + while ((i = getopt(argc, argv, optstring)) != -1) { switch(i) { case 'r': pt_root = optarg; diff --git a/scripts/Windows/wininit.c b/scripts/Windows/wininit.c index a1fc5577a1..9e93cdd5f5 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); @@ -928,7 +931,7 @@ int main(int argc, char **argv) * Currently neutered because that method ignores argc/argv de-facto. * opterr = 0; */ - while ((i = getopt(argc, argv, "+IUNDVh")) != -1) { + while ((i = getopt(argc, argv, optstring)) != -1) { switch (i) { case 'I': return SvcInstall(SVCNAME, NULL); diff --git a/server/upsd.c b/server/upsd.c index 3a370193e2..234cba89b0 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); @@ -2350,7 +2353,7 @@ int main(int argc, char **argv) print_banner_once(progname, 0); - while ((i = getopt(argc, argv, "+h46p:qr:i:fu:Vc:P:DFB")) != -1) { + while ((i = getopt(argc, argv, optstring)) != -1) { switch (i) { case 'p': case 'i': From 9ee23d2fd4d13e12835214746630ca4f917fa8e0 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 10:48:06 +0100 Subject: [PATCH 03/65] clients/ups{c,cmd,rw}.c: converge to naming the help() method same way as other programs [#3778] Signed-off-by: Jim Klimov --- clients/upsc.c | 4 ++-- clients/upscmd.c | 8 ++++---- clients/upsrw.c | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index e3a1b3f322..5e83ae8843 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -42,7 +42,7 @@ static int output_json = 0; /* For getopt loops below: */ static const char optstring[] = "+hlLcVW: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"); @@ -448,7 +448,7 @@ int main(int argc, char **argv) case 'h': default: - usage(prog); + help(prog); exit(EXIT_SUCCESS); } } diff --git a/clients/upscmd.c b/clients/upscmd.c index 7800b9fe50..e99950b88c 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -52,7 +52,7 @@ struct list_t { /* For getopt loops; should match usage documented below: */ static const char optstring[] = "+lhu:p:t:wVW:"; -static void usage(const char *prog) +static void help(const char *prog) { print_banner_once(prog, 2); printf("NUT administration client program to initiate instant commands on UPS hardware.\n"); @@ -363,7 +363,7 @@ int main(int argc, char **argv) case 'h': default: - usage(prog); + help(prog); exit(EXIT_SUCCESS); } } @@ -377,7 +377,7 @@ int main(int argc, char **argv) argv += optind; if (argc < 1) { - usage(prog); + help(prog); exit(EXIT_SUCCESS); } @@ -401,7 +401,7 @@ int main(int argc, char **argv) } if (argc < 2) { - usage(prog); + help(prog); exit(EXIT_SUCCESS); } diff --git a/clients/upsrw.c b/clients/upsrw.c index 957f4ece6f..0a11c37e83 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -52,7 +52,7 @@ struct list_t { /* For getopt loops; should match usage documented below: */ static const char optstring[] = "+hls:p:t:u:wVW:"; -static void usage(const char *prog) +static void help(const char *prog) { print_banner_once(prog, 2); printf("NUT administration client program to set variables within UPS hardware.\n"); @@ -717,7 +717,7 @@ int main(int argc, char **argv) break; case 'h': default: - usage(prog); + help(prog); exit(EXIT_SUCCESS); } } @@ -731,7 +731,7 @@ int main(int argc, char **argv) argv += optind; if (argc < 1) { - usage(prog); + help(prog); exit(EXIT_SUCCESS); } From 53a8e2619e54080d309815d4607d0e50b2279a51 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 11:02:14 +0100 Subject: [PATCH 04/65] Clients and daemons with command-line options: converge to using opt_ret=getopt(...), use a different variable for unrelated iteration [#3378] Signed-off-by: Jim Klimov --- clients/upsc.c | 6 +++--- clients/upscmd.c | 6 +++--- clients/upslog.c | 8 ++++---- clients/upsmon.c | 6 +++--- clients/upsrw.c | 6 +++--- clients/upssched.c | 8 ++++---- drivers/main.c | 18 ++++++++++-------- drivers/upsdrvctl.c | 12 +++++++----- scripts/Windows/wininit.c | 8 ++++---- server/upsd.c | 6 +++--- 10 files changed, 44 insertions(+), 40 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index 5e83ae8843..c8d5c27d75 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -391,7 +391,7 @@ static void clean_exit(void) int main(int argc, char **argv) { - int i = 0; + int opt_ret = 0, i; uint16_t port; int varlist = 0, clientlist = 0, verbose = 0; const char *prog = xbasename(argv[0]); @@ -415,9 +415,9 @@ int main(int argc, char **argv) callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, optstring)) != -1) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { - switch (i) + switch (opt_ret) { case 'L': verbose = 1; diff --git a/clients/upscmd.c b/clients/upscmd.c index e99950b88c..9cff54f766 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -298,7 +298,7 @@ static void clean_exit(void) int main(int argc, char **argv) { - int i; + int opt_ret = 0; uint16_t port; ssize_t ret; int have_un = 0, have_pw = 0, cmdlist = 0; @@ -323,9 +323,9 @@ int main(int argc, char **argv) callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, optstring)) != -1) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { - switch (i) + switch (opt_ret) { case 'l': cmdlist = 1; diff --git a/clients/upslog.c b/clients/upslog.c index 9bcbba720a..964b9d4d73 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -511,7 +511,7 @@ 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; + 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 *net_connect_timeout = NULL; @@ -532,8 +532,8 @@ int main(int argc, char **argv) setproctag(prog); print_banner_once(prog, 0); - while ((i = getopt(argc, argv, optstring)) != -1) { - switch(i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch(opt_ret) { case 'h': help(prog); #ifndef HAVE___ATTRIBUTE__NORETURN @@ -657,7 +657,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/upsmon.c b/clients/upsmon.c index 73ba04a615..3b6be49cdd 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -3790,7 +3790,7 @@ int main(int argc, char *argv[]) { const char *prog = xbasename(argv[0]); 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 @@ -3829,8 +3829,8 @@ int main(int argc, char *argv[]) run_as_user = xstrdup(RUN_AS_USER); - while ((i = getopt(argc, argv, optstring)) != -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; diff --git a/clients/upsrw.c b/clients/upsrw.c index 0a11c37e83..c67a9873f9 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -658,7 +658,7 @@ static void print_rwlist(void) int main(int argc, char **argv) { - int i; + int opt_ret = 0, i; uint16_t port; const char *prog = xbasename(argv[0]); const char *net_connect_timeout = NULL; @@ -681,8 +681,8 @@ int main(int argc, char **argv) callback_upsconf_args = do_upsconf_args; #endif - while ((i = getopt(argc, argv, optstring)) != -1) { - switch (i) + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch (opt_ret) { case 's': setvar = optarg; diff --git a/clients/upssched.c b/clients/upssched.c index 5116036384..cf5d1c51e8 100644 --- a/clients/upssched.c +++ b/clients/upssched.c @@ -2193,16 +2193,16 @@ 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"; - while ((i = getopt(argc, argv, optstring)) != -1) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { argn++; - switch (i) { + switch (opt_ret) { case 'D': nut_debug_level_args++; break; @@ -2225,7 +2225,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/drivers/main.c b/drivers/main.c index 88590117ad..d8f596cb1a 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -2154,7 +2154,7 @@ 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 @@ -2198,8 +2198,8 @@ int main(int argc, char **argv) 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++; @@ -2304,8 +2304,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 +2364,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))) { @@ -2393,7 +2394,8 @@ int main(int argc, char **argv) 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 if (cmd > 0) @@ -2485,7 +2487,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/drivers/upsdrvctl.c b/drivers/upsdrvctl.c index 5fd9c07726..42e20ac19e 100644 --- a/drivers/upsdrvctl.c +++ b/drivers/upsdrvctl.c @@ -1691,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]; @@ -1704,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, optstring)) != -1) { - switch(i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch(opt_ret) { case 'r': pt_root = optarg; break; @@ -1752,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"; @@ -1792,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/scripts/Windows/wininit.c b/scripts/Windows/wininit.c index 9e93cdd5f5..ebff2366e7 100644 --- a/scripts/Windows/wininit.c +++ b/scripts/Windows/wininit.c @@ -924,15 +924,15 @@ 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; 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, optstring)) != -1) { - switch (i) { + while ((opt_ret = getopt(argc, argv, optstring)) != -1) { + switch (opt_ret) { case 'I': return SvcInstall(SVCNAME, NULL); case 'U': @@ -968,7 +968,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/upsd.c b/server/upsd.c index 234cba89b0..9c1a6c5380 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -2306,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; @@ -2353,8 +2353,8 @@ int main(int argc, char **argv) print_banner_once(progname, 0); - while ((i = getopt(argc, argv, optstring)) != -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" From 5bcfc0155c98017f9cfc386ea7ce3698f088cfa4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 11:30:14 +0100 Subject: [PATCH 05/65] clients/ups{c,cmd,rw}.c, man pages, NEWS.adoc: add "-D" option support [#3378] Signed-off-by: Jim Klimov --- NEWS.adoc | 2 ++ clients/upsc.c | 48 ++++++++++++++++++++++++++++++++----------- clients/upscmd.c | 47 ++++++++++++++++++++++++++++++++---------- clients/upsrw.c | 50 ++++++++++++++++++++++++++++++++++----------- docs/man/upsc.txt | 5 +++++ docs/man/upscmd.txt | 4 ++++ docs/man/upsrw.txt | 4 ++++ 7 files changed, 125 insertions(+), 35 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index d9de4084e0..3905850e37 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -137,6 +137,8 @@ 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. [#3378] - NUT for Windows specific updates: * Revised detection of (relative) paths to program and configuration files diff --git a/clients/upsc.c b/clients/upsc.c index c8d5c27d75..9ecfe7017d 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -40,7 +40,7 @@ static UPSCONN_t *ups = NULL; static int output_json = 0; /* For getopt loops below: */ -static const char optstring[] = "+hlLcVW:j"; +static const char optstring[] = "+DhlLcVW:j"; static void help(const char *prog) { @@ -72,6 +72,7 @@ static void help(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(); @@ -391,34 +392,57 @@ static void clean_exit(void) int main(int argc, char **argv) { - int opt_ret = 0, i; + int opt_ret = 0; uint16_t port; int varlist = 0, clientlist = 0, verbose = 0; const char *prog = xbasename(argv[0]); 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. + /* 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_set_debug_level(nut_debug_level); + 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 + /* 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 'L': verbose = 1; goto fallthrough_case_l; diff --git a/clients/upscmd.c b/clients/upscmd.c index 9cff54f766..8915d2c296 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -50,7 +50,7 @@ struct list_t { }; /* For getopt loops; should match usage documented below: */ -static const char optstring[] = "+lhu:p:t:wVW:"; +static const char optstring[] = "+Dlhu:p:t:wVW:"; static void help(const char *prog) { @@ -75,6 +75,7 @@ static void help(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(); @@ -302,31 +303,55 @@ int main(int argc, char **argv) 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; + char buf[SMALLBUF * 2], username[SMALLBUF], password[SMALLBUF]; const char *prog = xbasename(argv[0]); 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. + /* 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_set_debug_level(nut_debug_level); + 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 + /* 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 'l': cmdlist = 1; break; diff --git a/clients/upsrw.c b/clients/upsrw.c index c67a9873f9..571fcb3772 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -50,7 +50,7 @@ struct list_t { }; /* For getopt loops; should match usage documented below: */ -static const char optstring[] = "+hls:p:t:u:wVW:"; +static const char optstring[] = "+Dhls:p:t:u:wVW:"; static void help(const char *prog) { @@ -74,6 +74,7 @@ static void help(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"); @@ -658,32 +659,57 @@ static void print_rwlist(void) int main(int argc, char **argv) { - int opt_ret = 0, i; + int opt_ret = 0; uint16_t port; const char *prog = xbasename(argv[0]); 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. + /* 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_set_debug_level(nut_debug_level); + 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 + /* 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; 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/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. From 6a1acba51fa9496f01b7edefb3e0cccd37e7aadd Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 16:24:01 +0100 Subject: [PATCH 06/65] NEWS.adoc: rephrase change about upsset [#3164] Signed-off-by: Jim Klimov --- NEWS.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.adoc b/NEWS.adoc index 3905850e37..be1bd4a6e0 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -397,7 +397,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. From 2499ec026c14602c674bb09df524e94465b2f045 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Mar 2026 01:10:15 +0100 Subject: [PATCH 07/65] Makefile.am: add {all,check,install}-quick goals integrated with NUT CI envvars for limited parallelism [#2871] Signed-off-by: Jim Klimov --- Makefile.am | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index eb0738a36b..bfa89c4361 100644 --- a/Makefile.am +++ b/Makefile.am @@ -855,9 +855,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@ From 315e39b92192f69f0808de01c9d8563f304164a5 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Mar 2026 02:45:59 +0100 Subject: [PATCH 08/65] Makefile.am, NEWS.adoc: Added `make .libs-dev-PATH` recipe to help devs and CI [#3379] Signed-off-by: Jim Klimov --- .gitignore | 1 + Makefile.am | 27 ++++++++++++++++++++++++--- NEWS.adoc | 3 +++ 3 files changed, 28 insertions(+), 3 deletions(-) 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 bfa89c4361..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 diff --git a/NEWS.adoc b/NEWS.adoc index be1bd4a6e0..bae8359783 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -543,6 +543,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` From fd80163186029dd26c14638de0c197b983585bb8 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 12:05:49 +0100 Subject: [PATCH 09/65] common/common.c, include/common.h: introduce xbasename_no_ext() to refactor setproctag() and other code later [#3302, #3368, #3373] Signed-off-by: Jim Klimov --- common/common.c | 144 +++++++++++++++++++++++++---------------------- include/common.h | 5 ++ 2 files changed, 81 insertions(+), 68 deletions(-) diff --git a/common/common.c b/common/common.c index 71312a5e26..03fe155d9d 100644 --- a/common/common.c +++ b/common/common.c @@ -2614,7 +2614,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 +2624,77 @@ 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; + + /* 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; +} + /* Based on https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html * modified for a syntax similar to difftime() */ @@ -4248,38 +4319,8 @@ static char *proctag = NULL, *proctag_for_upsdebug = NULL, static void proctag_cleanup(void) { if (proctag) { - char *pn = xstrdup(getmyprocbasename()); - char *tn = xstrdup(proctag); - -#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 (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 + 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" @@ -4339,43 +4380,10 @@ void setproctag(const char *tag) proctag_for_upsdebug_buflen = strlen(tag) + 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 diff --git a/include/common.h b/include/common.h index 69f8d7348a..cbd5fccf20 100644 --- a/include/common.h +++ b/include/common.h @@ -445,6 +445,11 @@ 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); /* enable writing upslog_with_errno() and upslogx() type messages to * the stdout instead of stderr, and end them with HTML
tag, From 1949cb3fb5265b5e354bd6b4efbdcbb15f2bf127 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 12:30:07 +0100 Subject: [PATCH 10/65] clients/upsmon.c: refactor with xbasename_no_ext() [#3373] Signed-off-by: Jim Klimov --- clients/upsmon.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/clients/upsmon.c b/clients/upsmon.c index 3b6be49cdd..cdc2dce33a 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -3788,7 +3788,7 @@ static void init_Inhibitor(const char *prog) int main(int argc, char *argv[]) { - const char *prog = xbasename(argv[0]); + const char *prog = xbasename_no_ext(argv[0]); const char *net_connect_timeout = NULL; int opt_ret = 0, cmdret = -1, checking_flag = 0, foreground = -1; struct timeval prevstart; @@ -3803,21 +3803,6 @@ 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 */ print_banner_once(prog, 0); From 1cd13c91b325d0899002557dc423f8865ddd2448 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 12:31:08 +0100 Subject: [PATCH 11/65] drivers/main.c: refactor with xbasename_no_ext() [#3373] Signed-off-by: Jim Klimov --- drivers/main.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/drivers/main.c b/drivers/main.c index d8f596cb1a..7bb617d802 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -2164,8 +2164,7 @@ int main(int argc, char **argv) /* FIXME NUT_WIN32_INCOMPLETE : *actually* handle WIN32 builds too */ const char * cmd = NULL; - const char * drv_name = NULL; - char * dot = NULL; + char * drv_name = NULL; char name[NUT_PATH_MAX + 1]; #endif /* WIN32 */ @@ -2267,25 +2266,23 @@ int main(int argc, char **argv) memset(prognames, 0, sizeof(prognames)); memset(prognames_should_free, 0, sizeof(prognames_should_free)); + + /* Note: "const char *" to (substring of) argv[0] itself: */ 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; + /* remove trailing .exe if present; returns new allocation (if not NULL) */ + drv_name = xbasename_no_ext(prognames[0]); + if (drv_name) { + if (strcmp(drv_name, prognames[0])) { + /* strings differ */ + prognames[0] = drv_name; prognames_should_free[0] = 1; + } else { + /* discard the dynamic copy */ + free(drv_name); } } - else { - prognames[0] = strdup(drv_name); - prognames_should_free[0] = 1; - } #endif /* WIN32 */ upsdrv_callbacks.upsdrv_tweak_prognames(); From b09dcf27eb0fb501281833438c566a00eff389cb Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 12:31:26 +0100 Subject: [PATCH 12/65] server/upsd.c: refactor with xbasename_no_ext() [#3373] Signed-off-by: Jim Klimov --- server/upsd.c | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/server/upsd.c b/server/upsd.c index 9c1a6c5380..7218ebac24 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -2317,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 = xbasename_no_ext(argv[0]); setproctag(progname); #if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS @@ -2330,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 */ From 2fedb983c721e36bd862ab538e3093a1a17ca7ba Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 12:33:38 +0100 Subject: [PATCH 13/65] drivers/main.c: define optstring near help_msg() to match other sources [#3378] Signed-off-by: Jim Klimov --- drivers/main.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/main.c b/drivers/main.c index 7bb617d802..0825992087 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -262,6 +262,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) { @@ -2168,12 +2175,6 @@ int main(int argc, char **argv) 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 callback_upsconf_args = do_upsconf_args; #else From 2192271d79cab099f0f8e6e3d8c1379cf83763bc Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 12:34:08 +0100 Subject: [PATCH 14/65] drivers/main.c: comment the complexity of many debug-level vars [#3378] Signed-off-by: Jim Klimov --- drivers/main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/main.c b/drivers/main.c index 0825992087..dcecbe2096 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -2194,7 +2194,10 @@ int main(int argc, char **argv) #endif #endif - /* 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 */ From d115b444aa71774c0de5c96cab7be130b6b936c1 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 12:34:46 +0100 Subject: [PATCH 15/65] drivers/main.c: rename "drv_pipe_name" for maintainability (WIN32) [#3302] Signed-off-by: Jim Klimov --- drivers/main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/main.c b/drivers/main.c index dcecbe2096..4113070f42 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -2172,7 +2172,7 @@ int main(int argc, char **argv) const char * cmd = NULL; char * drv_name = NULL; - char name[NUT_PATH_MAX + 1]; + char drv_pipe_name[NUT_PATH_MAX + 1]; #endif /* WIN32 */ #if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS @@ -2938,7 +2938,7 @@ int main(int argc, char **argv) } } #else /* WIN32 */ - snprintf(name, sizeof(name), "%s-%s", progname, upsname); + 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 */ @@ -2946,10 +2946,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()); } } @@ -2957,7 +2957,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) { @@ -2966,7 +2966,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; } From c027677ad2f291a981551356a5676cbcb6b07f03 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 12:51:19 +0100 Subject: [PATCH 16/65] common/common.c, include/common.h: introduce xbasename_no_ext_default() and getprogname_argv0_default() to refactor NUT programs later [#3378] Signed-off-by: Jim Klimov --- common/common.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ include/common.h | 14 ++++++++++ 2 files changed, 82 insertions(+) diff --git a/common/common.c b/common/common.c index 03fe155d9d..10871bb0a2 100644 --- a/common/common.c +++ b/common/common.c @@ -2695,6 +2695,74 @@ char *xbasename_no_ext(const char *file) 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 */ + atexit(cleanup_progname_argv0_default); + atexit_hooked = 1; + } + + 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() */ diff --git a/include/common.h b/include/common.h index cbd5fccf20..f6333e0b7a 100644 --- a/include/common.h +++ b/include/common.h @@ -450,6 +450,20 @@ const char *xbasename(const char *file); * 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, From 8362709a4ecf7a0171833a3b9ba95ff0e25c3c71 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 14:33:18 +0100 Subject: [PATCH 17/65] Refactor various NUT programs to use getprogname_argv0_default(), and with safer access to argv[0] [#3378] Signed-off-by: Jim Klimov --- clients/upsc.c | 6 ++++-- clients/upscmd.c | 6 ++++-- clients/upslog.c | 2 +- clients/upsmon.c | 10 +++++----- clients/upsrw.c | 6 ++++-- clients/upssched.c | 8 +++----- scripts/Windows/wininit.c | 3 +++ server/pipedebug.c | 2 +- server/sockdebug.c | 2 +- server/upsd.c | 2 +- 10 files changed, 27 insertions(+), 20 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index 9ecfe7017d..dfda0a3670 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -395,7 +395,7 @@ int main(int argc, char **argv) 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; /* NOTE: Debugging the client is primarily of use to developers, so @@ -483,6 +483,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; @@ -498,7 +500,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); diff --git a/clients/upscmd.c b/clients/upscmd.c index 8915d2c296..b6557d8b96 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -304,7 +304,7 @@ int main(int argc, char **argv) ssize_t ret; int have_un = 0, have_pw = 0, cmdlist = 0; char buf[SMALLBUF * 2], username[SMALLBUF], password[SMALLBUF]; - const char *prog = xbasename(argv[0]); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upscmd"); const char *net_connect_timeout = NULL; /* NOTE: Debugging the client is primarily of use to developers, so @@ -398,6 +398,8 @@ 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; @@ -412,7 +414,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)); diff --git a/clients/upslog.c b/clients/upslog.c index 964b9d4d73..b4b6dca7b9 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -513,7 +513,7 @@ int main(int argc, char **argv) { 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; diff --git a/clients/upsmon.c b/clients/upsmon.c index cdc2dce33a..513a9fcd25 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -3788,7 +3788,7 @@ static void init_Inhibitor(const char *prog) int main(int argc, char *argv[]) { - const char *prog = xbasename_no_ext(argv[0]); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsmon"); const char *net_connect_timeout = NULL; int opt_ret = 0, cmdret = -1, checking_flag = 0, foreground = -1; struct timeval prevstart; @@ -3829,12 +3829,12 @@ 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': @@ -3853,7 +3853,7 @@ int main(int argc, char *argv[]) configfile = xstrdup(optarg); break; case 'h': - help(argv[0]); + help(prog); #ifndef HAVE___ATTRIBUTE__NORETURN break; #endif @@ -3883,7 +3883,7 @@ int main(int argc, char *argv[]) net_connect_timeout = optarg; break; default: - help(argv[0]); + help(prog); #ifndef HAVE___ATTRIBUTE__NORETURN break; #endif diff --git a/clients/upsrw.c b/clients/upsrw.c index 571fcb3772..046037afbf 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -661,7 +661,7 @@ int main(int argc, char **argv) { 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; @@ -753,6 +753,8 @@ 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; @@ -767,7 +769,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)); diff --git a/clients/upssched.c b/clients/upssched.c index cf5d1c51e8..49843dd71f 100644 --- a/clients/upssched.c +++ b/clients/upssched.c @@ -2195,10 +2195,8 @@ int main(int argc, char **argv) { 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 ((opt_ret = getopt(argc, argv, optstring)) != -1) { argn++; @@ -2208,7 +2206,7 @@ int main(int argc, char **argv) break; case 'h': - help(argv[0]); + help(prog); #ifndef HAVE___ATTRIBUTE__NORETURN break; #endif diff --git a/scripts/Windows/wininit.c b/scripts/Windows/wininit.c index ebff2366e7..4d175d6de7 100644 --- a/scripts/Windows/wininit.c +++ b/scripts/Windows/wininit.c @@ -925,6 +925,9 @@ static void help(const char *arg_progname) int main(int argc, char **argv) { 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() 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 7218ebac24..685ec45514 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -2317,7 +2317,7 @@ int main(int argc, char **argv) const char *user = RUN_AS_USER; struct passwd *new_uid = NULL; - progname = xbasename_no_ext(argv[0]); + progname = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsd"); setproctag(progname); #if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS From 9b5af3e690b89d7b7e0cfa3ef5207f82dadc6759 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 14:40:46 +0100 Subject: [PATCH 18/65] tools/nut-scanner/nut-scanner.c, tools/nut-scanner/Makefile.am: refactor to use getprogname_argv0_default() [#3378] Signed-off-by: Jim Klimov --- tools/nut-scanner/Makefile.am | 2 +- tools/nut-scanner/nut-scanner.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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..29c80e17dc 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; From 6bd4768effb2f2e9b9671897c23683b3cdd7828d Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 14:41:11 +0100 Subject: [PATCH 19/65] tools/nutconf/nutconf-cli.cpp: refactor to use getprogname_argv0_default() for "nutconf" [#3378] Signed-off-by: Jim Klimov --- tools/nutconf/nutconf-cli.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index f4a83c67f2..5559b29315 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 @@ -185,21 +185,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; @@ -3123,7 +3126,7 @@ 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] : NULL, "nutconf"); char *s = nullptr; // Get options @@ -3131,14 +3134,14 @@ static int mainx(int argc, char * const argv[]) { // 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 +3150,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); } From 1caea032fc32a9ba3c90d0140529152f35b0db53 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 26 Mar 2026 16:29:30 +0100 Subject: [PATCH 20/65] common/nutstream.cpp: NutSocket::Address::Address() copier: sanity-check that socklen_t is in size_t range [#1651] Signed-off-by: Jim Klimov --- common/nutstream.cpp | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) 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); } From cd533a22a1b4d8f0d872fc7a7e46665dd7f64796 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 14:44:18 +0100 Subject: [PATCH 21/65] drivers/main.c: refactor to use getprogname_argv0_default(), get rid of WIN32 hacks [#3378] Signed-off-by: Jim Klimov --- drivers/main.c | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/drivers/main.c b/drivers/main.c index 4113070f42..65484c46c1 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -2170,8 +2170,6 @@ int main(int argc, char **argv) #else /* WIN32 */ /* FIXME NUT_WIN32_INCOMPLETE : *actually* handle WIN32 builds too */ const char * cmd = NULL; - - char * drv_name = NULL; char drv_pipe_name[NUT_PATH_MAX + 1]; #endif /* WIN32 */ @@ -2271,23 +2269,10 @@ int main(int argc, char **argv) memset(prognames, 0, sizeof(prognames)); memset(prognames_should_free, 0, sizeof(prognames_should_free)); - /* Note: "const char *" to (substring of) argv[0] itself: */ - prognames[0] = xbasename(argv[0]); - -#ifdef WIN32 - /* remove trailing .exe if present; returns new allocation (if not NULL) */ - drv_name = xbasename_no_ext(prognames[0]); - if (drv_name) { - if (strcmp(drv_name, prognames[0])) { - /* strings differ */ - prognames[0] = drv_name; - prognames_should_free[0] = 1; - } else { - /* discard the dynamic copy */ - free(drv_name); - } - } -#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_callbacks.upsdrv_tweak_prognames(); From 9811e99517025b1a7c448fe42132dbd1808cdf3e Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 14:45:41 +0100 Subject: [PATCH 22/65] clients/upslog.c: handle "-D" option before others, so it can impact help() and version() more naturally (regardless of CLI argument order) [#3378] Signed-off-by: Jim Klimov --- clients/upslog.c | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/clients/upslog.c b/clients/upslog.c index b4b6dca7b9..20a2148e64 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -525,26 +525,57 @@ int main(int argc, char **argv) logformat = DEFAULT_LOGFORMAT; user = RUN_AS_USER; + /* 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_set_debug_level(nut_debug_level); setproctag(prog); print_banner_once(prog, 0); + /* 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) { + + 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; From d05efaaa4d6486860eecedd066d9efe534d4312f Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 17:52:42 +0100 Subject: [PATCH 23/65] tools/nut-scanner/*, docs/man/*: introduce nutscan_{g,s}et_debug_level() [#3378] Signed-off-by: Jim Klimov --- docs/man/Makefile.am | 11 +++++++- docs/man/nutscan_set_debug_level.txt | 41 ++++++++++++++++++++++++++++ tools/nut-scanner/nutscan-init.c | 12 ++++++++ tools/nut-scanner/nutscan-init.h | 3 ++ tools/nut-scanner/scan_nut.c | 10 +++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 docs/man/nutscan_set_debug_level.txt diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 278aaec68e..277f7cd06f 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -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_set_debug_level.txt \ + nutscan_init.txt \ libupsclient-config.txt \ sockdebug.txt \ skel.txt @@ -781,6 +782,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_set_debug_level.$(MAN_SECTION_API) \ + $(NUTSCAN_SET_DEBUG_LEVEL_DEPS) \ nutscan_init.$(MAN_SECTION_API) # Alias page for one text describing two commands: @@ -808,6 +811,11 @@ 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_SET_DEBUG_LEVEL_DEPS = \ + nutscan_get_debug_level.$(MAN_SECTION_API) + +$(NUTSCAN_SET_DEBUG_LEVEL_DEPS): nutscan_set_debug_level.$(MAN_SECTION_API) + INST_MAN_DEV_CMD_USR_PAGES = \ libupsclient-config.$(MAN_SECTION_CMD_USR) @@ -891,6 +899,7 @@ INST_HTML_DEV_MANS = \ nutscan_add_option_to_device.html \ nutscan_add_device_to_device.html \ nutscan_get_serial_ports_list.html \ + nutscan_set_debug_level.html \ nutscan_init.html \ libupsclient-config.html \ sockdebug.html \ diff --git a/docs/man/nutscan_set_debug_level.txt b/docs/man/nutscan_set_debug_level.txt new file mode 100644 index 0000000000..4509a237cf --- /dev/null +++ b/docs/man/nutscan_set_debug_level.txt @@ -0,0 +1,41 @@ +NUTSCAN_SET_DEBUG_LEVEL(3) +========================== + +NAME +---- + +nutscan_set_debug_level, nutscan_get_debug_level - manipulate +debugging level for NUT common code in the nutscan library; +propagate to `libupsclient` if already loaded. + +SYNOPSIS +-------- + +------ + #include + + void nutscan_set_debug_level(int level); + int nutscan_get_debug_level(void); +------ + +DESCRIPTION +----------- + +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). + +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`) is also exposed, but should not +be used directly. + +SEE ALSO +-------- + +linkman:nutscan_init[3], linkman:upscli_set_debug_level[3] diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index cdbd2a611e..e8f024c589 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -73,6 +73,7 @@ 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); int nutscan_load_upower_library(const char *libname_path); int nutscan_unload_upower_library(void); @@ -149,6 +150,17 @@ void do_upsconf_args(char *confupsname, char *var, char *val) { } #endif /* WIN32 */ +void nutscan_set_debug_level(int level) { + nut_debug_level = level; + /* Succeeds after init and if the library is loaded, else no-op */ + nutscan_upscli_set_debug_level(level); +} + +int nutscan_get_debug_level(void) +{ + return nut_debug_level; +} + void nutscan_init(void) { char *libname = NULL; diff --git a/tools/nut-scanner/nutscan-init.h b/tools/nut-scanner/nutscan-init.h index b8306c50f8..ce0f21a5d7 100644 --- a/tools/nut-scanner/nutscan-init.h +++ b/tools/nut-scanner/nutscan-init.h @@ -44,6 +44,9 @@ extern int nutscan_avail_upower; void nutscan_init(void); void nutscan_free(void); +void nutscan_set_debug_level(int level); +int nutscan_get_debug_level(void); + #define DEFAULT_THREAD 512 #ifdef __cplusplus diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index f143b67a36..49125d03fa 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -85,6 +85,15 @@ 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); +void nutscan_upscli_set_debug_level(int level) +{ + if (nut_upscli_set_debug_level) { + (*nut_upscli_set_debug_level)(level); + } +} + /* Return 0 on error; visible externally */ int nutscan_load_upsclient_library(const char *libname_path); int nutscan_load_upsclient_library(const char *libname_path) @@ -166,6 +175,7 @@ int nutscan_load_upsclient_library(const char *libname_path) upsdebugx(1, "%s: %s() not found, using older libupsclient build?", __func__, symbol); } else { + /* Propagate value currently known in libnutscan into libupsclient */ (*nut_upscli_set_debug_level)(nut_debug_level); } From a2dca605e75eec60911faf66f372aab91f08160f Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 18:10:43 +0100 Subject: [PATCH 24/65] tools/nut-scanner/nut-scanner.c: stop using nut_debug_level directly: call nutscan_set_debug_level() now [#3378] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 29c80e17dc..50010ee899 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -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; @@ -1254,13 +1255,30 @@ 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_set_debug_level(_nut_debug_level); + } else { + char *s = getenv("NUT_DEBUG_LEVEL"); + int l; + if (s && str_to_int(s, &l, 10) && l > 0) { + _nut_debug_level = l; + nutscan_set_debug_level(_nut_debug_level); + upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " + "since none was requested by command-line options", l); + } /* else follow -D settings */ + } + + /* A non-trivial _nut_debug_level set above allows + * debug-tracing to troubleshoot these init methods: */ setproctag("init-ip-ranges"); nutscan_init_ip_ranges(&ip_ranges_list); setproctag("init-libnutscan"); nutscan_init(); + /* Re-set to cover libupsclient, if loaded: */ + nutscan_set_debug_level(_nut_debug_level); setproctag(progname); /* Default, see -Q/-N/-P below */ From fed66e0fb2483936160d6cd2d5617a5151b984a5 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 18:13:48 +0100 Subject: [PATCH 25/65] tools/nutconf/nutconf-cli.cpp, docs: support "-D" and `NUT_DEBUG_LEVEL` with `nutconf`; fix processing of short (single-char) optargs even if stacked after one dash [#3378] Signed-off-by: Jim Klimov --- NEWS.adoc | 4 ++- docs/man/nutconf.txt | 4 +++ tools/nutconf/nutconf-cli.cpp | 49 +++++++++++++++++++++++++++-------- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index bae8359783..4d08e45a71 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -138,7 +138,9 @@ but the `nutshutdown` script would bail out quickly and quietly. [PR #3008] 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. [#3378] + `upscmd` and `upsrw` command-line clients, and the `nutconf` tool. + Revised handling of `-D` and/or `NUT_DEBUG_LEVEL` environment variable + in other programs. [#3378] - NUT for Windows specific updates: * Revised detection of (relative) paths to program and configuration files 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/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index 5559b29315..9685b5cc0f 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -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", @@ -1333,17 +1334,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); + } } } @@ -3129,9 +3138,27 @@ static int mainx(int argc, char * const argv[]) { const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "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: + */ + nutscan_set_debug_level(nut_debug_level); + setproctag(prog); + upsdebugx(1, "Starting NUT configuration tool: %s", prog); + // Usage if (options.exists("help") || options.existsSingle("h")) { Usage::print(prog, ""); From 60fedc7bdc04f17be0f94fd9c5c6e195e668cf16 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 18:54:30 +0100 Subject: [PATCH 26/65] clients/upsclient.{c,h}, docs: expose upscli_{g,s}etproctag() [#3379] Signed-off-by: Jim Klimov --- clients/upsclient.c | 10 ++++++++++ clients/upsclient.h | 4 ++++ common/common.c | 2 +- docs/man/Makefile.am | 4 +++- docs/man/upscli_set_debug_level.txt | 9 +++++++-- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index 6af58cb62e..be2d9d7eff 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -2281,3 +2281,13 @@ int upscli_get_debug_level(void) { return nut_debug_level; } + +void upscli_setproctag(const char *tag) +{ + setproctag(tag); +} + +const char *upscli_getproctag(void) +{ + return getproctag(); +} diff --git a/clients/upsclient.h b/clients/upsclient.h index ea80550bc6..9b31f06b69 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -109,6 +109,10 @@ const char *upscli_strerror(UPSCONN_t *ups); void upscli_set_debug_level(int lvl); int upscli_get_debug_level(void); +/* Similarly for sub-process tags that help with troubleshooting */ +void upscli_setproctag(const char *tag); +const char *upscli_getproctag(void); + /* NOTE: effectively only runs once; re-runs quickly skip out */ int upscli_init(int certverify, const char *certpath, const char *certname, const char *certpasswd); int upscli_cleanup(void); diff --git a/common/common.c b/common/common.c index 10871bb0a2..06694e67e0 100644 --- a/common/common.c +++ b/common/common.c @@ -4455,7 +4455,7 @@ void setproctag(const char *tag) 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) */ + * when callers initialize with setproctag(progname) */ char *s = NULL; proctag_for_upsdebug_buflen += strlen(pn) + 1; s = (char *)xcalloc(proctag_for_upsdebug_buflen, sizeof(char)); diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 277f7cd06f..3f0a87ed1a 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -707,7 +707,9 @@ 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_get_debug_level.$(MAN_SECTION_API) \ + upscli_getproctag.$(MAN_SECTION_API) \ + upscli_setproctag.$(MAN_SECTION_API) $(UPSCLI_SET_DEBUG_LEVEL_DEPS): upscli_set_debug_level.$(MAN_SECTION_API) diff --git a/docs/man/upscli_set_debug_level.txt b/docs/man/upscli_set_debug_level.txt index f7e057283e..8b8e88425f 100644 --- a/docs/man/upscli_set_debug_level.txt +++ b/docs/man/upscli_set_debug_level.txt @@ -4,8 +4,10 @@ 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 +upscli_set_debug_level, upscli_get_debug_level, +upscli_setproctag, upscli_getproctag - manipulate +the possibly separate copies of the `nut_debug_level` +and sub-process tag variables in the `libupsclient` build SYNOPSIS -------- @@ -15,6 +17,9 @@ SYNOPSIS void upscli_set_debug_level(int); int upscli_get_debug_level(void); + + void upscli_setproctag(const char *tag); + const char *upscli_getproctag(void); ------ DESCRIPTION From f3ef7902b348bce9087889153ac47b1d60b33cd0 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 18:59:11 +0100 Subject: [PATCH 27/65] tools/nut-scanner/*, docs: expose nutscan_{g,s}etproctag() [#3379] Signed-off-by: Jim Klimov --- docs/man/Makefile.am | 4 +++- docs/man/nutscan_set_debug_level.txt | 14 ++++++++++---- tools/nut-scanner/nutscan-init.c | 13 +++++++++++++ tools/nut-scanner/nutscan-init.h | 3 +++ tools/nut-scanner/scan_nut.c | 20 ++++++++++++++++++++ 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 3f0a87ed1a..7dffe1de81 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -814,7 +814,9 @@ nutscan_add_commented_option_to_device.$(MAN_SECTION_API): nutscan_add_option_to touch $@ NUTSCAN_SET_DEBUG_LEVEL_DEPS = \ - nutscan_get_debug_level.$(MAN_SECTION_API) + nutscan_get_debug_level.$(MAN_SECTION_API) \ + nutscan_getproctag.$(MAN_SECTION_API) \ + nutscan_setproctag.$(MAN_SECTION_API) $(NUTSCAN_SET_DEBUG_LEVEL_DEPS): nutscan_set_debug_level.$(MAN_SECTION_API) diff --git a/docs/man/nutscan_set_debug_level.txt b/docs/man/nutscan_set_debug_level.txt index 4509a237cf..fab2908678 100644 --- a/docs/man/nutscan_set_debug_level.txt +++ b/docs/man/nutscan_set_debug_level.txt @@ -4,8 +4,10 @@ NUTSCAN_SET_DEBUG_LEVEL(3) NAME ---- -nutscan_set_debug_level, nutscan_get_debug_level - manipulate -debugging level for NUT common code in the nutscan library; +nutscan_set_debug_level, nutscan_get_debug_level, +nutscan_setproctag, nutscan_getproctag - manipulate +debugging level and sub-process tags for NUT common +code in the nutscan library; propagate to `libupsclient` if already loaded. SYNOPSIS @@ -16,6 +18,9 @@ SYNOPSIS void nutscan_set_debug_level(int level); int nutscan_get_debug_level(void); + + void nutscan_setproctag(const char *tag); + const char *nutscan_getproctag(void); ------ DESCRIPTION @@ -32,8 +37,9 @@ 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`) is also exposed, but should not -be used directly. +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 -------- diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index e8f024c589..3efb98216b 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -74,6 +74,7 @@ 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); +void nutscan_upscli_setproctag(const char *tag); int nutscan_load_upower_library(const char *libname_path); int nutscan_unload_upower_library(void); @@ -161,6 +162,18 @@ int nutscan_get_debug_level(void) return nut_debug_level; } +void nutscan_setproctag(const char *tag) +{ + setproctag(tag); + /* Succeeds after init and if the library is loaded, else no-op */ + nutscan_upscli_setproctag(tag); +} + +const char *nutscan_getproctag(void) +{ + return getproctag(); +} + void nutscan_init(void) { char *libname = NULL; diff --git a/tools/nut-scanner/nutscan-init.h b/tools/nut-scanner/nutscan-init.h index ce0f21a5d7..e9756f99ab 100644 --- a/tools/nut-scanner/nutscan-init.h +++ b/tools/nut-scanner/nutscan-init.h @@ -47,6 +47,9 @@ void nutscan_free(void); void nutscan_set_debug_level(int level); int nutscan_get_debug_level(void); +void nutscan_setproctag(const char *tag); +const char *nutscan_getproctag(void); + #define DEFAULT_THREAD 512 #ifdef __cplusplus diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index 49125d03fa..c7e042200c 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -53,6 +53,7 @@ static int (*nut_upscli_list_next)(UPSCONN_t *ups, size_t numq, static int (*nut_upscli_disconnect)(UPSCONN_t *ups); static int (*nut_upscli_ssl_caps)(void); static int (*nut_upscli_set_debug_level)(int); +static void (*nut_upscli_setproctag)(const char *tag); static void (*nut_upscli_report_build_details)(void); /* This variable collects device(s) from a sequential or parallel scan, @@ -94,6 +95,14 @@ void nutscan_upscli_set_debug_level(int level) } } +void nutscan_upscli_setproctag(const char *tag); +void nutscan_upscli_setproctag(const char *tag) +{ + if (nut_upscli_setproctag) { + (*nut_upscli_setproctag)(tag); + } +} + /* Return 0 on error; visible externally */ int nutscan_load_upsclient_library(const char *libname_path); int nutscan_load_upsclient_library(const char *libname_path) @@ -168,6 +177,17 @@ int nutscan_load_upsclient_library(const char *libname_path) __func__, symbol); } + *(void **) (&nut_upscli_setproctag) = lt_dlsym(dl_handle, + symbol = "upscli_setproctag"); + if ((dl_error = lt_dlerror()) != NULL) { + nut_upscli_setproctag = NULL; + upsdebugx(1, "%s: %s() not found, using older libupsclient build?", + __func__, symbol); + } else { + /* Propagate value currently known in libnutscan into libupsclient */ + (*nut_upscli_setproctag)(getproctag()); + } + *(void **) (&nut_upscli_set_debug_level) = lt_dlsym(dl_handle, symbol = "upscli_set_debug_level"); if ((dl_error = lt_dlerror()) != NULL) { From 6e18069eccf76e18abb3813ce5a95cb988532da6 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 19:15:55 +0100 Subject: [PATCH 28/65] tools/nut-scanner/nut-scanner.c: convert to using nutscan_setproctag() instead of directly setproctag() [#3378, #3379] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 48 +++++++++++++++++---------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 50010ee899..9a3bc9207e 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -1231,7 +1231,7 @@ int main(int argc, char *argv[]) } #endif /* HAVE_PTHREAD && ( HAVE_PTHREAD_TRYJOIN || HAVE_SEMAPHORE_UNNAMED || HAVE_SEMAPHORE_NAMED ) && HAVE_SYS_RESOURCE_H */ - setproctag(progname); + nutscan_setproctag(progname); memset(&snmp_sec, 0, sizeof(snmp_sec)); memset(&ipmi_sec, 0, sizeof(ipmi_sec)); @@ -1262,24 +1262,26 @@ int main(int argc, char *argv[]) nutscan_set_debug_level(_nut_debug_level); } else { char *s = getenv("NUT_DEBUG_LEVEL"); - int l; - if (s && str_to_int(s, &l, 10) && l > 0) { - _nut_debug_level = l; - nutscan_set_debug_level(_nut_debug_level); - upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " - "since none was requested by command-line options", l); + if (s) { + int l = atol(s); + if (l > 0) { + _nut_debug_level = l; + nutscan_set_debug_level(_nut_debug_level); + upsdebugx(1, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " + "since none was requested by command-line options", l); + } } /* else follow -D settings */ } /* A non-trivial _nut_debug_level set above allows * debug-tracing to troubleshoot these init methods: */ - setproctag("init-ip-ranges"); + nutscan_setproctag("init-ip-ranges"); nutscan_init_ip_ranges(&ip_ranges_list); - setproctag("init-libnutscan"); + nutscan_setproctag("init-libnutscan"); nutscan_init(); /* Re-set to cover libupsclient, if loaded: */ nutscan_set_debug_level(_nut_debug_level); - setproctag(progname); + nutscan_setproctag(progname); /* Default, see -Q/-N/-P below */ display_func = nutscan_display_ups_conf_with_sanity_check; @@ -1749,7 +1751,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_setproctag("scanning"); if (allow_usb && nutscan_avail_usb) { upsdebugx(quiet, "Scanning USB bus."); @@ -1759,7 +1761,7 @@ int main(int argc, char *argv[]) nutscan_avail_usb = 0; } #else - setproctag("usb"); + nutscan_setproctag("usb"); upsdebugx(1, "USB SCAN: no pthread support, starting nutscan_scan_usb..."); dev[TYPE_USB] = run_usb(&cli_link_detail_level); #endif /* HAVE_PTHREAD */ @@ -1781,7 +1783,7 @@ int main(int argc, char *argv[]) nutscan_avail_snmp = 0; } #else - setproctag("snmp"); + nutscan_setproctag("snmp"); 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); @@ -1806,7 +1808,7 @@ int main(int argc, char *argv[]) nutscan_avail_xml_http = 0; } #else - setproctag("netxml"); + nutscan_setproctag("netxml"); 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); @@ -1829,7 +1831,7 @@ int main(int argc, char *argv[]) nutscan_avail_nut = 0; } #else - setproctag("oldnut"); + nutscan_setproctag("oldnut"); 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); @@ -1848,7 +1850,7 @@ int main(int argc, char *argv[]) nutscan_avail_nut_simulation = 0; } #else - setproctag("nut_simulation"); + nutscan_setproctag("nut_simulation"); 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); @@ -1866,7 +1868,7 @@ int main(int argc, char *argv[]) nutscan_avail_avahi = 0; } #else - setproctag("avahi"); + nutscan_setproctag("avahi"); upsdebugx(1, "NUT bus (avahi) SCAN: no pthread support, starting nutscan_scan_avahi..."); /* dev[TYPE_AVAHI] = nutscan_scan_avahi(timeout); */ run_avahi(NULL); @@ -1889,7 +1891,7 @@ int main(int argc, char *argv[]) nutscan_avail_ipmi = 0; } #else - setproctag("ipmi"); + nutscan_setproctag("ipmi"); 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); @@ -1907,7 +1909,7 @@ int main(int argc, char *argv[]) nutscan_avail_upower = 0; } #else - setproctag("upower"); + nutscan_setproctag("upower"); upsdebugx(1, "UPOWER SCAN: no pthread support, starting nutscan_scan_upower..."); run_upower(NULL); #endif /* HAVE_PTHREAD */ @@ -1925,7 +1927,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_setproctag("serial"); 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); @@ -1973,7 +1975,7 @@ int main(int argc, char *argv[]) } #endif /* HAVE_PTHREAD */ - setproctag("post-processing"); + nutscan_setproctag("post-processing"); upsdebugx(1, "SCANS DONE: display results"); @@ -2022,7 +2024,7 @@ int main(int argc, char *argv[]) upsdebugx(1, "SCANS DONE: free resources: SERIAL"); nutscan_free_device(dev[TYPE_EATON_SERIAL]); - setproctag("cleanup"); + nutscan_setproctag("cleanup"); #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE_UNNAMED sem_destroy(nutscan_semaphore()); @@ -2040,7 +2042,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_setproctag(progname); upsdebugx(1, "SCANS DONE: EXIT_SUCCESS"); From 9a19e3c301685a1bbc2b37085971c41c5923b306 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 19:29:24 +0100 Subject: [PATCH 29/65] tools/nutconf/nutconf-cli.cpp: convert to using nutscan_setproctag() instead of directly setproctag() [#3378, #3379] Signed-off-by: Jim Klimov --- tools/nutconf/nutconf-cli.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index 9685b5cc0f..6c11d39199 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -3156,7 +3156,7 @@ static int mainx(int argc, char * const argv[]) { * initial timestamp, for the eventuality that debugs would be printed: */ nutscan_set_debug_level(nut_debug_level); - setproctag(prog); + nutscan_setproctag(prog); upsdebugx(1, "Starting NUT configuration tool: %s", prog); // Usage From 17ed85501cbaf2b55cb4fe5e8e5fb4d865662fca Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 19:54:01 +0100 Subject: [PATCH 30/65] common/common.c, include/common.h: refactor with upslog_start_sync() [#3379] Signed-off-by: Jim Klimov --- common/common.c | 51 ++++++++++++++++++++++++++++++++++-------------- include/common.h | 13 ++++++++++++ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/common/common.c b/common/common.c index 06694e67e0..28451f8fff 100644 --- a/common/common.c +++ b/common/common.c @@ -548,6 +548,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; @@ -4097,21 +4130,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) { diff --git a/include/common.h b/include/common.h index f6333e0b7a..31df398295 100644 --- a/include/common.h +++ b/include/common.h @@ -564,6 +564,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, ...) From 32950540deda82a2842c50b538b7b83bc68ee5a0 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 23:00:40 +0100 Subject: [PATCH 31/65] clients/upsclient.{c,h}, docs: expose upscli_upslog_start_sync() [#3378] Signed-off-by: Jim Klimov --- clients/upsclient.c | 5 +++++ clients/upsclient.h | 13 +++++++++++++ docs/man/Makefile.am | 1 + docs/man/upscli_set_debug_level.txt | 16 +++++++++++++++- tools/nut-scanner/nutscan-init.c | 8 ++++++++ 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index be2d9d7eff..8d8192d5a6 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -2291,3 +2291,8 @@ const char *upscli_getproctag(void) { return getproctag(); } + +struct timeval *upscli_upslog_start_sync(struct timeval *tv) +{ + return upslog_start_sync(tv); +} diff --git a/clients/upsclient.h b/clients/upsclient.h index 9b31f06b69..d28d41fb4d 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -113,6 +113,19 @@ int upscli_get_debug_level(void); void upscli_setproctag(const char *tag); const char *upscli_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); + /* NOTE: effectively only runs once; re-runs quickly skip out */ int upscli_init(int certverify, const char *certpath, const char *certname, const char *certpasswd); int upscli_cleanup(void); diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 7dffe1de81..a3e19cc73e 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -708,6 +708,7 @@ $(UPSCLI_SSL_CAPS_DEPS): upscli_ssl_caps.$(MAN_SECTION_API) UPSCLI_SET_DEBUG_LEVEL_DEPS = \ upscli_get_debug_level.$(MAN_SECTION_API) \ + upscli_upslog_start_sync.$(MAN_SECTION_API) \ upscli_getproctag.$(MAN_SECTION_API) \ upscli_setproctag.$(MAN_SECTION_API) diff --git a/docs/man/upscli_set_debug_level.txt b/docs/man/upscli_set_debug_level.txt index 8b8e88425f..c8b395472c 100644 --- a/docs/man/upscli_set_debug_level.txt +++ b/docs/man/upscli_set_debug_level.txt @@ -5,7 +5,8 @@ NAME ---- upscli_set_debug_level, upscli_get_debug_level, -upscli_setproctag, upscli_getproctag - manipulate +upscli_setproctag, upscli_getproctag, +upscli_upslog_start_sync - manipulate the possibly separate copies of the `nut_debug_level` and sub-process tag variables in the `libupsclient` build @@ -20,6 +21,8 @@ SYNOPSIS void upscli_setproctag(const char *tag); const char *upscli_getproctag(void); + + struct timeval *upscli_upslog_start_sync(struct timeval *tv); ------ DESCRIPTION @@ -43,6 +46,17 @@ 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 ------------ diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index 3efb98216b..539d822887 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -174,6 +174,14 @@ const char *nutscan_getproctag(void) return getproctag(); } +struct timeval *upscli_upslog_start_sync(struct timeval *tv) +{ + struct timeval *nstv = upslog_start_sync(tv); + /* Succeeds after init and if the library is loaded, else no-op */ + nutscan_upscli_upslog_start_sync(nstv); + return nstv; +} + void nutscan_init(void) { char *libname = NULL; From 578a3702a982227769f4f5d75103ed76c2c0e0bb Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 23:02:38 +0100 Subject: [PATCH 32/65] tools/nut-scanner/*, docs: expose nutscan_upslog_start_sync() [#3378] Signed-off-by: Jim Klimov --- docs/man/Makefile.am | 1 + docs/man/nutscan_set_debug_level.txt | 20 +++++++++++++++++++- tools/nut-scanner/nutscan-init.c | 3 ++- tools/nut-scanner/nutscan-init.h | 2 ++ tools/nut-scanner/scan_nut.c | 23 +++++++++++++++++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index a3e19cc73e..b123eda72f 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -816,6 +816,7 @@ nutscan_add_commented_option_to_device.$(MAN_SECTION_API): nutscan_add_option_to NUTSCAN_SET_DEBUG_LEVEL_DEPS = \ nutscan_get_debug_level.$(MAN_SECTION_API) \ + nutscan_upslog_start_sync.$(MAN_SECTION_API) \ nutscan_getproctag.$(MAN_SECTION_API) \ nutscan_setproctag.$(MAN_SECTION_API) diff --git a/docs/man/nutscan_set_debug_level.txt b/docs/man/nutscan_set_debug_level.txt index fab2908678..7af0c9c34a 100644 --- a/docs/man/nutscan_set_debug_level.txt +++ b/docs/man/nutscan_set_debug_level.txt @@ -5,7 +5,8 @@ NAME ---- nutscan_set_debug_level, nutscan_get_debug_level, -nutscan_setproctag, nutscan_getproctag - manipulate +nutscan_setproctag, nutscan_getproctag, +nutscan_upslog_start_sync - manipulate debugging level and sub-process tags for NUT common code in the nutscan library; propagate to `libupsclient` if already loaded. @@ -21,16 +22,33 @@ SYNOPSIS void nutscan_setproctag(const char *tag); const char *nutscan_getproctag(void); + + struct timeval *nutscan_upslog_start_sync(struct timeval *tv); ------ 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. + 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 ----- diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index 539d822887..8cad4a0ae5 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -75,6 +75,7 @@ int nutscan_load_upsclient_library(const char *libname_path); int nutscan_unload_upsclient_library(void); void nutscan_upscli_set_debug_level(int level); void nutscan_upscli_setproctag(const char *tag); +struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv); int nutscan_load_upower_library(const char *libname_path); int nutscan_unload_upower_library(void); @@ -174,7 +175,7 @@ const char *nutscan_getproctag(void) return getproctag(); } -struct timeval *upscli_upslog_start_sync(struct timeval *tv) +struct timeval *nutscan_upslog_start_sync(struct timeval *tv) { struct timeval *nstv = upslog_start_sync(tv); /* Succeeds after init and if the library is loaded, else no-op */ diff --git a/tools/nut-scanner/nutscan-init.h b/tools/nut-scanner/nutscan-init.h index e9756f99ab..197b49b4a6 100644 --- a/tools/nut-scanner/nutscan-init.h +++ b/tools/nut-scanner/nutscan-init.h @@ -50,6 +50,8 @@ int nutscan_get_debug_level(void); void nutscan_setproctag(const char *tag); const char *nutscan_getproctag(void); +struct timeval *nutscan_upslog_start_sync(struct timeval *tv); + #define DEFAULT_THREAD 512 #ifdef __cplusplus diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index c7e042200c..6566d2ae38 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -54,6 +54,7 @@ static int (*nut_upscli_disconnect)(UPSCONN_t *ups); static int (*nut_upscli_ssl_caps)(void); static int (*nut_upscli_set_debug_level)(int); static void (*nut_upscli_setproctag)(const char *tag); +static struct timeval *(*nut_upscli_upslog_start_sync)(struct timeval *tv); static void (*nut_upscli_report_build_details)(void); /* This variable collects device(s) from a sequential or parallel scan, @@ -103,6 +104,17 @@ void nutscan_upscli_setproctag(const char *tag) } } +struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv); +struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv) +{ + if (nut_upscli_upslog_start_sync) { + return (*nut_upscli_upslog_start_sync)(tv); + } + + /* 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) @@ -177,6 +189,17 @@ int nutscan_load_upsclient_library(const char *libname_path) __func__, symbol); } + *(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 */ + (*nut_upscli_upslog_start_sync)(upslog_start_sync(NULL)); + } + *(void **) (&nut_upscli_setproctag) = lt_dlsym(dl_handle, symbol = "upscli_setproctag"); if ((dl_error = lt_dlerror()) != NULL) { From 4717ca53b047be28d564e563b76c7cdb83658bf6 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 27 Mar 2026 23:29:43 +0100 Subject: [PATCH 33/65] tools/nutconf/nutconf-cli.cpp: init common timestamps with *upslog_start_sync() [#3378] Signed-off-by: Jim Klimov --- tools/nutconf/nutconf-cli.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index 6c11d39199..6bf8e0dc7f 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -3135,9 +3135,16 @@ static void scanSerialDevices(const NutConfOptions & options) { * \return 0 always (exits on error) */ static int mainx(int argc, char * const argv[]) { + /* 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 = upslog_start_sync(nutscan_upslog_start_sync(NULL)); const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "nutconf"); char *s = nullptr; + NUT_UNUSED_VARIABLE(upslog_start_tmp); + // Get options, also set nut_debug_level NutConfOptions options(argv, argc); From 37ac7564b47c635b567cdd0e73639e6628e55965 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Mar 2026 03:17:08 +0100 Subject: [PATCH 34/65] Introduce private setproctag_lib() for libupsclient and libnutscan [#3379] Signed-off-by: Jim Klimov --- clients/upsclient.c | 7 +++ common/common.c | 98 +++++++++++++++++++++++--------- tools/nut-scanner/nutscan-init.c | 8 ++- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index 8d8192d5a6..bbeb5a070d 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -2272,9 +2272,14 @@ 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. */ + +/* privately exported from common.c for internal libs */ +const char *setproctag_lib_once(const char *val); + void upscli_set_debug_level(int lvl) { nut_debug_level = lvl; + setproctag_lib_once("libupsclient"); } int upscli_get_debug_level(void) @@ -2284,6 +2289,7 @@ int upscli_get_debug_level(void) void upscli_setproctag(const char *tag) { + setproctag_lib_once("libupsclient"); setproctag(tag); } @@ -2294,5 +2300,6 @@ const char *upscli_getproctag(void) struct timeval *upscli_upslog_start_sync(struct timeval *tv) { + setproctag_lib_once("libupsclient"); return upslog_start_sync(tv); } diff --git a/common/common.c b/common/common.c index 28451f8fff..5c9bcd6e83 100644 --- a/common/common.c +++ b/common/common.c @@ -4403,7 +4403,7 @@ void upslogx(int priority, const char *fmt, ...) /* also keep a buffer with prefixed colon for debug printouts */ static char *proctag = NULL, *proctag_for_upsdebug = NULL, - proctag_cleanup_registered = 0; + *proctag_lib = NULL, proctag_cleanup_registered = 0; static void proctag_cleanup(void) { @@ -4428,9 +4428,33 @@ static void proctag_cleanup(void) free(tn); } + /* Free the global vars */ + if (proctag_lib) { + free(proctag_lib); + proctag_lib = NULL; + } + setproctag(NULL); } +/* 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) { + atexit(proctag_cleanup); + proctag_cleanup_registered = 1; + } + } + + return (const char *)proctag_lib; +} + const char *getproctag(void) { return (const char *)proctag; @@ -4439,6 +4463,20 @@ const char *getproctag(void) void setproctag(const char *tag) { size_t proctag_for_upsdebug_buflen = 0; + + if (proctag_cleanup_registered < 2) { + /* 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. + */ + getmyprocname(); + + if (proctag_cleanup_registered < 1) + atexit(proctag_cleanup); + proctag_cleanup_registered = 2; + } + if (proctag) { free(proctag); proctag = NULL; @@ -4450,53 +4488,57 @@ 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); 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 = xbasename_no_ext(getmyprocbasename()); char *tn = xbasename_no_ext(tag); int tagged = 0; - 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 setproctag(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); } if (pn) free(pn); if (tn) free(tn); - } + } /* else alloc error, we'll print no proctag + * (but maybe libname, see vupslog) */ } void s_upsdebug_with_errno(int level, const char *fmt, ...) @@ -4529,12 +4571,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))) { @@ -4593,12 +4635,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); } diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index 8cad4a0ae5..3d0fd9819c 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 @@ -79,6 +79,9 @@ struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv); 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 */ @@ -154,6 +157,7 @@ void do_upsconf_args(char *confupsname, char *var, char *val) { void nutscan_set_debug_level(int level) { nut_debug_level = level; + setproctag_lib_once("libnutscan"); /* Succeeds after init and if the library is loaded, else no-op */ nutscan_upscli_set_debug_level(level); } @@ -165,6 +169,7 @@ int nutscan_get_debug_level(void) void nutscan_setproctag(const char *tag) { + setproctag_lib_once("libnutscan"); setproctag(tag); /* Succeeds after init and if the library is loaded, else no-op */ nutscan_upscli_setproctag(tag); @@ -178,6 +183,7 @@ const char *nutscan_getproctag(void) struct timeval *nutscan_upslog_start_sync(struct timeval *tv) { struct timeval *nstv = upslog_start_sync(tv); + setproctag_lib_once("libnutscan"); /* Succeeds after init and if the library is loaded, else no-op */ nutscan_upscli_upslog_start_sync(nstv); return nstv; From ef8582e0458184eaa90a2f7f555ca4d87441ecbc Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Mar 2026 03:53:07 +0100 Subject: [PATCH 35/65] common/common.c: debug first run of setproctag() and runs of proctag_cleanup() [#3379] Signed-off-by: Jim Klimov --- common/common.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/common.c b/common/common.c index 5c9bcd6e83..df80479765 100644 --- a/common/common.c +++ b/common/common.c @@ -4407,6 +4407,8 @@ static char *proctag = NULL, *proctag_for_upsdebug = NULL, static void proctag_cleanup(void) { + upsdebugx(3, "%s: starting...", __func__); + if (proctag) { char *pn = xbasename_no_ext(getmyprocbasename()); char *tn = xbasename_no_ext(proctag); @@ -4470,6 +4472,7 @@ void setproctag(const char *tag) * 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) From abf3fb823edd61c6a51b146d37cc52c3d3cacac6 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Mar 2026 03:54:35 +0100 Subject: [PATCH 36/65] tools/nut-scanner/scan_nut.c: nutscan_upscli*(): avoid calls after libupsclient was unloaded [#3379] Signed-off-by: Jim Klimov --- tools/nut-scanner/scan_nut.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index 6566d2ae38..b69e0b7b59 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -91,7 +91,7 @@ int nutscan_unload_upsclient_library(void) void nutscan_upscli_set_debug_level(int level); void nutscan_upscli_set_debug_level(int level) { - if (nut_upscli_set_debug_level) { + if (nutscan_avail_nut && dl_handle && nut_upscli_set_debug_level) { (*nut_upscli_set_debug_level)(level); } } @@ -99,7 +99,7 @@ void nutscan_upscli_set_debug_level(int level) void nutscan_upscli_setproctag(const char *tag); void nutscan_upscli_setproctag(const char *tag) { - if (nut_upscli_setproctag) { + if (nutscan_avail_nut && dl_handle && nut_upscli_setproctag) { (*nut_upscli_setproctag)(tag); } } @@ -107,7 +107,7 @@ void nutscan_upscli_setproctag(const char *tag) struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv); struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv) { - if (nut_upscli_upslog_start_sync) { + if (nutscan_avail_nut && dl_handle && nut_upscli_upslog_start_sync) { return (*nut_upscli_upslog_start_sync)(tv); } From 32209a1ed597420dbc3097a429a8a5a3e1d5d375 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Mar 2026 15:24:06 +0100 Subject: [PATCH 37/65] common/common.c: xbasename_no_ext(): keep or even add ext for "nut.exe" in WIN32 builds [#3378] Signed-off-by: Jim Klimov --- common/common.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/common/common.c b/common/common.c index df80479765..30464e8dd5 100644 --- a/common/common.c +++ b/common/common.c @@ -2671,6 +2671,20 @@ char *xbasename_no_ext(const char *file) if (!cs || !*cs) return NULL; + /* TOTHINK: Generalize provided-if-missing strcasestr()? + * One implementation is currently tucked away in + * libusb0.c because net-snmp may provide another... + */ + +#ifdef WIN32 + /* Special handling for a known outlier (for man pages, etc.) */ + if (!strcmp(cs, "nut.exe")) + return xstrdup(cs); + + if (!strcmp(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 @@ -2700,10 +2714,6 @@ char *xbasename_no_ext(const char *file) 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, From d3ea1ad540a783b6d3f42a9a30de25e7b2e3ea17 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Mar 2026 15:27:57 +0100 Subject: [PATCH 38/65] common/common.c: vupslog(): only warn about buffer realloc at high verbosity or big previous bufsize; prefix with [D0] [#3379, #317] Signed-off-by: Jim Klimov --- common/common.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/common.c b/common/common.c index 30464e8dd5..0d4708fb1f 100644 --- a/common/common.c +++ b/common/common.c @@ -4064,8 +4064,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", From 2c350ac750725119b3df07b6e0e472a45455c3ce Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 28 Mar 2026 16:06:59 +0100 Subject: [PATCH 39/65] common/common.c: ensure that proctag_cleanup() does its work before procname_cleanup() [#3379] Signed-off-by: Jim Klimov --- common/common.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/common/common.c b/common/common.c index 0d4708fb1f..2e4b91d1a8 100644 --- a/common/common.c +++ b/common/common.c @@ -934,7 +934,17 @@ void chroot_start(const char *path) /* In forking, assume process name does not change (PID might); cache it */ static char *myProcName = NULL; 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, proctag_cleanup_registered = 0; +static void proctag_cleanup(void); + static void procname_cleanup(void) { + if (proctag_cleanup_registered) + proctag_cleanup(); /* calls getmyprocname() */ + if (myProcBaseName) { /* points to inside of myProcName */ myProcBaseName = NULL; @@ -4413,10 +4423,6 @@ 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_lib = NULL, proctag_cleanup_registered = 0; - static void proctag_cleanup(void) { upsdebugx(3, "%s: starting...", __func__); From 19ee993c09e12960eee36d5e7201a2b25aba2c58 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 13:20:16 +0200 Subject: [PATCH 40/65] tools/nut-scanner/nutscan-init.c: debug-log starting/ending of nutscan_init()/nutscan_free() [#3379] Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-init.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index 3d0fd9819c..ee6fea49e7 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -204,6 +204,8 @@ void nutscan_init(void) } #endif /* WIN32 */ + upsdebugx(1, "%s: starting...", __func__); + /* Optional filter to not walk things twice */ nut_prepare_search_paths(); @@ -734,6 +736,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"; @@ -790,6 +794,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(); @@ -815,4 +821,5 @@ void nutscan_free(void) # endif #endif + upsdebugx(1, "%s: done", __func__); } From 17630012321ac0bf844a0c8e16921ddf219ce955 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 16:06:31 +0200 Subject: [PATCH 41/65] common/common.c: vupslog(): handle reported invalid inputs for vsnprintf() gracefully [#3379, #317] Not each failed situation is a cause for (unlimited) memory extension. Signed-off-by: Jim Klimov --- common/common.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/common/common.c b/common/common.c index 2e4b91d1a8..15d60831f3 100644 --- a/common/common.c +++ b/common/common.c @@ -4034,6 +4034,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); @@ -4045,6 +4047,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. From 2b889a6fb0c33881b5f9f715815c8704b6195e67 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 16:12:14 +0200 Subject: [PATCH 42/65] common/common.c: track procname_cleanup_registered; report what context a proctag_cleanup() and/or procname_cleanup() ran for; do not run cleaners twice [#3379] Signed-off-by: Jim Klimov --- common/common.c | 59 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/common/common.c b/common/common.c index 15d60831f3..c6c0b54faa 100644 --- a/common/common.c +++ b/common/common.c @@ -932,7 +932,7 @@ void chroot_start(const char *path) } /* In forking, assume process name does not change (PID might); cache it */ -static char *myProcName = NULL; +static char *myProcName = NULL, procname_cleanup_registered = 0; static const char *myProcBaseName = NULL; /* We also keep a buffer with prefixed colon for debug printouts. @@ -942,8 +942,23 @@ static char *proctag = NULL, *proctag_for_upsdebug = NULL, static void proctag_cleanup(void); static void procname_cleanup(void) { - if (proctag_cleanup_registered) + 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: {%s}: starting for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + __func__, NUT_STRARG(myPTU), 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 */ @@ -953,6 +968,17 @@ static void procname_cleanup(void) { free(myProcName); myProcName = NULL; } + + procname_cleanup_registered = -1; + + upsdebugx(3, "%s: {%s}: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + __func__, NUT_STRARG(myPTU), 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) @@ -963,7 +989,9 @@ static const char * getmyprocname(void) 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) + atexit(procname_cleanup); + procname_cleanup_registered = 1; } return (const char *)myProcName; @@ -4443,7 +4471,19 @@ void upslogx(int priority, const char *fmt, ...) static void proctag_cleanup(void) { - upsdebugx(3, "%s: starting...", __func__); + char *myBN, *myPN, *myPT, *myLT, *myPTU; + + if (proctag_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: {%s}: starting for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + __func__, NUT_STRARG(myPTU), NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); if (proctag) { char *pn = xbasename_no_ext(getmyprocbasename()); @@ -4473,6 +4513,17 @@ static void proctag_cleanup(void) } setproctag(NULL); + + proctag_cleanup_registered = -1; + + upsdebugx(3, "%s: {%s}: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + __func__, NUT_STRARG(myPTU), 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 From 67856269df61655175079b942f0cb0020b396e5f Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 17:46:34 +0200 Subject: [PATCH 43/65] common/common.c: debug-log registrations of atexit() handlers [#3384] Signed-off-by: Jim Klimov --- common/common.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/common/common.c b/common/common.c index c6c0b54faa..8494dcd284 100644 --- a/common/common.c +++ b/common/common.c @@ -138,6 +138,7 @@ static int open_sdbus_once(const char *caller) { if (systemd_bus && !openedOnce) { openedOnce = 1; atexit(close_sdbus_once); + upsdebugx(5, "%s: registered atexit(close_sdbus_once)", __func__); } if (systemd_bus) { @@ -989,8 +990,10 @@ static const char * getmyprocname(void) myProcName = getprocname(getpid()); /* no xstrdup, we own and free this later */ if (myProcName) { myProcBaseName = xbasename(myProcName); /* substring inside myProcName */ - if (procname_cleanup_registered < 1) + if (procname_cleanup_registered < 1) { atexit(procname_cleanup); + upsdebugx(5, "%s: registered atexit(procname_cleanup)", __func__); + } procname_cleanup_registered = 1; } @@ -1936,6 +1939,7 @@ char * getfullpath(char * relative_path) if (win_PREFIX == NULL) { win_PREFIX = xstrdup(PREFIX); atexit(win_PREFIX_cleanup); + upsdebugx(5, "%s: registered atexit(win_PREFIX_cleanup)", __func__); last_slash = win_PREFIX; while ( (last_slash = strchr(last_slash, '/')) ) { *last_slash = '\\'; @@ -2807,6 +2811,7 @@ static const char *reset_progname_argv0_default(char *arg) { /* First time here */ atexit(cleanup_progname_argv0_default); atexit_hooked = 1; + upsdebugx(5, "%s: registered atexit(cleanup_progname_argv0_default)", __func__); } cleanup_progname_argv0_default(); @@ -4538,6 +4543,7 @@ const char *setproctag_lib_once(const char *val) { if (proctag_cleanup_registered < 1) { atexit(proctag_cleanup); proctag_cleanup_registered = 1; + /* NOISY? // upsdebugx(8, "%s: registered atexit(proctag_cleanup)", __func__); */ } } @@ -4562,8 +4568,10 @@ void setproctag(const char *tag) upsdebugx(3, "%s: starting first tagging as '%s'...", __func__, NUT_STRARG(tag)); getmyprocname(); - if (proctag_cleanup_registered < 1) + if (proctag_cleanup_registered < 1) { atexit(proctag_cleanup); + upsdebugx(5, "%s: registered atexit(proctag_cleanup)", __func__); + } proctag_cleanup_registered = 2; } @@ -5342,6 +5350,7 @@ void nut_prepare_search_paths(void) { if (!atexit_hooked) { atexit(nut_free_search_paths); atexit_hooked = 1; + upsdebugx(5, "%s: registered atexit(nut_free_search_paths)", __func__); } } From 6083a8cb0baab0099af35bf9e716902bcc1c39d7 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 17:47:54 +0200 Subject: [PATCH 44/65] tools/nutconf/nutconf-cli.cpp: limit calls to nutscan_* to builds WITH_NUTSCANNER, and call nutscan_free() explicitly when debugging (so logs make better sense) [#3379] Signed-off-by: Jim Klimov --- tools/nutconf/nutconf-cli.cpp | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index 6bf8e0dc7f..2120f39ffa 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -719,7 +719,11 @@ 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(NULL)); + nutscan_init(); + } /** Finalization */ ~InitFinal() { nutscan_free(); } @@ -3135,16 +3139,9 @@ static void scanSerialDevices(const NutConfOptions & options) { * \return 0 always (exits on error) */ static int mainx(int argc, char * const argv[]) { - /* 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 = upslog_start_sync(nutscan_upslog_start_sync(NULL)); const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "nutconf"); char *s = nullptr; - NUT_UNUSED_VARIABLE(upslog_start_tmp); - // Get options, also set nut_debug_level NutConfOptions options(argv, argc); @@ -3162,8 +3159,11 @@ static int mainx(int argc, char * const argv[]) { /* 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_set_debug_level(nut_debug_level); nutscan_setproctag(prog); +#endif + setproctag(prog); upsdebugx(1, "Starting NUT configuration tool: %s", prog); // Usage @@ -3291,6 +3291,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) { @@ -3327,6 +3328,23 @@ static int mainx(int argc, char * const argv[]) { scanSerialDevices(options); } + nutscan_setproctag(prog); +#endif // defined WITH_NUTSCANNER + + upsdebugx(1, "Finishing NUT configuration tool: %s", prog); + +#if (defined WITH_NUTSCANNER) + /* For proper logging attribution, we call this before exit() which calls + * static NutScanner::s_init_final ~InitFinal() + * C++ destructor which should run (for global objects) + * after any registered atexit() handlers, per + * https://stackoverflow.com/questions/16010083/order-between-destruction-of-global-object-and-atexit-in-c + * Apparently the destructor still runs and does its work, but + * there is nothing more to log under that copy of nutscan_free() + * which happens without a process/library tag in the debug log. + */ + if (nut_debug_level) + nutscan_free(); #endif // defined WITH_NUTSCANNER return 0; @@ -3343,6 +3361,7 @@ static int mainx(int argc, char * const argv[]) { */ int main(int argc, char * const argv[]) { try { + upslog_start_sync(NULL); return mainx(argc, argv); } catch (const std::exception & e) { From d91f613ebae8320dea5f4f0eaaefd257c2cf2a74 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 18:18:50 +0200 Subject: [PATCH 45/65] common/common.c: introduce nut_common_atexit() to clean-up once and in a deterministic order [#3384] Signed-off-by: Jim Klimov --- common/common.c | 116 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 15 deletions(-) diff --git a/common/common.c b/common/common.c index 8494dcd284..b697eca9ae 100644 --- a/common/common.c +++ b/common/common.c @@ -50,6 +50,93 @@ static const char * getmyprocname(void); static const char * getmyprocbasename(void); +/* 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 # include @@ -137,8 +224,8 @@ static int open_sdbus_once(const char *caller) { if (systemd_bus && !openedOnce) { openedOnce = 1; - atexit(close_sdbus_once); - upsdebugx(5, "%s: registered atexit(close_sdbus_once)", __func__); + nut_common_atexit(close_sdbus_once); + upsdebugx(5, "%s: registered nut_common_atexit(close_sdbus_once)", __func__); } if (systemd_bus) { @@ -940,7 +1027,6 @@ static const char *myProcBaseName = NULL; * Var/method used in procname_cleanup(), implemented further in the file */ static char *proctag = NULL, *proctag_for_upsdebug = NULL, *proctag_lib = NULL, proctag_cleanup_registered = 0; -static void proctag_cleanup(void); static void procname_cleanup(void) { char *myBN, *myPN, *myPT, *myLT, *myPTU; @@ -991,8 +1077,8 @@ static const char * getmyprocname(void) if (myProcName) { myProcBaseName = xbasename(myProcName); /* substring inside myProcName */ if (procname_cleanup_registered < 1) { - atexit(procname_cleanup); - upsdebugx(5, "%s: registered atexit(procname_cleanup)", __func__); + nut_common_atexit(procname_cleanup); + upsdebugx(5, "%s: registered nut_common_atexit(procname_cleanup)", __func__); } procname_cleanup_registered = 1; } @@ -1938,8 +2024,8 @@ char * getfullpath(char * relative_path) if (win_PREFIX == NULL) { win_PREFIX = xstrdup(PREFIX); - atexit(win_PREFIX_cleanup); - upsdebugx(5, "%s: registered atexit(win_PREFIX_cleanup)", __func__); + 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 = '\\'; @@ -2809,9 +2895,9 @@ static const char *reset_progname_argv0_default(char *arg) { if (!atexit_hooked) { /* First time here */ - atexit(cleanup_progname_argv0_default); + nut_common_atexit(cleanup_progname_argv0_default); atexit_hooked = 1; - upsdebugx(5, "%s: registered atexit(cleanup_progname_argv0_default)", __func__); + upsdebugx(5, "%s: registered nut_common_atexit(cleanup_progname_argv0_default)", __func__); } cleanup_progname_argv0_default(); @@ -4541,9 +4627,9 @@ const char *setproctag_lib_once(const char *val) { snprintf(proctag_lib, proctag_lib_buflen, ":%s", val); if (proctag_cleanup_registered < 1) { - atexit(proctag_cleanup); + nut_common_atexit(proctag_cleanup); proctag_cleanup_registered = 1; - /* NOISY? // upsdebugx(8, "%s: registered atexit(proctag_cleanup)", __func__); */ + /* NOISY? // upsdebugx(8, "%s: registered nut_common_atexit(proctag_cleanup)", __func__); */ } } @@ -4569,8 +4655,8 @@ void setproctag(const char *tag) getmyprocname(); if (proctag_cleanup_registered < 1) { - atexit(proctag_cleanup); - upsdebugx(5, "%s: registered atexit(proctag_cleanup)", __func__); + nut_common_atexit(proctag_cleanup); + upsdebugx(5, "%s: registered nut_common_atexit(proctag_cleanup)", __func__); } proctag_cleanup_registered = 2; } @@ -5348,9 +5434,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 atexit(nut_free_search_paths)", __func__); + upsdebugx(5, "%s: registered nut_common_atexit(nut_free_search_paths)", __func__); } } From 106c0a5eb2ce54eddc1cab001866e17395290112 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 18:34:07 +0200 Subject: [PATCH 46/65] tools/nutconf/nutconf-cli.cpp: hack for early enough nutscan_free() to yield reasonable debug logs is no longer needde [#3384] Signed-off-by: Jim Klimov --- tools/nutconf/nutconf-cli.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index 2120f39ffa..9b0bda485b 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -3332,21 +3332,6 @@ static int mainx(int argc, char * const argv[]) { #endif // defined WITH_NUTSCANNER upsdebugx(1, "Finishing NUT configuration tool: %s", prog); - -#if (defined WITH_NUTSCANNER) - /* For proper logging attribution, we call this before exit() which calls - * static NutScanner::s_init_final ~InitFinal() - * C++ destructor which should run (for global objects) - * after any registered atexit() handlers, per - * https://stackoverflow.com/questions/16010083/order-between-destruction-of-global-object-and-atexit-in-c - * Apparently the destructor still runs and does its work, but - * there is nothing more to log under that copy of nutscan_free() - * which happens without a process/library tag in the debug log. - */ - if (nut_debug_level) - nutscan_free(); -#endif // defined WITH_NUTSCANNER - return 0; } From cfc61066772dfbcb51fc94fda0de2c8dbd340b9f Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 18:44:05 +0200 Subject: [PATCH 47/65] common/common.c: proctag_cleanup(): clarify in log messages if it is the library unloading [#3379] Signed-off-by: Jim Klimov --- common/common.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/common/common.c b/common/common.c index b697eca9ae..6d857720c6 100644 --- a/common/common.c +++ b/common/common.c @@ -4584,11 +4584,20 @@ static void proctag_cleanup(void) /* 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) @@ -4597,12 +4606,13 @@ static void proctag_cleanup(void) free(tn); } - /* Free the global vars */ + /* Free some global vars */ if (proctag_lib) { free(proctag_lib); proctag_lib = NULL; } + /* Frees the remaining global vars */ setproctag(NULL); proctag_cleanup_registered = -1; From f5d66bfe7c0f7df8edeef26917e42fee3e8e0fae Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 18:50:58 +0200 Subject: [PATCH 48/65] common/common.c: proctag_cleanup()/procname_cleanup(): simplify "starting"/"finished" log messages [#3379] Signed-off-by: Jim Klimov --- common/common.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/common/common.c b/common/common.c index 6d857720c6..dfcc0ea3ad 100644 --- a/common/common.c +++ b/common/common.c @@ -1040,8 +1040,8 @@ static void procname_cleanup(void) { myLT = (proctag_lib ? xstrdup(proctag_lib) : NULL); myPTU = (proctag_for_upsdebug ? xstrdup(proctag_for_upsdebug) : NULL); - upsdebugx(3, "%s: {%s}: starting for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", - __func__, NUT_STRARG(myPTU), NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + 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() */ @@ -1058,8 +1058,13 @@ static void procname_cleanup(void) { procname_cleanup_registered = -1; - upsdebugx(3, "%s: {%s}: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", - __func__, NUT_STRARG(myPTU), NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + if (myPTU || myLT) { + upsdebugx(3, "{%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(3, "%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); @@ -4573,8 +4578,8 @@ static void proctag_cleanup(void) myLT = (proctag_lib ? xstrdup(proctag_lib) : NULL); myPTU = (proctag_for_upsdebug ? xstrdup(proctag_for_upsdebug) : NULL); - upsdebugx(3, "%s: {%s}: starting for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", - __func__, NUT_STRARG(myPTU), NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + 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()); @@ -4617,8 +4622,13 @@ static void proctag_cleanup(void) proctag_cleanup_registered = -1; - upsdebugx(3, "%s: {%s}: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", - __func__, NUT_STRARG(myPTU), NUT_STRARG(myPN), NUT_STRARG(myBN), NUT_STRARG(myPT), NUT_STRARG(myLT)); + if (myPTU || myLT) { + upsdebugx(3, "{%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(3, "%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); From 29ad53e808279fba4acc13c1d7d8683eac22964b Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 19:09:50 +0200 Subject: [PATCH 49/65] drivers/main.c: fix preprocessor macro indentation of DRIVERS_MAIN_WITHOUT_MAIN Signed-off-by: Jim Klimov --- drivers/main.c | 94 +++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/drivers/main.c b/drivers/main.c index 65484c46c1..4103be9419 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -2164,18 +2164,18 @@ int main(int argc, char **argv) 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 */ +# 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 @@ -2190,7 +2190,7 @@ 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) * Note that unlike simpler programs, this is a long-running @@ -2361,20 +2361,20 @@ 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) { @@ -2383,7 +2383,7 @@ int main(int argc, char **argv) "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", @@ -2392,20 +2392,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); @@ -2517,7 +2517,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 @@ -2543,7 +2543,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 @@ -2596,29 +2596,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) @@ -2715,11 +2715,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--) { @@ -2767,7 +2767,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 @@ -2824,7 +2824,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 " @@ -2834,9 +2834,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", @@ -2855,11 +2855,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); @@ -2922,7 +2922,7 @@ int main(int argc, char **argv) writepid(pidfn); /* before backgrounding */ } } -#else /* WIN32 */ +# else /* WIN32 */ snprintf(drv_pipe_name, sizeof(drv_pipe_name), "%s-%s", progname, upsname); if (cmd) { @@ -2961,7 +2961,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; @@ -3048,7 +3048,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 @@ -3145,10 +3145,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); } @@ -3203,20 +3203,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) { From d826e43419437a576b9e9c58a0178ec0cfe11a70 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 19:11:01 +0200 Subject: [PATCH 50/65] clients/*.c: for consumers of libupsclient, start with initialization of shared logging time stamps [#3378, #3379] Signed-off-by: Jim Klimov --- clients/upsc.c | 7 +++++++ clients/upscmd.c | 7 +++++++ clients/upsimage.c | 4 ++-- clients/upslog.c | 7 +++++++ clients/upsmon.c | 7 +++++++ clients/upsrw.c | 7 +++++++ clients/upsset.c | 5 +++-- clients/upsstats.c | 4 ++-- 8 files changed, 42 insertions(+), 6 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index dfda0a3670..77d0864ae3 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -392,12 +392,19 @@ static void clean_exit(void) int main(int argc, char **argv) { + /* 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)); int opt_ret = 0; uint16_t port; int varlist = 0, clientlist = 0, verbose = 0; const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsc"); const char *net_connect_timeout = NULL; + NUT_UNUSED_VARIABLE(upslog_start_tmp); + /* 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` diff --git a/clients/upscmd.c b/clients/upscmd.c index b6557d8b96..56b570e099 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -299,6 +299,11 @@ static void clean_exit(void) int main(int argc, char **argv) { + /* 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)); int opt_ret = 0; uint16_t port; ssize_t ret; @@ -307,6 +312,8 @@ int main(int argc, char **argv) const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upscmd"); const char *net_connect_timeout = NULL; + NUT_UNUSED_VARIABLE(upslog_start_tmp); + /* 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` diff --git a/clients/upsimage.c b/clients/upsimage.c index d3399d938d..9d39e64550 100644 --- a/clients/upsimage.c +++ b/clients/upsimage.c @@ -634,8 +634,8 @@ 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)); + 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 diff --git a/clients/upslog.c b/clients/upslog.c index 20a2148e64..f09580e1a0 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -511,6 +511,11 @@ static void run_flist(const struct monhost_ups_t *monhost_ups_print) int main(int argc, char **argv) { + /* 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)); 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 = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upslog"); @@ -525,6 +530,8 @@ int main(int argc, char **argv) logformat = DEFAULT_LOGFORMAT; user = RUN_AS_USER; + NUT_UNUSED_VARIABLE(upslog_start_tmp); + /* 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` diff --git a/clients/upsmon.c b/clients/upsmon.c index 513a9fcd25..d9ceabef80 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -3788,6 +3788,11 @@ static void init_Inhibitor(const char *prog) int main(int argc, char *argv[]) { + /* 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)); const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsmon"); const char *net_connect_timeout = NULL; int opt_ret = 0, cmdret = -1, checking_flag = 0, foreground = -1; @@ -3805,6 +3810,8 @@ int main(int argc, char *argv[]) pipe_conn_t *conn; #endif /* WIN32 */ + NUT_UNUSED_VARIABLE(upslog_start_tmp); + print_banner_once(prog, 0); /* if no configuration file is specified on the command line, use default */ diff --git a/clients/upsrw.c b/clients/upsrw.c index 046037afbf..e6e2362dca 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -659,12 +659,19 @@ static void print_rwlist(void) int main(int argc, char **argv) { + /* 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)); int opt_ret = 0; uint16_t port; 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; + NUT_UNUSED_VARIABLE(upslog_start_tmp); + /* 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` diff --git a/clients/upsset.c b/clients/upsset.c index 36d231f7d3..d3e8319a07 100644 --- a/clients/upsset.c +++ b/clients/upsset.c @@ -1133,8 +1133,9 @@ 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)); + getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsset(CGI)"); + username = password = function = monups = NULL; printf("Content-type: text/html\n"); diff --git a/clients/upsstats.c b/clients/upsstats.c index 55ad9b8cf4..177596790e 100644 --- a/clients/upsstats.c +++ b/clients/upsstats.c @@ -1698,8 +1698,8 @@ 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)); + 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 From 439a4182d20cdf5f25bdf99046d7820e67c44d37 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 20:35:44 +0200 Subject: [PATCH 51/65] common/common.c: proctag_cleanup()/procname_cleanup(): log "finished" messages with odd markup at lower verbosity [#3379] Signed-off-by: Jim Klimov --- common/common.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/common.c b/common/common.c index dfcc0ea3ad..f1b4584f1b 100644 --- a/common/common.c +++ b/common/common.c @@ -1059,10 +1059,10 @@ static void procname_cleanup(void) { procname_cleanup_registered = -1; if (myPTU || myLT) { - upsdebugx(3, "{%s}: %s: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + 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(3, "%s: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + 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)); } @@ -4623,10 +4623,10 @@ static void proctag_cleanup(void) proctag_cleanup_registered = -1; if (myPTU || myLT) { - upsdebugx(3, "{%s}: %s: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + 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(3, "%s: finished for: myProcName=[%s] myProcBaseName=[%s] proctag=[%s] proctag_lib=[%s]", + 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)); } From 19c888b8da27d1c0238e7a2809f4e802deb47333 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 20:36:31 +0200 Subject: [PATCH 52/65] clients/upsc.c: revise main processing loop to setproctag(prog) once instead of NULL [#3378] Signed-off-by: Jim Klimov --- clients/upsc.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index 77d0864ae3..01d6208ba7 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -385,9 +385,7 @@ static void clean_exit(void) free(hostname); free(ups); - /* Not a sub-process (do not let common::proctag_cleanup() mis-report us as such) */ upsdebugx(1, "%s: finished, exiting", __func__); - setproctag(NULL); } int main(int argc, char **argv) @@ -520,15 +518,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]); @@ -537,6 +533,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); } From 271e266f21b81b0ac491e0120978475f91847b67 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 20:40:25 +0200 Subject: [PATCH 53/65] NUT common code, libupsclient, libnutscan: expose setmyprocname() to initialize libs quietly where we can [#3379] Signed-off-by: Jim Klimov --- clients/upsclient.c | 9 +++++++++ clients/upsclient.h | 1 + common/common.c | 27 +++++++++++++++++++-------- docs/man/Makefile.am | 2 ++ docs/man/nutscan_set_debug_level.txt | 3 ++- docs/man/upscli_set_debug_level.txt | 3 ++- docs/nut.dict | 4 +++- include/common.h | 9 +++++++++ tools/nut-scanner/nutscan-init.c | 12 ++++++++++++ tools/nut-scanner/nutscan-init.h | 1 + tools/nut-scanner/scan_nut.c | 20 ++++++++++++++++++++ 11 files changed, 80 insertions(+), 11 deletions(-) diff --git a/clients/upsclient.c b/clients/upsclient.c index bbeb5a070d..c334bccde6 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -2287,6 +2287,15 @@ int upscli_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_setprocname(const char *pn) +{ + setproctag_lib_once("libupsclient"); + setmyprocname(pn); +} + void upscli_setproctag(const char *tag) { setproctag_lib_once("libupsclient"); diff --git a/clients/upsclient.h b/clients/upsclient.h index d28d41fb4d..0b862ea796 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -110,6 +110,7 @@ void upscli_set_debug_level(int lvl); int upscli_get_debug_level(void); /* Similarly for sub-process tags that help with troubleshooting */ +void upscli_setprocname(const char *pn); void upscli_setproctag(const char *tag); const char *upscli_getproctag(void); diff --git a/common/common.c b/common/common.c index f1b4584f1b..d8918e7209 100644 --- a/common/common.c +++ b/common/common.c @@ -47,9 +47,6 @@ # include #endif -static const char * getmyprocname(void); -static const char * getmyprocbasename(void); - /* 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)) @@ -1073,12 +1070,18 @@ static void procname_cleanup(void) { 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 */ if (procname_cleanup_registered < 1) { @@ -1087,11 +1090,19 @@ static const char * getmyprocname(void) } 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; diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index b123eda72f..3015ffe69f 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -709,6 +709,7 @@ $(UPSCLI_SSL_CAPS_DEPS): upscli_ssl_caps.$(MAN_SECTION_API) UPSCLI_SET_DEBUG_LEVEL_DEPS = \ upscli_get_debug_level.$(MAN_SECTION_API) \ upscli_upslog_start_sync.$(MAN_SECTION_API) \ + upscli_setprocname.$(MAN_SECTION_API) \ upscli_getproctag.$(MAN_SECTION_API) \ upscli_setproctag.$(MAN_SECTION_API) @@ -817,6 +818,7 @@ nutscan_add_commented_option_to_device.$(MAN_SECTION_API): nutscan_add_option_to NUTSCAN_SET_DEBUG_LEVEL_DEPS = \ nutscan_get_debug_level.$(MAN_SECTION_API) \ nutscan_upslog_start_sync.$(MAN_SECTION_API) \ + nutscan_setprocname.$(MAN_SECTION_API) \ nutscan_getproctag.$(MAN_SECTION_API) \ nutscan_setproctag.$(MAN_SECTION_API) diff --git a/docs/man/nutscan_set_debug_level.txt b/docs/man/nutscan_set_debug_level.txt index 7af0c9c34a..8e28a9cd39 100644 --- a/docs/man/nutscan_set_debug_level.txt +++ b/docs/man/nutscan_set_debug_level.txt @@ -5,7 +5,7 @@ NAME ---- nutscan_set_debug_level, nutscan_get_debug_level, -nutscan_setproctag, nutscan_getproctag, +nutscan_setprocname, nutscan_setproctag, nutscan_getproctag, nutscan_upslog_start_sync - manipulate debugging level and sub-process tags for NUT common code in the nutscan library; @@ -20,6 +20,7 @@ SYNOPSIS void nutscan_set_debug_level(int level); int nutscan_get_debug_level(void); + void nutscan_setprocname(const char *full_procname); void nutscan_setproctag(const char *tag); const char *nutscan_getproctag(void); diff --git a/docs/man/upscli_set_debug_level.txt b/docs/man/upscli_set_debug_level.txt index c8b395472c..cbd6a6374a 100644 --- a/docs/man/upscli_set_debug_level.txt +++ b/docs/man/upscli_set_debug_level.txt @@ -5,7 +5,7 @@ NAME ---- upscli_set_debug_level, upscli_get_debug_level, -upscli_setproctag, upscli_getproctag, +upscli_setprocname, upscli_setproctag, upscli_getproctag, upscli_upslog_start_sync - manipulate the possibly separate copies of the `nut_debug_level` and sub-process tag variables in the `libupsclient` build @@ -19,6 +19,7 @@ SYNOPSIS void upscli_set_debug_level(int); int upscli_get_debug_level(void); + void upscli_setprocname(const char *full_procname); void upscli_setproctag(const char *tag); const char *upscli_getproctag(void); diff --git a/docs/nut.dict b/docs/nut.dict index 1faf5c6983..b2877f5d40 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 3709 utf-8 AAC AAS ABI @@ -2983,6 +2983,7 @@ prjconf problemMatcher probu proc +procname productid prog progname @@ -3171,6 +3172,7 @@ setfsd setgid setinfo setpci +setprocname setproctag setq setuid diff --git a/include/common.h b/include/common.h index 31df398295..a521a5a7e1 100644 --- a/include/common.h +++ b/include/common.h @@ -298,6 +298,15 @@ 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 */ +/* 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); diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index ee6fea49e7..d410fbddd1 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -74,6 +74,7 @@ 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); +void nutscan_upscli_setprocname(const char *pn); void nutscan_upscli_setproctag(const char *tag); struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv); int nutscan_load_upower_library(const char *libname_path); @@ -167,6 +168,17 @@ int nutscan_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_setprocname(const char *pn) +{ + setproctag_lib_once("libnutscan"); + setmyprocname(pn); + /* Succeeds after init and if the library is loaded, else no-op */ + nutscan_upscli_setprocname(pn); +} + void nutscan_setproctag(const char *tag) { setproctag_lib_once("libnutscan"); diff --git a/tools/nut-scanner/nutscan-init.h b/tools/nut-scanner/nutscan-init.h index 197b49b4a6..cca5753200 100644 --- a/tools/nut-scanner/nutscan-init.h +++ b/tools/nut-scanner/nutscan-init.h @@ -47,6 +47,7 @@ void nutscan_free(void); void nutscan_set_debug_level(int level); int nutscan_get_debug_level(void); +void nutscan_setprocname(const char *pn); void nutscan_setproctag(const char *tag); const char *nutscan_getproctag(void); diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index b69e0b7b59..61c84dd297 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -54,6 +54,7 @@ static int (*nut_upscli_disconnect)(UPSCONN_t *ups); static int (*nut_upscli_ssl_caps)(void); static int (*nut_upscli_set_debug_level)(int); static void (*nut_upscli_setproctag)(const char *tag); +static void (*nut_upscli_setprocname)(const char *tag); static struct timeval *(*nut_upscli_upslog_start_sync)(struct timeval *tv); static void (*nut_upscli_report_build_details)(void); @@ -96,6 +97,14 @@ void nutscan_upscli_set_debug_level(int level) } } +void nutscan_upscli_setprocname(const char *pn); +void nutscan_upscli_setprocname(const char *pn) +{ + if (nutscan_avail_nut && dl_handle && nut_upscli_setprocname) { + (*nut_upscli_setprocname)(pn); + } +} + void nutscan_upscli_setproctag(const char *tag); void nutscan_upscli_setproctag(const char *tag) { @@ -200,6 +209,17 @@ int nutscan_load_upsclient_library(const char *libname_path) (*nut_upscli_upslog_start_sync)(upslog_start_sync(NULL)); } + *(void **) (&nut_upscli_setprocname) = lt_dlsym(dl_handle, + symbol = "upscli_setprocname"); + if ((dl_error = lt_dlerror()) != NULL) { + nut_upscli_setprocname = NULL; + upsdebugx(1, "%s: %s() not found, using older libupsclient build?", + __func__, symbol); + } else { + /* Propagate value currently known in libnutscan into libupsclient */ + (*nut_upscli_setprocname)(xstrdup(getmyprocname())); + } + *(void **) (&nut_upscli_setproctag) = lt_dlsym(dl_handle, symbol = "upscli_setproctag"); if ((dl_error = lt_dlerror()) != NULL) { From b7748309144c50694f3800d809447ab515b078fe Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 20:59:33 +0200 Subject: [PATCH 54/65] NUT consumers of libupsclient/libnutscan: call *_setprocname() early to avoid re-getting the value in the library copy of the code [#3379] Signed-off-by: Jim Klimov --- clients/upsc.c | 1 + clients/upscmd.c | 1 + clients/upsimage.c | 1 + clients/upslog.c | 1 + clients/upsmon.c | 1 + clients/upsrw.c | 1 + clients/upsset.c | 1 + clients/upsstats.c | 1 + tools/nut-scanner/nut-scanner.c | 11 +++++++++-- tools/nutconf/nutconf-cli.cpp | 3 ++- 10 files changed, 19 insertions(+), 3 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index 01d6208ba7..d7b4956abd 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -402,6 +402,7 @@ int main(int argc, char **argv) const char *net_connect_timeout = NULL; NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_setprocname(xstrdup(getmyprocname())); /* 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. diff --git a/clients/upscmd.c b/clients/upscmd.c index 56b570e099..2745cb3076 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -313,6 +313,7 @@ int main(int argc, char **argv) const char *net_connect_timeout = NULL; NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_setprocname(xstrdup(getmyprocname())); /* 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. diff --git a/clients/upsimage.c b/clients/upsimage.c index 9d39e64550..395409867b 100644 --- a/clients/upsimage.c +++ b/clients/upsimage.c @@ -635,6 +635,7 @@ int main(int argc, char **argv) #endif upscli_upslog_start_sync(upslog_start_sync(NULL)); + upscli_setprocname(xstrdup(getmyprocname())); getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsimage(CGI)"); /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc diff --git a/clients/upslog.c b/clients/upslog.c index f09580e1a0..1c9adc3785 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -531,6 +531,7 @@ int main(int argc, char **argv) user = RUN_AS_USER; NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_setprocname(xstrdup(getmyprocname())); /* 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. diff --git a/clients/upsmon.c b/clients/upsmon.c index d9ceabef80..17d6a3969a 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -3811,6 +3811,7 @@ int main(int argc, char *argv[]) #endif /* WIN32 */ NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_setprocname(xstrdup(getmyprocname())); print_banner_once(prog, 0); diff --git a/clients/upsrw.c b/clients/upsrw.c index e6e2362dca..4d1af9e3b3 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -671,6 +671,7 @@ int main(int argc, char **argv) char *password = NULL, *username = NULL, *setvar = NULL; NUT_UNUSED_VARIABLE(upslog_start_tmp); + upscli_setprocname(xstrdup(getmyprocname())); /* 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. diff --git a/clients/upsset.c b/clients/upsset.c index d3e8319a07..8e13cacb3d 100644 --- a/clients/upsset.c +++ b/clients/upsset.c @@ -1134,6 +1134,7 @@ int main(int argc, char **argv) #endif upscli_upslog_start_sync(upslog_start_sync(NULL)); + upscli_setprocname(xstrdup(getmyprocname())); getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsset(CGI)"); username = password = function = monups = NULL; diff --git a/clients/upsstats.c b/clients/upsstats.c index 177596790e..9ab38fffa1 100644 --- a/clients/upsstats.c +++ b/clients/upsstats.c @@ -1699,6 +1699,7 @@ int main(int argc, char **argv) #endif upscli_upslog_start_sync(upslog_start_sync(NULL)); + upscli_setprocname(xstrdup(getmyprocname())); getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsstats(CGI)"); /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 9a3bc9207e..a22c29d9ca 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -1202,7 +1202,16 @@ 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_setprocname(xstrdup(getmyprocname())) + * because its provider of those data and debugging methods + * is libnutscan itself. + */ + nutscan_setproctag(progname); +#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 @@ -1231,8 +1240,6 @@ int main(int argc, char *argv[]) } #endif /* HAVE_PTHREAD && ( HAVE_PTHREAD_TRYJOIN || HAVE_SEMAPHORE_UNNAMED || HAVE_SEMAPHORE_NAMED ) && HAVE_SYS_RESOURCE_H */ - nutscan_setproctag(progname); - memset(&snmp_sec, 0, sizeof(snmp_sec)); memset(&ipmi_sec, 0, sizeof(ipmi_sec)); memset(&xml_sec, 0, sizeof(xml_sec)); diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index 9b0bda485b..4b658e502a 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -721,7 +721,8 @@ class NutScanner { /** Initialization */ InitFinal() { /* Register atexit() cleanups to scope around libnutscan lifetime */ - nutscan_upslog_start_sync(upslog_start_sync(NULL)); + nutscan_upslog_start_sync(upslog_start_sync(nullptr)); + nutscan_setprocname(xstrdup(getmyprocname())); nutscan_init(); } From afaa8d198363bc220d3cb2d11c7c30d2d87caf57 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 21:12:26 +0200 Subject: [PATCH 55/65] clients/*.c: redefine setproctag() to cover both in-program common code and libupsclient copy [#3379, #3378] Signed-off-by: Jim Klimov --- clients/upsc.c | 4 ++++ clients/upscmd.c | 4 ++++ clients/upsimage.c | 4 ++++ clients/upslog.c | 4 ++++ clients/upsmon.c | 4 ++++ clients/upsrw.c | 4 ++++ clients/upsset.c | 4 ++++ clients/upsstats.c | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/clients/upsc.c b/clients/upsc.c index d7b4956abd..531cbc9ed2 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -35,6 +35,10 @@ /* 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_setproctag(x); } while(0) + static char *upsname = NULL, *hostname = NULL; static UPSCONN_t *ups = NULL; static int output_json = 0; diff --git a/clients/upscmd.c b/clients/upscmd.c index 2745cb3076..77dfb543d4 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -36,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_setproctag(x); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" diff --git a/clients/upsimage.c b/clients/upsimage.c index 395409867b..538d2a3033 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_setproctag(x); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" diff --git a/clients/upslog.c b/clients/upslog.c index 1c9adc3785..a0ab17df7b 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_setproctag(x); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" diff --git a/clients/upsmon.c b/clients/upsmon.c index 17d6a3969a..9fdb8f3d37 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_setproctag(x); } while(0) + static char *shutdowncmd = NULL, *notifycmd = NULL; static char *powerdownflag = NULL, *configfile = NULL; diff --git a/clients/upsrw.c b/clients/upsrw.c index 4d1af9e3b3..5f133e9a78 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -36,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_setproctag(x); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" diff --git a/clients/upsset.c b/clients/upsset.c index 8e13cacb3d..25f9857a21 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_setproctag(x); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" diff --git a/clients/upsstats.c b/clients/upsstats.c index 9ab38fffa1..22bc47644a 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_setproctag(x); } while(0) + /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" From e0179db5a82a48c28aa99cf74d2175c446ea4295 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 21:23:56 +0200 Subject: [PATCH 56/65] drivers/dummy-ups.c: as a libupsclient consumer, use upscli_upslog_start_sync() and upscli_setprocname() to initialize consistent logging [#3379] Signed-off-by: Jim Klimov --- drivers/dummy-ups.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index 863740890e..64a0fac3a5 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 = @@ -426,12 +426,21 @@ void upsdrv_help(void) /* optionally tweak prognames[] entries */ void upsdrv_tweak_prognames(void) { + /* 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)); + upscli_set_debug_level(nut_debug_level); + upscli_setprocname(xstrdup(getmyprocname())); + /* 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? */ + upscli_setproctag(xstrdup(getproctag())); } 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) From 29351e07e2eb0964621175ef431d1a1862053a2f Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 21:42:41 +0200 Subject: [PATCH 57/65] drivers/main.c: setproctag(prognames[0]) as soon as we can, so CLI/CONF processing is better traceable [#3379] Signed-off-by: Jim Klimov --- drivers/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/main.c b/drivers/main.c index 4103be9419..9e844c8309 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -2273,6 +2273,7 @@ int main(int argc, char **argv) * 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"); + setproctag(prognames[0]); upsdrv_callbacks.upsdrv_tweak_prognames(); From 671ab21e0ce293ec5ab7dad8cb7a60292bdd23a8 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 29 Mar 2026 23:51:00 +0200 Subject: [PATCH 58/65] common/common.c: satisfy compilers where default char is signed [#3379] Signed-off-by: Jim Klimov --- common/common.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/common.c b/common/common.c index d8918e7209..3199fc9dbd 100644 --- a/common/common.c +++ b/common/common.c @@ -1017,13 +1017,15 @@ void chroot_start(const char *path) } /* In forking, assume process name does not change (PID might); cache it */ -static char *myProcName = NULL, procname_cleanup_registered = 0; +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, proctag_cleanup_registered = 0; + *proctag_lib = NULL; +static int proctag_cleanup_registered = 0; static void procname_cleanup(void) { char *myBN, *myPN, *myPT, *myLT, *myPTU; From 9c6278d72fb302fe4b4a875f1b718ea513717474 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 30 Mar 2026 11:07:37 +0200 Subject: [PATCH 59/65] clients/*.c: diligently call upscli_cleanup() to minimize valgrind alerts [#3378, #3379] Signed-off-by: Jim Klimov --- clients/upsc.c | 2 ++ clients/upscmd.c | 6 ++++-- clients/upsimage.c | 8 ++++++++ clients/upslog.c | 4 ++++ clients/upsrw.c | 6 ++++-- clients/upsset.c | 8 ++++++++ clients/upsstats.c | 6 ++++++ 7 files changed, 36 insertions(+), 4 deletions(-) diff --git a/clients/upsc.c b/clients/upsc.c index 531cbc9ed2..bf8a35a140 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -389,6 +389,8 @@ static void clean_exit(void) free(hostname); free(ups); + upscli_cleanup(); + upsdebugx(1, "%s: finished, exiting", __func__); } diff --git a/clients/upscmd.c b/clients/upscmd.c index 77dfb543d4..7c2c48492f 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -296,9 +296,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); } int main(int argc, char **argv) @@ -539,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 538d2a3033..e433592945 100644 --- a/clients/upsimage.c +++ b/clients/upsimage.c @@ -617,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; @@ -674,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 a0ab17df7b..48249023e4 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -1059,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); @@ -1084,6 +1087,7 @@ int main(int argc, char **argv) logformat = NULL; } + upscli_cleanup(); exit(EXIT_SUCCESS); } diff --git a/clients/upsrw.c b/clients/upsrw.c index 5f133e9a78..8d875491ad 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -99,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) ) @@ -797,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/upsset.c b/clients/upsset.c index 25f9857a21..baf2948024 100644 --- a/clients/upsset.c +++ b/clients/upsset.c @@ -1115,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; @@ -1177,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 22bc47644a..476647fe56 100644 --- a/clients/upsstats.c +++ b/clients/upsstats.c @@ -1681,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) { @@ -1743,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() From 024a1ec2ed885ca3adde745956db570ff774f79a Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 30 Mar 2026 09:41:16 +0200 Subject: [PATCH 60/65] Rename libnutscan and libupsclient API methods related to logging with libname_upslog_ prefix; use cookies to differentiate library run-time instances; update consumers accordingly [#3378, #3379] Signed-off-by: Jim Klimov --- NEWS.adoc | 4 +- clients/Makefile.am | 2 +- clients/upsc.c | 8 +-- clients/upsclient.c | 34 +++++++--- clients/upsclient.h | 19 ++++-- clients/upscmd.c | 8 +-- clients/upsimage.c | 10 +-- clients/upslog.c | 8 +-- clients/upsmon.c | 20 +++--- clients/upsrw.c | 8 +-- clients/upsset.c | 10 +-- clients/upsstats.c | 10 +-- common/common.c | 33 +++++++-- docs/man/Makefile.am | 42 ++++++------ ...txt => nutscan_upslog_set_debug_level.txt} | 25 ++++--- ....txt => upscli_upslog_set_debug_level.txt} | 27 +++++--- docs/nut.dict | 3 +- drivers/dummy-ups.c | 16 ++--- include/common.h | 11 ++- tools/nut-scanner/nut-scanner.c | 45 ++++++------ tools/nut-scanner/nutscan-init.c | 54 ++++++++++----- tools/nut-scanner/nutscan-init.h | 26 +++++-- tools/nut-scanner/scan_nut.c | 68 ++++++++++--------- tools/nutconf/nutconf-cli.cpp | 10 +-- 24 files changed, 306 insertions(+), 195 deletions(-) rename docs/man/{nutscan_set_debug_level.txt => nutscan_upslog_set_debug_level.txt} (59%) rename docs/man/{upscli_set_debug_level.txt => upscli_upslog_set_debug_level.txt} (65%) diff --git a/NEWS.adoc b/NEWS.adoc index 4d08e45a71..d90ac46958 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -140,7 +140,9 @@ but the `nutshutdown` script would bail out quickly and quietly. [PR #3008] * 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. [#3378] + 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 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 bf8a35a140..c2c002a3b0 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -37,7 +37,7 @@ /* 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_setproctag(x); } while(0) +#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; @@ -400,7 +400,7 @@ int main(int argc, char **argv) * 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)); + 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; @@ -408,7 +408,7 @@ int main(int argc, char **argv) const char *net_connect_timeout = NULL; NUT_UNUSED_VARIABLE(upslog_start_tmp); - upscli_setprocname(xstrdup(getmyprocname())); + 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. @@ -437,7 +437,7 @@ int main(int argc, char **argv) /* These lines aim to just initialize the logging subsystem, and set * initial timestamp, for the eventuality that debugs would be printed: */ - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); setproctag(prog); upsdebugx(1, "Starting NUT client: %s", prog); diff --git a/clients/upsclient.c b/clients/upsclient.c index c334bccde6..22f05f95f2 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -2276,13 +2276,22 @@ int upscli_str_add_unique_token(char *tgt, size_t tgtsize, const char *token, /* privately exported from common.c for internal libs */ const char *setproctag_lib_once(const char *val); -void upscli_set_debug_level(int lvl) +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; } @@ -2290,25 +2299,32 @@ int upscli_get_debug_level(void) /* 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_setprocname(const char *pn) +void upscli_upslog_setprocname(const char *pn, const void *cookie) { - setproctag_lib_once("libupsclient"); + if (cookie != upscli_upslog_cookie()) + setproctag_lib_once("libupsclient"); + setmyprocname(pn); } -void upscli_setproctag(const char *tag) +void upscli_upslog_setproctag(const char *tag, const void *cookie) { - setproctag_lib_once("libupsclient"); + if (cookie != upscli_upslog_cookie()) + setproctag_lib_once("libupsclient"); + setproctag(tag); } -const char *upscli_getproctag(void) +const char *upscli_upslog_getproctag(void) { return getproctag(); } -struct timeval *upscli_upslog_start_sync(struct timeval *tv) +struct timeval *upscli_upslog_start_sync(struct timeval *tv, const void *cookie) { - setproctag_lib_once("libupsclient"); + 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 0b862ea796..84889fbeb7 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -104,15 +104,20 @@ 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_setprocname(const char *pn); -void upscli_setproctag(const char *tag); -const char *upscli_getproctag(void); +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, @@ -125,7 +130,7 @@ const char *upscli_getproctag(void); * NOTE: In WIN32 builds also enforces line-buffering for * stdout and stderr streams. */ -struct timeval *upscli_upslog_start_sync(struct timeval *tv); +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 7c2c48492f..8cb7e50f07 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -38,7 +38,7 @@ /* 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_setproctag(x); } while(0) +#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" @@ -307,7 +307,7 @@ int main(int argc, char **argv) * 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)); + 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; @@ -317,7 +317,7 @@ int main(int argc, char **argv) const char *net_connect_timeout = NULL; NUT_UNUSED_VARIABLE(upslog_start_tmp); - upscli_setprocname(xstrdup(getmyprocname())); + 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. @@ -346,7 +346,7 @@ int main(int argc, char **argv) /* These lines aim to just initialize the logging subsystem, and set * initial timestamp, for the eventuality that debugs would be printed: */ - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); setproctag(prog); upsdebugx(1, "Starting NUT client: %s", prog); diff --git a/clients/upsimage.c b/clients/upsimage.c index e433592945..f9adbe8890 100644 --- a/clients/upsimage.c +++ b/clients/upsimage.c @@ -51,7 +51,7 @@ /* 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_setproctag(x); } while(0) +#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" @@ -645,8 +645,8 @@ int main(int argc, char **argv) setmode(STDOUT_FILENO, O_BINARY); #endif - upscli_upslog_start_sync(upslog_start_sync(NULL)); - upscli_setprocname(xstrdup(getmyprocname())); + 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 @@ -657,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 @@ -667,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) { diff --git a/clients/upslog.c b/clients/upslog.c index 48249023e4..80977a0179 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -45,7 +45,7 @@ /* 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_setproctag(x); } while(0) +#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" @@ -519,7 +519,7 @@ int main(int argc, char **argv) * 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)); + 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 = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upslog"); @@ -535,7 +535,7 @@ int main(int argc, char **argv) user = RUN_AS_USER; NUT_UNUSED_VARIABLE(upslog_start_tmp); - upscli_setprocname(xstrdup(getmyprocname())); + 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. @@ -568,7 +568,7 @@ int main(int argc, char **argv) /* These lines aim to just initialize the logging subsystem, and set * initial timestamp, for the eventuality that debugs would be printed: */ - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); setproctag(prog); print_banner_once(prog, 0); diff --git a/clients/upsmon.c b/clients/upsmon.c index 9fdb8f3d37..b17fb5c715 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -45,7 +45,7 @@ /* 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_setproctag(x); } while(0) +#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; @@ -2674,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, @@ -3415,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"); */ @@ -3796,7 +3796,7 @@ int main(int argc, char *argv[]) * 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)); + 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 opt_ret = 0, cmdret = -1, checking_flag = 0, foreground = -1; @@ -3815,7 +3815,7 @@ int main(int argc, char *argv[]) #endif /* WIN32 */ NUT_UNUSED_VARIABLE(upslog_start_tmp); - upscli_setprocname(xstrdup(getmyprocname())); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); print_banner_once(prog, 0); @@ -3852,7 +3852,7 @@ int main(int argc, char *argv[]) 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; @@ -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 8d875491ad..a861c43211 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -38,7 +38,7 @@ /* 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_setproctag(x); } while(0) +#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" @@ -667,7 +667,7 @@ int main(int argc, char **argv) * 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)); + 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 = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "upsrw"); @@ -675,7 +675,7 @@ int main(int argc, char **argv) char *password = NULL, *username = NULL, *setvar = NULL; NUT_UNUSED_VARIABLE(upslog_start_tmp); - upscli_setprocname(xstrdup(getmyprocname())); + 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. @@ -704,7 +704,7 @@ int main(int argc, char **argv) /* These lines aim to just initialize the logging subsystem, and set * initial timestamp, for the eventuality that debugs would be printed: */ - upscli_set_debug_level(nut_debug_level); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); setproctag(prog); upsdebugx(1, "Starting NUT client: %s", prog); diff --git a/clients/upsset.c b/clients/upsset.c index baf2948024..20f70bf8b6 100644 --- a/clients/upsset.c +++ b/clients/upsset.c @@ -47,7 +47,7 @@ struct list_t { /* 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_setproctag(x); } while(0) +#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" @@ -1144,8 +1144,8 @@ int main(int argc, char **argv) setmode(STDIN_FILENO, O_BINARY); #endif - upscli_upslog_start_sync(upslog_start_sync(NULL)); - upscli_setprocname(xstrdup(getmyprocname())); + 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; @@ -1162,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 @@ -1172,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) { diff --git a/clients/upsstats.c b/clients/upsstats.c index 476647fe56..ab14107d51 100644 --- a/clients/upsstats.c +++ b/clients/upsstats.c @@ -35,7 +35,7 @@ /* 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_setproctag(x); } while(0) +#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" @@ -1707,8 +1707,8 @@ int main(int argc, char **argv) setmode(STDOUT_FILENO, O_BINARY); #endif - upscli_upslog_start_sync(upslog_start_sync(NULL)); - upscli_setprocname(xstrdup(getmyprocname())); + 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 @@ -1719,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()); } @@ -1730,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) { diff --git a/common/common.c b/common/common.c index 3199fc9dbd..f3c8c08e91 100644 --- a/common/common.c +++ b/common/common.c @@ -47,6 +47,16 @@ # include #endif +/* 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)) @@ -4678,7 +4688,8 @@ void setproctag(const char *tag) { size_t proctag_for_upsdebug_buflen = 0; - if (proctag_cleanup_registered < 2) { + 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 @@ -4694,7 +4705,8 @@ void setproctag(const char *tag) proctag_cleanup_registered = 2; } - if (proctag) { + if (proctag && proctag != tag) { + /* Take care to not free the caller's copy, or not too soon */ free(proctag); proctag = NULL; } @@ -4710,7 +4722,9 @@ void setproctag(const char *tag) } /* 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) @@ -4750,12 +4764,21 @@ void setproctag(const char *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) */ + } 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)); + } } void s_upsdebug_with_errno(int level, const char *fmt, ...) diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 3015ffe69f..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 \ @@ -588,7 +588,7 @@ SRC_DEV_PAGES = \ nutscan_add_option_to_device.txt \ nutscan_add_device_to_device.txt \ nutscan_get_serial_ports_list.txt \ - nutscan_set_debug_level.txt \ + nutscan_upslog_set_debug_level.txt \ nutscan_init.txt \ libupsclient-config.txt \ sockdebug.txt \ @@ -706,14 +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_setprocname.$(MAN_SECTION_API) \ - upscli_getproctag.$(MAN_SECTION_API) \ - upscli_setproctag.$(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) \ @@ -735,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) \ @@ -744,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) \ @@ -786,8 +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_set_debug_level.$(MAN_SECTION_API) \ - $(NUTSCAN_SET_DEBUG_LEVEL_DEPS) \ + 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: @@ -815,14 +816,15 @@ 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_SET_DEBUG_LEVEL_DEPS = \ - nutscan_get_debug_level.$(MAN_SECTION_API) \ +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_setprocname.$(MAN_SECTION_API) \ - nutscan_getproctag.$(MAN_SECTION_API) \ - nutscan_setproctag.$(MAN_SECTION_API) + nutscan_upslog_setprocname.$(MAN_SECTION_API) \ + nutscan_upslog_getproctag.$(MAN_SECTION_API) \ + nutscan_upslog_setproctag.$(MAN_SECTION_API) -$(NUTSCAN_SET_DEBUG_LEVEL_DEPS): nutscan_set_debug_level.$(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) @@ -865,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 \ @@ -907,7 +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_set_debug_level.html \ + nutscan_upslog_set_debug_level.html \ nutscan_init.html \ libupsclient-config.html \ sockdebug.html \ diff --git a/docs/man/nutscan_set_debug_level.txt b/docs/man/nutscan_upslog_set_debug_level.txt similarity index 59% rename from docs/man/nutscan_set_debug_level.txt rename to docs/man/nutscan_upslog_set_debug_level.txt index 8e28a9cd39..3f6c88b1a4 100644 --- a/docs/man/nutscan_set_debug_level.txt +++ b/docs/man/nutscan_upslog_set_debug_level.txt @@ -4,11 +4,12 @@ NUTSCAN_SET_DEBUG_LEVEL(3) NAME ---- -nutscan_set_debug_level, nutscan_get_debug_level, -nutscan_setprocname, nutscan_setproctag, nutscan_getproctag, +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 -debugging level and sub-process tags for NUT common -code in the nutscan library; +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 @@ -17,14 +18,16 @@ SYNOPSIS ------ #include - void nutscan_set_debug_level(int level); + 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); - void nutscan_setproctag(const char *tag); + 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); + struct timeval *nutscan_upslog_start_sync(struct timeval *tv, const void *cookie); ------ DESCRIPTION @@ -34,6 +37,12 @@ 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 diff --git a/docs/man/upscli_set_debug_level.txt b/docs/man/upscli_upslog_set_debug_level.txt similarity index 65% rename from docs/man/upscli_set_debug_level.txt rename to docs/man/upscli_upslog_set_debug_level.txt index cbd6a6374a..aef4ace4e0 100644 --- a/docs/man/upscli_set_debug_level.txt +++ b/docs/man/upscli_upslog_set_debug_level.txt @@ -1,13 +1,14 @@ -UPSCLI_SET_DEBUG_LEVEL(3) -========================= +UPSCLI_UPSLOG_SET_DEBUG_LEVEL(3) +================================ NAME ---- -upscli_set_debug_level, upscli_get_debug_level, -upscli_setprocname, upscli_setproctag, upscli_getproctag, +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 copies of the `nut_debug_level` +the possibly separate (identified via cookie) copies of the `nut_debug_level` and sub-process tag variables in the `libupsclient` build SYNOPSIS @@ -16,14 +17,16 @@ SYNOPSIS ------ #include - void upscli_set_debug_level(int); + 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); - void upscli_setproctag(const char *tag); + 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); + struct timeval *upscli_upslog_start_sync(struct timeval *tv, const void *cookie); ------ DESCRIPTION @@ -36,6 +39,12 @@ 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. diff --git a/docs/nut.dict b/docs/nut.dict index b2877f5d40..6d2471fe4f 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3709 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 diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index 64a0fac3a5..f42532ddf4 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -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,7 +419,7 @@ 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(); } @@ -428,13 +428,13 @@ void upsdrv_tweak_prognames(void) { /* 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)); - upscli_set_debug_level(nut_debug_level); - upscli_setprocname(xstrdup(getmyprocname())); + upscli_upslog_start_sync(upslog_start_sync(NULL), nut_common_cookie()); + upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); + upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_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? */ - upscli_setproctag(xstrdup(getproctag())); + upscli_upslog_setproctag(xstrdup(getproctag()), nut_common_cookie()); } void upsdrv_makevartable(void) @@ -447,7 +447,7 @@ 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) { diff --git a/include/common.h b/include/common.h index a521a5a7e1..d493ac7565 100644 --- a/include/common.h +++ b/include/common.h @@ -298,8 +298,15 @@ 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 */ +/* 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); diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index a22c29d9ca..ba28aa47a8 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -1205,11 +1205,12 @@ int main(int argc, char *argv[]) #endif /* NOTE: No hassle in this program about upslog_start_sync() - * nor nutscan_setprocname(xstrdup(getmyprocname())) + * nor nutscan_upslog_setprocname(xstrdup(getmyprocname())) * because its provider of those data and debugging methods - * is libnutscan itself. + * is libnutscan itself (so cookie is NULL as we do not even + * see NUT-common methods in the program source). */ - nutscan_setproctag(progname); + 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 @@ -1266,14 +1267,14 @@ int main(int argc, char *argv[]) } if (_nut_debug_level) { - nutscan_set_debug_level(_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_set_debug_level(_nut_debug_level); + 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); } @@ -1282,13 +1283,13 @@ int main(int argc, char *argv[]) /* A non-trivial _nut_debug_level set above allows * debug-tracing to troubleshoot these init methods: */ - nutscan_setproctag("init-ip-ranges"); + nutscan_upslog_setproctag("init-ip-ranges", NULL); nutscan_init_ip_ranges(&ip_ranges_list); - nutscan_setproctag("init-libnutscan"); + nutscan_upslog_setproctag("init-libnutscan", NULL); nutscan_init(); /* Re-set to cover libupsclient, if loaded: */ - nutscan_set_debug_level(_nut_debug_level); - nutscan_setproctag(progname); + 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; @@ -1758,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... */ - nutscan_setproctag("scanning"); + nutscan_upslog_setproctag("scanning", NULL); if (allow_usb && nutscan_avail_usb) { upsdebugx(quiet, "Scanning USB bus."); @@ -1768,7 +1769,7 @@ int main(int argc, char *argv[]) nutscan_avail_usb = 0; } #else - nutscan_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 */ @@ -1790,7 +1791,7 @@ int main(int argc, char *argv[]) nutscan_avail_snmp = 0; } #else - nutscan_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); @@ -1815,7 +1816,7 @@ int main(int argc, char *argv[]) nutscan_avail_xml_http = 0; } #else - nutscan_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); @@ -1838,7 +1839,7 @@ int main(int argc, char *argv[]) nutscan_avail_nut = 0; } #else - nutscan_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); @@ -1857,7 +1858,7 @@ int main(int argc, char *argv[]) nutscan_avail_nut_simulation = 0; } #else - nutscan_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); @@ -1875,7 +1876,7 @@ int main(int argc, char *argv[]) nutscan_avail_avahi = 0; } #else - nutscan_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); @@ -1898,7 +1899,7 @@ int main(int argc, char *argv[]) nutscan_avail_ipmi = 0; } #else - nutscan_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); @@ -1916,7 +1917,7 @@ int main(int argc, char *argv[]) nutscan_avail_upower = 0; } #else - nutscan_setproctag("upower"); + nutscan_upslog_setproctag("upower", NULL); upsdebugx(1, "UPOWER SCAN: no pthread support, starting nutscan_scan_upower..."); run_upower(NULL); #endif /* HAVE_PTHREAD */ @@ -1934,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 - nutscan_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); @@ -1982,7 +1983,7 @@ int main(int argc, char *argv[]) } #endif /* HAVE_PTHREAD */ - nutscan_setproctag("post-processing"); + nutscan_upslog_setproctag("post-processing", NULL); upsdebugx(1, "SCANS DONE: display results"); @@ -2031,7 +2032,7 @@ int main(int argc, char *argv[]) upsdebugx(1, "SCANS DONE: free resources: SERIAL"); nutscan_free_device(dev[TYPE_EATON_SERIAL]); - nutscan_setproctag("cleanup"); + nutscan_upslog_setproctag("cleanup", NULL); #ifdef HAVE_PTHREAD # ifdef HAVE_SEMAPHORE_UNNAMED sem_destroy(nutscan_semaphore()); @@ -2049,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) */ - nutscan_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 d410fbddd1..3714df3031 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -73,10 +73,10 @@ 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); -void nutscan_upscli_setprocname(const char *pn); -void nutscan_upscli_setproctag(const char *tag); -struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv); +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); @@ -156,14 +156,23 @@ void do_upsconf_args(char *confupsname, char *var, char *val) { } #endif /* WIN32 */ -void nutscan_set_debug_level(int level) { +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; - setproctag_lib_once("libnutscan"); + + 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); + nutscan_upscli_set_debug_level(level, cookie ? cookie : nutscan_upslog_cookie()); + } -int nutscan_get_debug_level(void) +int nutscan_upslog_get_debug_level(void) { return nut_debug_level; } @@ -171,33 +180,42 @@ int nutscan_get_debug_level(void) /* 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_setprocname(const char *pn) +void nutscan_upslog_setprocname(const char *pn, const void *cookie) { - setproctag_lib_once("libnutscan"); + 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); + nutscan_upscli_setprocname(pn, cookie ? cookie : nutscan_upslog_cookie()); } -void nutscan_setproctag(const char *tag) +void nutscan_upslog_setproctag(const char *tag, const void *cookie) { - setproctag_lib_once("libnutscan"); + 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); + nutscan_upscli_setproctag(tag, cookie ? cookie : nutscan_upslog_cookie()); } -const char *nutscan_getproctag(void) +const char *nutscan_upslog_getproctag(void) { return getproctag(); } -struct timeval *nutscan_upslog_start_sync(struct timeval *tv) +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); - setproctag_lib_once("libnutscan"); + + 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); + nutscan_upscli_upslog_start_sync(nstv, cookie ? cookie : nutscan_upslog_cookie()); + return nstv; } diff --git a/tools/nut-scanner/nutscan-init.h b/tools/nut-scanner/nutscan-init.h index cca5753200..359245d193 100644 --- a/tools/nut-scanner/nutscan-init.h +++ b/tools/nut-scanner/nutscan-init.h @@ -44,14 +44,28 @@ extern int nutscan_avail_upower; void nutscan_init(void); void nutscan_free(void); -void nutscan_set_debug_level(int level); -int nutscan_get_debug_level(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_setprocname(const char *pn); -void nutscan_setproctag(const char *tag); -const char *nutscan_getproctag(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); +struct timeval *nutscan_upslog_start_sync(struct timeval *tv, const void *cookie); #define DEFAULT_THREAD 512 diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index 61c84dd297..c22bdb7d9b 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -52,10 +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 void (*nut_upscli_setproctag)(const char *tag); -static void (*nut_upscli_setprocname)(const char *tag); -static struct timeval *(*nut_upscli_upslog_start_sync)(struct timeval *tv); +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, @@ -89,35 +89,35 @@ int nutscan_unload_upsclient_library(void) } /* Visible externally */ -void nutscan_upscli_set_debug_level(int level); -void nutscan_upscli_set_debug_level(int level) +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_set_debug_level) { - (*nut_upscli_set_debug_level)(level); + 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); -void nutscan_upscli_setprocname(const char *pn) +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_setprocname) { - (*nut_upscli_setprocname)(pn); + if (nutscan_avail_nut && dl_handle && nut_upscli_upslog_setprocname) { + (*nut_upscli_upslog_setprocname)(pn, cookie); } } -void nutscan_upscli_setproctag(const char *tag); -void nutscan_upscli_setproctag(const char *tag) +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_setproctag) { - (*nut_upscli_setproctag)(tag); + 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); -struct timeval *nutscan_upscli_upslog_start_sync(struct timeval *tv) +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); + return (*nut_upscli_upslog_start_sync)(tv, cookie); } /* So far return the nutscan library's copy */ @@ -206,40 +206,44 @@ int nutscan_load_upsclient_library(const char *libname_path) __func__, symbol); } else { /* Propagate value currently known in libnutscan into libupsclient */ - (*nut_upscli_upslog_start_sync)(upslog_start_sync(NULL)); + 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_setprocname) = lt_dlsym(dl_handle, - symbol = "upscli_setprocname"); + *(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_setprocname = 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 */ - (*nut_upscli_setprocname)(xstrdup(getmyprocname())); + 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_setproctag) = lt_dlsym(dl_handle, - symbol = "upscli_setproctag"); + *(void **) (&nut_upscli_upslog_setprocname) = lt_dlsym(dl_handle, + symbol = "upscli_upslog_setprocname"); if ((dl_error = lt_dlerror()) != NULL) { - nut_upscli_setproctag = 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 */ - (*nut_upscli_setproctag)(getproctag()); + upsdebugx(1, "%s: initializing %s() from libnutscan data (%s)", __func__, symbol, getmyprocname()); + (*nut_upscli_upslog_setprocname)(xstrdup(getmyprocname()), nut_common_cookie()); } - *(void **) (&nut_upscli_set_debug_level) = lt_dlsym(dl_handle, - symbol = "upscli_set_debug_level"); + *(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 { /* Propagate value currently known in libnutscan into libupsclient */ - (*nut_upscli_set_debug_level)(nut_debug_level); + 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 4b658e502a..75fae609a0 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -721,8 +721,8 @@ class NutScanner { /** Initialization */ InitFinal() { /* Register atexit() cleanups to scope around libnutscan lifetime */ - nutscan_upslog_start_sync(upslog_start_sync(nullptr)); - nutscan_setprocname(xstrdup(getmyprocname())); + nutscan_upslog_start_sync(upslog_start_sync(nullptr), nut_common_cookie()); + nutscan_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); nutscan_init(); } @@ -3161,8 +3161,8 @@ static int mainx(int argc, char * const argv[]) { * initial timestamp, for the eventuality that debugs would be printed: */ #if (defined WITH_NUTSCANNER) - nutscan_set_debug_level(nut_debug_level); - nutscan_setproctag(prog); + 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); @@ -3329,7 +3329,7 @@ static int mainx(int argc, char * const argv[]) { scanSerialDevices(options); } - nutscan_setproctag(prog); + nutscan_upslog_setproctag(prog, nut_common_cookie()); #endif // defined WITH_NUTSCANNER upsdebugx(1, "Finishing NUT configuration tool: %s", prog); From 11ecd948b5edb420efc9716371f211c0b3533886 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 30 Mar 2026 11:37:53 +0200 Subject: [PATCH 61/65] drivers/dummy-ups.c: diligently call upscli_cleanup() to minimize valgrind alerts [#3378, #3379] Signed-off-by: Jim Klimov --- drivers/dummy-ups.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index f42532ddf4..bcf8f1c6b7 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -607,6 +607,8 @@ void upsdrv_cleanup(void) free(ctx); ctx = NULL; } + + upscli_cleanup(); } static int setvar(const char *varname, const char *val) From 7e4b163ea37e852cdc14fe96de1e3c1a20470d0c Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 30 Mar 2026 11:50:58 +0200 Subject: [PATCH 62/65] drivers/dummy-ups.c: fix upsdrv_tweak_prognames() to only set upscli procname/proctag with duplicates if cookies differ [#3378, #3379] Signed-off-by: Jim Klimov --- drivers/dummy-ups.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index bcf8f1c6b7..057fcbd34a 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -426,15 +426,21 @@ void upsdrv_help(void) /* 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), nut_common_cookie()); - upscli_upslog_set_debug_level(nut_debug_level, nut_common_cookie()); - upscli_upslog_setprocname(xstrdup(getmyprocname()), nut_common_cookie()); + 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? */ - upscli_upslog_setproctag(xstrdup(getproctag()), nut_common_cookie()); + if (cookie != upscli_upslog_cookie()) { + /* Send over a copy */ + upscli_upslog_setprocname(xstrdup(getmyprocname()), cookie); + upscli_upslog_setproctag(xstrdup(getproctag()), cookie); + } } void upsdrv_makevartable(void) From bb1e5a47d6d66cc9cf83a326e585e083bfeadb09 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 30 Mar 2026 12:54:35 +0200 Subject: [PATCH 63/65] drivers/main.{c,h}, drivers/dummy-ups.c: introduce *upsdrv_callback_setproctag and upsdrv_setproctag() [#3379] Signed-off-by: Jim Klimov --- drivers/dummy-ups.c | 10 ++++++++++ drivers/main.c | 27 +++++++++++++++++++++++---- drivers/main.h | 4 ++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index 057fcbd34a..ab359d987c 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -423,6 +423,13 @@ void upsdrv_help(void) 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) { @@ -440,6 +447,8 @@ void upsdrv_tweak_prognames(void) /* Send over a copy */ upscli_upslog_setprocname(xstrdup(getmyprocname()), cookie); upscli_upslog_setproctag(xstrdup(getproctag()), cookie); + + upsdrv_callback_setproctag = dummy_setproctag_callback; } } @@ -615,6 +624,7 @@ void upsdrv_cleanup(void) } 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 9e844c8309..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 @@ -1957,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"); @@ -2002,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]; @@ -2012,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 */ @@ -2273,7 +2292,7 @@ int main(int argc, char **argv) * 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"); - setproctag(prognames[0]); + upsdrv_setproctag(prognames[0]); upsdrv_callbacks.upsdrv_tweak_prognames(); @@ -2499,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) { 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). From 32510ab28881cb9eb295f321c564c298be49c03d Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 30 Mar 2026 20:24:45 +0200 Subject: [PATCH 64/65] tools/nutconf/nutconf-cli.cpp: fix NULL=>nullptr [#3379] Signed-off-by: Jim Klimov --- tools/nutconf/nutconf-cli.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index 75fae609a0..d4955811e4 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -3140,7 +3140,7 @@ static void scanSerialDevices(const NutConfOptions & options) { * \return 0 always (exits on error) */ static int mainx(int argc, char * const argv[]) { - const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : NULL, "nutconf"); + const char *prog = getprogname_argv0_default(argc > 0 ? argv[0] : nullptr, "nutconf"); char *s = nullptr; // Get options, also set nut_debug_level @@ -3347,7 +3347,7 @@ static int mainx(int argc, char * const argv[]) { */ int main(int argc, char * const argv[]) { try { - upslog_start_sync(NULL); + upslog_start_sync(nullptr); return mainx(argc, argv); } catch (const std::exception & e) { From 989ead92f7139fc5b97fb903dd42a19d7a88c360 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 31 Mar 2026 02:09:55 +0200 Subject: [PATCH 65/65] common/common.c: xbasename_no_ext(): use ubiquitous strcasecmp() for "nut.exe" [#3379] Signed-off-by: Jim Klimov --- common/common.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/common/common.c b/common/common.c index f3c8c08e91..6ebb5c0b27 100644 --- a/common/common.c +++ b/common/common.c @@ -2827,17 +2827,12 @@ char *xbasename_no_ext(const char *file) if (!cs || !*cs) return NULL; - /* TOTHINK: Generalize provided-if-missing strcasestr()? - * One implementation is currently tucked away in - * libusb0.c because net-snmp may provide another... - */ - #ifdef WIN32 /* Special handling for a known outlier (for man pages, etc.) */ - if (!strcmp(cs, "nut.exe")) + if (!strcasecmp(cs, "nut.exe")) return xstrdup(cs); - if (!strcmp(cs, "nut")) + if (!strcasecmp(cs, "nut")) return xstrdup("nut.exe"); #endif @@ -2870,6 +2865,10 @@ char *xbasename_no_ext(const char *file) 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,