* 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>
+#ifndef FILE_READ_ONLY_VOLUME
+#define FILE_READ_ONLY_VOLUME 0x00080000
+#endif
+
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
#endif
#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
{
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
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,
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,
#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
{
}
+const char *
+_g_local_file_get_filename (GLocalFile *file)
+{
+ return file->filename;
+}
static char *
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
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)
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)
{
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);
}
}
}
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);
NULL);
g_free (escaped_path);
-
+#ifdef G_OS_WIN32
+ g_free (dup_filename);
+#endif
if (free_utf8_filename)
g_free (utf8_filename);
}
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);
}
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);
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:
case 0xEF51:
return "ext2";
case 0xEF53:
- return "ext3";
+ return "ext3/ext4";
case 0x4244:
return "hfs";
case 0xF995E849:
return "romfs";
case 0x517B:
return "smb";
+ case 0x73717368:
+ return "squashfs";
case 0x012FF7B6:
return "sysv2";
case 0x012FF7B5:
return "xfs";
case 0x012FD16D:
return "xiafs";
+ case 0x52345362:
+ return "reiser4";
default:
return NULL;
}
const char *path,
GFileAttributeMatcher *matcher)
{
- struct stat buf;
+ GStatBuf buf;
gboolean got_info;
gpointer info_as_ptr;
guint mount_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)
g_unix_mount_free (mount);
}
+ g_free (mountpoint);
+
dev = g_new0 (dev_t, 1);
*dev = buf.st_dev;
if (result == -1)
{
+#ifndef _MSC_VER
OSVERSIONINFOEX ver_info = {0};
DWORDLONG cond_mask = 0;
int op = VER_GREATER_EQUAL;
result = VerifyVersionInfo (&ver_info,
VER_MAJORVERSION | VER_MINORVERSION,
cond_mask) != 0;
+#else
+ result = ((DWORD)(LOBYTE (LOWORD (GetVersion ())))) >= 5;
+#endif
}
return result;
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);
{
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;
- const char *fstype;
#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;
#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)
{
attribute_matcher = g_file_attribute_matcher_new (attributes);
- if (g_file_attribute_matcher_matches (attribute_matcher,
+ if (!no_size &&
+ g_file_attribute_matcher_matches (attribute_matcher,
G_FILE_ATTRIBUTE_FILESYSTEM_FREE))
{
#ifdef G_OS_WIN32
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 (g_file_attribute_matcher_matches (attribute_matcher,
+ if (!no_size &&
+ g_file_attribute_matcher_matches (attribute_matcher,
G_FILE_ATTRIBUTE_FILESYSTEM_SIZE))
{
#ifdef G_OS_WIN32
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);
+#else
fstype = get_fs_type (statfs_buffer.f_type);
+#endif
+
+#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 */
+
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))
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);
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,
- _("Containing mount does not exist"));
+ 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. */
+ _("Containing mount does not exist"));
return NULL;
}
mountpoint = find_mountpoint_for (local->filename, buf.st_dev);
if (mountpoint == NULL)
{
- g_set_error (error, G_IO_ERROR,
- G_IO_ERROR_NOT_FOUND,
- _("Containing mount does not exist"));
+ 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. */
+ _("Containing mount does not exist"));
return NULL;
}
if (mount)
return mount;
- g_set_error (error, G_IO_ERROR,
- G_IO_ERROR_NOT_FOUND,
- _("Containing mount does not exist"));
+ 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. */
+ _("Containing mount does not exist"));
return NULL;
}
{
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;
}
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;
}
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),
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;
}
matcher, flags, &parent_info,
error);
+
+ _g_local_file_info_free_parent_info (&parent_info);
g_free (basename);
g_file_attribute_matcher_unref (matcher);
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
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"),
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;
}
GError **error)
{
return _g_local_file_output_stream_create (G_LOCAL_FILE (file)->filename,
- flags, cancellable, error);
+ FALSE, flags, NULL,
+ cancellable, error);
}
static GFileOutputStream *
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,
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 it's what nautilus
+ expects */
+ if (errsv == EEXIST)
+ errsv = ENOTEMPTY;
+
g_set_error (error, G_IO_ERROR,
g_io_error_from_errno (errsv),
_("Error removing file: %s"),
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)
{
dev_t *parent_dev)
{
char *parent, *tmp;
- struct stat parent_stat;
+ GStatBuf parent_stat;
int num_recursions;
char *path_copy;
if (strcmp (parent, ".") == 0 ||
strcmp (parent, path_copy) == 0)
{
+ g_free (parent);
g_free (path_copy);
return NULL;
}
return res;
}
-#ifndef G_OS_WIN32
-
static char *
find_mountpoint_for (const char *file,
dev_t dev)
return g_strdup (path);
}
-static char *
-escape_trash_name (char *name)
+gboolean
+_g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev)
{
- GString *str;
- const gchar hex[16] = "0123456789ABCDEF";
-
- str = g_string_new ("");
+ static gsize home_dev_set = 0;
+ static dev_t home_dev;
+ char *topdir, *globaldir, *trashdir, *tmpname;
+ uid_t uid;
+ char uid_str[32];
+ GStatBuf global_stat, trash_stat;
+ gboolean res;
- while (*name != 0)
+ if (g_once_init_enter (&home_dev_set))
{
- char c;
+ GStatBuf home_stat;
- c = *name++;
+ g_stat (g_get_home_dir (), &home_stat);
+ home_dev = home_stat.st_dev;
+ g_once_init_leave (&home_dev_set, 1);
+ }
- 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]);
- }
+ /* Assume we can trash to the home */
+ if (dir_dev == home_dev)
+ return TRUE;
+
+ topdir = find_mountpoint_for (dirname, dir_dev);
+ if (topdir == NULL)
+ return FALSE;
+
+ 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)
+ {
+ /* got a toplevel sysadmin created dir, assume we
+ * can trash to it (we should be able to create a dir)
+ * This fails for the FAT case where the ownership of
+ * that dir would be wrong though..
+ */
+ g_free (globaldir);
+ g_free (topdir);
+ return TRUE;
+ }
+ g_free (globaldir);
+
+ /* 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);
+
+ if (g_lstat (trashdir, &trash_stat) == 0)
+ {
+ g_free (topdir);
+ g_free (trashdir);
+ 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;
+}
- return g_string_free (str, FALSE);
+#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,
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;
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;
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;
}
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;
}
}
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;
}
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);
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 */
{
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? */
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);
return TRUE;
}
#else /* G_OS_WIN32 */
+gboolean
+_g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev)
+{
+ return FALSE; /* XXX ??? */
+}
+
static gboolean
g_local_file_trash (GFile *file,
GCancellable *cancellable,
g_set_error (error, G_IO_ERROR,
G_IO_ERROR_CANCELLED,
_("Unable to trash file: %s"),
- _("cancelled"));
+ _("Operation was cancelled"));
success = FALSE;
}
else if (!success)
g_set_error (error, G_IO_ERROR,
G_IO_ERROR_FAILED,
_("Unable to trash file: %s"),
- _("failed"));
+ _("internal error"));
g_free (wfilename);
return success;
{
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),
- _("Error removing file: %s"),
+ _("Error creating directory: %s"),
g_strerror (errsv));
return FALSE;
}
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),
}
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
}
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;
}
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;
}
g_strerror (errsv));
return FALSE;
}
- else
- source_is_dir = S_ISDIR (statbuf.st_mode);
+ source_is_dir = S_ISDIR (statbuf.st_mode);
source_size = statbuf.st_size;
destination_exist = FALSE;
/* Always fail on dirs, even with overwrite */
if (S_ISDIR (statbuf.st_mode))
{
- g_set_error (error,
- G_IO_ERROR,
- G_IO_ERROR_WOULD_MERGE,
- _("Can't move directory over directory"));
+ if (source_is_dir)
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_WOULD_MERGE,
+ _("Can't move directory over directory"));
+ else
+ 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;
}
}
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;
}
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),
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);
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,
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*
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
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;
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;
}