[gvfs] Add workaround for ESTALE on NFS for metadata files



commit 589ad8dc3f3bb6b13402170fdf765fba6e2c73d0
Author: Alexander Larsson <alexl redhat com>
Date:   Mon Jun 22 18:51:48 2009 +0200

    Add workaround for ESTALE on NFS for metadata files

 configure.ac        |   63 +++++++++++++++++-
 metadata/metatree.c |  189 +++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 246 insertions(+), 6 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index ab64543..016b37e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -563,6 +563,67 @@ dnl ==========================================================================
 AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
 
 dnl ==========================================================================
+dnl Look for various fs info getters
+
+AC_CHECK_HEADERS([sys/statfs.h sys/statvfs.h sys/vfs.h sys/mount.h sys/param.h])
+AC_CHECK_FUNCS(statvfs statfs)
+AC_CHECK_MEMBERS([struct statfs.f_fstypename, struct statfs.f_bavail],,, [#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_STATFS_H
+#include <sys/statfs.h>
+#endif
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#ifdef HAVE_SYS_MOUNT_H
+#include <sys/mount.h>
+#endif])
+# struct statvfs.f_basetype is available on Solaris but not for Linux. 
+AC_CHECK_MEMBERS([struct statvfs.f_basetype],,, [#include <sys/statvfs.h>])
+
+dnl
+dnl if statfs() takes 2 arguments (Posix) or 4 (Solaris)
+dnl
+if test "$ac_cv_func_statfs" = yes ; then
+  AC_MSG_CHECKING([number of arguments to statfs()])
+  AC_TRY_COMPILE([#include <unistd.h>
+  #ifdef HAVE_SYS_PARAM_H
+  #include <sys/param.h>
+  #endif
+  #ifdef HAVE_SYS_VFS_H
+  #include <sys/vfs.h>
+  #endif
+  #ifdef HAVE_SYS_MOUNT_H
+  #include <sys/mount.h>
+  #endif
+  #ifdef HAVE_SYS_STATFS_H
+  #include <sys/statfs.h>
+  #endif], [struct statfs st;
+  statfs(NULL, &st);],[
+    AC_MSG_RESULT([2])
+    AC_DEFINE(STATFS_ARGS, 2, [Number of arguments to statfs()])],[
+    AC_TRY_COMPILE([#include <unistd.h>
+  #ifdef HAVE_SYS_PARAM_H
+  #include <sys/param.h>
+  #endif
+  #ifdef HAVE_SYS_VFS_H
+  #include <sys/vfs.h>
+  #endif
+  #ifdef HAVE_SYS_MOUNT_H
+  #include <sys/mount.h>
+  #endif
+  #ifdef HAVE_SYS_STATFS_H
+  #include <sys/statfs.h>
+  #endif], [struct statfs st;
+  statfs(NULL, &st, sizeof (st), 0);],[
+      AC_MSG_RESULT([4])
+      AC_DEFINE(STATFS_ARGS, 4, [Number of arguments to statfs()])],[
+      AC_MSG_RESULT(unknown)
+      AC_MSG_ERROR([unable to determine number of arguments to statfs()])])])
+fi
+
+dnl ==========================================================================
 dnl Turn on the additional warnings last, so -Werror doesn't affect other tests.
 
 AC_ARG_ENABLE(more-warnings,
@@ -603,7 +664,7 @@ if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then
 else
 	AC_MSG_RESULT(no)
 fi
-			       
+
 AC_OUTPUT([
 Makefile
 common/Makefile
diff --git a/metadata/metatree.c b/metadata/metatree.c
index a950d6c..04993e6 100644
--- a/metadata/metatree.c
+++ b/metadata/metatree.c
@@ -5,9 +5,47 @@
 #include <fcntl.h>
 #include <string.h>
 #include <unistd.h>
+#include <errno.h>
 #include <stdlib.h>
 #include <time.h>
 
+#if HAVE_SYS_STATFS_H
+#include <sys/statfs.h>
+#endif
+#if HAVE_SYS_STATVFS_H
+#include <sys/statvfs.h>
+#endif
+#if HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#elif HAVE_SYS_MOUNT_H
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <sys/mount.h>
+#endif
+
+#if defined(HAVE_STATFS) && defined(HAVE_STATVFS)
+/* Some systems have both statfs and statvfs, pick the
+   most "native" for these */
+# if !defined(HAVE_STRUCT_STATFS_F_BAVAIL)
+   /* on solaris and irix, statfs doesn't even have the
+      f_bavail field */
+#  define USE_STATVFS
+# else
+  /* at least on linux, statfs is the actual syscall */
+#  define USE_STATFS
+# endif
+
+#elif defined(HAVE_STATFS)
+
+# define USE_STATFS
+
+#elif defined(HAVE_STATVFS)
+
+# define USE_STATVFS
+
+#endif
+
 #include "metatree.h"
 #include "metabuilder.h"
 #include <glib.h>
@@ -115,6 +153,7 @@ struct _MetaTree {
   volatile guint ref_count;
   char *filename;
   gboolean for_write;
+  gboolean on_nfs;
 
   int fd;
   char *data;
@@ -131,7 +170,8 @@ struct _MetaTree {
   MetaJournal *journal;
 };
 
-static MetaJournal *meta_journal_open          (const char  *filename,
+static MetaJournal *meta_journal_open          (MetaTree    *tree,
+						const char  *filename,
 						gboolean     for_write,
 						guint32      tag);
 static void         meta_journal_free          (MetaJournal *journal);
@@ -237,6 +277,144 @@ meta_tree_clear (MetaTree *tree)
 }
 
 static gboolean
+is_on_nfs (char *filename)
+{
+#ifdef USE_STATFS
+  struct statfs statfs_buffer;
+  int statfs_result;
+#elif defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
+  struct statvfs statfs_buffer;
+  int statfs_result;
+#endif
+  char *dirname;
+  gboolean res;
+
+  dirname = g_path_get_dirname (filename);
+
+  res = FALSE;
+
+#ifdef USE_STATFS
+
+# if STATFS_ARGS == 2
+  statfs_result = statfs (dirname, &statfs_buffer);
+# elif STATFS_ARGS == 4
+  statfs_result = statfs (dirname, &statfs_buffer,
+			  sizeof (statfs_buffer), 0);
+# endif
+  if (statfs_result == 0)
+    res = statfs_buffer.f_type == 0x6969;
+
+#elif defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
+  statfs_result = statvfs (dirname, &statfs_buffer);
+
+  if (statfs_result == 0)
+    res = strcmp (statfs_buffer.f_basetype, "nfs") == 0;
+#endif
+
+  g_free (dirname);
+
+  return res;
+}
+
+static gboolean
+link_to_tmp (const char *source, char *tmpl)
+{
+  char *XXXXXX;
+  int count, res;
+  static const char letters[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+  static const int NLETTERS = sizeof (letters) - 1;
+  glong value;
+  GTimeVal tv;
+  static int counter = 0;
+
+  /* find the last occurrence of "XXXXXX" */
+  XXXXXX = g_strrstr (tmpl, "XXXXXX");
+  g_assert (XXXXXX != NULL);
+
+  /* Get some more or less random data.  */
+  g_get_current_time (&tv);
+  value = (tv.tv_usec ^ tv.tv_sec) + counter++;
+
+  for (count = 0; count < 100; value += 7777, ++count)
+    {
+      glong v = value;
+
+      /* Fill in the random bits.  */
+      XXXXXX[0] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[1] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[2] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[3] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[4] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[5] = letters[v % NLETTERS];
+
+      res = link (source, tmpl);
+
+      if (res >= 0)
+	return TRUE;
+      else if (errno != EEXIST)
+	/* Any other error will apply also to other names we might
+	 *  try, and there are 2^32 or so of them, so give up now.
+	 */
+	return FALSE;
+    }
+
+  return FALSE;
+}
+
+static int
+safe_open (MetaTree *tree,
+	   char *filename,
+	   int flags)
+{
+  if (tree->on_nfs)
+    {
+      char *dirname, *tmpname;
+      int fd, errsv;
+
+      /* On NFS if another client unlinks an open file
+       * it is actually removed on the server and this
+       * client will get an ESTALE error on later access.
+       *
+       * For a local (i.e. on this client) unlink this is
+       * handled by the kernel keeping track of unlinks of
+       * open files (by this client) using ".nfsXXXX" files.
+       *
+       * We work around the ESTALE problem by first linking
+       * the file to a temp file that we then unlink on
+       * this client. We never leak the tmpfile (unless
+       * the kernel crashes) and no other client should
+       * remove our tmpfile.
+       */
+
+      dirname = g_path_get_dirname (filename);
+      tmpname = g_build_filename (dirname, ".openXXXXXX", NULL);
+      g_free (dirname);
+
+      if (!link_to_tmp (filename, tmpname))
+	fd = open (filename, flags); /* link failed, what can we do... */
+      else
+	{
+	  fd = open (tmpname, flags);
+	  errsv = errno;
+	  unlink (tmpname);
+	  errno = errsv;
+	}
+
+      g_free (tmpname);
+      return fd;
+    }
+  else
+    return open (filename, flags);
+
+}
+
+static gboolean
 meta_tree_init (MetaTree *tree)
 {
   struct stat statbuf;
@@ -248,7 +426,8 @@ meta_tree_init (MetaTree *tree)
 
   retried = FALSE;
  retry:
-  fd = open (tree->filename, O_RDONLY);
+  tree->on_nfs = is_on_nfs (tree->filename);
+  fd = safe_open (tree, tree->filename, O_RDONLY);
   if (fd == -1)
     {
       if (tree->for_write && !retried)
@@ -317,7 +496,7 @@ meta_tree_init (MetaTree *tree)
   tree->tag = GUINT32_FROM_BE (tree->header->random_tag);
   tree->time_t_base = GINT64_FROM_BE (tree->header->time_t_base);
 
-  tree->journal = meta_journal_open (tree->filename, tree->for_write, tree->tag);
+  tree->journal = meta_journal_open (tree, tree->filename, tree->for_write, tree->tag);
 
   /* There is a race with tree replacing, where the journal could have been
      deleted (and the tree replaced) inbetween opening the tree file and the
@@ -909,7 +1088,7 @@ meta_journal_add_entry (MetaJournal *journal,
 }
 
 static MetaJournal *
-meta_journal_open (const char *filename, gboolean for_write, guint32 tag)
+meta_journal_open (MetaTree *tree, const char *filename, gboolean for_write, guint32 tag)
 {
   MetaJournal *journal;
   struct stat statbuf;
@@ -927,7 +1106,7 @@ meta_journal_open (const char *filename, gboolean for_write, guint32 tag)
   else
     open_flags = O_RDONLY;
 
-  fd = open (journal_filename, open_flags);
+  fd = safe_open (tree, journal_filename, open_flags);
   g_free (journal_filename);
   if (fd == -1)
     return NULL;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]