On 8/31/23 00:21, Eric Blake wrote: > I hit another transient failure in libnbd CI when a poorly-written > eval script did not consume all of stdin during .pwrite. As behaving > as a data sink can be a somewhat reasonable feature of a > quickly-written sh or eval plugin, we should not be so insistent as > treating an EPIPE failure as an immediate return of EIO to the client. > > Signed-off-by: Eric Blake <ebl...@redhat.com> > --- > > I probably need to add unit test coverage of this before committing > (although proving that I win the data race on a client process exiting > faster than the parent can write enough data to hit EPIPE is hard). > > plugins/sh/nbdkit-sh-plugin.pod | 8 +++++++ > plugins/sh/call.c | 38 ++++++++++++++++++++++----------- > 2 files changed, 34 insertions(+), 12 deletions(-) > > diff --git a/plugins/sh/nbdkit-sh-plugin.pod b/plugins/sh/nbdkit-sh-plugin.pod > index b2c946a0..8b83a5b3 100644 > --- a/plugins/sh/nbdkit-sh-plugin.pod > +++ b/plugins/sh/nbdkit-sh-plugin.pod > @@ -505,6 +505,14 @@ Unlike in other languages, if you provide a C<pwrite> > method you > B<must> also provide a C<can_write> method which exits with code C<0> > (true). > > +With nbdkit E<ge> 1.36, this method may return C<0> without consuming > +any data from stdin, and without producing any output, in order to > +behave as an intentional data sink. But in older versions, nbdkit > +would treat any C<EPIPE> failure in writing to your script as an error > +condition even if your script returns success; to avoid unintended > +failures, you may want to include C<"cat >/dev/null"> in a script > +intending to ignore the client's write requests. > + > =item C<flush> > > /path/to/script flush <handle> > diff --git a/plugins/sh/call.c b/plugins/sh/call.c > index 888c6459..79c67a04 100644 > --- a/plugins/sh/call.c > +++ b/plugins/sh/call.c > @@ -34,6 +34,7 @@ > > #include <assert.h> > #include <fcntl.h> > +#include <stdbool.h> > #include <stdio.h> > #include <stdlib.h> > #include <inttypes.h> > @@ -130,6 +131,7 @@ debug_call (const char **argv) > */ > static int > call3 (const char *wbuf, size_t wbuflen, /* sent to stdin (can be NULL) */ > + bool *pipe_full, /* set if wbuf not fully written */ > string *rbuf, /* read from stdout */ > string *ebuf, /* read from stderr */ > const char **argv) /* script + parameters */ > @@ -275,15 +277,8 @@ call3 (const char *wbuf, size_t wbuflen, /* sent to > stdin (can be NULL) */ > r = write (pfds[0].fd, wbuf, wbuflen); > if (r == -1) { > if (errno == EPIPE) { > - /* We tried to write to the script but it didn't consume > - * the data. Probably the script exited without reading > - * from stdin. This is an error in the script. > - */ > - nbdkit_error ("%s: write to script failed because of a broken > pipe: " > - "this can happen if the script exits without " > - "consuming stdin, which usually indicates a bug " > - "in the script", > - argv0); > + *pipe_full = true; > + r = wbuflen; > } > else > nbdkit_error ("%s: write: %m", argv0); > @@ -555,7 +550,7 @@ call (const char **argv) > CLEANUP_FREE_STRING string rbuf = empty_vector; > CLEANUP_FREE_STRING string ebuf = empty_vector; > > - r = call3 (NULL, 0, &rbuf, &ebuf, argv); > + r = call3 (NULL, 0, NULL, &rbuf, &ebuf, argv); > return handle_script_error (argv[0], &ebuf, r); > } > > @@ -568,7 +563,7 @@ call_read (string *rbuf, const char **argv) > int r; > CLEANUP_FREE_STRING string ebuf = empty_vector; > > - r = call3 (NULL, 0, rbuf, &ebuf, argv); > + r = call3 (NULL, 0, NULL, rbuf, &ebuf, argv); > r = handle_script_error (argv[0], &ebuf, r); > if (r == ERROR) > string_reset (rbuf); > @@ -584,7 +579,26 @@ call_write (const char *wbuf, size_t wbuflen, const char > **argv) > int r; > CLEANUP_FREE_STRING string rbuf = empty_vector; > CLEANUP_FREE_STRING string ebuf = empty_vector; > + bool pipe_full = false; > > - r = call3 (wbuf, wbuflen, &rbuf, &ebuf, argv); > + r = call3 (wbuf, wbuflen, &pipe_full, &rbuf, &ebuf, argv); > + if (pipe_full && r == OK) { > + /* We allow scripts to intentionally ignore data, but they must > + * have no output when doing so. > + */ > + if (rbuf.len > 0 || ebuf.len > 0) { > + nbdkit_error ("%s: write to script failed because of a broken pipe: " > + "this can happen if the script exits without " > + "consuming stdin, which usually indicates a bug " > + "in the script", > + argv[0]); > + r = ERROR; > + } > + else > + nbdkit_debug ("%s: write to script failed because of a broken pipe; " > + "assuming this was an intentional data sink, although it > " > + "may indicate a bug in the script", > + argv[0]); > + } > return handle_script_error (argv[0], &ebuf, r); > }
The patch appears to do what it says on the tin, but I'm unconvinced we should relax the requirement. The "may indicate a bug" debug message is easy to miss. A hard failure upon EPIPE is noticeable, and as you write the script can just drain unwanted input with "cat >/dev/null". I think I may not be appreciating this patch due to not understanding how difficult it could be to update such a script. Can you elaborate on that? Can you provide an example where adding "cat >/dev/null" to the pwrite script is a disproportionate chore? Thanks, Laszlo _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs