[PATCH nbdkit] server: Allow file descriptors to be passed to nbdkit_read_password.
by Richard W.M. Jones
Allow password parameters such as ‘password=-FD’ where FD is a file
descriptor number inherited by nbdkit from the parent process. This
is another way to allow programs to hand passwords to nbdkit in a very
secure way, for example over a pipe so they never touch the
filesystem.
Previously nbdkit allowed you to use literal passwords on the command
line if they began with a ‘-’ (but were not just that single
character). However that was contrary to the documentation, and this
commit now prevents that.
---
docs/nbdkit-plugin.pod | 4 ++
plugins/curl/nbdkit-curl-plugin.pod | 10 ++++-
plugins/ssh/nbdkit-ssh-plugin.pod | 8 +++-
plugins/vddk/nbdkit-vddk-plugin.pod | 11 +++++-
server/public.c | 58 +++++++++++++++++++++--------
server/test-public.c | 31 +++++++++++++++
6 files changed, 102 insertions(+), 20 deletions(-)
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index e34ffd1..0afd5e1 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -1120,6 +1120,10 @@ or from a file:
nbdkit myplugin password=+/tmp/secret
+or from a file descriptor inherited by nbdkit:
+
+ nbdkit myplugin password=-99
+
(If the password begins with a C<-> or C<+> character then it must be
passed in a file).
diff --git a/plugins/curl/nbdkit-curl-plugin.pod b/plugins/curl/nbdkit-curl-plugin.pod
index 63500a4..827e0bd 100644
--- a/plugins/curl/nbdkit-curl-plugin.pod
+++ b/plugins/curl/nbdkit-curl-plugin.pod
@@ -70,10 +70,16 @@ Ask for the password (interactively) when nbdkit starts up.
=item B<password=+>FILENAME
-Read the password from the named file. This is the most secure method
+Read the password from the named file. This is a secure method
to supply a password, as long as you set the permissions on the file
appropriately.
+=item B<password=->FD
+
+Read the password from file descriptor number C<FD>, inherited from
+the parent process when nbdkit starts up. This is also a secure
+method to supply a password.
+
=item B<protocols=>PROTO,PROTO,...
Limit the protocols that are allowed in the URL. Use this option for
@@ -100,6 +106,8 @@ The default is to allow any protocol.
=item B<proxy-password=+>FILENAME
+=item B<proxy-password=->FD
+
=item B<proxy-user=>USERNAME
Set the proxy username and password.
diff --git a/plugins/ssh/nbdkit-ssh-plugin.pod b/plugins/ssh/nbdkit-ssh-plugin.pod
index 0d0bc2b..687c08c 100644
--- a/plugins/ssh/nbdkit-ssh-plugin.pod
+++ b/plugins/ssh/nbdkit-ssh-plugin.pod
@@ -96,10 +96,16 @@ Ask for the password (interactively) when nbdkit starts up.
=item B<password=+>FILENAME
-Read the password from the named file. This is the most secure method
+Read the password from the named file. This is a secure method
to supply a password, as long as you set the permissions on the file
appropriately.
+=item B<password=->FD
+
+Read the password from file descriptor number C<FD>, inherited from
+the parent process when nbdkit starts up. This is also a secure
+method to supply a password.
+
=item [B<path=>]PATH
Specify the path to the remote file. This can be a relative path in
diff --git a/plugins/vddk/nbdkit-vddk-plugin.pod b/plugins/vddk/nbdkit-vddk-plugin.pod
index bf6f7e7..4ae647c 100644
--- a/plugins/vddk/nbdkit-vddk-plugin.pod
+++ b/plugins/vddk/nbdkit-vddk-plugin.pod
@@ -6,7 +6,8 @@ nbdkit-vddk-plugin - nbdkit VMware VDDK plugin
nbdkit vddk file=FILENAME [config=FILENAME] [libdir=LIBRARY]
[vm=moref=ID] [server=HOSTNAME] [user=USERNAME]
- [password=PASSWORD | password=- | password=+FILENAME]
+ [password=PASSWORD | password=- | password=+FILENAME |
+ password=-FD]
[cookie=COOKIE] [thumbprint=THUMBPRINT]
[port=PORT] [nfchostport=PORT] [single-link=true]
[snapshot=MOREF] [transports=MODE:MODE:...]
@@ -140,10 +141,16 @@ Ask for the password (interactively) when nbdkit starts up.
=item B<password=+>FILENAME
-Read the password from the named file. This is the most secure method
+Read the password from the named file. This is a secure method
to supply a password, as long as you set the permissions on the file
appropriately.
+=item B<password=->FD
+
+Read the password from file descriptor number C<FD>, inherited from
+the parent process when nbdkit starts up. This is also a secure
+method to supply a password.
+
=item B<port=>PORT
The port on the VCenter/ESXi host. Defaults to 443.
diff --git a/server/public.c b/server/public.c
index 9a3aa31..418945f 100644
--- a/server/public.c
+++ b/server/public.c
@@ -405,6 +405,8 @@ nbdkit_parse_bool (const char *str)
}
/* Read a password from configuration value. */
+static int read_password_from_fd (const char *what, int fd, char **password);
+
int
nbdkit_read_password (const char *value, char **password)
{
@@ -412,7 +414,6 @@ nbdkit_read_password (const char *value, char **password)
struct termios orig, temp;
ssize_t r;
size_t n;
- FILE *fp;
*password = NULL;
@@ -448,6 +449,16 @@ nbdkit_read_password (const char *value, char **password)
(*password)[r-1] = '\0';
}
+ /* Read from numbered file descriptor. */
+ else if (value[0] == '-') {
+ int fd;
+
+ if (nbdkit_parse_int ("password file descriptor", &value[1], &fd) == -1)
+ return -1;
+ if (read_password_from_fd (&value[1], fd, password) == -1)
+ return -1;
+ }
+
/* Read password from a file. */
else if (value[0] == '+') {
int fd;
@@ -457,22 +468,8 @@ nbdkit_read_password (const char *value, char **password)
nbdkit_error ("open %s: %m", &value[1]);
return -1;
}
- fp = fdopen (fd, "r");
- if (fp == NULL) {
- nbdkit_error ("fdopen %s: %m", &value[1]);
- close (fd);
+ if (read_password_from_fd (&value[1], fd, password) == -1)
return -1;
- }
- r = getline (password, &n, fp);
- err = errno;
- fclose (fp);
- if (r == -1) {
- errno = err;
- nbdkit_error ("could not read password from file %s: %m", &value[1]);
- return -1;
- }
- if (*password && r > 0 && (*password)[r-1] == '\n')
- (*password)[r-1] = '\0';
}
/* Parameter is the password. */
@@ -487,6 +484,35 @@ nbdkit_read_password (const char *value, char **password)
return 0;
}
+static int
+read_password_from_fd (const char *what, int fd, char **password)
+{
+ FILE *fp;
+ size_t n;
+ ssize_t r;
+ int err;
+
+ fp = fdopen (fd, "r");
+ if (fp == NULL) {
+ nbdkit_error ("fdopen %s: %m", what);
+ close (fd);
+ return -1;
+ }
+ r = getline (password, &n, fp);
+ err = errno;
+ fclose (fp);
+ if (r == -1) {
+ errno = err;
+ nbdkit_error ("could not read password from %s: %m", what);
+ return -1;
+ }
+
+ if (*password && r > 0 && (*password)[r-1] == '\n')
+ (*password)[r-1] = '\0';
+
+ return 0;
+}
+
int
nbdkit_nanosleep (unsigned sec, unsigned nsec)
{
diff --git a/server/test-public.c b/server/test-public.c
index ea10189..4a7eb17 100644
--- a/server/test-public.c
+++ b/server/test-public.c
@@ -335,6 +335,8 @@ test_nbdkit_read_password (void)
{
bool pass = true;
char template[] = "+/tmp/nbdkit_testpw_XXXXXX";
+ char template2[] = "/tmp/nbdkit_testpw2_XXXXXX";
+ char fdbuf[16];
char *pw = template;
int fd;
@@ -391,6 +393,35 @@ test_nbdkit_read_password (void)
unlink (&template[1]);
}
+ /* Test reading password from file descriptor. */
+ fd = mkstemp (template2);
+ if (fd < 0) {
+ perror ("mkstemp");
+ pass = false;
+ }
+ else if (write (fd, "abc\n", 4) != 4) {
+ fprintf (stderr, "Failed to write to file %s\n", template2);
+ pass = false;
+ }
+ else {
+ snprintf (fdbuf, sizeof fdbuf, "-%d", fd);
+ lseek (fd, 0, 0);
+ if (nbdkit_read_password (fdbuf, &pw) == -1) {
+ fprintf (stderr, "Failed to read password from fd %s\n", fdbuf);
+ pass = false;
+ }
+ else if (strcmp (pw, "abc") != 0) {
+ fprintf (stderr, "Wrong file password, expected 'abc' got '%s'\n", pw);
+ pass = false;
+ }
+ free (pw);
+ }
+
+ if (fd >= 0) {
+ /* Don't close fd, it is closed by nbdkit_read_password. */
+ unlink (template2);
+ }
+
if (error_flagged) {
fprintf (stderr, "Wrong error message handling\n");
pass = false;
--
2.23.0
5 years, 1 month
Using Clevis/Tang (NBDE) to automatically decrypt volumes from within libguestfs
by Richard W.M. Jones
This is about Network-Bound Disk Encryption (NBDE) not to be confused
of course with NBD! NBDE is where you use disk encryption in your
virtual machines. But instead of having to type a passphrase when the
guest boots, there is a network server which gives out tokens, so as
long as the guest is booted from the trusted network it is able to
boot unattended.
In RHEL[1] we have three pieces of software which help here:
- Clevis: Installed in the guest, it replaces the normal askpass
script with one which goes to the server to get the decryption
token.
- Tang: This is the server component, ie. it must always be running
on the trusted network so your guests can boot unattended.
- JOSE: Something something JSON encryption. Does some JSON
reformatting and is otherwise very opaque.
A disk from a VM which is using LUKS + NBDE will have a Clevis
keyslot, shown in luksDump output:
# cryptsetup luksDump /dev/sda2
...
Tokens:
0: clevis
Keyslot: 1
It will also usually have one or more regular keyslots, since a guest
which uses NBDE can also be booted disconnected from the trusted
network using a regular passphrase at the keyboard.
There's an obscure sequence of commands which can be used (when on the
trusted network of course) to unlock the disk:
Clevis token ID, not keyslot
|
V
# cryptsetup token export --token-id 0 /dev/sda2
{"type":"clevis","keyslots":["1"],"jwe":{"ciphertext":<....>,"encrypted_key":"","iv":<....>,"protected":<....>}}
We then use the JOSE tool to extract the "jwe" field alone. I'm not
clear if JOSE is necessary here, or we could use any other JSON tool.
>From casual inspection it appears all this is doing is taking the
"jwe" field and reformatting it as a top-level JSON object in the
output:
# jose fmt -j- -Og jwe -o- < /tmp/token.json
{"ciphertext":"CIPHERTEXT",
"encrypted_key":"",
"iv":"IV",
"protected":"PROTECTED\n"}
I've formatted the output, but in reality it's all in one very long
line of JSON. For me, encrypted_key field was empty, the other fields
had long ASCII strings in some kind of unknown (but printable)
encoding.
The next step takes the four fields and concatenates them with dots:
# jose fmt -j- -Og jwe -o- < /tmp/token | jose jwe fmt -i- -c
PROTECTED..IV.CIPHERTEXT
It's possible that the double dot contains the empty encrypted_key
field. The final string is about 1300 characters long, but printable.
We can use this to get the decryption key. Note networking must be
available for this to work:
# jose fmt -j- -Og jwe -o- < /tmp/token | jose jwe fmt -i- -c > /tmp/key
# clevis decrypt < /tmp/key
<-- prints a plaintext ASCII key here
# cryptsetup open --type luks /dev/sda2 vol
Enter passphrase for /dev/sda2: <-- type the ASCII key here
(I guess there is a way to automatically feed the key from
clevis-decrypt to cryptsetup)
This will unlock the disk.
To integrate this into libguestfs, we will probably need to carry out
the following steps:
(a) Add clevis and jose to the packagelist.
(b) In programs like virt-v2v that want to use NBDE:
(b-1) Increase the memory available to the appliance, see:
https://gitlab.com/cryptsetup/cryptsetup/issues/488
(b-2) Enable network, obviously necessary for NBDE.
(c) Decide how we are going to automate the clevis steps above. Do we
integrate this with the existing common/options/decrypt.c code? Or do
we change appliance/init so that it attempts these steps automatically
if clevis is present, if there are encrypted disks and if the network
is up?
Rich.
[1] RHEL and Fedora, but it's broken at the moment in Fedora:
https://bugzilla.redhat.com/show_bug.cgi?id=1628258
--
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
5 years, 1 month
Splitting the large libguestfs repo
by Richard W.M. Jones
I got a little way into this. The two attached patches are
preliminary work.
My proposed split is:
libguestfs.git
common -> git submodule libguestfs-common.git
generator/
lib/
all language bindings
C based tools (eg. virt-df, virt-edit, guestfish)
guestfs-tools.git
common -> git submodule libguestfs-common.git
virt-builder, virt-customize, virt-sparsify, virt-sparsify, etc
virt-v2v.git
common -> git submodule libguestfs-common.git
virt-p2v.git
[already done]
The current common/ subdirectory would become a git submodule. While
git submodules are awkward, they do solve this particular problem with
having common code shared across the repositories, there's only one
git submodule and it's under our control. It does mean that any time
there's a change to common/, we would need to add a commit to the
other 3 repos updating the submodule hash.
There are only 6 generated files which are not confined to the new
libguestfs.git. 4 files are needed by virt-customize and 2 by
virt-v2v (and these last 2 will probably go away in future). My
proposal (in patch 2/2) is that we simply copy these 6 files into the
common submodule. It's not very elegant but it only affects a tiny
fraction of all generated files, these ones are updated very
infrequently, and it solves the problem for now. A possible future
path would be to split the generator so there's a mini-generator for
virt-customize.
libguestfs-common.git can be created from libguestfs.git using 'git
filter-branch --prune-empty --subdirectory-filter common master'. I
tested this already and it seems to work.
The two other new repos can be created using the rather more complex
technique described here which I didn't try yet:
https://stackoverflow.com/questions/2797191/how-to-split-a-git-repository...
Previous discussion was in this thread over several months:
https://www.redhat.com/archives/libguestfs/2018-February/thread.html#00067
https://www.redhat.com/archives/libguestfs/2019-April/thread.html#00260
https://www.redhat.com/archives/libguestfs/2019-June/thread.html#00095
https://www.redhat.com/archives/libguestfs/2019-July/thread.html#00000
Rich.
5 years, 1 month
[PATCH] v2v: Output saved overlays in a machine-readable fashion
by Martin Kletzander
Even though this option is not to be used according to the manual, it:
a) still might be useful even for machine-readable logs
b) should not break the machine-readable output
Signed-off-by: Martin Kletzander <mkletzan(a)redhat.com>
---
v2v/v2v.ml | 29 ++++++++++++++++++++++-------
1 file changed, 22 insertions(+), 7 deletions(-)
diff --git a/v2v/v2v.ml b/v2v/v2v.ml
index 4ee15663f261..508a2b4f39a5 100644
--- a/v2v/v2v.ml
+++ b/v2v/v2v.ml
@@ -815,13 +815,28 @@ and actual_target_size target_file disk_stats =
(* Save overlays if --debug-overlays option was used. *)
and preserve_overlays overlays src_name =
- List.iter (
- fun ov ->
- let saved_filename =
- sprintf "%s/%s-%s.qcow2" overlay_dir src_name ov.ov_sd in
- rename ov.ov_overlay_file saved_filename;
- info (f_"Overlay saved as %s [--debug-overlays]") saved_filename
- ) overlays
+ let filenames = List.map (
+ fun ov ->
+ let saved_filename =
+ sprintf "%s/%s-%s.qcow2" overlay_dir src_name ov.ov_sd in
+ rename ov.ov_overlay_file saved_filename;
+ saved_filename
+ ) overlays in
+ match machine_readable () with
+ | None ->
+ List.iter (
+ fun filename ->
+ info (f_"Overlay saved as %s [--debug-overlays]") filename
+ ) filenames
+ | Some {pr} ->
+ let json = [
+ "type", JSON.String "data";
+ "data", JSON.Dict [
+ "saved_overlays",
+ JSON.List (List.map (fun s -> JSON.String s) filenames);
+ ]
+ ] in
+ pr "%s\n" (JSON.string_of_doc json)
(* Request guest caps based on source configuration. *)
and rcaps_from_source source =
--
2.23.0
5 years, 1 month
[PATCH libnbd] nbdfuse: New tool to present a network block device in a FUSE filesystem.
by Richard W.M. Jones
This program allows you to turn a network block device source into a
FUSE filesystem containing a virtual file:
$ nbdkit memory 128M
$ mkdir mp
$ nbdfuse mp/ramdisk nbd://localhost &
$ ls -l mp
total 0
-rw-rw-rw-. 1 rjones rjones 134217728 Oct 12 15:09 ramdisk
$ dd if=/dev/urandom bs=1M count=128 of=mp/ramdisk conv=notrunc,nocreat
128+0 records in
128+0 records out
134217728 bytes (134 MB, 128 MiB) copied, 3.10171 s, 43.3 MB/s
$ fusermount -u mp
There are still some shortcomings, such as lack of zero and trim
support. These are documented in the TODO file.
Now for some history:
In libguestfs which is where most of this code derives from we have a
program called ‘guestmount’ which is a FUSE interface to libguestfs:
http://libguestfs.org/guestmount.1.html
Originally that was a standalone program like nbdfuse, but after some
time we realized that the ability to mount libguestfs under a
directory was generally useful to all guestfs API users and we created
new APIs for it. guestmount has now become a thin wrapper around
those APIs.
This of course argues that we should do the same thing for libnbd.
But ...
(1) For NBD this is a little less useful than for libguestfs.
(2) We can always do this in future if we need to.
Most importantly:
(3) the libguestfs FUSE API turned out to have a problem - still
unresolved - with handling threads and SELinux and it may not be a
good idea to bake this into the libnbd API until that problem has been
solved. For more details about (3), read this:
https://bugzilla.redhat.com/show_bug.cgi?id=1060423#c2
---
.gitignore | 2 +
Makefile.am | 3 +-
README | 2 +
TODO | 7 +
configure.ac | 18 ++
docs/libnbd.pod | 1 +
fuse/Makefile.am | 60 +++++
fuse/nbdfuse.c | 590 ++++++++++++++++++++++++++++++++++++++++++++
fuse/nbdfuse.pod | 262 ++++++++++++++++++++
fuse/test-nbdkit.sh | 63 +++++
fuse/test-qcow2.sh | 64 +++++
run.in | 1 +
sh/nbdsh.pod | 1 +
13 files changed, 1073 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 1970e6c..f2654a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,6 +57,8 @@ Makefile.in
/examples/server-flags
/examples/strict-structured-reads
/examples/threaded-reads-and-writes
+/fuse/nbdfuse
+/fuse/nbdfuse.1
/fuzzing/libnbd-fuzz-wrapper
/fuzzing/sync_dir/
/generator/generator-cache.v1
diff --git a/Makefile.am b/Makefile.am
index b2d9dca..568e735 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -39,6 +39,7 @@ SUBDIRS = \
tests \
python \
sh \
+ fuse \
ocaml \
ocaml/examples \
ocaml/tests \
@@ -64,7 +65,7 @@ maintainer-check-extra-dist:
@echo PASS: EXTRA_DIST tests
check-valgrind: all
- @for d in tests ocaml/tests interop; do \
+ @for d in tests fuse ocaml/tests interop; do \
$(MAKE) -C $$d check-valgrind || exit 1; \
done
diff --git a/README b/README
index 1c9d816..8d6b563 100644
--- a/README
+++ b/README
@@ -82,6 +82,8 @@ Optional:
* Python >= 3.3 to build the Python 3 bindings and NBD shell (nbdsh).
+ * FUSE to build the nbdfuse program.
+
Optional, only needed to run the test suite:
* nbdkit >= 1.12, the nbdkit basic plugins and the nbdkit basic
diff --git a/TODO b/TODO
index 71d678b..8b7dbe4 100644
--- a/TODO
+++ b/TODO
@@ -27,6 +27,13 @@ Should we ship a "nbdcp" copying tool?
- Could upload, download or copy between servers.
- Duplicates functionality already available in qemu-img convert.
+nbdfuse:
+ - If you write beyond the end of the virtual file, it returns EIO.
+ - Implement trim/discard.
+ - Implement write_zeroes.
+ - Implement block_status.
+ - Could be made multithreaded for improved performance.
+
Suggested API improvements:
general:
- synchronous APIs that have a timeout or can be cancelled
diff --git a/configure.ac b/configure.ac
index fde43dc..96cb4bd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -200,6 +200,23 @@ PKG_CHECK_MODULES([GLIB], [glib-2.0], [
])
AM_CONDITIONAL([HAVE_GLIB], [test "x$GLIB_LIBS" != "x"])
+dnl FUSE is optional to build the FUSE module.
+AC_ARG_ENABLE([fuse],
+ AS_HELP_STRING([--disable-fuse], [disable FUSE (guestmount) support]),
+ [],
+ [enable_fuse=yes])
+AS_IF([test "x$enable_fuse" != "xno"],[
+ PKG_CHECK_MODULES([FUSE],[fuse],[
+ AC_SUBST([FUSE_CFLAGS])
+ AC_SUBST([FUSE_LIBS])
+ AC_DEFINE([HAVE_FUSE],[1],[Define to 1 if you have FUSE.])
+ ],[
+ enable_fuse=no
+ AC_MSG_WARN([FUSE library and headers are missing, so optional FUSE module won't be built])
+ ])
+])
+AM_CONDITIONAL([HAVE_FUSE],[test "x$enable_fuse" != "xno"])
+
dnl Check we have enough to run podwrapper.
AC_CHECK_PROG([PERL],[perl],[perl],[no])
AS_IF([test "x$PERL" != "xno"],[
@@ -353,6 +370,7 @@ AC_CONFIG_FILES([Makefile
common/include/Makefile
docs/Makefile
examples/Makefile
+ fuse/Makefile
fuzzing/Makefile
generator/Makefile
include/Makefile
diff --git a/docs/libnbd.pod b/docs/libnbd.pod
index 2c3eaf4..9ab6150 100644
--- a/docs/libnbd.pod
+++ b/docs/libnbd.pod
@@ -840,6 +840,7 @@ L<https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md>.
L<libnbd-security(3)>,
L<nbdsh(1)>,
+L<nbdfuse(1)>,
L<qemu(1)>.
=head1 AUTHORS
diff --git a/fuse/Makefile.am b/fuse/Makefile.am
new file mode 100644
index 0000000..3d827aa
--- /dev/null
+++ b/fuse/Makefile.am
@@ -0,0 +1,60 @@
+# nbd client library in userspace
+# Copyright (C) 2013-2019 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+include $(top_srcdir)/subdir-rules.mk
+
+EXTRA_DIST = \
+ nbdfuse.pod \
+ test-nbdkit.sh \
+ test-qcow2.sh \
+ $(NULL)
+
+TESTS_ENVIRONMENT = LIBNBD_DEBUG=1
+LOG_COMPILER = $(top_builddir)/run
+TESTS =
+
+if HAVE_FUSE
+
+bin_PROGRAMS = nbdfuse
+
+nbdfuse_SOURCES = nbdfuse.c
+nbdfuse_CPPFLAGS = -I$(top_srcdir)/include
+nbdfuse_CFLAGS = $(WARNINGS_CFLAGS) $(FUSE_CFLAGS)
+nbdfuse_LDADD = $(top_builddir)/lib/libnbd.la $(FUSE_LIBS)
+
+if HAVE_POD
+
+man_MANS = \
+ nbdfuse.1 \
+ $(NULL)
+
+nbdfuse.1: nbdfuse.pod $(top_builddir)/podwrapper.pl
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
+
+TESTS += \
+ test-nbdkit.sh \
+ test-qcow2.sh \
+ $(NULL)
+
+check-valgrind:
+ LIBNBD_VALGRIND=1 $(MAKE) check
+
+endif HAVE_FUSE
diff --git a/fuse/nbdfuse.c b/fuse/nbdfuse.c
new file mode 100644
index 0000000..5703b95
--- /dev/null
+++ b/fuse/nbdfuse.c
@@ -0,0 +1,590 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2019 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* FUSE support. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define FUSE_USE_VERSION 26
+
+#include <fuse.h>
+#include <fuse_lowlevel.h>
+
+#include <libnbd.h>
+
+#define MAX_REQUEST_SIZE (64 * 1024 * 1024)
+
+static struct nbd_handle *nbd;
+static bool readonly = false;
+static char *mountpoint, *filename;
+static const char *pidfile;
+static char *fuse_options;
+static struct fuse_chan *ch;
+static struct fuse *fuse;
+static struct timespec start_t;
+static uint64_t size;
+
+static int nbdfuse_getattr (const char *path, struct stat *stbuf);
+static int nbdfuse_readdir (const char *path, void *buf,
+ fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi);
+static int nbdfuse_open (const char *path, struct fuse_file_info *fi);
+static int nbdfuse_read (const char *path, char *buf,
+ size_t count, off_t offset,
+ struct fuse_file_info *fi);
+static int nbdfuse_write (const char *path, const char *buf,
+ size_t count, off_t offset,
+ struct fuse_file_info *fi);
+static int nbdfuse_fsync (const char *path, int datasync,
+ struct fuse_file_info *fi);
+static int nbdfuse_release (const char *path, struct fuse_file_info *fi);
+
+static struct fuse_operations fuse_operations = {
+ .getattr = nbdfuse_getattr,
+ .readdir = nbdfuse_readdir,
+ .open = nbdfuse_open,
+ .read = nbdfuse_read,
+ .write = nbdfuse_write,
+ .fsync = nbdfuse_fsync,
+ .release = nbdfuse_release,
+};
+
+static void __attribute__((noreturn))
+usage (FILE *fp, int exitcode)
+{
+ fprintf (fp,
+" nbdfuse [-r] MOUNTPOINT[/FILENAME] URI\n"
+"Other modes:\n"
+" nbdfuse MOUNTPOINT[/FILENAME] --command CMD [ARGS ...]\n"
+" nbdfuse MOUNTPOINT[/FILENAME] --socket-activation CMD [ARGS ...]\n"
+" nbdfuse MOUNTPOINT[/FILENAME] --fd N\n"
+" nbdfuse MOUNTPOINT[/FILENAME] --tcp HOST PORT\n"
+" nbdfuse MOUNTPOINT[/FILENAME] --unix SOCKET\n"
+"\n"
+"Please read the nbdfuse(1) manual page for full usage.\n"
+);
+ exit (exitcode);
+}
+
+static void
+display_version (void)
+{
+ printf ("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
+}
+
+static void
+fuse_help (const char *prog)
+{
+ static struct fuse_operations null_operations;
+ const char *tmp_argv[] = { prog, "--help", NULL };
+ fuse_main (2, (char **) tmp_argv, &null_operations, NULL);
+ exit (EXIT_SUCCESS);
+}
+
+static bool
+is_directory (const char *path)
+{
+ struct stat statbuf;
+
+ if (stat (path, &statbuf) == -1)
+ return false;
+ return S_ISDIR (statbuf.st_mode);
+}
+
+int
+main (int argc, char *argv[])
+{
+ enum {
+ MODE_URI,
+ MODE_COMMAND,
+ MODE_FD,
+ MODE_SOCKET_ACTIVATION,
+ MODE_TCP,
+ MODE_UNIX,
+ } mode = MODE_URI;
+ enum {
+ HELP_OPTION = CHAR_MAX + 1,
+ FUSE_HELP_OPTION,
+ };
+ /* Note the "+" means we stop processing as soon as we get to the
+ * first non-option argument (the mountpoint) and then we parse the
+ * rest of the command line without getopt.
+ */
+ const char *short_options = "+o:P:rV";
+ const struct option long_options[] = {
+ { "fuse-help", no_argument, NULL, FUSE_HELP_OPTION },
+ { "help", no_argument, NULL, HELP_OPTION },
+ { "pidfile", required_argument, NULL, 'P' },
+ { "pid-file", required_argument, NULL, 'P' },
+ { "readonly", no_argument, NULL, 'r' },
+ { "read-only", no_argument, NULL, 'r' },
+ { "version", no_argument, NULL, 'V' },
+
+ { NULL }
+ };
+ int c, fd, r;
+ int64_t ssize;
+ const char *s;
+ struct fuse_args fuse_args = FUSE_ARGS_INIT (0, NULL);
+ struct sigaction sa;
+ FILE *fp;
+
+ for (;;) {
+ c = getopt_long (argc, argv, short_options, long_options, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case HELP_OPTION:
+ usage (stdout, EXIT_SUCCESS);
+
+ case FUSE_HELP_OPTION:
+ fuse_help (argv[0]);
+ exit (EXIT_SUCCESS);
+
+ case 'o':
+ fuse_opt_add_opt_escaped (&fuse_options, optarg);
+ break;
+
+ case 'P':
+ pidfile = optarg;
+ break;
+
+ case 'r':
+ readonly = true;
+ break;
+
+ case 'V':
+ display_version ();
+ exit (EXIT_SUCCESS);
+
+ default:
+ fprintf (stderr, "\n");
+ usage (stderr, EXIT_FAILURE);
+ }
+ }
+
+ /* There must be at least 2 parameters (mountpoint and
+ * URI/--command/etc).
+ */
+ if (argc - optind < 2)
+ usage (stderr, EXIT_FAILURE);
+
+ /* Parse and check the mountpoint. It might be MOUNTPOINT or
+ * MOUNTPOINT/FILENAME. In either case MOUNTPOINT must be an
+ * existing directory.
+ */
+ s = argv[optind++];
+ if (is_directory (s)) {
+ mountpoint = strdup (s);
+ filename = strdup ("nbd");
+ if (mountpoint == NULL || filename == NULL) {
+ strdup_error:
+ perror ("strdup");
+ exit (EXIT_FAILURE);
+ }
+ }
+ else {
+ const char *p = strrchr (s, '/');
+
+ if (p == NULL) {
+ mp_error:
+ fprintf (stderr, "%s: %s: "
+ "mountpoint must be \"directory\" or \"directory/filename\"\n",
+ argv[0], s);
+ exit (EXIT_FAILURE);
+ }
+ mountpoint = strndup (s, p-s);
+ if (mountpoint == NULL) goto strdup_error;
+ if (! is_directory (mountpoint)) goto mp_error;
+ if (strlen (p+1) == 0) goto mp_error;
+ filename = strdup (p+1);
+ if (filename == NULL) goto strdup_error;
+ }
+
+ /* The next parameter is either a URI or a mode switch. */
+ if (strcmp (argv[optind], "--command") == 0 ||
+ strcmp (argv[optind], "--cmd") == 0) {
+ mode = MODE_COMMAND;
+ optind++;
+ }
+ else if (strcmp (argv[optind], "--socket-activation") == 0 ||
+ strcmp (argv[optind], "--systemd-socket-activation") == 0) {
+ mode = MODE_SOCKET_ACTIVATION;
+ optind++;
+ }
+ else if (strcmp (argv[optind], "--fd") == 0) {
+ mode = MODE_FD;
+ optind++;
+ }
+ else if (strcmp (argv[optind], "--tcp") == 0) {
+ mode = MODE_TCP;
+ optind++;
+ }
+ else if (strcmp (argv[optind], "--unix") == 0) {
+ mode = MODE_UNIX;
+ optind++;
+ }
+ else if (argv[optind][0] == '-') {
+ fprintf (stderr, "%s: unknown mode: %s\n\n", argv[0], argv[optind]);
+ usage (stderr, EXIT_FAILURE);
+ }
+
+ /* Check there are enough parameters following given the mode. */
+ switch (mode) {
+ case MODE_URI:
+ case MODE_FD:
+ case MODE_UNIX:
+ if (argc - optind != 1)
+ usage (stderr, EXIT_FAILURE);
+ break;
+ case MODE_TCP:
+ if (argc - optind != 2)
+ usage (stderr, EXIT_FAILURE);
+ break;
+ case MODE_COMMAND:
+ case MODE_SOCKET_ACTIVATION:
+ if (argc - optind < 1)
+ usage (stderr, EXIT_FAILURE);
+ break;
+ }
+ /* At this point we know the command line is valid, and so can start
+ * opening FUSE and libnbd.
+ */
+
+ /* Create the libnbd handle. */
+ nbd = nbd_create ();
+ if (nbd == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ /* Connect to the NBD server synchronously. */
+ switch (mode) {
+ case MODE_URI:
+ if (nbd_connect_uri (nbd, argv[optind]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ case MODE_COMMAND:
+ if (nbd_connect_command (nbd, &argv[optind]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ case MODE_SOCKET_ACTIVATION:
+ if (nbd_connect_systemd_socket_activation (nbd, &argv[optind]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ case MODE_FD:
+ if (sscanf (argv[optind], "%d", &fd) != 1) {
+ fprintf (stderr, "%s: could not parse file descriptor: %s\n\n",
+ argv[0], argv[optind]);
+ exit (EXIT_FAILURE);
+ }
+ if (nbd_connect_socket (nbd, fd) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ case MODE_TCP:
+ if (nbd_connect_tcp (nbd, argv[optind], argv[optind+1]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ case MODE_UNIX:
+ if (nbd_connect_unix (nbd, argv[optind]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ break;
+ }
+
+ ssize = nbd_get_size (nbd);
+ if (ssize == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ size = (uint64_t) ssize;
+
+ /* This is just used to give an unchanging time when they stat in
+ * the mountpoint.
+ */
+ clock_gettime (CLOCK_REALTIME, &start_t);
+
+ /* Create the FUSE args. */
+ if (fuse_opt_add_arg (&fuse_args, argv[0]) == -1) {
+ fuse_opt_error:
+ perror ("fuse_opt_add_arg");
+ exit (EXIT_FAILURE);
+ }
+
+ if (fuse_options) {
+ if (fuse_opt_add_arg (&fuse_args, "-o") == -1 ||
+ fuse_opt_add_arg (&fuse_args, fuse_options) == -1)
+ goto fuse_opt_error;
+ }
+
+ /* Create the FUSE mountpoint. */
+ ch = fuse_mount (mountpoint, &fuse_args);
+ if (ch == NULL) {
+ fprintf (stderr,
+ "%s: fuse_mount failed: see error messages above", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Set F_CLOEXEC on the channel. Some versions of libfuse don't do
+ * this.
+ */
+ fd = fuse_chan_fd (ch);
+ if (fd >= 0) {
+ int flags = fcntl (fd, F_GETFD, 0);
+ if (flags >= 0)
+ fcntl (fd, F_SETFD, flags & ~FD_CLOEXEC);
+ }
+
+ /* Create the FUSE handle. */
+ fuse = fuse_new (ch, &fuse_args,
+ &fuse_operations, sizeof fuse_operations, NULL);
+ if (!fuse) {
+ perror ("fuse_new");
+ exit (EXIT_FAILURE);
+ }
+ fuse_opt_free_args (&fuse_args);
+
+ /* Catch signals since they can leave the mountpoint in a funny
+ * state. To exit the program callers must use ‘fusermount -u’. We
+ * also must be careful not to call exit(2) in this program until we
+ * have unmounted the filesystem below.
+ */
+ memset (&sa, 0, sizeof sa);
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_RESTART;
+ sigaction (SIGPIPE, &sa, NULL);
+ sigaction (SIGINT, &sa, NULL);
+ sigaction (SIGQUIT, &sa, NULL);
+
+ /* Ready to serve, write pidfile. */
+ if (pidfile) {
+ fp = fopen (pidfile, "w");
+ if (fp) {
+ fprintf (fp, "%ld", (long) getpid ());
+ fclose (fp);
+ }
+ }
+
+ /* Enter the main loop. */
+ r = fuse_loop (fuse);
+ if (r != 0)
+ perror ("fuse_loop");
+
+ /* Close FUSE. */
+ fuse_unmount (mountpoint, ch);
+ fuse_destroy (fuse);
+
+ /* Close NBD handle. */
+ nbd_close (nbd);
+
+ free (mountpoint);
+ free (filename);
+ free (fuse_options);
+
+ exit (r == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+/* Wraps calls to libnbd functions and automatically checks for a
+ * returns errors in the format required by FUSE. It also prints out
+ * the full error message on stderr, so that we don't lose it.
+ */
+#define CHECK_NBD_ERROR(CALL) \
+ do { if ((CALL) == -1) return check_nbd_error (); } while (0)
+static int
+check_nbd_error (void)
+{
+ int err;
+
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ err = nbd_get_errno ();
+ if (err != 0)
+ return -err;
+ else
+ return -EIO;
+}
+
+static int
+nbdfuse_getattr (const char *path, struct stat *statbuf)
+{
+ const int mode = readonly ? 0444 : 0666;
+
+ memset (statbuf, 0, sizeof (struct stat));
+
+ /* We're probably making some Linux-specific assumptions here, but
+ * this file is not compiled on non-Linux systems.
+ */
+ statbuf->st_atim = start_t;
+ statbuf->st_mtim = start_t;
+ statbuf->st_ctim = start_t;
+ statbuf->st_uid = geteuid ();
+ statbuf->st_gid = getegid ();
+
+ if (strcmp (path, "/") == 0) {
+ /* getattr "/" */
+ statbuf->st_mode = S_IFDIR | (mode & 0111);
+ statbuf->st_nlink = 2;
+ }
+ else if (path[0] == '/' && strcmp (path+1, filename) == 0) {
+ /* getattr "/filename" */
+ statbuf->st_mode = S_IFREG | mode;
+ statbuf->st_nlink = 1;
+ statbuf->st_size = size;
+ }
+ else
+ return -ENOENT;
+
+ return 0;
+}
+
+static int
+nbdfuse_readdir (const char *path, void *buf,
+ fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi)
+{
+ if (strcmp (path, "/") != 0)
+ return -ENOENT;
+
+ filler (buf, ".", NULL, 0);
+ filler (buf, "..", NULL, 0);
+ filler (buf, filename, NULL, 0);
+
+ return 0;
+}
+
+/* This function checks the O_RDONLY/O_RDWR flags passed to the
+ * open(2) call, so we have to check the open mode is compatible with
+ * the readonly flag.
+ */
+static int
+nbdfuse_open (const char *path, struct fuse_file_info *fi)
+{
+ if (path[0] != '/' || strcmp (path+1, filename) != 0)
+ return -ENOENT;
+
+ if (readonly && (fi->flags & O_ACCMODE) != O_RDONLY)
+ return -EACCES;
+
+ return 0;
+}
+
+static int
+nbdfuse_read (const char *path, char *buf,
+ size_t count, off_t offset,
+ struct fuse_file_info *fi)
+{
+ if (path[0] != '/' || strcmp (path+1, filename) != 0)
+ return -ENOENT;
+
+ if (offset >= size)
+ return 0;
+
+ if (count > MAX_REQUEST_SIZE)
+ count = MAX_REQUEST_SIZE;
+
+ if (offset + count > size)
+ count = size - offset;
+
+ CHECK_NBD_ERROR (nbd_pread (nbd, buf, count, offset, 0));
+
+ return (int) count;
+}
+
+static int
+nbdfuse_write (const char *path, const char *buf,
+ size_t count, off_t offset,
+ struct fuse_file_info *fi)
+{
+ /* Probably shouldn't happen because of nbdfuse_open check. */
+ if (readonly)
+ return -EACCES;
+
+ if (path[0] != '/' || strcmp (path+1, filename) != 0)
+ return -ENOENT;
+
+ if (offset >= size)
+ return 0;
+
+ if (count > MAX_REQUEST_SIZE)
+ count = MAX_REQUEST_SIZE;
+
+ if (offset + count > size)
+ count = size - offset;
+
+ CHECK_NBD_ERROR (nbd_pwrite (nbd, buf, count, offset, 0));
+
+ return (int) count;
+}
+
+static int
+nbdfuse_fsync (const char *path, int datasync, struct fuse_file_info *fi)
+{
+ if (readonly)
+ return 0;
+
+ /* If the server doesn't support flush then the operation is
+ * silently ignored.
+ */
+ if (nbd_can_flush (nbd))
+ CHECK_NBD_ERROR (nbd_flush (nbd, 0));
+
+ return 0;
+}
+
+/* This is called on the last close of a file. We do a flush here to
+ * be on the safe side, but it's not strictly necessary.
+ */
+static int
+nbdfuse_release (const char *path, struct fuse_file_info *fi)
+{
+ if (readonly)
+ return 0;
+
+ return nbdfuse_fsync (path, 0, fi);
+}
diff --git a/fuse/nbdfuse.pod b/fuse/nbdfuse.pod
new file mode 100644
index 0000000..e43e23c
--- /dev/null
+++ b/fuse/nbdfuse.pod
@@ -0,0 +1,262 @@
+=head1 NAME
+
+nbdfuse - present a network block device in a FUSE filesystem
+
+=head1 SYNOPSIS
+
+ nbdfuse [-o FUSE-OPTION] [-P PIDFILE] [-r]
+ MOUNTPOINT[/FILENAME] URI
+
+Other modes:
+
+ nbdfuse MOUNTPOINT[/FILENAME] --command CMD [ARGS ...]
+
+ nbdfuse MOUNTPOINT[/FILENAME] --socket-activation CMD [ARGS ...]
+
+ nbdfuse MOUNTPOINT[/FILENAME] --fd N
+
+ nbdfuse MOUNTPOINT[/FILENAME] --tcp HOST PORT
+
+ nbdfuse MOUNTPOINT[/FILENAME] --unix SOCKET
+
+=head1 DESCRIPTION
+
+nbdfuse presents a Network Block Device as a local file inside a FUSE
+filesystem.
+
+The FUSE filesystem is mounted at F<MOUNTPOINT> and contains a single
+virtual file called F<FILENAME> (defaulting to F<nbd>). Reads and
+writes to the virtual file or device are turned into reads and writes
+to the NBD device.
+
+The NBD device itself can be local or remote and is specified by an
+NBD URI (like C<nbd://localhost>, see L<nbd_connect_uri(3)>) or
+various other modes.
+
+Use C<fusermount -u MOUNTPOINT> to unmount the filesystem after you
+have used it.
+
+This program is similar in concept to L<nbd-client(8)> (which turns
+NBD into F</dev/nbdX> device nodes), except:
+
+=over 4
+
+=item *
+
+nbd-client is faster because it uses a special kernel module
+
+=item *
+
+nbd-client requires root, but nbdfuse can be used by any user
+
+=item *
+
+nbdfuse virtual files can be mounted anywhere in the filesystem
+
+=item *
+
+nbdfuse uses libnbd to talk to the NBD server
+
+=item *
+
+nbdfuse requires FUSE support in the kernel
+
+=back
+
+=head1 EXAMPLES
+
+=head2 Present a remote NBD server as a local file
+
+If there is a remote NBD server running on C<example.com> at the
+default NBD port number (10809) then you can turn it into a local file
+by doing:
+
+ $ mkdir dir
+ $ nbdfuse dir nbd://example.com &
+ $ ls -l dir/
+ total 0
+ -rw-rw-rw-. 1 nbd nbd 1073741824 Jan 1 10:10 nbd
+
+The file is called F<dir/nbd> and you can read and write to it as if
+it is a normal file. Note that writes to the file will write to the
+remote NBD server. After using it, unmount it:
+
+ $ fusermount -u dir
+ $ rmdir dir
+
+=head2 Use nbdkit to create a file backed by a temporary RAM disk
+
+L<nbdkit(1)> has an I<-s> option allowing it to serve over
+stdin/stdout. You can combine this with nbdfuse as follows:
+
+ $ mkdir dir
+ $ nbdfuse dir/ramdisk --command nbdkit -s memory 1G &
+ $ ls -l dir/
+ total 0
+ -rw-rw-rw-. 1 nbd nbd 1073741824 Jan 1 10:10 ramdisk
+ $ dd if=/dev/urandom bs=1M count=100 of=mp/ramdisk conv=notrunc,nocreat
+ 100+0 records in
+ 100+0 records out
+ 104857600 bytes (105 MB, 100 MiB) copied, 2.08319 s, 50.3 MB/s
+
+When you have finished with the RAM disk, you can unmount it as below
+which will cause nbdkit to exit and the RAM disk contents to be
+discarded:
+
+ $ fusermount -u dir
+ $ rmdir dir
+
+=head2 Use qemu-nbd to read and modify a qcow2 file
+
+L<qemu-nbd(8)> cannot serve over stdin/stdout, but it can use systemd
+socket activation. You can combine this with nbdfuse and use it to
+open any file format which qemu understands:
+
+ $ mkdir dir
+ $ nbdfuse dir/file.raw \
+ --socket-activation qemu-nbd -f qcow2 file.qcow2 &
+ $ ls -l dir/
+ total 0
+ -rw-rw-rw-. 1 nbd nbd 1073741824 Jan 1 10:10 file.raw
+
+File F<dir/file.raw> is in raw format, backed by F<file.qcow2>. Any
+changes made to F<dir/file.raw> are reflected into the qcow2 file. To
+unmount the file do:
+
+ $ fusermount -u dir
+ $ rmdir dir
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display brief command line help and exit.
+
+=item B<--fuse-help>
+
+Display FUSE options and exit. See I<-o> below.
+
+=item B<--command> CMD [ARGS ...]
+
+Select command mode. In this mode an NBD server can be run directly
+from the command line with nbdfuse communicating with the server over
+the server’s stdin/stdout. Normally you would use this with
+C<nbdkit -s>. See L</EXAMPLES> above and L<nbd_connect_command(3)>.
+
+=item B<--fd> N
+
+Select file descriptor mode. In this mode a connected socket is
+passed to nbdfuse. nbdfuse connects to the socket on the numbered
+file descriptor. See also L<nbd_connect_socket(3)>.
+
+=item B<-o> FUSE-OPTION
+
+Pass extra options to FUSE. To get a list of all the extra options
+supported by FUSE, use I<--fuse-help>.
+
+Some potentially useful FUSE options:
+
+=over 4
+
+=item B<-o> B<allow_other>
+
+Allow other users to see the filesystem. This option has no effect
+unless you enable it globally in F</etc/fuse.conf>.
+
+=item B<-o> B<kernel_cache>
+
+Allow the kernel to cache files (reduces the number of reads that have
+to go through the L<libnbd(3)> API). This is generally a good idea if
+you can afford the extra memory usage.
+
+=item B<-o> B<uid=>N B<-o> B<gid=>N
+
+Use these options to map UIDs and GIDs.
+
+=back
+
+=item B<-P> PIDFILE
+
+=item B<--pidfile> PIDFILE
+
+When nbdfuse is ready to serve, write the nbdfuse process ID (PID) to
+F<PIDFILE>. This can be used in scripts to wait until nbdfuse is
+ready. Note you mustn't try to kill nbdfuse. Use C<fusermount -u> to
+unmount the mountpoint which will cause nbdfuse to exit cleanly.
+
+=item B<-r>
+
+=item B<--readonly>
+
+Access the network block device read-only. The virtual file will have
+read-only permissions, and any writes will return errors.
+
+=item B<--socket-activation> CMD [ARGS ...]
+
+Select systemd socket activation mode. This is similar to
+I<--command>, but is used for servers like L<qemu-nbd(8)> which
+support systemd socket activation. See L</EXAMPLES> above and
+L<nbd_connect_systemd_socket_activation(3)>.
+
+=item B<--tcp> HOST PORT
+
+Select TCP mode. Connect to an NBD server on a host and port over an
+unencrypted TCP socket. See also L<nbd_connect_tcp(3)>.
+
+=item B<--unix> SOCKET
+
+Select Unix mode. Connect to an NBD server on a Unix domain socket.
+See also L<nbd_connect_unix(3)>.
+
+=item B<-V>
+
+=item B<--version>
+
+Display the package name and version and exit.
+
+=back
+
+=head1 NOTES
+
+=head2 Loop mounting
+
+It is tempting (and possible) to loop mount the file. However this
+will be very slow and may sometimes deadlock. Better alternatives are
+to use either L<nbd-client(8)>, or more securely L<libguestfs(3)>,
+L<guestfish(1)> or L<guestmount(1)> which can all access NBD servers.
+
+=head2 As a way to access NBD servers
+
+You can use this to access NBD servers, but it is usually better (and
+definitely much faster) to use L<libnbd(3)> directly instead. To
+access NBD servers from the command line, look at L<nbdsh(1)>.
+
+=head1 SEE ALSO
+
+L<libnbd(3)>,
+L<nbdsh(1)>,
+L<fusermount(1)>,
+L<mount.fuse(8)>,
+L<nbd_connect_uri(3)>,
+L<nbd_connect_command(3)>,
+L<nbd_connect_socket(3)>,
+L<nbd_connect_systemd_socket_activation(3)>,
+L<nbd_connect_tcp(3)>,
+L<nbd_connect_unix(3)>,
+L<libguestfs(3)>,
+L<guestfish(1)>,
+L<guestmount(1)>,
+L<nbdkit(1)>,
+L<nbdkit-loop(1)>,
+L<qemu-nbd(8)>,
+L<nbd-client(8)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2019 Red Hat Inc.
diff --git a/fuse/test-nbdkit.sh b/fuse/test-nbdkit.sh
new file mode 100755
index 0000000..fe7279b
--- /dev/null
+++ b/fuse/test-nbdkit.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+# Test nbdfuse + nbdkit.
+
+. ../tests/functions.sh
+
+set -e
+set -x
+
+requires nbdkit --exit-with-parent --version
+requires cmp --version
+requires dd --version
+
+if ! test -r /dev/urandom; then
+ echo "$0: test skipped: /dev/urandom not readable"
+ exit 77
+fi
+
+pidfile=test-nbdkit.pid
+mp=test-nbdkit.d
+data=test-nbdkit.data
+cleanup_fn fusermount -u $mp
+cleanup_fn rm -rf $mp
+cleanup_fn rm -f $pidfile $data
+
+mkdir -p $mp
+$VG nbdfuse -P $pidfile $mp \
+ --command nbdkit -s --exit-with-parent memory 10M &
+
+# Wait for the pidfile to appear.
+for i in {1..60}; do
+ if test -f $pidfile; then
+ break
+ fi
+ sleep 1
+done
+if ! test -f $pidfile; then
+ echo "$0: nbdfuse PID file $pidfile was not created"
+ exit 1
+fi
+
+dd if=/dev/urandom of=$data bs=1M count=10
+# Use a weird block size when writing. It's a bit pointless because
+# something in the Linux/FUSE stack turns these into exact 4096 byte
+# writes.
+dd if=$data of=$mp/nbd bs=65519 conv=nocreat,notrunc
+cmp $data $mp/nbd
diff --git a/fuse/test-qcow2.sh b/fuse/test-qcow2.sh
new file mode 100755
index 0000000..95f97b5
--- /dev/null
+++ b/fuse/test-qcow2.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+# The nbdfuse documentation describes how you can use nbdfuse +
+# qemu-nbd to open qcow2 files. This claim is tested here.
+
+. ../tests/functions.sh
+
+set -e
+set -x
+
+requires qemu-nbd --version
+requires qemu-img --version
+requires cmp --version
+requires dd --version
+
+if ! test -r /dev/urandom; then
+ echo "$0: test skipped: /dev/urandom not readable"
+ exit 77
+fi
+
+pidfile=test-qcow2.pid
+mp=test-qcow2.d
+data=test-qcow2.data
+qcow2=test-qcow2.qcow2
+cleanup_fn fusermount -u $mp
+cleanup_fn rm -rf $mp
+cleanup_fn rm -f $pidfile $data $qcow2
+
+dd if=/dev/urandom of=$data bs=1M count=1
+qemu-img convert -f raw $data -O qcow2 $qcow2
+
+mkdir -p $mp
+$VG nbdfuse -r -P $pidfile $mp \
+ --socket-activation qemu-nbd -f qcow2 $qcow2 &
+
+# Wait for the pidfile to appear.
+for i in {1..60}; do
+ if test -f $pidfile; then
+ break
+ fi
+ sleep 1
+done
+if ! test -f $pidfile; then
+ echo "$0: nbdfuse PID file $pidfile was not created"
+ exit 1
+fi
+
+cmp $data $mp/nbd
diff --git a/run.in b/run.in
index 83c92a7..599752d 100755
--- a/run.in
+++ b/run.in
@@ -51,6 +51,7 @@ s="$(cd @abs_srcdir@ && pwd)"
b="$(cd @abs_builddir@ && pwd)"
# Set the PATH to contain all libnbd binaries.
+prepend PATH "$b/fuse"
prepend PATH "$b/sh"
export PATH
diff --git a/sh/nbdsh.pod b/sh/nbdsh.pod
index 0037dc3..c1d93ba 100644
--- a/sh/nbdsh.pod
+++ b/sh/nbdsh.pod
@@ -97,6 +97,7 @@ Display the package name and version and exit.
L<libnbd(3)>,
L<libnbd-security(3)>,
+L<nbdfuse(1)>,
L<qemu-img(1)>.
=head1 AUTHORS
--
2.23.0
5 years, 1 month
access function in fuse_operations (concerns libguestfs)
by Daniel Haid
Hello,
libguestfs has a guestmount command that basically gives access via fuse
to filesystem images. It also takes options for fuse.
When -o default_permissions is not given, the mounting user should have
full access to everything (he will have access to the image anyway). Of
course, it should also be possible to give -o default_permissions to
have the kernel check the permissions, which could be useful for some
use cases.
Now the strange thing is that the guestmount command implements the
access function in fuse_operations. This results in the following bug:
https://bugzilla.redhat.com/show_bug.cgi?id=1761563
I have been asked to ask here for clarification on certain points:
1) If the above behaviour is wanted (no access checks without "-o
default_permissions"; the expected checks by the kernel if the option is
given), what is the right way to implement it? Can the access function
be just omitted?
2) If so, in which use cases is the access function usually implemented?
Do I understand correctly that this is only in case some fuse clients
wants to implement some *own* particular access restrictions (for
whatever reason) when "-o default_permissions" is *not* given?
3) What could be the case why the access function was implemented in the
first place? Maybe someone from libguestfs knows this, but it have found
https://www.redhat.com/archives/libguestfs/2014-June/msg00080.html
where some problem is described when there is no access function.
D.H.
5 years, 1 month
[PATCH NOT WORKING nbdkit v2 0/2] vddk: Restructure plugin to allow greater parallelism.
by Richard W.M. Jones
This is my second attempt at this. The first version (also not
working) was here:
https://www.redhat.com/archives/libguestfs/2019-October/msg00062.html
In part 1/2 I introduce a new .ready_to_serve plugin method which is
called after forking and just before accepting any client connection.
The idea would be that plugins could start background threads here.
However this doesn't work well in practice because plugins which do
start any background threads seem to always get a segfault at
shutdown, with a race between __nptl_deallocate_tsd and some code
being unloaded (I'm not sure exactly what).
Part 2/2 restructures the VDDK plugin. This time I fixed the message
passing so it doesn't deadlock as it did before.
I thought that this patch might have solved the other VDDK problem we
have around "Resolve Host" taking so long, but in fact it does not fix
that either. (VDDK is still broken, news as 11).
Anyway I don't think there's anything worth saving here. I'm mainly
posting it so we have a back up.
Rich.
5 years, 1 month
[PATCH] v2v: windows: install QEMU Guest Agent MSI
by Tomáš Golembiovský
Use firstboot script to install MSI with QEMU-GA from virtio-win ISO or
oVirt/RHV guest tools ISO.
Signed-off-by: Tomáš Golembiovský <tgolembi(a)redhat.com>
---
v2v/convert_windows.ml | 19 +++++++++++++++++++
v2v/windows_virtio.ml | 27 +++++++++++++++++++++++++++
v2v/windows_virtio.mli | 4 ++++
3 files changed, 50 insertions(+)
diff --git a/v2v/convert_windows.ml b/v2v/convert_windows.ml
index 7ea56592c..06122e42a 100644
--- a/v2v/convert_windows.ml
+++ b/v2v/convert_windows.ml
@@ -291,6 +291,13 @@ let convert (g : G.guestfs) inspect source output rcaps =
if Sys.file_exists tool_path then
configure_vmdp tool_path;
+ (* Install QEMU Guest Agent unconditionally and warn if missing *)
+ let qemu_ga_files = Windows_virtio.copy_qemu_ga g inspect in
+ if qemu_ga_files <> [] then (
+ configure_qemu_ga qemu_ga_files;
+ ) else
+ warning (f_"QEMU Guest Agent MSI not found on tools ISO/directory. You may want to install the guest agent manually after conversion.");
+
unconfigure_xenpv ();
unconfigure_prltools ();
unconfigure_vmwaretools ()
@@ -416,6 +423,18 @@ popd
Firstboot.add_firstboot_script g inspect.i_root
"finish vmdp setup" fb_recover_script
+ and configure_qemu_ga files =
+ List.iter (
+ fun msi_path ->
+ let fb_script = "\
+echo Installing qemu-ga from " ^ msi_path ^ "
+\"\\" ^ msi_path ^ "\" /qn /forcerestart /l+*vx \"%cd%\\qemu-ga.log\"
+" in
+ Firstboot.add_firstboot_script g inspect.i_root
+ ("install " ^ msi_path) fb_script;
+ ) files
+
+
and unconfigure_xenpv () =
match xenpv_uninst with
| None -> () (* nothing to be uninstalled *)
diff --git a/v2v/windows_virtio.ml b/v2v/windows_virtio.ml
index 56c7a6757..e040eabaf 100644
--- a/v2v/windows_virtio.ml
+++ b/v2v/windows_virtio.ml
@@ -302,6 +302,13 @@ and copy_drivers g inspect driverdir =
(fun () ->
error (f_"root directory ‘/’ is missing from the virtio-win directory or ISO.\n\nThis should not happen and may indicate that virtio-win or virt-v2v is broken in some way. Please report this as a bug with a full debug log."))
+and copy_qemu_ga g inspect =
+ copy_from_virtio_win g inspect "/" "/"
+ virtio_iso_path_matches_qemu_ga
+ (fun () ->
+ error (f_"root directory ‘/’ is missing from the virtio-win directory or ISO.\n\nThis should not happen and may indicate that virtio-win or virt-v2v is broken in some way. Please report this as a bug with a full debug log."))
+
+
(* Copy all files from virtio_win directory/ISO located in [srcdir]
* subdirectory and all its subdirectories to the [destdir]. The directory
* hierarchy is not preserved, meaning all files will be directly in [destdir].
@@ -433,6 +440,26 @@ and virtio_iso_path_matches_guest_os path inspect =
with Not_found -> false
+(* Given a path of a file relative to the root of the directory tree
+ * with virtio-win drivers, figure out if it's suitable for the
+ * specific Windows flavor of the current guest.
+ *)
+and virtio_iso_path_matches_qemu_ga path inspect =
+ let { i_arch = arch } = inspect in
+ (* Lowercased path, since the ISO may contain upper or lowercase path
+ * elements.
+ *)
+ let lc_name = String.lowercase_ascii (Filename.basename path) in
+ lc_name = "rhev-qga.msi" ||
+ match arch, lc_name with
+ | ("i386", "qemu-ga-x86.msi")
+ | ("i386", "qemu-ga-i386.msi")
+ | ("i386", "RHEV-QGA.msi")
+ | ("x86_64", "qemu-ga-x64.msi")
+ | ("x86_64", "qemu-ga-x86_64.msi")
+ | ("x86_64", "RHEV-QGA64.msi") -> true
+ | _ -> false
+
(* The following function is only exported for unit tests. *)
module UNIT_TESTS = struct
let virtio_iso_path_matches_guest_os = virtio_iso_path_matches_guest_os
diff --git a/v2v/windows_virtio.mli b/v2v/windows_virtio.mli
index ae3b7e865..731dbd6f0 100644
--- a/v2v/windows_virtio.mli
+++ b/v2v/windows_virtio.mli
@@ -44,6 +44,10 @@ val install_linux_tools : Guestfs.guestfs -> Types.inspect -> unit
(** installs QEMU Guest Agent on Linux guest OS from the driver directory or
driver ISO. It is not fatal if we fail to install the agent. *)
+val copy_qemu_ga : Guestfs.guestfs -> Types.inspect -> string list
+(** copy MSIs (idealy just one) with QEMU Guest Agent to Windows guest. The
+ MSIs are not installed by this function. *)
+
(**/**)
(* The following function is only exported for unit tests. *)
--
2.23.0
5 years, 1 month
[PATCH NOT WORKING nbdkit] vddk: Restructure plugin to allow greater parallelism.
by Richard W.M. Jones
We had a query yesterday about the VDDK plugin and making it actually
obey the weird "Multithreading Considerations" rules in the VDDK
documentation
(https://vdc-download.vmware.com/vmwb-repository/dcr-public/8f96698a-0e7b-...)
This patch is my attempt to implement this.
The idea is that the plugin starts a background thread and routes all
calls to certain VDDK functions through this thread, ensuring that the
"Multithreading Considerations" are obeyed.
It doesn't work. Because we start the background thread when the
plugin is loaded, when nbdkit forks the thread disappears. It's
possible that we need in nbdkit a supported way for plugins to create
background threads.
In any case the actual benefit of this patch is dubious. Closer
reading of the documentation indicates that VDDK does not allow full
parallel requests on single handles. The way we normally use VDDK
(eg. from virt-v2v) is that we only have a single connection open (and
therefore a single handle). So whether we'd gain any benefit in real
world cases seems unlikely.
We cannot enable multi-conn on writable VDDK handles because the
documentation gives us no indication that VDDK provides the required
guarantees, although we might consider enabling multi-conn for
readonly handles.
Rich.
5 years, 1 month
LIBNBD SECURITY: Remote code execution vulnerability
by Richard W.M. Jones
We have discovered a remote code execution vulnerability in libnbd.
Lifecycle
---------
Reported: 2019-10-05 Fixed: 2019-10-05 Published: 2019-10-09
There is no CVE number assigned for this issue yet, but the bug is
being categorized and processed by Red Hat's security team which may
result in a CVE being published later.
Credit
------
Reported and patched by Richard W.M. Jones <rjones(a)redhat.com>.
Reviewed by Eric Blake <eblake(a)redhat.com>.
Description
-----------
libnbd is a Network Block Device (NBD) client library.
Because of improper bounds checking, when receiving a structured reply
some offset/lengths sent by the server could cause libnbd to execute
arbitrary code under control of a malicious server.
Structured reply is a feature of the newstyle NBD protocol allowing
the server to send a reply in chunks. A bounds check which was
supposed to test for chunk offsets smaller than the beginning of the
request did not work because of signed/unsigned confusion. If one of
these chunks contains a negative offset then data under control of the
server is written to memory before the read buffer supplied by the
client. If the read buffer is located on the stack then this allows
the stack return address from nbd_pread() to be trivially modified,
allowing arbitrary code execution under the control of the server. If
the buffer is located on the heap then other memory objects before the
buffer can be overwritten, which again would usually lead to arbitrary
code execution.
Test if libnbd is vulnerable
----------------------------
(There is no simple test for this vulnerability)
Workarounds
-----------
It is highly recommended to apply the fix or upgrade to a fixed
version. If you cannot do this, then you could use:
nbd_set_tls (h, LIBNBD_TLS_REQUIRE)
to only connect to trusted servers over TLS.
Fixes
-----
This affects all versions of libnbd. A fix is available for 1.0, and
the current development branch.
* development branch (1.1)
https://github.com/libguestfs/libnbd/commit/f75f602a6361c0c5f42debfeea698...
or use libnbd >= 1.1.4 from
http://download.libguestfs.org/libnbd/1.1-development/
* stable branch 1.0
https://github.com/libguestfs/libnbd/commit/2c1987fc23d6d0f537edc6d4701e9...
or use libnbd >= 1.0.3 from
http://download.libguestfs.org/libnbd/1.0-stable/
--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-top is 'top' for virtual machines. Tiny program with many
powerful monitoring features, net stats, disk stats, logging, etc.
http://people.redhat.com/~rjones/virt-top
5 years, 1 month