Skip to content

Commit 300e8d2

Browse files
authored
Merge 4a41f75 into b30d183
2 parents b30d183 + 4a41f75 commit 300e8d2

23 files changed

+499
-54
lines changed

NEWS.adoc

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,9 +403,27 @@ This requirement compromises usability of `make distcheck` on platforms without
403403
* Updated `upsmon` client with setting to toggle whether an `ALARM`
404404
status can prompt the UPS to become critical in certain situations.
405405
406-
- New `libupsclient` API methods added, `upscli_str_add_unique_token()` and
407-
`upscli_str_contains_token()`, to help C NUT clients process `ups.status` and
408-
similarly structured strings same way as NUT core code base. [#2852, #2859]
406+
- New `libupsclient` API methods added:
407+
* `upscli_str_add_unique_token()` and `upscli_str_contains_token()`,
408+
to help C NUT clients process `ups.status` and similarly structured
409+
strings same way as NUT core code base. [#2852, #2859]
410+
* `upscli_connect()` was previously always blocking; now this is sort of
411+
optional, with new `upscli_set_default_timeout()` able to change the
412+
implicit timeout from default zero (meaning blocking) to a positive
413+
value (or back to 0). Several NUT clients (`upsc`, `upscmd`, `upsrw`,
414+
`upslog`, `upsmon`, `upsimage`, `upsset` and `upsstats`) were updated
415+
to default with a 10-second timeout in case of name resolution lags or
416+
unresponsive hosts (notably a problem with `upsmon` contacting many
417+
remote systems at once). The `NUT_DEFAULT_CONNECT_TIMEOUT` environment
418+
variable can be used to modify this timeout for all clients. [#2847]
419+
* Symbols exported from `libupsclient` now include `nut_debug_level*` so
420+
that NUT clients can be usefully debugged (e.g. using `NUT_DEBUG_LEVEL`
421+
environment variable). [#2847]
422+
423+
- Several NUT clients including `upscmd`, `upsrw`, `upsimage`, `upsset`,
424+
`upsstats`, and `upslog` (during reconnection), did not `UPSCLI_CONN_TRYSSL`
425+
so went plaintext even when secure connections were possible. Fixed to at
426+
least try being secure, same way as `upsc` does for a long time. [#2847]
409427
410428
- upsmon:
411429
* it was realized that the `POWERDOWNFLAG` must be explicitly set in the

UPGRADING.adoc

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,30 @@ Changes from 2.8.2 to 2.8.3
7777
on having those for translations to be found -- this should be reflected
7878
in OS packaging recipes as well. [#2845]
7979
80-
- New `libupsclient` API methods added, `upscli_str_add_unique_token()` and
81-
`upscli_str_contains_token()`, to help C NUT clients process `ups.status` and
82-
similarly structured strings same way as NUT core code base. [#2852, #2859]
80+
- New `libupsclient` API methods added:
81+
* `upscli_str_add_unique_token()` and `upscli_str_contains_token()`,
82+
to help C NUT clients process `ups.status` and similarly structured
83+
strings same way as NUT core code base. [#2852, #2859]
84+
* `upscli_set_default_timeout()` to modify the internal timeout used by
85+
`upscli_connect()` (default 0 still means blocking connections, positive
86+
values should time-limit the connection attempts). [#2847]
87+
* `upscli_get_default_timeout()` to retrieve internal timeout
88+
89+
- Standard NUT clients including `upsc`, `upscmd`, `upsrw`, `upslog`, `upsmon`,
90+
`upsimage`, `upsset` and `upsstats`) were updated to default with a 10-second
91+
connection establishment timeout in case of name resolution lags or
92+
unresponsive hosts (notably a problem with `upsmon` contacting many remote
93+
systems at once). This may potentially impact NUT deployments which somehow
94+
relied on the blocking behavior of these clients; you can use the
95+
`NUT_DEFAULT_CONNECT_TIMEOUT` environment variable to fix this. [#2847]
96+
97+
- Several NUT clients including `upscmd`, `upsrw`, `upsimage`, `upsset`,
98+
`upsstats`, and `upslog` (during reconnection), did not `UPSCLI_CONN_TRYSSL`
99+
so went plaintext even when secure connections were possible. Fixed to at
100+
least try being secure, same way as `upsc` does for a long time. This may
101+
cause console or log messages when SSL can not be initialized, you can use
102+
the `NUT_QUIET_INIT_SSL` environment variable to suppress them where the
103+
cryptography is known to be not set up, so the warnings bring no value. [#2847]
83104
84105
- `lib/*.pc.in`: propagate `-R/PATH` (or equivalent -- as detected by the
85106
`configure` script for the currently used compiler and linker toolkits)

clients/Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ endif WITH_SSL
113113

114114
# libupsclient version information
115115
libupsclient_la_LDFLAGS = -version-info 6:2:0
116-
libupsclient_la_LDFLAGS += -export-symbols-regex '^(upscli_)'
116+
libupsclient_la_LDFLAGS += -export-symbols-regex '^(upscli_|nut_debug_level)'
117+
#|s_upsdebug|fatalx|fatal_with_errno|xcalloc|xbasename|print_banner_once)'
117118
if HAVE_WINDOWS
118119
# Many versions of MingW seem to fail to build non-static DLL without this
119120
libupsclient_la_LDFLAGS += -no-undefined

clients/upsc.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
#include "nut_stdint.h"
3232
#include "upsclient.h"
3333

34+
#define UPSCLI_DEFAULT_TIMEOUT "10" /* network timeout in secs */
35+
3436
static char *upsname = NULL, *hostname = NULL;
3537
static UPSCONN_t *ups = NULL;
3638

@@ -59,6 +61,8 @@ static void usage(const char *prog)
5961

6062
printf("\nCommon arguments:\n");
6163
printf(" -V - display the version of this software\n");
64+
printf(" -W <secs> - network timeout (default: %s)\n",
65+
UPSCLI_DEFAULT_TIMEOUT);
6266
printf(" -h - display this help text\n");
6367

6468
nut_report_config_flags();
@@ -223,6 +227,7 @@ int main(int argc, char **argv)
223227
uint16_t port;
224228
int varlist = 0, clientlist = 0, verbose = 0;
225229
const char *prog = xbasename(argv[0]);
230+
const char *net_timeout = NULL;
226231
char *s = NULL;
227232

228233
/* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc
@@ -236,7 +241,7 @@ int main(int argc, char **argv)
236241
}
237242
upsdebugx(1, "Starting NUT client: %s", prog);
238243

239-
while ((i = getopt(argc, argv, "+hlLcV")) != -1) {
244+
while ((i = getopt(argc, argv, "+hlLcVW:")) != -1) {
240245

241246
switch (i)
242247
{
@@ -247,6 +252,7 @@ int main(int argc, char **argv)
247252
fallthrough_case_l:
248253
varlist = 1;
249254
break;
255+
250256
case 'c':
251257
clientlist = 1;
252258
break;
@@ -258,13 +264,22 @@ int main(int argc, char **argv)
258264
nut_report_config_flags();
259265
exit(EXIT_SUCCESS);
260266

267+
case 'W':
268+
net_timeout = optarg;
269+
break;
270+
261271
case 'h':
262272
default:
263273
usage(prog);
264274
exit(EXIT_SUCCESS);
265275
}
266276
}
267277

278+
if (upscli_init_default_timeout(net_timeout, NULL, UPSCLI_DEFAULT_TIMEOUT) < 0) {
279+
fatalx(EXIT_FAILURE, "Error: invalid network timeout: %s",
280+
net_timeout);
281+
}
282+
268283
argc -= optind;
269284
argv += optind;
270285

clients/upsclient.c

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757

5858
#include "common.h"
5959
#include "nut_stdint.h"
60+
#include "nut_float.h"
6061
#include "timehead.h"
6162
#include "upsclient.h"
6263

@@ -154,9 +155,12 @@ typedef struct HOST_CERT_s {
154155
} HOST_CERT_t;
155156
static HOST_CERT_t* upscli_find_host_cert(const char* hostname);
156157

157-
158+
/* Flag for SSL init */
158159
static int upscli_initialized = 0;
159160

161+
/* 0 means no timeout in upscli_connect() */
162+
static struct timeval upscli_default_timeout = {0, 0};
163+
160164
#ifdef WITH_OPENSSL
161165
static SSL_CTX *ssl_ctx;
162166
#elif defined(WITH_NSS) /* WITH_OPENSLL */
@@ -345,6 +349,11 @@ int upscli_init(int certverify, const char *certpath,
345349
NUT_UNUSED_VARIABLE(certpasswd);
346350
#endif /* WITH_OPENSSL | WITH_NSS */
347351

352+
if (upscli_initialized == 1) {
353+
upslogx(LOG_WARNING, "upscli already initialized");
354+
return -1;
355+
}
356+
348357
quiet_init_ssl = getenv("NUT_QUIET_INIT_SSL");
349358
if (quiet_init_ssl != NULL) {
350359
if (*quiet_init_ssl == '\0'
@@ -357,11 +366,6 @@ int upscli_init(int certverify, const char *certpath,
357366
}
358367
}
359368

360-
if (upscli_initialized == 1) {
361-
upslogx(LOG_WARNING, "upscli already initialized");
362-
return -1;
363-
}
364-
365369
#ifdef WITH_OPENSSL
366370

367371
#if OPENSSL_VERSION_NUMBER < 0x10100000L
@@ -1130,6 +1134,8 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags
11301134
else {
11311135
/* Timeout */
11321136
v = -1;
1137+
ups->upserror = UPSCLI_ERR_CONNFAILURE;
1138+
ups->syserrno = ETIMEDOUT;
11331139
break;
11341140
}
11351141
}
@@ -1150,6 +1156,14 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags
11501156

11511157
if (v < 0) {
11521158
close(sock_fd);
1159+
/* if timeout, break out so client can continue */
1160+
/* match Linux behavior that updates timeout struct */
1161+
if (timeout != NULL &&
1162+
ups->upserror == UPSCLI_ERR_CONNFAILURE &&
1163+
ups->syserrno == ETIMEDOUT
1164+
) {
1165+
break;
1166+
}
11531167
continue;
11541168
}
11551169

@@ -1234,7 +1248,12 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags
12341248

12351249
int upscli_connect(UPSCONN_t *ups, const char *host, uint16_t port, int flags)
12361250
{
1237-
return upscli_tryconnect(ups,host,port,flags,NULL);
1251+
struct timeval tv = upscli_default_timeout, *ptv = NULL;
1252+
if (tv.tv_sec != 0 || tv.tv_usec != 0) {
1253+
/* By default, ptv==NULL for a blocking upscli_tryconnect() */
1254+
ptv = &tv;
1255+
}
1256+
return upscli_tryconnect(ups, host, port, flags, ptv);
12381257
}
12391258

12401259
/* map upsd error strings back to upsclient internal numbers */
@@ -1846,6 +1865,113 @@ int upscli_ssl(UPSCONN_t *ups)
18461865
return 0;
18471866
}
18481867

1868+
int upscli_set_default_timeout(const char *secs) {
1869+
double fsecs;
1870+
1871+
if (secs) {
1872+
if (str_to_double(secs, &fsecs, 10) < 1) {
1873+
return -1;
1874+
}
1875+
if (d_equal(fsecs, 0.0)) {
1876+
upscli_default_timeout.tv_sec = 0;
1877+
upscli_default_timeout.tv_usec = 0;
1878+
return 0;
1879+
}
1880+
if (fsecs < 0.0) {
1881+
return -1;
1882+
}
1883+
upscli_default_timeout.tv_sec = (time_t)fsecs;
1884+
fsecs *= 1000000;
1885+
upscli_default_timeout.tv_usec =
1886+
(suseconds_t)((int)fsecs % 1000000);
1887+
}
1888+
else {
1889+
upscli_default_timeout.tv_sec = 0;
1890+
upscli_default_timeout.tv_usec = 0;
1891+
}
1892+
return 0;
1893+
}
1894+
1895+
void upscli_get_default_timeout(struct timeval *ptv) {
1896+
if (ptv) {
1897+
*ptv = upscli_default_timeout;
1898+
}
1899+
}
1900+
1901+
int upscli_init_default_timeout(const char *cli_secs, const char *config_secs, const char *default_secs) {
1902+
const char *envvar_secs, *cause = "built-in";
1903+
int failed = 0, applied = 0;
1904+
1905+
/* First the very default: blocking connections as we always had */
1906+
upscli_default_timeout.tv_sec = 0;
1907+
upscli_default_timeout.tv_usec = 0;
1908+
1909+
/* Then try a program's built-in default, if any */
1910+
if (default_secs) {
1911+
if (upscli_set_default_timeout(default_secs) < 0) {
1912+
upsdebugx(1, "%s: default_secs='%s' value was not recognized, ignored",
1913+
__func__, default_secs);
1914+
failed++;
1915+
} else {
1916+
cause = "default_secs";
1917+
applied++;
1918+
}
1919+
}
1920+
1921+
/* Then override with envvar setting, if any (and if its value is valid) */
1922+
envvar_secs = getenv("NUT_DEFAULT_CONNECT_TIMEOUT");
1923+
if (envvar_secs) {
1924+
if (upscli_set_default_timeout(envvar_secs) < 0) {
1925+
upsdebugx(1, "%s: NUT_DEFAULT_CONNECT_TIMEOUT='%s' value was not recognized, ignored",
1926+
__func__, envvar_secs);
1927+
failed++;
1928+
} else {
1929+
cause = "envvar_secs";
1930+
applied++;
1931+
}
1932+
}
1933+
1934+
/* Then override with config-file setting, if any (and if its value is valid) */
1935+
if (config_secs) {
1936+
if (upscli_set_default_timeout(config_secs) < 0) {
1937+
upsdebugx(1, "%s: config_secs='%s' value was not recognized, ignored",
1938+
__func__, config_secs);
1939+
failed++;
1940+
} else {
1941+
cause = "config_secs";
1942+
applied++;
1943+
}
1944+
}
1945+
1946+
/* Then override with command-line setting, if any (and if its value is valid) */
1947+
if (cli_secs) {
1948+
if (upscli_set_default_timeout(cli_secs) < 0) {
1949+
upsdebugx(1, "%s: cli_secs='%s' value was not recognized, ignored",
1950+
__func__, cli_secs);
1951+
failed++;
1952+
} else {
1953+
cause = "cli_secs";
1954+
applied++;
1955+
}
1956+
}
1957+
1958+
upsdebugx(1, "%s: upscli_default_timeout=%" PRIiMAX
1959+
".%06" PRIiMAX " sec assigned from: %s",
1960+
__func__, (intmax_t)upscli_default_timeout.tv_sec,
1961+
(intmax_t)upscli_default_timeout.tv_usec, cause);
1962+
1963+
/* Some non-built-in value was OK */
1964+
if (applied)
1965+
return 0;
1966+
1967+
/* None of provided non-built-in values was OK */
1968+
if (failed)
1969+
return -1;
1970+
1971+
/* At least we have the built-in default and nothing failed */
1972+
return 0;
1973+
}
1974+
18491975
/* Pick up the methods below from libcommon and expose in the NUT client API */
18501976
int upscli_str_contains_token(const char *string, const char *token)
18511977
{

clients/upsclient.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* upsclient.h - definitions for upsclient functions
22
3-
Copyright (C) 2002 Russell Kroll <rkroll@exploits.org>
3+
Copyright (C)
4+
2002 Russell Kroll <rkroll@exploits.org>
5+
2020 - 2025 Jim Klimov <jimklimov+nu@gmail.com>
46
57
This program is free software; you can redistribute it and/or modify
68
it under the terms of the GNU General Public License as published by
@@ -97,10 +99,12 @@ typedef struct {
9799

98100
const char *upscli_strerror(UPSCONN_t *ups);
99101

102+
/* NOTE: effectively only runs once; re-runs quickly skip out */
100103
int upscli_init(int certverify, const char *certpath, const char *certname, const char *certpasswd);
101104
int upscli_cleanup(void);
102105

103106
int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags, struct timeval *tv);
107+
/* blocking unless default timeout is specified, see also: upscli_init_default_timeout() */
104108
int upscli_connect(UPSCONN_t *ups, const char *host, uint16_t port, int flags);
105109

106110
void upscli_add_host_cert(const char* hostname, const char* certname, int certverify, int forcessl);
@@ -136,6 +140,21 @@ int upscli_upserror(UPSCONN_t *ups);
136140
/* returns 1 if SSL mode is active for this connection */
137141
int upscli_ssl(UPSCONN_t *ups);
138142

143+
/* Assign default upscli_connect() from string; return 0 if OK, or
144+
* return -1 if parsing failed and current value was kept */
145+
int upscli_set_default_timeout(const char *secs);
146+
/* If ptv!=NULL, populate it with a copy of last assigned internal timeout */
147+
void upscli_get_default_timeout(struct timeval *ptv);
148+
/* Initialize default upscli_connect() timeout from a number of sources:
149+
* built-in (0 = blocking), envvar NUT_DEFAULT_CONNECT_TIMEOUT,
150+
* or specified strings (may be NULL) most-preferred first.
151+
* Returns 0 if any provided value was valid and applied,
152+
* or if none were provided so the built-in default was applied;
153+
* returns -1 if all provided values were not valid (so the built-in
154+
* default was applied) - not necessarily fatal, rather useful to report.
155+
*/
156+
int upscli_init_default_timeout(const char *cli_secs, const char *config_secs, const char *default_secs);
157+
139158
/* upsclient error list */
140159

141160
#define UPSCLI_ERR_NONE -1 /* No known error (internally used in tools like upsmon, not set by upsclient.c) */

0 commit comments

Comments
 (0)