/* This file is part of the KDE project Copyright (C) 2004 David Faure <faure@kde.org> 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( KGlobal::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 KDE 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 KIO::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 KIO::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 = KGlobal::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 = KGlobalSettings::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( KIO::ERR_ACCESS_DENIED, origPath ); else error( KIO::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( KIO::RenameDlg::suggestName( baseDirectory, url.fileName() ) ); // and try again on the next iteration } else { error( KIO::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( KIO::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( KIO::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 KIO::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 != KIO::ERR_UNSUPPORTED_ACTION ) return false; KURL urlSrc, urlDest; urlSrc.setPath( src ); urlDest.setPath( dest ); kdDebug() << k_funcinfo << urlSrc << " -> " << urlDest << endl; KIO::CopyJob* job = KIO::moveAs( urlSrc, urlDest, false ); #ifdef KIO_COPYJOB_HAS_SETINTERACTIVE job->setInteractive( false ); #endif connect( job, TQT_SIGNAL( result(KIO::Job *) ), this, TQT_SLOT( jobFinished(KIO::Job *) ) ); qApp->eventLoop()->enterLoop(); return m_lastErrorCode == 0; } void TrashImpl::jobFinished(KIO::Job* job) { kdDebug() << k_funcinfo << " error=" << job->error() << endl; error( job->error(), job->errorText() ); qApp->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; KIO::CopyJob* job = KIO::copyAs( urlSrc, urlDest, false ); #ifdef KIO_COPYJOB_HAS_SETINTERACTIVE job->setInteractive( false ); #endif connect( job, TQT_SIGNAL( result( KIO::Job* ) ), this, TQT_SLOT( jobFinished( KIO::Job* ) ) ); qApp->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( KIO::ERR_UNSUPPORTED_ACTION, TQString::fromLatin1("rename") ); } else { if (( errno == EACCES ) || (errno == EPERM)) { error( KIO::ERR_ACCESS_DENIED, dest ); } else if (errno == EROFS) { // The file is on a read-only filesystem error( KIO::ERR_CANNOT_DELETE, src ); } else { error( KIO::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( KIO::ERR_ACCESS_DENIED, path ); return false; } else if ( errno == ENOSPC ) { error( KIO::ERR_DISK_FULL, path ); return false; } else { error( KIO::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( KIO::ERR_ACCESS_DENIED, file ); else error( KIO::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 ); KIO::ChmodJob* chmodJob = KIO::chmod( fileItemList, 0200, 0200, TQString::null, TQString::null, true /*recursive*/, false /*showProgressInfo*/ ); connect( chmodJob, TQT_SIGNAL( result(KIO::Job *) ), this, TQT_SLOT( jobFinished(KIO::Job *) ) ); qApp->eventLoop()->enterLoop(); } kdDebug() << k_funcinfo << "deleting " << url << endl; KIO::DeleteJob *job = KIO::del( url, false, false ); connect( job, TQT_SIGNAL( result(KIO::Job *) ), this, TQT_SLOT( jobFinished(KIO::Job *) ) ); qApp->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( KIO::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 = 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 = KIO::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"