diff --git a/gio/glocalfile.c b/gio/glocalfile.c index bc33af644..7b70c614c 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -1997,6 +1997,8 @@ g_local_file_trash (GFile *file, GVfsClass *class; GVfs *vfs; int errsv; + size_t basename_len; + GError *my_error = NULL; if (glib_should_use_portal ()) return g_trash_portal_trash_file (file, error); @@ -2224,40 +2226,91 @@ g_local_file_trash (GFile *file, g_free (trashdir); basename = g_path_get_basename (local->filename); + basename_len = strlen (basename); i = 1; trashname = NULL; infofile = NULL; - do { - g_free (trashname); - g_free (infofile); - - trashname = get_unique_filename (basename, i++); - infoname = g_strconcat (trashname, ".trashinfo", NULL); - infofile = g_build_filename (infodir, infoname, NULL); - g_free (infoname); + while (TRUE) + { + g_free (trashname); + g_free (infofile); - fd = g_open (infofile, O_CREAT | O_EXCL | O_CLOEXEC, 0666); - errsv = errno; - } while (fd == -1 && errsv == EEXIST); + /* Make sure we can create a unique info file */ + trashname = get_unique_filename (basename, i++); + infoname = g_strconcat (trashname, ".trashinfo", NULL); + infofile = g_build_filename (infodir, infoname, NULL); + g_free (infoname); + + fd = g_open (infofile, O_CREAT | O_EXCL | O_CLOEXEC, 0666); + errsv = errno; + + if (fd == -1) + { + if (errsv == EEXIST) + continue; + else if (errsv == ENAMETOOLONG) + { + if (basename_len <= strlen (".trashinfo")) + break; /* fail with ENAMETOOLONG */ + basename_len -= strlen (".trashinfo"); + basename[basename_len] = '\0'; + i = 1; + continue; + } + else + break; /* fail with other error */ + } + + (void) g_close (fd, NULL); + + /* Make sure we can write the info file */ + if (!g_file_set_contents_full (infofile, NULL, 0, + G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING, + 0600, &my_error)) + { + g_unlink (infofile); + if (g_error_matches (my_error, + G_FILE_ERROR, + G_FILE_ERROR_NAMETOOLONG)) + { + if (basename_len <= strlen (".XXXXXX")) + break; /* fail with ENAMETOOLONG */ + basename_len -= strlen (".XXXXXX"); + basename[basename_len] = '\0'; + i = 1; + g_clear_error (&my_error); + continue; + } + else + break; /* fail with other error */ + } + + /* file created */ + break; + } g_free (basename); g_free (infodir); - if (fd == -1) + if (fd == -1 || my_error) { g_free (filesdir); g_free (topdir); g_free (trashname); g_free (infofile); - g_set_io_error (error, - _("Unable to create trashing info file for %s: %s"), - file, errsv); + if (my_error) + g_propagate_error (error, my_error); + else + { + g_set_io_error (error, + _("Unable to create trashing info file for %s: %s"), + file, errsv); + } + return FALSE; } - (void) g_close (fd, NULL); - /* Write the full content of the info file before trashing to make * sure someone doesn't read an empty file. See #749314 */ diff --git a/gio/tests/trash.c b/gio/tests/trash.c index 63b4fee89..277f6ca70 100644 --- a/gio/tests/trash.c +++ b/gio/tests/trash.c @@ -26,6 +26,7 @@ #include #include #include +#include /* Test that g_file_trash() returns G_IO_ERROR_NOT_SUPPORTED for files on system mounts. */ static void @@ -188,6 +189,79 @@ test_trash_symlinks (void) g_free (target); } +/* Test that long filename are handled correctly */ +static void +test_trash_long_filename (void) +{ + const gchar *long_filename = "test_trash_long_filename_aaaaaaaaaaaaaaaaaaaaaaaaa" \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ + "aaaaa"; /* 255 bytes */ + gchar *filepath; + int fd; + GFile *file; + GError *error = NULL; + + /* The test assumes that test file is located on ext fs. */ + filepath = g_build_filename (g_get_home_dir (), long_filename, NULL); + fd = g_open (filepath, O_CREAT | O_RDONLY, 0666); + if (fd == -1) + { + g_test_skip ("Failed to create test file"); + g_free (filepath); + return; + } + (void) g_close (fd, NULL); + file = g_file_new_for_path (filepath); + g_file_trash (file, NULL, &error); + g_unlink (filepath); + g_assert_no_error (error); + + /* Delete trashed version of test file */ + { + GFileEnumerator *enumerator; + GFile *trash; + + trash = g_file_new_for_uri ("trash:///"); + enumerator = g_file_enumerate_children (trash, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_TRASH_ORIG_PATH, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, NULL); + + if (enumerator) + { + GFileInfo *info; + + while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) + { + const char *origpath = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH); + + if (strcmp (filepath, origpath) == 0) + { + GFile *item = g_file_get_child (trash, g_file_info_get_name (info)); + g_file_delete (item, NULL, NULL); + g_object_unref (item); + g_object_unref (info); + break; + } + + g_object_unref (info); + } + + g_file_enumerator_close (enumerator, NULL, NULL); + g_object_unref (enumerator); + } + g_object_unref (trash); + } + + g_free (filepath); + g_object_unref (file); + g_clear_error (&error); +} + int main (int argc, char *argv[]) { @@ -195,6 +269,7 @@ main (int argc, char *argv[]) g_test_add_func ("/trash/not-supported", test_trash_not_supported); g_test_add_func ("/trash/symlinks", test_trash_symlinks); + g_test_add_func ("/trash/long-filename", test_trash_long_filename); return g_test_run (); }