1 /* This file is part of the KDE project
2 Copyright (C) 2004 David Faure <faure@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
20 #include "trashimpl.h"
21 #include "discspaceutil.h"
25 #include <kio/global.h>
26 #include <kio/renamedialog.h>
28 #include <kio/chmodjob.h>
29 #include <kio/copyjob.h>
30 #include <kio/deletejob.h>
33 #include <kdirnotify.h>
35 #include <kstandarddirs.h>
36 #include <kglobalsettings.h>
37 #include <kfileitem.h>
38 #include <kconfiggroup.h>
39 #include <kmountpoint.h>
41 #include <QApplication>
45 #include <kjobuidelegate.h>
48 #include <sys/types.h>
49 #include <sys/param.h>
56 #include <solid/device.h>
57 #include <solid/block.h>
58 #include <solid/storageaccess.h>
60 TrashImpl::TrashImpl() :
63 m_initStatus( InitToBeDone
),
65 m_trashDirectoriesScanned( false ),
66 // not using kio_trashrc since KIO uses that one already for kio_trash
67 // so better have a separate one, for faster parsing by e.g. kmimetype.cpp
71 if ( KDE_lstat( QFile::encodeName( QDir::homePath() ), &buff
) == 0 ) {
72 m_homeDevice
= buff
.st_dev
;
74 kError() << "Should never happen: couldn't stat $HOME " << strerror( errno
) << endl
;
79 * Test if a directory exists, create otherwise
80 * @param _name full path of the directory
81 * @return errorcode, or 0 if the dir was created or existed already
82 * Warning, don't use return value like a bool
84 int TrashImpl::testDir( const QString
&_name
) const
86 DIR *dp
= opendir( QFile::encodeName(_name
) );
90 if ( name
.endsWith( '/' ) )
91 name
.truncate( name
.length() - 1 );
92 QByteArray path
= QFile::encodeName(name
);
94 bool ok
= KDE_mkdir( path
, S_IRWXU
) == 0;
95 if ( !ok
&& errno
== EEXIST
) {
96 #if 0 // this would require to use SlaveBase's method to ask the question
97 //int ret = KMessageBox::warningYesNo( 0, i18n("%1 is a file, but KDE needs it to be a directory. Move it to %2.orig and create directory?").arg(name).arg(name) );
98 //if ( ret == KMessageBox::Yes ) {
100 if ( KDE_rename( path
, path
+ ".orig" ) == 0 ) {
101 ok
= KDE_mkdir( path
, S_IRWXU
) == 0;
102 } else { // foo.orig existed already. How likely is that?
106 return KIO::ERR_DIR_ALREADY_EXIST
;
116 //KMessageBox::sorry( 0, i18n( "Could not create directory %1. Check for permissions." ).arg( name ) );
117 kWarning() << "could not create " << name
;
118 return KIO::ERR_COULD_NOT_MKDIR
;
120 kDebug() << name
<< " created.";
123 else // exists already
130 bool TrashImpl::init()
132 if ( m_initStatus
== InitOK
)
134 if ( m_initStatus
== InitError
)
137 // Check the trash directory and its info and files subdirs
138 // see also kdesktop/init.cc for first time initialization
139 m_initStatus
= InitError
;
140 // $XDG_DATA_HOME/Trash, i.e. ~/.local/share/Trash by default.
141 const QString xdgDataDir
= KGlobal::dirs()->localxdgdatadir();
142 if ( !KStandardDirs::makeDir( xdgDataDir
, 0700 ) ) {
143 kWarning() << "failed to create " << xdgDataDir
;
147 const QString trashDir
= xdgDataDir
+ "Trash";
149 if ( ( err
= testDir( trashDir
) ) ) {
150 error( err
, trashDir
);
153 if ( ( err
= testDir( trashDir
+ "/info" ) ) ) {
154 error( err
, trashDir
+ "/info" );
157 if ( ( err
= testDir( trashDir
+ "/files" ) ) ) {
158 error( err
, trashDir
+ "/files" );
161 m_trashDirectories
.insert( 0, trashDir
);
162 m_initStatus
= InitOK
;
163 kDebug() << "initialization OK, home trash dir: " << trashDir
;
167 void TrashImpl::migrateOldTrash()
171 KConfigGroup
g( KGlobal::config(), "Paths" );
172 const QString oldTrashDir
= g
.readPathEntry( "Trash", QString() );
174 if ( oldTrashDir
.isEmpty() )
177 const QStringList entries
= listDir( oldTrashDir
);
179 for ( QStringList::const_iterator entryIt
= entries
.begin(), entryEnd
= entries
.end();
180 entryIt
!= entryEnd
; ++entryIt
)
182 QString srcPath
= *entryIt
;
183 if ( srcPath
== "." || srcPath
== ".." || srcPath
== ".directory" )
185 srcPath
.prepend( oldTrashDir
); // make absolute
188 if ( !createInfo( srcPath
, trashId
, fileId
) ) {
189 kWarning() << "Trash migration: failed to create info for " << srcPath
;
192 bool ok
= moveToTrash( srcPath
, trashId
, fileId
);
194 (void)deleteInfo( trashId
, fileId
);
195 kWarning() << "Trash migration: failed to create info for " << srcPath
;
198 kDebug() << "Trash migration: moved " << srcPath
;
203 // We need to remove the old one, otherwise the desktop will have two trashcans...
204 kDebug() << "Trash migration: all OK, removing old trash directory";
205 synchronousDel( oldTrashDir
, false, true );
209 bool TrashImpl::createInfo( const QString
& origPath
, int& trashId
, QString
& fileId
)
211 kDebug() << origPath
;
213 const QByteArray
origPath_c( QFile::encodeName( origPath
) );
215 * off_t should be 64bit on Unix systems to have large file support
216 * FIXME: on windows this gets disabled until trash gets integrated
219 char off_t_should_be_64bit
[sizeof(off_t
) >= 8 ? 1:-1]; (void)off_t_should_be_64bit
;
221 KDE_struct_stat buff_src
;
222 if ( KDE_lstat( origPath_c
.data(), &buff_src
) == -1 ) {
223 if ( errno
== EACCES
)
224 error( KIO::ERR_ACCESS_DENIED
, origPath
);
226 error( KIO::ERR_DOES_NOT_EXIST
, origPath
);
230 // Choose destination trash
231 trashId
= findTrashDirectory( origPath
);
233 kWarning() << "OUCH - internal error, TrashImpl::findTrashDirectory returned " << trashId
;
234 return false; // ### error() needed?
236 kDebug() << "trashing to " << trashId
;
238 // Grab original filename
240 url
.setPath( origPath
);
241 const QString origFileName
= url
.fileName();
243 // Make destination file in info/
244 url
.setPath( infoPath( trashId
, origFileName
) ); // we first try with origFileName
246 baseDirectory
.setPath( url
.directory() );
247 // Here we need to use O_EXCL to avoid race conditions with other kioslave processes
250 kDebug() << "trying to create " << url
.path() ;
251 fd
= KDE_open( QFile::encodeName( url
.path() ), O_WRONLY
| O_CREAT
| O_EXCL
, 0600 );
253 if ( errno
== EEXIST
) {
254 url
.setFileName( KIO::RenameDialog::suggestName( baseDirectory
, url
.fileName() ) );
255 // and try again on the next iteration
257 error( KIO::ERR_COULD_NOT_WRITE
, url
.path() );
262 const QString infoPath
= url
.path();
263 fileId
= url
.fileName();
264 Q_ASSERT( fileId
.endsWith( ".trashinfo" ) );
265 fileId
.truncate( fileId
.length() - 10 ); // remove .trashinfo from fileId
267 FILE* file
= ::fdopen( fd
, "w" );
268 if ( !file
) { // can't see how this would happen
269 error( KIO::ERR_COULD_NOT_WRITE
, infoPath
);
273 // Contents of the info file. We could use KSimpleConfig, but that would
274 // mean closing and reopening fd, i.e. opening a race condition...
275 QByteArray info
= "[Trash Info]\n";
277 // Escape filenames according to the way they are encoded on the filesystem
278 // All this to basically get back to the raw 8-bit representation of the filename...
279 if ( trashId
== 0 ) // home trash: absolute path
280 info
+= QUrl::toPercentEncoding( origPath
, "/" );
282 info
+= QUrl::toPercentEncoding( makeRelativePath( topDirectoryPath( trashId
), origPath
), "/" );
284 info
+= "DeletionDate=";
285 info
+= QDateTime::currentDateTime().toString( Qt::ISODate
).toLatin1();
287 size_t sz
= info
.size();
289 size_t written
= ::fwrite(info
.data(), 1, sz
, file
);
290 if ( written
!= sz
) {
292 QFile::remove( infoPath
);
293 error( KIO::ERR_DISK_FULL
, infoPath
);
299 kDebug() << "info file created in trashId=" << trashId
<< " : " << fileId
;
303 QString
TrashImpl::makeRelativePath( const QString
& topdir
, const QString
& path
)
305 const QString realPath
= KStandardDirs::realFilePath( path
);
306 // topdir ends with '/'
307 if ( realPath
.startsWith( topdir
) ) {
308 const QString rel
= realPath
.mid( topdir
.length() );
309 Q_ASSERT( rel
[0] != '/' );
311 } else { // shouldn't happen...
312 kWarning() << "Couldn't make relative path for " << realPath
<< " (" << path
<< "), with topdir=" << topdir
;
317 void TrashImpl::enterLoop()
319 QEventLoop eventLoop
;
320 connect(this, SIGNAL(leaveModality()),
321 &eventLoop
, SLOT(quit()));
322 eventLoop
.exec(QEventLoop::ExcludeUserInputEvents
);
325 QString
TrashImpl::infoPath( int trashId
, const QString
& fileId
) const
327 QString trashPath
= trashDirectoryPath( trashId
);
328 trashPath
+= "/info/";
330 trashPath
+= ".trashinfo";
334 QString
TrashImpl::filesPath( int trashId
, const QString
& fileId
) const
336 QString trashPath
= trashDirectoryPath( trashId
);
337 trashPath
+= "/files/";
342 bool TrashImpl::deleteInfo( int trashId
, const QString
& fileId
)
344 bool ok
= QFile::remove( infoPath( trashId
, fileId
) );
350 bool TrashImpl::moveToTrash( const QString
& origPath
, int trashId
, const QString
& fileId
)
353 if ( !adaptTrashSize( origPath
, trashId
) )
356 const QString dest
= filesPath( trashId
, fileId
);
357 if ( !move( origPath
, dest
) ) {
358 // Maybe the move failed due to no permissions to delete source.
359 // In that case, delete dest to keep things consistent, since KIO doesn't do it.
360 if ( QFileInfo( dest
).isFile() )
361 QFile::remove( dest
);
363 synchronousDel( dest
, false, true );
370 bool TrashImpl::moveFromTrash( const QString
& dest
, int trashId
, const QString
& fileId
, const QString
& relativePath
)
372 QString src
= filesPath( trashId
, fileId
);
373 if ( !relativePath
.isEmpty() ) {
377 if ( !move( src
, dest
) )
382 bool TrashImpl::move( const QString
& src
, const QString
& dest
)
384 if ( directRename( src
, dest
) ) {
385 // This notification is done by KIO::moveAs when using the code below
386 // But if we do a direct rename we need to do the notification ourselves
387 org::kde::KDirNotify::emitFilesAdded( dest
);
390 if ( m_lastErrorCode
!= KIO::ERR_UNSUPPORTED_ACTION
)
393 KUrl urlSrc
, urlDest
;
394 urlSrc
.setPath( src
);
395 urlDest
.setPath( dest
);
396 kDebug() << urlSrc
<< " -> " << urlDest
;
397 KIO::CopyJob
* job
= KIO::moveAs( urlSrc
, urlDest
, KIO::HideProgressInfo
);
398 job
->setUiDelegate(0);
399 connect( job
, SIGNAL( result(KJob
*) ),
400 this, SLOT( jobFinished(KJob
*) ) );
403 return m_lastErrorCode
== 0;
406 void TrashImpl::jobFinished(KJob
* job
)
408 kDebug() << " error=" << job
->error();
409 error( job
->error(), job
->errorText() );
410 emit
leaveModality();
413 bool TrashImpl::copyToTrash( const QString
& origPath
, int trashId
, const QString
& fileId
)
416 if ( !adaptTrashSize( origPath
, trashId
) )
419 const QString dest
= filesPath( trashId
, fileId
);
420 if ( !copy( origPath
, dest
) )
426 bool TrashImpl::copyFromTrash( const QString
& dest
, int trashId
, const QString
& fileId
, const QString
& relativePath
)
428 QString src
= filesPath( trashId
, fileId
);
429 if ( !relativePath
.isEmpty() ) {
433 return copy( src
, dest
);
436 bool TrashImpl::copy( const QString
& src
, const QString
& dest
)
438 // kio_file's copy() method is quite complex (in order to be fast), let's just call it...
441 urlSrc
.setPath( src
);
443 urlDest
.setPath( dest
);
444 kDebug() << "copying " << src
<< " to " << dest
;
445 KIO::CopyJob
* job
= KIO::copyAs( urlSrc
, urlDest
, KIO::HideProgressInfo
);
446 job
->setUiDelegate(0);
447 connect( job
, SIGNAL( result(KJob
*) ),
448 this, SLOT( jobFinished(KJob
*) ) );
451 return m_lastErrorCode
== 0;
454 bool TrashImpl::directRename( const QString
& src
, const QString
& dest
)
456 kDebug() << src
<< " -> " << dest
;
457 if ( KDE_rename( QFile::encodeName( src
), QFile::encodeName( dest
) ) != 0 ) {
458 if (errno
== EXDEV
) {
459 error( KIO::ERR_UNSUPPORTED_ACTION
, QString::fromLatin1("rename") );
461 if (( errno
== EACCES
) || (errno
== EPERM
)) {
462 error( KIO::ERR_ACCESS_DENIED
, dest
);
463 } else if (errno
== EROFS
) { // The file is on a read-only filesystem
464 error( KIO::ERR_CANNOT_DELETE
, src
);
466 error( KIO::ERR_CANNOT_RENAME
, src
);
475 bool TrashImplKDE_mkdir( int trashId
, const QString
& fileId
, int permissions
)
477 const QString path
= filesPath( trashId
, fileId
);
478 if ( KDE_mkdir( QFile::encodeName( path
), permissions
) != 0 ) {
479 if ( errno
== EACCES
) {
480 error( KIO::ERR_ACCESS_DENIED
, path
);
482 } else if ( errno
== ENOSPC
) {
483 error( KIO::ERR_DISK_FULL
, path
);
486 error( KIO::ERR_COULD_NOT_MKDIR
, path
);
490 if ( permissions
!= -1 )
491 ::chmod( QFile::encodeName( path
), permissions
);
497 bool TrashImpl::del( int trashId
, const QString
& fileId
)
499 QString info
= infoPath(trashId
, fileId
);
500 QString file
= filesPath(trashId
, fileId
);
502 QByteArray info_c
= QFile::encodeName(info
);
504 KDE_struct_stat buff
;
505 if ( KDE_lstat( info_c
.data(), &buff
) == -1 ) {
506 if ( errno
== EACCES
)
507 error( KIO::ERR_ACCESS_DENIED
, file
);
509 error( KIO::ERR_DOES_NOT_EXIST
, file
);
513 if ( !synchronousDel( file
, true, QFileInfo(file
).isDir() ) )
516 QFile::remove( info
);
521 bool TrashImpl::synchronousDel( const QString
& path
, bool setLastErrorCode
, bool isDir
)
523 const int oldErrorCode
= m_lastErrorCode
;
524 const QString oldErrorMsg
= m_lastErrorMessage
;
527 // First ensure that all dirs have u+w permissions,
528 // otherwise we won't be able to delete files in them (#130780).
530 kDebug() << "chmod'ing " << url
;
531 KFileItem
fileItem( url
, "inode/directory", KFileItem::Unknown
);
532 KFileItemList fileItemList
;
533 fileItemList
.append( fileItem
);
534 KIO::ChmodJob
* chmodJob
= KIO::chmod( fileItemList
, 0200, 0200, QString(), QString(), true /*recursive*/, KIO::HideProgressInfo
);
535 connect( chmodJob
, SIGNAL( result(KJob
*) ),
536 this, SLOT( jobFinished(KJob
*) ) );
540 KIO::DeleteJob
*job
= KIO::del( url
, KIO::HideProgressInfo
);
541 connect( job
, SIGNAL( result(KJob
*) ),
542 this, SLOT( jobFinished(KJob
*) ) );
544 bool ok
= m_lastErrorCode
== 0;
545 if ( !setLastErrorCode
) {
546 m_lastErrorCode
= oldErrorCode
;
547 m_lastErrorMessage
= oldErrorMsg
;
552 bool TrashImpl::emptyTrash()
555 // The naive implementation "delete info and files in every trash directory"
556 // breaks when deleted directories contain files owned by other users.
557 // We need to ensure that the .trashinfo file is only removed when the
558 // corresponding files could indeed be removed (#116371)
560 // On the other hand, we certainly want to remove any file that has no associated
561 // .trashinfo file for some reason (#167051)
563 QSet
<QString
> unremoveableFiles
;
565 const TrashedFileInfoList fileInfoList
= list();
567 TrashedFileInfoList::const_iterator it
= fileInfoList
.begin();
568 const TrashedFileInfoList::const_iterator end
= fileInfoList
.end();
569 for ( ; it
!= end
; ++it
) {
570 const TrashedFileInfo
& info
= *it
;
571 const QString filesPath
= info
.physicalPath
;
572 if ( synchronousDel( filesPath
, true, true ) ) {
573 QFile::remove( infoPath( info
.trashId
, info
.fileId
) );
575 // error code is set already
576 // just remember not to remove this file
577 unremoveableFiles
.insert(filesPath
);
578 kDebug() << "Unremoveable:" << filesPath
;
582 // Now do the orphaned-files cleanup
583 TrashDirMap::const_iterator trit
= m_trashDirectories
.constBegin();
584 for (; trit
!= m_trashDirectories
.constEnd() ; ++trit
) {
585 //const int trashId = trit.key();
586 QString filesDir
= trit
.value();
587 filesDir
+= "/files";
588 Q_FOREACH(const QString
& fileName
, listDir(filesDir
)) {
589 if (fileName
== "." || fileName
== "..")
591 const QString filePath
= filesDir
+ '/' + fileName
;
592 if (!unremoveableFiles
.contains(filePath
)) {
593 kWarning() << "Removing orphaned file" << filePath
;
594 QFile::remove(filePath
);
601 return m_lastErrorCode
== 0;
604 TrashImpl::TrashedFileInfoList
TrashImpl::list()
606 // Here we scan for trash directories unconditionally. This allows
607 // noticing plugged-in [e.g. removeable] devices, or new mounts etc.
608 scanTrashDirectories();
610 TrashedFileInfoList lst
;
611 // For each known trash directory...
612 TrashDirMap::const_iterator it
= m_trashDirectories
.constBegin();
613 for ( ; it
!= m_trashDirectories
.constEnd() ; ++it
) {
614 const int trashId
= it
.key();
615 QString infoPath
= it
.value();
617 // Code taken from kio_file
618 const QStringList entryNames
= listDir( infoPath
);
619 //char path_buffer[PATH_MAX];
620 //getcwd(path_buffer, PATH_MAX - 1);
621 //if ( chdir( infoPathEnc ) )
623 for ( QStringList::const_iterator entryIt
= entryNames
.constBegin(), entryEnd
= entryNames
.constEnd();
624 entryIt
!= entryEnd
; ++entryIt
)
626 QString fileName
= *entryIt
;
627 if ( fileName
== "." || fileName
== ".." )
629 if ( !fileName
.endsWith( ".trashinfo" ) ) {
630 kWarning() << "Invalid info file found in " << infoPath
<< " : " << fileName
;
633 fileName
.truncate( fileName
.length() - 10 );
635 TrashedFileInfo info
;
636 if ( infoForFile( trashId
, fileName
, info
) )
643 // Returns the entries in a given directory - including "." and ".."
644 QStringList
TrashImpl::listDir( const QString
& physicalPath
)
646 QDir
dir( physicalPath
);
647 return dir
.entryList( QDir::Dirs
| QDir::Files
| QDir::Hidden
);
650 bool TrashImpl::infoForFile( int trashId
, const QString
& fileId
, TrashedFileInfo
& info
)
652 kDebug() << trashId
<< " " << fileId
;
653 info
.trashId
= trashId
; // easy :)
654 info
.fileId
= fileId
; // equally easy
655 info
.physicalPath
= filesPath( trashId
, fileId
);
656 return readInfoFile( infoPath( trashId
, fileId
), info
, trashId
);
659 bool TrashImpl::readInfoFile( const QString
& infoPath
, TrashedFileInfo
& info
, int trashId
)
661 KConfig
cfg( infoPath
, KConfig::SimpleConfig
);
662 if ( !cfg
.hasGroup( "Trash Info" ) ) {
663 error( KIO::ERR_CANNOT_OPEN_FOR_READING
, infoPath
);
666 const KConfigGroup group
= cfg
.group( "Trash Info" );
667 info
.origPath
= QUrl::fromPercentEncoding( group
.readEntry( "Path" ).toLatin1() );
668 if ( info
.origPath
.isEmpty() )
669 return false; // path is mandatory...
670 if ( trashId
== 0 ) {
671 Q_ASSERT( info
.origPath
[0] == '/' );
673 const QString topdir
= topDirectoryPath( trashId
); // includes trailing slash
674 info
.origPath
.prepend( topdir
);
676 const QString line
= group
.readEntry( "DeletionDate" );
677 if ( !line
.isEmpty() ) {
678 info
.deletionDate
= QDateTime::fromString( line
, Qt::ISODate
);
683 QString
TrashImpl::physicalPath( int trashId
, const QString
& fileId
, const QString
& relativePath
)
685 QString filePath
= filesPath( trashId
, fileId
);
686 if ( !relativePath
.isEmpty() ) {
688 filePath
+= relativePath
;
693 void TrashImpl::error( int e
, const QString
& s
)
696 kDebug() << e
<< " " << s
;
698 m_lastErrorMessage
= s
;
701 bool TrashImpl::isEmpty() const
703 // For each known trash directory...
704 if ( !m_trashDirectoriesScanned
)
705 scanTrashDirectories();
706 TrashDirMap::const_iterator it
= m_trashDirectories
.constBegin();
707 for ( ; it
!= m_trashDirectories
.constEnd() ; ++it
) {
708 QString infoPath
= it
.value();
711 DIR *dp
= opendir( QFile::encodeName( infoPath
) );
714 KDE_struct_dirent
*ep
;
715 ep
= KDE_readdir( dp
);
716 ep
= KDE_readdir( dp
); // ignore '.' and '..' dirent
717 ep
= KDE_readdir( dp
); // look for third file
720 //kDebug() << ep->d_name << " in " << infoPath << " -> not empty";
721 return false; // not empty
728 void TrashImpl::fileAdded()
730 KConfigGroup group
= m_config
.group( "Status" );
731 if ( group
.readEntry( "Empty", true) == true ) {
732 group
.writeEntry( "Empty", false );
735 // The apps showing the trash (e.g. kdesktop) will be notified
736 // of this change when KDirNotify::FilesAdded("trash:/") is emitted,
737 // which will be done by the job soon after this.
740 void TrashImpl::fileRemoved()
743 KConfigGroup group
= m_config
.group( "Status" );
744 group
.writeEntry( "Empty", true );
747 // The apps showing the trash (e.g. kdesktop) will be notified
748 // of this change when KDirNotify::FilesRemoved(...) is emitted,
749 // which will be done by the job soon after this.
752 static int idForDevice(const Solid::Device
& device
)
754 const Solid::Block
* block
= device
.as
<Solid::Block
>();
755 kDebug() << "major=" << block
->deviceMajor() << " minor=" << block
->deviceMinor();
756 return block
->deviceMajor()*1000 + block
->deviceMinor();
759 int TrashImpl::findTrashDirectory( const QString
& origPath
)
761 kDebug() << origPath
;
762 // First check if same device as $HOME, then we use the home trash right away.
763 KDE_struct_stat buff
;
764 if ( KDE_lstat( QFile::encodeName( origPath
), &buff
) == 0
765 && buff
.st_dev
== m_homeDevice
)
768 KMountPoint::Ptr mp
= KMountPoint::currentMountPoints().findByPath( origPath
);
771 QString mountPoint
= mp
->mountPoint();
772 const QString trashDir
= trashForMountPoint( mountPoint
, true );
773 kDebug() << "mountPoint=" << mountPoint
<< " trashDir=" << trashDir
;
774 if ( trashDir
.isEmpty() )
775 return 0; // no trash available on partition
776 int id
= idForTrashDirectory( trashDir
);
778 kDebug() << " known with id " << id
;
781 // new trash dir found, register it
782 // but we need stability in the trash IDs, so that restoring or asking
783 // for properties works even kio_trash gets killed because idle.
785 kDebug() << "found " << trashDir
;
786 m_trashDirectories
.insert( ++m_lastId
, trashDir
);
787 if ( !mountPoint
.endsWith( '/' ) )
789 m_topDirectories
.insert( m_lastId
, mountPoint
);
793 const QString query
= "[StorageAccess.accessible == true AND StorageAccess.filePath == '"+mountPoint
+"']";
794 //kDebug() << "doing solid query:" << query;
795 const QList
<Solid::Device
> lst
= Solid::Device::listFromQuery(query
);
796 //kDebug() << "got" << lst.count() << "devices";
797 if ( lst
.isEmpty() ) // not a device. Maybe some tmpfs mount for instance.
798 return 0; // use the home trash instead
799 // Pretend we got exactly one...
800 const Solid::Device device
= lst
[0];
802 // new trash dir found, register it
803 id
= idForDevice( device
);
804 m_trashDirectories
.insert( id
, trashDir
);
805 kDebug() << "found" << trashDir
<< "gave it id" << id
;
806 if ( !mountPoint
.endsWith( '/' ) )
808 m_topDirectories
.insert( id
, mountPoint
);
810 return idForTrashDirectory( trashDir
);
813 void TrashImpl::scanTrashDirectories() const
815 const QList
<Solid::Device
> lst
= Solid::Device::listFromQuery("StorageAccess.accessible == true");
816 for ( QList
<Solid::Device
>::ConstIterator it
= lst
.begin() ; it
!= lst
.end() ; ++it
) {
817 QString topdir
= (*it
).as
<Solid::StorageAccess
>()->filePath();
818 QString trashDir
= trashForMountPoint( topdir
, false );
819 if ( !trashDir
.isEmpty() ) {
820 // OK, trashDir is a valid trash directory. Ensure it's registered.
821 int trashId
= idForTrashDirectory( trashDir
);
822 if ( trashId
== -1 ) {
823 // new trash dir found, register it
824 trashId
= idForDevice( *it
);
825 m_trashDirectories
.insert( trashId
, trashDir
);
826 kDebug() << "found " << trashDir
<< " gave it id " << trashId
;
827 if ( !topdir
.endsWith( '/' ) )
829 m_topDirectories
.insert( trashId
, topdir
);
833 m_trashDirectoriesScanned
= true;
836 TrashImpl::TrashDirMap
TrashImpl::trashDirectories() const
838 if ( !m_trashDirectoriesScanned
)
839 scanTrashDirectories();
840 return m_trashDirectories
;
843 TrashImpl::TrashDirMap
TrashImpl::topDirectories() const
845 if ( !m_trashDirectoriesScanned
)
846 scanTrashDirectories();
847 return m_topDirectories
;
850 QString
TrashImpl::trashForMountPoint( const QString
& topdir
, bool createIfNeeded
) const
852 // (1) Administrator-created $topdir/.Trash directory
854 const QString rootTrashDir
= topdir
+ "/.Trash";
855 const QByteArray rootTrashDir_c
= QFile::encodeName( rootTrashDir
);
856 // Can't use QFileInfo here since we need to test for the sticky bit
857 uid_t uid
= getuid();
858 KDE_struct_stat buff
;
859 const unsigned int requiredBits
= S_ISVTX
; // Sticky bit required
860 if ( KDE_lstat( rootTrashDir_c
, &buff
) == 0 ) {
861 if ( (S_ISDIR(buff
.st_mode
)) // must be a dir
862 && (!S_ISLNK(buff
.st_mode
)) // not a symlink
863 && ((buff
.st_mode
& requiredBits
) == requiredBits
)
864 && (::access(rootTrashDir_c
, W_OK
))
866 const QString trashDir
= rootTrashDir
+ '/' + QString::number( uid
);
867 const QByteArray trashDir_c
= QFile::encodeName( trashDir
);
868 if ( KDE_lstat( trashDir_c
, &buff
) == 0 ) {
869 if ( (buff
.st_uid
== uid
) // must be owned by user
870 && (S_ISDIR(buff
.st_mode
)) // must be a dir
871 && (!S_ISLNK(buff
.st_mode
)) // not a symlink
872 && (buff
.st_mode
& 0777) == 0700 ) { // rwx for user
875 kDebug() << "Directory " << trashDir
<< " exists but didn't pass the security checks, can't use it";
877 else if ( createIfNeeded
&& initTrashDirectory( trashDir_c
) ) {
881 kDebug() << "Root trash dir " << rootTrashDir
<< " exists but didn't pass the security checks, can't use it";
885 // (2) $topdir/.Trash-$uid
886 const QString trashDir
= topdir
+ "/.Trash-" + QString::number( uid
);
887 const QByteArray trashDir_c
= QFile::encodeName( trashDir
);
888 if ( KDE_lstat( trashDir_c
, &buff
) == 0 )
890 if ( (buff
.st_uid
== uid
) // must be owned by user
891 && (S_ISDIR(buff
.st_mode
)) // must be a dir
892 && (!S_ISLNK(buff
.st_mode
)) // not a symlink
893 && ((buff
.st_mode
& 0777) == 0700) ) { // rwx for user, ------ for group and others
895 if ( checkTrashSubdirs( trashDir_c
) )
898 kDebug() << "Directory " << trashDir
<< " exists but didn't pass the security checks, can't use it";
899 // Exists, but not useable
902 if ( createIfNeeded
&& initTrashDirectory( trashDir_c
) ) {
908 int TrashImpl::idForTrashDirectory( const QString
& trashDir
) const
910 // If this is too slow we can always use a reverse map...
911 TrashDirMap::ConstIterator it
= m_trashDirectories
.constBegin();
912 for ( ; it
!= m_trashDirectories
.constEnd() ; ++it
) {
913 if ( it
.value() == trashDir
) {
920 bool TrashImpl::initTrashDirectory( const QByteArray
& trashDir_c
) const
922 kDebug() << trashDir_c
;
923 if ( KDE_mkdir( trashDir_c
, 0700 ) != 0 )
926 // This trash dir will be useable only if the directory is owned by user.
927 // In theory this is the case, but not on e.g. USB keys...
928 uid_t uid
= getuid();
929 KDE_struct_stat buff
;
930 if ( KDE_lstat( trashDir_c
, &buff
) != 0 )
931 return false; // huh?
932 if ( (buff
.st_uid
== uid
) // must be owned by user
933 && ((buff
.st_mode
& 0777) == 0700) ) { // rwx for user, --- for group and others
935 return checkTrashSubdirs( trashDir_c
);
938 kDebug() << trashDir_c
<< " just created, by it doesn't have the right permissions, must be a FAT partition. Removing it again.";
939 // Not good, e.g. USB key. Delete again.
940 // I'm paranoid, it would be better to find a solution that allows
941 // to trash directly onto the USB key, but I don't see how that would
942 // pass the security checks. It would also make the USB key appears as
943 // empty when it's in fact full...
944 ::rmdir( trashDir_c
);
950 bool TrashImpl::checkTrashSubdirs( const QByteArray
& trashDir_c
) const
952 // testDir currently works with a QString - ## optimize
953 QString trashDir
= QFile::decodeName( trashDir_c
);
954 const QString info
= trashDir
+ "/info";
955 if ( testDir( info
) != 0 )
957 const QString files
= trashDir
+ "/files";
958 if ( testDir( files
) != 0 )
963 QString
TrashImpl::trashDirectoryPath( int trashId
) const
965 // Never scanned for trash dirs? (This can happen after killing kio_trash
966 // and reusing a directory listing from the earlier instance.)
967 if ( !m_trashDirectoriesScanned
)
968 scanTrashDirectories();
969 Q_ASSERT( m_trashDirectories
.contains( trashId
) );
970 return m_trashDirectories
[trashId
];
973 QString
TrashImpl::topDirectoryPath( int trashId
) const
975 if ( !m_trashDirectoriesScanned
)
976 scanTrashDirectories();
977 assert( trashId
!= 0 );
978 Q_ASSERT( m_topDirectories
.contains( trashId
) );
979 return m_topDirectories
[trashId
];
982 // Helper method. Creates a URL with the format trash:/trashid-fileid or
983 // trash:/trashid-fileid/relativePath/To/File for a file inside a trashed directory.
984 KUrl
TrashImpl::makeURL( int trashId
, const QString
& fileId
, const QString
& relativePath
)
987 url
.setProtocol( "trash" );
989 path
+= QString::number( trashId
);
992 if ( !relativePath
.isEmpty() ) {
994 path
+= relativePath
;
1000 // Helper method. Parses a trash URL with the URL scheme defined in makeURL.
1001 // The trash:/ URL itself isn't parsed here, must be caught by the caller before hand.
1002 bool TrashImpl::parseURL( const KUrl
& url
, int& trashId
, QString
& fileId
, QString
& relativePath
)
1004 if ( url
.protocol() != "trash" )
1006 const QString path
= url
.path();
1008 if ( path
[0] == '/' ) // always true I hope
1010 int slashPos
= path
.indexOf( '-', 0 ); // don't match leading slash
1011 if ( slashPos
<= 0 )
1014 trashId
= path
.mid( start
, slashPos
- start
).toInt( &ok
);
1018 start
= slashPos
+ 1;
1019 slashPos
= path
.indexOf( '/', start
);
1020 if ( slashPos
<= 0 ) {
1021 fileId
= path
.mid( start
);
1022 relativePath
.clear();
1025 fileId
= path
.mid( start
, slashPos
- start
);
1026 relativePath
= path
.mid( slashPos
+ 1 );
1030 bool TrashImpl::adaptTrashSize( const QString
& origPath
, int trashId
)
1032 KConfig
config( "ktrashrc" );
1034 const QString trashPath
= trashDirectoryPath( trashId
);
1035 KConfigGroup group
= config
.group( trashPath
);
1037 bool useTimeLimit
= group
.readEntry( "UseTimeLimit", false );
1038 bool useSizeLimit
= group
.readEntry( "UseSizeLimit", true );
1039 double percent
= group
.readEntry( "Percent", 10.0 );
1040 int actionType
= group
.readEntry( "LimitReachedAction", 0 );
1042 if ( useTimeLimit
) { // delete all files in trash older than X days
1043 const int maxDays
= group
.readEntry( "Days", 7 );
1044 const QDateTime currentDate
= QDateTime::currentDateTime();
1046 const TrashedFileInfoList trashedFiles
= list();
1047 for ( int i
= 0; i
< trashedFiles
.count(); ++i
) {
1048 struct TrashedFileInfo info
= trashedFiles
.at( i
);
1049 if ( info
.trashId
!= trashId
)
1052 if ( info
.deletionDate
.daysTo( currentDate
) > maxDays
)
1053 del( info
.trashId
, info
.fileId
);
1059 if ( useSizeLimit
) { // check if size limit exceeded
1061 // calculate size of the files to be put into the trash
1062 qulonglong additionalSize
= DiscSpaceUtil::sizeOfPath( origPath
);
1064 DiscSpaceUtil
util( trashPath
+ "/files/" );
1065 if ( util
.usage( additionalSize
) >= percent
) {
1066 if ( actionType
== 0 ) { // warn the user only
1067 m_lastErrorCode
= KIO::ERR_SLAVE_DEFINED
;
1068 m_lastErrorMessage
= i18n( "The trash has reached its maximum size!\nCleanup the trash manually." );
1071 // before we start to remove any files from the trash,
1072 // check whether the new file will fit into the trash
1075 qulonglong partitionSize
= util
.size();
1077 if ( (((double)additionalSize
/(double)partitionSize
)*100) >= percent
) {
1078 m_lastErrorCode
= KIO::ERR_SLAVE_DEFINED
;
1079 m_lastErrorMessage
= i18n( "The file is too large to be trashed." );
1083 // the size of the to be trashed file is ok, so lets start removing
1084 // some other files from the trash
1086 QDir
dir( trashPath
+ "/files" );
1087 QFileInfoList infos
;
1088 if ( actionType
== 1 ) // delete oldest files first
1089 infos
= dir
.entryInfoList( QDir::Files
| QDir::AllDirs
| QDir::NoDotAndDotDot
, QDir::Time
| QDir::Reversed
);
1090 else if ( actionType
== 2 ) // delete biggest files first
1091 infos
= dir
.entryInfoList( QDir::Files
| QDir::AllDirs
| QDir::NoDotAndDotDot
, QDir::Size
);
1093 qWarning( "Should never happen!" );
1095 bool deleteFurther
= true;
1096 for ( int i
= 0; (i
< infos
.count()) && deleteFurther
; ++i
) {
1097 const QFileInfo info
= infos
.at( i
);
1099 del( trashId
, info
.fileName() ); // delete trashed file
1101 if ( util
.usage( additionalSize
) < percent
) // check whether we have enough space now
1102 deleteFurther
= false;
1111 #include "trashimpl.moc"