[parted-devel] [PATCH]: Don't fail all tests when "." lacks O_DIRECT support.

Jim Meyering jim at meyering.net
Wed May 23 20:03:12 UTC 2007


I often build tools on a tmpfs file system (it's faster), and
found that parted tests always failed there.  That's because it tries
to open the "device" (a file) with O_DIRECT, and at least the linux tmpfs
driver always fails with EINVAL in that case.

So here's a patch that makes it work.
Since the test may require writing in a directory like /tmp,
to which others typically have write access, it is particularly
careful about security (see the mkdtemp script below), in case
"make check" is run by e.g., root.

2007-05-23  Jim Meyering  <jim at meyering.net>

	Don't fail all tests when "." lacks O_DIRECT support.
	Before, running "make check" on a file system that doesn't support
	O_DIRECT (e.g. tmpfs), would always fail.  Now, it works, as long as
	the test machinery can find a writable directory in which open with
        O_DIRECT *does* work.
	* m4/o-direct.m4: New file.  Find a directory/FS with O_DIRECT support.
	* configure.ac: Use the new macro.
	* libparted/tests/t1000-label.sh: New file.  Wrap the binary, so
	it can take advantage of the code that finds O_DIRECT supporting FS.
	* tests/mkdtemp: New file.  Required, since when running tests as
	root, we may have to create a temporary directory in a directory
	like /tmp that's writable by others.
	* tests/Makefile.am (EXTRA_DIST): Add mkdtemp.
	* tests/test-lib.sh: When creating test subdir, and setting up "trap",
	use the directory specified in $PARTED_USABLE_TEST_DIR.
	Don't set PATH here.  Now, that's done via the generated, and always-
	sourced, init.sh.  As a result, invoke parted via its full file name.

diff --git a/configure.ac b/configure.ac
index 18a30d9..b21be56 100644
--- a/configure.ac
+++ b/configure.ac
@@ -171,6 +171,7 @@ AC_PROG_GCC_TRADITIONAL
 AM_PROG_CC_C_O

 gl_EARLY
+parted_FIND_USABLE_TEST_DIR

 dnl This test must come as early as possible after the compiler configuration
 dnl tests, because the choice of the file model can (in principle) affect
@@ -460,7 +461,6 @@ AC_C_INLINE
 AC_C_CONST
 AC_C_RESTRICT

-
 dnl Checks for library functions.
 AC_CHECK_FUNCS(sigaction)
 AC_CHECK_FUNCS(getuid)
diff --git a/doc/C/po/partprobe.8.pot b/doc/C/po/partprobe.8.pot
diff --git a/libparted/tests/Makefile.am b/libparted/tests/Makefile.am
index 8c1e07b..12ad29f 100644
--- a/libparted/tests/Makefile.am
+++ b/libparted/tests/Makefile.am
@@ -1,12 +1,24 @@
 # This file is part of GNU Parted
-# Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
+# Copyright (C) 1999, 2000, 2001, 2007 Free Software Foundation, Inc.
 #
 # This file may be modified and/or distributed without restriction.

-TESTS = label
+TESTS = t1000-label.sh
+EXTRA_DIST = $(TESTS)
 bin_PROGRAMS     = label
 label_CFLAGS    = $(CHECK_CFLAGS) -I$(top_srcdir)/include
 label_LDADD     = $(CHECK_LIBS) $(top_builddir)/libparted/libparted.la
 label_SOURCES   = common.h common.c label.c

 MAINTAINERCLEANFILES = Makefile.in
+
+CLEANFILES = init.sh
+all: init.sh
+init.sh: Makefile.in
+	rm -f $@-t $@
+	echo 'PARTED_USABLE_TEST_DIR=$(PARTED_USABLE_TEST_DIR)' > $@-t
+	echo 'abs_top_srcdir=$(abs_top_srcdir)' >> $@-t
+	echo '. $(abs_top_srcdir)/tests/test-lib.sh' >> $@-t
+	echo 'PATH=$(abs_builddir)$(PATH_SEPARATOR)$$PATH; export PATH' >> $@-t
+	chmod a-w $@-t
+	mv $@-t $@
diff --git a/libparted/tests/t1000-label.sh b/libparted/tests/t1000-label.sh
new file mode 100755
index 0000000..34010b1
--- /dev/null
+++ b/libparted/tests/t1000-label.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# Copyright (C) 2007 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+test_description='run the label unit tests in a directory supporting O_DIRECT'
+# This wrapper around the ./label binary is used to find a directory
+# in which one can open a file with the O_DIRECT flag.
+
+. ./init.sh
+
+test_expect_success \
+    'run the actual tests' 'label'
+
+test_done
diff --git a/m4/o-direct.m4 b/m4/o-direct.m4
new file mode 100644
index 0000000..ec84ef4
--- /dev/null
+++ b/m4/o-direct.m4
@@ -0,0 +1,235 @@
+#serial 1
+# Find a directory in which a disk-simulating file is usable by parted.
+# The problem is that on systems supporting O_DIRECT, open with O_DIRECT
+# fails for some file system types (e.g., tmpfs on linux-2.6.21).
+
+# Copyright (C) 2007 Free Software Foundation, Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# From Jim Meyering.
+
+# Set PARTED_USABLE_TEST_DIR to the name of the first usable directory
+# from the list below.  If none is usable, set it to the empty string.
+# Consider $TMPDIR only if it specifies an absolute name, and that
+# name contains no shell meta-character.  Likewise for $HOME.
+# The candidate directories:
+#   . $HOME $TMPDIR /tmp /var/tmp /dev/shm
+AC_DEFUN([parted_FIND_USABLE_TEST_DIR],
+[
+  AC_CACHE_CHECK([for a usable (O_DIRECT-supporting) temporary dir],
+    [parted_cv_func_open_O_DIRECT_temp_dir],
+    [
+      # First of all, if there is no O_DIRECT definition, use ".",
+      # and skip the run-test.
+      AC_EGREP_CPP([frobnozzle], [
+#include <fcntl.h>
+#ifdef O_DIRECT
+frobnozzle
+#endif
+		  ], pe_have_O_DIRECT=yes, pe_have_O_DIRECT=no)
+      if test $pe_have_O_DIRECT = no; then
+	  # With no O_DIRECT definition, "." is fine.
+	  pe_cand_dirs=.
+      else
+	  pe_cand_dirs=.
+	  for pe_dir in "$HOME" "$TMPDIR"; do
+	      case $pe_dir in
+	      /tmp) ;;
+	      /var/tmp) ;;
+	      /dev/shm) ;;
+	      /*) case $pe_dir in
+		  # Accept $HOME or $TMP only if the value is nice and boring.
+		  *[^/a-zA-Z0-9_.-]*) ;;
+		  *) pe_cand_dirs="$pe_cand_dirs $pe_dir";;
+		  esac
+	      esac
+	  done
+
+	  # This is the list of candidate directories.
+	  pe_cand_dirs="$pe_cand_dirs /tmp /var/tmp /dev/shm"
+
+	  PARTED_CANDIDATE_DIRS=$pe_cand_dirs
+	  export PARTED_CANDIDATE_DIRS
+
+	  AC_RUN_IFELSE(
+	    [AC_LANG_SOURCE(
+	      [[
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define LOGICAL_BLOCK_SIZE 4096
+static char g_buf[2 * LOGICAL_BLOCK_SIZE];
+
+static inline void *
+ptr_align (void const *ptr, size_t alignment)
+{
+  char const *p0 = ptr;
+  char const *p1 = p0 + alignment - 1;
+  return (void *) (p1 - (size_t) p1 % alignment);
+}
+
+static int
+create_input_file (char const *file, char const *buf, size_t n_bytes)
+{
+  int fd = open (file, O_CREAT | O_WRONLY, 0600);
+  if (fd < 0)
+    return 1;
+  if (write (fd, buf, n_bytes) != n_bytes)
+    {
+      close (fd);
+      return 1;
+    }
+  return !! close (fd);
+}
+
+static int
+try_o_direct (char const *file)
+{
+  char const *p = ptr_align (g_buf, LOGICAL_BLOCK_SIZE);
+  int fd;
+
+  if (!(p + LOGICAL_BLOCK_SIZE < g_buf + sizeof g_buf))
+    return 4;
+
+  fd = open (file, O_WRONLY | O_DIRECT);
+  if (fd < 0)
+    return 1;
+
+  if (write (fd, p, LOGICAL_BLOCK_SIZE) != LOGICAL_BLOCK_SIZE)
+    return 1;
+
+  return !! close (fd);
+}
+
+#undef stpcpy
+#define stpcpy(a, b) my_stpcpy (a, b)
+static char *
+my_stpcpy (char *dest, const char *src)
+{
+  char *d = dest;
+  const char *s = src;
+  do *d++ = *s; while (*s++ != '\0');
+  return d - 1;
+}
+
+/* The base name of the file we'll create in the mkdtemp-returned
+   temporary directory.  */
+#define BASENAME "x"
+
+/* Return 0 upon failure, else the 1-based index of the first
+   useful directory name from PARTED_CANDIDATE_DIRS.  */
+int
+main ()
+{
+  char const *env_dirs;
+  char *dirs;
+  char *dir;
+  unsigned int n;
+  int found = 0;
+  size_t dirs_len;
+
+  if ((env_dirs = getenv ("PARTED_CANDIDATE_DIRS")) == NULL)
+    return 0;
+
+  dirs_len = strlen (env_dirs);
+  if ((dirs = strndup (env_dirs, dirs_len)) == NULL)
+    return 0;
+  dir = dirs;
+
+  for (n = 1; ; n++)
+    {
+      size_t dirname_len;
+      char *space;
+
+      /* Skip any leading spaces.  */
+      while (*dir == ' ')
+	++dir;
+
+      space = strchr (dir, ' ');
+      if (space)
+	{
+	  *space = '\0';
+	  dirname_len = space - dir;
+	}
+      else
+	{
+	  dirname_len = strlen (dir);
+	}
+
+      if (dirname_len != 0)
+	{
+	  /* Create an mkdtemp template starting with dir.  */
+	  char *tmp;
+	  char *endp;
+	  char const *base = "partedOD.XXXXXX";
+	  /* Allocate enough space not just for the dir name, but
+	     also for the name of the file to create within it.  */
+	  char *template = malloc (dirname_len + 1 + strlen (base)
+				   + 1 + strlen (BASENAME) + 1);
+	  if (template != NULL
+	      && (endp = stpcpy (stpcpy (stpcpy (template, dir), "/"), base))
+	      && (tmp = mkdtemp (template)) != NULL)
+	    {
+	      /* Append "/BASENAME" to create the file name.  */
+	      stpcpy (stpcpy (endp, "/"), BASENAME);
+
+	      if (create_input_file (tmp, g_buf, sizeof g_buf) == 0
+		  && try_o_direct (tmp) == 0)
+		found = 1;
+
+	      unlink (tmp); /* ignore failure */
+	      *endp = '\0';
+	      rmdir (tmp); /* ignore failure */
+	    }
+	  if (template)
+	    free (template);
+	}
+
+      if (found)
+	break;
+
+      dir += dirname_len + 1;
+      if (dirs + dirs_len < dir)
+	{
+	  n = 0;
+	  break;
+	}
+    }
+  free (dirs);
+
+  return n;
+}
+	      ]])],
+	    # If the above program exits with status 0, then
+	    # there it found no useful directory.  Use ".".
+	    [parted_cv_func_open_O_DIRECT_temp_dir=.],
+
+	    # It found one.  The exit status is an index into the list.
+	    # We also run this code when the program fails to compile or
+	    # to link, as will happen on systems without a mkdtemp function.
+	    [pe_err=$?; set _ $pe_cand_dirs; shift
+	      eval parted_cv_func_open_O_DIRECT_temp_dir='$'$pe_err],
+
+	    # When cross-compiling, use ".".
+	    [parted_cv_func_open_O_DIRECT_temp_dir=.]
+	    )
+      fi
+    ])
+  PARTED_USABLE_TEST_DIR=$parted_cv_func_open_O_DIRECT_temp_dir
+  AC_SUBST([PARTED_USABLE_TEST_DIR])
+
+  # If the result is ".", don't cache it.  The next user of
+  # the cache may well be running from a different file system.
+  dnl Here, I'm using "$as_unset", which is a non-published (i.e., internal)
+  dnl part of autoconf, but we don't expect its name to change any time soon.
+  dnl and by then, it'll probably be ok to use "unset" all by itself.
+  if test "$parted_cv_func_open_O_DIRECT_temp_dir" = .; then
+    $as_unset parted_cv_func_open_O_DIRECT_temp_dir
+  fi
+])
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 93247a0..59d38e5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -5,16 +5,17 @@ TESTS = \
   t1500-small-ext2.sh \
   t2000-mkfs.sh

-TESTS_ENVIRONMENT = \
-  PATH="`pwd`/../parted$(PATH_SEPARATOR)$$PATH"
-
 EXTRA_DIST = \
-  $(TESTS) test-lib.sh
+  $(TESTS) test-lib.sh mkdtemp

 CLEANFILES = init.sh
 all: init.sh
 init.sh: Makefile.in
 	rm -f $@-t $@
-	( echo 'srcdir=$(srcdir)'; echo '. $$srcdir/test-lib.sh' ) > $@-t
+	echo 'PARTED_USABLE_TEST_DIR=$(PARTED_USABLE_TEST_DIR)' > $@-t
+	echo 'abs_top_srcdir=$(abs_top_srcdir)' >> $@-t
+	echo '. $(abs_top_srcdir)/tests/test-lib.sh' >> $@-t
+	echo 'PATH=$(abs_top_builddir)/parted$(PATH_SEPARATOR)$$PATH' >> $@-t
+	echo 'export PATH' >> $@-t
 	chmod a-w $@-t
 	mv $@-t $@
diff --git a/tests/mkdtemp b/tests/mkdtemp
new file mode 100755
index 0000000..48fe054
--- /dev/null
+++ b/tests/mkdtemp
@@ -0,0 +1,107 @@
+#!/bin/sh
+# Create a temporary directory, sort of like mktemp -d does.
+# Usage: mkdtemp /tmp phoey.XXXXXXXXXX
+
+# First, try to use the mktemp program.
+# Failing that, we'll roll our own mktemp-like function:
+#  - try to get random bytes from /dev/urandom
+#  - failing that, generate output from a combination of quickly-varying
+#      sources and gzip.  Ignore non-varying gzip header, and extract
+#      "random" bits from there.
+#  - given those bits, map to file-name bytes using tr, and try to create
+#      the desired directory.
+#  - make only $MAX_TRIES attempts
+
+ME=$(basename "$0")
+die() { echo >&2 "$ME: $@"; exit 1; }
+
+MAX_TRIES=4
+
+rand_bytes()
+{
+  n=$1
+
+  chars=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+
+  dev_rand=/dev/urandom
+  if test -r "$dev_rand"; then
+    # Note: 256-length($chars) == 194; 3 copies of $chars is 186 + 8 = 194.
+    head -c$n "$dev_rand" | tr -c $chars 01234567$chars$chars$chars
+    return
+  fi
+
+  cmds='date; date +%N; free; who -a; w; ps auxww; ps ef; netstat -n'
+  data=$( (eval "$cmds") 2>&1 | gzip )
+
+  n_plus_50=$(expr $n + 50)
+
+  # Ensure that $data has length at least 50+$n
+  while :; do
+    len=$(echo "$data"|wc -c)
+    test $n_plus_50 -le $len && break;
+    data=$( (echo "$data"; eval "$cmds") 2>&1 | gzip )
+  done
+
+  echo "$data" \
+    | dd bs=1 skip=50 count=$n 2>/dev/null \
+    | tr -c $chars 01234567$chars$chars$chars
+}
+
+mkdtemp()
+{
+  case $# in
+  2);;
+  *) die "Usage: $ME DIR TEMPLATE";;
+  esac
+
+  destdir=$1
+  template=$2
+
+  case $template in
+  *XXXX) ;;
+  *) die "invalid template: $template (must have a suffix of at least 4 X's)";;
+  esac
+
+  fail=0
+
+  # First, try to use mktemp.
+  d=$(env -u TMPDIR mktemp -d -t -p "$destdir" "$template" 2>/dev/null) \
+    || fail=1
+
+  # The resulting name must be in the specified directory.
+  case $d in "$destdir"*);; *) fail=1;; esac
+
+  # It must have created the directory.
+  test -d "$d" || fail=1
+
+  # It must have 0700 permissions.
+  perms=$(ls -dgo "$d" 2>/dev/null) || fail=1
+  case $perms in drwx------*) ;; *) fail=1;; esac
+
+  test $fail = 0 && {
+    echo "$d"
+    return
+  }
+
+  # If we reach this point, we'll have to create a directory manually.
+
+  # Get a copy of the template without its suffix of X's.
+  base_template=$(echo "$template"|sed 's/XX*$//')
+
+  # Calculate how many X's we've just removed.
+  nx=$(expr length "$template" - length "$base_template")
+
+  err=
+  i=1
+  while :; do
+    X=$(rand_bytes $nx)
+    candidate_dir="$destdir/$base_template$X"
+    err=$(mkdir -m 0700 "$candidate_dir" 2>&1) \
+      && { echo "$candidate_dir"; return; }
+    test $MAX_TRIES -le $i && break;
+    i=$(expr $i + 1)
+  done
+  die "$err"
+}
+
+mkdtemp "$@"
diff --git a/tests/test-lib.sh b/tests/test-lib.sh
index 8370963..59f85a4 100644
--- a/tests/test-lib.sh
+++ b/tests/test-lib.sh
@@ -201,11 +201,12 @@ if test "$privileges_required_" != ''; then
     fi
 fi

+# Test the binaries we have just built.
 pwd_=`pwd`
+parted_="$pwd_/../parted/parted"

-# Test the binaries we have just built.
-PATH=$pwd_/../parted:$PATH
-export PATH
+test_dir_=$PARTED_USABLE_TEST_DIR
+test $test_dir_ = . && test_dir_=$pwd_

 fail=
 # Some tests require an actual hardware device, e.g., a real disk with a
@@ -222,8 +223,8 @@ if test $skip_ = 0 && test "$erasable_device_required_" != ''; then
   if test "$DEVICE_TO_ERASE" != '' && test "$DEVICE_TO_ERASE_SIZE" != ''; then
     dev_=$DEVICE_TO_ERASE
     sz=$DEVICE_TO_ERASE_SIZE
-    parted_output=$(parted -s $dev_ print) || fail="no such device: $dev_"
-    parted -s $dev_ print|grep "^Disk $dev_: $sz$" \
+    parted_output=$($parted -s $dev_ print) || fail="no such device: $dev_"
+    $parted -s $dev_ print|grep "^Disk $dev_: $sz$" \
 	> /dev/null || fail="actual device size is not $sz"
     # Try to see if $dev_ or any of its partitions is mounted.
     # This is not reliable.  FIXME: find a better way.
@@ -232,7 +233,7 @@ if test $skip_ = 0 && test "$erasable_device_required_" != ''; then
     # contains no "//" or "/./" components.

     # Prefer df --local, if it works, so we don't waste time
-    # enumerating with lots of automounted file systems.
+    # enumerating lots of automounted file systems.
     ( df --local / > /dev/null 2>&1 ) && df='df --local' || df=df
     $df | grep "^$dev_" && fail="$dev_ is already mounted"
     $df | grep "^$dev_[0-9]" && fail="a partition of $dev_ is already mounted"
@@ -274,17 +275,16 @@ do
 	esac
 done

+t0=$($abs_top_srcdir/tests/mkdtemp $test_dir_ parted-$this_test.XXXXXXXXXX) \
+    || error "failed to create temporary directory in $test_dir_"
+
 # Run each test from within a temporary sub-directory named after the
-# test itself, and arrange to remove it upon exception and upon normal exit.
-t0=`echo "$0"|sed 's,.*/,,'`.tmp; tmp_=$t0/$$
-trap 'st=$?; cleanup_; cd "$pwd_" && chmod -R u+rwx $t0 && rm -rf $t0 && exit $st' 0
+# test itself, and arrange to remove it upon exception or normal exit.
+trap 'st=$?; cleanup_; d='"$t0"';
+    cd '"$test_dir_"' && chmod -R u+rwx "$d" && rm -rf "$d" && exit $st' 0
 trap '(exit $?); exit $?' 1 2 13 15

-framework_failure=0
-mkdir -p $tmp_ || framework_failure=1
-cd $tmp_ || framework_failure=1
-test $framework_failure = 0 \
-     || error 'failed to create temporary directory'
+cd $t0 || error "failed to cd to $t0"

 if ( diff --version < /dev/null 2>&1 | grep GNU ) 2>&1 > /dev/null; then
   compare='diff -u'



More information about the parted-devel mailing list