diff --git a/clients/Makefile.am b/clients/Makefile.am index 89669a7992..0ea33105cc 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -57,7 +57,7 @@ endif # object .so names would differ) # libupsclient version information -libupsclient_la_LDFLAGS = -version-info 4:0:0 +libupsclient_la_LDFLAGS = -version-info 5:0:0 # libnutclient version information libnutclient_la_SOURCES = nutclient.h nutclient.cpp diff --git a/clients/upsclient.c b/clients/upsclient.c index b90587b001..dd4c9486ae 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -442,7 +442,7 @@ static HOST_CERT_t* upscli_find_host_cert(const char* hostname) return NULL; } -int upscli_cleanup() +int upscli_cleanup(void) { #ifdef WITH_OPENSSL if (ssl_ctx) { @@ -567,7 +567,7 @@ static int upscli_select_read(const int fd, void *buf, const size_t buflen, cons } /* internal: abstract the SSL calls for the other functions */ -static int net_read(UPSCONN_t *ups, char *buf, size_t buflen) +static int net_read(UPSCONN_t *ups, char *buf, size_t buflen, unsigned int timeout) { int ret = -1; @@ -587,7 +587,7 @@ static int net_read(UPSCONN_t *ups, char *buf, size_t buflen) } #endif - ret = upscli_select_read(ups->fd, buf, buflen, 5, 0); + ret = upscli_select_read(ups->fd, buf, buflen, timeout, 0); /* error reading data, server disconnected? */ if (ret < 0) { @@ -628,7 +628,7 @@ static int upscli_select_write(const int fd, const void *buf, const size_t bufle } /* internal: abstract the SSL calls for the other functions */ -static int net_write(UPSCONN_t *ups, const char *buf, size_t buflen) +static int net_write(UPSCONN_t *ups, const char *buf, size_t buflen, unsigned int timeout) { int ret = -1; @@ -648,7 +648,7 @@ static int net_write(UPSCONN_t *ups, const char *buf, size_t buflen) } #endif - ret = upscli_select_write(ups->fd, buf, buflen, 0, 0); + ret = upscli_select_write(ups->fd, buf, buflen, timeout, 0); /* error writing data, server disconnected? */ if (ret < 0) { @@ -1326,7 +1326,7 @@ int upscli_list_next(UPSCONN_t *ups, unsigned int numq, const char **query, return 1; } -int upscli_sendline(UPSCONN_t *ups, const char *buf, size_t buflen) +int upscli_sendline_timeout(UPSCONN_t *ups, const char *buf, size_t buflen, unsigned int timeout) { int ret; @@ -1349,7 +1349,7 @@ int upscli_sendline(UPSCONN_t *ups, const char *buf, size_t buflen) return -1; } - ret = net_write(ups, buf, buflen); + ret = net_write(ups, buf, buflen, timeout); if (ret < 1) { upscli_disconnect(ups); @@ -1359,7 +1359,12 @@ int upscli_sendline(UPSCONN_t *ups, const char *buf, size_t buflen) return 0; } -int upscli_readline(UPSCONN_t *ups, char *buf, size_t buflen) +int upscli_sendline(UPSCONN_t *ups, const char *buf, size_t buflen) +{ + return upscli_sendline_timeout(ups, buf, buflen, 0); +} + +int upscli_readline_timeout(UPSCONN_t *ups, char *buf, size_t buflen, unsigned int timeout) { int ret; size_t recv; @@ -1387,7 +1392,7 @@ int upscli_readline(UPSCONN_t *ups, char *buf, size_t buflen) if (ups->readidx == ups->readlen) { - ret = net_read(ups, ups->readbuf, sizeof(ups->readbuf)); + ret = net_read(ups, ups->readbuf, sizeof(ups->readbuf), timeout); if (ret < 1) { upscli_disconnect(ups); @@ -1409,6 +1414,11 @@ int upscli_readline(UPSCONN_t *ups, char *buf, size_t buflen) return 0; } +int upscli_readline(UPSCONN_t *ups, char *buf, size_t buflen) +{ + return upscli_readline_timeout(ups, buf, buflen, DEFAULT_NETWORK_TIMEOUT); +} + /* split upsname[@hostname[:port]] into separate components */ int upscli_splitname(const char *buf, char **upsname, char **hostname, int *port) { @@ -1518,7 +1528,7 @@ int upscli_disconnect(UPSCONN_t *ups) return 0; } - net_write(ups, "LOGOUT\n", 7); + net_write(ups, "LOGOUT\n", 7, 0); #ifdef WITH_OPENSSL if (ups->ssl) { diff --git a/clients/upsclient.h b/clients/upsclient.h index ac5fb5d567..d3da201bf1 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -69,7 +69,7 @@ typedef struct { const char *upscli_strerror(UPSCONN_t *ups); int upscli_init(int certverify, const char *certpath, const char *certname, const char *certpasswd); -int upscli_cleanup(); +int upscli_cleanup(void); int upscli_tryconnect(UPSCONN_t *ups, const char *host, int port, int flags, struct timeval *tv); int upscli_connect(UPSCONN_t *ups, const char *host, int port, int flags); @@ -86,8 +86,10 @@ int upscli_list_start(UPSCONN_t *ups, unsigned int numq, const char **query); int upscli_list_next(UPSCONN_t *ups, unsigned int numq, const char **query, unsigned int *numa, char ***answer); +int upscli_sendline_timeout(UPSCONN_t *ups, const char *buf, size_t buflen, unsigned int timeout); int upscli_sendline(UPSCONN_t *ups, const char *buf, size_t buflen); +int upscli_readline_timeout(UPSCONN_t *ups, char *buf, size_t buflen, unsigned int timeout); int upscli_readline(UPSCONN_t *ups, char *buf, size_t buflen); int upscli_splitname(const char *buf, char **upsname, char **hostname, diff --git a/clients/upscmd.c b/clients/upscmd.c index f4990a872c..f8e237d429 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -1,6 +1,8 @@ /* upscmd - simple "client" to test instant commands via upsd - Copyright (C) 2000 Russell Kroll + Copyright (C) + 2000 Russell Kroll + 2019 EATON (author: Arnaud Quette ) 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 @@ -28,8 +30,10 @@ #include "upsclient.h" -static char *upsname = NULL, *hostname = NULL; +static char *upsname = NULL, *hostname = NULL; static UPSCONN_t *ups = NULL; +static int tracking_enabled = 0; +static unsigned int timeout = DEFAULT_TRACKING_TIMEOUT; struct list_t { char *name; @@ -41,13 +45,16 @@ static void usage(const char *prog) printf("Network UPS Tools upscmd %s\n\n", UPS_VERSION); printf("usage: %s [-h]\n", prog); printf(" %s [-l ]\n", prog); - printf(" %s [-u ] [-p ] []\n\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(" -l show available commands on UPS \n"); printf(" -u set username for command authentication\n"); printf(" -p set password for command authentication\n"); + printf(" -w wait for the completion of command by the driver\n"); + printf(" and return its actual result from the device\n"); + printf(" -t set a timeout when using -w (in seconds, default: %u)\n", DEFAULT_TRACKING_TIMEOUT); printf("\n"); printf(" UPS identifier - [@[:]]\n"); printf(" Valid instant command - test.panel.start, etc.\n"); @@ -138,7 +145,10 @@ static void listcmds(void) static void do_cmd(char **argv, const int argc) { + int cmd_complete = 0; char buf[SMALLBUF]; + char tracking_id[UUID4_LEN]; + time_t start, now; if (argc > 1) { snprintf(buf, sizeof(buf), "INSTCMD %s %s %s\n", upsname, argv[0], argv[1]); @@ -154,11 +164,49 @@ static void do_cmd(char **argv, const int argc) fatalx(EXIT_FAILURE, "Instant command failed: %s", upscli_strerror(ups)); } - /* FUTURE: status cookies will tie in here */ + /* verify answer */ if (strncmp(buf, "OK", 2) != 0) { fatalx(EXIT_FAILURE, "Unexpected response from upsd: %s", buf); } + /* check for status tracking id */ + if ( + !tracking_enabled || + /* sanity check on the size: "OK TRACKING " + UUID4_LEN */ + strlen(buf) != (UUID4_LEN - 1 + strlen("OK TRACKING ")) + ) { + /* reply as usual */ + fprintf(stderr, "%s\n", buf); + return; + } + + snprintf(tracking_id, sizeof(tracking_id), "%s", buf + strlen("OK TRACKING ")); + time(&start); + + /* send status tracking request, looping if status is PENDING */ + while (!cmd_complete) { + + /* check for timeout */ + time(&now); + if (difftime(now, start) >= timeout) + fatalx(EXIT_FAILURE, "Can't receive status tracking information: timeout"); + + snprintf(buf, sizeof(buf), "GET TRACKING %s\n", tracking_id); + + if (upscli_sendline(ups, buf, strlen(buf)) < 0) + fatalx(EXIT_FAILURE, "Can't send status tracking request: %s", upscli_strerror(ups)); + + /* and get status tracking reply */ + if (upscli_readline_timeout(ups, buf, sizeof(buf), timeout) < 0) + fatalx(EXIT_FAILURE, "Can't receive status tracking information: %s", upscli_strerror(ups)); + + if (strncmp(buf, "PENDING", 7)) + cmd_complete = 1; + else + /* wait a second before retrying */ + sleep(1); + } + fprintf(stderr, "%s\n", buf); } @@ -180,7 +228,7 @@ int main(int argc, char **argv) char buf[SMALLBUF], username[SMALLBUF], password[SMALLBUF]; const char *prog = xbasename(argv[0]); - while ((i = getopt(argc, argv, "+lhu:p:V")) != -1) { + while ((i = getopt(argc, argv, "+lhu:p:t:wV")) != -1) { switch (i) { @@ -198,6 +246,15 @@ int main(int argc, char **argv) have_pw = 1; break; + case 't': + if (!str_to_uint(optarg, &timeout, 10)) + fatal_with_errno(EXIT_FAILURE, "Could not convert the provided value for timeout ('-t' option) to unsigned int"); + break; + + case 'w': + tracking_enabled = 1; + break; + case 'V': fatalx(EXIT_SUCCESS, "Network UPS Tools upscmd %s", UPS_VERSION); @@ -313,6 +370,25 @@ int main(int argc, char **argv) fatalx(EXIT_FAILURE, "Set password failed: %s", upscli_strerror(ups)); } + /* enable status tracking ID */ + if (tracking_enabled) { + + snprintf(buf, sizeof(buf), "SET TRACKING ON\n"); + + if (upscli_sendline(ups, buf, strlen(buf)) < 0) { + fatalx(EXIT_FAILURE, "Can't enable command status tracking: %s", upscli_strerror(ups)); + } + + if (upscli_readline(ups, buf, sizeof(buf)) < 0) { + fatalx(EXIT_FAILURE, "Enabling command status tracking failed: %s", upscli_strerror(ups)); + } + + /* Verify the result */ + if (strncmp(buf, "OK", 2) != 0) { + fatalx(EXIT_FAILURE, "Enabling command status tracking failed. upsd answered: %s", buf); + } + } + do_cmd(&argv[1], argc - 1); exit(EXIT_SUCCESS); diff --git a/clients/upsrw.c b/clients/upsrw.c index dd388e1702..0d1906de51 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -1,6 +1,8 @@ /* upsrw - simple client for read/write variable access (formerly upsct2) - Copyright (C) 1999 Russell Kroll + Copyright (C) + 1999 Russell Kroll + 2019 EATON (author: Arnaud Quette ) 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 @@ -28,8 +30,10 @@ #include "upsclient.h" #include "extstate.h" -static char *upsname = NULL, *hostname = NULL; +static char *upsname = NULL, *hostname = NULL; static UPSCONN_t *ups = NULL; +static int tracking_enabled = 0; +static unsigned int timeout = DEFAULT_TRACKING_TIMEOUT; struct list_t { char *name; @@ -40,7 +44,7 @@ static void usage(const char *prog) { printf("Network UPS Tools %s %s\n\n", prog, UPS_VERSION); printf("usage: %s [-h]\n", prog); - printf(" %s [-s ] [-u ] [-p ] \n\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"); @@ -48,6 +52,9 @@ static void usage(const char *prog) printf(" use -s VAR=VALUE to avoid prompting for value\n"); printf(" -u set username for command authentication\n"); printf(" -p set password for command authentication\n"); + printf(" -w wait for the completion of setting by the driver\n"); + printf(" and return its actual result from the device\n"); + printf(" -t set a timeout when using -w (in seconds, default: %u)\n", DEFAULT_TRACKING_TIMEOUT); printf("\n"); printf(" UPS identifier - [@[:]]\n"); printf("\n"); @@ -67,7 +74,10 @@ static void clean_exit(void) static void do_set(const char *varname, const char *newval) { + int cmd_complete = 0; char buf[SMALLBUF], enc[SMALLBUF]; + char tracking_id[UUID4_LEN]; + time_t start, now; snprintf(buf, sizeof(buf), "SET VAR %s %s \"%s\"\n", upsname, varname, pconf_encode(newval, enc, sizeof(enc))); @@ -79,11 +89,49 @@ static void do_set(const char *varname, const char *newval) fatalx(EXIT_FAILURE, "Set variable failed: %s", upscli_strerror(ups)); } - /* FUTURE: status cookies will tie in here */ + /* verify answer */ if (strncmp(buf, "OK", 2) != 0) { fatalx(EXIT_FAILURE, "Unexpected response from upsd: %s", buf); } + /* check for status tracking id */ + if ( + !tracking_enabled || + /* sanity check on the size: "OK TRACKING " + UUID4_LEN */ + strlen(buf) != (UUID4_LEN - 1 + strlen("OK TRACKING ")) + ) { + /* reply as usual */ + fprintf(stderr, "%s\n", buf); + return; + } + + snprintf(tracking_id, sizeof(tracking_id), "%s", buf + strlen("OK TRACKING ")); + time(&start); + + /* send status tracking request, looping if status is PENDING */ + while (!cmd_complete) { + + /* check for timeout */ + time(&now); + if (difftime(now, start) >= timeout) + fatalx(EXIT_FAILURE, "Can't receive status tracking information: timeout"); + + snprintf(buf, sizeof(buf), "GET TRACKING %s\n", tracking_id); + + if (upscli_sendline(ups, buf, strlen(buf)) < 0) + fatalx(EXIT_FAILURE, "Can't send status tracking request: %s", upscli_strerror(ups)); + + /* and get status tracking reply */ + if (upscli_readline_timeout(ups, buf, sizeof(buf), timeout) < 0) + fatalx(EXIT_FAILURE, "Can't receive status tracking information: %s", upscli_strerror(ups)); + + if (strncmp(buf, "PENDING", 7)) + cmd_complete = 1; + else + /* wait a second before retrying */ + sleep(1); + } + fprintf(stderr, "%s\n", buf); } @@ -178,6 +226,25 @@ static void do_setvar(const char *varname, char *uin, const char *pass) fatalx(EXIT_FAILURE, "Error: old variable names are not supported"); } + /* enable status tracking ID */ + if (tracking_enabled) { + + snprintf(temp, sizeof(temp), "SET TRACKING ON\n"); + + if (upscli_sendline(ups, temp, strlen(temp)) < 0) { + fatalx(EXIT_FAILURE, "Can't enable set variable status tracking: %s", upscli_strerror(ups)); + } + + if (upscli_readline(ups, temp, sizeof(temp)) < 0) { + fatalx(EXIT_FAILURE, "Enabling set variable status tracking failed: %s", upscli_strerror(ups)); + } + + /* Verify the result */ + if (strncmp(temp, "OK", 2) != 0) { + fatalx(EXIT_FAILURE, "Enabling set variable status tracking failed. upsd answered: %s", temp); + } + } + do_set(varname, newval); } @@ -369,7 +436,7 @@ static void do_type(const char *varname) ret = upscli_get(ups, numq, query, &numa, &answer); if ((ret < 0) || (numa < numq)) { - printf("Unknown type\n"); + printf("Unknown type\n"); return; } @@ -519,7 +586,7 @@ int main(int argc, char **argv) const char *prog = xbasename(argv[0]); char *password = NULL, *username = NULL, *setvar = NULL; - while ((i = getopt(argc, argv, "+hs:p:u:V")) != -1) { + while ((i = getopt(argc, argv, "+hs:p:t:u:wV")) != -1) { switch (i) { case 's': @@ -528,9 +595,16 @@ int main(int argc, char **argv) case 'p': password = optarg; break; + case 't': + if (!str_to_uint(optarg, &timeout, 10)) + fatal_with_errno(EXIT_FAILURE, "Could not convert the provided value for timeout ('-t' option) to unsigned int"); + break; case 'u': username = optarg; break; + case 'w': + tracking_enabled = 1; + break; case 'V': printf("Network UPS Tools %s %s\n", prog, UPS_VERSION); exit(EXIT_SUCCESS); diff --git a/common/snprintf.c b/common/snprintf.c index 62c8c14182..be7785266f 100644 --- a/common/snprintf.c +++ b/common/snprintf.c @@ -54,7 +54,7 @@ #include "config.h" #include -# include +#include #include #if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) diff --git a/conf/upsd.conf.sample b/conf/upsd.conf.sample index e7328f6865..5cb94062ce 100644 --- a/conf/upsd.conf.sample +++ b/conf/upsd.conf.sample @@ -21,6 +21,14 @@ # the data fresh within the normal 15 second interval. Watch the syslog # for notifications from upsd about staleness. +# ======================================================================= +# TRACKINGDELAY +# TRACKINGDELAY 3600 +# +# This defaults to 1 hour. When instant commands and variables setting status +# tracking is enabled, status execution information are kept during this +# amount of time, and then cleaned up. + # ======================================================================= # STATEPATH # STATEPATH /var/run/nut diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 0c0646bcd6..fef33f6db0 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -252,7 +252,9 @@ MAN3_DEV_PAGES = \ upscli_list_next.3 \ upscli_list_start.3 \ upscli_readline.3 \ + upscli_readline_timeout.3 \ upscli_sendline.3 \ + upscli_sendline_timeout.3 \ upscli_splitaddr.3 \ upscli_splitname.3 \ upscli_ssl.3 \ @@ -289,6 +291,12 @@ MAN3_DEV_PAGES = \ nutscan_get_serial_ports_list.3 \ nutscan_init.3 +upscli_readline_timeout.3: upscli_readline.3 + touch $@ + +upscli_sendline_timeout.3: upscli_sendline.3 + touch $@ + MAN1_DEV_PAGES = \ libupsclient-config.1 endif diff --git a/docs/man/upscli_cleanup.txt b/docs/man/upscli_cleanup.txt index cd88a518e1..611c3de051 100644 --- a/docs/man/upscli_cleanup.txt +++ b/docs/man/upscli_cleanup.txt @@ -11,7 +11,7 @@ SYNOPSIS #include - int upscli_cleanup(); + int upscli_cleanup(void); DESCRIPTION ----------- diff --git a/docs/man/upscli_readline.txt b/docs/man/upscli_readline.txt index 52106ade43..06db9c62c3 100644 --- a/docs/man/upscli_readline.txt +++ b/docs/man/upscli_readline.txt @@ -4,7 +4,7 @@ UPSCLI_READLINE(3) NAME ---- -upscli_readline - read a single response from a UPS +upscli_readline, upscli_readline_timeout - read a single response from a UPS SYNOPSIS -------- @@ -12,23 +12,28 @@ SYNOPSIS #include int upscli_readline(UPSCONN_t *ups, char *buf, size_t buflen); + int upscli_readline_timeout(UPSCONN_t *ups, char *buf, size_t buflen, unsigned int timeout); DESCRIPTION ----------- -The *upscli_readline()* function takes the pointer 'ups' to a -`UPSCONN_t` state structure, receives a single line from the server, and -copies up to 'buflen' bytes of the response into the buffer -'buf'. +The *upscli_readline()* and *upscli_readline_timeout()* functions take the +pointer 'ups' to a `UPSCONN_t` state structure, receive a single line from the +server, and copy up to 'buflen' bytes of the response into the buffer 'buf'. Some parsing of the string occurs during reception. In particular, ERR messages from linkman:upsd[8] are detected and will cause this function to return -1. +The difference between the two functions is that *upscli_readline_timeout()* +lets the caller decide the amount of time ('timeout' seconds) after which it +should give up and return, whereas *upscli_readline()* does not offer this +freedom, and uses NUT default network timeout (5 seconds). + RETURN VALUE ------------ -The *upscli_readline()* function returns 0 on success, or -1 if an -error occurs. +The *upscli_readline()* and *upscli_readline_timeout()* functions return 0 on +success, or -1 if an error occurs. SEE ALSO -------- diff --git a/docs/man/upscli_sendline.txt b/docs/man/upscli_sendline.txt index c449c6dd5d..cefa264538 100644 --- a/docs/man/upscli_sendline.txt +++ b/docs/man/upscli_sendline.txt @@ -4,7 +4,7 @@ UPSCLI_SENDLINE(3) NAME ---- -upscli_sendline - send a single command to a UPS +upscli_sendline, upscli_sendline_timeout - send a single command to a UPS SYNOPSIS -------- @@ -13,22 +13,28 @@ SYNOPSIS #include int upscli_sendline(UPSCONN_t *ups, const char *buf, size_t buflen); + int upscli_sendline_timeout(UPSCONN_t *ups, const char *buf, size_t buflen, unsigned int timeout); DESCRIPTION ----------- -The *upscli_sendline()* function takes the pointer 'ups' to a -`UPSCONN_t` state structure and transmits a buffer 'buf' of size -'buflen' to the server. +The *upscli_sendline()* and *upscli_sendline_timeout()* functions take the +pointer 'ups' to a `UPSCONN_t` state structure and transmit a buffer 'buf' of +size 'buflen' to the server. The data in 'buf' must be a fully formatted protocol command as no parsing of the buffer occurs within this function. +The difference between the two functions is that *upscli_sendline_timeout()* +lets the caller decide the amount of time ('timeout' seconds) after which it +should give up and return, whereas *upscli_sendline()* does not offer this +freedom, and uses an immediate timeout (0 second). + RETURN VALUE ------------ -The *upscli_sendline()* function returns 0 on success, or -1 if an -error occurs. +The *upscli_sendline()* and *upscli_sendline_timeout()* functions return 0 on +success, or -1 if an error occurs. SEE ALSO -------- diff --git a/docs/man/upscmd.txt b/docs/man/upscmd.txt index 8083fece02..a66d0f1399 100644 --- a/docs/man/upscmd.txt +++ b/docs/man/upscmd.txt @@ -11,7 +11,7 @@ SYNOPSIS *upscmd* -l 'ups' -*upscmd* [-u 'username'] [-p 'password'] 'ups' 'command' +*upscmd* [-u 'username'] [-p 'password'] [-w] [-t ] 'ups' 'command' DESCRIPTION ----------- @@ -42,6 +42,17 @@ you will be prompted for this when invoking a command if -u is not used. Set the password to authenticate to the server. This is also optional like -u, and you will be prompted for it if necessary. +*-w*:: +Wait for the completion of command execution by the driver and return its +actual result from the device. Note that this feature requires that both upsd +and the driver support TRACKING (NUT version 2.7.5 or higher) or it will +otherwise fail. +The command will also block until an actual result is provided from the driver, +or the timeout is reached (see *-t*). + +*-t* 'seconds':: +Set a timeout when using *-w*. Defaults to 10 seconds. + 'ups':: Connect to this UPS. The format is `upsname[@hostname[:port]]`. The default hostname is "localhost". @@ -84,8 +95,7 @@ BUGS ---- There is currently no way to tell the user when the driver requires -confirmation to invoke a command such as `load.off`. Similarly, there is -not yet a way to tell the user if a command succeeds or fails. +confirmation to invoke a command such as `load.off`. This is on the list of things to fix in the future, so don't despair. It involves magic cookies. diff --git a/docs/man/upsd.conf.txt b/docs/man/upsd.conf.txt index ebdfef6024..5947792f20 100644 --- a/docs/man/upsd.conf.txt +++ b/docs/man/upsd.conf.txt @@ -26,6 +26,12 @@ to make upsd wait longer. + Most users should leave this at the default value. +"TRACKINGDELAY 'seconds'":: + +When instant commands and variables setting status tracking is enabled, status +execution information are kept during this amount of time, and then cleaned up. +This defaults to 3600 (1 hour). + "STATEPATH 'path'":: Tell upsd to look for the driver state sockets in 'path' rather diff --git a/docs/man/upsrw.txt b/docs/man/upsrw.txt index 8053364104..eb4ed351f8 100644 --- a/docs/man/upsrw.txt +++ b/docs/man/upsrw.txt @@ -13,7 +13,7 @@ SYNOPSIS *upsrw* -h -*upsrw* -s 'variable' [-u 'username'] [-p 'password'] 'ups' +*upsrw* -s 'variable' [-u 'username'] [-p 'password'] [-w] [-t ] 'ups' DESCRIPTION ----------- @@ -58,6 +58,17 @@ linkman:upsd.users[5], and are not linked to system usernames. Set the password to authenticate to the server. This is also optional like -u, and you will be prompted for it if necessary. +*-w*:: +Wait for the completion of setting execution by the driver and return its +actual result from the device. Note that this feature requires that both upsd +and the driver support TRACKING (NUT version 2.7.5 or higher) or it will +otherwise fail. +The command will also block until an actual result is provided from the driver, +or the timeout is reached (see *-t*). + +*-t* 'seconds':: +Set a timeout when using *-w*. Defaults to 10 seconds. + 'ups':: View or change the settings on this UPS. The format for this option is `upsname[@hostname[:port]]`. The default hostname is "localhost". diff --git a/docs/net-protocol.txt b/docs/net-protocol.txt index 4f9a7fa34d..91c62dbdc4 100644 --- a/docs/net-protocol.txt +++ b/docs/net-protocol.txt @@ -44,6 +44,8 @@ NUT network protocol, over the time: |1.1 |>= 1.5.0 |Original protocol (without old commands) .2+|1.2 .2+|>= 2.6.4 |Add "LIST CLIENTS" and "NETVER" commands |Add ranges of values for writable variables +.2+|1.3 .2+|>= 2.7.5 |Add "cmdparam" to "INSTCMD" + |Add "TRACKING" commands (GET, SET) |=============================================================================== NOTE: any new version of the protocol implies an update of NUT_NETVERSION @@ -189,6 +191,26 @@ This is like DESC above, but it applies to the instant commands. This replaces the old "INSTCMDDESC" command. +TRACKING +~~~~~~~~ + +Form: + + GET TRACKING (activation status of TRACKING) + GET TRACKING (execution status of a command / setvar) + GET TRACKING 1bd31808-cb49-4aec-9d75-d056e6f018d2 + +Response: + + ON (TRACKING feature is enabled) + OFF (TRACKING feature is disabled) + PENDING (command execution is pending) + SUCCESS (command was successfully executed) + ERR UNKNOWN (command execution failed with unknown error) + ERR INVALID-ARGUMENT (command execution failed due to missing or invalid argument) + ERR FAILED (command execution failed) + + LIST ---- @@ -377,22 +399,54 @@ Response: SET --- +VAR +~~~ + Form: SET VAR "" SET VAR su700 ups.id "My UPS" +Response: + + OK (if TRACKING is not enabled) + OK TRACKING (if TRACKING is enabled) + ERR [...] (see Error responses) + + +TRACKING +~~~~~~~~ + +Form: + + SET TRACKING + SET TRACKING ON + SET TRACKING OFF + +Response: + + OK + ERR INVALID-ARGUMENT (if is not "ON" or "OFF") + ERR USERNAME-REQUIRED (if not yet authenticated) + ERR PASSWORD-REQUIRED (if not yet authenticated) + INSTCMD ------- Form: - INSTCMD [cmdparam] + INSTCMD [] INSTCMD su700 test.panel.start INSTCMD su700 load.off.delay 120 -NOTE: cmdparam is an additional and optional parameter for the command. +NOTE: is an additional and optional parameter for the command. + +Response: + + OK (if TRACKING is not enabled) + OK TRACKING (if TRACKING is enabled) + ERR [...] (see Error responses) LOGOUT @@ -699,13 +753,6 @@ For example, "LIST VARS +DESC" would return the current value like now, but it would also append the description of that variable. -Command status -~~~~~~~~~~~~~~ - -After sending an INSTCMD or SET, a client will eventually be able to -poll to see whether it was completed successfully by the driver. - - Get collection ~~~~~~~~~~~~~~ diff --git a/docs/nut-names.txt b/docs/nut-names.txt index bdc30b8d06..c5eb47cb3d 100644 --- a/docs/nut-names.txt +++ b/docs/nut-names.txt @@ -621,46 +621,43 @@ Instant commands ---------------- [options="header"] -|=============================================================================== -| Name | Description -| load.off | Turn off the load immediately -| load.on | Turn on the load immediately -| load.off.delay | Turn off the load possibly after a delay -| load.on.delay | Turn on the load possibly after a delay -| shutdown.return | Turn off the load possibly after a delay +|======================================================================== +| Name | Description +| load.off | Turn off the load immediately +| load.on | Turn on the load immediately +| load.off.delay | Turn off the load possibly after a delay +| load.on.delay | Turn on the load possibly after a delay +| shutdown.return | Turn off the load possibly after a delay and return when power is back -| shutdown.stayoff | Turn off the load possibly after a delay - and remain off even if power returns -| shutdown.stop | Stop a shutdown in progress -| shutdown.reboot | Shut down the load briefly while rebooting the UPS -| shutdown.reboot.graceful | After a delay, shut down the load briefly - while rebooting the UPS -| test.panel.start | Start testing the UPS panel -| test.panel.stop | Stop a UPS panel test -| test.failure.start | Start a simulated power failure -| test.failure.stop | Stop simulating a power failure -| test.battery.start | Start a battery test -| test.battery.start.quick | Start a "quick" battery test -| test.battery.start.deep | Start a "deep" battery test -| test.battery.stop | Stop the battery test -| test.system.start | Start a system test -| calibrate.start | Start runtime calibration -| calibrate.stop | Stop runtime calibration -| bypass.start | Put the UPS in bypass mode -| bypass.stop | Take the UPS out of bypass mode -| reset.input.minmax | Reset minimum and maximum input voltage status -| reset.watchdog | Reset watchdog timer (forced reboot of load) -| beeper.enable | Enable UPS beeper/buzzer -| beeper.disable | Disable UPS beeper/buzzer -| beeper.mute | Temporarily mute UPS beeper/buzzer -| beeper.toggle | Toggle UPS beeper/buzzer -| outlet.n.shutdown.return | Turn off the outlet possibly after a delay - and return when power is back -| outlet.n.load.off | Turn off the outlet immediately -| outlet.n.load.on | Turn on the outlet immediately -| outlet.n.load.cycle | Power cycle the outlet immediately -| outlet.n.load.off.delay | Turn off the outlet possibly after a delay -| outlet.n.load.on.delay | Turn on the outlet possibly after a delay -| outlet.n.load.cycle.delay | Power cycle the outlet possibly after a delay -| outlet.n.shutdown.return | Turn off the outlet and return when power is back -|=============================================================================== +| shutdown.stayoff | Turn off the load possibly after a delay + and remain off even if power returns +| shutdown.stop | Stop a shutdown in progress +| shutdown.reboot | Shut down the load briefly while rebooting the UPS +| shutdown.reboot.graceful | After a delay, shut down the load briefly + while rebooting the UPS +| test.panel.start | Start testing the UPS panel +| test.panel.stop | Stop a UPS panel test +| test.failure.start | Start a simulated power failure +| test.failure.stop | Stop simulating a power failure +| test.battery.start | Start a battery test +| test.battery.start.quick | Start a "quick" battery test +| test.battery.start.deep | Start a "deep" battery test +| test.battery.stop | Stop the battery test +| test.system.start | Start a system test +| calibrate.start | Start runtime calibration +| calibrate.stop | Stop runtime calibration +| bypass.start | Put the UPS in bypass mode +| bypass.stop | Take the UPS out of bypass mode +| reset.input.minmax | Reset minimum and maximum input voltage status +| reset.watchdog | Reset watchdog timer (forced reboot of load) +| beeper.enable | Enable UPS beeper/buzzer +| beeper.disable | Disable UPS beeper/buzzer +| beeper.mute | Temporarily mute UPS beeper/buzzer +| beeper.toggle | Toggle UPS beeper/buzzer +| outlet.n.shutdown.return | Turn off the outlet possibly after a delay + and return when power is back +| outlet.n.load.off | Turn off the outlet immediately +| outlet.n.load.on | Turn on the outlet immediately +| outlet.n.load.cycle | Power cycle the outlet immediately +| outlet.n.shutdown.return | Turn off the outlet and return when power is back +|======================================================================== diff --git a/docs/nut.dict b/docs/nut.dict index 111b886fe3..db8f027385 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 2434 utf-8 +personal_ws-1.1 en 2440 utf-8 AAS ACFAIL ACFREQ @@ -12,6 +12,7 @@ ADDRINFO ADK ADKK AEC +aec AEG AES AGM @@ -136,6 +137,7 @@ ByPass CA's CABAC CAs +cb CBLimit CCC CCCC @@ -203,6 +205,7 @@ DDDDD DDF DEADTIME DEBUGOUT +dedb DELCMD DELENUM DELINFO @@ -999,6 +1002,7 @@ TIOCMBIC TIOCMBIS TLS TODO +TRACKINGDELAY TRYSSL TSR TST @@ -2318,6 +2322,7 @@ upserror upsfetch upsgone upsh +upshandler upsidentmodel upsimage upsload diff --git a/docs/sock-protocol.txt b/docs/sock-protocol.txt index 944d3ef7fd..f951aae6d0 100644 --- a/docs/sock-protocol.txt +++ b/docs/sock-protocol.txt @@ -146,6 +146,18 @@ status information once this has been sent. This will be sent in the beginning of a dump if the data is stale, and may be repeated. It is cleared by DATAOK. +TRACKING +~~~~~~~~ + + TRACKING + +This is sent in response to an INSTCMD or SET VAR that includes a TRACKING, +upon completion of request execution by the driver. is the integer +return value from the driver handlers instcmd and setvar (see +drivers/upshandler.h). The server is in charge of translating these codes into +strings, as per docs/net-protocol.txt GET TRACKING. + + Commands sent by the server --------------------------- @@ -166,16 +178,30 @@ server must not be passed on to the clients when this happens. INSTCMD ~~~~~~~ - INSTCMD + INSTCMD [] [TRACKING ] INSTCMD panel.test.start + INSTCMD load.off 10 + INSTCMD load.on 10 TRACKING 1bd31808-cb49-4aec-9d75-d056e6f018d2 + +NOTE: +* is an additional and optional parameter for the command, +* "TRACKING " can be provided to track commands execution status, if +TRACKING was set to ON on upsd. In this case, driver will later return +the execution status, using TRACKING. SET ~~~ - SET "" + SET "" [TRACKING ] SET ups.id "Data room" + SET ups.id "Data room" TRACKING 2dedb58a-3b91-4fab-831f-c8af4b90760a + +NOTE: +* "TRACKING " can be provided to track commands execution status, if +TRACKING was set to ON on upsd. In this case, driver will later return +the execution status, using TRACKING. DUMPALL ~~~~~~~ diff --git a/drivers/dstate.c b/drivers/dstate.c index 276da18b63..51e41a052a 100644 --- a/drivers/dstate.c +++ b/drivers/dstate.c @@ -201,6 +201,7 @@ static int send_to_one(conn_t *conn, const char *fmt, ...) ret = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); + upsdebugx(2, "%s: sending %.*s", __func__, (int)strcspn(buf, "\n"), buf); if (ret < 1) { upsdebugx(2, "%s: nothing to write", __func__); return 1; @@ -356,6 +357,12 @@ static int cmd_dump_conn(conn_t *conn) return 1; } + +static void send_tracking(conn_t *conn, const char *id, int value) +{ + send_to_one(conn, "TRACKING %s %i\n", id, value); +} + static int sock_arg(conn_t *conn, int numarg, char **arg) { if (numarg < 1) { @@ -394,20 +401,40 @@ static int sock_arg(conn_t *conn, int numarg, char **arg) return 0; } - /* INSTCMD []*/ + /* INSTCMD [] [TRACKING ] */ if (!strcasecmp(arg[0], "INSTCMD")) { + int ret; + char *cmdname = arg[1]; + char *cmdparam = NULL; + char *cmdid = NULL; + + /* Check if and TRACKING were provided */ + if (numarg == 3) { + cmdparam = arg[2]; + } else if (numarg == 4 && !strcasecmp(arg[2], "TRACKING")) { + cmdid = arg[3]; + } else if (numarg == 5 && !strcasecmp(arg[3], "TRACKING")) { + cmdparam = arg[2]; + cmdid = arg[4]; + } else if (numarg != 2) { + upslogx(LOG_NOTICE, "Malformed INSTCMD request"); + return 0; + } + + if (cmdid) + upsdebugx(3, "%s: TRACKING = %s", __func__, cmdid); /* try the new handler first if present */ if (upsh.instcmd) { - if (numarg > 2) { - upsh.instcmd(arg[1], arg[2]); - return 1; - } + ret = upsh.instcmd(cmdname, cmdparam); - upsh.instcmd(arg[1], NULL); + /* send back execution result */ + if (cmdid) + send_tracking(conn, cmdid, ret); + + /* The command was handled, status is a separate consideration */ return 1; } - upslogx(LOG_NOTICE, "Got INSTCMD, but driver lacks a handler"); return 1; } @@ -416,12 +443,33 @@ static int sock_arg(conn_t *conn, int numarg, char **arg) return 0; } - /* SET */ + /* SET [TRACKING ] */ if (!strcasecmp(arg[0], "SET")) { + int ret; + char *setid = NULL; + + /* Check if TRACKING was provided */ + if (numarg == 5) { + if (!strcasecmp(arg[3], "TRACKING")) { + setid = arg[4]; + } + else { + upslogx(LOG_NOTICE, "Got SET with unsupported parameters (%s/%s)", + arg[3], arg[4]); + return 0; + } + upsdebugx(3, "%s: TRACKING = %s", __func__, setid); + } /* try the new handler first if present */ if (upsh.setvar) { - upsh.setvar(arg[1], arg[2]); + ret = upsh.setvar(arg[1], arg[2]); + + /* send back execution result */ + if (setid) + send_tracking(conn, setid, ret); + + /* The command was handled, status is a separate consideration */ return 1; } diff --git a/drivers/upshandler.h b/drivers/upshandler.h index ca9a349594..fea10bc20c 100644 --- a/drivers/upshandler.h +++ b/drivers/upshandler.h @@ -25,15 +25,15 @@ enum { STAT_INSTCMD_HANDLED = 0, /* completed successfully */ STAT_INSTCMD_UNKNOWN, /* unspecified error */ STAT_INSTCMD_INVALID, /* invalid command */ - STAT_INSTCMD_FAILED /* command failed */ + STAT_INSTCMD_FAILED /* command failed */ }; /* return values for setvar */ enum { STAT_SET_HANDLED = 0, /* completed successfully */ - STAT_SET_UNKNOWN, /* unspecified error */ - STAT_SET_INVALID, /* not writeable */ - STAT_SET_FAILED /* writing failed */ + STAT_SET_UNKNOWN, /* unspecified error */ + STAT_SET_INVALID, /* not writeable */ + STAT_SET_FAILED /* writing failed */ }; /* structure for funcs that get called by msg parse routine */ diff --git a/include/common.h b/include/common.h index b1fb53ed2d..248dadd374 100644 --- a/include/common.h +++ b/include/common.h @@ -53,6 +53,12 @@ extern "C" { extern const char *UPS_VERSION; +/** @brief Default timeout (in seconds) for network operations, as used by `upsclient` and `nut-scanner`. */ +#define DEFAULT_NETWORK_TIMEOUT 5 + +/** @brief Default timeout (in seconds) for retrieving the result of a `TRACKING`-enabled operation (e.g. `INSTCMD`, `SET VAR`). */ +#define DEFAULT_TRACKING_TIMEOUT 10 + /* get the syslog ready for us */ void open_syslog(const char *progname); @@ -128,6 +134,9 @@ char * get_libname(const char* base_libname); #define SMALLBUF 512 #define LARGEBUF 1024 +/** @brief (Minimum) Size that a string must have to hold a UUID4 (i.e. UUID4 length + the terminating null character). */ +#define UUID4_LEN 37 + /* Provide declarations for getopt() global variables */ #ifdef NEED_GETOPT_H diff --git a/scripts/augeas/nutupsdconf.aug.in b/scripts/augeas/nutupsdconf.aug.in index d3aef1d300..4c200039e8 100644 --- a/scripts/augeas/nutupsdconf.aug.in +++ b/scripts/augeas/nutupsdconf.aug.in @@ -39,6 +39,7 @@ let comment = Util.comment let path = word let upsd_maxage = [ opt_spc . key "MAXAGE" . sep_spc . store num . eol ] +let upsd_trackingdelay = [ opt_spc . key "TRACKINGDELAY" . sep_spc . store num . eol ] let upsd_statepath = [ opt_spc . key "STATEPATH" . sep_spc . store path . eol ] let upsd_listen = [ opt_spc . key "LISTEN" . sep_spc . [ label "interface" . store ip ] @@ -49,13 +50,14 @@ let upsd_certfile = [ opt_spc . key "CERTFILE" . sep_spc . store path . eol ] (************************************************************************ * MAXAGE seconds + * TRACKINGDELAY seconds * STATEPATH path * LISTEN interface port * Multiple LISTEN addresses may be specified. The default is to bind to 0.0.0.0 if no LISTEN addresses are specified. * LISTEN 127.0.0.1 LISTEN 192.168.50.1 LISTEN ::1 LISTEN 2001:0db8:1234:08d3:1319:8a2e:0370:7344 * *************************************************************************) -let upsd_other = upsd_maxage | upsd_statepath | upsd_listen_list | upsd_maxconn | upsd_certfile +let upsd_other = upsd_maxage | upsd_trackingdelay | upsd_statepath | upsd_listen_list | upsd_maxconn | upsd_certfile let upsd_lns = (upsd_other|comment|empty)* diff --git a/scripts/augeas/tests/test_nut.aug b/scripts/augeas/tests/test_nut.aug index 4153ed7cf5..eb79b07bd3 100644 --- a/scripts/augeas/tests/test_nut.aug +++ b/scripts/augeas/tests/test_nut.aug @@ -27,6 +27,7 @@ test NutUpsConf.ups_lns get ups_conf = let upsd_conf = " MAXAGE 30 +TRACKINGDELAY 600 LISTEN 0.0.0.0 3493 MAXCONN 1024 " @@ -34,6 +35,7 @@ MAXCONN 1024 test NutUpsdConf.upsd_lns get upsd_conf = { } { "MAXAGE" = "30" } + { "TRACKINGDELAY" = "600" } { "LISTEN" { "interface" = "0.0.0.0" } { "port" = "3493" } } diff --git a/server/conf.c b/server/conf.c index aea8220cd7..e6046ed7c9 100644 --- a/server/conf.c +++ b/server/conf.c @@ -23,6 +23,7 @@ #include "sstate.h" #include "user.h" #include "netssl.h" +#include ups_t *upstable = NULL; int num_ups = 0; @@ -121,14 +122,38 @@ static int parse_upsd_conf_args(int numargs, char **arg) /* MAXAGE */ if (!strcmp(arg[0], "MAXAGE")) { - maxage = atoi(arg[1]); - return 1; + if (isdigit(arg[1])) { + maxage = atoi(arg[1]); + return 1; + } + else { + upslogx(LOG_ERR, "MAXAGE has non numeric value (%s)!", arg[1]); + return 0; + } + } + + /* TRACKINGDELAY */ + if (!strcmp(arg[0], "TRACKINGDELAY")) { + if (isdigit(arg[1])) { + tracking_delay = atoi(arg[1]); + return 1; + } + else { + upslogx(LOG_ERR, "TRACKINGDELAY has non numeric value (%s)!", arg[1]); + return 0; + } } /* MAXCONN */ if (!strcmp(arg[0], "MAXCONN")) { - maxconn = atoi(arg[1]); - return 1; + if (isdigit(arg[1])) { + maxconn = atoi(arg[1]); + return 1; + } + else { + upslogx(LOG_ERR, "MAXCONN has non numeric value (%s)!", arg[1]); + return 0; + } } /* STATEPATH */ @@ -162,8 +187,14 @@ static int parse_upsd_conf_args(int numargs, char **arg) #ifdef WITH_CLIENT_CERTIFICATE_VALIDATION /* CERTREQUEST (0 | 1 | 2) */ if (!strcmp(arg[0], "CERTREQUEST")) { - certrequest = atoi(arg[1]); - return 1; + if (isdigit(arg[1])) { + certrequest = atoi(arg[1]); + return 1; + } + else { + upslogx(LOG_ERR, "CERTREQUEST has non numeric value (%s)!", arg[1]); + return 0; + } } #endif /* WITH_CLIENT_CERTIFICATE_VALIDATION */ #endif /* WITH_OPENSSL | WITH_NSS */ diff --git a/server/netget.c b/server/netget.c index 5ab41c3f3a..55a27a429c 100644 --- a/server/netget.c +++ b/server/netget.c @@ -216,6 +216,25 @@ static void get_var(nut_ctype_t *client, const char *upsname, const char *var) void net_get(nut_ctype_t *client, int numarg, const char **arg) { + if (numarg < 1) { + send_err(client, NUT_ERR_INVALID_ARGUMENT); + return; + } + + /* GET TRACKING [ID] */ + if (!strcasecmp(arg[0], "TRACKING")) { + if (numarg < 2) { + sendback(client, "%s\n", (client->tracking) ? "ON" : "OFF"); + } + else { + if (client->tracking) + sendback(client, "%s\n", tracking_get(arg[1])); + else + send_err(client, NUT_ERR_FEATURE_NOT_CONFIGURED); + } + return; + } + if (numarg < 2) { send_err(client, NUT_ERR_INVALID_ARGUMENT); return; diff --git a/server/netinstcmd.c b/server/netinstcmd.c index c21d7ec1d7..3c4ccc8212 100644 --- a/server/netinstcmd.c +++ b/server/netinstcmd.c @@ -29,9 +29,9 @@ #include "netinstcmd.h" static void send_instcmd(nut_ctype_t *client, const char *upsname, - const char *cmdname, const char *value) + const char *cmdname, const char *value, const char *tracking_id) { - int found; + int found, have_tracking_id = 0; upstype_t *ups; const cmdlist_t *ctmp; char sockcmd[SMALLBUF], esc[SMALLBUF]; @@ -70,20 +70,30 @@ static void send_instcmd(nut_ctype_t *client, const char *upsname, return; } - /* see if the user has also passed a value for this command */ - if (value != NULL) { - upslogx(LOG_INFO, "Instant command: %s@%s did %s with value \"%s\" on %s", - client->username, client->addr, cmdname, value, ups->name); + /* Format the base command */ + snprintf(sockcmd, sizeof(sockcmd), "INSTCMD %s", cmdname); - snprintf(sockcmd, sizeof(sockcmd), "INSTCMD %s %s\n", - cmdname, pconf_encode(value, esc, sizeof(esc))); + /* see if the user has also passed a value for this command */ + if (value != NULL) + snprintfcat(sockcmd, sizeof(sockcmd), " %s", pconf_encode(value, esc, sizeof(esc))); + + /* see if the user want execution tracking for this command */ + if (tracking_id && *tracking_id) { + snprintfcat(sockcmd, sizeof(sockcmd), " TRACKING %s", tracking_id); + /* Add an entry in the tracking structure */ + tracking_add(tracking_id); + have_tracking_id = 1; } - else { - upslogx(LOG_INFO, "Instant command: %s@%s did %s on %s", - client->username, client->addr, cmdname, ups->name); - snprintf(sockcmd, sizeof(sockcmd), "INSTCMD %s\n", cmdname); - } + /* add EOL */ + snprintfcat(sockcmd, sizeof(sockcmd), "\n"); + + upslogx(LOG_INFO, "Instant command: %s@%s did %s%s%s on %s (tracking ID: %s)", + client->username, client->addr, cmdname, + (value != NULL)?" with value ":"", + (value != NULL)?value:"", + ups->name, + (have_tracking_id) ? tracking_id : "disabled"); if (!sstate_sendline(ups, sockcmd)) { upslogx(LOG_INFO, "Set command send failed"); @@ -91,18 +101,38 @@ static void send_instcmd(nut_ctype_t *client, const char *upsname, return; } - /* FIXME: need to retrieve the cookie number */ - sendback(client, "OK\n"); + /* return the result, possibly including tracking_id */ + if (have_tracking_id) + sendback(client, "OK TRACKING %s\n", tracking_id); + else + sendback(client, "OK\n"); } void net_instcmd(nut_ctype_t *client, int numarg, const char **arg) { + const char *devname = NULL; + const char *cmdname = NULL; + const char *cmdparam = NULL; + char tracking_id[UUID4_LEN] = ""; + if (numarg < 2) { send_err(client, NUT_ERR_INVALID_ARGUMENT); return; } - - /* INSTCMD []*/ - send_instcmd(client, arg[0], arg[1], (numarg == 3)?arg[2]:NULL); + + /* INSTCMD [cmdparam] */ + /* Check arguments */ + devname = arg[0]; + cmdname = arg[1]; + if (numarg == 3) + cmdparam = arg[2]; + + if (client->tracking) { + /* Generate a tracking ID, if client requested status tracking */ + nut_uuid_v4(tracking_id); + } + + send_instcmd(client, devname, cmdname, cmdparam, tracking_id); + return; } diff --git a/server/netset.c b/server/netset.c index b88bb40f7d..a69aa96ff4 100644 --- a/server/netset.c +++ b/server/netset.c @@ -28,13 +28,14 @@ #include "netset.h" static void set_var(nut_ctype_t *client, const char *upsname, const char *var, - const char *newval) + const char *newval, const char *tracking_id) { upstype_t *ups; const char *val; const enum_t *etmp; const range_t *rtmp; char cmd[SMALLBUF], esc[SMALLBUF]; + int have_tracking_id = 0; ups = get_ups_ptr(upsname); @@ -134,31 +135,88 @@ static void set_var(nut_ctype_t *client, const char *upsname, const char *var, /* must be OK now */ - upslogx(LOG_INFO, "Set variable: %s@%s set %s on %s to %s", - client->username, client->addr, var, ups->name, newval); - - snprintf(cmd, sizeof(cmd), "SET %s \"%s\"\n", + snprintf(cmd, sizeof(cmd), "SET %s \"%s\"", var, pconf_encode(newval, esc, sizeof(esc))); + /* see if the user want execution tracking for this command */ + if (tracking_id && *tracking_id) { + snprintfcat(cmd, sizeof(cmd), " TRACKING %s", tracking_id); + /* Add an entry in the tracking structure */ + tracking_add(tracking_id); + have_tracking_id = 1; + } + + /* add EOL */ + snprintfcat(cmd, sizeof(cmd), "\n"); + + upslogx(LOG_INFO, "Set variable: %s@%s set %s on %s to %s (tracking ID: %s)", + client->username, client->addr, var, ups->name, newval, + (have_tracking_id) ? tracking_id : "disabled"); + if (!sstate_sendline(ups, cmd)) { upslogx(LOG_INFO, "Set command send failed"); send_err(client, NUT_ERR_SET_FAILED); return; } - sendback(client, "OK\n"); + /* return the result, possibly including tracking_id */ + if (have_tracking_id) + sendback(client, "OK TRACKING %s\n", tracking_id); + else + sendback(client, "OK\n"); } void net_set(nut_ctype_t *client, int numarg, const char **arg) { - if (numarg < 4) { + char tracking_id[UUID4_LEN] = ""; + + /* Base verification, to ensure that we have at least the SET parameter */ + if (numarg < 2) { send_err(client, NUT_ERR_INVALID_ARGUMENT); return; } /* SET VAR UPS VARNAME VALUE */ if (!strcasecmp(arg[0], "VAR")) { - set_var(client, arg[1], arg[2], arg[3]); + if (numarg < 4) { + send_err(client, NUT_ERR_INVALID_ARGUMENT); + return; + } + + if (client->tracking) { + /* Generate a tracking ID, if client requested status tracking */ + nut_uuid_v4(tracking_id); + } + + set_var(client, arg[1], arg[2], arg[3], tracking_id); + + return; + } + + /* SET TRACKING VALUE */ + if (!strcasecmp(arg[0], "TRACKING")) { + if (!strcasecmp(arg[1], "ON")) { + /* general enablement along with for this client */ + client->tracking = tracking_enable(); + } + else if (!strcasecmp(arg[1], "OFF")) { + /* disable status tracking for this client first */ + client->tracking = 0; + /* then only disable the general one if no other clients use it! + * Note: don't call tracking_free() since we want info to + * persist, and tracking_cleanup() takes care of cleaning */ + tracking_disable(); + } + else { + send_err(client, NUT_ERR_INVALID_ARGUMENT); + return; + } + upsdebugx(1, "%s: TRACKING general %s, client %s.", __func__, + tracking_is_enabled() ? "enabled" : "disabled", + client->tracking ? "enabled" : "disabled"); + + sendback(client, "OK\n"); + return; } diff --git a/server/nut_ctype.h b/server/nut_ctype.h index c2302aadb8..54184d2baa 100644 --- a/server/nut_ctype.h +++ b/server/nut_ctype.h @@ -45,6 +45,9 @@ typedef struct nut_ctype_s { char *loginups; char *password; char *username; + /* per client status info for commands and settings + * (disabled by default) */ + int tracking; #ifdef WITH_OPENSSL SSL *ssl; diff --git a/server/sstate.c b/server/sstate.c index 34e1b2de9a..0365cd30f6 100644 --- a/server/sstate.c +++ b/server/sstate.c @@ -25,6 +25,7 @@ #include "timehead.h" #include "sstate.h" +#include "upsd.h" #include "upstype.h" #include @@ -116,6 +117,18 @@ static int parse_args(upstype_t *ups, int numargs, char **arg) return 1; } + /* TRACKING */ + if (!strcasecmp(arg[0], "TRACKING")) { + tracking_set(arg[1], arg[2]); + upsdebugx(1, "TRACKING: ID %s status %s", arg[1], arg[2]); + + /* log actual result of instcmd / setvar */ + if (strncmp(arg[2], "PENDING", 7) != 0) { + upslogx(LOG_INFO, "tracking ID: %s\tresult: %s", arg[1], tracking_get(arg[1])); + } + return 1; + } + if (numargs < 4) return 0; @@ -388,7 +401,7 @@ int sstate_sendline(upstype_t *ups, const char *buf) ret = write(ups->sock_fd, buf, strlen(buf)); if (ret == (int)strlen(buf)) { - return 1; + return 1; } upslog_with_errno(LOG_NOTICE, "Send to UPS [%s] failed", ups->name); diff --git a/server/upsd.c b/server/upsd.c index a490608b2d..a49b51110f 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -4,6 +4,7 @@ 1999 Russell Kroll 2008 Arjen de Korte 2011 - 2012 Arnaud Quette + 2019 Eaton (author: Arnaud Quette ) 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 @@ -53,6 +54,9 @@ int deny_severity = LOG_WARNING; /* default 15 seconds before data is marked stale */ int maxage = 15; + /* default to 1h before cleaning up status tracking entries */ + int tracking_delay = 3600; + /* preloaded to {OPEN_MAX} in main, can be overridden via upsd.conf */ int maxconn = 0; @@ -84,6 +88,28 @@ typedef struct { void *data; } handler_t; + +/* Commands and settings status tracking */ + +/* general enable/disable status info for commands and settings + * (disabled by default) + * Note that only client that requested it will have it enabled + * (see nut_ctype.h) */ +static int tracking_enabled = 0; + +/* Commands and settings status tracking structure */ +typedef struct tracking_s { + char *id; + int status; + time_t request_time; /* for cleanup */ + /* doubly linked list */ + struct tracking_s *prev; + struct tracking_s *next; +} tracking_t; + +static tracking_t *tracking_list = NULL; + + /* pollfd */ static struct pollfd *fds = NULL; static handler_t *handler = NULL; @@ -94,6 +120,11 @@ static char pidfn[SMALLBUF]; /* set by signal handlers */ static int reload_flag = 0, exit_flag = 0; +/* Minimalistic support for UUID v4 */ +/* Ref: RFC 4122 https://tools.ietf.org/html/rfc4122#section-4.1.2 */ +#define UUID4_BYTESIZE 16 + + static const char *inet_ntopW (struct sockaddr_storage *s) { static char str[40]; @@ -480,6 +511,8 @@ static void client_connect(stype_t *server) client->addr = xstrdup(inet_ntopW(&csock)); + client->tracking = 0; + pconf_init(&client->ctx, NULL); if (firstclient) { @@ -644,6 +677,7 @@ static void upsd_cleanup(void) server_free(); client_free(); driver_free(); + tracking_free(); free(statepath); free(datapath); @@ -672,6 +706,219 @@ void poll_reload(void) handler = xrealloc(handler, maxconn * sizeof(*handler)); } +/* instant command and setvar status tracking */ + +/* allocate a new status tracking entry */ +int tracking_add(const char *id) +{ + tracking_t *item; + + if ((!tracking_enabled) || (!id)) + return 0; + + item = xcalloc(1, sizeof(*item)); + + item->id = xstrdup(id); + item->status = STAT_PENDING; + time(&item->request_time); + + if (tracking_list) { + tracking_list->prev = item; + item->next = tracking_list; + } + + tracking_list = item; + + return 1; +} + +/* set status of a specific tracking entry */ +int tracking_set(const char *id, const char *value) +{ + tracking_t *item, *next_item; + + /* sanity checks */ + if ((!tracking_list) || (!id) || (!value)) + return 0; + + for (item = tracking_list; item; item = next_item) { + + next_item = item->next; + + if (!strcasecmp(item->id, id)) { + item->status = atoi(value); + return 1; + } + } + + return 0; /* id not found! */ +} + +/* free a specific tracking entry */ +int tracking_del(const char *id) +{ + tracking_t *item, *next_item; + + /* sanity check */ + if ((!tracking_list) || (!id)) + return 0; + + upsdebugx(3, "%s: deleting id %s", __func__, id); + + for (item = tracking_list; item; item = next_item) { + + next_item = item->next; + + if (strcasecmp(item->id, id)) + continue; + + if (item->prev) + item->prev->next = item->next; + else + /* deleting first entry */ + tracking_list = item->next; + + if (item->next) + item->next->prev = item->prev; + + free(item->id); + free(item); + + return 1; + + } + + return 0; /* id not found! */ +} + +/* free all status tracking entries */ +void tracking_free(void) +{ + tracking_t *item, *next_item; + + /* sanity check */ + if (!tracking_list) + return; + + upsdebugx(3, "%s", __func__); + + for (item = tracking_list; item; item = next_item) { + next_item = item->next; + tracking_del(item->id); + } +} + +/* cleanup status tracking entries according to their age and tracking_delay */ +void tracking_cleanup(void) +{ + tracking_t *item, *next_item; + time_t now; + + /* sanity check */ + if (!tracking_list) + return; + + time(&now); + + upsdebugx(3, "%s", __func__); + + for (item = tracking_list; item; item = next_item) { + + next_item = item->next; + + if (difftime(now, item->request_time) > tracking_delay) { + tracking_del(item->id); + } + } +} + +/* get status of a specific tracking entry */ +char *tracking_get(const char *id) +{ + tracking_t *item, *next_item; + + /* sanity checks */ + if ((!tracking_list) || (!id)) + return "ERR UNKNOWN"; + + for (item = tracking_list; item; item = next_item) { + + next_item = item->next; + + if (strcasecmp(item->id, id)) + continue; + + switch (item->status) + { + case STAT_PENDING: + return "PENDING"; + case STAT_HANDLED: + return "SUCCESS"; + case STAT_UNKNOWN: + return "ERR UNKNOWN"; + case STAT_INVALID: + return "ERR INVALID-ARGUMENT"; + case STAT_FAILED: + return "ERR FAILED"; + } + } + + return "ERR UNKNOWN"; /* id not found! */ +} + +/* enable general status tracking (tracking_enabled) and return its value (1). */ +int tracking_enable(void) +{ + tracking_enabled = 1; + + return tracking_enabled; +} + +/* disable general status tracking only if no client use it anymore. + * return the new value for tracking_enabled */ +int tracking_disable(void) +{ + nut_ctype_t *client, *cnext; + + for (client = firstclient; client; client = cnext) { + cnext = client->next; + if (client->tracking == 1) + return 1; + } + return 0; +} + +/* return current general status of tracking (tracking_enabled). */ +int tracking_is_enabled(void) +{ + return tracking_enabled; +} + +/* UUID v4 basic implementation + * Note: 'dest' must be at least `UUID4_LEN` long */ +int nut_uuid_v4(char *uuid_str) +{ + size_t i; + uint8_t nut_uuid[UUID4_BYTESIZE]; + + if (!uuid_str) + return 0; + + for (i = 0; i < UUID4_BYTESIZE; i++) + nut_uuid[i] = (unsigned)rand() + (unsigned)rand(); + + /* set variant and version */ + nut_uuid[6] = (nut_uuid[6] & 0x0F) | 0x40; + nut_uuid[8] = (nut_uuid[8] & 0x3F) | 0x80; + + return snprintf(uuid_str, UUID4_LEN, + "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", + nut_uuid[0], nut_uuid[1], nut_uuid[2], nut_uuid[3], + nut_uuid[4], nut_uuid[5], nut_uuid[6], nut_uuid[7], + nut_uuid[8], nut_uuid[9], nut_uuid[10], nut_uuid[11], + nut_uuid[12], nut_uuid[13], nut_uuid[14], nut_uuid[15]); +} + /* service requests and check on new data */ static void mainloop(void) { @@ -690,6 +937,9 @@ static void mainloop(void) reload_flag = 0; } + /* cleanup instcmd/setvar status tracking entries if needed */ + tracking_cleanup(); + /* scan through driver sockets */ for (ups = firstups; ups && (nfds < maxconn); ups = ups->next) { @@ -722,6 +972,7 @@ static void mainloop(void) if (difftime(now, client->last_heard) > 60) { /* shed clients after 1 minute of inactivity */ + /* FIXME: create an upsd.conf parameter (CLIENT_INACTIVITY_DELAY) */ client_disconnect(client); continue; } diff --git a/server/upsd.h b/server/upsd.h index 2b3a3a74d1..2cd3eb399e 100644 --- a/server/upsd.h +++ b/server/upsd.h @@ -68,9 +68,30 @@ void server_free(void); void check_perms(const char *fn); +/* return values for instcmd / setvar status tracking, + * mapped on drivers/upshandler.h, apart from STAT_PENDING (initial state) */ +enum { + STAT_PENDING = -1, /* not yet completed */ + STAT_HANDLED = 0, /* completed successfully (NUT_SUCCESS or "OK") */ + STAT_UNKNOWN, /* unspecified error (NUT_ERR_UNKNOWN) */ + STAT_INVALID, /* invalid command/setvar (NUT_ERR_INVALID_ARGUMENT) */ + STAT_FAILED /* command/setvar failed (NUT_ERR_INSTCMD_FAILED / NUT_ERR_SET_FAILED) */ +}; + +/* Commands and settings status tracking functions */ +int tracking_add(const char *id); +int tracking_set(const char *id, const char *value); +int tracking_del(const char *id); +void tracking_free(void); +void tracking_cleanup(void); +char *tracking_get(const char *id); +int tracking_enable(void); +int tracking_disable(void); +int tracking_is_enabled(void); + /* declarations from upsd.c */ -extern int maxage, maxconn; +extern int maxage, maxconn, tracking_delay; extern char *statepath, *datapath; extern upstype_t *firstups; extern nut_ctype_t *firstclient; @@ -91,6 +112,10 @@ extern nut_ctype_t *firstclient; #define shutdown_how 2 #endif +/* UUID v4 generation function + * Note: 'dest' must be at least `UUID4_LEN` long */ +int nut_uuid_v4(char *uuid_str); + #ifdef __cplusplus /* *INDENT-OFF* */ } diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 8b41a3dbf9..25e39d9875 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -38,8 +38,6 @@ #include "nut-scan.h" -#define DEFAULT_TIMEOUT 5 - #define ERR_BAD_OPTION (-1) const char optstring[] = "?ht:s:e:E:c:l:u:W:X:w:x:p:b:B:d:L:CUSMOAm:NPqIVaD"; @@ -84,7 +82,7 @@ const struct option longopts[] = static nutscan_device_t *dev[TYPE_END]; -static long timeout = DEFAULT_TIMEOUT*1000*1000; /* in usec */ +static long timeout = DEFAULT_NETWORK_TIMEOUT * 1000 * 1000; /* in usec */ static char * start_ip = NULL; static char * end_ip = NULL; static char * port = NULL; @@ -169,7 +167,7 @@ void show_usage() printf(" -E, --eaton_serial : Scan serial Eaton devices (XCP, SHUT and Q1).\n"); printf("\nNetwork specific options:\n"); - printf(" -t, --timeout : network operation timeout (default %d).\n",DEFAULT_TIMEOUT); + printf(" -t, --timeout : network operation timeout (default %d).\n", DEFAULT_NETWORK_TIMEOUT); printf(" -s, --start_ip : First IP address to scan.\n"); printf(" -e, --end_ip : Last IP address to scan.\n"); printf(" -m, --mask_cidr : Give a range of IP using CIDR notation.\n"); @@ -270,8 +268,8 @@ int main(int argc, char *argv[]) case 't': timeout = atol(optarg)*1000*1000; /*in usec*/ if( timeout == 0 ) { - fprintf(stderr,"Illegal timeout value, using default %ds\n", DEFAULT_TIMEOUT); - timeout = DEFAULT_TIMEOUT*1000*1000; + fprintf(stderr,"Illegal timeout value, using default %ds\n", DEFAULT_NETWORK_TIMEOUT); + timeout = DEFAULT_NETWORK_TIMEOUT * 1000 * 1000; } break; case 's':