[Libvir] Don't fail to read a file because it's non-seekable (e.g., a pipe).

Jim Meyering jim at meyering.net
Tue Apr 8 15:00:03 UTC 2008


FYI, I expect to add fread_file_lim or something like it to gnulib,
and it already has some unit tests (passed).
I removed the "*  tab-width: 4" line because it seriously
mangled the code that I initially added.  Besides saying "tab-width 4"
is contradictory with the "indent-tabs-mode: nil" setting.

This fix addresses a problem exposed in an ovirt script whereby
trying to use bash process substitution, e.g., in
virsh define <(command to generate xml)
would fail.

Oops.  Just noticed that the indentation in the added function
(gnulib style) is not consistent with the rest of the file.
I'll adjust that before committing, of course.

	Don't fail to read a file because it's non-seekable (e.g., a pipe).
	* src/util.c (fread_file_lim): New function.
	(__virFileReadAll): Use fread_file_lim, rather than requiring
	that stat.st_size provide a usable file size.
	* tests/read-non-seekable: New test, for the above.
	* tests/Makefile.am (test_scripts): Add read-non-seekable.
	* tests/test-lib.sh (mkfifo_or_skip_): New helper function.

Signed-off-by: Jim Meyering <meyering at redhat.com>
---
 src/util.c              |   87 +++++++++++++++++++++++++++++++++++++---------
 tests/Makefile.am       |    1 +
 tests/read-non-seekable |   47 +++++++++++++++++++++++++
 tests/test-lib.sh       |   12 ++++++
 4 files changed, 130 insertions(+), 17 deletions(-)
 create mode 100755 tests/read-non-seekable

diff --git a/src/util.c b/src/util.c
index 0667780..e951eb5 100644
--- a/src/util.c
+++ b/src/util.c
@@ -49,6 +49,10 @@

 #include "util-lib.c"

+#ifndef MIN
+# define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
 #define MAX_ERROR_LEN   1024

 #define TOLOWER(Ch) (isupper (Ch) ? tolower (Ch) : (Ch))
@@ -283,6 +287,61 @@ virExecNonBlock(virConnectPtr conn,

 #endif /* __MINGW32__ */

+/* Like gnulib's fread_file, but read no more than the specified maximum
+   number of bytes.  If the length of the input is <= max_len, and
+   upon error while reading that data, it works just like fread_file.  */
+static char *
+fread_file_lim (FILE *stream, size_t max_len, size_t *length)
+{
+  char *buf = NULL;
+  size_t alloc = 0;
+  size_t size = 0;
+  int save_errno;
+
+  for (;;)
+    {
+      size_t count;
+      size_t requested;
+
+      if (size + BUFSIZ + 1 > alloc)
+        {
+          char *new_buf;
+
+          alloc += alloc / 2;
+          if (alloc < size + BUFSIZ + 1)
+            alloc = size + BUFSIZ + 1;
+
+          new_buf = realloc (buf, alloc);
+          if (!new_buf)
+            {
+              save_errno = errno;
+              break;
+            }
+
+          buf = new_buf;
+        }
+
+      /* Ensure that (size + requested <= max_len); */
+      requested = MIN (size < max_len ? max_len - size : 0,
+                       alloc - size - 1);
+      count = fread (buf + size, 1, requested, stream);
+      size += count;
+
+      if (count != requested || requested == 0)
+        {
+          save_errno = errno;
+          if (ferror (stream))
+            break;
+          buf[size] = '\0';
+          *length = size;
+          return buf;
+        }
+    }
+
+  free (buf);
+  errno = save_errno;
+  return NULL;
+}

 int __virFileReadAll(const char *path,
                      int maxlen,
@@ -291,6 +350,8 @@ int __virFileReadAll(const char *path,
     FILE *fh;
     struct stat st;
     int ret = -1;
+    size_t len;
+    char *s;

     if (!(fh = fopen(path, "r"))) {
         virLog("Failed to open file '%s': %s",
@@ -309,28 +370,21 @@ int __virFileReadAll(const char *path,
         goto error;
     }

-    if (st.st_size > maxlen) {
-        virLog("File '%s' is too large %d, max %d", path, (int)st.st_size, maxlen);
-        goto error;
-    }
-
-    *buf = malloc(st.st_size + 1);
-    if (*buf == NULL) {
-        virLog("Failed to allocate data");
+    s = fread_file_lim(fh, maxlen+1, &len);
+    if (s == NULL) {
+        virLog("Failed to read '%s': %s", path, strerror (errno));
         goto error;
     }

-    if ((ret = fread(*buf, st.st_size, 1, fh)) != 1) {
-        free(*buf);
-        *buf = NULL;
-        virLog("Failed to read config file '%s': %s",
-               path, strerror(errno));
+    if (len > maxlen || (int)len != len) {
+        free(s);
+        virLog("File '%s' is too large %d, max %d",
+               path, (int)st.st_size, maxlen);
         goto error;
     }

-    (*buf)[st.st_size] = '\0';
-
-    ret = st.st_size;
+    *buf = s;
+    ret = len;

  error:
     if (fh)
@@ -739,6 +793,5 @@ virParseMacAddr(const char* str, unsigned char *addr)
  *  indent-tabs-mode: nil
  *  c-indent-level: 4
  *  c-basic-offset: 4
- *  tab-width: 4
  * End:
  */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 901e88a..ca12b84 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -46,6 +46,7 @@ noinst_PROGRAMS = xmlrpctest xml2sexprtest sexpr2xmltest virshtest conftest \
 test_scripts = \
 	daemon-conf \
 	int-overflow \
+	read-non-seekable \
 	vcpupin

 EXTRA_DIST += $(test_scripts)
diff --git a/tests/read-non-seekable b/tests/read-non-seekable
new file mode 100755
index 0000000..9bb4a21
--- /dev/null
+++ b/tests/read-non-seekable
@@ -0,0 +1,47 @@
+#!/bin/sh
+# ensure that certain file-reading commands can handle non-seekable files
+
+# Copyright (C) 2008 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program 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 General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if test "$VERBOSE" = yes; then
+  set -x
+  virsh --version
+fi
+
+. $srcdir/test-lib.sh
+
+fail=0
+
+cat <<\EOF > dom
+<domain type='test' id='2'>
+  <name>t2</name>
+  <uuid>004b96e1-2d78-c30f-5aa5-000000000000</uuid>
+  <memory>8388608</memory>
+  <vcpu>2</vcpu>
+  <on_reboot>restart</on_reboot>
+  <on_poweroff>destroy</on_poweroff>
+  <on_crash>restart</on_crash>
+</domain>
+EOF
+
+virsh -c test:///default define dom > /dev/null || fail=1
+
+mkfifo_or_skip_ fifo
+cat dom > fifo &
+
+virsh -c test:///default define fifo > /dev/null || fail=1
+
+(exit $fail); exit $fail
diff --git a/tests/test-lib.sh b/tests/test-lib.sh
index cdbea5d..a007109 100644
--- a/tests/test-lib.sh
+++ b/tests/test-lib.sh
@@ -120,6 +120,18 @@ skip_if_root_() { uid_is_privileged_ && skip_test_ "must be run as non-root"; }
 error_() { echo "$0: $@" 1>&2; (exit 1); exit 1; }
 framework_failure() { error_ 'failure in testing framework'; }

+mkfifo_or_skip_()
+{
+  test $# = 1 || framework_failure
+  if ! mkfifo "$1"; then
+    # Make an exception of this case -- usually we interpret framework-creation
+    # failure as a test failure.  However, in this case, when running on a SunOS
+    # system using a disk NFS mounted from OpenBSD, the above fails like this:
+    # mkfifo: cannot make fifo `fifo-10558': Not owner
+    skip_test_ 'NOTICE: unable to create test prerequisites'
+  fi
+}
+
 test_dir_=$(pwd)

 this_test_() { echo "./$0" | sed 's,.*/,,'; }
--
1.5.5.rc2.7.g144a




More information about the libvir-list mailing list