Merge branch 'trashing-long-file-name' into 'main'

GLocalFile: support trashing long file name

See merge request GNOME/glib!3697
This commit is contained in:
Philip Withnall 2024-06-19 10:11:50 +00:00
commit 07fa7b261c
2 changed files with 145 additions and 17 deletions

View File

@ -1997,6 +1997,8 @@ g_local_file_trash (GFile *file,
GVfsClass *class; GVfsClass *class;
GVfs *vfs; GVfs *vfs;
int errsv; int errsv;
size_t basename_len;
GError *my_error = NULL;
if (glib_should_use_portal ()) if (glib_should_use_portal ())
return g_trash_portal_trash_file (file, error); return g_trash_portal_trash_file (file, error);
@ -2224,40 +2226,91 @@ g_local_file_trash (GFile *file,
g_free (trashdir); g_free (trashdir);
basename = g_path_get_basename (local->filename); basename = g_path_get_basename (local->filename);
basename_len = strlen (basename);
i = 1; i = 1;
trashname = NULL; trashname = NULL;
infofile = NULL; infofile = NULL;
do { while (TRUE)
g_free (trashname); {
g_free (infofile); 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);
fd = g_open (infofile, O_CREAT | O_EXCL | O_CLOEXEC, 0666); /* Make sure we can create a unique info file */
errsv = errno; trashname = get_unique_filename (basename, i++);
} while (fd == -1 && errsv == EEXIST); 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 (basename);
g_free (infodir); g_free (infodir);
if (fd == -1) if (fd == -1 || my_error)
{ {
g_free (filesdir); g_free (filesdir);
g_free (topdir); g_free (topdir);
g_free (trashname); g_free (trashname);
g_free (infofile); g_free (infofile);
g_set_io_error (error, if (my_error)
_("Unable to create trashing info file for %s: %s"), g_propagate_error (error, my_error);
file, errsv); else
{
g_set_io_error (error,
_("Unable to create trashing info file for %s: %s"),
file, errsv);
}
return FALSE; return FALSE;
} }
(void) g_close (fd, NULL);
/* Write the full content of the info file before trashing to make /* Write the full content of the info file before trashing to make
* sure someone doesn't read an empty file. See #749314 * sure someone doesn't read an empty file. See #749314
*/ */

View File

@ -26,6 +26,7 @@
#include <glib/gstdio.h> #include <glib/gstdio.h>
#include <gio/gio.h> #include <gio/gio.h>
#include <gio/gunixmounts.h> #include <gio/gunixmounts.h>
#include <fcntl.h>
/* Test that g_file_trash() returns G_IO_ERROR_NOT_SUPPORTED for files on system mounts. */ /* Test that g_file_trash() returns G_IO_ERROR_NOT_SUPPORTED for files on system mounts. */
static void static void
@ -188,6 +189,79 @@ test_trash_symlinks (void)
g_free (target); 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 int
main (int argc, char *argv[]) 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/not-supported", test_trash_not_supported);
g_test_add_func ("/trash/symlinks", test_trash_symlinks); g_test_add_func ("/trash/symlinks", test_trash_symlinks);
g_test_add_func ("/trash/long-filename", test_trash_long_filename);
return g_test_run (); return g_test_run ();
} }