GNOME Bugzilla – Bug 764532
Unable to trash file: File exists
Last modified: 2016-09-09 14:22:07 UTC
After deleting multiple files with the same name, I get the following error: An error occured when sending image 'test.jpg' to trash. Unable to trash file: File exists strace ristretto '/media/c/test.jpg' 2>&1 | grep -i 'file' 7615:sendmsg(5, {msg_name(0)=NULL, msg_iov(2)=[{"l\1\0\1\324\0\0\0\36\0\0\0\265\0\0\0\1\1o\0(\0\0\0/org/fre"..., 200}, {"\236\0\0\0\231\0\0\0file:///media/"..., 212}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 412 8084:mkdir("/media/c/.Trash-1000/info", 0700) = -1 EEXIST (File exists) 8085:mkdir("/media/c/.Trash-1000/files", 0700) = -1 EEXIST (File exists) 8098:fstatfs(10, {f_type="MSDOS_SUPER_MAGIC", f_bsize=65536, f_blocks=3509567, f_bfree=106394, f_bavail=106394, f_files=0, f_ffree=0, f_fsid={65031, 0}, f_namelen=1530, f_frsize=65536, f_flags=4128}) = 0 8102:rename("/media/c/test.jpg", "/media/c/.Trash-1000/files/test.jpg") = -1 EEXIST (File exists) For some reason this bug is spurious. I know that the problem seems to be files with the same name, but simpley creating two files with the same name and deleting it works. I get errors in ristretto, FreeFileSync(very often, because I sync many files and overwriting a file will first trash a file and then create a new version!) and nemo. Weirdly(!) calling 'trash /media/c/test.jpg' from command line will delete the file correctly, even for those files giving errors in ristretto, nemo and FreeFileSync. It seems to me, that those programs use an interface, which maybe because of caching or something doesn't recognize that "/media/c/.Trash-1000/files/test.jpg" already exists. The 'trash' command and also normally those programs should and would rename it to "/media/c/.Trash-1000/files/test.2.jpg" in that case. Quick workaround: Call 'trash-empty' when the error happens. I will update if I find out, how to reproduce it with certainty.
The problem seems to be a bug concerning case sensitiveness and vfat! I have the bug on my USB thumb drive (/media/transcend) and on another partition (/media/c), but not in / and also not in /media/d. Here is what df -T says about those partitions: Type Mounted on ext4 / vfat /media/c fuseblk /media/d vfat /media/transcend Note that the error only happens on vfat, which is FAT32. How to reproduce on a vfat file system in command line: # Think before you uncomment; this is only to show, that the state of trash doesn't matter #command rm -r ./.Trash* touch test && trash test # following files were created: find ./.Trash* -type f # ./.Trash/1000/info/test.trashinfo # ./.Trash/1000/files/test touch Test && trash Test # following file do now exist: find ./.Trash* -type f # ./.Trash/1000/info/test.trashinfo # ./.Trash/1000/files/test # ./.Trash-1000/info/Test.trashinfo # ./.Trash-1000/files/Test # => instead of ./Trash ./Trash-1000 was created and used! I always wondered, why I often had both folders! touch teSt && trash teSt # trash: cannot trash regular empty file `teSt' When using nemo / FreeFileSync or others the problem can be reproduced earlier, because those won't create './.Trash/1000' at all! touch test # go to nemo and delete 'test' there find ./.Trash* -type f # ./.Trash-1000/info/test.trashinfo # ./.Trash-1000/files/test touch Test # go to nemo and delete 'Test' there # Cannot move file to trash, do you want to delete immediately? # Show more details: Unable to trash file: File exists
Trashing is handled by GLib. So this is probably bug in g_local_file_trash... (In reply to xyzdragon from comment #1) > The problem seems to be a bug concerning case sensitiveness and vfat! > > I have the bug on my USB thumb drive (/media/transcend) and on another > partition (/media/c), but not in / and also not in /media/d. > > Here is what df -T says about those partitions: > > Type Mounted on > ext4 / > vfat /media/c > fuseblk /media/d > vfat /media/transcend > > Note that the error only happens on vfat, which is FAT32. > > How to reproduce on a vfat file system in command line: > > # Think before you uncomment; this is only to show, that the state of > trash doesn't matter > #command rm -r ./.Trash* > touch test && trash test > # following files were created: > find ./.Trash* -type f > # ./.Trash/1000/info/test.trashinfo > # ./.Trash/1000/files/test > touch Test && trash Test > # following file do now exist: > find ./.Trash* -type f > # ./.Trash/1000/info/test.trashinfo > # ./.Trash/1000/files/test > # ./.Trash-1000/info/Test.trashinfo > # ./.Trash-1000/files/Test > # => instead of ./Trash ./Trash-1000 was created and used! I always > wondered, why I often had both folders! This is probably bug in trash-cli, which is not implemented using GLib. It should not create .Trash-1000 if .Trash/1000 exists. You might want to check this with gvfs-trash instead. > touch teSt && trash teSt > # trash: cannot trash regular empty file `teSt' > > When using nemo / FreeFileSync or others the problem can be reproduced > earlier, because those won't create './.Trash/1000' at all! > > touch test > # go to nemo and delete 'test' there > find ./.Trash* -type f > # ./.Trash-1000/info/test.trashinfo > # ./.Trash-1000/files/test > touch Test > # go to nemo and delete 'Test' there > # Cannot move file to trash, do you want to delete immediately? > # Show more details: Unable to trash file: File exists I wonder why it fails with "File exists", code to generate a unique name for trashfile should not have problems with cases... Hmm, it might signalize some problem with file permissions... are the trash file permissions correctly set to your user?
> This is probably bug in trash-cli Ah, interesting, I didn't even notice that 'trash' is different from 'gvfs-trash', thanks. #command rm -r ./.Trash* touch test && gvfs-trash test find ./.Trash* -exec bash -c 'echo {}; ls -la "{}"' \; ./.Trash-1000 total 256 drwx------ 4 xyzdragon xyzdragon 65536 Apr 4 11:28 . drwx------ 66 xyzdragon xyzdragon 65536 Apr 4 11:28 .. drwx------ 2 xyzdragon xyzdragon 65536 Apr 4 11:28 files drwx------ 2 xyzdragon xyzdragon 65536 Apr 4 11:28 info ./.Trash-1000/info total 192 drwx------ 2 xyzdragon xyzdragon 65536 Apr 4 11:28 . drwx------ 4 xyzdragon xyzdragon 65536 Apr 4 11:28 .. -rwx------ 1 xyzdragon xyzdragon 56 Apr 4 11:28 test.trashinfo ./.Trash-1000/info/test.trashinfo -rwx------ 1 xyzdragon xyzdragon 56 Apr 4 11:28 ./.Trash-1000/info/test.trashinfo ./.Trash-1000/files total 128 drwx------ 2 xyzdragon xyzdragon 65536 Apr 4 11:28 . drwx------ 4 xyzdragon xyzdragon 65536 Apr 4 11:28 .. -rwx------ 1 xyzdragon xyzdragon 0 Apr 4 11:28 test ./.Trash-1000/files/test -rwx------ 1 xyzdragon xyzdragon 0 Apr 4 11:28 ./.Trash-1000/files/test touch Test && gvfs-trash Test Error trashing file: Unable to trash file: File exists => this explains the behavior of nemo, FreeFileSync and Co. So only trash-cli creates that additional folder on error. gvfs-trash fails already on first name clash. The permissions look correct to me, I think ...
> This is probably bug in trash-cli, which is not implemented using GLib. It should not create .Trash-1000 if .Trash/1000 exists. https://specifications.freedesktop.org/trash-spec/trashspec-0.8.html > (2) If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be used as the user's trash directory for this device/partition. $uid is the user's numeric identifier. > > The following paragraph applies ONLY to the case when the implementation supports trashing in the top directory, and a $topdir/.Trash does not exist or has not passed the checks: > > When trashing a file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST immediately create it, without any warnings or delays for the user. I guess not being able to write, because it didn't notice it had to create a unique name, could count as "has not passed the checks" ... not sure though
After many hours of debugging and trying to create minimal non-working examples it basically boils down to this problem: http://superuser.com/questions/896946/why-mounted-vfat-volume-is-case-sensitive-partially Here is a minimal working example showing that: /* gcc glibc-open.c && ( dir=$(pwd); cd $(readlink fat32mount) && "$dir/a.out" . ) */ #include <fcntl.h> #include <stdio.h> int main( void ) { int fd1, fd2; fd1 = open( "mImImImI", O_CREAT | O_EXCL, 0666 ); // Works fd2 = open( "MiMiMiMi", O_CREAT | O_EXCL, 0666 ); // Fails! printf( "fd1 = %i, fd2 = %i\n", fd1, fd2 ); fd1 = open( "mImImImIm", O_CREAT | O_EXCL, 0666 ); // Works fd2 = open( "MiMiMiMiM", O_CREAT | O_EXCL, 0666 ); // Works, too because name is longer than 8 characters ! printf( "fd1 = %i, fd2 = %i\n", fd1, fd2 ); return 0; } The directory listing after executing this: -rw-r--r-- 1 0 Apr 23 23:28 mImImImI -rw-r--r-- 1 0 Apr 23 23:28 mImImImIm -rw-r--r-- 1 0 Apr 23 23:28 MiMiMiMiM So why does this matter in g_local_file_trash ? Because g_local_file_trash checks if it can open '.Trash/info/mImi.trashinfo' which will be true even if '.Trash/info/Mimi.trashinfo' exists, because 'Mimi.trashinfo' is far longer than 8 characters. The problem then happens when trying to move 'mImi' to '.Trash/info/mImi' where './Trash/files/Mimi' already exists. It will fail, because 'mImi' is far shorter than 8 characters, meaning it won't (can't ?! -> if FAT allows long file names even for short ones, then I would classify this behavior as a bug) use the long file name extension, i.e. something like 'mimi~1' and 'mimi~2'! The fix a attach is relatively easy, but I don't know if it follows any rules for programming GLib, I would be grateful if someone could check it. I hope I'm at the right place here for posting patches. The patch just adds a check for './Trash/files/$basename' additionally to './Trash/info/$basename.trashinfo' in the loop where the unique file name is decided. As the file will be created in the process it will delete that that opened file after that. The patch was created with: git diff --ignore-space-at-eol gio/glocalfile.c > bug-764532.patch Here is a minimal test which fails before the patch and works with the patch applied: /* sudo apt-get install -t sid libglib2.0-dev libglib2.0-doc libselinux1-dev libffi-dev git clone git://git.gnome.org/glib && cd glib mkdir -p ./release && ./autogen.sh ./configure --enable-static --enable-debug=yes --prefix="$(pwd)/release" && make -j 4 && make install g++ -g -Wall -Wextra \ -I /opt/glib -I /opt/glib/gmodule -I /opt/glib/glib \ trash.cpp \ /opt/glib/gio/.libs/libgio-2.0.a \ /opt/glib/gobject/.libs/libgobject-2.0.a \ /opt/glib/gmodule/.libs/libgmodule-2.0.a \ /opt/glib/glib/.libs/libglib-2.0.a \ -lffi -lpthread -lpcre -lz -lresolv -lselinux -ldl ( dir=$(pwd); cd $(readlink fat32mount) && "$dir/a.out" . ) */ #include <gio/gio.h> #include <stdio.h> int createAndTrash(const char * folder, const char * fname ) { char path[2048]; sprintf( path, "%s/%s", folder, fname ); printf( "Create and trash '%s'\n", path ); /* create file to trash */ FILE * createdFile = fopen( path, "w+" ); if ( createdFile == NULL ) { printf( "Couldn't create or open file!\n" ); return 2; } else { fclose( createdFile ); } /* move file to trash https://developer.gnome.org/gio/stable/GFile.html#g-file-trash */ GError * err = NULL; bool const trashed = g_file_trash( g_file_new_for_path( path ), NULL, &err ); if ( trashed ) { printf( "Was trashed.\n" ); } else { printf( "Error while trashing! ( GError.code = %i, message = %s )\n", err->code, err->message ); remove( path ); return 3; } return 0; } int main( int argc, const char ** argv ) { if ( argc < 2 ) return 1; if ( createAndTrash( argv[1], "Mimi" ) != 0 ) return 1; if ( createAndTrash( argv[1], "mImi" ) != 0 ) return 1; if ( createAndTrash( argv[1], "miMi" ) != 0 ) return 1; if ( createAndTrash( argv[1], "mimI" ) != 0 ) return 1; return 0; } Btw: the git state has multiple cases of trailing whitespaces! As my editor automatically strips those of, it is not fun to work with the source.
Created attachment 326610 [details] [review] patch suggestion
No feedback from anyone?
Review of attachment 326610 [details] [review]: Hmm, you are right, vfat is really stupid, because it is case insensitive up to 8 chars and case sensitive for more than 8 chars... ::: gio/glocalfile.c @@ +2088,3 @@ + if (fd != -1) + { + trashfile = g_build_filename (filesdir, trashname, NULL); Don't forget g_free (trashfile) somewhere... @@ +2089,3 @@ + { + trashfile = g_build_filename (filesdir, trashname, NULL); + fddst = open (trashfile, O_CREAT | O_EXCL, 0666); You should use g_open instead of open with glib, however it would be nicer to use g_file_test (filename, G_FILE_TEST_EXISTS)... Although there is a possibility for a race, when deleting two files with a same name, but it is probably acceptable in this case...
Review of attachment 326610 [details] [review]: You should also create the patch using "git format-patch", see the following article please: https://wiki.gnome.org/Newcomers/SubmittingPatches
Created attachment 335166 [details] [review] glocalfile: Avoid EEXIST on FAT when trashing So there is improved patch. The problem is that I am not able to reproduce the bug. Maybe it was a kernel bug and it was fixed... are you still able to reproduce this issue? Can you please provide more info, how is the disk formated, how is it mounted, what kernel version do you have?
@Ondrej Thanks for improving the patch. I just procastinated doing this myself, sorry. It works for me with every FAT32 mounted system, e.g. my USB thumb drive. Here are is how to reproduce this with a virtual file system: fallocate -l 1M trashTestDrive.img mkfs -t vfat trashTestDrive.img mkdir -p mountedTestDrive sudo mount -o loop,umask=0000,uid=$UID trashTestDrive.img mountedTestDrive cd mountedTestDrive/ # trashing part touch test && gvfs-trash test touch Test && gvfs-trash Test Error trashing file: Unable to trash file: File exists If that does not work for you, then I don't know. My Kernel version is: uname -a Linux hypatia-pc 4.5.0-1-amd64 #1 SMP Debian 4.5.1-1 (2016-04-14) x86_64 GNU/Linux
Thanks for your response. Unfortunately, it works correctly for me and I can see: $ ls .Trash-1000/files/ test Test.2 $ uname -a Linux t450s 4.7.2-201.fc24.x86_64 #1 SMP Fri Aug 26 15:58:40 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux Can you please retest this with a newer kernel? It seems that Debian unstable has 4.7.0, experimental even 4.8.0...
Sorry, can't test it. After sudo apt-get install -t sid update, upgrade, dist-upgrade my system does not boot anymore ... ... That is why I never update and why I hate Linux with a passion.
Sorry. The dist-upgrade can be dangerous and I am convinced that Debian has some guides to upgrade system in order to minimize such problems. I suppose you could use the following in order to test just new kernel and to not upgrade the whole distro. Then you could simply boot the previous kernel if the new one doesn't work. $ sudo apt-get -t sid install linux-image
This is probably a rather kernel bug, so let's close this. Please reopen this bug with additional info if you see such problems also with newer kernels...