Bug 116371 - Trash appears to be empty, but files remain in ~/.local/share/Trash/files
Summary: Trash appears to be empty, but files remain in ~/.local/share/Trash/files
Status: RESOLVED FIXED
Alias: None
Product: kio
Classification: Frameworks and Libraries
Component: trash (show other bugs)
Version: unspecified
Platform: Fedora RPMs Linux
: NOR normal
Target Milestone: ---
Assignee: David Faure
URL:
Keywords:
: 126357 130780 (view as bug list)
Depends on:
Blocks:
 
Reported: 2005-11-14 20:28 UTC by kim Lux
Modified: 2008-12-24 19:40 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed In:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description kim Lux 2005-11-14 20:28:03 UTC
Version:           3.4.3-1.0-fc4 (using KDE KDE 3.4.3)
Installed from:    Fedora RPMs
OS:                Linux

When I empty trash, the emptying  dialog was appearing and hanging. 

To stop the hang, I pressed the close icon. (X)

After "emptying", the trash icon didn't go to empty it stayed the full icon.  

My hard drive ran out of room, but it didn't make sense. 

I started looking into the situation and found ~/.local/Trash/files had about 10 GB of stuff in it.  ~/.local/Trash/info was empty. 

Trash wasn't emptying.

I did an su rm -rf of ~/.local/Trash/file, which cleaned out my trash. 

Trash hasn't worked properly since I upgraded to kde-3.4.3.
Comment 1 Frank Siegert 2005-12-25 00:53:10 UTC
I can confirm this in KDE 3.5 (Kubuntu packages). If any error appears when deleting a folder from trash (like "no access rights"), the folder is removed from ~/.local/Trash/info and thus not visible in trash:/ anymore, but the folder is still in ~/.local/Trash/files, and thus filling up hardware space, which a newbie would never be able to clean up.
Comment 2 Julien Meunier 2006-02-08 12:07:17 UTC
I confirm this bug too in Kubuntu (KDE 3.5.1). The Trash (in KDE) is empty, but in ~/.local/share/Trash/files, there are a lot of files!
Comment 3 David Faure 2006-02-08 12:16:48 UTC
> I confirm this bug too in Kubuntu (KDE 3.5.1). The Trash (in KDE) is empty, but in ~/.local/share/Trash/files, there are a lot of files!


And in ~/.local/share/Trash/info/ ?
Comment 4 Julien Meunier 2006-02-08 21:52:22 UTC
~/.local/share/Trash/info/ is empty.
Comment 5 David Faure 2006-02-09 09:56:07 UTC
> ~/.local/share/Trash/info/ is empty.

So it's normal that KDE sees the trash as empty.
Now the question is: how can a file remain in files/ and while its info got deleted...
This isn't supposed to happen, of course.
Are those files in files/ owned by the same user, i.e. deletable?
Can you clean up files/ and use the kde trash system again until you find a way 
to end up with a file in files/ but not in info/ ?
Comment 6 Julien Meunier 2006-02-12 18:24:06 UTC
(Sorry for my bad English, I'm a young French)
I erased /.local/share/Trash. When I removed files in Trash, and used the KDE trash... there is nothing in ~/.local/share/Trash/info/ and ~/.local/share/Trash/files/!
Before removing /.local/share/Trash, in the directory files, there was lot of files, and nothing in info.
Comment 7 hardouin 2006-02-12 18:28:27 UTC
This bug looks fixed afted an update of kubuntu !
Comment 8 Emmanuel B 2006-08-01 00:52:38 UTC
Kubuntu 6.06 KDE 3.5.3

there is still a bug with the trash: when emptying the trash, the icon says "empty", but a lot of files & folders stay in ~/.local/share/trash/.

Tonight, i had to clean my HD running out of space, I found about 2.2Go in the ~/.local/share/trash/ folder. But the trash seemed to be empty according to the systray icon and .Trash folder.

I'm using Fat32 mounted USB-Hard-drive to share files with win32 cuptuters.

bug was submitted to (k)ubuntu launchpad with a screen capture:
https://launchpad.net/distros/ubuntu/+source/meta-kde/+bug/30842

best regards

Emmanuel
Comment 9 David Faure 2006-09-13 15:09:02 UTC
*** Bug 130780 has been marked as a duplicate of this bug. ***
Comment 10 David Faure 2006-09-13 17:27:10 UTC
*** Bug 126357 has been marked as a duplicate of this bug. ***
Comment 11 David Faure 2006-09-13 17:39:33 UTC
SVN commit 583812 by dfaure:

Do the equivalent of a "chmod -R a+w" on the directory before attempting to delete it from the trash
 (e.g. when emptying the trash or deleting a single dir from the trash).
This helps fixes readonly dirs not being cleaned up.

Hmm, the problem with root-owned dirs isn't solved though. In fact there is *no* way for a user
to delete such a dir (if not empty)... What then? I guess the info file should stay so that the user
can see that the trash isn't empty, but he/she will have to ask root to clean up the directory, there's
no other way. This is all happening because of the atomic move on the same partition, which can
bring along files/dirs owned by root or other users along with it.

CCBUG: 116371


 M  +48 -7     testtrash.cpp  
 M  +2 -0      testtrash.h  
 M  +25 -8     trashimpl.cpp  
 M  +1 -1      trashimpl.h  


--- branches/KDE/3.5/kdebase/kioslave/trash/testtrash.cpp #583811:583812
@@ -98,6 +98,11 @@
     return QDir::homeDirPath() + "/.kde/testtrash/";
 }
 
+QString TestTrash::readOnlyDirPath() const
+{
+    return homeTmpDir() + QString( "readonly" );
+}
+
 QString TestTrash::otherTmpDir() const
 {
     // This one needs to be on another partition
@@ -191,8 +196,16 @@
         kdWarning() << "No writable partition other than $HOME found, some tests will be skipped" << endl;
 
     // Start with a clean base dir
-    KIO::NetAccess::del( homeTmpDir(), 0 );
-    KIO::NetAccess::del( otherTmpDir(), 0 );
+    if ( QFileInfo( homeTmpDir() ).exists() ) {
+        bool ok = KIO::NetAccess::del( homeTmpDir(), 0 );
+        if ( !ok )
+            kdFatal() << "Couldn't delete " << homeTmpDir() << endl;
+    }
+    if ( QFileInfo( otherTmpDir() ).exists() ) {
+        bool ok = KIO::NetAccess::del( otherTmpDir(), 0 );
+        if ( !ok )
+            kdFatal() << "Couldn't delete " << otherTmpDir() << endl;
+    }
     QDir dir; // TT: why not a static method?
     bool ok = dir.mkdir( homeTmpDir() );
     if ( !ok )
@@ -206,6 +219,7 @@
 
 void TestTrash::cleanTrash()
 {
+    kdDebug() << k_funcinfo << endl;
     // Start with a relatively clean trash too
     removeFile( m_trashDir, "/info/fileFromHome.trashinfo" );
     removeFile( m_trashDir, "/files/fileFromHome" );
@@ -227,6 +241,7 @@
     removeFile( m_trashDir, "/files/brokenSymlinkFromHome" );
     removeFile( m_trashDir, "/info/trashDirFromHome.trashinfo" );
     removeFile( m_trashDir, "/files/trashDirFromHome/testfile" );
+    removeFile( m_trashDir, "/info/readonly.trashinfo" );
     removeDir( m_trashDir, "/files/trashDirFromHome" );
     removeFile( m_trashDir, "/info/trashDirFromHome_1.trashinfo" );
     removeFile( m_trashDir, "/files/trashDirFromHome_1/testfile" );
@@ -234,6 +249,7 @@
     removeFile( m_trashDir, "/info/trashDirFromOther.trashinfo" );
     removeFile( m_trashDir, "/files/trashDirFromOther/testfile" );
     removeDir( m_trashDir, "/files/trashDirFromOther" );
+    KIO::NetAccess::del( m_trashDir + "/files/readonly", 0 );
     // for trashDirectoryOwnedByRoot
     KIO::NetAccess::del( m_trashDir + "/files/cups", 0 );
     KIO::NetAccess::del( m_trashDir + "/files/boot", 0 );
@@ -254,6 +270,7 @@
     trashUtf8FileFromHome();
 #endif
     trashUmlautFileFromHome();
+    trashReadOnlyDirFromHome();
     testTrashNotEmpty();
     trashFileFromOther();
     trashFileIntoOtherPartition();
@@ -599,14 +616,16 @@
 {
     kdDebug() << k_funcinfo << fileId << endl;
     // setup
-    QDir dir;
-    bool ok = dir.mkdir( origPath );
-    Q_ASSERT( ok );
+    if ( !QFileInfo( origPath ).exists() ) {
+        QDir dir;
+        bool ok = dir.mkdir( origPath );
+        Q_ASSERT( ok );
+    }
     createTestFile( origPath + "/testfile" );
     KURL u; u.setPath( origPath );
 
     // test
-    ok = KIO::NetAccess::move( u, "trash:/" );
+    bool ok = KIO::NetAccess::move( u, "trash:/" );
     assert( ok );
     if ( origPath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
         kdDebug() << " TESTS SKIPPED" << endl;
@@ -632,6 +651,23 @@
     trashDirectory( homeTmpDir() + dirName, dirName + "_1" );
 }
 
+void TestTrash::trashReadOnlyDirFromHome()
+{
+    kdDebug() << k_funcinfo << endl;
+    const QString dirName = readOnlyDirPath();
+    QDir dir;
+    bool ok = dir.mkdir( dirName );
+    Q_ASSERT( ok );
+    // #130780
+    const QString subDirPath = dirName + "/readonly_subdir";
+    ok = dir.mkdir( subDirPath );
+    Q_ASSERT( ok );
+    createTestFile( subDirPath + "/testfile_in_subdir" );
+    ::chmod( QFile::encodeName( subDirPath ), 0500 );
+
+    trashDirectory( dirName, "readonly" );
+}
+
 void TestTrash::trashDirectoryFromOther()
 {
     kdDebug() << k_funcinfo << endl;
@@ -1136,7 +1172,7 @@
     // ## Even though we use a custom XDG_DATA_HOME value, emptying the
     // trash would still empty the other trash directories in other partitions.
     // So we can't activate this test by default.
-#if 0
+#if 1
     kdDebug() << k_funcinfo << endl;
     QByteArray packedArgs;
     QDataStream stream( packedArgs, IO_WriteOnly );
@@ -1149,6 +1185,11 @@
     assert( cfg.hasGroup( "Status" ) );
     cfg.setGroup( "Status" );
     assert( cfg.readBoolEntry( "Empty", false ) == true );
+
+    assert( !QFile::exists( m_trashDir + "/files/fileFromHome" ) );
+    assert( !QFile::exists( m_trashDir + "/files/readonly" ) );
+    assert( !QFile::exists( m_trashDir + "/info/readonly.trashinfo" ) );
+
 #else
     kdDebug() << k_funcinfo << " : SKIPPED" << endl;
 #endif
--- branches/KDE/3.5/kdebase/kioslave/trash/testtrash.h #583811:583812
@@ -50,6 +50,7 @@
     void trashSymlinkFromOther();
     void trashBrokenSymlinkFromHome();
     void trashDirectoryFromHome();
+    void trashReadOnlyDirFromHome();
     void trashDirectoryFromOther();
     void trashDirectoryOwnedByRoot();
 
@@ -100,6 +101,7 @@
     QString otherTmpDir() const;
     QString utf8FileName() const;
     QString umlautFileName() const;
+    QString readOnlyDirPath() const;
 
     QString m_trashDir;
 
--- branches/KDE/3.5/kdebase/kioslave/trash/trashimpl.cpp #583811:583812
@@ -30,6 +30,8 @@
 #include <kstandarddirs.h>
 #include <kglobalsettings.h>
 #include <kmountpoint.h>
+#include <kfileitem.h>
+#include <kio/chmodjob.h>
 
 #include <dcopref.h>
 
@@ -181,7 +183,7 @@
     if ( allOK ) {
         // We need to remove the old one, otherwise the desktop will have two trashcans...
         kdDebug() << "Trash migration: all OK, removing old trash directory" << endl;
-        synchronousDel( oldTrashDir, false );
+        synchronousDel( oldTrashDir, false, true );
     }
 }
 
@@ -321,7 +323,7 @@
         if ( QFileInfo( dest ).isFile() )
             QFile::remove( dest );
         else
-            synchronousDel( dest, false );
+            synchronousDel( dest, false, true );
         return false;
     }
     fileAdded();
@@ -477,18 +479,33 @@
 
     QFile::remove( info );
 
-    if ( !synchronousDel( file, true ) )
+    if ( !synchronousDel( file, true, QFileInfo(file).isDir() ) )
         return false;
     fileRemoved();
     return true;
 }
 
-bool TrashImpl::synchronousDel( const QString& path, bool setLastErrorCode )
+bool TrashImpl::synchronousDel( const QString& path, bool setLastErrorCode, bool isDir )
 {
     const int oldErrorCode = m_lastErrorCode;
     const QString oldErrorMsg = m_lastErrorMessage;
     KURL url;
     url.setPath( path );
+
+    // First ensure that all dirs have u+w permissions,
+    // otherwise we won't be able to delete files in them (#130780).
+    if ( isDir ) {
+        kdDebug() << k_funcinfo << "chmod'ing " << url << endl;
+        KFileItem fileItem( url, "inode/directory", KFileItem::Unknown );
+        KFileItemList fileItemList;
+        fileItemList.append( &fileItem );
+        KIO::ChmodJob* chmodJob = KIO::chmod( fileItemList, 0777, 0222, QString::null, QString::null, true /*recursive*/, false /*showProgressInfo*/ );
+        connect( chmodJob, SIGNAL( result(KIO::Job *) ),
+                 this, SLOT( jobFinished(KIO::Job *) ) );
+        qApp->eventLoop()->enterLoop();
+    }
+
+    kdDebug() << k_funcinfo << "deleting " << url << endl;
     KIO::DeleteJob *job = KIO::del( url, false, false );
     connect( job, SIGNAL( result(KIO::Job *) ),
              this, SLOT( jobFinished(KIO::Job *) ) );
@@ -511,11 +528,11 @@
     for ( ; it != m_trashDirectories.end() ; ++it ) {
         QDir dir;
         const QString infoPath = it.data() + "/info";
-        // TODO show errors (with warning()), e.g. when permission denied?
-        synchronousDel( infoPath, false );
+        synchronousDel( infoPath, false, true );
         dir.mkdir( infoPath );
         const QString filesPath = it.data() + "/files";
-        synchronousDel( filesPath, false );
+        // TODO show errors (with warning()), e.g. when permission denied?
+        synchronousDel( filesPath, false, true );
         dir.mkdir( filesPath );
     }
     fileRemoved();
@@ -763,7 +780,7 @@
     uid_t uid = getuid();
     KDE_struct_stat buff;
     // Minimum permissions required: write+execute for 'others', and sticky bit
-    int requiredBits = S_IWOTH | S_IXOTH | S_ISVTX;
+    uint requiredBits = S_IWOTH | S_IXOTH | S_ISVTX;
     if ( KDE_lstat( QFile::encodeName( rootTrashDir ), &buff ) == 0 ) {
         if ( (buff.st_uid == 0) // must be owned by root
              && (S_ISDIR(buff.st_mode)) // must be a dir
--- branches/KDE/3.5/kdebase/kioslave/trash/trashimpl.h #583811:583812
@@ -137,7 +137,7 @@
     QString trashDirectoryPath( int trashId ) const;
     QString topDirectoryPath( int trashId ) const;
 
-    bool synchronousDel( const QString& path, bool setLastErrorCode );
+    bool synchronousDel( const QString& path, bool setLastErrorCode, bool isDir );
 
     void scanTrashDirectories() const;
 
Comment 12 David Faure 2006-09-13 19:28:58 UTC
SVN commit 583856 by dfaure:

Only remove the entry (.trashinfo file) if the deletion succeeded; otherwise report the error.
This ensures that we don't end up with "trash appears to be empty but it still takes much hdd space".
BUG: 116371


 M  +4 -2      kio_trash.cpp  
 M  +1 -1      testtrash.cpp  
 M  +20 -16    trashimpl.cpp  
 M  +1 -1      trashimpl.h  


--- branches/KDE/3.5/kdebase/kioslave/trash/kio_trash.cpp #583855:583856
@@ -466,8 +466,10 @@
 
     switch (cmd) {
     case 1:
-        impl.emptyTrash();
-        finished();
+        if ( impl.emptyTrash() )
+            finished();
+        else
+            error( impl.lastErrorCode(), impl.lastErrorMessage() );
         break;
     case 2:
         impl.migrateOldTrash();
--- branches/KDE/3.5/kdebase/kioslave/trash/testtrash.cpp #583855:583856
@@ -1172,7 +1172,7 @@
     // ## Even though we use a custom XDG_DATA_HOME value, emptying the
     // trash would still empty the other trash directories in other partitions.
     // So we can't activate this test by default.
-#if 0
+#if 1
     kdDebug() << k_funcinfo << endl;
     QByteArray packedArgs;
     QDataStream stream( packedArgs, IO_WriteOnly );
--- branches/KDE/3.5/kdebase/kioslave/trash/trashimpl.cpp #583855:583856
@@ -477,10 +477,10 @@
         return false;
     }
 
-    QFile::remove( info );
-
     if ( !synchronousDel( file, true, QFileInfo(file).isDir() ) )
         return false;
+
+    QFile::remove( info );
     fileRemoved();
     return true;
 }
@@ -518,24 +518,28 @@
     return ok;
 }
 
-void TrashImpl::emptyTrash()
+bool TrashImpl::emptyTrash()
 {
     kdDebug() << k_funcinfo << endl;
-    if ( !m_trashDirectoriesScanned )
-        scanTrashDirectories();
-    // For each known trash directory...
-    TrashDirMap::const_iterator it = m_trashDirectories.begin();
-    for ( ; it != m_trashDirectories.end() ; ++it ) {
-        QDir dir;
-        const QString infoPath = it.data() + "/info";
-        synchronousDel( infoPath, false, true );
-        dir.mkdir( infoPath );
-        const QString filesPath = it.data() + "/files";
-        // TODO show errors (with warning()), e.g. when permission denied?
-        synchronousDel( filesPath, false, true );
-        dir.mkdir( filesPath );
+    // The naive implementation "delete info and files in every trash directory"
+    // breaks when deleted directories contain files owned by other users.
+    // We need to ensure that the .trashinfo file is only removed when the
+    // corresponding files could indeed be removed.
+
+    const TrashedFileInfoList fileInfoList = list();
+
+    TrashedFileInfoList::const_iterator it = fileInfoList.begin();
+    const TrashedFileInfoList::const_iterator end = fileInfoList.end();
+    for ( ; it != end ; ++it ) {
+        const TrashedFileInfo& info = *it;
+        const QString filesPath = info.physicalPath;
+        if ( synchronousDel( filesPath, true, true ) ) {
+            QFile::remove( infoPath( info.trashId, info.fileId ) );
+        } // else error code is set
     }
     fileRemoved();
+
+    return m_lastErrorCode == 0;
 }
 
 TrashImpl::TrashedFileInfoList TrashImpl::list()
--- branches/KDE/3.5/kdebase/kioslave/trash/trashimpl.h #583855:583856
@@ -72,7 +72,7 @@
     bool del( int trashId, const QString& fileId );
 
     /// Empty trash, i.e. delete all trashed files
-    void emptyTrash();
+    bool emptyTrash();
 
     /// Return true if the trash is empty
     bool isEmpty() const;
Comment 13 David Faure 2006-09-13 22:42:22 UTC
SVN commit 583941 by dfaure:

OK, u+w, not a+w, since a+w doesn't give us more chances of successful deletion of root-owned subdirs anyway.
CCBUG: 116371


 M  +1 -1      trashimpl.cpp  


--- branches/KDE/3.5/kdebase/kioslave/trash/trashimpl.cpp #583940:583941
@@ -499,7 +499,7 @@
         KFileItem fileItem( url, "inode/directory", KFileItem::Unknown );
         KFileItemList fileItemList;
         fileItemList.append( &fileItem );
-        KIO::ChmodJob* chmodJob = KIO::chmod( fileItemList, 0777, 0222, QString::null, QString::null, true /*recursive*/, false /*showProgressInfo*/ );
+        KIO::ChmodJob* chmodJob = KIO::chmod( fileItemList, 0200, 0200, QString::null, QString::null, true /*recursive*/, false /*showProgressInfo*/ );
         connect( chmodJob, SIGNAL( result(KIO::Job *) ),
                  this, SLOT( jobFinished(KIO::Job *) ) );
         qApp->eventLoop()->enterLoop();
Comment 14 David Faure 2008-12-24 19:40:45 UTC
Seems some people still have orphaned files in ~/.local/share/Trash/files:  https://bugs.launchpad.net/ubuntu/+source/meta-kde/+bug/30842, KDE bug 167051.
Let's discuss that in 167051.