Skip to content

Commit 6686e1d

Browse files
committed
clients/upsmon.c, clients/upssched.c, docs: handle NOTIFYCMD and CMDSCRIPT values strictly as paths [#3499]
Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
1 parent 9b8e1c0 commit 6686e1d

11 files changed

Lines changed: 135 additions & 56 deletions

File tree

NEWS.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ https://github.com/networkupstools/nut/milestone/13
135135
success of SSL initialization with both backends is more diligently
136136
tracked) should now not cause exit of the client if secure connection
137137
is not required by its configuration in the first place. [#3420]
138+
* Potentially a breaking change for existing deployments with `upsmon` and
139+
`upssched` integration: the `CMDSCRIPT` and `NOTIFYCMD` definitions were
140+
revised to mean an exact program name (possibly with spaces in the value),
141+
not a shell scriptlet. Documentation in earlier NUT releases implied that
142+
arguments could be passed here -- this is no longer true. If you need to
143+
pass special arguments to a notification program, please write a small
144+
script to do so and specify its path here. The `SHUTDOWNCMD` is still
145+
a scriptlet, as far as specifying command arguments is concerned. [#3499]
138146

139147
- `NUT-Monitor` Python GUI client:
140148
* Fixed Qt tray tooltips to render in plain text, not rich text which

UPGRADING.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ Changes from 2.8.5 to 2.8.6
4646
if the requested value is larger than what is allowed (minus some reserve
4747
for configuration files and other use-cases). [issue #3365]
4848
49+
- Potentially a breaking change for existing deployments with `upsmon` and
50+
`upssched` integration: the `CMDSCRIPT` and `NOTIFYCMD` definitions were
51+
revised to mean an exact program name (possibly with spaces in the value),
52+
not a shell scriptlet. Documentation in earlier NUT releases implied that
53+
arguments could be passed here -- this is no longer true. If you need to
54+
pass special arguments to a notification program, please write a small
55+
script to do so and specify its path here. The `SHUTDOWNCMD` is still
56+
a scriptlet, as far as specifying command arguments is concerned. [#3499]
4957
5058
Changes from 2.8.4 to 2.8.5
5159
---------------------------

clients/upsmon.c

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -238,25 +238,18 @@ static void wall(const char *text)
238238
pclose(wf);
239239
#else /* WIN32 */
240240
# define MESSAGE_CMD "message.exe"
241-
char *command;
242-
243-
/* first +1 is for the space between message and text
244-
* second +1 is for trailing 0
245-
* +2 is for ""
246-
*/
247-
size_t commandsz = strlen(MESSAGE_CMD) + 1 + 2 + strlen(text) + 1;
241+
char *argv[3];
242+
int ret;
248243

249-
command = malloc (commandsz);
250-
if (command == NULL) {
251-
upslog_with_errno(LOG_NOTICE, "Not enough memory for wall");
252-
return;
253-
}
244+
argv[0] = MESSAGE_CMD;
245+
argv[1] = (char *)text;
246+
argv[2] = NULL;
254247

255-
snprintf(command, commandsz, "%s \"%s\"", MESSAGE_CMD, text);
256-
if (system(command) != 0) {
257-
upslog_with_errno(LOG_NOTICE, "Can't invoke wall");
248+
upsdebugx(6, "%s: executing %s with message: %s", __func__, MESSAGE_CMD, NUT_STRARG(text));
249+
ret = _spawnvp(_P_WAIT, MESSAGE_CMD, (const char * const *)argv);
250+
if (ret != 0) {
251+
upslog_with_errno(LOG_NOTICE, "Can't invoke wall (status: %d)", ret);
258252
}
259-
free(command);
260253
#endif /* WIN32 */
261254
}
262255

@@ -273,30 +266,35 @@ static unsigned __stdcall async_notify(LPVOID param)
273266
{
274267
char exec[LARGEBUF];
275268
char notice[LARGEBUF];
269+
char *argv[3];
270+
int ret;
276271

277272
/* the following code is a copy of the content of the NOT WIN32 part of
278-
"notify" function below */
273+
* "notify" function below */
279274

280275
async_notify_t *data = (async_notify_t *)param;
281276

282277
if (flag_isset(data->flags, NOTIFY_WALL)) {
283-
snprintf(notice,LARGEBUF,"%s: %s", data->date, data->notice);
278+
snprintf(notice, sizeof(notice), "%s: %s", data->date, data->notice);
284279
wall(notice);
285280
}
286281

287282
if (flag_isset(data->flags, NOTIFY_EXEC)) {
288283
if (notifycmd != NULL) {
289-
snprintf(exec, sizeof(exec), "%s \"%s\"", notifycmd, data->notice);
290-
291-
upsdebugx(6, "%s: Calling NOTIFYCMD: %s", __func__, exec);
284+
upsdebugx(6, "%s: Calling NOTIFYCMD: %s with notice: %s",
285+
__func__, notifycmd, NUT_STRARG(data->notice));
292286
if (data->upsname)
293287
setenv("UPSNAME", data->upsname, 1);
294288
else
295289
setenv("UPSNAME", "", 1);
296290

297291
setenv("NOTIFYTYPE", data->ntype, 1);
298-
if (system(exec) == -1) {
299-
upslog_with_errno(LOG_ERR, "%s", __func__);
292+
argv[0] = notifycmd;
293+
argv[1] = data->notice;
294+
argv[2] = NULL;
295+
ret = _spawnvp(_P_WAIT, notifycmd, (const char * const *)argv);
296+
if (ret == -1) {
297+
upslog_with_errno(LOG_ERR, "%s: _spawnvp failed", __func__);
300298
}
301299
}
302300
}
@@ -319,7 +317,7 @@ static void notify(const char *notice, unsigned int flags, const char *ntype,
319317
#endif /* !WIN32 */
320318

321319
upsdebugx(6, "%s: sending notification for [%s]: type %s with flags 0x%04x: %s",
322-
__func__, upsname ? upsname : "upsmon itself", ntype, flags, notice);
320+
__func__, upsname ? upsname : "upsmon itself", ntype, flags, NUT_STRARG);
323321

324322
if (flag_isset(flags, NOTIFY_IGNORE)) {
325323
upsdebugx(6, "%s: NOTIFY_IGNORE", __func__);
@@ -328,7 +326,7 @@ static void notify(const char *notice, unsigned int flags, const char *ntype,
328326

329327
if (flag_isset(flags, NOTIFY_SYSLOG)) {
330328
upsdebugx(6, "%s: NOTIFY_SYSLOG (as LOG_NOTICE)", __func__);
331-
upslogx(LOG_NOTICE, "%s", notice);
329+
upslogx(LOG_NOTICE, "%s", NUT_STRARG(notice));
332330
}
333331

334332
#ifndef WIN32
@@ -357,20 +355,24 @@ static void notify(const char *notice, unsigned int flags, const char *ntype,
357355

358356
if (flag_isset(flags, NOTIFY_EXEC)) {
359357
if (notifycmd != NULL) {
360-
upsdebugx(6, "%s (%schild): NOTIFY_EXEC: calling NOTIFYCMD as '%s \"%s\"'",
361-
__func__, use_pipe ? "grand" : "", notifycmd, notice);
358+
char *argv[3];
362359

363-
snprintf(exec, sizeof(exec), "%s \"%s\"", notifycmd, notice);
360+
upsdebugx(6, "%s (%schild): NOTIFY_EXEC: calling NOTIFYCMD as '%s' with notice: '%s'",
361+
__func__, use_pipe ? "grand" : "", notifycmd, NUT_STRARG(notice));
364362

365363
if (upsname)
366364
setenv("UPSNAME", upsname, 1);
367365
else
368366
setenv("UPSNAME", "", 1);
369367

370368
setenv("NOTIFYTYPE", ntype, 1);
371-
if (system(exec) == -1) {
372-
upslog_with_errno(LOG_ERR, "%s", __func__);
373-
}
369+
argv[0] = (char *)notifycmd;
370+
argv[1] = (char *)notice;
371+
argv[2] = NULL;
372+
execvp(notifycmd, argv);
373+
/* execvp() only returns on error */
374+
upslog_with_errno(LOG_ERR, "%s: execvp(%s) failed", __func__, notifycmd);
375+
exit(EXIT_FAILURE);
374376
} else {
375377
upsdebugx(6, "%s (%schild): NOTIFY_EXEC: no NOTIFYCMD was configured", __func__, use_pipe ? "grand" : "");
376378
}

clients/upssched.c

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -105,32 +105,67 @@ static OVERLAPPED connect_overlapped;
105105

106106
static void exec_cmd(const char *cmd)
107107
{
108-
int err;
109-
char buf[LARGEBUF];
108+
int err, waitstatus = 0;
109+
pid_t pid, waitret;
110+
char *argv[3];
110111

111-
snprintf(buf, sizeof(buf), "%s %s", cmdscript, cmd);
112+
if (cmdscript == NULL) {
113+
upslogx(LOG_ERR, "No CMDSCRIPT defined, cannot execute command: %s", NUT_STRARG(cmd));
114+
return;
115+
}
116+
117+
argv[0] = cmdscript;
118+
argv[1] = (char *)cmd;
119+
argv[2] = NULL;
120+
121+
upsdebugx(4, "%s: calling: %s %s", __func__, cmdscript, NUT_STRARG(cmd));
112122

113-
upsdebugx(4, "%s: calling: %s", __func__, buf);
114-
err = system(buf);
115-
upsdebugx(3, "%s(%s): returned %d", __func__, buf, err);
116123
#ifndef WIN32
117-
if (WIFEXITED(err)) {
118-
if (WEXITSTATUS(err)) {
119-
upslogx(LOG_INFO, "exec_cmd(%s) returned %d", buf, WEXITSTATUS(err));
124+
pid = fork();
125+
if (pid < 0) {
126+
upslog_with_errno(LOG_ERR, "fork() failed in exec_cmd");
127+
return;
128+
}
129+
130+
if (pid == 0) {
131+
/* child process */
132+
execvp(cmdscript, argv);
133+
/* execvp() only returns on error */
134+
upslog_with_errno(LOG_ERR, "execvp(%s) failed", cmdscript);
135+
exit(EXIT_FAILURE);
136+
}
137+
138+
/* parent process - wait for child */
139+
waitret = waitpid(pid, &waitstatus, 0);
140+
if (waitret < 0) {
141+
upslog_with_errno(LOG_ERR, "waitpid(%d) failed", (int)pid);
142+
return;
143+
}
144+
145+
if (WIFEXITED(waitstatus)) {
146+
if (WEXITSTATUS(waitstatus)) {
147+
upslogx(LOG_INFO, "exec_cmd(%s %s) returned %d",
148+
cmdscript, NUT_STRARG(cmd), WEXITSTATUS(waitstatus));
120149
}
121150
} else {
122-
if (WIFSIGNALED(err)) {
123-
upslogx(LOG_WARNING, "exec_cmd(%s) terminated with signal %d", buf, WTERMSIG(err));
151+
if (WIFSIGNALED(waitstatus)) {
152+
upslogx(LOG_WARNING, "exec_cmd(%s %s) terminated with signal %d",
153+
cmdscript, NUT_STRARG(cmd), WTERMSIG(waitstatus));
124154
} else {
125-
upslogx(LOG_ERR, "Execute command failure: %s", buf);
155+
upslogx(LOG_ERR, "Execute command failure: %s %s",
156+
cmdscript, NUT_STRARG(cmd));
126157
}
127158
}
159+
160+
upsdebugx(3, "%s: returned status %d", __func__, waitstatus);
128161
#else /* WIN32 */
129-
if(err != -1) {
130-
upslogx(LOG_INFO, "Execute command \"%s\" OK", buf);
131-
}
132-
else {
133-
upslogx(LOG_ERR, "Execute command failure : %s", buf);
162+
/* Use _spawnvp for Windows */
163+
err = _spawnvp(_P_WAIT, cmdscript, (const char * const *)argv);
164+
if (err != -1) {
165+
upslogx(LOG_INFO, "Execute command \"%s %s\" OK", cmdscript, NUT_STRARG(cmd));
166+
upsdebugx(3, "%s: returned status %d", __func__, err);
167+
} else {
168+
upslog_with_errno(LOG_ERR, "Execute command \"%s %s\" failure", cmdscript, NUT_STRARG(cmd));
134169
}
135170
#endif /* WIN32 */
136171

conf/upsmon.conf.sample.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ SHUTDOWNCMD "/sbin/shutdown -h +0"
210210
#
211211
# upsmon calls this to send messages when things happen
212212
#
213+
# The value is interpreted as a complete path to an externally stored
214+
# executable script (e.g. not an inline scriptlet) or program file without
215+
# further arguments (this is a change from NUT v2.8.5 and older releases).
213216
# This command is called with the full text of the message (from NOTIFYMSG)
214217
# as one argument.
215218
#

conf/upssched.conf.sample.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
# CMDSCRIPT <scriptname>
2222
#
2323
# This script gets called to invoke commands for timers that trigger.
24+
# The value is interpreted as a complete path to an externally stored
25+
# executable script (e.g. not an inline scriptlet) or program file without
26+
# further arguments (this is a change from NUT v2.8.5 and older releases).
2427
# It is given a single argument - the <timername> in your
2528
# AT ... START-TIMER defines.
2629
#

docs/man/upsmon.conf.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,13 @@ NOTIFYFLAG. See NOTIFYFLAG below for more details.
230230
Making this some sort of shell script might not be a bad idea. For
231231
more information and ideas, see docs/scheduling.txt
232232
+
233-
Remember, this command also needs to be one element in the configuration file,
234-
so if your command has spaces, then wrap it in quotes.
233+
The value is interpreted as a complete path to an externally stored
234+
executable script (e.g. not an inline scriptlet) or program file without
235+
further arguments (this is a change from NUT v2.8.5 and older releases).
236+
Remember, this command also needs to be one element in the configuration
237+
file, so if your command path has spaces, then wrap it in quotes.
235238
+
236-
+NOTIFYCMD "/path/to/script --foo --bar"+
239+
+NOTIFYCMD "/path to/script"+
237240
+
238241
This script is run in the background--that is, upsmon forks before it
239242
calls out to start it. This means that your NOTIFYCMD may have multiple

docs/man/upsmon.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,12 @@ Example:
258258
- `NOTIFYCMD "/usr/local/bin/notifyme"`
259259

260260
Remember to wrap the path in "double quotes" if it contains any spaces.
261-
It should probably not rely on receiving any command-line arguments.
261+
It should not rely on receiving any command-line arguments: the value of
262+
this directive is interpreted as a complete path to an externally stored
263+
executable script (e.g. not an inline scriptlet) or program file without
264+
further arguments (this is a change from NUT v2.8.5 and older releases).
265+
If you need to pass special arguments to a notification program, please
266+
write a small script to do so and specify its path here.
262267

263268
The program you run as your NOTIFYCMD can use the environment variables
264269
NOTIFYTYPE and UPSNAME to know what has happened and on which UPS. It

docs/man/upssched.conf.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Example:
4646
Required. This must be above any AT lines. This script is used to
4747
invoke commands when your timers are triggered. It receives a single
4848
argument which is the name of the timer that caused it to trigger.
49+
+
50+
The value is interpreted as a complete path to an externally stored
51+
executable script (e.g. not an inline scriptlet) or program file without
52+
further arguments (this is a change from NUT v2.8.5 and older releases).
4953

5054
*PIPEFN* 'filename'::
5155
Required. This sets the file name of the socket which will be used for

docs/man/upssched.txt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,16 @@ EARLY SHUTDOWNS
9898
---------------
9999

100100
To shut down the system early, define a timer that starts due to an ONBATT
101-
condition. When it triggers, make your CMDSCRIPT call your shutdown
102-
routine. It should finish by calling `upsmon -c fsd` so that upsmon gets
103-
to shut down the slaves in a controlled manner.
101+
condition. When it triggers, make your `CMDSCRIPT` call your shutdown
102+
routine, especially if you have to prioritize something to stop first,
103+
or have subsystems that can take longer to gracefully stop than the normal
104+
OS shutdown patience allows (some systems might `kill` remaining processes
105+
after a hard-coded timeout).
106+
107+
Your `CMDSCRIPT` should finish by calling `upsmon -c fsd`, so that your
108+
primary instance of linkman:upsmon[8] gets to shut down the secondary
109+
systems in a controlled manner (note the primary instance would also run
110+
its local definition of `SHUTDOWNCMD` in the end).
104111

105112
Be sure you cancel the timer if power returns (ONLINE).
106113

0 commit comments

Comments
 (0)