X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gio%2Fglocalfile.c;h=846eedbcc27e26a7d0ebf134c99ee1e5ea186392;hb=0f9acd9d9be0d6efe3110d20eeee9a8b57b558bd;hp=d13b9e9cc06ed979a3c55e40237c2591be00ea19;hpb=9c17697b56501d11b4c653432cc9e290347aa03e;p=platform%2Fupstream%2Fglib.git diff --git a/gio/glocalfile.c b/gio/glocalfile.c index d13b9e9..846eedb 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -13,21 +13,20 @@ * 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 . * * Author: Alexander Larsson */ -#include +#include "config.h" #include #include #include #include #include -#ifdef HAVE_UNISTD_H +#if G_OS_UNIX +#include #include #endif @@ -50,41 +49,28 @@ #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 #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 #include #include @@ -101,12 +87,11 @@ #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 { @@ -154,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, @@ -176,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, @@ -192,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 @@ -217,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) @@ -236,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 @@ -253,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) @@ -303,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) { @@ -423,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); } } @@ -441,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); @@ -450,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); } @@ -587,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); @@ -599,19 +605,16 @@ 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) { @@ -627,6 +630,8 @@ get_fs_type (long f_type) return "befs"; case 0x1BADFACE: return "bfs"; + case 0x9123683E: + return "btrfs"; case 0xFF534D42: return "cifs"; case 0x73757245: @@ -644,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: @@ -685,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: @@ -705,6 +712,8 @@ get_fs_type (long f_type) return "xfs"; case 0x012FD16D: return "xiafs"; + case 0x52345362: + return "reiser4"; default: return NULL; } @@ -739,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; @@ -776,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) @@ -787,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; @@ -841,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); @@ -922,14 +933,15 @@ g_local_file_query_filesystem_info (GFile *file, 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; @@ -941,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) { @@ -989,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, @@ -1006,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)) @@ -1034,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); @@ -1048,7 +1090,7 @@ 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; @@ -1094,7 +1136,9 @@ 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); @@ -1111,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) + { + 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 exist")); + _("Can't rename file, filename already exists")); return NULL; } @@ -1142,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; } @@ -1171,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); @@ -1191,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 @@ -1246,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"), @@ -1261,9 +1368,15 @@ 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); + (void) g_close (fd, NULL); g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Can't open directory")); @@ -1290,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 * @@ -1302,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, @@ -1313,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; @@ -1330,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) { @@ -1391,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; @@ -1458,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) @@ -1565,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) { @@ -1600,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); @@ -1621,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) { @@ -1641,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); @@ -1650,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, @@ -1671,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; @@ -1681,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; @@ -1850,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); @@ -1872,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 */ @@ -1885,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? */ @@ -1906,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); @@ -1989,7 +2199,7 @@ 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; @@ -2027,6 +2237,10 @@ g_local_file_make_symbolic_link (GFile *file, 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_NOT_SUPPORTED, + _("Filesystem does not support symbolic links")); else g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), @@ -2066,12 +2280,14 @@ 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)) { @@ -2189,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); @@ -2196,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, @@ -2203,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* @@ -2213,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 @@ -2247,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; @@ -2255,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; }