Previously, attempts to use nbdkit without -r in order to visit a
read-only image failed to open the image (in fact, a single read-only
file in an otherwise usable directory makes "nbdkit file dir=. --run
'nbdinfo --list'" fail to print anything in libnbd 1.6). But a saner
approach is to try a fallback to a readonly fd if a read-write fd
fails.
The windows code compiles, but I wasn't able to test it as thoroughly
as the Unix code.
---
plugins/file/file.c | 30 ++++++++++++++++--
plugins/file/winfile.c | 21 +++++++++++--
tests/Makefile.am | 12 +++++--
tests/test-file-readonly.sh | 63 +++++++++++++++++++++++++++++++++++++
4 files changed, 118 insertions(+), 8 deletions(-)
create mode 100755 tests/test-file-readonly.sh
diff --git a/plugins/file/file.c b/plugins/file/file.c
index 1651079a..a9ecceb6 100644
--- a/plugins/file/file.c
+++ b/plugins/file/file.c
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 Red Hat Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -304,6 +304,7 @@ struct handle {
int fd;
bool is_block_device;
int sector_size;
+ bool can_write;
bool can_punch_hole;
bool can_zero_range;
bool can_fallocate;
@@ -343,12 +344,25 @@ file_open (int readonly)
}
flags = O_CLOEXEC|O_NOCTTY;
- if (readonly)
+ if (readonly) {
flags |= O_RDONLY;
- else
+ h->can_write = false;
+ }
+ else {
flags |= O_RDWR;
+ h->can_write = true;
+ }
h->fd = openat (dfd, file, flags);
+ if (h->fd == -1 && !readonly) {
+ int saved_errno = errno;
+ flags = (flags & ~O_ACCMODE) | O_RDONLY;
+ h->fd = openat (dfd, file, flags);
+ if (h->fd == -1)
+ errno = saved_errno;
+ else
+ h->can_write = false;
+ }
if (h->fd == -1) {
nbdkit_error ("open: %s: %m", file);
if (dfd != -1)
@@ -464,6 +478,15 @@ file_get_size (void *handle)
}
}
+/* Check if file is read-only. */
+static int
+file_can_write (void *handle)
+{
+ struct handle *h = handle;
+
+ return h->can_write;
+}
+
/* Allow multiple parallel connections from a single client. */
static int
file_can_multi_conn (void *handle)
@@ -880,6 +903,7 @@ static struct nbdkit_plugin plugin = {
.open = file_open,
.close = file_close,
.get_size = file_get_size,
+ .can_write = file_can_write,
.can_multi_conn = file_can_multi_conn,
.can_trim = file_can_trim,
.can_fua = file_can_fua,
diff --git a/plugins/file/winfile.c b/plugins/file/winfile.c
index 6d1a6643..ec186255 100644
--- a/plugins/file/winfile.c
+++ b/plugins/file/winfile.c
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 Red Hat Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -106,6 +106,7 @@ winfile_dump_plugin (void)
struct handle {
HANDLE fh;
int64_t size;
+ bool is_readonly;
bool is_volume;
bool is_sparse;
};
@@ -126,6 +127,12 @@ winfile_open (int readonly)
fh = CreateFile (filename, flags, FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (fh == INVALID_HANDLE_VALUE && !readonly) {
+ flags &= ~GENERIC_WRITE;
+ readonly = true;
+ fh = CreateFile (filename, flags, FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ }
if (fh == INVALID_HANDLE_VALUE) {
nbdkit_error ("%s: error %lu", filename, GetLastError ());
return NULL;
@@ -179,15 +186,24 @@ winfile_open (int readonly)
}
h->fh = fh;
h->size = size.QuadPart;
+ h->is_readonly = readonly;
h->is_volume = is_volume;
h->is_sparse = is_sparse;
- nbdkit_debug ("%s: size=%" PRIi64 " is_volume=%s is_sparse=%s",
+ nbdkit_debug ("%s: size=%" PRIi64 " readonly=%s is_volume=%s
is_sparse=%s",
filename, h->size,
+ readonly ? "true" : "false",
is_volume ? "true" : "false",
is_sparse ? "true" : "false");
return h;
}
+static int
+wintfile_can_write (void *handle)
+{
+ struct handle *h = handle;
+ return !h->is_readonly;
+}
+
static int
winfile_can_trim (void *handle)
{
@@ -425,6 +441,7 @@ static struct nbdkit_plugin plugin = {
.dump_plugin = winfile_dump_plugin,
.open = winfile_open,
+ .can_write = winfile_can_write,
.can_trim = winfile_can_trim,
.can_zero = winfile_can_zero,
.can_extents = winfile_can_extents,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 70898f20..e8c9c535 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,5 +1,5 @@
# nbdkit
-# Copyright (C) 2013-2020 Red Hat Inc.
+# Copyright (C) 2013-2021 Red Hat Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
@@ -673,8 +673,14 @@ EXTRA_DIST += \
$(NULL)
# file plugin test.
-TESTS += test-file.sh
-EXTRA_DIST += test-file.sh
+TESTS += \
+ test-file.sh \
+ test-file-readonly.sh \
+ $(NULL)
+EXTRA_DIST += \
+ test-file.sh \
+ test-file-readonly.sh \
+ $(NULL)
LIBGUESTFS_TESTS += test-file-block
test_file_block_SOURCES = test-file-block.c test.h
diff --git a/tests/test-file-readonly.sh b/tests/test-file-readonly.sh
new file mode 100755
index 00000000..cde2bd65
--- /dev/null
+++ b/tests/test-file-readonly.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2013-2021 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+# Test the file plugin on readonly files.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin file
+requires_nbdsh_uri
+requires truncate --version
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+files="file-readonly.pid file-readonly.img $sock"
+rm -f $files
+cleanup_fn rm -f $files
+
+truncate -s 16384 file-readonly.img
+chmod a-w file-readonly.img
+start_nbdkit -P file-readonly.pid -U $sock file file-readonly.img
+
+# Try to test all the major functions supported by both
+# the Unix and Windows versions of the file plugin.
+nbdsh -u "nbd+unix://?socket=$sock" -c '
+assert h.is_read_only()
+assert not h.can_zero()
+
+buf = h.pread(8192, 0)
+assert buf == b"\0" * 8192
+
+if h.can_flush():
+ h.flush()
+'
--
2.30.1