diff options
Diffstat (limited to 'kioslave/trash/trashimpl.cpp')
-rw-r--r-- | kioslave/trash/trashimpl.cpp | 962 |
1 files changed, 0 insertions, 962 deletions
diff --git a/kioslave/trash/trashimpl.cpp b/kioslave/trash/trashimpl.cpp deleted file mode 100644 index 3e109c119..000000000 --- a/kioslave/trash/trashimpl.cpp +++ /dev/null @@ -1,962 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 2004 David Faure <[email protected]> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#include "trashimpl.h" -#include <klocale.h> -#include <klargefile.h> -#include <kio/global.h> -#include <kio/renamedlg.h> -#include <kio/job.h> -#include <kdebug.h> -#include <kurl.h> -#include <kdirnotify_stub.h> -#include <kglobal.h> -#include <kstandarddirs.h> -#include <kglobalsettings.h> -#include <kmountpoint.h> -#include <kfileitem.h> -#include <kio/chmodjob.h> - -#include <dcopref.h> - -#include <tqapplication.h> -#include <tqeventloop.h> -#include <tqfile.h> -#include <tqdir.h> - -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/param.h> -#include <fcntl.h> -#include <unistd.h> -#include <dirent.h> -#include <stdlib.h> -#include <errno.h> - -TrashImpl::TrashImpl() : - TQObject(), - m_lastErrorCode( 0 ), - m_initStatus( InitToBeDone ), - m_lastId( 0 ), - m_homeDevice( 0 ), - m_trashDirectoriesScanned( false ), - m_mibEnum( TDEGlobal::locale()->fileEncodingMib() ), - // not using kio_trashrc since KIO uses that one already for kio_trash - // so better have a separate one, for faster parsing by e.g. kmimetype.cpp - m_config( "trashrc" ) -{ - KDE_struct_stat buff; - if ( KDE_lstat( TQFile::encodeName( TQDir::homeDirPath() ), &buff ) == 0 ) { - m_homeDevice = buff.st_dev; - } else { - kdError() << "Should never happen: couldn't stat $HOME " << strerror( errno ) << endl; - } -} - -/** - * Test if a directory exists, create otherwise - * @param _name full path of the directory - * @return errorcode, or 0 if the dir was created or existed already - * Warning, don't use return value like a bool - */ -int TrashImpl::testDir( const TQString &_name ) const -{ - DIR *dp = opendir( TQFile::encodeName(_name) ); - if ( dp == NULL ) - { - TQString name = _name; - if ( name.endsWith( "/" ) ) - name.truncate( name.length() - 1 ); - TQCString path = TQFile::encodeName(name); - - bool ok = ::mkdir( path, S_IRWXU ) == 0; - if ( !ok && errno == EEXIST ) { -#if 0 // this would require to use SlaveBase's method to ask the question - //int ret = KMessageBox::warningYesNo( 0, i18n("%1 is a file, but TDE needs it to be a directory. Move it to %2.orig and create directory?").arg(name).arg(name) ); - //if ( ret == KMessageBox::Yes ) { -#endif - if ( ::rename( path, path + ".orig" ) == 0 ) { - ok = ::mkdir( path, S_IRWXU ) == 0; - } else { // foo.orig existed already. How likely is that? - ok = false; - } - if ( !ok ) { - return TDEIO::ERR_DIR_ALREADY_EXIST; - } -#if 0 - //} else { - // return 0; - //} -#endif - } - if ( !ok ) - { - //KMessageBox::sorry( 0, i18n( "Couldn't create directory %1. Check for permissions." ).arg( name ) ); - kdWarning() << "could not create " << name << endl; - return TDEIO::ERR_COULD_NOT_MKDIR; - } else { - kdDebug() << name << " created." << endl; - } - } - else // exists already - { - closedir( dp ); - } - return 0; // success -} - -bool TrashImpl::init() -{ - if ( m_initStatus == InitOK ) - return true; - if ( m_initStatus == InitError ) - return false; - - // Check the trash directory and its info and files subdirs - // see also kdesktop/init.cc for first time initialization - m_initStatus = InitError; - // $XDG_DATA_HOME/Trash, i.e. ~/.local/share/Trash by default. - const TQString xdgDataDir = TDEGlobal::dirs()->localxdgdatadir(); - if ( !KStandardDirs::makeDir( xdgDataDir, 0700 ) ) { - kdWarning() << "failed to create " << xdgDataDir << endl; - return false; - } - - const TQString trashDir = xdgDataDir + "Trash"; - int err; - if ( ( err = testDir( trashDir ) ) ) { - error( err, trashDir ); - return false; - } - if ( ( err = testDir( trashDir + "/info" ) ) ) { - error( err, trashDir + "/info" ); - return false; - } - if ( ( err = testDir( trashDir + "/files" ) ) ) { - error( err, trashDir + "/files" ); - return false; - } - m_trashDirectories.insert( 0, trashDir ); - m_initStatus = InitOK; - kdDebug() << k_funcinfo << "initialization OK, home trash dir: " << trashDir << endl; - return true; -} - -void TrashImpl::migrateOldTrash() -{ - kdDebug() << k_funcinfo << endl; - const TQString oldTrashDir = TDEGlobalSettings::trashPath(); - const TQStrList entries = listDir( oldTrashDir ); - bool allOK = true; - TQStrListIterator entryIt( entries ); - for (; entryIt.current(); ++entryIt) { - TQString srcPath = TQFile::decodeName( *entryIt ); - if ( srcPath == "." || srcPath == ".." || srcPath == ".directory" ) - continue; - srcPath.prepend( oldTrashDir ); // make absolute - int trashId; - TQString fileId; - if ( !createInfo( srcPath, trashId, fileId ) ) { - kdWarning() << "Trash migration: failed to create info for " << srcPath << endl; - allOK = false; - } else { - bool ok = moveToTrash( srcPath, trashId, fileId ); - if ( !ok ) { - (void)deleteInfo( trashId, fileId ); - kdWarning() << "Trash migration: failed to create info for " << srcPath << endl; - allOK = false; - } else { - kdDebug() << "Trash migration: moved " << srcPath << endl; - } - } - } - 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, true ); - } -} - -bool TrashImpl::createInfo( const TQString& origPath, int& trashId, TQString& fileId ) -{ - kdDebug() << k_funcinfo << origPath << endl; - // Check source - const TQCString origPath_c( TQFile::encodeName( origPath ) ); - KDE_struct_stat buff_src; - if ( KDE_lstat( origPath_c.data(), &buff_src ) == -1 ) { - if ( errno == EACCES ) - error( TDEIO::ERR_ACCESS_DENIED, origPath ); - else - error( TDEIO::ERR_DOES_NOT_EXIST, origPath ); - return false; - } - - // Choose destination trash - trashId = findTrashDirectory( origPath ); - if ( trashId < 0 ) { - kdWarning() << "OUCH - internal error, TrashImpl::findTrashDirectory returned " << trashId << endl; - return false; // ### error() needed? - } - kdDebug() << k_funcinfo << "trashing to " << trashId << endl; - - // Grab original filename - KURL url; - url.setPath( origPath ); - const TQString origFileName = url.fileName(); - - // Make destination file in info/ - url.setPath( infoPath( trashId, origFileName ) ); // we first try with origFileName - KURL baseDirectory; - baseDirectory.setPath( url.directory() ); - // Here we need to use O_EXCL to avoid race conditions with other kioslave processes - int fd = 0; - do { - kdDebug() << k_funcinfo << "trying to create " << url.path() << endl; - fd = ::open( TQFile::encodeName( url.path() ), O_WRONLY | O_CREAT | O_EXCL, 0600 ); - if ( fd < 0 ) { - if ( errno == EEXIST ) { - url.setFileName( TDEIO::RenameDlg::suggestName( baseDirectory, url.fileName() ) ); - // and try again on the next iteration - } else { - error( TDEIO::ERR_COULD_NOT_WRITE, url.path() ); - return false; - } - } - } while ( fd < 0 ); - const TQString infoPath = url.path(); - fileId = url.fileName(); - Q_ASSERT( fileId.endsWith( ".trashinfo" ) ); - fileId.truncate( fileId.length() - 10 ); // remove .trashinfo from fileId - - FILE* file = ::fdopen( fd, "w" ); - if ( !file ) { // can't see how this would happen - error( TDEIO::ERR_COULD_NOT_WRITE, infoPath ); - return false; - } - - // Contents of the info file. We could use KSimpleConfig, but that would - // mean closing and reopening fd, i.e. opening a race condition... - TQCString info = "[Trash Info]\n"; - info += "Path="; - // Escape filenames according to the way they are encoded on the filesystem - // All this to basically get back to the raw 8-bit representation of the filename... - if ( trashId == 0 ) // home trash: absolute path - info += KURL::encode_string( origPath, m_mibEnum ).latin1(); - else - info += KURL::encode_string( makeRelativePath( topDirectoryPath( trashId ), origPath ), m_mibEnum ).latin1(); - info += "\n"; - info += "DeletionDate="; - info += TQDateTime::currentDateTime().toString( Qt::ISODate ).latin1(); - info += "\n"; - size_t sz = info.size() - 1; // avoid trailing 0 from QCString - - size_t written = ::fwrite(info.data(), 1, sz, file); - if ( written != sz ) { - ::fclose( file ); - TQFile::remove( infoPath ); - error( TDEIO::ERR_DISK_FULL, infoPath ); - return false; - } - - ::fclose( file ); - - kdDebug() << k_funcinfo << "info file created in trashId=" << trashId << " : " << fileId << endl; - return true; -} - -TQString TrashImpl::makeRelativePath( const TQString& topdir, const TQString& path ) -{ - const TQString realPath = KStandardDirs::realFilePath( path ); - // topdir ends with '/' - if ( realPath.startsWith( topdir ) ) { - const TQString rel = realPath.mid( topdir.length() ); - Q_ASSERT( rel[0] != '/' ); - return rel; - } else { // shouldn't happen... - kdWarning() << "Couldn't make relative path for " << realPath << " (" << path << "), with topdir=" << topdir << endl; - return realPath; - } -} - -TQString TrashImpl::infoPath( int trashId, const TQString& fileId ) const -{ - TQString trashPath = trashDirectoryPath( trashId ); - trashPath += "/info/"; - trashPath += fileId; - trashPath += ".trashinfo"; - return trashPath; -} - -TQString TrashImpl::filesPath( int trashId, const TQString& fileId ) const -{ - TQString trashPath = trashDirectoryPath( trashId ); - trashPath += "/files/"; - trashPath += fileId; - return trashPath; -} - -bool TrashImpl::deleteInfo( int trashId, const TQString& fileId ) -{ - bool ok = TQFile::remove( infoPath( trashId, fileId ) ); - if ( ok ) - fileRemoved(); - return ok; -} - -bool TrashImpl::moveToTrash( const TQString& origPath, int trashId, const TQString& fileId ) -{ - kdDebug() << k_funcinfo << endl; - const TQString dest = filesPath( trashId, fileId ); - if ( !move( origPath, dest ) ) { - // Maybe the move failed due to no permissions to delete source. - // In that case, delete dest to keep things consistent, since KIO doesn't do it. - if ( TQFileInfo( dest ).isFile() ) - TQFile::remove( dest ); - else - synchronousDel( dest, false, true ); - return false; - } - fileAdded(); - return true; -} - -bool TrashImpl::moveFromTrash( const TQString& dest, int trashId, const TQString& fileId, const TQString& relativePath ) -{ - TQString src = filesPath( trashId, fileId ); - if ( !relativePath.isEmpty() ) { - src += '/'; - src += relativePath; - } - if ( !move( src, dest ) ) - return false; - return true; -} - -bool TrashImpl::move( const TQString& src, const TQString& dest ) -{ - if ( directRename( src, dest ) ) { - // This notification is done by TDEIO::moveAs when using the code below - // But if we do a direct rename we need to do the notification ourselves - KDirNotify_stub allDirNotify( "*", "KDirNotify*" ); - KURL urlDest; urlDest.setPath( dest ); - urlDest.setPath( urlDest.directory() ); - allDirNotify.FilesAdded( urlDest ); - return true; - } - if ( m_lastErrorCode != TDEIO::ERR_UNSUPPORTED_ACTION ) - return false; - - KURL urlSrc, urlDest; - urlSrc.setPath( src ); - urlDest.setPath( dest ); - kdDebug() << k_funcinfo << urlSrc << " -> " << urlDest << endl; - TDEIO::CopyJob* job = TDEIO::moveAs( urlSrc, urlDest, false ); -#ifdef KIO_COPYJOB_HAS_SETINTERACTIVE - job->setInteractive( false ); -#endif - connect( job, TQT_SIGNAL( result(TDEIO::Job *) ), - this, TQT_SLOT( jobFinished(TDEIO::Job *) ) ); - tqApp->eventLoop()->enterLoop(); - - return m_lastErrorCode == 0; -} - -void TrashImpl::jobFinished(TDEIO::Job* job) -{ - kdDebug() << k_funcinfo << " error=" << job->error() << endl; - error( job->error(), job->errorText() ); - tqApp->eventLoop()->exitLoop(); -} - -bool TrashImpl::copyToTrash( const TQString& origPath, int trashId, const TQString& fileId ) -{ - kdDebug() << k_funcinfo << endl; - const TQString dest = filesPath( trashId, fileId ); - if ( !copy( origPath, dest ) ) - return false; - fileAdded(); - return true; -} - -bool TrashImpl::copyFromTrash( const TQString& dest, int trashId, const TQString& fileId, const TQString& relativePath ) -{ - TQString src = filesPath( trashId, fileId ); - if ( !relativePath.isEmpty() ) { - src += '/'; - src += relativePath; - } - return copy( src, dest ); -} - -bool TrashImpl::copy( const TQString& src, const TQString& dest ) -{ - // kio_file's copy() method is quite complex (in order to be fast), let's just call it... - m_lastErrorCode = 0; - KURL urlSrc; - urlSrc.setPath( src ); - KURL urlDest; - urlDest.setPath( dest ); - kdDebug() << k_funcinfo << "copying " << src << " to " << dest << endl; - TDEIO::CopyJob* job = TDEIO::copyAs( urlSrc, urlDest, false ); -#ifdef KIO_COPYJOB_HAS_SETINTERACTIVE - job->setInteractive( false ); -#endif - connect( job, TQT_SIGNAL( result( TDEIO::Job* ) ), - this, TQT_SLOT( jobFinished( TDEIO::Job* ) ) ); - tqApp->eventLoop()->enterLoop(); - - return m_lastErrorCode == 0; -} - -bool TrashImpl::directRename( const TQString& src, const TQString& dest ) -{ - kdDebug() << k_funcinfo << src << " -> " << dest << endl; - if ( ::rename( TQFile::encodeName( src ), TQFile::encodeName( dest ) ) != 0 ) { - if (errno == EXDEV) { - error( TDEIO::ERR_UNSUPPORTED_ACTION, TQString::fromLatin1("rename") ); - } else { - if (( errno == EACCES ) || (errno == EPERM)) { - error( TDEIO::ERR_ACCESS_DENIED, dest ); - } else if (errno == EROFS) { // The file is on a read-only filesystem - error( TDEIO::ERR_CANNOT_DELETE, src ); - } else { - error( TDEIO::ERR_CANNOT_RENAME, src ); - } - } - return false; - } - return true; -} - -#if 0 -bool TrashImpl::mkdir( int trashId, const TQString& fileId, int permissions ) -{ - const TQString path = filesPath( trashId, fileId ); - if ( ::mkdir( TQFile::encodeName( path ), permissions ) != 0 ) { - if ( errno == EACCES ) { - error( TDEIO::ERR_ACCESS_DENIED, path ); - return false; - } else if ( errno == ENOSPC ) { - error( TDEIO::ERR_DISK_FULL, path ); - return false; - } else { - error( TDEIO::ERR_COULD_NOT_MKDIR, path ); - return false; - } - } else { - if ( permissions != -1 ) - ::chmod( TQFile::encodeName( path ), permissions ); - } - return true; -} -#endif - -bool TrashImpl::del( int trashId, const TQString& fileId ) -{ - TQString info = infoPath(trashId, fileId); - TQString file = filesPath(trashId, fileId); - - TQCString info_c = TQFile::encodeName(info); - - KDE_struct_stat buff; - if ( KDE_lstat( info_c.data(), &buff ) == -1 ) { - if ( errno == EACCES ) - error( TDEIO::ERR_ACCESS_DENIED, file ); - else - error( TDEIO::ERR_DOES_NOT_EXIST, file ); - return false; - } - - if ( !synchronousDel( file, true, TQFileInfo(file).isDir() ) ) - return false; - - TQFile::remove( info ); - fileRemoved(); - return true; -} - -bool TrashImpl::synchronousDel( const TQString& path, bool setLastErrorCode, bool isDir ) -{ - const int oldErrorCode = m_lastErrorCode; - const TQString 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 ); - TDEIO::ChmodJob* chmodJob = TDEIO::chmod( fileItemList, 0200, 0200, TQString::null, TQString::null, true /*recursive*/, false /*showProgressInfo*/ ); - connect( chmodJob, TQT_SIGNAL( result(TDEIO::Job *) ), - this, TQT_SLOT( jobFinished(TDEIO::Job *) ) ); - tqApp->eventLoop()->enterLoop(); - } - - kdDebug() << k_funcinfo << "deleting " << url << endl; - TDEIO::DeleteJob *job = TDEIO::del( url, false, false ); - connect( job, TQT_SIGNAL( result(TDEIO::Job *) ), - this, TQT_SLOT( jobFinished(TDEIO::Job *) ) ); - tqApp->eventLoop()->enterLoop(); - bool ok = m_lastErrorCode == 0; - if ( !setLastErrorCode ) { - m_lastErrorCode = oldErrorCode; - m_lastErrorMessage = oldErrorMsg; - } - return ok; -} - -bool TrashImpl::emptyTrash() -{ - kdDebug() << k_funcinfo << endl; - // 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 TQString filesPath = info.physicalPath; - if ( synchronousDel( filesPath, true, true ) ) { - TQFile::remove( infoPath( info.trashId, info.fileId ) ); - } // else error code is set - } - fileRemoved(); - - return m_lastErrorCode == 0; -} - -TrashImpl::TrashedFileInfoList TrashImpl::list() -{ - // Here we scan for trash directories unconditionally. This allows - // noticing plugged-in [e.g. removeable] devices, or new mounts etc. - scanTrashDirectories(); - - TrashedFileInfoList lst; - // For each known trash directory... - TrashDirMap::const_iterator it = m_trashDirectories.begin(); - for ( ; it != m_trashDirectories.end() ; ++it ) { - const int trashId = it.key(); - TQString infoPath = it.data(); - infoPath += "/info"; - // Code taken from kio_file - TQStrList entryNames = listDir( infoPath ); - //char path_buffer[PATH_MAX]; - //getcwd(path_buffer, PATH_MAX - 1); - //if ( chdir( infoPathEnc ) ) - // continue; - TQStrListIterator entryIt( entryNames ); - for (; entryIt.current(); ++entryIt) { - TQString fileName = TQFile::decodeName( *entryIt ); - if ( fileName == "." || fileName == ".." ) - continue; - if ( !fileName.endsWith( ".trashinfo" ) ) { - kdWarning() << "Invalid info file found in " << infoPath << " : " << fileName << endl; - continue; - } - fileName.truncate( fileName.length() - 10 ); - - TrashedFileInfo info; - if ( infoForFile( trashId, fileName, info ) ) - lst << info; - } - } - return lst; -} - -// Returns the entries in a given directory - including "." and ".." -TQStrList TrashImpl::listDir( const TQString& physicalPath ) -{ - const TQCString physicalPathEnc = TQFile::encodeName( physicalPath ); - kdDebug() << k_funcinfo << "listing " << physicalPath << endl; - TQStrList entryNames; - DIR *dp = opendir( physicalPathEnc ); - if ( dp == 0 ) - return entryNames; - KDE_struct_dirent *ep; - while ( ( ep = KDE_readdir( dp ) ) != 0L ) - entryNames.append( ep->d_name ); - closedir( dp ); - return entryNames; -} - -bool TrashImpl::infoForFile( int trashId, const TQString& fileId, TrashedFileInfo& info ) -{ - kdDebug() << k_funcinfo << trashId << " " << fileId << endl; - info.trashId = trashId; // easy :) - info.fileId = fileId; // equally easy - info.physicalPath = filesPath( trashId, fileId ); - return readInfoFile( infoPath( trashId, fileId ), info, trashId ); -} - -bool TrashImpl::readInfoFile( const TQString& infoPath, TrashedFileInfo& info, int trashId ) -{ - KSimpleConfig cfg( infoPath, true ); - if ( !cfg.hasGroup( "Trash Info" ) ) { - error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, infoPath ); - return false; - } - cfg.setGroup( "Trash Info" ); - info.origPath = KURL::decode_string( cfg.readEntry( "Path" ), m_mibEnum ); - if ( info.origPath.isEmpty() ) - return false; // path is mandatory... - if ( trashId == 0 ) - Q_ASSERT( info.origPath[0] == '/' ); - else { - const TQString topdir = topDirectoryPath( trashId ); // includes trailing slash - info.origPath.prepend( topdir ); - } - TQString line = cfg.readEntry( "DeletionDate" ); - if ( !line.isEmpty() ) { - info.deletionDate = TQT_TQDATETIME_OBJECT(TQDateTime::fromString( line, Qt::ISODate )); - } - return true; -} - -TQString TrashImpl::physicalPath( int trashId, const TQString& fileId, const TQString& relativePath ) -{ - TQString filePath = filesPath( trashId, fileId ); - if ( !relativePath.isEmpty() ) { - filePath += "/"; - filePath += relativePath; - } - return filePath; -} - -void TrashImpl::error( int e, const TQString& s ) -{ - if ( e ) - kdDebug() << k_funcinfo << e << " " << s << endl; - m_lastErrorCode = e; - m_lastErrorMessage = s; -} - -bool TrashImpl::isEmpty() const -{ - // For each known trash directory... - if ( !m_trashDirectoriesScanned ) - scanTrashDirectories(); - TrashDirMap::const_iterator it = m_trashDirectories.begin(); - for ( ; it != m_trashDirectories.end() ; ++it ) { - TQString infoPath = it.data(); - infoPath += "/info"; - - DIR *dp = opendir( TQFile::encodeName( infoPath ) ); - if ( dp ) - { - struct dirent *ep; - ep = readdir( dp ); - ep = readdir( dp ); // ignore '.' and '..' dirent - ep = readdir( dp ); // look for third file - closedir( dp ); - if ( ep != 0 ) { - //kdDebug() << ep->d_name << " in " << infoPath << " -> not empty" << endl; - return false; // not empty - } - } - } - return true; -} - -void TrashImpl::fileAdded() -{ - m_config.setGroup( "Status" ); - if ( m_config.readBoolEntry( "Empty", true ) == true ) { - m_config.writeEntry( "Empty", false ); - m_config.sync(); - } - // The apps showing the trash (e.g. kdesktop) will be notified - // of this change when KDirNotify::FilesAdded("trash:/") is emitted, - // which will be done by the job soon after this. -} - -void TrashImpl::fileRemoved() -{ - if ( isEmpty() ) { - m_config.setGroup( "Status" ); - m_config.writeEntry( "Empty", true ); - m_config.sync(); - } - // The apps showing the trash (e.g. kdesktop) will be notified - // of this change when KDirNotify::FilesRemoved(...) is emitted, - // which will be done by the job soon after this. -} - -int TrashImpl::findTrashDirectory( const TQString& origPath ) -{ - kdDebug() << k_funcinfo << origPath << endl; - // First check if same device as $HOME, then we use the home trash right away. - KDE_struct_stat buff; - if ( KDE_lstat( TQFile::encodeName( origPath ), &buff ) == 0 - && buff.st_dev == m_homeDevice ) - return 0; - - TQString mountPoint = TDEIO::findPathMountPoint( origPath ); - const TQString trashDir = trashForMountPoint( mountPoint, true ); - kdDebug() << "mountPoint=" << mountPoint << " trashDir=" << trashDir << endl; - if ( trashDir.isEmpty() ) - return 0; // no trash available on partition - int id = idForTrashDirectory( trashDir ); - if ( id > -1 ) { - kdDebug() << " known with id " << id << endl; - return id; - } - // new trash dir found, register it - // but we need stability in the trash IDs, so that restoring or asking - // for properties works even kio_trash gets killed because idle. -#if 0 - kdDebug() << k_funcinfo << "found " << trashDir << endl; - m_trashDirectories.insert( ++m_lastId, trashDir ); - if ( !mountPoint.endsWith( "/" ) ) - mountPoint += '/'; - m_topDirectories.insert( m_lastId, mountPoint ); - return m_lastId; -#endif - scanTrashDirectories(); - return idForTrashDirectory( trashDir ); -} - -void TrashImpl::scanTrashDirectories() const -{ - const KMountPoint::List lst = KMountPoint::currentMountPoints(); - for ( KMountPoint::List::ConstIterator it = lst.begin() ; it != lst.end() ; ++it ) { - const TQCString str = (*it)->mountType().latin1(); - // Skip pseudo-filesystems, there's no chance we'll find a .Trash on them :) - // ## Maybe we should also skip readonly filesystems - if ( str != "proc" && str != "devfs" && str != "usbdevfs" && - str != "sysfs" && str != "devpts" && str != "subfs" /* #96259 */ && - str != "autofs" /* #101116 */ ) { - TQString topdir = (*it)->mountPoint(); - TQString trashDir = trashForMountPoint( topdir, false ); - if ( !trashDir.isEmpty() ) { - // OK, trashDir is a valid trash directory. Ensure it's registered. - int trashId = idForTrashDirectory( trashDir ); - if ( trashId == -1 ) { - // new trash dir found, register it - m_trashDirectories.insert( ++m_lastId, trashDir ); - kdDebug() << k_funcinfo << "found " << trashDir << " gave it id " << m_lastId << endl; - if ( !topdir.endsWith( "/" ) ) - topdir += '/'; - m_topDirectories.insert( m_lastId, topdir ); - } - } - } - } - m_trashDirectoriesScanned = true; -} - -TrashImpl::TrashDirMap TrashImpl::trashDirectories() const -{ - if ( !m_trashDirectoriesScanned ) - scanTrashDirectories(); - return m_trashDirectories; -} - -TrashImpl::TrashDirMap TrashImpl::topDirectories() const -{ - if ( !m_trashDirectoriesScanned ) - scanTrashDirectories(); - return m_topDirectories; -} - -TQString TrashImpl::trashForMountPoint( const TQString& topdir, bool createIfNeeded ) const -{ - // (1) Administrator-created $topdir/.Trash directory - - const TQString rootTrashDir = topdir + "/.Trash"; - const TQCString rootTrashDir_c = TQFile::encodeName( rootTrashDir ); - // Can't use TQFileInfo here since we need to test for the sticky bit - uid_t uid = getuid(); - KDE_struct_stat buff; - const uint requiredBits = S_ISVTX; // Sticky bit required - if ( KDE_lstat( rootTrashDir_c, &buff ) == 0 ) { - if ( (S_ISDIR(buff.st_mode)) // must be a dir - && (!S_ISLNK(buff.st_mode)) // not a symlink - && ((buff.st_mode & requiredBits) == requiredBits) - && (::access(rootTrashDir_c, W_OK)) - ) { - const TQString trashDir = rootTrashDir + "/" + TQString::number( uid ); - const TQCString trashDir_c = TQFile::encodeName( trashDir ); - if ( KDE_lstat( trashDir_c, &buff ) == 0 ) { - if ( (buff.st_uid == uid) // must be owned by user - && (S_ISDIR(buff.st_mode)) // must be a dir - && (!S_ISLNK(buff.st_mode)) // not a symlink - && (buff.st_mode & 0777) == 0700 ) { // rwx for user - return trashDir; - } - kdDebug() << "Directory " << trashDir << " exists but didn't pass the security checks, can't use it" << endl; - } - else if ( createIfNeeded && initTrashDirectory( trashDir_c ) ) { - return trashDir; - } - } else { - kdDebug() << "Root trash dir " << rootTrashDir << " exists but didn't pass the security checks, can't use it" << endl; - } - } - - // (2) $topdir/.Trash-$uid - const TQString trashDir = topdir + "/.Trash-" + TQString::number( uid ); - const TQCString trashDir_c = TQFile::encodeName( trashDir ); - if ( KDE_lstat( trashDir_c, &buff ) == 0 ) - { - if ( (buff.st_uid == uid) // must be owned by user - && (S_ISDIR(buff.st_mode)) // must be a dir - && (!S_ISLNK(buff.st_mode)) // not a symlink - && ((buff.st_mode & 0777) == 0700) ) { // rwx for user, ------ for group and others - - if ( checkTrashSubdirs( trashDir_c ) ) - return trashDir; - } - kdDebug() << "Directory " << trashDir << " exists but didn't pass the security checks, can't use it" << endl; - // Exists, but not useable - return TQString::null; - } - if ( createIfNeeded && initTrashDirectory( trashDir_c ) ) { - return trashDir; - } - return TQString::null; -} - -int TrashImpl::idForTrashDirectory( const TQString& trashDir ) const -{ - // If this is too slow we can always use a reverse map... - TrashDirMap::ConstIterator it = m_trashDirectories.begin(); - for ( ; it != m_trashDirectories.end() ; ++it ) { - if ( it.data() == trashDir ) { - return it.key(); - } - } - return -1; -} - -bool TrashImpl::initTrashDirectory( const TQCString& trashDir_c ) const -{ - if ( ::mkdir( trashDir_c, 0700 ) != 0 ) - return false; - // This trash dir will be useable only if the directory is owned by user. - // In theory this is the case, but not on e.g. USB keys... - uid_t uid = getuid(); - KDE_struct_stat buff; - if ( KDE_lstat( trashDir_c, &buff ) != 0 ) - return false; // huh? - if ( (buff.st_uid == uid) // must be owned by user - && ((buff.st_mode & 0777) == 0700) ) { // rwx for user, --- for group and others - - return checkTrashSubdirs( trashDir_c ); - - } else { - kdDebug() << trashDir_c << " just created, by it doesn't have the right permissions, must be a FAT partition. Removing it again." << endl; - // Not good, e.g. USB key. Delete again. - // I'm paranoid, it would be better to find a solution that allows - // to trash directly onto the USB key, but I don't see how that would - // pass the security checks. It would also make the USB key appears as - // empty when it's in fact full... - ::rmdir( trashDir_c ); - return false; - } - return true; -} - -bool TrashImpl::checkTrashSubdirs( const TQCString& trashDir_c ) const -{ - // testDir currently works with a TQString - ## optimize - TQString trashDir = TQFile::decodeName( trashDir_c ); - const TQString info = trashDir + "/info"; - if ( testDir( info ) != 0 ) - return false; - const TQString files = trashDir + "/files"; - if ( testDir( files ) != 0 ) - return false; - return true; -} - -TQString TrashImpl::trashDirectoryPath( int trashId ) const -{ - // Never scanned for trash dirs? (This can happen after killing kio_trash - // and reusing a directory listing from the earlier instance.) - if ( !m_trashDirectoriesScanned ) - scanTrashDirectories(); - Q_ASSERT( m_trashDirectories.contains( trashId ) ); - return m_trashDirectories[trashId]; -} - -TQString TrashImpl::topDirectoryPath( int trashId ) const -{ - if ( !m_trashDirectoriesScanned ) - scanTrashDirectories(); - assert( trashId != 0 ); - Q_ASSERT( m_topDirectories.contains( trashId ) ); - return m_topDirectories[trashId]; -} - -// Helper method. Creates a URL with the format trash:/trashid-fileid or -// trash:/trashid-fileid/relativePath/To/File for a file inside a trashed directory. -KURL TrashImpl::makeURL( int trashId, const TQString& fileId, const TQString& relativePath ) -{ - KURL url; - url.setProtocol( "trash" ); - TQString path = "/"; - path += TQString::number( trashId ); - path += '-'; - path += fileId; - if ( !relativePath.isEmpty() ) { - path += '/'; - path += relativePath; - } - url.setPath( path ); - return url; -} - -// Helper method. Parses a trash URL with the URL scheme defined in makeURL. -// The trash:/ URL itself isn't parsed here, must be caught by the caller before hand. -bool TrashImpl::parseURL( const KURL& url, int& trashId, TQString& fileId, TQString& relativePath ) -{ - if ( url.protocol() != "trash" ) - return false; - const TQString path = url.path(); - int start = 0; - if ( path[0] == '/' ) // always true I hope - start = 1; - int slashPos = path.find( '-', 0 ); // don't match leading slash - if ( slashPos <= 0 ) - return false; - bool ok = false; - trashId = path.mid( start, slashPos - start ).toInt( &ok ); - Q_ASSERT( ok ); - if ( !ok ) - return false; - start = slashPos + 1; - slashPos = path.find( '/', start ); - if ( slashPos <= 0 ) { - fileId = path.mid( start ); - relativePath = TQString::null; - return true; - } - fileId = path.mid( start, slashPos - start ); - relativePath = path.mid( slashPos + 1 ); - return true; -} - -#include "trashimpl.moc" |