hook gvariant vectors up to kdbus
[platform/upstream/glib.git] / gio / glocalfile.c
index aa009e7..846eedb 100644 (file)
  * 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., 59 Temple Place, Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
  *
  * Author: Alexander Larsson <alexl@redhat.com>
  */
 
-#include <config.h>
+#include "config.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <string.h>
 #include <errno.h>
 #include <fcntl.h>
-#ifdef HAVE_UNISTD_H
+#if G_OS_UNIX
+#include <dirent.h>
 #include <unistd.h>
 #endif
 
 #define O_BINARY 0
 #endif
 
-#if defined(HAVE_STATFS) && defined(HAVE_STATVFS)
-/* Some systems have both statfs and statvfs, pick the
-   most "native" for these */
-# if defined(sun) && defined(__SVR4)
-   /* on solaris, 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 "gfileattribute.h"
 #include "glocalfile.h"
 #include "glocalfileinfo.h"
 #include "glocalfileenumerator.h"
 #include "glocalfileinputstream.h"
 #include "glocalfileoutputstream.h"
+#include "glocalfileiostream.h"
 #include "glocaldirectorymonitor.h"
 #include "glocalfilemonitor.h"
 #include "gmountprivate.h"
+#include "gunixmounts.h"
+#include "gioerror.h"
 #include <glib/gstdio.h>
 #include "glibintl.h"
+#ifdef G_OS_UNIX
+#include "glib-unix.h"
+#endif
+#include "glib-private.h"
+
+#include "glib-private.h"
 
 #ifdef G_OS_WIN32
-#define _WIN32_WINNT 0x0500
 #include <windows.h>
 #include <io.h>
 #include <direct.h>
 #endif
 #endif
 
-#include "gioalias.h"
 
 static void g_local_file_file_iface_init (GFileIface *iface);
 
 static GFileAttributeInfoList *local_writable_attributes = NULL;
-static GFileAttributeInfoList *local_writable_namespaces = NULL;
+static /* GFileAttributeInfoList * */ gsize local_writable_namespaces = 0;
 
 struct _GLocalFile
 {
@@ -130,9 +115,8 @@ g_local_file_finalize (GObject *object)
   local = G_LOCAL_FILE (object);
 
   g_free (local->filename);
-  
-  if (G_OBJECT_CLASS (g_local_file_parent_class)->finalize)
-    (*G_OBJECT_CLASS (g_local_file_parent_class)->finalize) (object);
+
+  G_OBJECT_CLASS (g_local_file_parent_class)->finalize (object);
 }
 
 static void
@@ -155,7 +139,7 @@ g_local_file_class_init (GLocalFileClass *klass)
                                  G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
                                  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
   
-#ifdef HAVE_CHOWN
+#ifdef G_OS_UNIX
   g_file_attribute_info_list_add (list,
                                  G_FILE_ATTRIBUTE_UNIX_UID,
                                  G_FILE_ATTRIBUTE_TYPE_UINT32,
@@ -177,11 +161,16 @@ g_local_file_class_init (GLocalFileClass *klass)
   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);
+  /* When copying, the target file is accessed. Replicating
+   * the source access time does not make sense in this case.
+   */
   g_file_attribute_info_list_add (list,
                                  G_FILE_ATTRIBUTE_TIME_ACCESS,
                                  G_FILE_ATTRIBUTE_TYPE_UINT64,
@@ -193,24 +182,6 @@ g_local_file_class_init (GLocalFileClass *klass)
 #endif
 
   local_writable_attributes = list;
-
-  /* Writable namespaces: */
-  
-  list = g_file_attribute_info_list_new ();
-
-#ifdef HAVE_XATTR
-  g_file_attribute_info_list_add (list,
-                                 "xattr",
-                                 G_FILE_ATTRIBUTE_TYPE_STRING,
-                                 G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
-                                 G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
-  g_file_attribute_info_list_add (list,
-                                 "xattr-sys",
-                                 G_FILE_ATTRIBUTE_TYPE_STRING,
-                                 G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
-#endif
-
-  local_writable_namespaces = list;
 }
 
 static void
@@ -218,6 +189,11 @@ g_local_file_init (GLocalFile *local)
 {
 }
 
+const char *
+_g_local_file_get_filename (GLocalFile *file)
+{
+  return file->filename;
+}
 
 static char *
 canonicalize_filename (const char *filename)
@@ -237,6 +213,15 @@ canonicalize_filename (const char *filename)
 
   start = (char *)g_path_skip_root (canon);
 
+  if (start == NULL)
+    {
+      /* This shouldn't really happen, as g_get_current_dir() should
+        return an absolute pathname, but bug 573843 shows this is
+        not always happening */
+      g_free (canon);
+      return g_build_filename (G_DIR_SEPARATOR_S, filename, NULL);
+    }
+  
   /* POSIX allows double slashes at the start to
    * mean something special (as does windows too).
    * So, "//" != "/", but more than two slashes
@@ -254,6 +239,11 @@ canonicalize_filename (const char *filename)
       start -= i;
       memmove (start, start+i, strlen (start+i)+1);
     }
+
+  /* Make sure we're using the canonical dir separator */
+  p++;
+  while (p < start && G_IS_DIR_SEPARATOR (*p))
+    *p++ = G_DIR_SEPARATOR;
   
   p = start;
   while (*p != 0)
@@ -304,12 +294,6 @@ canonicalize_filename (const char *filename)
   return canon;
 }
 
-/**
- * _g_local_file_new:
- * @filename: filename of the file to create.
- * 
- * Returns: new local #GFile.
- **/
 GFile *
 _g_local_file_new (const char *filename)
 {
@@ -424,11 +408,13 @@ g_local_file_get_parse_name (GFile *file)
                                             charset, "UTF-8", NULL, NULL, NULL);
          
          if (roundtripped_filename == NULL ||
-             strcmp (utf8_filename, roundtripped_filename) != 0)
+             strcmp (filename, roundtripped_filename) != 0)
            {
              g_free (utf8_filename);
              utf8_filename = NULL;
            }
+
+         g_free (roundtripped_filename);
        }
     }
 
@@ -442,6 +428,23 @@ g_local_file_get_parse_name (GFile *file)
     }
   else
     {
+#ifdef G_OS_WIN32
+      char *dup_filename, *p, *backslash;
+
+      /* Turn backslashes into forward slashes like
+       * g_filename_to_uri() would do (but we can't use that because
+       * it doesn't output IRIs).
+       */
+      dup_filename = g_strdup (filename);
+      filename = p = dup_filename;
+
+      while ((backslash = strchr (p, '\\')) != NULL)
+       {
+         *backslash = '/';
+         p = backslash + 1;
+       }
+#endif
+
       escaped_path = g_uri_escape_string (filename,
                                          G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT "/",
                                          TRUE);
@@ -451,7 +454,9 @@ g_local_file_get_parse_name (GFile *file)
                                NULL);
       
       g_free (escaped_path);
-
+#ifdef G_OS_WIN32
+      g_free (dup_filename);
+#endif
       if (free_utf8_filename)
        g_free (utf8_filename);
     }
@@ -578,7 +583,7 @@ g_local_file_enumerate_children (GFile                *file,
                                 GError              **error)
 {
   GLocalFile *local = G_LOCAL_FILE (file);
-  return _g_local_file_enumerator_new (local->filename,
+  return _g_local_file_enumerator_new (local,
                                       attributes, flags,
                                       cancellable, error);
 }
@@ -588,7 +593,7 @@ g_local_file_get_child_for_display_name (GFile        *file,
                                         const char   *display_name,
                                         GError      **error)
 {
-  GFile *parent, *new_file;
+  GFile *new_file;
   char *basename;
 
   basename = g_filename_from_utf8 (display_name, -1, NULL, NULL, NULL);
@@ -600,30 +605,33 @@ g_local_file_get_child_for_display_name (GFile        *file,
       return NULL;
     }
 
-  parent = g_file_get_parent (file);
   new_file = g_file_get_child (file, basename);
-  g_object_unref (parent);
   g_free (basename);
   
   return new_file;
 }
 
-#ifdef USE_STATFS
+#if defined(USE_STATFS) && !defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
 static const char *
 get_fs_type (long f_type)
 {
-
   /* filesystem ids taken from linux manpage */
   switch (f_type) 
     {
     case 0xadf5:
       return "adfs";
+    case 0x5346414f:
+      return "afs";
+    case 0x0187:
+      return "autofs";
     case 0xADFF:
       return "affs";
     case 0x42465331:
       return "befs";
     case 0x1BADFACE:
       return "bfs";
+    case 0x9123683E:
+      return "btrfs";
     case 0xFF534D42:
       return "cifs";
     case 0x73757245:
@@ -641,7 +649,7 @@ get_fs_type (long f_type)
     case 0xEF51:
       return "ext2";
     case 0xEF53:
-      return "ext3";
+      return "ext3/ext4";
     case 0x4244:
       return "hfs";
     case 0xF995E849:
@@ -682,6 +690,8 @@ get_fs_type (long f_type)
       return "romfs";
     case 0x517B:
       return "smb";
+    case 0x73717368:
+      return "squashfs";
     case 0x012FF7B6:
       return "sysv2";
     case 0x012FF7B5:
@@ -702,6 +712,8 @@ get_fs_type (long f_type)
       return "xfs";
     case 0x012FD16D:
       return "xiafs";
+    case 0x52345362:
+      return "reiser4";
     default:
       return NULL;
     }
@@ -736,7 +748,7 @@ get_mount_info (GFileInfo             *fs_info,
                const char            *path,
                GFileAttributeMatcher *matcher)
 {
-  struct stat buf;
+  GStatBuf buf;
   gboolean got_info;
   gpointer info_as_ptr;
   guint mount_info;
@@ -773,7 +785,7 @@ get_mount_info (GFileInfo             *fs_info,
 
       mountpoint = find_mountpoint_for (path, buf.st_dev);
       if (mountpoint == NULL)
-       mountpoint = "/";
+       mountpoint = g_strdup ("/");
 
       mount = g_unix_mount_at (mountpoint, &cache_time);
       if (mount)
@@ -784,6 +796,8 @@ get_mount_info (GFileInfo             *fs_info,
          g_unix_mount_free (mount);
        }
 
+      g_free (mountpoint);
+
       dev = g_new0 (dev_t, 1);
       *dev = buf.st_dev;
       
@@ -808,6 +822,7 @@ is_xp_or_later (void)
 
   if (result == -1)
     {
+#ifndef _MSC_VER    
       OSVERSIONINFOEX ver_info = {0};
       DWORDLONG cond_mask = 0;
       int op = VER_GREATER_EQUAL;
@@ -822,6 +837,9 @@ is_xp_or_later (void)
       result = VerifyVersionInfo (&ver_info,
                                  VER_MAJORVERSION | VER_MINORVERSION, 
                                  cond_mask) != 0;
+#else
+      result = ((DWORD)(LOBYTE (LOWORD (GetVersion ())))) >= 5;  
+#endif
     }
 
   return result;
@@ -834,10 +852,10 @@ get_volume_for_path (const char *path)
   wchar_t *wpath;
   wchar_t *result;
 
-  wpath = g_utf8_to_utf16 (path, -1, NULL, &len, NULL);
-  result = g_new (wchar_t, len + 2);
+  wpath = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
+  result = g_new (wchar_t, MAX_PATH);
 
-  if (!GetVolumePathNameW (wpath, result, len + 2))
+  if (!GetVolumePathNameW (wpath, result, MAX_PATH))
     {
       char *msg = g_win32_error_message (GetLastError ());
       g_critical ("GetVolumePathName failed: %s", msg);
@@ -912,17 +930,18 @@ g_local_file_query_filesystem_info (GFile         *file,
 {
   GLocalFile *local = G_LOCAL_FILE (file);
   GFileInfo *info;
-  int statfs_result;
+  int statfs_result = 0;
   gboolean no_size;
 #ifndef G_OS_WIN32
-  guint64 block_size;
   const char *fstype;
 #ifdef USE_STATFS
+  guint64 block_size;
   struct statfs statfs_buffer;
 #elif defined(USE_STATVFS)
+  guint64 block_size;
   struct statvfs statfs_buffer;
-#endif
-#endif
+#endif /* USE_STATFS */
+#endif /* G_OS_WIN32 */
   GFileAttributeMatcher *attribute_matcher;
        
   no_size = FALSE;
@@ -934,24 +953,24 @@ g_local_file_query_filesystem_info (GFile         *file,
 #elif STATFS_ARGS == 4
   statfs_result = statfs (local->filename, &statfs_buffer,
                          sizeof (statfs_buffer), 0);
-#endif
+#endif /* STATFS_ARGS == 2 */
   block_size = statfs_buffer.f_bsize;
   
-#if defined(__linux__)
-  /* ncpfs does not know the amount of available and free space *
-   * assuming ncpfs is linux specific, if you are on a non-linux platform
-   * where ncpfs is available, please file a bug about it on bugzilla.gnome.org
+  /* Many backends can't report free size (for instance the gvfs fuse
+     backend for backend not supporting this), and set f_bfree to 0,
+     but it can be 0 for real too. We treat the available == 0 and
+     free == 0 case as "both of these are invalid".
    */
-  if (statfs_buffer.f_bavail == 0 && statfs_buffer.f_bfree == 0 &&
-      /* linux/ncp_fs.h: NCP_SUPER_MAGIC == 0x564c */
-      statfs_buffer.f_type == 0x564c)
+#ifndef G_OS_WIN32
+  if (statfs_result == 0 &&
+      statfs_buffer.f_bavail == 0 && statfs_buffer.f_bfree == 0)
     no_size = TRUE;
-#endif
+#endif /* G_OS_WIN32 */
   
 #elif defined(USE_STATVFS)
   statfs_result = statvfs (local->filename, &statfs_buffer);
   block_size = statfs_buffer.f_frsize; 
-#endif
+#endif /* USE_STATFS */
 
   if (statfs_result == -1)
     {
@@ -982,8 +1001,10 @@ g_local_file_query_filesystem_info (GFile         *file,
         g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, (guint64)li.QuadPart);
       g_free (wdirname);
 #else
+#if defined(USE_STATFS) || defined(USE_STATVFS)
       g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, block_size * statfs_buffer.f_bavail);
 #endif
+#endif
     }
   if (!no_size &&
       g_file_attribute_matcher_matches (attribute_matcher,
@@ -999,26 +1020,54 @@ g_local_file_query_filesystem_info (GFile         *file,
         g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,  (guint64)li.QuadPart);
       g_free (wdirname);
 #else
+#if defined(USE_STATFS) || defined(USE_STATVFS)
       g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, block_size * statfs_buffer.f_blocks);
 #endif
+#endif /* G_OS_WIN32 */
     }
+
+  if (!no_size &&
+      g_file_attribute_matcher_matches (attribute_matcher,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_USED))
+    {
+#ifdef G_OS_WIN32
+      gchar *localdir = g_path_get_dirname (local->filename);
+      wchar_t *wdirname = g_utf8_to_utf16 (localdir, -1, NULL, NULL, NULL);
+      ULARGE_INTEGER li_free;
+      ULARGE_INTEGER li_total;
+
+      g_free (localdir);
+      if (GetDiskFreeSpaceExW (wdirname, &li_free, &li_total, NULL))
+        g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED,  (guint64)li_total.QuadPart - (guint64)li_free.QuadPart);
+      g_free (wdirname);
+#else
+      g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED, block_size * (statfs_buffer.f_blocks - statfs_buffer.f_bfree));
+#endif /* G_OS_WIN32 */
+    }
+
+#ifndef G_OS_WIN32
 #ifdef USE_STATFS
 #if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
-  fstype = g_strdup(statfs_buffer.f_fstypename);
+  fstype = g_strdup (statfs_buffer.f_fstypename);
 #else
   fstype = get_fs_type (statfs_buffer.f_type);
 #endif
 
-#elif defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
-  fstype = g_strdup(statfs_buffer.f_basetype); 
+#elif defined(USE_STATVFS)
+#if defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
+  fstype = g_strdup (statfs_buffer.f_fstypename);
+#elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
+  fstype = g_strdup (statfs_buffer.f_basetype);
+#else
+  fstype = NULL;
 #endif
+#endif /* USE_STATFS */
 
-#ifndef G_OS_WIN32
   if (fstype &&
       g_file_attribute_matcher_matches (attribute_matcher,
                                        G_FILE_ATTRIBUTE_FILESYSTEM_TYPE))
     g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, fstype);
-#endif  
+#endif /* G_OS_WIN32 */
 
   if (g_file_attribute_matcher_matches (attribute_matcher,
                                        G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
@@ -1027,7 +1076,7 @@ g_local_file_query_filesystem_info (GFile         *file,
       get_filesystem_readonly (info, local->filename);
 #else
       get_mount_info (info, local->filename, attribute_matcher);
-#endif
+#endif /* G_OS_WIN32 */
     }
   
   g_file_attribute_matcher_unref (attribute_matcher);
@@ -1041,13 +1090,13 @@ g_local_file_find_enclosing_mount (GFile         *file,
                                    GError       **error)
 {
   GLocalFile *local = G_LOCAL_FILE (file);
-  struct stat buf;
+  GStatBuf buf;
   char *mountpoint;
   GMount *mount;
 
   if (g_lstat (local->filename, &buf) != 0)
     {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                      /* Translators: This is an error message when trying to
                       * find the enclosing (user visible) mount of a file, but
                       * none exists. */
@@ -1058,7 +1107,7 @@ g_local_file_find_enclosing_mount (GFile         *file,
   mountpoint = find_mountpoint_for (local->filename, buf.st_dev);
   if (mountpoint == NULL)
     {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                      /* Translators: This is an error message when trying to
                       * find the enclosing (user visible) mount of a file, but
                       * none exists. */
@@ -1071,7 +1120,7 @@ g_local_file_find_enclosing_mount (GFile         *file,
   if (mount)
     return mount;
 
-  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                  /* Translators: This is an error message when trying to find
                   * the enclosing (user visible) mount of a file, but none
                   * exists. */
@@ -1087,15 +1136,17 @@ g_local_file_set_display_name (GFile         *file,
 {
   GLocalFile *local, *new_local;
   GFile *new_file, *parent;
-  struct stat statbuf;
+  GStatBuf statbuf;
+  GVfsClass *class;
+  GVfs *vfs;
   int errsv;
 
   parent = g_file_get_parent (file);
   if (parent == NULL)
     {
-      g_set_error (error, G_IO_ERROR,
-                  G_IO_ERROR_FAILED,
-                  _("Can't rename root directory"));
+      g_set_error_literal (error, G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           _("Can't rename root directory"));
       return NULL;
     }
   
@@ -1104,16 +1155,27 @@ g_local_file_set_display_name (GFile         *file,
   
   if (new_file == NULL)
     return NULL;
-  
   local = G_LOCAL_FILE (file);
   new_local = G_LOCAL_FILE (new_file);
 
-  if (!(g_lstat (new_local->filename, &statbuf) == -1 &&
-       errno == ENOENT))
+  if (g_lstat (new_local->filename, &statbuf) == -1) 
     {
-      g_set_error (error, G_IO_ERROR,
-                  G_IO_ERROR_EXISTS,
-                  _("Can't rename file, filename already exist"));
+      errsv = errno;
+
+      if (errsv != ENOENT)
+        {
+         g_set_error (error, G_IO_ERROR,
+                      g_io_error_from_errno (errsv),
+                      _("Error renaming file: %s"),
+                      g_strerror (errsv));
+          return NULL;
+        }
+    }
+  else
+    {
+      g_set_error_literal (error, G_IO_ERROR,
+                           G_IO_ERROR_EXISTS,
+                           _("Can't rename file, filename already exists"));
       return NULL;
     }
 
@@ -1124,9 +1186,9 @@ g_local_file_set_display_name (GFile         *file,
       if (errsv == EINVAL)
        /* We can't get a rename file into itself error herer,
           so this must be an invalid filename, on e.g. FAT */
-       g_set_error (error, G_IO_ERROR,
-                    G_IO_ERROR_INVALID_FILENAME,
-                    _("Invalid filename"));
+       g_set_error_literal (error, G_IO_ERROR,
+                             G_IO_ERROR_INVALID_FILENAME,
+                             _("Invalid filename"));
       else
        g_set_error (error, G_IO_ERROR,
                     g_io_error_from_errno (errsv),
@@ -1135,7 +1197,12 @@ g_local_file_set_display_name (GFile         *file,
       g_object_unref (new_file);
       return NULL;
     }
-  
+
+  vfs = g_vfs_get_default ();
+  class = G_VFS_GET_CLASS (vfs);
+  if (class->local_file_moved)
+    class->local_file_moved (vfs, local->filename, new_local->filename);
+
   return new_file;
 }
 
@@ -1164,6 +1231,8 @@ g_local_file_query_info (GFile                *file,
                                 matcher, flags, &parent_info,
                                 error);
   
+
+  _g_local_file_info_free_parent_info (&parent_info);
   g_free (basename);
 
   g_file_attribute_matcher_unref (matcher);
@@ -1184,7 +1253,38 @@ g_local_file_query_writable_namespaces (GFile         *file,
                                        GCancellable  *cancellable,
                                        GError       **error)
 {
-  return g_file_attribute_info_list_ref (local_writable_namespaces);
+  GFileAttributeInfoList *list;
+  GVfsClass *class;
+  GVfs *vfs;
+
+  if (g_once_init_enter (&local_writable_namespaces))
+    {
+      /* Writable namespaces: */
+
+      list = g_file_attribute_info_list_new ();
+
+#ifdef HAVE_XATTR
+      g_file_attribute_info_list_add (list,
+                                     "xattr",
+                                     G_FILE_ATTRIBUTE_TYPE_STRING,
+                                     G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
+                                     G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+      g_file_attribute_info_list_add (list,
+                                     "xattr-sys",
+                                     G_FILE_ATTRIBUTE_TYPE_STRING,
+                                     G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+#endif
+
+      vfs = g_vfs_get_default ();
+      class = G_VFS_GET_CLASS (vfs);
+      if (class->add_writable_namespaces)
+       class->add_writable_namespaces (vfs, list);
+
+      g_once_init_leave (&local_writable_namespaces, (gsize)list);
+    }
+  list = (GFileAttributeInfoList *)local_writable_namespaces;
+
+  return g_file_attribute_info_list_ref (list);
 }
 
 static gboolean
@@ -1239,14 +1339,28 @@ g_local_file_read (GFile         *file,
                   GError       **error)
 {
   GLocalFile *local = G_LOCAL_FILE (file);
-  int fd;
-  struct stat buf;
+  int fd, ret;
+  GLocalFileStat buf;
   
   fd = g_open (local->filename, O_RDONLY|O_BINARY, 0);
   if (fd == -1)
     {
       int errsv = errno;
 
+#ifdef G_OS_WIN32
+      if (errsv == EACCES)
+       {
+         ret = _stati64 (local->filename, &buf);
+         if (ret == 0 && S_ISDIR (buf.st_mode))
+           {
+             g_set_error_literal (error, G_IO_ERROR,
+                                  G_IO_ERROR_IS_DIRECTORY,
+                                  _("Can't open directory"));
+             return NULL;
+           }
+       }
+#endif
+
       g_set_error (error, G_IO_ERROR,
                   g_io_error_from_errno (errsv),
                   _("Error opening file: %s"),
@@ -1254,12 +1368,18 @@ g_local_file_read (GFile         *file,
       return NULL;
     }
 
-  if (fstat(fd, &buf) == 0 && S_ISDIR (buf.st_mode))
+#ifdef G_OS_WIN32
+  ret = _fstati64 (fd, &buf);
+#else
+  ret = fstat (fd, &buf);
+#endif
+
+  if (ret == 0 && S_ISDIR (buf.st_mode))
     {
-      close (fd);
-      g_set_error (error, G_IO_ERROR,
-                  G_IO_ERROR_IS_DIRECTORY,
-                  _("Can't open directory"));
+      (void) g_close (fd, NULL);
+      g_set_error_literal (error, G_IO_ERROR,
+                           G_IO_ERROR_IS_DIRECTORY,
+                           _("Can't open directory"));
       return NULL;
     }
   
@@ -1283,7 +1403,8 @@ g_local_file_create (GFile             *file,
                     GError           **error)
 {
   return _g_local_file_output_stream_create (G_LOCAL_FILE (file)->filename,
-                                            flags, cancellable, error);
+                                             FALSE, flags, NULL,
+                                             cancellable, error);
 }
 
 static GFileOutputStream *
@@ -1295,10 +1416,72 @@ g_local_file_replace (GFile             *file,
                      GError           **error)
 {
   return _g_local_file_output_stream_replace (G_LOCAL_FILE (file)->filename,
-                                             etag, make_backup, flags,
-                                             cancellable, error);
+                                              FALSE,
+                                              etag, make_backup, flags, NULL,
+                                              cancellable, error);
 }
 
+static GFileIOStream *
+g_local_file_open_readwrite (GFile                      *file,
+                            GCancellable               *cancellable,
+                            GError                    **error)
+{
+  GFileOutputStream *output;
+  GFileIOStream *res;
+
+  output = _g_local_file_output_stream_open (G_LOCAL_FILE (file)->filename,
+                                            TRUE,
+                                            cancellable, error);
+  if (output == NULL)
+    return NULL;
+
+  res = _g_local_file_io_stream_new (G_LOCAL_FILE_OUTPUT_STREAM (output));
+  g_object_unref (output);
+  return res;
+}
+
+static GFileIOStream *
+g_local_file_create_readwrite (GFile                      *file,
+                              GFileCreateFlags            flags,
+                              GCancellable               *cancellable,
+                              GError                    **error)
+{
+  GFileOutputStream *output;
+  GFileIOStream *res;
+
+  output = _g_local_file_output_stream_create (G_LOCAL_FILE (file)->filename,
+                                              TRUE, flags, NULL,
+                                              cancellable, error);
+  if (output == NULL)
+    return NULL;
+
+  res = _g_local_file_io_stream_new (G_LOCAL_FILE_OUTPUT_STREAM (output));
+  g_object_unref (output);
+  return res;
+}
+
+static GFileIOStream *
+g_local_file_replace_readwrite (GFile                      *file,
+                               const char                 *etag,
+                               gboolean                    make_backup,
+                               GFileCreateFlags            flags,
+                               GCancellable               *cancellable,
+                               GError                    **error)
+{
+  GFileOutputStream *output;
+  GFileIOStream *res;
+
+  output = _g_local_file_output_stream_replace (G_LOCAL_FILE (file)->filename,
+                                                TRUE,
+                                                etag, make_backup, flags, NULL,
+                                                cancellable, error);
+  if (output == NULL)
+    return NULL;
+
+  res = _g_local_file_io_stream_new (G_LOCAL_FILE_OUTPUT_STREAM (output));
+  g_object_unref (output);
+  return res;
+}
 
 static gboolean
 g_local_file_delete (GFile         *file,
@@ -1306,13 +1489,15 @@ g_local_file_delete (GFile         *file,
                     GError       **error)
 {
   GLocalFile *local = G_LOCAL_FILE (file);
-  
+  GVfsClass *class;
+  GVfs *vfs;
+
   if (g_remove (local->filename) == -1)
     {
       int errsv = errno;
 
       /* Posix allows EEXIST too, but the more sane error
-        is G_IO_ERROR_NOT_FOUND, and its what nautilus
+        is G_IO_ERROR_NOT_FOUND, and it's what nautilus
         expects */
       if (errsv == EEXIST)
        errsv = ENOTEMPTY;
@@ -1323,10 +1508,17 @@ g_local_file_delete (GFile         *file,
                   g_strerror (errsv));
       return FALSE;
     }
-  
+
+  vfs = g_vfs_get_default ();
+  class = G_VFS_GET_CLASS (vfs);
+  if (class->local_file_removed)
+    class->local_file_removed (vfs, local->filename);
+
   return TRUE;
 }
 
+#ifndef G_OS_WIN32
+
 static char *
 strip_trailing_slashes (const char *path)
 {
@@ -1384,7 +1576,7 @@ get_parent (const char *path,
             dev_t      *parent_dev)
 {
   char *parent, *tmp;
-  struct stat parent_stat;
+  GStatBuf parent_stat;
   int num_recursions;
   char *path_copy;
 
@@ -1451,8 +1643,6 @@ expand_all_symlinks (const char *path)
   return res;
 }
 
-#ifndef G_OS_WIN32
-
 static char *
 find_mountpoint_for (const char *file, 
                      dev_t       dev)
@@ -1558,33 +1748,6 @@ try_make_relative (const char *path,
   return g_strdup (path);
 }
 
-static char *
-escape_trash_name (char *name)
-{
-  GString *str;
-  const gchar hex[16] = "0123456789ABCDEF";
-  
-  str = g_string_new ("");
-
-  while (*name != 0)
-    {
-      char c;
-
-      c = *name++;
-
-      if (g_ascii_isprint (c))
-       g_string_append_c (str, c);
-      else
-       {
-          g_string_append_c (str, '%');
-          g_string_append_c (str, hex[((guchar)c) >> 4]);
-          g_string_append_c (str, hex[((guchar)c) & 0xf]);
-       }
-    }
-
-  return g_string_free (str, FALSE);
-}
-
 gboolean
 _g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev)
 {
@@ -1593,14 +1756,13 @@ _g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev)
   char *topdir, *globaldir, *trashdir, *tmpname;
   uid_t uid;
   char uid_str[32];
-  struct stat global_stat, trash_stat;
+  GStatBuf global_stat, trash_stat;
   gboolean res;
-  int statres;
-      
+
   if (g_once_init_enter (&home_dev_set))
     {
-      struct stat home_stat;
-      
+      GStatBuf home_stat;
+
       g_stat (g_get_home_dir (), &home_stat);
       home_dev = home_stat.st_dev;
       g_once_init_leave (&home_dev_set, 1);
@@ -1614,9 +1776,8 @@ _g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev)
   if (topdir == NULL)
     return FALSE;
 
-  globaldir = g_build_filename (topdir, ".Trash", NULL); 
-  statres = g_lstat (globaldir, &global_stat);
- if (g_lstat (globaldir, &global_stat) == 0 &&
+  globaldir = g_build_filename (topdir, ".Trash", NULL);
+  if (g_lstat (globaldir, &global_stat) == 0 &&
       S_ISDIR (global_stat.st_mode) &&
       (global_stat.st_mode & S_ISVTX) != 0)
     {
@@ -1634,7 +1795,7 @@ _g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev)
   /* No global trash dir, or it failed the tests, fall back to $topdir/.Trash-$uid */
   uid = geteuid ();
   g_snprintf (uid_str, sizeof (uid_str), "%lu", (unsigned long) uid);
-  
+
   tmpname = g_strdup_printf (".Trash-%s", uid_str);
   trashdir = g_build_filename (topdir, tmpname, NULL);
   g_free (tmpname);
@@ -1643,20 +1804,58 @@ _g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev)
     {
       g_free (topdir);
       g_free (trashdir);
-      return
-       S_ISDIR (trash_stat.st_mode) &&
-       trash_stat.st_uid == uid;
+      return S_ISDIR (trash_stat.st_mode) &&
+            trash_stat.st_uid == uid;
     }
   g_free (trashdir);
 
   /* User specific trash didn't exist, can we create it? */
   res = g_access (topdir, W_OK) == 0;
-  
   g_free (topdir);
-  
+
   return res;
 }
 
+#ifdef G_OS_UNIX
+gboolean
+_g_local_file_is_lost_found_dir (const char *path, dev_t path_dev)
+{
+  gboolean ret = FALSE;
+  gchar *mount_dir = NULL;
+  size_t mount_dir_len;
+  GStatBuf statbuf;
+
+  if (!g_str_has_suffix (path, "/lost+found"))
+    goto out;
+
+  mount_dir = find_mountpoint_for (path, path_dev);
+  if (mount_dir == NULL)
+    goto out;
+
+  mount_dir_len = strlen (mount_dir);
+  /* We special-case rootfs ('/') since it's the only case where
+   * mount_dir ends in '/'
+   */
+  if (mount_dir_len == 1)
+    mount_dir_len--;
+  if (mount_dir_len + strlen ("/lost+found") != strlen (path))
+    goto out;
+
+  if (g_lstat (path, &statbuf) != 0)
+    goto out;
+
+  if (!(S_ISDIR (statbuf.st_mode) &&
+        statbuf.st_uid == 0 &&
+        statbuf.st_gid == 0))
+    goto out;
+
+  ret = TRUE;
+
+ out:
+  g_free (mount_dir);
+  return ret;
+}
+#endif
 
 static gboolean
 g_local_file_trash (GFile         *file,
@@ -1664,7 +1863,7 @@ g_local_file_trash (GFile         *file,
                    GError       **error)
 {
   GLocalFile *local = G_LOCAL_FILE (file);
-  struct stat file_stat, home_stat;
+  GStatBuf file_stat, home_stat;
   const char *homedir;
   char *trashdir, *topdir, *infodir, *filesdir;
   char *basename, *trashname, *trashfile, *infoname, *infofile;
@@ -1674,9 +1873,11 @@ g_local_file_trash (GFile         *file,
   gboolean is_homedir_trash;
   char delete_time[32];
   int fd;
-  struct stat trash_stat, global_stat;
+  GStatBuf trash_stat, global_stat;
   char *dirname, *globaldir;
-  
+  GVfsClass *class;
+  GVfs *vfs;
+
   if (g_lstat (local->filename, &file_stat) != 0)
     {
       int errsv = errno;
@@ -1725,9 +1926,9 @@ g_local_file_trash (GFile         *file,
       topdir = find_topdir_for (local->filename);
       if (topdir == NULL)
        {
-         g_set_error (error, G_IO_ERROR,
-                      G_IO_ERROR_NOT_SUPPORTED,
-                      _("Unable to find toplevel directory for trash"));
+         g_set_error_literal (error, G_IO_ERROR,
+                               G_IO_ERROR_NOT_SUPPORTED,
+                               _("Unable to find toplevel directory for trash"));
          return FALSE;
        }
       
@@ -1804,9 +2005,9 @@ g_local_file_trash (GFile         *file,
       if (trashdir == NULL)
        {
          g_free (topdir);
-         g_set_error (error, G_IO_ERROR,
-                      G_IO_ERROR_NOT_SUPPORTED,
-                      _("Unable to find or create trash directory"));
+         g_set_error_literal (error, G_IO_ERROR,
+                               G_IO_ERROR_NOT_SUPPORTED,
+                               _("Unable to find or create trash directory"));
          return FALSE;
        }
     }
@@ -1824,9 +2025,9 @@ g_local_file_trash (GFile         *file,
       g_free (topdir);
       g_free (infodir);
       g_free (filesdir);
-      g_set_error (error, G_IO_ERROR,
-                  G_IO_ERROR_NOT_SUPPORTED,
-                  _("Unable to find or create trash directory"));
+      g_set_error_literal (error, G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           _("Unable to find or create trash directory"));
       return FALSE;
     }  
 
@@ -1843,7 +2044,7 @@ g_local_file_trash (GFile         *file,
     infofile = g_build_filename (infodir, infoname, NULL);
     g_free (infoname);
 
-    fd = open (infofile, O_CREAT | O_EXCL, 0666);
+    fd = g_open (infofile, O_CREAT | O_EXCL, 0666);
   } while (fd == -1 && errno == EEXIST);
 
   g_free (basename);
@@ -1865,7 +2066,7 @@ g_local_file_trash (GFile         *file,
       return FALSE;
     }
 
-  close (fd);
+  (void) g_close (fd, NULL);
 
   /* TODO: Maybe we should verify that you can delete the file from the trash
      before moving it? OTOH, that is hard, as it needs a recursive scan */
@@ -1878,18 +2079,34 @@ g_local_file_trash (GFile         *file,
     {
       int errsv = errno;
 
+      g_unlink (infofile);
+
       g_free (topdir);
       g_free (trashname);
       g_free (infofile);
       g_free (trashfile);
-      
-      g_set_error (error, G_IO_ERROR,
-                  g_io_error_from_errno (errsv),
-                  _("Unable to trash file: %s"),
-                  g_strerror (errsv));
+
+      if (errsv == EXDEV)
+       /* The trash dir was actually on another fs anyway!?
+          This can happen when the same device is mounted multiple
+          times, or with bind mounts of the same fs. */
+       g_set_error (error, G_IO_ERROR,
+                    G_IO_ERROR_NOT_SUPPORTED,
+                    _("Unable to trash file: %s"),
+                    g_strerror (errsv));
+      else
+       g_set_error (error, G_IO_ERROR,
+                    g_io_error_from_errno (errsv),
+                    _("Unable to trash file: %s"),
+                    g_strerror (errsv));
       return FALSE;
     }
 
+  vfs = g_vfs_get_default ();
+  class = G_VFS_GET_CLASS (vfs);
+  if (class->local_file_moved)
+    class->local_file_moved (vfs, local->filename, trashfile);
+
   g_free (trashfile);
 
   /* TODO: Do we need to update mtime/atime here after the move? */
@@ -1899,7 +2116,7 @@ g_local_file_trash (GFile         *file,
     original_name = g_strdup (local->filename);
   else
     original_name = try_make_relative (local->filename, topdir);
-  original_name_escaped = escape_trash_name (original_name);
+  original_name_escaped = g_uri_escape_string (original_name, "/", FALSE);
   
   g_free (original_name);
   g_free (topdir);
@@ -1982,15 +2199,15 @@ g_local_file_make_directory (GFile         *file,
 {
   GLocalFile *local = G_LOCAL_FILE (file);
   
-  if (g_mkdir (local->filename, 0755) == -1)
+  if (g_mkdir (local->filename, 0777) == -1)
     {
       int errsv = errno;
 
       if (errsv == EINVAL)
        /* This must be an invalid filename, on e.g. FAT */
-       g_set_error (error, G_IO_ERROR,
-                    G_IO_ERROR_INVALID_FILENAME,
-                    _("Invalid filename"));
+       g_set_error_literal (error, G_IO_ERROR,
+                             G_IO_ERROR_INVALID_FILENAME,
+                             _("Invalid filename"));
       else
        g_set_error (error, G_IO_ERROR,
                     g_io_error_from_errno (errsv),
@@ -2017,9 +2234,13 @@ g_local_file_make_symbolic_link (GFile         *file,
 
       if (errsv == EINVAL)
        /* This must be an invalid filename, on e.g. FAT */
+       g_set_error_literal (error, G_IO_ERROR,
+                             G_IO_ERROR_INVALID_FILENAME,
+                             _("Invalid filename"));
+      else if (errsv == EPERM)
        g_set_error (error, G_IO_ERROR,
-                    G_IO_ERROR_INVALID_FILENAME,
-                    _("Invalid filename"));
+                    G_IO_ERROR_NOT_SUPPORTED,
+                    _("Filesystem does not support symbolic links"));
       else
        g_set_error (error, G_IO_ERROR,
                     g_io_error_from_errno (errsv),
@@ -2029,7 +2250,7 @@ g_local_file_make_symbolic_link (GFile         *file,
     }
   return TRUE;
 #else
-  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Symlinks not supported");
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Symlinks not supported");
   return FALSE;
 #endif
 }
@@ -2045,7 +2266,7 @@ g_local_file_copy (GFile                  *source,
                   GError                **error)
 {
   /* Fall back to default copy */
-  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported");
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported");
   return FALSE;
 }
 
@@ -2059,17 +2280,19 @@ g_local_file_move (GFile                  *source,
                   GError                **error)
 {
   GLocalFile *local_source, *local_destination;
-  struct stat statbuf;
+  GStatBuf statbuf;
   gboolean destination_exist, source_is_dir;
   char *backup_name;
   int res;
   off_t source_size;
-  
+  GVfsClass *class;
+  GVfs *vfs;
+
   if (!G_IS_LOCAL_FILE (source) ||
       !G_IS_LOCAL_FILE (destination))
     {
       /* Fall back to default move */
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Move not supported");
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Move not supported");
       return FALSE;
     }
   
@@ -2103,24 +2326,24 @@ g_local_file_move (GFile                  *source,
          if (S_ISDIR (statbuf.st_mode))
            {
              if (source_is_dir)
-               g_set_error (error,
-                            G_IO_ERROR,
-                            G_IO_ERROR_WOULD_MERGE,
-                            _("Can't move directory over directory"));
+               g_set_error_literal (error,
+                                     G_IO_ERROR,
+                                     G_IO_ERROR_WOULD_MERGE,
+                                     _("Can't move directory over directory"));
               else
-               g_set_error (error,
-                            G_IO_ERROR,
-                            G_IO_ERROR_IS_DIRECTORY,
-                            _("Can't copy over directory"));
+               g_set_error_literal (error,
+                                     G_IO_ERROR,
+                                     G_IO_ERROR_IS_DIRECTORY,
+                                     _("Can't copy over directory"));
              return FALSE;
            }
        }
       else
        {
-         g_set_error (error,
-                      G_IO_ERROR,
-                      G_IO_ERROR_EXISTS,
-                      _("Target file exists"));
+         g_set_error_literal (error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_EXISTS,
+                               _("Target file exists"));
          return FALSE;
        }
     }
@@ -2130,10 +2353,10 @@ g_local_file_move (GFile                  *source,
       backup_name = g_strconcat (local_destination->filename, "~", NULL);
       if (g_rename (local_destination->filename, backup_name) == -1)
        {
-         g_set_error (error,
-                      G_IO_ERROR,
-                      G_IO_ERROR_CANT_CREATE_BACKUP,
-                      _("Backup file creation failed"));
+         g_set_error_literal (error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_CANT_CREATE_BACKUP,
+                               _("Backup file creation failed"));
          g_free (backup_name);
          return FALSE;
        }
@@ -2164,16 +2387,16 @@ g_local_file_move (GFile                  *source,
 
       if (errsv == EXDEV)
        /* This will cause the fallback code to run */
-       g_set_error (error, G_IO_ERROR,
-                    G_IO_ERROR_NOT_SUPPORTED,
-                    _("Move between mounts not supported"));
+       g_set_error_literal (error, G_IO_ERROR,
+                             G_IO_ERROR_NOT_SUPPORTED,
+                             _("Move between mounts not supported"));
       else if (errsv == EINVAL)
        /* This must be an invalid filename, on e.g. FAT, or
           we're trying to move the file into itself...
           We return invalid filename for both... */
-       g_set_error (error, G_IO_ERROR,
-                    G_IO_ERROR_INVALID_FILENAME,
-                    _("Invalid filename"));
+       g_set_error_literal (error, G_IO_ERROR,
+                             G_IO_ERROR_INVALID_FILENAME,
+                             _("Invalid filename"));
       else
        g_set_error (error, G_IO_ERROR,
                     g_io_error_from_errno (errsv),
@@ -2182,6 +2405,11 @@ g_local_file_move (GFile                  *source,
       return FALSE;
     }
 
+  vfs = g_vfs_get_default ();
+  class = G_VFS_GET_CLASS (vfs);
+  if (class->local_file_moved)
+    class->local_file_moved (vfs, local_source->filename, local_destination->filename);
+
   /* Make sure we send full copied size */
   if (progress_callback)
     progress_callback (source_size, source_size, progress_callback_data);
@@ -2189,6 +2417,87 @@ g_local_file_move (GFile                  *source,
   return TRUE;
 }
 
+#ifdef G_OS_WIN32
+
+static gboolean
+is_remote (const gchar *filename)
+{
+  return FALSE;
+}
+
+#else
+
+static gboolean
+is_remote_fs (const gchar *filename)
+{
+  const char *fsname = NULL;
+
+#ifdef USE_STATFS
+  struct statfs statfs_buffer;
+  int statfs_result = 0;
+
+#if STATFS_ARGS == 2
+  statfs_result = statfs (filename, &statfs_buffer);
+#elif STATFS_ARGS == 4
+  statfs_result = statfs (filename, &statfs_buffer, sizeof (statfs_buffer), 0);
+#endif
+
+#elif defined(USE_STATVFS)
+  struct statvfs statfs_buffer;
+  int statfs_result = 0;
+
+  statfs_result = statvfs (filename, &statfs_buffer);
+#else
+  return FALSE;
+#endif
+
+  if (statfs_result == -1)
+    return FALSE;
+
+#ifdef USE_STATFS
+#if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
+  fsname = statfs_buffer.f_fstypename;
+#else
+  fsname = get_fs_type (statfs_buffer.f_type);
+#endif
+
+#elif defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
+  fsname = statfs_buffer.f_basetype;
+#endif
+
+  if (fsname != NULL)
+    {
+      if (strcmp (fsname, "nfs") == 0)
+        return TRUE;
+      if (strcmp (fsname, "nfs4") == 0)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+is_remote (const gchar *filename)
+{
+  static gboolean remote_home;
+  static gsize initialized;
+  const gchar *home;
+
+  home = g_get_home_dir ();
+  if (path_has_prefix (filename, home))
+    {
+      if (g_once_init_enter (&initialized))
+        {
+          remote_home = is_remote_fs (home);
+          g_once_init_leave (&initialized, TRUE);
+        }
+      return remote_home;
+    }
+
+  return FALSE;
+}
+#endif /* !G_OS_WIN32 */
+
 static GFileMonitor*
 g_local_file_monitor_dir (GFile             *file,
                          GFileMonitorFlags  flags,
@@ -2196,7 +2505,7 @@ g_local_file_monitor_dir (GFile             *file,
                          GError           **error)
 {
   GLocalFile* local_file = G_LOCAL_FILE(file);
-  return _g_local_directory_monitor_new (local_file->filename, flags, error);
+  return _g_local_directory_monitor_new (local_file->filename, flags, NULL, is_remote (local_file->filename), TRUE, error);
 }
 
 static GFileMonitor*
@@ -2206,7 +2515,322 @@ g_local_file_monitor_file (GFile             *file,
                           GError           **error)
 {
   GLocalFile* local_file = G_LOCAL_FILE(file);
-  return _g_local_file_monitor_new (local_file->filename, flags, error);
+  return _g_local_file_monitor_new (local_file->filename, flags, NULL, is_remote (local_file->filename), TRUE, error);
+}
+
+GLocalDirectoryMonitor *
+g_local_directory_monitor_new_in_worker (const char         *pathname,
+                                         GFileMonitorFlags   flags,
+                                         GError            **error)
+{
+  return (gpointer) _g_local_directory_monitor_new (pathname, flags,
+                                                    GLIB_PRIVATE_CALL (g_get_worker_context) (),
+                                                    is_remote (pathname), FALSE, error);
+}
+
+GLocalFileMonitor *
+g_local_file_monitor_new_in_worker (const char         *pathname,
+                                    GFileMonitorFlags   flags,
+                                    GError            **error)
+{
+  return (gpointer) _g_local_file_monitor_new (pathname, flags,
+                                               GLIB_PRIVATE_CALL (g_get_worker_context) (),
+                                               is_remote (pathname), FALSE, error);
+}
+
+
+/* Here is the GLocalFile implementation of g_file_measure_disk_usage().
+ *
+ * If available, we use fopenat() in preference to filenames for
+ * efficiency and safety reasons.  We know that fopenat() is available
+ * based on if AT_FDCWD is defined.  POSIX guarantees that this will be
+ * defined as a macro.
+ *
+ * We use a linked list of stack-allocated GSList nodes in order to be
+ * able to reconstruct the filename for error messages.  We actually
+ * pass the filename to operate on through the top node of the list.
+ *
+ * In case we're using openat(), this top filename will be a basename
+ * which should be opened in the directory which has also had its fd
+ * passed along.  If we're not using openat() then it will be a full
+ * absolute filename.
+ */
+
+static gboolean
+g_local_file_measure_size_error (GFileMeasureFlags   flags,
+                                 gint                saved_errno,
+                                 GSList             *name,
+                                 GError            **error)
+{
+  /* Only report an error if we were at the toplevel or if the caller
+   * requested reporting of all errors.
+   */
+  if ((name->next == NULL) || (flags & G_FILE_MEASURE_REPORT_ANY_ERROR))
+    {
+      GString *filename;
+      GSList *node;
+
+      /* Skip some work if there is no error return */
+      if (!error)
+        return FALSE;
+
+#ifdef AT_FDCWD
+      /* If using openat() we need to rebuild the filename for the message */
+      filename = g_string_new (name->data);
+      for (node = name->next; node; node = node->next)
+        {
+          gchar *utf8;
+
+          g_string_prepend_c (filename, G_DIR_SEPARATOR);
+          utf8 = g_filename_display_name (node->data);
+          g_string_prepend (filename, utf8);
+          g_free (utf8);
+        }
+#else
+      {
+        gchar *utf8;
+
+        /* Otherwise, we already have it, so just use it. */
+        node = name;
+        filename = g_string_new (NULL);
+        utf8 = g_filename_display_name (node->data);
+        g_string_append (filename, utf8);
+        g_free (utf8);
+      }
+#endif
+
+      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno),
+                   _("Could not determine the disk usage of %s: %s"),
+                   filename->str, g_strerror (saved_errno));
+
+      g_string_free (filename, TRUE);
+
+      return FALSE;
+    }
+
+  else
+    /* We're not reporting this error... */
+    return TRUE;
+}
+
+typedef struct
+{
+  GFileMeasureFlags  flags;
+  dev_t              contained_on;
+  GCancellable      *cancellable;
+
+  GFileMeasureProgressCallback progress_callback;
+  gpointer                     progress_data;
+
+  guint64 disk_usage;
+  guint64 num_dirs;
+  guint64 num_files;
+
+  guint64 last_progress_report;
+} MeasureState;
+
+static gboolean
+g_local_file_measure_size_of_contents (gint           fd,
+                                       GSList        *dir_name,
+                                       MeasureState  *state,
+                                       GError       **error);
+
+static gboolean
+g_local_file_measure_size_of_file (gint           parent_fd,
+                                   GSList        *name,
+                                   MeasureState  *state,
+                                   GError       **error)
+{
+  GLocalFileStat buf;
+
+  if (g_cancellable_set_error_if_cancelled (state->cancellable, error))
+    return FALSE;
+
+#if defined (AT_FDCWD)
+  if (fstatat (parent_fd, name->data, &buf, AT_SYMLINK_NOFOLLOW) != 0)
+#else
+  if (g_lstat (name->data, &buf) != 0)
+#endif
+    return g_local_file_measure_size_error (state->flags, errno, name, error);
+
+  if (name->next)
+    {
+      /* If not at the toplevel, check for a device boundary. */
+
+      if (state->flags & G_FILE_MEASURE_NO_XDEV)
+        if (state->contained_on != buf.st_dev)
+          return TRUE;
+    }
+  else
+    {
+      /* If, however, this is the toplevel, set the device number so
+       * that recursive invocations can compare against it.
+       */
+      state->contained_on = buf.st_dev;
+    }
+
+#if defined (HAVE_STRUCT_STAT_ST_BLOCKS)
+  if (~state->flags & G_FILE_MEASURE_APPARENT_SIZE)
+    state->disk_usage += buf.st_blocks * G_GUINT64_CONSTANT (512);
+  else
+#endif
+    state->disk_usage += buf.st_size;
+
+  if (S_ISDIR (buf.st_mode))
+    state->num_dirs++;
+  else
+    state->num_files++;
+
+  if (state->progress_callback)
+    {
+      /* We could attempt to do some cleverness here in order to avoid
+       * calling clock_gettime() so much, but we're doing stats and opens
+       * all over the place already...
+       */
+      if (state->last_progress_report)
+        {
+          guint64 now;
+
+          now = g_get_monotonic_time ();
+
+          if (state->last_progress_report + 200 * G_TIME_SPAN_MILLISECOND < now)
+            {
+              (* state->progress_callback) (TRUE,
+                                            state->disk_usage, state->num_dirs, state->num_files,
+                                            state->progress_data);
+              state->last_progress_report = now;
+            }
+        }
+      else
+        {
+          /* We must do an initial report to inform that more reports
+           * will be coming.
+           */
+          (* state->progress_callback) (TRUE, 0, 0, 0, state->progress_data);
+          state->last_progress_report = g_get_monotonic_time ();
+        }
+    }
+
+  if (S_ISDIR (buf.st_mode))
+    {
+      int dir_fd = -1;
+
+      if (g_cancellable_set_error_if_cancelled (state->cancellable, error))
+        return FALSE;
+
+#ifdef AT_FDCWD
+#ifdef HAVE_OPEN_O_DIRECTORY
+      dir_fd = openat (parent_fd, name->data, O_RDONLY|O_DIRECTORY);
+#else
+      dir_fd = openat (parent_fd, name->data, O_RDONLY);
+#endif
+      if (dir_fd < 0)
+        return g_local_file_measure_size_error (state->flags, errno, name, error);
+#endif
+
+      if (!g_local_file_measure_size_of_contents (dir_fd, name, state, error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+g_local_file_measure_size_of_contents (gint           fd,
+                                       GSList        *dir_name,
+                                       MeasureState  *state,
+                                       GError       **error)
+{
+  gboolean success = TRUE;
+  const gchar *name;
+  GDir *dir;
+
+#ifdef AT_FDCWD
+  {
+    /* If this fails, we want to preserve the errno from fopendir() */
+    DIR *dirp;
+    dirp = fdopendir (fd);
+    dir = dirp ? GLIB_PRIVATE_CALL(g_dir_new_from_dirp) (dirp) : NULL;
+  }
+#else
+  dir = GLIB_PRIVATE_CALL(g_dir_open_with_errno) (dir_name->data, 0);
+#endif
+
+  if (dir == NULL)
+    {
+      gint saved_errno = errno;
+
+#ifdef AT_FDCWD
+      close (fd);
+#endif
+
+      return g_local_file_measure_size_error (state->flags, saved_errno, dir_name, error);
+    }
+
+  while (success && (name = g_dir_read_name (dir)))
+    {
+      GSList node;
+
+      node.next = dir_name;
+#ifdef AT_FDCWD
+      node.data = (gchar *) name;
+#else
+      node.data = g_build_filename (dir_name->data, name, NULL);
+#endif
+
+      success = g_local_file_measure_size_of_file (fd, &node, state, error);
+
+#ifndef AT_FDCWD
+      g_free (node.data);
+#endif
+    }
+
+  g_dir_close (dir);
+
+  return success;
+}
+
+static gboolean
+g_local_file_measure_disk_usage (GFile                         *file,
+                                 GFileMeasureFlags              flags,
+                                 GCancellable                  *cancellable,
+                                 GFileMeasureProgressCallback   progress_callback,
+                                 gpointer                       progress_data,
+                                 guint64                       *disk_usage,
+                                 guint64                       *num_dirs,
+                                 guint64                       *num_files,
+                                 GError                       **error)
+{
+  GLocalFile *local_file = G_LOCAL_FILE (file);
+  MeasureState state = { 0, };
+  gint root_fd = -1;
+  GSList node;
+
+  state.flags = flags;
+  state.cancellable = cancellable;
+  state.progress_callback = progress_callback;
+  state.progress_data = progress_data;
+
+#ifdef AT_FDCWD
+  root_fd = AT_FDCWD;
+#endif
+
+  node.data = local_file->filename;
+  node.next = NULL;
+
+  if (!g_local_file_measure_size_of_file (root_fd, &node, &state, error))
+    return FALSE;
+
+  if (disk_usage)
+    *disk_usage = state.disk_usage;
+
+  if (num_dirs)
+    *num_dirs = state.num_dirs;
+
+  if (num_files)
+    *num_files = state.num_files;
+
+  return TRUE;
 }
 
 static void
@@ -2240,6 +2864,9 @@ g_local_file_file_iface_init (GFileIface *iface)
   iface->append_to = g_local_file_append_to;
   iface->create = g_local_file_create;
   iface->replace = g_local_file_replace;
+  iface->open_readwrite = g_local_file_open_readwrite;
+  iface->create_readwrite = g_local_file_create_readwrite;
+  iface->replace_readwrite = g_local_file_replace_readwrite;
   iface->delete_file = g_local_file_delete;
   iface->trash = g_local_file_trash;
   iface->make_directory = g_local_file_make_directory;
@@ -2248,4 +2875,7 @@ g_local_file_file_iface_init (GFileIface *iface)
   iface->move = g_local_file_move;
   iface->monitor_dir = g_local_file_monitor_dir;
   iface->monitor_file = g_local_file_monitor_file;
+  iface->measure_disk_usage = g_local_file_measure_disk_usage;
+
+  iface->supports_thread_contexts = TRUE;
 }