[gvfs/nfs: 3/3] Add an nfs backend based on libnfs



commit f280f8d0fa1139ed39bf816a35c2b0d3c61826c1
Author: Ross Lagerwall <rosslagerwall gmail com>
Date:   Tue May 6 19:06:41 2014 +0100

    Add an nfs backend based on libnfs
    
    Add an nfs backend based on libnfs to make userspace mounting and usage
    of nfs shares easier.  The backend is written in a single-threaded,
    asynchronous style.  Performance measurements show around 60-70 MiB/s
    throughput on 1GbE.
    
    To make use of it, simply mount the share with gvfs-mount or Nautilus
    with the following syntax:
    nfs://host/export/path
    
    Authentication is simple, based on UNIX uid.
    
    Since this is a userspace nfs client, it comes with the caveat that the
    mount needs to be exported with "insecure" on Linux (or some equivalent
    for other NFS servers) so that it allows connections from port numbers
    higher than 1023.  Alternatively, a special capability can be given to
    the binary:
    sudo setcap 'cap_net_bind_service=+ep' /path/to/executable

 configure.ac            |   22 +
 daemon/.gitignore       |    1 +
 daemon/Makefile.am      |   21 +
 daemon/gvfsbackendnfs.c | 2509 +++++++++++++++++++++++++++++++++++++++++++++++
 daemon/gvfsbackendnfs.h |   48 +
 daemon/nfs.mount.in     |    5 +
 6 files changed, 2606 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 021bf9d..afb25d3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -738,6 +738,27 @@ if test "x$enable_afp" != "xno"; then
 fi
 AM_CONDITIONAL(USE_AFP, test "x$enable_afp" != "xno")
 
+dnl *******************
+dnl *** NFS backend ***
+dnl *******************
+AC_ARG_ENABLE(nfs, AS_HELP_STRING([--disable-nfs], [build without NFS support]))
+msg_nfs="no"
+NFS_CFLAGS=
+NFS_LIBS=
+
+if test "x$enable_nfs" != "xno"; then
+  PKG_CHECK_EXISTS([libnfs >= 1.9.5], msg_nfs=yes)
+
+  if test "x$msg_nfs" = "xyes"; then
+    PKG_CHECK_MODULES([NFS],[libnfs >= 1.9.5])
+    AC_DEFINE(HAVE_NFS, 1, [Define to 1 if nfs is going to be built])
+  fi
+fi
+
+AC_SUBST(NFS_CFLAGS)
+AC_SUBST(NFS_LIBS)
+AM_CONDITIONAL(HAVE_NFS, [test "$msg_nfs" = "yes"])
+
 dnl ***************************************
 dnl *** Check for nl_langinfo constants ***
 dnl ***************************************
@@ -948,6 +969,7 @@ echo "
        archive support:              $msg_archive
        AFC support:                  $msg_afc
         AFP support:                  $msg_afp
+        NFS support:                  $msg_nfs
         DNS-SD support:               $msg_avahi
        Build HAL volume monitor:     $msg_hal (with fast init path: $have_hal_fast_init)
        Build GDU volume monitor:     $msg_gdu
diff --git a/daemon/.gitignore b/daemon/.gitignore
index 2b8e1eb..96935bc 100644
--- a/daemon/.gitignore
+++ b/daemon/.gitignore
@@ -14,6 +14,7 @@ gvfsd-http
 gvfsd-localtest
 gvfsd-network
 gvfsd-mtp
+gvfsd-nfs
 gvfsd-obexftp
 gvfsd-recent
 gvfsd-sftp
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index a6a9ae1..910269b 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -129,6 +129,12 @@ mount_DATA += afp-browse.mount afp.mount
 libexec_PROGRAMS += gvfsd-afp-browse gvfsd-afp
 endif
 
+mount_in_files += nfs.mount.in
+if HAVE_NFS
+mount_DATA += nfs.mount
+libexec_PROGRAMS += gvfsd-nfs
+endif
+
 noinst_DATA = $(mount_DATA:.mount=.localmount)
 
 EXTRA_DIST =                           \
@@ -607,6 +613,21 @@ gvfsd_afp_LDADD = \
        $(libraries) \
        $(LIBGCRYPT_LIBS)
 
+gvfsd_nfs_SOURCES = \
+       gvfsbackendnfs.c gvfsbackendnfs.h \
+       daemon-main.c daemon-main.h \
+       daemon-main-generic.c
+
+gvfsd_nfs_CPPFLAGS = \
+       $(flags) \
+       -DBACKEND_HEADER=gvfsbackendnfs.h \
+       -DDEFAULT_BACKEND_TYPE=nfs \
+       -DMAX_JOB_THREADS=1 \
+       -DBACKEND_TYPES='"nfs", G_VFS_TYPE_BACKEND_NFS,' \
+       $(NFS_CFLAGS)
+
+gvfsd_nfs_LDADD = $(libraries) $(NFS_LIBS)
+
 
 # GSettings stuff
 gsettings_ENUM_NAMESPACE = org.gnome.system.gvfs
diff --git a/daemon/gvfsbackendnfs.c b/daemon/gvfsbackendnfs.c
new file mode 100644
index 0000000..761014f
--- /dev/null
+++ b/daemon/gvfsbackendnfs.c
@@ -0,0 +1,2509 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2014 Ross Lagerwall
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <poll.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "gvfsbackendnfs.h"
+#include "gvfsjobopenforread.h"
+#include "gvfsjobread.h"
+#include "gvfsjobseekread.h"
+#include "gvfsjobdelete.h"
+#include "gvfsjobopenforwrite.h"
+#include "gvfsjobwrite.h"
+#include "gvfsjobclosewrite.h"
+#include "gvfsjobseekwrite.h"
+#include "gvfsjobtruncate.h"
+#include "gvfsjobsetdisplayname.h"
+#include "gvfsjobqueryinfo.h"
+#include "gvfsjobqueryinforead.h"
+#include "gvfsjobqueryinfowrite.h"
+#include "gvfsjobqueryfsinfo.h"
+#include "gvfsjobqueryattributes.h"
+#include "gvfsjobsetattribute.h"
+#include "gvfsjobenumerate.h"
+#include "gvfsjobmove.h"
+#include "gvfsdaemonprotocol.h"
+#include "gvfsdaemonutils.h"
+
+#include <nfsc/libnfs.h>
+#include <nfsc/libnfs-raw-nfs.h>
+#include <nfsc/libnfs-raw-mount.h>
+
+struct _GVfsBackendNfs
+{
+  GVfsBackend parent_instance;
+
+  struct nfs_context *ctx;
+  GSource *source;
+  mode_t umask;               /* cached umask of process */
+};
+
+typedef struct
+{
+  GSource source;
+  struct nfs_context *ctx;
+  GVfsBackendNfs *backend;
+  int fd;                     /* fd registered for IO */
+  gpointer tag;               /* tag for fd attached to this source */
+  int events;                 /* IO events we're interested in */
+} NfsSource;
+
+G_DEFINE_TYPE (GVfsBackendNfs, g_vfs_backend_nfs, G_VFS_TYPE_BACKEND)
+
+static void
+g_vfs_backend_nfs_init (GVfsBackendNfs *backend)
+{
+}
+
+static void
+g_vfs_backend_nfs_finalize (GObject *object)
+{
+  GVfsBackendNfs *backend = G_VFS_BACKEND_NFS (object);
+
+  if (backend->source)
+    {
+      g_source_destroy (backend->source);
+      g_source_unref (backend->source);
+    }
+
+  if (backend->ctx)
+    nfs_destroy_context (backend->ctx);
+
+  if (G_OBJECT_CLASS (g_vfs_backend_nfs_parent_class)->finalize)
+    (*G_OBJECT_CLASS (g_vfs_backend_nfs_parent_class)->finalize) (object);
+}
+
+static GIOCondition
+event_to_condition (int events)
+{
+  GIOCondition condition = 0;
+
+  if (events & POLLIN)
+    condition |= G_IO_IN;
+  if (events & POLLOUT)
+    condition |= G_IO_OUT;
+  if (events & POLLPRI)
+    condition |= G_IO_PRI;
+
+  return condition;
+}
+
+static int
+condition_to_event (GIOCondition condition)
+{
+  int events = 0;
+
+  if (condition & G_IO_IN)
+    events |= POLLIN;
+  if (condition & G_IO_OUT)
+    events |= POLLOUT;
+  if (condition & G_IO_PRI)
+    events |= POLLPRI;
+  if (condition & G_IO_ERR)
+    events |= POLLERR;
+  if (condition & G_IO_HUP)
+    events |= POLLHUP;
+  if (condition & G_IO_NVAL)
+    events |= POLLNVAL;
+
+  return events;
+}
+
+static gboolean
+nfs_source_prepare (GSource *source, gint *timeout)
+{
+  NfsSource *nfs_source = (NfsSource *) source;
+  int events, fd;
+
+  *timeout = -1;
+
+  fd = nfs_get_fd (nfs_source->ctx);
+  events = nfs_which_events (nfs_source->ctx);
+
+  if (fd < 0)
+    {
+      g_vfs_backend_force_unmount (G_VFS_BACKEND (nfs_source->backend));
+      nfs_destroy_context (nfs_source->ctx);
+      g_source_destroy (source);
+      g_source_unref (source);
+    }
+  else if (fd != nfs_source->fd)
+    {
+      g_source_remove_unix_fd (source, nfs_source->tag);
+      nfs_source->fd = fd;
+      nfs_source->events = events;
+      nfs_source->tag = g_source_add_unix_fd (source,
+                                              nfs_source->fd,
+                                              event_to_condition (events));
+    }
+  else if (events != nfs_source->events)
+    {
+      nfs_source->events = events;
+      g_source_modify_unix_fd (source,
+                               nfs_source->tag,
+                               event_to_condition (events));
+    }
+
+  return FALSE;
+}
+
+static gboolean
+nfs_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
+{
+  NfsSource *nfs_source = (NfsSource *) source;
+  int err;
+
+  err = nfs_service (nfs_source->ctx,
+                     condition_to_event (g_source_query_unix_fd (source,
+                                                                 nfs_source->tag)));
+  if (err)
+    {
+      g_warning ("nfs_service error: %d, %s\n",
+                 err, nfs_get_error (nfs_source->ctx));
+      g_vfs_backend_force_unmount (G_VFS_BACKEND (nfs_source->backend));
+      nfs_destroy_context (nfs_source->ctx);
+      g_source_destroy (source);
+      g_source_unref (source);
+    }
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+do_mount (GVfsBackend *backend,
+          GVfsJobMount *job,
+          GMountSpec *mount_spec,
+          GMountSource *mount_source,
+          gboolean is_automount)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  GMountSpec *nfs_mount_spec;
+  GSource *source;
+  NfsSource *nfs_source;
+  struct exportnode *export_list, *ptr;
+  const char *host;
+  char *basename, *display_name, *export = NULL;
+  int err;
+  int pathlen = strlen (mount_spec->mount_prefix);
+  int exportlen = 0;
+  static GSourceFuncs nfs_source_callbacks = {
+    nfs_source_prepare,
+    NULL,
+    nfs_source_dispatch,
+    NULL
+  };
+
+  host = g_mount_spec_get (mount_spec, "host");
+  export_list = mount_getexports (host);
+
+  /* Find the longest matching mount. E.g. if the given mount_prefix is
+   * /some/long/path * and there exist two mounts, /some and /some/long, match
+   * against the latter. */
+  for (ptr = export_list; ptr; ptr = ptr->ex_next)
+    {
+      if (strstr (mount_spec->mount_prefix, ptr->ex_dir) == mount_spec->mount_prefix)
+        {
+          int this_exportlen = strlen (ptr->ex_dir);
+
+          if (pathlen > this_exportlen)
+            {
+              char *s = mount_spec->mount_prefix + this_exportlen;
+              if ((*s == '/' || *s == '\0') && this_exportlen > exportlen)
+                {
+                  export = ptr->ex_dir;
+                  exportlen = this_exportlen;
+                }
+            }
+          else if (this_exportlen > exportlen)
+            {
+              export = ptr->ex_dir;
+              exportlen = this_exportlen;
+            }
+        }
+    }
+
+  if (!export)
+    {
+      mount_free_export_list (export_list);
+      g_vfs_job_failed_literal (G_VFS_JOB (job),
+                                G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                                _("Mount point does not exist"));
+      return;
+    }
+
+  export = strdup (export);
+  mount_free_export_list (export_list);
+
+  op_backend->ctx = nfs_init_context ();
+  err = nfs_mount (op_backend->ctx, host, export);
+  if (err)
+    {
+      g_vfs_job_failed_from_errno (G_VFS_JOB (job), -err);
+      return;
+    }
+
+  source = g_source_new (&nfs_source_callbacks, sizeof (NfsSource));
+  nfs_source = (NfsSource *) source;
+  nfs_source->ctx = op_backend->ctx;
+  nfs_source->backend = op_backend;
+  nfs_source->events = nfs_which_events (op_backend->ctx);
+  nfs_source->fd = nfs_get_fd (op_backend->ctx);
+  nfs_source->tag = g_source_add_unix_fd (source,
+                                          nfs_source->fd,
+                                          event_to_condition (nfs_source->events));
+  g_source_attach (source, NULL);
+  op_backend->source = source;
+
+  basename = g_path_get_basename (export);
+  /* Translators: This is "<mount point> on <host>" and is used as name for an NFS mount */
+  display_name = g_strdup_printf (_("%s on %s"), basename, host);
+  g_vfs_backend_set_display_name (backend, display_name);
+  g_free (basename);
+  g_free (display_name);
+
+  g_vfs_backend_set_icon_name (G_VFS_BACKEND (backend), "folder-remote");
+  g_vfs_backend_set_symbolic_icon_name (G_VFS_BACKEND (backend), "folder-remote-symbolic");
+
+  nfs_mount_spec = g_mount_spec_new ("nfs");
+  g_mount_spec_set (nfs_mount_spec, "host", host);
+  g_mount_spec_set_mount_prefix (nfs_mount_spec, export);
+  g_vfs_backend_set_mount_spec (backend, nfs_mount_spec);
+  g_mount_spec_unref (nfs_mount_spec);
+
+  /* cache the process's umask for later */
+  op_backend->umask = umask (0);
+  umask (op_backend->umask);
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+static void
+null_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+}
+
+static void
+generic_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    g_vfs_job_succeeded (job);
+  else
+    g_vfs_job_failed_from_errno (job, -err);
+}
+
+static char *
+create_etag (uint64_t mtime, uint32_t nsec)
+{
+  return g_strdup_printf ("%lu:%lu",
+                          (long unsigned int)mtime,
+                          (long unsigned int)nsec);
+}
+
+static void
+open_for_read_fstat_cb (int err,
+                        struct nfs_context *ctx,
+                        void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobOpenForRead *op_job = G_VFS_JOB_OPEN_FOR_READ (job);
+      struct stat *st = data;
+
+      if (S_ISDIR (st->st_mode))
+        {
+          struct nfsfh *fh = op_job->backend_handle;
+
+          nfs_close_async (ctx, fh, null_cb, NULL);
+          g_vfs_job_failed_literal (job,
+                                    G_IO_ERROR,
+                                    G_IO_ERROR_IS_DIRECTORY,
+                                    _("Can't open directory"));
+          return;
+        }
+    }
+  g_vfs_job_succeeded (job);
+}
+
+static void
+open_for_read_cb (int err,
+                  struct nfs_context *ctx,
+                  void *data, void *private_data)
+{
+  if (err == 0)
+    {
+      GVfsJobOpenForRead *op_job = G_VFS_JOB_OPEN_FOR_READ (private_data);
+
+      g_vfs_job_open_for_read_set_handle (op_job, data);
+      g_vfs_job_open_for_read_set_can_seek (op_job, TRUE);
+
+      nfs_fstat_async (ctx, data, open_for_read_fstat_cb, private_data);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (G_VFS_JOB (private_data), -err);
+    }
+}
+
+static gboolean
+try_open_for_read (GVfsBackend *backend,
+                   GVfsJobOpenForRead *job,
+                   const char *filename)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  nfs_open_async (op_backend->ctx, filename, O_RDONLY, open_for_read_cb, job);
+  return TRUE;
+}
+
+static void
+read_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err >= 0)
+    {
+      GVfsJobRead *op_job = G_VFS_JOB_READ (job);
+
+      memcpy (op_job->buffer, data, err);
+      g_vfs_job_read_set_size (op_job, err);
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_read (GVfsBackend *backend,
+          GVfsJobRead *job,
+          GVfsBackendHandle _handle,
+          char *buffer,
+          gsize bytes_requested)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  struct nfsfh *fh = _handle;
+
+  nfs_read_async (op_backend->ctx, fh, bytes_requested, read_cb, job);
+  return TRUE;
+}
+
+static char *
+set_type_from_mode (GFileInfo *info, uint64_t mode)
+{
+  GFileType type = G_FILE_TYPE_UNKNOWN;
+  char *mimetype = NULL;
+
+  if (S_ISREG (mode))
+    type = G_FILE_TYPE_REGULAR;
+  else if (S_ISDIR (mode))
+    {
+      type = G_FILE_TYPE_DIRECTORY;
+      mimetype = "inode/directory";
+    }
+  else if (S_ISFIFO (mode))
+    {
+      type = G_FILE_TYPE_SPECIAL;
+      mimetype = "inode/fifo";
+    }
+  else if (S_ISSOCK (mode))
+    {
+      type = G_FILE_TYPE_SPECIAL;
+      mimetype = "inode/socket";
+    }
+  else if (S_ISCHR (mode))
+    {
+      type = G_FILE_TYPE_SPECIAL;
+      mimetype = "inode/chardevice";
+    }
+  else if (S_ISBLK (mode))
+    {
+      type = G_FILE_TYPE_SPECIAL;
+      mimetype = "inode/blockdevice";
+    }
+  else if (S_ISLNK (mode))
+    {
+      type = G_FILE_TYPE_SYMBOLIC_LINK;
+      g_file_info_set_is_symlink (info, TRUE);
+      mimetype = "inode/symlink";
+    }
+  g_file_info_set_file_type (info, type);
+
+  return mimetype;
+}
+
+static void
+set_name_info (GFileInfo *info,
+               char *mimetype,
+               const char *basename,
+               GFileAttributeMatcher *matcher)
+{
+  gboolean free_mimetype = FALSE;
+
+  g_file_info_set_name (info, basename);
+  if (basename[0] == '.')
+    g_file_info_set_is_hidden (info, TRUE);
+  if (basename[strlen (basename) -1] == '~')
+    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, TRUE);
+
+  /* We use the same setting as for local files. */
+  if (g_file_attribute_matcher_matches (matcher,
+                                        G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
+    {
+      char *display_name = g_filename_display_name (basename);
+
+      if (strstr (display_name, "\357\277\275") != NULL)
+        {
+          char *p = display_name;
+          display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL);
+          g_free (p);
+        }
+      g_file_info_set_display_name (info, display_name);
+      g_free (display_name);
+    }
+
+  if (g_file_attribute_matcher_matches (matcher,
+                                        G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME))
+    {
+      char *edit_name = g_filename_display_name (basename);
+      g_file_info_set_edit_name (info, edit_name);
+      g_free (edit_name);
+    }
+
+  if (mimetype == NULL)
+    {
+      if (basename)
+        {
+          mimetype = g_content_type_guess (basename, NULL, 0, NULL);
+          free_mimetype = TRUE;
+        }
+      else
+        mimetype = "application/octet-stream";
+    }
+
+  g_file_info_set_content_type (info, mimetype);
+  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, mimetype);
+
+  if (g_file_attribute_matcher_matches (matcher,
+                                        G_FILE_ATTRIBUTE_STANDARD_ICON) ||
+      g_file_attribute_matcher_matches (matcher,
+                                        G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON))
+    {
+      GIcon *icon = NULL;
+      GIcon *symbolic_icon = NULL;
+
+      if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+        {
+          icon = g_themed_icon_new ("folder");
+          symbolic_icon = g_themed_icon_new ("folder-symbolic");
+        }
+      else if (mimetype)
+        {
+          icon = g_content_type_get_icon (mimetype);
+          symbolic_icon = g_content_type_get_symbolic_icon (mimetype);
+        }
+
+      if (icon == NULL)
+        icon = g_themed_icon_new ("text-x-generic");
+      if (symbolic_icon == NULL)
+        symbolic_icon = g_themed_icon_new ("text-x-generic-symbolic");
+
+      g_file_info_set_icon (info, icon);
+      g_file_info_set_symbolic_icon (info, symbolic_icon);
+      g_object_unref (icon);
+      g_object_unref (symbolic_icon);
+    }
+
+  if (free_mimetype)
+    g_free (mimetype);
+}
+
+static void
+set_info_from_stat (GFileInfo *info, struct nfs_stat_64 *st, GFileAttributeMatcher *matcher)
+{
+  g_file_info_set_size (info, st->nfs_size);
+  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, st->nfs_used);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, st->nfs_mode);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, st->nfs_uid);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, st->nfs_gid);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_NLINK, st->nfs_nlink);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE, st->nfs_dev);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_RDEV, st->nfs_rdev);
+  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, st->nfs_ino);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE, st->nfs_blksize);
+  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_BLOCKS, st->nfs_blocks);
+  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, st->nfs_atime);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, st->nfs_atime_nsec / 1000);
+  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, st->nfs_mtime);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, st->nfs_mtime_nsec / 1000);
+  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED, st->nfs_ctime);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, st->nfs_ctime_nsec / 1000);
+}
+
+static void
+query_info_on_read_cb (int err,
+                       struct nfs_context *ctx,
+                       void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobQueryInfoRead *op_job = G_VFS_JOB_QUERY_INFO_READ (job);
+      struct nfs_stat_64 *st = data;
+
+      set_info_from_stat (op_job->file_info, st, op_job->attribute_matcher);
+      set_type_from_mode (op_job->file_info, st->nfs_mode);
+
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_query_info_on_read (GVfsBackend *backend,
+                        GVfsJobQueryInfoRead *job,
+                        GVfsBackendHandle _handle,
+                        GFileInfo *info,
+                        GFileAttributeMatcher *attribute_matcher)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  struct nfsfh *nfsfh = _handle;
+
+  nfs_fstat64_async (op_backend->ctx, nfsfh, query_info_on_read_cb, job);
+  return TRUE;
+}
+
+static void
+seek_on_read_cb (int err,
+                 struct nfs_context *ctx,
+                 void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err >= 0)
+    {
+      GVfsJobSeekRead *op_job = G_VFS_JOB_SEEK_READ (job);
+      uint64_t *pos = data;
+
+      g_vfs_job_seek_read_set_offset (op_job, *pos);
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_seek_on_read (GVfsBackend *backend,
+                  GVfsJobSeekRead *job,
+                  GVfsBackendHandle _handle,
+                  goffset offset,
+                  GSeekType type)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  struct nfsfh *fh = _handle;
+
+  nfs_lseek_async (op_backend->ctx,
+                   fh, offset, gvfs_seek_type_to_lseek (type),
+                   seek_on_read_cb, job);
+  return TRUE;
+}
+
+static gboolean
+try_close_read (GVfsBackend *backend,
+                GVfsJobCloseRead *job,
+                GVfsBackendHandle _handle)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  struct nfsfh *fh = _handle;
+
+  nfs_close_async (op_backend->ctx, fh, generic_cb, job);
+  return TRUE;
+}
+
+static gboolean
+try_make_directory (GVfsBackend *backend,
+                    GVfsJobMakeDirectory *job,
+                    const char *filename)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  nfs_mkdir_async (op_backend->ctx, filename, generic_cb, job);
+  return TRUE;
+}
+
+static void
+unlink_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+  GVfsJobDelete *op_job = G_VFS_JOB_DELETE (job);
+
+  if (err == 0)
+    g_vfs_job_succeeded (job);
+  else if (err == -EPERM || err == -EISDIR)
+    nfs_rmdir_async (ctx, op_job->filename, generic_cb, private_data);
+  else
+    g_vfs_job_failed_from_errno (job, -err);
+}
+
+static gboolean
+try_delete (GVfsBackend *backend,
+            GVfsJobDelete *job,
+            const char *filename)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  nfs_unlink_async (op_backend->ctx, filename, unlink_cb, job);
+  return TRUE;
+}
+
+static gboolean
+try_make_symlink (GVfsBackend *backend,
+                  GVfsJobMakeSymlink *job,
+                  const char *filename,
+                  const char *symlink_value)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  nfs_symlink_async (op_backend->ctx,
+                     symlink_value, filename,
+                     generic_cb, job);
+  return TRUE;
+}
+
+typedef struct {
+  struct nfsfh *fh;
+  GVfsJob *job;
+  char *filename;
+  char *tempname;
+  char *backup_filename;
+  uint64_t uid;
+  uint64_t gid;
+  uint64_t nlink;
+  uint64_t mode;
+  gboolean is_symlink;
+} WriteHandle;
+
+static void
+write_handle_free (WriteHandle *handle)
+{
+  if (handle->job)
+    g_object_unref (handle->job);
+  if (handle->filename)
+    g_free (handle->filename);
+  if (handle->tempname)
+    g_free (handle->tempname);
+  if (handle->backup_filename)
+    g_free (handle->backup_filename);
+  g_slice_free (WriteHandle, handle);
+}
+
+static void
+append_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+  if (err == 0)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+      WriteHandle *handle = g_slice_new0 (WriteHandle);
+
+      handle->fh = data;
+      g_vfs_job_open_for_write_set_handle (op_job, handle);
+      g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
+      g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_append_to (GVfsBackend *backend,
+               GVfsJobOpenForWrite *job,
+               const char *filename,
+               GFileCreateFlags flags)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  nfs_create_async (op_backend->ctx,
+                    filename,
+                    O_APPEND,
+                    (flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask,
+                    append_cb, job);
+  return TRUE;
+}
+
+#define COPY_BLKSIZE (64 * 1024)
+
+typedef void (*CopyFileCallback) (gboolean success, void *private_data);
+
+typedef struct
+{
+  struct nfsfh *srcfh;
+  struct nfsfh *destfh;
+  char *dest;
+  CopyFileCallback cb;
+  void *private_data;
+} CopyHandle;
+
+static void
+copy_read_cb (int err, struct nfs_context *ctx, void *data, void *private_data);
+
+static void
+copy_write_cb (int err,
+               struct nfs_context *ctx,
+               void *data,
+               void *private_data)
+{
+  CopyHandle *handle = private_data;
+
+  if (err > 0)
+    {
+      nfs_read_async (ctx, handle->srcfh, COPY_BLKSIZE, copy_read_cb, handle);
+    }
+  else
+    {
+      nfs_close_async (ctx, handle->srcfh, null_cb, NULL);
+      nfs_close_async (ctx, handle->destfh, null_cb, NULL);
+      handle->cb (FALSE, handle->private_data);
+      g_slice_free (CopyHandle, handle);
+    }
+}
+
+static void
+copy_read_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  CopyHandle *handle = private_data;
+
+  if (err == 0)
+    {
+      nfs_close_async (ctx, handle->srcfh, null_cb, NULL);
+      nfs_close_async (ctx, handle->destfh, null_cb, NULL);
+      handle->cb (TRUE, handle->private_data);
+      g_slice_free (CopyHandle, handle);
+    }
+  else if (err > 0)
+    {
+      nfs_write_async (ctx, handle->destfh, err, data, copy_write_cb, handle);
+    }
+  else
+    {
+      nfs_close_async (ctx, handle->srcfh, null_cb, NULL);
+      nfs_close_async (ctx, handle->destfh, null_cb, NULL);
+      handle->cb (FALSE, handle->private_data);
+      g_slice_free (CopyHandle, handle);
+    }
+}
+
+static void
+copy_open_dest_cb (int err,
+                   struct nfs_context *ctx,
+                   void *data, void *private_data)
+{
+  CopyHandle *handle = private_data;
+
+  if (err == 0)
+    {
+      handle->destfh = data;
+
+      nfs_read_async (ctx, handle->srcfh, COPY_BLKSIZE, copy_read_cb, handle);
+    }
+  else
+    {
+      nfs_close_async (ctx, handle->srcfh, null_cb, NULL);
+      handle->cb (FALSE, handle->private_data);
+      g_slice_free (CopyHandle, handle);
+    }
+}
+
+static void
+copy_open_source_cb (int err,
+                     struct nfs_context *ctx,
+                     void *data, void *private_data)
+{
+  CopyHandle *handle = private_data;
+
+  if (err == 0)
+    {
+      handle->srcfh = data;
+      nfs_create_async (ctx,
+                        handle->dest, O_TRUNC, 0600,
+                        copy_open_dest_cb, handle);
+      g_free (handle->dest);
+    }
+  else
+    {
+      handle->cb (FALSE, handle->private_data);
+      g_free (handle->dest);
+      g_slice_free (CopyHandle, handle);
+    }
+}
+
+static void
+copy_file (struct nfs_context *ctx,
+           const char *src, const char *dest,
+           CopyFileCallback cb, void *private_data)
+{
+  CopyHandle *handle;
+
+  handle = g_slice_new0 (CopyHandle);
+  handle->dest = g_strdup (dest);
+  handle->cb = cb;
+  handle->private_data = private_data;
+
+  nfs_open_async (ctx, src, O_RDONLY, copy_open_source_cb, handle);
+}
+
+static void
+replace_trunc_cb (int err,
+                  struct nfs_context *ctx,
+                  void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+
+      handle->fh = data;
+      g_vfs_job_open_for_write_set_handle (op_job, handle);
+      g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
+      g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
+      g_vfs_job_succeeded (job);
+
+      g_object_unref (handle->job);
+      handle->job = NULL;
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_backup_chown_cb (int err,
+                         struct nfs_context *ctx,
+                         void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  g_free (handle->backup_filename);
+  handle->backup_filename = NULL;
+
+  if (err == 0)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+      GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (op_job->backend);
+
+      nfs_create_async (op_backend->ctx,
+                        op_job->filename,
+                        O_TRUNC,
+                        (op_job->flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask,
+                        replace_trunc_cb, handle);
+    }
+  else
+    {
+      g_vfs_job_failed_literal (job,
+                                G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
+                                _("Backup file creation failed"));
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_backup_chmod_cb (int err,
+                         struct nfs_context *ctx,
+                         void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0)
+    {
+      nfs_chown_async (ctx,
+                       handle->backup_filename, handle->uid, handle->gid,
+                       replace_backup_chown_cb, handle);
+    }
+  else
+    {
+      g_vfs_job_failed_literal (job,
+                                G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
+                                _("Backup file creation failed"));
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_backup_cb (gboolean success, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (success)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+      GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (op_job->backend);
+
+      nfs_chmod_async (op_backend->ctx,
+                       handle->backup_filename, handle->mode & 0777,
+                       replace_backup_chmod_cb, handle);
+    }
+  else
+    {
+      g_vfs_job_failed_literal (job,
+                                G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
+                                _("Backup file creation failed"));
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_rm_backup_cb (int err,
+                      struct nfs_context *ctx,
+                      void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0 || err == -ENOENT)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+
+      copy_file (ctx,
+                 op_job->filename, handle->backup_filename,
+                 replace_backup_cb, handle);
+    }
+  else
+    {
+      g_vfs_job_failed_literal (job,
+                                G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
+                                _("Backup file creation failed"));
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_truncate (struct nfs_context *ctx, WriteHandle *handle)
+{
+  GVfsJob *job = handle->job;
+  GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (op_job->backend);
+
+  g_free (handle->filename);
+  g_free (handle->tempname);
+  handle->filename = handle->tempname = NULL;
+
+  if (op_job->make_backup)
+    {
+      handle->backup_filename = g_strconcat (op_job->filename, "~", NULL);
+      nfs_unlink_async (ctx,
+                        handle->backup_filename,
+                        replace_rm_backup_cb, handle);
+    }
+  else
+    {
+      nfs_create_async (ctx,
+                        op_job->filename,
+                        O_TRUNC,
+                        (op_job->flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask,
+                        replace_trunc_cb, handle);
+    }
+}
+
+static void
+replace_temp_chmod_cb (int err,
+                       struct nfs_context *ctx,
+                       void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+
+      g_vfs_job_open_for_write_set_handle (op_job, handle);
+      g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
+      g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
+      g_vfs_job_succeeded (job);
+
+      g_object_unref (handle->job);
+      handle->job = NULL;
+    }
+  else
+    {
+      nfs_close_async (ctx, handle->fh, null_cb, NULL);
+      g_vfs_job_failed_literal (job,
+                                G_IO_ERROR, G_IO_ERROR_FAILED,
+                                _("Unable to create temporary file"));
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_temp_chown_cb (int err,
+                       struct nfs_context *ctx,
+                       void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+
+  if (err == 0)
+    {
+      nfs_fchmod_async (ctx,
+                        handle->fh, handle->mode & 0777,
+                        replace_temp_chmod_cb, handle);
+    }
+  else
+    {
+      nfs_close_async (ctx, handle->fh, null_cb, NULL);
+      g_vfs_job_failed_literal (handle->job,
+                                G_IO_ERROR, G_IO_ERROR_FAILED,
+                                _("Unable to create temporary file"));
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_temp_cb (int err,
+                 struct nfs_context *ctx,
+                 void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+  GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+
+  if (err == 0)
+    {
+      handle->fh = data;
+
+      if (op_job->make_backup)
+        handle->backup_filename = g_strconcat (op_job->filename, "~", NULL);
+
+      if (op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION)
+        {
+          g_vfs_job_open_for_write_set_handle (op_job, handle);
+          g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
+          g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
+          g_vfs_job_succeeded (job);
+
+          g_object_unref (handle->job);
+          handle->job = NULL;
+        }
+      else
+        {
+          nfs_fchown_async (ctx,
+                            handle->fh,
+                            handle->uid, handle->gid,
+                            replace_temp_chown_cb, handle);
+        }
+    }
+  else if ((err == -EACCES || err == -EEXIST) &&
+           !(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION))
+    {
+      replace_truncate (ctx, handle);
+    }
+  else if (err == -EEXIST)
+    {
+      g_vfs_job_failed_literal (job,
+                                G_IO_ERROR, G_IO_ERROR_FAILED,
+                                _("Unable to create temporary file"));
+      write_handle_free (handle);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_lstat_cb (int err,
+                  struct nfs_context *ctx,
+                  void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+      GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (op_job->backend);
+      struct nfs_stat_64 *st = data;
+
+      handle->is_symlink = S_ISLNK (st->nfs_mode);
+
+      if ((op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION) &&
+          !handle->is_symlink && S_ISDIR (handle->mode))
+        {
+          g_vfs_job_failed_literal (job,
+                                    G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
+                                    _("Target file is a directory"));
+          write_handle_free (handle);
+        }
+      else if ((op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION) ||
+               (!handle->is_symlink && handle->nlink <= 1))
+        {
+          char *dirname;
+          char basename[] = ".giosaveXXXXXX";
+
+          handle->filename = g_strdup (op_job->filename);
+
+          dirname = g_path_get_dirname (op_job->filename);
+          gvfs_randomize_string (basename + 8, 6);
+          handle->tempname = g_build_filename (dirname, basename, NULL);
+          g_free (dirname);
+
+          nfs_create_async (ctx,
+                            handle->tempname,
+                            O_EXCL,
+                            (op_job->flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask,
+                            replace_temp_cb, handle);
+        }
+      else
+        {
+          replace_truncate (ctx, handle);
+        }
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+      write_handle_free (handle);
+    }
+}
+
+static void
+replace_stat_cb (int err,
+                 struct nfs_context *ctx,
+                 void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+      struct nfs_stat_64 *st = data;
+
+      if (!(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION) &&
+          S_ISDIR (st->nfs_mode))
+        {
+          g_vfs_job_failed_literal (job,
+                                    G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
+                                    _("Target file is a directory"));
+        }
+      else if (!(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION) &&
+               !S_ISREG (st->nfs_mode))
+        {
+          g_vfs_job_failed_literal (job,
+                                    G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE,
+                                    _("Target file is not a regular file"));
+        }
+      else
+        {
+          WriteHandle *handle;
+
+          if (op_job->etag)
+            {
+              char *etag;
+
+              etag = create_etag (st->nfs_mtime, st->nfs_mtime_nsec);
+              if (strcmp (etag, op_job->etag))
+                {
+                  g_free (etag);
+                  g_vfs_job_failed_literal (job,
+                                            G_IO_ERROR, G_IO_ERROR_WRONG_ETAG,
+                                            _("The file was externally modified"));
+                  return;
+                }
+              g_free (etag);
+            }
+
+          handle = g_slice_new0 (WriteHandle);
+          handle->mode = st->nfs_mode;
+          handle->uid = st->nfs_uid;
+          handle->gid = st->nfs_gid;
+          handle->nlink = st->nfs_nlink;
+          handle->job = g_object_ref (job);
+
+          nfs_lstat64_async (ctx, op_job->filename, replace_lstat_cb, handle);
+        }
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static void
+replace_create_cb (int err,
+                   struct nfs_context *ctx,
+                   void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+  GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+
+  if (err == 0)
+    {
+      WriteHandle *handle = g_slice_new0 (WriteHandle);
+
+      handle->fh = data;
+      g_vfs_job_open_for_write_set_handle (op_job, handle);
+      g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
+      g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
+      g_vfs_job_succeeded (job);
+    }
+  else if (err == -EEXIST)
+    {
+      nfs_stat64_async (ctx, op_job->filename, replace_stat_cb, job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_replace (GVfsBackend *backend,
+             GVfsJobOpenForWrite *job,
+             const char *filename,
+             const char *etag,
+             gboolean make_backup,
+             GFileCreateFlags flags)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  nfs_create_async (op_backend->ctx,
+                    filename,
+                    O_EXCL,
+                    (flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask,
+                    replace_create_cb, job);
+  return TRUE;
+}
+
+static void
+create_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+      WriteHandle *handle = g_slice_new0 (WriteHandle);
+
+      handle->fh = data;
+      g_vfs_job_open_for_write_set_handle (op_job, handle);
+      g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
+      g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_create (GVfsBackend *backend,
+            GVfsJobOpenForWrite *job,
+            const char *filename,
+            GFileCreateFlags flags)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  nfs_create_async (op_backend->ctx,
+                    filename,
+                    O_EXCL,
+                    (flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask,
+                    create_cb, job);
+  return TRUE;
+}
+
+static void
+write_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err >= 0)
+    {
+      g_vfs_job_write_set_written_size (G_VFS_JOB_WRITE (job), err);
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_write (GVfsBackend *backend,
+           GVfsJobWrite *job,
+           GVfsBackendHandle _handle,
+           char *buffer,
+           gsize buffer_size)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  WriteHandle *handle = _handle;
+  struct nfsfh *fh = handle->fh;
+
+  nfs_write_async (op_backend->ctx, fh, buffer_size, buffer, write_cb, job);
+  return TRUE;
+}
+
+static void
+close_backup_cb (int err,
+                 struct nfs_context *ctx,
+                 void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0)
+    {
+      nfs_rename_async (ctx,
+                        handle->tempname, handle->filename,
+                        generic_cb, job);
+    }
+  else
+    {
+      g_vfs_job_failed_literal (job,
+                                G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
+                                _("Backup file creation failed"));
+    }
+  write_handle_free (handle);
+}
+
+static void
+close_write_cb (int err,
+                struct nfs_context *ctx,
+                void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0)
+    {
+      if (handle->backup_filename)
+        {
+          nfs_rename_async (ctx,
+                            handle->filename, handle->backup_filename,
+                            close_backup_cb, handle);
+        }
+      else
+        {
+          nfs_rename_async (ctx,
+                            handle->tempname, handle->filename,
+                            generic_cb, job);
+          write_handle_free (handle);
+        }
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+      write_handle_free (handle);
+    }
+}
+
+static void
+close_stat_cb (int err,
+               struct nfs_context *ctx,
+               void *data, void *private_data)
+{
+  WriteHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0)
+    {
+      GVfsJobCloseWrite *op_job = G_VFS_JOB_CLOSE_WRITE (job);
+      char *etag;
+      struct nfs_stat_64 *st = data;
+
+      etag = create_etag (st->nfs_mtime, st->nfs_mtime_nsec);
+      g_vfs_job_close_write_set_etag (op_job, etag);
+      g_free (etag);
+    }
+
+  if (handle->tempname)
+    {
+      nfs_close_async (ctx, handle->fh, close_write_cb, handle);
+    }
+  else
+    {
+      nfs_close_async (ctx, handle->fh, generic_cb, job);
+      write_handle_free (handle);
+    }
+}
+
+static gboolean
+try_close_write (GVfsBackend *backend,
+                 GVfsJobCloseWrite *job,
+                 GVfsBackendHandle _handle)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  WriteHandle *handle = _handle;
+
+  handle->job = g_object_ref (job);
+  nfs_fstat64_async (op_backend->ctx, handle->fh, close_stat_cb, handle);
+
+  return TRUE;
+}
+
+static void
+query_info_on_write_cb (int err,
+                        struct nfs_context *ctx,
+                        void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobQueryInfoWrite *op_job = G_VFS_JOB_QUERY_INFO_WRITE (job);
+      struct nfs_stat_64 *st = data;
+
+      set_info_from_stat (op_job->file_info, data, op_job->attribute_matcher);
+      set_type_from_mode (op_job->file_info, st->nfs_mode);
+
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_query_info_on_write (GVfsBackend *backend,
+                        GVfsJobQueryInfoWrite *job,
+                        GVfsBackendHandle _handle,
+                        GFileInfo *info,
+                        GFileAttributeMatcher *attribute_matcher)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  WriteHandle *handle = _handle;
+  struct nfsfh *fh = handle->fh;
+
+  nfs_fstat64_async (op_backend->ctx, fh, query_info_on_write_cb, job);
+  return TRUE;
+}
+
+static void
+seek_on_write_cb (int err,
+                  struct nfs_context *ctx,
+                  void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err >= 0)
+    {
+      GVfsJobSeekWrite *op_job = G_VFS_JOB_SEEK_WRITE (job);
+      uint64_t *pos = data;
+
+      g_vfs_job_seek_write_set_offset (op_job, *pos);
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_seek_on_write (GVfsBackend *backend,
+                   GVfsJobSeekWrite *job,
+                   GVfsBackendHandle _handle,
+                   goffset offset,
+                   GSeekType type)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  WriteHandle *handle = _handle;
+  struct nfsfh *fh = handle->fh;
+
+  nfs_lseek_async (op_backend->ctx,
+                   fh, offset, gvfs_seek_type_to_lseek (type),
+                   seek_on_write_cb, job);
+  return TRUE;
+}
+
+static gboolean
+try_truncate (GVfsBackend *backend,
+              GVfsJobTruncate *job,
+              GVfsBackendHandle _handle,
+              goffset size)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  WriteHandle *handle = _handle;
+  struct nfsfh *fh = handle->fh;
+
+  nfs_ftruncate_async (op_backend->ctx, fh, size, generic_cb, job);
+  return TRUE;
+}
+
+static void
+query_fs_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobQueryFsInfo *op_job = G_VFS_JOB_QUERY_FS_INFO (job);
+      struct statvfs *stat = data;
+
+      /* If free and available are both 0, treat it like the size information
+       * is missing.
+       */
+      if (stat->f_bfree || stat->f_bavail)
+        {
+          g_file_info_set_attribute_uint64 (op_job->file_info,
+                                            G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
+                                            stat->f_frsize * stat->f_bavail);
+          g_file_info_set_attribute_uint64 (op_job->file_info,
+                                            G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
+                                            stat->f_frsize * stat->f_blocks);
+          g_file_info_set_attribute_uint64 (op_job->file_info,
+                                            G_FILE_ATTRIBUTE_FILESYSTEM_USED,
+                                            stat->f_frsize * (stat->f_blocks - stat->f_bfree));
+        }
+      g_file_info_set_attribute_boolean (op_job->file_info,
+                                         G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
+                                         stat->f_flag & ST_RDONLY);
+
+      g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_query_fs_info (GVfsBackend *backend,
+                   GVfsJobQueryFsInfo *job,
+                   const char *filename,
+                   GFileInfo *info,
+                   GFileAttributeMatcher *matcher)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  g_file_info_set_attribute_string (info,
+                                    G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "nfs");
+
+  if (g_file_attribute_matcher_matches (matcher,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) ||
+      g_file_attribute_matcher_matches (matcher,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
+      g_file_attribute_matcher_matches (matcher,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_USED) ||
+      g_file_attribute_matcher_matches (matcher,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
+    {
+      nfs_statvfs_async (op_backend->ctx, filename, query_fs_cb, job);
+    }
+  else
+    {
+      g_vfs_job_succeeded (G_VFS_JOB (job));
+    }
+
+  return TRUE;
+}
+
+typedef struct
+{
+  GSList *readlink_list;
+  GSList *symlink_list;
+  GSList *access_list;
+  gboolean requires_access;
+  GVfsJobEnumerate *op_job;
+} EnumerateHandle;
+
+static void enumerate_continue (EnumerateHandle *handle,
+                                struct nfs_context *ctx);
+
+static void
+enumerate_access_cb (int err,
+                     struct nfs_context *ctx,
+                     void *data, void *private_data)
+{
+  EnumerateHandle *handle = private_data;
+  GFileInfo *info = handle->access_list->data;
+
+  if (err >= 0)
+    {
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, err & R_OK);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, err & W_OK);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, err & X_OK);
+    }
+
+  g_vfs_job_enumerate_add_info (handle->op_job, info);
+  g_object_unref (info);
+
+  handle->access_list = g_slist_delete_link (handle->access_list,
+                                             handle->access_list);
+
+  enumerate_continue (handle, ctx);
+}
+
+static void
+enumerate_stat_cb (int err,
+                   struct nfs_context *ctx,
+                   void *data, void *private_data)
+{
+  EnumerateHandle *handle = private_data;
+  GFileInfo *info = handle->symlink_list->data;
+
+  if (err == 0)
+    {
+      struct nfs_stat_64 *st = data;
+      char *mimetype;
+      GFileInfo *new_info;
+
+      new_info = g_file_info_new ();
+      set_info_from_stat (new_info, st, handle->op_job->attribute_matcher);
+      mimetype = set_type_from_mode (new_info, st->nfs_mode);
+      set_name_info (new_info,
+                     mimetype,
+                     g_file_info_get_name (info),
+                     handle->op_job->attribute_matcher);
+      g_file_info_set_is_symlink (new_info, TRUE);
+
+      if (g_file_attribute_matcher_matches (handle->op_job->attribute_matcher,
+                                            G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
+        g_file_info_set_symlink_target (new_info,
+                                        g_file_info_get_symlink_target (info));
+
+      g_object_unref (info);
+      info = new_info;
+    }
+
+  if (handle->requires_access)
+    handle->access_list = g_slist_prepend (handle->access_list, info);
+  else
+    {
+      g_vfs_job_enumerate_add_info (handle->op_job, info);
+      g_object_unref (info);
+    }
+
+  handle->symlink_list = g_slist_delete_link (handle->symlink_list,
+                                              handle->symlink_list);
+
+  enumerate_continue (handle, ctx);
+}
+
+static void
+enumerate_readlink_cb (int err,
+                       struct nfs_context *ctx,
+                       void *data, void *private_data)
+{
+  EnumerateHandle *handle = private_data;
+  GFileInfo *info = handle->readlink_list->data;
+
+  if (err == 0)
+    g_file_info_set_symlink_target (info, data);
+
+  if (!(handle->op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
+    handle->symlink_list = g_slist_prepend (handle->symlink_list, info);
+  else if (handle->requires_access)
+    handle->access_list = g_slist_prepend (handle->access_list, info);
+  else
+    {
+      g_vfs_job_enumerate_add_info (handle->op_job, info);
+      g_object_unref (info);
+    }
+
+  handle->readlink_list = g_slist_delete_link (handle->readlink_list,
+                                              handle->readlink_list);
+
+  enumerate_continue (handle, ctx);
+}
+
+static char *
+build_filename (GVfsJobEnumerate *op_job, GFileInfo *item)
+{
+  const char *filename = g_file_info_get_name (item);
+  return g_build_filename (op_job->filename, filename, NULL);
+}
+
+static void
+enumerate_continue (EnumerateHandle *handle, struct nfs_context *ctx)
+{
+  if (handle->readlink_list || handle->symlink_list || handle->access_list)
+    {
+      char *path;
+
+      if (handle->readlink_list)
+        {
+          path = build_filename (handle->op_job, handle->readlink_list->data);
+          nfs_readlink_async (ctx, path, enumerate_readlink_cb, handle);
+        }
+      else if (handle->symlink_list)
+        {
+          path = build_filename (handle->op_job, handle->symlink_list->data);
+          nfs_stat64_async (ctx, path, enumerate_stat_cb, handle);
+        }
+      else if (handle->access_list)
+        {
+          path = build_filename (handle->op_job, handle->access_list->data);
+          nfs_access2_async (ctx, path, enumerate_access_cb, handle);
+        }
+
+      g_free (path);
+    }
+  else
+    {
+      g_vfs_job_enumerate_done (handle->op_job);
+    }
+}
+
+static void
+enumerate_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  EnumerateHandle *handle = private_data;
+  GVfsJob *job = G_VFS_JOB (handle->op_job);
+
+  if (err == 0)
+    {
+      GVfsJobEnumerate *op_job = handle->op_job;
+      struct nfsdir *dir = data;
+      struct nfsdirent *d;
+
+      g_vfs_job_succeeded (job);
+
+      handle->requires_access =
+          g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                            G_FILE_ATTRIBUTE_ACCESS_CAN_READ) ||
+          g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                            G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) ||
+          g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                            G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);
+
+      while ((d = nfs_readdir (ctx, dir)))
+        {
+          GFileInfo *info;
+          GFileType type = G_FILE_TYPE_UNKNOWN;
+          char *etag, *mimetype = NULL;
+
+          if (!strcmp (d->name, ".") || !strcmp (d->name, ".."))
+            continue;
+
+          info = g_file_info_new ();
+          g_file_info_set_size (info, d->size);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, d->uid);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, d->gid);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, d->mode);
+          g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, d->inode);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_NLINK, d->nlink);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE, d->dev);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_RDEV, d->rdev);
+          g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, d->atime.tv_sec);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, d->atime.tv_usec);
+          g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, d->mtime.tv_sec);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, d->mtime.tv_usec);
+          g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED, d->ctime.tv_sec);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, d->ctime.tv_usec);
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE, d->blksize);
+          g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_BLOCKS, d->blocks);
+          g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, d->used);
+
+          etag = create_etag (d->mtime.tv_sec, d->mtime_nsec);
+          g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
+          g_free (etag);
+
+          switch (d->type)
+            {
+            case NF3REG:
+              type = G_FILE_TYPE_REGULAR;
+              break;
+            case NF3DIR:
+              type = G_FILE_TYPE_DIRECTORY;
+              mimetype = "inode/directory";
+              break;
+            case NF3BLK:
+              type = G_FILE_TYPE_SPECIAL;
+              mimetype = "inode/blockdevice";
+              break;
+            case NF3CHR:
+              type = G_FILE_TYPE_SPECIAL;
+              mimetype = "inode/chardevice";
+              break;
+            case NF3SOCK:
+              type = G_FILE_TYPE_SPECIAL;
+              mimetype = "inode/socket";
+              break;
+            case NF3FIFO:
+              type = G_FILE_TYPE_SPECIAL;
+              mimetype = "inode/fifo";
+              break;
+            case NF3LNK:
+              type = G_FILE_TYPE_SYMBOLIC_LINK;
+              mimetype = "inode/symlink";
+              g_file_info_set_is_symlink (info, TRUE);
+              break;
+            }
+
+          g_file_info_set_file_type (info, type);
+          set_name_info (info, mimetype, d->name, op_job->attribute_matcher);
+
+          if (d->type == NF3LNK &&
+              g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                                G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
+            {
+              handle->readlink_list = g_slist_prepend (handle->readlink_list,
+                                                       info);
+              continue;
+            }
+
+          if (d->type == NF3LNK &&
+              !(op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
+            {
+              handle->symlink_list = g_slist_prepend (handle->symlink_list,
+                                                      info);
+              continue;
+            }
+
+          if (handle->requires_access)
+            {
+              handle->access_list = g_slist_prepend (handle->access_list,
+                                                     info);
+              continue;
+            }
+
+          g_vfs_job_enumerate_add_info (op_job, info);
+          g_object_unref (info);
+        }
+
+      nfs_closedir (ctx, dir);
+      enumerate_continue (handle, ctx);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_enumerate (GVfsBackend *backend,
+               GVfsJobEnumerate *job,
+               const char *filename,
+               GFileAttributeMatcher *attribute_matcher,
+               GFileQueryInfoFlags flags)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  EnumerateHandle *handle;
+
+  handle = g_slice_new0 (EnumerateHandle);
+  handle->op_job = job;
+  nfs_opendir_async (op_backend->ctx, filename, enumerate_cb, handle);
+
+  return TRUE;
+}
+
+static void stat_readlink_cb (int err,
+                              struct nfs_context *ctx,
+                              void *data, void *private_data);
+
+static void
+stat_access_cb (int err,
+                struct nfs_context *ctx,
+                void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+  GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job);
+  GFileInfo *info = op_job->file_info;
+
+  if (err >= 0)
+    {
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, err & R_OK);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, err & W_OK);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, err & X_OK);
+    }
+
+  if (g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                        G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
+    {
+      if (!(op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) ||
+          g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK)
+        nfs_readlink_async (ctx, op_job->filename, stat_readlink_cb, job);
+      else
+        g_vfs_job_succeeded (job);
+    }
+  else
+    {
+      g_vfs_job_succeeded (job);
+    }
+}
+
+static void
+stat_readlink_cb (int err,
+                  struct nfs_context *ctx,
+                  void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+  GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job);
+  GFileInfo *info = op_job->file_info;
+
+  if (err == 0)
+    g_file_info_set_symlink_target (info, data);
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+stat_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job);
+      GFileInfo *info = op_job->file_info;
+      struct nfs_stat_64 *st = data;
+      char *basename, *mimetype, *etag;
+
+      set_info_from_stat (info, st, op_job->attribute_matcher);
+
+      etag = create_etag (st->nfs_mtime, st->nfs_mtime_nsec);
+      g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
+      g_free (etag);
+
+      mimetype = set_type_from_mode (info, st->nfs_mode);
+
+      if (!strcmp (op_job->filename, "/"))
+        {
+          GMountSpec *mount_spec = g_vfs_backend_get_mount_spec (op_job->backend);
+          basename = g_path_get_basename (mount_spec->mount_prefix);
+        }
+      else
+        {
+          basename = g_path_get_basename (op_job->filename);
+        }
+      set_name_info (info, mimetype, basename, op_job->attribute_matcher);
+      g_free (basename);
+
+      if (g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                            G_FILE_ATTRIBUTE_ACCESS_CAN_READ) ||
+          g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                            G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) ||
+          g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                            G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+        {
+          nfs_access2_async (ctx, op_job->filename, stat_access_cb, job);
+        }
+      else if (g_file_attribute_matcher_matches (op_job->attribute_matcher,
+                                            G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
+        {
+          if (!(op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) ||
+              S_ISLNK (st->nfs_mode))
+            nfs_readlink_async (ctx, op_job->filename, stat_readlink_cb, job);
+          else
+            g_vfs_job_succeeded (job);
+        }
+      else
+        {
+          g_vfs_job_succeeded (job);
+        }
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static void
+stat_is_symlink_cb (int err,
+                    struct nfs_context *ctx,
+                    void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job);
+      struct nfs_stat_64 *st = data;
+
+      g_file_info_set_is_symlink (op_job->file_info, S_ISLNK (st->nfs_mode));
+      nfs_stat64_async (ctx, op_job->filename, stat_cb, job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_query_info (GVfsBackend *backend,
+                GVfsJobQueryInfo *job,
+                const char *filename,
+                GFileQueryInfoFlags flags,
+                GFileInfo *info,
+                GFileAttributeMatcher *matcher)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+    nfs_lstat64_async (op_backend->ctx, filename, stat_cb, job);
+  else
+    nfs_lstat64_async (op_backend->ctx, filename, stat_is_symlink_cb, job);
+
+  return TRUE;
+}
+
+static gboolean
+try_set_display_name (GVfsBackend *backend,
+                      GVfsJobSetDisplayName *job,
+                      const char *filename,
+                      const char *display_name)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+  char *dirname, *basename, *new_name;
+
+  dirname = g_path_get_dirname (filename);
+  basename = g_filename_from_utf8 (display_name, -1, NULL, NULL, NULL);
+  if (basename == NULL)
+    basename = g_strdup (display_name);
+  new_name = g_build_filename (dirname, basename, NULL);
+  g_free (dirname);
+  g_free (basename);
+
+  g_vfs_job_set_display_name_set_new_path (job, new_name);
+
+  nfs_rename_async (op_backend->ctx, filename, new_name, generic_cb, job);
+  g_free (new_name);
+
+  return TRUE;
+}
+
+static gboolean
+try_query_settable_attributes (GVfsBackend *backend,
+                               GVfsJobQueryAttributes *job,
+                               const char *filename)
+{
+  GFileAttributeInfoList *list;
+
+  list = g_file_attribute_info_list_new ();
+
+  g_file_attribute_info_list_add (list,
+                                  G_FILE_ATTRIBUTE_TIME_ACCESS,
+                                  G_FILE_ATTRIBUTE_TYPE_UINT64,
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+  g_file_attribute_info_list_add (list,
+                                  G_FILE_ATTRIBUTE_TIME_ACCESS_USEC,
+                                  G_FILE_ATTRIBUTE_TYPE_UINT32,
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+  g_file_attribute_info_list_add (list,
+                                  G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                  G_FILE_ATTRIBUTE_TYPE_UINT64,
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+  g_file_attribute_info_list_add (list,
+                                  G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
+                                  G_FILE_ATTRIBUTE_TYPE_UINT32,
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+  g_file_attribute_info_list_add (list,
+                                  G_FILE_ATTRIBUTE_UNIX_UID,
+                                  G_FILE_ATTRIBUTE_TYPE_UINT32,
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+  g_file_attribute_info_list_add (list,
+                                  G_FILE_ATTRIBUTE_UNIX_GID,
+                                  G_FILE_ATTRIBUTE_TYPE_UINT32,
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+  g_file_attribute_info_list_add (list,
+                                  G_FILE_ATTRIBUTE_UNIX_MODE,
+                                  G_FILE_ATTRIBUTE_TYPE_UINT32,
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
+                                  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+
+  g_vfs_job_query_attributes_set_list (job, list);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+  g_file_attribute_info_list_unref (list);
+
+  return TRUE;
+}
+
+static void
+set_mod_cb (int err, struct nfs_context *ctx, void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobSetAttribute *op_job = G_VFS_JOB_SET_ATTRIBUTE (job);
+      struct nfs_stat_64 *st = data;
+      gpointer *value_p = _g_dbus_attribute_as_pointer (op_job->type,
+                                                        &op_job->value);
+      struct timeval tv[2];
+
+      if (!strcmp (op_job->attribute, G_FILE_ATTRIBUTE_TIME_ACCESS))
+        tv[0].tv_sec = *(guint64 *)value_p;
+      else
+        tv[0].tv_sec = st->nfs_atime;
+      if (!strcmp (op_job->attribute, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC))
+        tv[0].tv_usec = *(guint32 *)value_p;
+      else
+        tv[0].tv_usec = st->nfs_atime_nsec / 1000;
+
+      if (!strcmp (op_job->attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+        tv[1].tv_sec = *(guint64 *)value_p;
+      else
+        tv[1].tv_sec = st->nfs_mtime;
+      if (!strcmp (op_job->attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC))
+        tv[1].tv_usec = *(guint32 *)value_p;
+      else
+        tv[1].tv_usec = st->nfs_mtime_nsec / 1000;
+
+      if (op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+        nfs_lutimes_async (ctx, op_job->filename, tv, generic_cb, job);
+      else
+        nfs_utimes_async (ctx, op_job->filename, tv, generic_cb, job);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_set_attribute (GVfsBackend *backend,
+                   GVfsJobSetAttribute *job,
+                   const char *filename,
+                   const char *attribute,
+                   GFileAttributeType type,
+                   gpointer value_p,
+                   GFileQueryInfoFlags flags)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  if (!strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) ||
+      !strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+    {
+      if (type != G_FILE_ATTRIBUTE_TYPE_UINT64)
+        goto error;
+
+      if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+        nfs_lstat64_async (op_backend->ctx, filename, set_mod_cb, job);
+      else
+        nfs_stat64_async (op_backend->ctx, filename, set_mod_cb, job);
+    }
+  else if (!strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC) ||
+           !strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC))
+    {
+      if (type != G_FILE_ATTRIBUTE_TYPE_UINT32)
+        goto error;
+
+      if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+        nfs_lstat64_async (op_backend->ctx, filename, set_mod_cb, job);
+      else
+        nfs_stat64_async (op_backend->ctx, filename, set_mod_cb, job);
+    }
+  else if (!strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_UID))
+    {
+      if (type != G_FILE_ATTRIBUTE_TYPE_UINT32)
+        goto error;
+
+      if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+        nfs_lchown_async (op_backend->ctx,
+                          filename,
+                          *(guint32 *)value_p, -1,
+                          generic_cb, job);
+      else
+        nfs_chown_async (op_backend->ctx,
+                         filename,
+                         *(guint32 *)value_p, -1,
+                         generic_cb, job);
+    }
+  else if (!strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_GID))
+    {
+      if (type != G_FILE_ATTRIBUTE_TYPE_UINT32)
+        goto error;
+
+      if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+        nfs_lchown_async (op_backend->ctx,
+                          filename,
+                          -1, *(guint32 *)value_p,
+                          generic_cb, job);
+      else
+        nfs_chown_async (op_backend->ctx,
+                         filename,
+                         -1, *(guint32 *)value_p,
+                         generic_cb, job);
+    }
+  else if (!strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE))
+    {
+      if (type != G_FILE_ATTRIBUTE_TYPE_UINT32)
+        goto error;
+
+      if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+        nfs_lchmod_async (op_backend->ctx,
+                          filename,
+                          *(guint32 *)value_p & 0777,
+                          generic_cb, job);
+      else
+        nfs_chmod_async (op_backend->ctx,
+                         filename,
+                         *(guint32 *)value_p & 0777,
+                         generic_cb, job);
+    }
+  else
+    {
+      g_vfs_job_failed_literal (G_VFS_JOB (job),
+                                G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                                _("Operation unsupported"));
+    }
+
+  return TRUE;
+
+error:
+  g_vfs_job_failed_literal (G_VFS_JOB (job),
+                            G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                            _("Invalid attribute type"));
+  return TRUE;
+}
+
+static gboolean
+try_unmount (GVfsBackend *backend,
+             GVfsJobUnmount *job,
+             GMountUnmountFlags flags,
+             GMountSource *mount_source)
+{
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  return TRUE;
+}
+
+typedef struct
+{
+  GVfsJob *job;
+  gboolean source_is_dir;
+  uint64_t file_size;
+} MoveHandle;
+
+static void
+move_rename_cb (int err,
+                struct nfs_context *ctx,
+                void *data, void *private_data)
+{
+  MoveHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+  uint64_t file_size = handle->file_size;
+
+  g_slice_free (MoveHandle, handle);
+
+  if (err == 0)
+    {
+      g_vfs_job_progress_callback (file_size, file_size, job);
+      g_vfs_job_succeeded (job);
+    }
+  else if (err == -EXDEV)
+    {
+      g_vfs_job_failed_literal (job,
+                                G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                                _("Not supported"));
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static void
+move_remove_cb (int err,
+                struct nfs_context *ctx,
+                void *data, void *private_data)
+{
+  MoveHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+
+  if (err == 0)
+    {
+      GVfsJobMove *op_job = G_VFS_JOB_MOVE (job);
+
+      nfs_rename_async (ctx,
+                        op_job->source, op_job->destination,
+                        move_rename_cb, handle);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+      g_slice_free (MoveHandle, handle);
+    }
+}
+
+static void
+move_stat_dest_cb (int err,
+                   struct nfs_context *ctx,
+                   void *data, void *private_data)
+{
+  MoveHandle *handle = private_data;
+  GVfsJob *job = handle->job;
+  GVfsJobMove *op_job = G_VFS_JOB_MOVE (job);
+
+  if (err == 0)
+    {
+      struct nfs_stat_64 *st = data;
+
+      if (op_job->flags & G_FILE_COPY_OVERWRITE)
+        {
+          if (S_ISDIR (st->nfs_mode))
+            {
+              if (handle->source_is_dir)
+                {
+                  g_vfs_job_failed_literal (job,
+                                            G_IO_ERROR, G_IO_ERROR_WOULD_MERGE,
+                                            _("Can't move directory over directory"));
+                }
+              else
+                {
+                  g_vfs_job_failed_literal (job,
+                                            G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
+                                            _("File is directory"));
+                }
+              g_slice_free (MoveHandle, handle);
+              return;
+            }
+        }
+      else
+        {
+         g_vfs_job_failed (job,
+                           G_IO_ERROR, G_IO_ERROR_EXISTS,
+                           _("Target file already exists"));
+          g_slice_free (MoveHandle, handle);
+          return;
+        }
+
+      if (handle->source_is_dir && op_job->flags & G_FILE_COPY_OVERWRITE)
+        {
+          nfs_unlink_async (ctx, op_job->destination, move_remove_cb, handle);
+          return;
+        }
+    }
+
+  nfs_rename_async (ctx,
+                    op_job->source, op_job->destination,
+                    move_rename_cb, handle);
+}
+
+static void
+move_stat_source_cb (int err,
+                     struct nfs_context *ctx,
+                     void *data, void *private_data)
+{
+  GVfsJob *job = G_VFS_JOB (private_data);
+
+  if (err == 0)
+    {
+      GVfsJobMove *op_job = G_VFS_JOB_MOVE (job);
+      struct nfs_stat_64 *st = data;
+      MoveHandle *handle;
+
+      handle = g_slice_new0 (MoveHandle);
+      handle->job = job;
+      handle->source_is_dir = S_ISDIR (st->nfs_mode);
+      handle->file_size = st->nfs_size;
+
+      nfs_lstat64_async (ctx, op_job->destination, move_stat_dest_cb, handle);
+    }
+  else
+    {
+      g_vfs_job_failed_from_errno (job, -err);
+    }
+}
+
+static gboolean
+try_move (GVfsBackend *backend,
+          GVfsJobMove *job,
+          const char *source,
+          const char *destination,
+          GFileCopyFlags flags,
+          GFileProgressCallback progress_callback,
+          gpointer progress_callback_data)
+{
+  GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend);
+
+  if (flags & G_FILE_COPY_BACKUP)
+    {
+      g_vfs_job_failed_literal (G_VFS_JOB (job),
+                                G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                                _("Not supported"));
+      return TRUE;
+    }
+
+  nfs_lstat64_async (op_backend->ctx, source, move_stat_source_cb, job);
+
+  return TRUE;
+}
+
+static void
+g_vfs_backend_nfs_class_init (GVfsBackendNfsClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
+
+  gobject_class->finalize = g_vfs_backend_nfs_finalize;
+
+  backend_class->mount = do_mount;
+  backend_class->try_open_for_read = try_open_for_read;
+  backend_class->try_read = try_read;
+  backend_class->try_query_info_on_read = try_query_info_on_read;
+  backend_class->try_seek_on_read = try_seek_on_read;
+  backend_class->try_close_read = try_close_read;
+  backend_class->try_make_directory = try_make_directory;
+  backend_class->try_delete = try_delete;
+  backend_class->try_make_symlink = try_make_symlink;
+  backend_class->try_create = try_create;
+  backend_class->try_append_to = try_append_to;
+  backend_class->try_replace = try_replace;
+  backend_class->try_write = try_write;
+  backend_class->try_query_info_on_write = try_query_info_on_write;
+  backend_class->try_seek_on_write = try_seek_on_write;
+  backend_class->try_truncate = try_truncate;
+  backend_class->try_close_write = try_close_write;
+  backend_class->try_query_fs_info = try_query_fs_info;
+  backend_class->try_enumerate = try_enumerate;
+  backend_class->try_query_info = try_query_info;
+  backend_class->try_set_display_name = try_set_display_name;
+  backend_class->try_query_settable_attributes = try_query_settable_attributes;
+  backend_class->try_set_attribute = try_set_attribute;
+  backend_class->try_unmount = try_unmount;
+  backend_class->try_move = try_move;
+}
diff --git a/daemon/gvfsbackendnfs.h b/daemon/gvfsbackendnfs.h
new file mode 100644
index 0000000..169ad75
--- /dev/null
+++ b/daemon/gvfsbackendnfs.h
@@ -0,0 +1,48 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2014 Ross Lagerwall
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __G_VFS_BACKEND_NFS_H__
+#define __G_VFS_BACKEND_NFS_H__
+
+#include <gvfsbackend.h>
+#include <gmountspec.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_BACKEND_NFS         (g_vfs_backend_nfs_get_type ())
+#define G_VFS_BACKEND_NFS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_BACKEND_NFS, 
GVfsBackendNfs))
+#define G_VFS_BACKEND_NFS_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_BACKEND_NFS, 
GVfsBackendNfsClass))
+#define G_VFS_IS_BACKEND_NFS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_BACKEND_NFS))
+#define G_VFS_IS_BACKEND_NFS_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_BACKEND_NFS))
+#define G_VFS_BACKEND_NFS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_BACKEND_NFS, 
GVfsBackendNfsClass))
+
+typedef struct _GVfsBackendNfs        GVfsBackendNfs;
+typedef struct _GVfsBackendNfsClass   GVfsBackendNfsClass;
+
+struct _GVfsBackendNfsClass
+{
+  GVfsBackendClass parent_class;
+};
+
+GType g_vfs_backend_nfs_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __G_VFS_BACKEND_NFS_H__ */
diff --git a/daemon/nfs.mount.in b/daemon/nfs.mount.in
new file mode 100644
index 0000000..e56cf01
--- /dev/null
+++ b/daemon/nfs.mount.in
@@ -0,0 +1,5 @@
+[Mount]
+Type=nfs
+Exec= libexecdir@/gvfsd-nfs
+AutoMount=false
+Scheme=nfs


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