diff --git a/gio/glocalfile.c b/gio/glocalfile.c index 354ac7c8c..2d928cc31 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -1677,20 +1677,16 @@ find_mountpoint_for (const char *file, } } -static char * -_g_local_file_find_topdir_for_internal (const char *file, dev_t file_dev) +char * +_g_local_file_find_topdir_for (const char *file) { char *dir; char *mountpoint = NULL; dev_t dir_dev; dir = get_parent (file, &dir_dev); - if (dir == NULL || dir_dev != file_dev) - { - g_free (dir); - - return NULL; - } + if (dir == NULL) + return NULL; mountpoint = find_mountpoint_for (dir, dir_dev); g_free (dir); @@ -1698,17 +1694,6 @@ _g_local_file_find_topdir_for_internal (const char *file, dev_t file_dev) return mountpoint; } -char * -_g_local_file_find_topdir_for (const char *file) -{ - GStatBuf file_stat; - - if (g_lstat (file, &file_stat) != 0) - return NULL; - - return _g_local_file_find_topdir_for_internal (file, file_stat.st_dev); -} - static char * get_unique_filename (const char *basename, int id) @@ -1908,6 +1893,7 @@ g_local_file_trash (GFile *file, char *original_name, *original_name_escaped; int i; char *data; + char *path; gboolean is_homedir_trash; char *delete_time = NULL; int fd; @@ -1932,6 +1918,24 @@ g_local_file_trash (GFile *file, is_homedir_trash = FALSE; trashdir = NULL; + + /* On overlayfs, a file's st_dev will be different to the home directory's. + * We still want to create our trash directory under the home directory, so + * instead we should stat the directory that the file we're deleting is in as + * this will have the same st_dev. + */ + if (!S_ISDIR (file_stat.st_mode)) + { + path = g_path_get_dirname (local->filename); + /* If the parent is a symlink to a different device then it might have + * st_dev equal to the home directory's, in which case we will end up + * trying to rename across a filesystem boundary, which doesn't work. So + * we use g_stat here instead of g_lstat, to know where the symlink + * points to. */ + g_stat (path, &file_stat); + g_free (path); + } + if (file_stat.st_dev == home_stat.st_dev) { is_homedir_trash = TRUE; @@ -1962,8 +1966,7 @@ g_local_file_trash (GFile *file, uid = geteuid (); g_snprintf (uid_str, sizeof (uid_str), "%lu", (unsigned long)uid); - topdir = _g_local_file_find_topdir_for_internal (local->filename, - file_stat.st_dev); + topdir = _g_local_file_find_topdir_for (local->filename); if (topdir == NULL) { g_set_io_error (error, diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c index 9cf7ff845..58802fd43 100644 --- a/gio/glocalfileinfo.c +++ b/gio/glocalfileinfo.c @@ -924,13 +924,9 @@ get_access_rights (GFileAttributeMatcher *attribute_matcher, _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_DELETE, writable); - /* Trashing is supported only if the parent device is the same */ if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_TRASH)) - _g_file_info_set_attribute_boolean_by_id (info, - G_FILE_ATTRIBUTE_ID_ACCESS_CAN_TRASH, - writable && - parent_info->has_trash_dir && - parent_info->device == statbuf->st_dev); + _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_TRASH, + writable && parent_info->has_trash_dir); } } diff --git a/gio/tests/trash.c b/gio/tests/trash.c index 2abe0aa0c..176c4b9c3 100644 --- a/gio/tests/trash.c +++ b/gio/tests/trash.c @@ -35,23 +35,26 @@ test_trash_not_supported (void) GFileInfo *info; GError *error = NULL; gboolean ret; - GStatBuf file_stat, home_stat; + gchar *parent_dirname; + GStatBuf parent_stat, home_stat; /* The test assumes that tmp file is located on system internal mount. */ file = g_file_new_tmp ("test-trashXXXXXX", &stream, &error); + parent_dirname = g_path_get_dirname (g_file_peek_path (file)); g_assert_no_error (error); - g_assert_cmpint (g_lstat (g_file_peek_path (file), &file_stat), ==, 0); - g_test_message ("File: %s (dev: %" G_GUINT64_FORMAT ")", - g_file_peek_path (file), (guint64) file_stat.st_dev); + g_assert_cmpint (g_stat (parent_dirname, &parent_stat), ==, 0); + g_test_message ("File: %s (parent st_dev: %" G_GUINT64_FORMAT ")", + g_file_peek_path (file), (guint64) parent_stat.st_dev); g_assert_cmpint (g_stat (g_get_home_dir (), &home_stat), ==, 0); - g_test_message ("Home: %s (dev: %" G_GUINT64_FORMAT ")", + g_test_message ("Home: %s (st_dev: %" G_GUINT64_FORMAT ")", g_get_home_dir (), (guint64) home_stat.st_dev); - if (file_stat.st_dev == home_stat.st_dev) + if (parent_stat.st_dev == home_stat.st_dev) { g_test_skip ("The file has to be on another filesystem than the home trash to run this test"); + g_free (parent_dirname); g_object_unref (stream); g_object_unref (file); @@ -85,6 +88,7 @@ test_trash_not_supported (void) g_io_stream_close (G_IO_STREAM (stream), NULL, &error); g_assert_no_error (error); + g_free (parent_dirname); g_object_unref (info); g_object_unref (stream); g_object_unref (file);