diff --git a/NEWS.adoc b/NEWS.adoc index 7c30176bef..4ea9ab8e87 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -60,6 +60,8 @@ https://github.com/networkupstools/nut/milestone/11 sockets and a tight polling loop that hogged CPU. While the underlying bug is ancient, it took recent development to hit it as a practical regression. [issue #1904, issue #2484] + * Fallback `localtime_r()` and `gmtime_r()` for some platform builds where + a `*_s()` variant was available was not handled correctly. [PR #2583] - development iterations of NUT should now identify with not only the semantic version of a preceding release, but with git-derived information about the @@ -93,7 +95,7 @@ https://github.com/networkupstools/nut/milestone/11 - A new `NUT_QUIET_INIT_BANNER` envvar (presence or "true" value) can now prevent the tool name and NUT version banner from being unilaterally - printed out when NUT programs start. [issues #1789 vs. #316] + printed out when NUT programs start. [issues #1789 vs. #316; #2573] - Extended `upsdrvctl` with a `list` operation (or `-l` option) to report manageable device configuration names (possible `` arguments to diff --git a/clients/upsc.c b/clients/upsc.c index 80c9bd6591..d99d18085a 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -36,15 +36,14 @@ static UPSCONN_t *ups = NULL; static void usage(const char *prog) { - printf("Network UPS Tools upsc %s\n\n", UPS_VERSION); + print_banner_once(prog, 2); + printf("NUT read-only client program to display UPS variables.\n"); - printf("usage: %s -l | -L [[:port]]\n", prog); + printf("\nusage: %s -l | -L [[:port]]\n", prog); printf(" %s []\n", prog); printf(" %s -c \n", prog); - printf("\nDemo program to display UPS variables.\n\n"); - - printf("First form (lists UPSes):\n"); + printf("\nFirst form (lists UPSes):\n"); printf(" -l - lists each UPS on , one per line.\n"); printf(" -L - lists each UPS followed by its description (from ups.conf).\n"); printf(" Default hostname: localhost\n"); @@ -251,12 +250,11 @@ int main(int argc, char **argv) break; case 'V': + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); - - fatalx(EXIT_SUCCESS, "Network UPS Tools upsc %s", UPS_VERSION); -#ifndef HAVE___ATTRIBUTE__NORETURN - exit(EXIT_SUCCESS); /* Should not get here in practice, but compiler is afraid we can fall through */ -#endif + exit(EXIT_SUCCESS); case 'h': default: diff --git a/clients/upscmd.c b/clients/upscmd.c index 4d71ebce2f..868009bf53 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -47,11 +47,12 @@ struct list_t { static void usage(const char *prog) { - printf("Network UPS Tools upscmd %s\n\n", UPS_VERSION); - printf("usage: %s [-h]\n", prog); + print_banner_once(prog, 2); + printf("NUT administration client program to initiate instant commands on UPS hardware.\n"); + + printf("\nusage: %s [-h]\n", prog); printf(" %s [-l ]\n", prog); printf(" %s [-u ] [-p ] [-w] [-t ] []\n\n", prog); - printf("Administration program to initiate instant commands on UPS hardware.\n"); printf("\n"); printf(" -h display this help text\n"); printf(" -V display the version of this software\n"); @@ -312,12 +313,11 @@ int main(int argc, char **argv) break; case 'V': + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); - - fatalx(EXIT_SUCCESS, "Network UPS Tools upscmd %s", UPS_VERSION); -#ifndef HAVE___ATTRIBUTE__NORETURN - exit(EXIT_SUCCESS); /* Should not get here in practice, but compiler is afraid we can fall through */ -#endif + exit(EXIT_SUCCESS); case 'h': default: diff --git a/clients/upslog.c b/clients/upslog.c index ded8d79e86..9f51539de2 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -144,7 +144,8 @@ static void help(const char *prog) static void help(const char *prog) { - printf("UPS status logger.\n"); + print_banner_once(prog, 2); + printf("NUT read-only client program - UPS status logger.\n"); printf("\nusage: %s [OPTIONS]\n", prog); printf("\n"); @@ -439,10 +440,7 @@ int main(int argc, char **argv) logformat = DEFAULT_LOGFORMAT; user = RUN_AS_USER; - if (!banner_is_disabled()) { - printf("Network UPS Tools %s %s\n", prog, UPS_VERSION); - fflush(stdout); - } + print_banner_once(prog, 0); while ((i = getopt(argc, argv, "+hs:l:i:f:u:Vp:FBm:")) != -1) { switch(i) { @@ -508,6 +506,9 @@ int main(int argc, char **argv) break; case 'V': + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); exit(EXIT_SUCCESS); diff --git a/clients/upsmon.c b/clients/upsmon.c index 79ccb83e32..40d320b347 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -2627,9 +2627,10 @@ static void help(const char *arg_progname) /* printf("\n"); */ } - printf("Monitors UPS servers and may initiate shutdown if necessary.\n\n"); + print_banner_once(arg_progname, 2); + printf("NUT client which monitors UPS servers and may initiate shutdown if necessary.\n"); - printf("usage: %s [OPTIONS]\n\n", arg_progname); + printf("\nusage: %s [OPTIONS]\n\n", arg_progname); printf(" -c send command to running process\n"); printf(" commands:\n"); printf(" - fsd: shutdown all primary-mode UPSes (use with caution)\n"); @@ -2908,10 +2909,7 @@ int main(int argc, char *argv[]) } #endif - if (!banner_is_disabled()) { - printf("Network UPS Tools %s %s\n", prog, UPS_VERSION); - fflush(stdout); - } + print_banner_once(prog, 0); /* if no configuration file is specified on the command line, use default */ configfile = xmalloc(SMALLBUF); @@ -2973,7 +2971,9 @@ int main(int argc, char *argv[]) run_as_user = xstrdup(optarg); break; case 'V': - /* just show the optional CONFIG_FLAGS banner */ + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); exit(EXIT_SUCCESS); case '4': diff --git a/clients/upsrw.c b/clients/upsrw.c index 2086d6de43..710d39d51e 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -47,10 +47,11 @@ struct list_t { static void usage(const char *prog) { - printf("Network UPS Tools %s %s\n\n", prog, UPS_VERSION); - printf("usage: %s [-h]\n", prog); + print_banner_once(prog, 2); + printf("NUT administration client program to set variables within UPS hardware.\n"); + + printf("\nusage: %s [-h]\n", prog); printf(" %s [-s ] [-u ] [-p ] [-w] [-t ] \n\n", prog); - printf("Demo program to set variables within UPS hardware.\n"); printf("\n"); printf(" -h display this help text\n"); printf(" -V display the version of this software\n"); @@ -668,7 +669,9 @@ int main(int argc, char **argv) tracking_enabled = 1; break; case 'V': - printf("Network UPS Tools %s %s\n", prog, UPS_VERSION); + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); exit(EXIT_SUCCESS); case 'h': diff --git a/common/common.c b/common/common.c index 47ae99f620..6b62d66736 100644 --- a/common/common.c +++ b/common/common.c @@ -209,6 +209,72 @@ int banner_is_disabled(void) return value; } +const char *describe_NUT_VERSION_once(void) +{ + static char buf[LARGEBUF]; + static const char *printed = NULL; + + if (printed) + return printed; + + memset(buf, 0, sizeof(buf)); + +#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 + /* NOTE: Some compilers deduce that macro-based decisions about + * NUT_VERSION_IS_RELEASE make one of codepaths unreachable in + * a particular build. So we pragmatically handwave this away. + */ + if (1 < snprintf(buf, sizeof(buf), + "%s %s%s%s", + NUT_VERSION_MACRO, + NUT_VERSION_IS_RELEASE ? "release" : "(development iteration after ", + NUT_VERSION_IS_RELEASE ? "" : NUT_VERSION_SEMVER_MACRO, + NUT_VERSION_IS_RELEASE ? "" : ")" + )) { + printed = buf; + } else { + upslogx(LOG_WARNING, "%s: failed to report detailed NUT version", __func__); + printed = UPS_VERSION; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic pop +#endif + + return printed; +} + +int print_banner_once(const char *prog, int even_if_disabled) +{ + static int printed = 0; + static int ret = -1; + + if (printed) + return ret; + + if (!banner_is_disabled() || even_if_disabled) { + ret = printf("Network UPS Tools %s %s%s\n", + prog, describe_NUT_VERSION_once(), + even_if_disabled == 2 ? "\n" : ""); + fflush(stdout); + if (ret > 0) + printed = 1; + } + + return ret; +} + /* enable writing upslog_with_errno() and upslogx() type messages to the syslog */ void syslogbit_set(void) @@ -2195,28 +2261,11 @@ void nut_report_config_flags(void) now.tv_sec -= 1; } -#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 - /* NOTE: Some compilers deduce that macro-based decisions about - * NUT_VERSION_IS_RELEASE make one of codepaths unreachable in - * a particular build. So we pragmatically handwave this away. - */ if (xbit_test(upslog_flags, UPSLOG_STDERR)) { - fprintf(stderr, "%4.0f.%06ld\t[D1] Network UPS Tools version %s%s%s%s%s%s%s %s%s\n", + fprintf(stderr, "%4.0f.%06ld\t[D1] Network UPS Tools version %s%s%s%s %s%s\n", difftime(now.tv_sec, upslog_start.tv_sec), (long)(now.tv_usec - upslog_start.tv_usec), - UPS_VERSION, - NUT_VERSION_IS_RELEASE ? " release" : " (development iteration after ", - NUT_VERSION_IS_RELEASE ? "" : NUT_VERSION_SEMVER_MACRO, - NUT_VERSION_IS_RELEASE ? "" : ")", + describe_NUT_VERSION_once(), (compiler_ver && *compiler_ver != '\0' ? " built with " : ""), (compiler_ver && *compiler_ver != '\0' ? compiler_ver : ""), (compiler_ver && *compiler_ver != '\0' ? " and" : ""), @@ -2231,11 +2280,8 @@ void nut_report_config_flags(void) /* NOTE: May be ignored or truncated by receiver if that syslog server * (and/or OS sender) does not accept messages of such length */ if (xbit_test(upslog_flags, UPSLOG_SYSLOG)) { - syslog(LOG_DEBUG, "Network UPS Tools version %s%s%s%s%s%s%s %s%s", - UPS_VERSION, - NUT_VERSION_IS_RELEASE ? " release" : " (development iteration after ", - NUT_VERSION_IS_RELEASE ? "" : NUT_VERSION_SEMVER_MACRO, - NUT_VERSION_IS_RELEASE ? "" : ")", + syslog(LOG_DEBUG, "Network UPS Tools version %s%s%s%s %s%s", + describe_NUT_VERSION_once(), (compiler_ver && *compiler_ver != '\0' ? " built with " : ""), (compiler_ver && *compiler_ver != '\0' ? compiler_ver : ""), (compiler_ver && *compiler_ver != '\0' ? " and" : ""), @@ -2243,12 +2289,6 @@ void nut_report_config_flags(void) (config_flags && *config_flags != '\0' ? config_flags : "") ); } -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -#pragma GCC diagnostic pop -#endif } static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) diff --git a/configure.ac b/configure.ac index 88876d323e..13cf303e59 100644 --- a/configure.ac +++ b/configure.ac @@ -752,6 +752,19 @@ AS_IF([test "$ac_cv_header_sys_time_h" = yes], and . This macro is deemed obsolete by autotools.]) ], []) +CODE_TIMEINCL=" +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +" + AC_CHECK_HEADERS_ONCE([fcntl.h sys/stat.h sys/socket.h netdb.h]) AC_CHECK_FUNCS(flock lockf fcvt fcvtl dup dup2 abs_val abs) @@ -920,34 +933,38 @@ dnl These appear with CFLAGS="-std=c99 -D_POSIX_C_SOURCE=200112L": dnl# Methods below currently do not cause build errors for C99+ modes so are dnl# not tested as thoroughly as those further below: AC_CHECK_FUNCS(strtof strtok_r fileno sigemptyset sigaction, - [], [AC_MSG_WARN([Required C library routine not found; try adding -D_POSIX_C_SOURCE=200112L])]) + [], [AC_MSG_WARN([Required C library routine not found by linker; try adding -D_POSIX_C_SOURCE=200112L])]) dnl For these we have a fallback implementation via the other, dnl if at least one is available, so initial check is quiet. dnl This typically pops up in POSIX vs. Windows builds: -AC_CHECK_FUNCS(localtime_r localtime_s gmtime_r gmtime_s timegm _mkgmtime, - [], []) +dnl Reminder: the former checks for declarations in headers, +dnl the latter checks if known libraries suffice for linker. +dnl Might need AC_CHECK_LIBS as well to populate the list with +dnl known variants? +AC_CHECK_DECLS([localtime_r, localtime_s, gmtime_r, gmtime_s, timegm, _mkgmtime], [], [], [$CODE_TIMEINCL]) +AC_CHECK_FUNCS(localtime_r localtime_s gmtime_r gmtime_s timegm _mkgmtime, [], []) AC_MSG_CHECKING([for at least one gmtime implementation]) -AS_IF([test x"${ac_cv_func_gmtime_s}-${ac_cv_func_gmtime_r}" = "xno-no"], [ +AS_IF([test x"${ac_cv_func_gmtime_s}-${ac_cv_func_gmtime_r}" = "xno-no" && test x"${ac_cv_have_decl_gmtime_s}-${ac_cv_have_decl_gmtime_r}" = "xno-no"], [ AC_MSG_RESULT([no]) - AC_MSG_WARN([Required C library routine gmtime_s nor gmtime_r was not found; try adding -D_POSIX_C_SOURCE=200112L]) + AC_MSG_WARN([Required C library routine gmtime_s nor gmtime_r was not found by linker nor in headers; try adding -D_POSIX_C_SOURCE=200112L and/or -D_POSIX_THREAD_SAFE_FUNCTIONS=200112L]) ],[ AC_MSG_RESULT([yes]) ]) AC_MSG_CHECKING([for at least one localtime implementation]) -AS_IF([test x"${ac_cv_func_localtime_s}-${ac_cv_func_localtime_r}" = "xno-no"], [ +AS_IF([test x"${ac_cv_func_localtime_s}-${ac_cv_func_localtime_r}" = "xno-no" && test x"${ac_cv_have_decl_localtime_s}-${ac_cv_have_decl_localtime_r}" = "xno-no"], [ AC_MSG_RESULT([no]) - AC_MSG_WARN([Required C library routine localtime_s nor localtime_r was not found; try adding -D_POSIX_C_SOURCE=200112L]) + AC_MSG_WARN([Required C library routine localtime_s nor localtime_r was not found by linker nor in headers; try adding -D_POSIX_C_SOURCE=200112L and/or -D_POSIX_THREAD_SAFE_FUNCTIONS=200112L]) ],[ AC_MSG_RESULT([yes]) ]) AC_MSG_CHECKING([for at least one timegm implementation]) -AS_IF([test x"${ac_cv_func_timegm}-${ac_cv_func__mkgmtime}" = "xno-no"], [ +AS_IF([test x"${ac_cv_func_timegm}-${ac_cv_func__mkgmtime}" = "xno-no" && test x"${ac_cv_have_decl_timegm}-${ac_cv_have_decl__mkgmtime}" = "xno-no"], [ AC_MSG_RESULT([no]) - AC_MSG_WARN([Required C library routine timegm nor _mkgmtime was not found]) + AC_MSG_WARN([Required C library routine timegm nor _mkgmtime was not found by linker nor in headers]) AC_DEFINE_UNQUOTED([WANT_TIMEGM_FALLBACK], [1], [Defined if we want to use timegm_fallback()]) AM_CONDITIONAL([WANT_TIMEGM_FALLBACK], [true]) ],[ @@ -1053,16 +1070,7 @@ AC_CACHE_CHECK([for strptime(s1,s2,tm)], [ac_cv_func_strptime], [AX_RUN_OR_LINK_IFELSE( [AC_LANG_PROGRAM([$CODE_STRINGINCL -#ifdef TIME_WITH_SYS_TIME -# include -# include -#else -# ifdef HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif +$CODE_TIMEINCL ], [struct tm tm; char *date = "12/30/1999"; @@ -1082,16 +1090,7 @@ AC_CACHE_CHECK([for clock_gettime(CLOCK_MONOTONIC,ts)], [ac_cv_func_clock_gettime], [AX_RUN_OR_LINK_IFELSE( [AC_LANG_PROGRAM([$CODE_STRINGINCL -#ifdef TIME_WITH_SYS_TIME -# include -# include -#else -# ifdef HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif +$CODE_TIMEINCL ], [struct timespec monoclock_ts; int got_monoclock = clock_gettime(CLOCK_MONOTONIC, &monoclock_ts); diff --git a/docs/nut.dict b/docs/nut.dict index 3c5b682485..82e572a0f2 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3203 utf-8 +personal_ws-1.1 en 3204 utf-8 AAC AAS ABI @@ -1959,6 +1959,7 @@ gitk gitlab gmail gmake +gmtime gnuplot gnutls google diff --git a/drivers/apc_modbus.c b/drivers/apc_modbus.c index 8aafce1479..b56faf2498 100644 --- a/drivers/apc_modbus.c +++ b/drivers/apc_modbus.c @@ -17,11 +17,13 @@ */ #include "main.h" + #if defined NUT_MODBUS_HAS_USB -#include "nut_libusb.h" -#include "libhid.h" -#include "hidparser.h" +# include "nut_libusb.h" +# include "libhid.h" +# include "hidparser.h" #endif /* defined NUT_MODBUS_HAS_USB */ + #include "timehead.h" #include "nut_stdint.h" #include "apc_modbus.h" @@ -777,9 +779,9 @@ static int _apc_modbus_date_from_nut(const char *value, uint16_t *output, size_t } memset(&tm_struct, 0, sizeof(tm_struct)); - if (strptime(value, "%Y-%m-%d", &tm_struct) == NULL) { - return 0; - } + if (strptime(value, "%Y-%m-%d", &tm_struct) == NULL) { + return 0; + } if ((epoch_time = timegm(&tm_struct)) == -1) { return 0; diff --git a/drivers/huawei-ups2000.c b/drivers/huawei-ups2000.c index ce9cb2bbae..1c74eaff8d 100644 --- a/drivers/huawei-ups2000.c +++ b/drivers/huawei-ups2000.c @@ -51,7 +51,7 @@ #include "timehead.h" /* fallback gmtime_r() variants if needed (e.g. some WIN32) */ #define DRIVER_NAME "NUT Huawei UPS2000 (1kVA-3kVA) RS-232 Modbus driver" -#define DRIVER_VERSION "0.06" +#define DRIVER_VERSION "0.07" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 1 @@ -1858,7 +1858,7 @@ static time_t time_seek(time_t t, int seconds) if (!t) fatalx(EXIT_FAILURE, "time_seek() failed!"); - if (!gmtime_r(&t, &time_tm)) + if (gmtime_r(&t, &time_tm) == NULL) fatalx(EXIT_FAILURE, "time_seek() failed!"); time_tm.tm_sec += seconds; diff --git a/drivers/main.c b/drivers/main.c index 9a254784f6..7cd41b086a 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -139,7 +139,9 @@ void upsdrv_banner (void) { int i; - printf("Network UPS Tools - %s %s (%s)\n", upsdrv_info.name, upsdrv_info.version, UPS_VERSION); + printf("Network UPS Tools driver %s - %s %s\n", + describe_NUT_VERSION_once(), + upsdrv_info.name, upsdrv_info.version); /* process sub driver(s) information */ for (i = 0; upsdrv_info.subdrv_info[i]; i++) { @@ -180,6 +182,11 @@ static void help_msg(void) { vartab_t *tmp; + if (banner_is_disabled()) { + /* Was not printed at start of main() */ + upsdrv_banner(); + } + nut_report_config_flags(); printf("\nusage: %s (-a |-s ) [OPTIONS]\n", progname); @@ -1934,7 +1941,20 @@ int main(int argc, char **argv) group_from_cmdline = 1; break; case 'V': - /* already printed the banner for program name */ + /* Avoid the verbose message about + * driver daemon state integration + * with a service management framework + * like systemd, as not too relevant + * to program version reporting here + * (only seen with non-zero debug) */ + setenv("NUT_QUIET_INIT_UPSNOTIFY", "yes", 0); + + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + if (banner_is_disabled()) { + /* Was not printed at start of main() */ + upsdrv_banner(); + } nut_report_config_flags(); exit(EXIT_SUCCESS); case 'x': diff --git a/drivers/upsdrvctl.c b/drivers/upsdrvctl.c index f7c48d94d1..f6babb0577 100644 --- a/drivers/upsdrvctl.c +++ b/drivers/upsdrvctl.c @@ -1172,7 +1172,9 @@ static void help(const char *progname) static void help(const char *arg_progname) { - printf("Starts and stops UPS drivers via ups.conf.\n\n"); + print_banner_once(arg_progname, 2); + printf("UPS driver controller: Starts and stops UPS drivers via ups.conf.\n\n"); + printf("usage: %s [OPTIONS] (start | stop | shutdown | status) []\n\n", arg_progname); printf("usage: %s [OPTIONS] (list | -l) []\n\n", arg_progname); printf("usage: %s [OPTIONS] -c []\n\n", arg_progname); @@ -1406,15 +1408,14 @@ static void exit_cleanup(void) int main(int argc, char **argv) { int i, lastarg = 0; - char *prog, *command_name = NULL; - - if (!banner_is_disabled()) { - printf("Network UPS Tools - UPS driver controller %s\n", - UPS_VERSION); - fflush(stdout); - } + char *prog, *command_name = NULL, progdesc[LARGEBUF]; prog = argv[0]; + + /* Historically special banner*/ + snprintf(progdesc, sizeof(progdesc), "%s - UPS driver controller", xbasename(prog)); + print_banner_once(progdesc, 0); + while ((i = getopt(argc, argv, "+htu:r:DdFBVc:l")) != -1) { switch(i) { case 'r': @@ -1430,6 +1431,10 @@ int main(int argc, char **argv) break; case 'V': + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(progdesc, 1); + nut_report_config_flags(); exit(EXIT_SUCCESS); case 'D': @@ -1519,6 +1524,7 @@ int main(int argc, char **argv) break; case 'h': default: + /* not progdesc, shows details of its own */ help(prog); } } diff --git a/include/common.h b/include/common.h index 07d374d05d..5589952793 100644 --- a/include/common.h +++ b/include/common.h @@ -200,10 +200,30 @@ extern const char *UPS_VERSION; /** @brief Default timeout (in seconds) for retrieving the result of a `TRACKING`-enabled operation (e.g. `INSTCMD`, `SET VAR`). */ #define DEFAULT_TRACKING_TIMEOUT 10 +/* Returns a pointer to static internal char[] buffer with current value + * of NUT_VERSION_MACRO (aka char* UPS_VERSION) and its layman description + * (e.g. a "release" or "development iteration after" a certain semantically + * versioned release). Returns UPS_VERSION if failed to construct a better + * description. Either way, should not be free()'d by caller and does not + * have an end-of-line char of its own. */ +const char *describe_NUT_VERSION_once(void); + /* Based on NUT_QUIET_INIT_BANNER envvar (present and empty or "true") * hide the NUT tool name+version banners; show them by default */ int banner_is_disabled(void); +/* Some NUT programs have historically printed their banner at start-up + * always, and so did not print one in help()/usage() or handling `-V` + * like others did. Now that we have NUT_QUIET_INIT_BANNER, we need a + * way to print that banner (regardless of the flag in some cases). + * The "even_if_disabled" should be 0 for initial banner of those + * programs (so the envvar would hide it), 1 -V case and 2 in -h case + * (for a blank line after). As before, the banner is printed to stdout. + * Returns the result of printf() involved. Remembers to not print again + * if the earlier printf() was successful. + */ +int print_banner_once(const char *prog, int even_if_disabled); + /* Normally we can (attempt to) use the syslog or Event Log (WIN32), * but environment variable NUT_DEBUG_SYSLOG allows to bypass it, and * perhaps keep daemons logging to stderr (e.g. in NUT Integration Test diff --git a/include/timehead.h b/include/timehead.h index de72d9b8fe..0eeea4bf9b 100644 --- a/include/timehead.h +++ b/include/timehead.h @@ -44,10 +44,12 @@ extern "C" { char * strptime(const char *buf, const char *fmt, struct tm *tm); #endif -#ifndef HAVE_LOCALTIME_R -# ifdef HAVE_LOCALTIME_S -/* A bit of a silly trick, but should help on MSYS2 builds it seems */ -# define localtime_r(timer, buf) localtime_s(timer, buf) +#if !(defined HAVE_LOCALTIME_R && HAVE_LOCALTIME_R) && !(defined HAVE_DECL_LOCALTIME_R && HAVE_DECL_LOCALTIME_R) +# if (defined HAVE_LOCALTIME_S && HAVE_LOCALTIME_S) || (defined HAVE_DECL_LOCALTIME_S && HAVE_DECL_LOCALTIME_S) +/* A bit of a silly trick, but should help on MSYS2 builds it seems + * errno_t localtime_s(struct tm *_Tm, const time_t *_Time) + */ +# define localtime_r(timer, buf) (localtime_s(buf, timer) ? NULL : buf) # else # include /* memcpy */ static inline struct tm *localtime_r( const time_t *timer, struct tm *buf ) { @@ -59,9 +61,10 @@ static inline struct tm *localtime_r( const time_t *timer, struct tm *buf ) { # endif #endif -#ifndef HAVE_GMTIME_R -# ifdef HAVE_GMTIME_S -# define gmtime_r(timer, buf) gmtime_s(timer, buf) +#if !(defined HAVE_GMTIME_R && HAVE_GMTIME_R) && !(defined HAVE_DECL_GMTIME_R && HAVE_DECL_GMTIME_R) +# if (defined HAVE_GMTIME_S && HAVE_GMTIME_S) || (defined HAVE_DECL_GMTIME_S && HAVE_DECL_GMTIME_S) +/* See comment above */ +# define gmtime_r(timer, buf) (gmtime_s(buf, timer) ? NULL : buf) # else # include /* memcpy */ static inline struct tm *gmtime_r( const time_t *timer, struct tm *buf ) { @@ -73,8 +76,8 @@ static inline struct tm *gmtime_r( const time_t *timer, struct tm *buf ) { # endif #endif -#ifndef HAVE_TIMEGM -# ifdef HAVE__MKGMTIME +#if !(defined HAVE_TIMEGM && HAVE_TIMEGM) && !(defined HAVE_DECL_TIMEGM && HAVE_DECL_TIMEGM) +# if (defined HAVE__MKGMTIME && HAVE__MKGMTIME) || (defined HAVE_DECL__MKGMTIME && HAVE_DECL__MKGMTIME) # define timegm(tm) _mkgmtime(tm) # else # ifdef WANT_TIMEGM_FALLBACK diff --git a/scripts/Windows/build-mingw-nut.sh b/scripts/Windows/build-mingw-nut.sh index 77ba08e2ad..e36c14de3f 100755 --- a/scripts/Windows/build-mingw-nut.sh +++ b/scripts/Windows/build-mingw-nut.sh @@ -105,8 +105,11 @@ if [ "$cmd" == "all64" ] || [ "$cmd" == "b64" ] || [ "$cmd" == "all32" ] || [ "$ # Note: _WIN32_WINNT>=0x0600 is needed for inet_ntop in mingw headers # and the value 0xffff is anyway forced into some components at least # by netsnmp cflags. - export CFLAGS+=" -D_POSIX=1 -D_POSIX_C_SOURCE=200112L -I${ARCH_PREFIX}/include/ -D_WIN32_WINNT=0xffff" - export CXXFLAGS+=" -D_POSIX=1 -D_POSIX_C_SOURCE=200112L -I${ARCH_PREFIX}/include/ -D_WIN32_WINNT=0xffff" + # _POSIX_THREAD_SAFE_FUNCTIONS whould help with localtime_r() gmtime_r() + # on recent mingw releases (as of 2019), per + # https://stackoverflow.com/questions/18551409/localtime-r-support-on-mingw + export CFLAGS+=" -D_POSIX=1 -D_POSIX_C_SOURCE=200112L -D_POSIX_THREAD_SAFE_FUNCTIONS=200112L -I${ARCH_PREFIX}/include/ -D_WIN32_WINNT=0xffff" + export CXXFLAGS+=" -D_POSIX=1 -D_POSIX_C_SOURCE=200112L -D_POSIX_THREAD_SAFE_FUNCTIONS=200112L -I${ARCH_PREFIX}/include/ -D_WIN32_WINNT=0xffff" export LDFLAGS+=" -L${ARCH_PREFIX}/lib/" KEEP_NUT_REPORT_FEATURE_FLAG="" diff --git a/scripts/Windows/wininit.c b/scripts/Windows/wininit.c index abb6643090..527a87e831 100644 --- a/scripts/Windows/wininit.c +++ b/scripts/Windows/wininit.c @@ -702,7 +702,7 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) static void help(const char *arg_progname) { - printf("Network UPS Tools %s %s\n\n", arg_progname, UPS_VERSION); + print_banner_once(arg_progname, 2); printf("NUT for Windows all-in-one wrapper for driver(s), data server and monitoring client\n"); printf("including shutdown and power-off handling (where supported). All together they rely\n"); @@ -778,8 +778,9 @@ int main(int argc, char **argv) nut_debug_level++; break; case 'V': - /* also show the optional CONFIG_FLAGS banner if available */ - printf("Network UPS Tools %s %s\n", progname, UPS_VERSION); + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(progname, 1); nut_report_config_flags(); return EXIT_SUCCESS; case 'h': diff --git a/server/upsd.c b/server/upsd.c index 1573bd7a9a..5fd4e4be51 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -1811,9 +1811,10 @@ static void help(const char *arg_progname) static void help(const char *arg_progname) { - printf("Network server for UPS data.\n\n"); - printf("usage: %s [OPTIONS]\n", arg_progname); + print_banner_once(arg_progname, 2); + printf("NUT network data server for UPS monitoring and management.\n"); + printf("\nusage: %s [OPTIONS]\n", arg_progname); printf("\n"); printf(" -c send via signal to background process\n"); printf(" commands:\n"); @@ -1935,10 +1936,7 @@ int main(int argc, char **argv) /* set up some things for later */ snprintf(pidfn, sizeof(pidfn), "%s/%s.pid", altpidpath(), progname); - if (!banner_is_disabled()) { - printf("Network UPS Tools %s %s\n", progname, UPS_VERSION); - fflush(stdout); - } + print_banner_once(progname, 0); while ((i = getopt(argc, argv, "+h46p:qr:i:fu:Vc:P:DFB")) != -1) { switch (i) { @@ -1964,9 +1962,10 @@ int main(int argc, char **argv) break; case 'V': - /* Note - we already printed the banner for program name */ + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(progname, 1); nut_report_config_flags(); - exit(EXIT_SUCCESS); case 'c': diff --git a/tools/nut-scanner/Makefile.am b/tools/nut-scanner/Makefile.am index 78bab5633d..0aa53d8342 100644 --- a/tools/nut-scanner/Makefile.am +++ b/tools/nut-scanner/Makefile.am @@ -100,7 +100,7 @@ libnutscan_la_LDFLAGS += -version-info 2:6:0 # 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|xcalloc|snprintfcat|max_threads|curr_threads|nut_report_config_flags|upsdebugx_report_search_paths|nut_prepare_search_paths)' +libnutscan_la_LDFLAGS += -export-symbols-regex '^(nutscan_|nut_debug_level|s_upsdebug|fatalx|fatal_with_errno|xcalloc|xbasename|snprintfcat|max_threads|curr_threads|nut_report_config_flags|upsdebugx_report_search_paths|nut_prepare_search_paths|print_banner_once)' 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 a3d785f5a4..c0f56ae05e 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -32,7 +32,6 @@ #include #include #include -#include "nut_version.h" #include #include @@ -969,10 +968,11 @@ static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) #endif /* HAVE_GETIFADDRS || ( WIN32 && (HAVE_GETADAPTERSINFO || HAVE_GETADAPTERSADDRESSES)) */ } -static void show_usage(void) +static void show_usage(const char *arg_progname) { /* NOTE: This code uses `nutscan_avail_*` global vars from nutscan-init.c */ - puts("nut-scanner : utility for detection of available power devices.\n"); + print_banner_once(arg_progname, 2); + puts("NUT utility for detection of available power devices.\n"); nut_report_config_flags(); @@ -1158,6 +1158,7 @@ static void show_usage(void) int main(int argc, char *argv[]) { + const char *progname = xbasename(argv[0]); nutscan_snmp_t snmp_sec; nutscan_ipmi_t ipmi_sec; nutscan_xml_t xml_sec; @@ -1543,32 +1544,9 @@ int main(int argc, char *argv[]) quiet = 1; break; case 'V': -#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 - /* NOTE: Some compilers deduce that macro-based decisions about - * NUT_VERSION_IS_RELEASE make one of codepaths unreachable in - * a particular build. So we pragmatically handwave this away. - */ - printf("Network UPS Tools - %s %s%s%s\n", - NUT_VERSION_MACRO, - NUT_VERSION_IS_RELEASE ? "release" : "(development iteration after ", - NUT_VERSION_IS_RELEASE ? "" : NUT_VERSION_SEMVER_MACRO, - NUT_VERSION_IS_RELEASE ? "" : ")" - ); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE -#pragma GCC diagnostic pop -#endif + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(progname, 1); nut_report_config_flags(); exit(EXIT_SUCCESS); case 'a': @@ -1597,7 +1575,7 @@ int main(int argc, char *argv[]) case 'h': default: display_help: - show_usage(); + show_usage(progname); if ((opt_ret != 'h') || (ret_code != EXIT_SUCCESS)) fprintf(stderr, "\n\n" "WARNING: Some error has occurred while processing 'nut-scanner' command-line\n" diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index b4cdff1729..75504f272f 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -29,11 +29,14 @@ #include "nutstream.hpp" extern "C" { +/* FIXME? Is it counter-intentional to use our C common library + * for C++ code (here for common printing of version banner)? */ +#include "common.h" #if (defined WITH_NUTSCANNER) -#include "nut-scan.h" -#include "nutscan-init.h" -#include "nutscan-device.h" -#endif // defined WITH_NUTSCANNER +# include "nut-scan.h" +# include "nutscan-init.h" +# include "nutscan-device.h" +#endif /* WITH_NUTSCANNER */ } #include @@ -56,15 +59,20 @@ class Usage { public: - /** Print usage */ + /** Print version and usage to stderr */ static void print(const std::string & bin); + /** Print version info to stdout */ + static void printVersion(const std::string & bin); + }; // end of class usage const char * Usage::s_text[] = { " -h -help", " --help Display this help and exit", + " -V", + " --version Display tool version on stdout and exit", " --autoconfigure Perform automatic configuration", " --is-configured Checks whether NUT is configured", " --local Sets configuration directory", @@ -100,6 +108,9 @@ 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 + * could setenv() the envvar for libnutscan perhaps? */ " -v", " --verbose Increase verbosity of output one level", " May be specified multiple times", @@ -164,9 +175,23 @@ const char * Usage::s_text[] = { "", }; +/** + * Print version info to stdout (like other NUT tools) + */ +void Usage::printVersion(const std::string & bin) { + std::cout + << "Network UPS Tools " << bin + << " " << describe_NUT_VERSION_once() << std::endl; +} +/** + * Print help text (including version info) to stderr + */ void Usage::print(const std::string & bin) { std::cerr + << "Network UPS Tools " << bin + << " " << describe_NUT_VERSION_once() << std::endl + << std::endl << "Usage: " << bin << " [OPTIONS]" << std::endl << std::endl << "OPTIONS:" << std::endl; @@ -3085,12 +3110,21 @@ 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]); + // Get options NutConfOptions options(argv, argc); // Usage if (options.exists("help") || options.existsSingle("h")) { - Usage::print(argv[0]); + Usage::print(prog); + + ::exit(0); + } + + // Usage + if (options.exists("version") || options.existsSingle("V")) { + Usage::printVersion(prog); ::exit(0); }