2 * Copyright (C) 2018-2019 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
45 #include <sys/types.h>
49 #include <nbdkit-plugin.h>
55 /* Ensure there is at least 1 byte of space in the buffer. */
57 expand_buf (char **buf
, size_t *buflen
, size_t *bufalloc
)
61 if (*bufalloc
> *buflen
)
64 *bufalloc
= *bufalloc
== 0 ? 64 : *bufalloc
* 2;
65 nb
= realloc (*buf
, *bufalloc
);
67 nbdkit_error ("%s: malloc: %m", script
);
74 /* This is the generic function that calls the script. It can
75 * optionally write to the script's stdin and read from the script's
76 * stdout and stderr. It returns the raw error code and does no error
80 call3 (const char *wbuf
, size_t wbuflen
, /* sent to stdin */
81 char **rbuf
, size_t *rbuflen
, /* read from stdout */
82 char **ebuf
, size_t *ebuflen
, /* read from stderr */
83 const char **argv
) /* script + parameters */
88 int in_fd
[2] = { -1, -1 };
89 int out_fd
[2] = { -1, -1 };
90 int err_fd
[2] = { -1, -1 };
91 size_t rbufalloc
, ebufalloc
;
92 struct pollfd pfds
[3];
96 *rbuflen
= *ebuflen
= 0;
97 rbufalloc
= ebufalloc
= 0;
99 /* Decent logging is worth the hassle. We don't send more than 5 args. */
100 const char *arg1
= argv
[1], *arg2
= "", *arg3
= "", *arg4
= "", *arg5
= "";
113 nbdkit_debug ("%s: invoking %s %s %s %s %s ...",
114 script
, arg1
, arg2
, arg3
, arg4
, arg5
);
118 if (pipe2 (in_fd
, O_CLOEXEC
) == -1) {
119 nbdkit_error ("%s: pipe2: %m", script
);
122 if (pipe2 (out_fd
, O_CLOEXEC
) == -1) {
123 nbdkit_error ("%s: pipe2: %m", script
);
126 if (pipe2 (err_fd
, O_CLOEXEC
) == -1) {
127 nbdkit_error ("%s: pipe2: %m", script
);
131 /* Without pipe2, nbdkit forces the thread model maximum down to
132 * NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS, this in turn ensures
133 * no other thread will be trying to fork, and thus we can skip
134 * worrying about CLOEXEC races. Therefore, it's not worth adding a
135 * loop after fork to close unexpected fds.
137 if (pipe (in_fd
) == -1) {
138 nbdkit_error ("%s: pipe: %m", script
);
141 if (pipe (out_fd
) == -1) {
142 nbdkit_error ("%s: pipe: %m", script
);
145 if (pipe (err_fd
) == -1) {
146 nbdkit_error ("%s: pipe: %m", script
);
151 /* Ensure that stdin/out/err of the current process were not empty
152 * before we started creating pipes (otherwise, the close and dup2
153 * calls below become more complex to juggle fds around correctly).
155 assert (in_fd
[0] > STDERR_FILENO
&& in_fd
[1] > STDERR_FILENO
&&
156 out_fd
[0] > STDERR_FILENO
&& out_fd
[1] > STDERR_FILENO
&&
157 err_fd
[0] > STDERR_FILENO
&& err_fd
[1] > STDERR_FILENO
);
161 nbdkit_error ("%s: fork: %m", script
);
165 if (pid
== 0) { /* Child. */
176 /* Restore SIGPIPE back to SIG_DFL, since shell can't undo SIG_IGN */
177 signal (SIGPIPE
, SIG_DFL
);
179 execvp (argv
[0], (char **) argv
);
181 _exit (EXIT_FAILURE
);
185 close (in_fd
[0]); in_fd
[0] = -1;
186 close (out_fd
[1]); out_fd
[1] = -1;
187 close (err_fd
[1]); err_fd
[1] = -1;
189 while (out_fd
[0] >= 0 || err_fd
[0] >= 0) {
190 pfds
[0].fd
= in_fd
[1]; /* Connected to child stdin. */
191 pfds
[0].events
= wbuflen
? POLLOUT
: 0;
192 pfds
[1].fd
= out_fd
[0]; /* Connected to child stdout. */
193 pfds
[1].events
= POLLIN
;
194 pfds
[2].fd
= err_fd
[0]; /* Connected to child stderr. */
195 pfds
[2].events
= POLLIN
;
197 if (poll (pfds
, 3, -1) == -1) {
198 if (errno
== EINTR
|| errno
== EAGAIN
)
200 nbdkit_error ("%s: poll: %m", script
);
204 /* Write more data to stdin. */
205 if (pfds
[0].revents
& POLLOUT
) {
206 r
= write (pfds
[0].fd
, wbuf
, wbuflen
);
208 nbdkit_error ("%s: write: %m", script
);
213 /* After writing all the data we close the pipe so that
214 * the reader on the other end doesn't wait for more.
218 in_fd
[1] = -1; /* poll will ignore this fd */
223 if (pfds
[1].revents
& POLLIN
) {
224 if (expand_buf (rbuf
, rbuflen
, &rbufalloc
) == -1)
226 r
= read (pfds
[1].fd
, *rbuf
+ *rbuflen
, rbufalloc
- *rbuflen
);
228 nbdkit_error ("%s: read: %m", script
);
234 out_fd
[0] = -1; /* poll will ignore this fd */
239 else if (pfds
[1].revents
& POLLHUP
) {
244 if (pfds
[2].revents
& POLLIN
) {
245 if (expand_buf (ebuf
, ebuflen
, &ebufalloc
) == -1)
247 r
= read (pfds
[2].fd
, *ebuf
+ *ebuflen
, ebufalloc
- *ebuflen
);
249 nbdkit_error ("%s: read: %m", script
);
255 err_fd
[0] = -1; /* poll will ignore this fd */
260 else if (pfds
[2].revents
& POLLHUP
) {
265 if (waitpid (pid
, &status
, 0) == -1) {
266 nbdkit_error ("%s: waitpid: %m", script
);
272 if (WIFSIGNALED (status
)) {
273 nbdkit_error ("%s: script terminated by signal %d",
274 script
, WTERMSIG (status
));
278 if (WIFSTOPPED (status
)) {
279 nbdkit_error ("%s: script stopped by signal %d",
280 script
, WTERMSIG (status
));
284 /* \0-terminate both read buffers (for convenience). */
285 if (expand_buf (rbuf
, rbuflen
, &rbufalloc
) == -1)
287 if (expand_buf (ebuf
, ebuflen
, &ebufalloc
) == -1)
289 (*rbuf
)[*rbuflen
] = '\0';
290 (*ebuf
)[*ebuflen
] = '\0';
292 ret
= WEXITSTATUS (status
);
293 nbdkit_debug ("%s: ... %s completed with status %d", script
, argv
[1], ret
);
309 waitpid (pid
, NULL
, 0);
315 handle_script_error (char *ebuf
, size_t len
)
321 if (strncasecmp (ebuf
, "EPERM", 5) == 0) {
325 else if (strncasecmp (ebuf
, "EIO", 3) == 0) {
329 else if (strncasecmp (ebuf
, "ENOMEM", 6) == 0) {
333 else if (strncasecmp (ebuf
, "EINVAL", 6) == 0) {
337 else if (strncasecmp (ebuf
, "ENOSPC", 6) == 0) {
341 else if (strncasecmp (ebuf
, "EOVERFLOW", 9) == 0) {
345 else if (strncasecmp (ebuf
, "ESHUTDOWN", 9) == 0) {
350 /* Default to EIO. */
355 if (skip
&& ebuf
[skip
]) {
356 if (!isspace ((unsigned char) ebuf
[skip
])) {
357 /* Treat 'EINVALID' as EIO, not EINVAL */
364 while (isspace ((unsigned char) ebuf
[skip
]));
367 while (len
> 0 && ebuf
[len
-1] == '\n')
371 p
= strchr (ebuf
+ skip
, '\n');
373 /* More than one line, so write the whole message to debug ... */
374 nbdkit_debug ("%s: %s", script
, ebuf
);
375 /* ... but truncate it for the error message below. */
379 nbdkit_error ("%s: %s", script
, ebuf
);
382 nbdkit_error ("%s: script exited with error, "
383 "but did not print an error message on stderr", script
);
389 /* Call the script with parameters. Don't write to stdin or read from
390 * stdout, but handle stderr if an error occurs. Returns the exit
391 * code from the script.
394 call (const char **argv
)
397 CLEANUP_FREE
char *rbuf
= NULL
;
399 CLEANUP_FREE
char *ebuf
= NULL
;
402 r
= call3 (NULL
, 0, &rbuf
, &rbuflen
, &ebuf
, &ebuflen
, argv
);
407 /* Script successful. */
413 handle_script_error (ebuf
, ebuflen
);
418 /* Call the script with parameters. Read from stdout and return the
419 * buffer. Returns the exit code from the script.
422 call_read (char **rbuf
, size_t *rbuflen
, const char **argv
)
425 CLEANUP_FREE
char *ebuf
= NULL
;
428 r
= call3 (NULL
, 0, rbuf
, rbuflen
, &ebuf
, &ebuflen
, argv
);
433 /* Script successful. */
441 handle_script_error (ebuf
, ebuflen
);
446 /* Call the script with parameters. Write to stdin of the script.
447 * Returns the exit code from the script.
450 call_write (const char *wbuf
, size_t wbuflen
, const char **argv
)
453 CLEANUP_FREE
char *rbuf
= NULL
;
455 CLEANUP_FREE
char *ebuf
= NULL
;
458 r
= call3 (wbuf
, wbuflen
, &rbuf
, &rbuflen
, &ebuf
, &ebuflen
, argv
);
463 /* Script successful. */
469 handle_script_error (ebuf
, ebuflen
);