[gvfs] Add workaround for ESTALE on NFS for metadata files
- From: Alexander Larsson <alexl src gnome org>
- To: svn-commits-list gnome org
- Subject: [gvfs] Add workaround for ESTALE on NFS for metadata files
- Date: Tue, 23 Jun 2009 11:11:44 -0400 (EDT)
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]