Re: [Libguestfs] [PATCH nbdkit 2/2] file: Allow a file descriptor to be passed to the plugin

2022-08-18 Thread Eric Blake
On Thu, Aug 18, 2022 at 04:36:13PM +0100, Richard W.M. Jones wrote:
> This is what I pushed - how does it look?  I ignored the cases we
> cannot deal with, except O_WRONLY where I emit a debug message but
> continue:
> 
> https://gitlab.com/nbdkit/nbdkit/-/blob/master/plugins/file/file.c#L572
> 
> > > 
> > > There's also the case where r == O_WRONLY which the plugin (and NBD)
> > > cannot deal with.  Not sure what to do about that - error?
> > 
> > Or allow it, but with the caveat that every NBD_CMD_READ will fail.
> > The only reason to special case h->can_write=false for O_RDONLY is
> > because then we don't advertise it to the client; to save them from
> > getting failures on NBD_CMD_WRITE - but that's because it is an easy
> > thing to advertise.  Advertising that NBD_CMD_READ will fail is not
> > easy (and less likely to happen in practice), so failing to serve the
> > file is just as viable as serving it and letting every NBD_CMD_READ
> > fail.
> 
> That's basically what I did, plus a debug message :-)

What you have committed looks okay to me for handling the common case
of O_RDONLY without worrying about the other cases.  And if someone
tries to play with passing in an fd opened under O_EXEC, they deserve
whatever behavior happens naturally from that explicitly unusual
corner-case.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.   +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



Re: [Libguestfs] [PATCH nbdkit 2/2] file: Allow a file descriptor to be passed to the plugin

2022-08-18 Thread Richard W.M. Jones
On Thu, Aug 18, 2022 at 10:28:17AM -0500, Eric Blake wrote:
> On Wed, Aug 17, 2022 at 10:56:58PM +0100, Richard W.M. Jones wrote:
> > On Wed, Aug 17, 2022 at 10:37:00PM +0100, Richard W.M. Jones wrote:
> > > Is that actually possible?  “fcntl (fd, F_GETFL) & O_WRONLY”
> > > should do it?
> > 
> > So the answer is no as it's a kind of tri-state.
> 
> More like 5-state (by POSIX): O_RDONLY, O_WRONLY, O_RDWR, O_SEARCH,
> O_EXEC (although O_SEARCH and O_EXEC can be the same bit, because
> their uses are mutually exclusive).  And in reality, since Linux burns
> 3 bits on it, there can be other states as well (O_PATH, for example).
> 
> Some systems have O_RDONLY==1, O_WRONLY==2, O_RDWR==3 (then an obvious
> extension for O_PATH would be 0); but most systems have O_RDONLY==0,
> O_WRONLY==1, O_RDWR==2, which makes bit-wise testing impossible.
> 
> > 
> > I think this should work (untested)?
> > 
> >   r = fcntl (fd, F_GETFL);
> >   if (r == -1) ...
> >   r &= O_ACCMODE;
> >   if (r == O_RDONLY)
> > h->can_write = false;
> 
> Yeah, that's one approach.  Another might be as Lazslo suggested:
> 
>   switch (r & O_ACCMODE) {
>   case O_RDWR: break;
>   case O_RDONLY: h->can_write = false; break;
>   default: //error about unsupported mode
>   }

This is what I pushed - how does it look?  I ignored the cases we
cannot deal with, except O_WRONLY where I emit a debug message but
continue:

https://gitlab.com/nbdkit/nbdkit/-/blob/master/plugins/file/file.c#L572

> > 
> > There's also the case where r == O_WRONLY which the plugin (and NBD)
> > cannot deal with.  Not sure what to do about that - error?
> 
> Or allow it, but with the caveat that every NBD_CMD_READ will fail.
> The only reason to special case h->can_write=false for O_RDONLY is
> because then we don't advertise it to the client; to save them from
> getting failures on NBD_CMD_WRITE - but that's because it is an easy
> thing to advertise.  Advertising that NBD_CMD_READ will fail is not
> easy (and less likely to happen in practice), so failing to serve the
> file is just as viable as serving it and letting every NBD_CMD_READ
> fail.

That's basically what I did, plus a debug message :-)

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-p2v converts physical machines to virtual machines.  Boot with a
live CD or over the network (PXE) and turn machines into KVM guests.
http://libguestfs.org/virt-v2v
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


Re: [Libguestfs] [PATCH nbdkit 2/2] file: Allow a file descriptor to be passed to the plugin

2022-08-18 Thread Eric Blake
On Wed, Aug 17, 2022 at 10:56:58PM +0100, Richard W.M. Jones wrote:
> On Wed, Aug 17, 2022 at 10:37:00PM +0100, Richard W.M. Jones wrote:
> > Is that actually possible?  “fcntl (fd, F_GETFL) & O_WRONLY”
> > should do it?
> 
> So the answer is no as it's a kind of tri-state.

More like 5-state (by POSIX): O_RDONLY, O_WRONLY, O_RDWR, O_SEARCH,
O_EXEC (although O_SEARCH and O_EXEC can be the same bit, because
their uses are mutually exclusive).  And in reality, since Linux burns
3 bits on it, there can be other states as well (O_PATH, for example).

Some systems have O_RDONLY==1, O_WRONLY==2, O_RDWR==3 (then an obvious
extension for O_PATH would be 0); but most systems have O_RDONLY==0,
O_WRONLY==1, O_RDWR==2, which makes bit-wise testing impossible.

> 
> I think this should work (untested)?
> 
>   r = fcntl (fd, F_GETFL);
>   if (r == -1) ...
>   r &= O_ACCMODE;
>   if (r == O_RDONLY)
> h->can_write = false;

Yeah, that's one approach.  Another might be as Lazslo suggested:

  switch (r & O_ACCMODE) {
  case O_RDWR: break;
  case O_RDONLY: h->can_write = false; break;
  default: //error about unsupported mode
  }

> 
> There's also the case where r == O_WRONLY which the plugin (and NBD)
> cannot deal with.  Not sure what to do about that - error?

Or allow it, but with the caveat that every NBD_CMD_READ will fail.
The only reason to special case h->can_write=false for O_RDONLY is
because then we don't advertise it to the client; to save them from
getting failures on NBD_CMD_WRITE - but that's because it is an easy
thing to advertise.  Advertising that NBD_CMD_READ will fail is not
easy (and less likely to happen in practice), so failing to serve the
file is just as viable as serving it and letting every NBD_CMD_READ
fail.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.   +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


Re: [Libguestfs] [PATCH nbdkit 2/2] file: Allow a file descriptor to be passed to the plugin

2022-08-18 Thread Laszlo Ersek
On 08/17/22 23:56, Richard W.M. Jones wrote:
> On Wed, Aug 17, 2022 at 10:37:00PM +0100, Richard W.M. Jones wrote:
>> Is that actually possible?  “fcntl (fd, F_GETFL) & O_WRONLY”
>> should do it?
> 
> So the answer is no as it's a kind of tri-state.
> 
> I think this should work (untested)?
> 
>   r = fcntl (fd, F_GETFL);
>   if (r == -1) ...
>   r &= O_ACCMODE;
>   if (r == O_RDONLY)
> h->can_write = false;
> 
> There's also the case where r == O_WRONLY which the plugin (and NBD)
> cannot deal with.  Not sure what to do about that - error?

(after a bit of reading POSIX):

I think a switch statement should handle these; O_RDONLY and O_RDWR
intuitively, everything else (O_EXEC, O_SEARCH, O_WRONLY) should be
rejected.

Laszlo
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


Re: [Libguestfs] [PATCH nbdkit 2/2] file: Allow a file descriptor to be passed to the plugin

2022-08-17 Thread Richard W.M. Jones
On Wed, Aug 17, 2022 at 10:37:00PM +0100, Richard W.M. Jones wrote:
> Is that actually possible?  “fcntl (fd, F_GETFL) & O_WRONLY”
> should do it?

So the answer is no as it's a kind of tri-state.

I think this should work (untested)?

  r = fcntl (fd, F_GETFL);
  if (r == -1) ...
  r &= O_ACCMODE;
  if (r == O_RDONLY)
h->can_write = false;

There's also the case where r == O_WRONLY which the plugin (and NBD)
cannot deal with.  Not sure what to do about that - error?

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
libguestfs lets you edit virtual machines.  Supports shell scripting,
bindings from many languages.  http://libguestfs.org
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


Re: [Libguestfs] [PATCH nbdkit 2/2] file: Allow a file descriptor to be passed to the plugin

2022-08-17 Thread Richard W.M. Jones
On Wed, Aug 17, 2022 at 03:57:24PM -0500, Eric Blake wrote:
> On Wed, Aug 17, 2022 at 04:38:11PM +0100, Richard W.M. Jones wrote:
> > This allows you to pass in a file descriptor to the plugin, eg:
> > 
> >   $ exec 6<>/var/tmp/fedora-36.img
> >   $ ./nbdkit file fd=6 --run 'nbdinfo $uri'
> > 
> > Note this was previously possible on most platforms using /dev/fd/,
> > but not all platforms that have file descriptors support this
> > (although it is POSIX) and it seems better to make this explicit.
> 
> The parenthetical feels odd here.  /dev/fd/ is not POSIX; what IS a
> POSIX feature is the ability to pass an open file descriptor by
> inheritance.

Will fix.

> > @@ -202,6 +208,17 @@ file_config (const char *key, const char *value)
> >  if (!directory)
> >return -1;
> >}
> > +  else if (strcmp (key, "fd") == 0) {
> > +if (mode != mode_none) goto wrong_mode;
> > +mode = mode_filedesc;
> > +assert (filedesc == -1);
> > +if (nbdkit_parse_int ("fd", value, ) == -1)
> > +  return -1;
> > +if (filedesc <= 2) {
> > +  nbdkit_error ("file descriptor must be >= 3");
> 
> Is it worth using STDERR_FILENO instead of open-coding 2? I'll be okay
> if your answer is that this is one place where  has too much
> indirection ;)

Will fix.

> > @@ -408,59 +436,69 @@ file_open (int readonly)
> ...
> > +  case mode_filedesc:
> > +h->fd = dup (filedesc);
> > +if (h->fd == -1) {
> > +  nbdkit_error ("dup fd=%d: %m", filedesc);
> > +  free (h);
> > +  return NULL;
> > +}
> > +file = ""; /* This is needed for error messages. */
> > +break;
> 
> Is it worth trying to figure out if the FD has O_RDWR privileges and
> set h->can_write accordingly, to match...

Is that actually possible?  “fcntl (fd, F_GETFL) & O_WRONLY”
should do it?

> >default: abort ();
> >}
> >  
> > -  h = malloc (sizeof *h);
> > -  if (h == NULL) {
> > -nbdkit_error ("malloc: %m");
> > -if (dfd != -1)
> > -  close (dfd);
> > -return NULL;
> > -  }
> > -
> > -  flags = O_CLOEXEC|O_NOCTTY;
> > -  if (readonly) {
> > -flags |= O_RDONLY;
> > -h->can_write = false;
> > -  }
> > -  else {
> > -flags |= O_RDWR;
> > -h->can_write = true;
> > -  }
> > -
> > -  h->fd = openat (dfd, file, flags);
> > -  if (h->fd == -1 && !readonly) {
> > -nbdkit_debug ("open O_RDWR failed, falling back to read-only: %s: %m",
> > -  file);
> > -flags = (flags & ~O_ACCMODE) | O_RDONLY;
> > -h->fd = openat (dfd, file, flags);
> > -h->can_write = false;
> > -  }
> 
> ... what we do for normal files?
> 
> Overall looks like a nice addition.
> 
> ACK series

I also want to add dirfd= similarly for directory FDs (seems better to
be explicit rather than overloading fd= and distinguishing through stat?)

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-builder quickly builds VMs from scratch
http://libguestfs.org/virt-builder.1.html
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs


Re: [Libguestfs] [PATCH nbdkit 2/2] file: Allow a file descriptor to be passed to the plugin

2022-08-17 Thread Eric Blake
On Wed, Aug 17, 2022 at 04:38:11PM +0100, Richard W.M. Jones wrote:
> This allows you to pass in a file descriptor to the plugin, eg:
> 
>   $ exec 6<>/var/tmp/fedora-36.img
>   $ ./nbdkit file fd=6 --run 'nbdinfo $uri'
> 
> Note this was previously possible on most platforms using /dev/fd/,
> but not all platforms that have file descriptors support this
> (although it is POSIX) and it seems better to make this explicit.

The parenthetical feels odd here.  /dev/fd/ is not POSIX; what IS a
POSIX feature is the ability to pass an open file descriptor by
inheritance.

> @@ -202,6 +208,17 @@ file_config (const char *key, const char *value)
>  if (!directory)
>return -1;
>}
> +  else if (strcmp (key, "fd") == 0) {
> +if (mode != mode_none) goto wrong_mode;
> +mode = mode_filedesc;
> +assert (filedesc == -1);
> +if (nbdkit_parse_int ("fd", value, ) == -1)
> +  return -1;
> +if (filedesc <= 2) {
> +  nbdkit_error ("file descriptor must be >= 3");

Is it worth using STDERR_FILENO instead of open-coding 2? I'll be okay
if your answer is that this is one place where  has too much
indirection ;)

> @@ -408,59 +436,69 @@ file_open (int readonly)
...
> +  case mode_filedesc:
> +h->fd = dup (filedesc);
> +if (h->fd == -1) {
> +  nbdkit_error ("dup fd=%d: %m", filedesc);
> +  free (h);
> +  return NULL;
> +}
> +file = ""; /* This is needed for error messages. */
> +break;

Is it worth trying to figure out if the FD has O_RDWR privileges and
set h->can_write accordingly, to match...

>default: abort ();
>}
>  
> -  h = malloc (sizeof *h);
> -  if (h == NULL) {
> -nbdkit_error ("malloc: %m");
> -if (dfd != -1)
> -  close (dfd);
> -return NULL;
> -  }
> -
> -  flags = O_CLOEXEC|O_NOCTTY;
> -  if (readonly) {
> -flags |= O_RDONLY;
> -h->can_write = false;
> -  }
> -  else {
> -flags |= O_RDWR;
> -h->can_write = true;
> -  }
> -
> -  h->fd = openat (dfd, file, flags);
> -  if (h->fd == -1 && !readonly) {
> -nbdkit_debug ("open O_RDWR failed, falling back to read-only: %s: %m",
> -  file);
> -flags = (flags & ~O_ACCMODE) | O_RDONLY;
> -h->fd = openat (dfd, file, flags);
> -h->can_write = false;
> -  }

... what we do for normal files?

Overall looks like a nice addition.

ACK series

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.   +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



[Libguestfs] [PATCH nbdkit 2/2] file: Allow a file descriptor to be passed to the plugin

2022-08-17 Thread Richard W.M. Jones
This allows you to pass in a file descriptor to the plugin, eg:

  $ exec 6<>/var/tmp/fedora-36.img
  $ ./nbdkit file fd=6 --run 'nbdinfo $uri'

Note this was previously possible on most platforms using /dev/fd/,
but not all platforms that have file descriptors support this
(although it is POSIX) and it seems better to make this explicit.
---
 plugins/file/nbdkit-file-plugin.pod |  22 +-
 tests/Makefile.am   |   2 +
 plugins/file/file.c | 108 +++-
 tests/test-file-fd.sh   |  65 +
 4 files changed, 160 insertions(+), 37 deletions(-)

diff --git a/plugins/file/nbdkit-file-plugin.pod 
b/plugins/file/nbdkit-file-plugin.pod
index 49c330663..dadbe9432 100644
--- a/plugins/file/nbdkit-file-plugin.pod
+++ b/plugins/file/nbdkit-file-plugin.pod
@@ -11,6 +11,10 @@ nbdkit-file-plugin - nbdkit file plugin
 
  nbdkit file dir=DIRECTORY
 
+=for paragraph
+
+ nbdkit file fd=FILE_DESCRIPTOR
+
 =head1 DESCRIPTION
 
 C is a file serving plugin for L.
@@ -22,10 +26,16 @@ If you use the C parameter the plugin works in a 
different mode
 where it serves files from the given C, chosen by the
 client using the NBD export name.
 
+If you use the C parameter then you can pass the file descriptor
+of a single disk to the plugin, inherited from the parent process.
+This can be useful where special permissions / capabilities are needed
+to open the file or you want to run nbdkit in a sandboxed environment.
+
 =head1 PARAMETERS
 
-Either B or B must be given which controls the mode of the
-plugin, either serving a single file or the files in a directory.
+B, B or B must be given.  This controls the mode of the
+plugin, either serving a single file, the files in a directory, or a
+single file descriptor.
 
 =over 4
 
@@ -72,6 +82,14 @@ read-ahead.  See also L.
 
 The default is C.
 
+=item BFILE_DESCRIPTOR
+
+(nbdkit E 1.34, not Windows)
+
+The parameter is the number of a file descriptor.  Serve the file or
+device already open on this file descriptor.  The file descriptor is
+usually inherited from the parent process.
+
 =item [B]FILENAME
 
 Serve the file named C.  A local block device name can also
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3dc428540..3667a1dea 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -732,10 +732,12 @@ EXTRA_DIST += \
 TESTS += \
test-file.sh \
test-file-readonly.sh \
+   test-file-fd.sh \
$(NULL)
 EXTRA_DIST += \
test-file.sh \
test-file-readonly.sh \
+   test-file-fd.sh \
$(NULL)
 LIBGUESTFS_TESTS += test-file-block
 
diff --git a/plugins/file/file.c b/plugins/file/file.c
index f673ec132..4e41e738a 100644
--- a/plugins/file/file.c
+++ b/plugins/file/file.c
@@ -70,9 +70,15 @@
 #include "isaligned.h"
 #include "fdatasync.h"
 
-static enum { mode_none, mode_filename, mode_directory } mode = mode_none;
+static enum {
+  mode_none,
+  mode_filename,
+  mode_directory,
+  mode_filedesc,
+} mode = mode_none;
 static char *filename = NULL;
 static char *directory = NULL;
+static int filedesc = -1;
 
 /* posix_fadvise mode: -1 = don't set it, or POSIX_FADV_*. */
 static int fadvise_mode =
@@ -184,7 +190,7 @@ file_config (const char *key, const char *value)
 if (mode != mode_none) {
 wrong_mode:
   nbdkit_error ("%s parameter can only appear once on the command line",
-"file|dir");
+"file|dir|fd");
   return -1;
 }
 mode = mode_filename;
@@ -202,6 +208,17 @@ file_config (const char *key, const char *value)
 if (!directory)
   return -1;
   }
+  else if (strcmp (key, "fd") == 0) {
+if (mode != mode_none) goto wrong_mode;
+mode = mode_filedesc;
+assert (filedesc == -1);
+if (nbdkit_parse_int ("fd", value, ) == -1)
+  return -1;
+if (filedesc <= 2) {
+  nbdkit_error ("file descriptor must be >= 3");
+  return -1;
+}
+  }
   else if (strcmp (key, "fadvise") == 0) {
 /* As this is a hint, if the kernel doesn't support the feature
  * ignore the parameter.
@@ -263,18 +280,26 @@ file_config_complete (void)
   struct stat sb;
 
   if (mode == mode_none) {
-nbdkit_error ("you must supply either [file=] or "
-  "dir= parameter after the plugin name "
+nbdkit_error ("you must supply [file=], "
+  "dir= or fd= "
+  "parameter after the plugin name "
   "on the command line");
 return -1;
   }
   if (mode == mode_filename) {
 assert (filename != NULL);
 assert (directory == NULL);
+assert (filedesc == -1);
   }
   if (mode == mode_directory) {
 assert (filename == NULL);
 assert (directory != NULL);
+assert (filedesc == -1);
+  }
+  if (mode == mode_filedesc) {
+assert (filename == NULL);
+assert (directory == NULL);
+assert (filedesc >= 3);
   }
 
   /* Sanity check now, rather than waiting for first client open.
@@ -299,6 +324,8 @@