File indexing completed on 2024-04-14 05:43:04

0001 /*
0002     SPDX-FileCopyrightText: 1999 Michael Kropfberger <michael.kropfberger@gmx.net>
0003     SPDX-FileCopyrightText: 2009 Dario Andres Rodriguez <andresbajotierra@gmail.com>
0004     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "disklist.h"
0010 
0011 #include "kdfprivate_debug.h"
0012 #include "kdfutil.h"
0013 
0014 #include <KConfigGroup>
0015 #include <KLocalizedString>
0016 #include <KProcess>
0017 
0018 #include <QTextStream>
0019 #include <QFile>
0020 #include <QRegExp>
0021 
0022 #include <math.h>
0023 #include <stdlib.h>
0024 
0025 static const QLatin1Char Blank = QLatin1Char( ' ' );
0026 static const QLatin1Char Delimiter = QLatin1Char( '#' );
0027 
0028 /***************************************************************************
0029   * constructor
0030 **/
0031 DiskList::DiskList(QObject *parent)
0032         : QObject(parent), dfProc(new KProcess(this))
0033 {
0034     qCDebug(KDF);
0035 
0036     updatesDisabled = false;
0037 
0038     if (No_FS_Type)
0039     {
0040         qCDebug(KDF) << "df gives no FS_TYPE";
0041     }
0042 
0043     disks = new Disks();
0044 
0045     // BackgroundProcesses ****************************************
0046     dfProc->setOutputChannelMode(KProcess::MergedChannels);
0047     connect(dfProc,SIGNAL(finished(int,QProcess::ExitStatus)),
0048             this, SLOT(dfDone()) );
0049 
0050     readingDFStdErrOut=false;
0051     config = KSharedConfig::openConfig();
0052     loadSettings();
0053 }
0054 
0055 
0056 /***************************************************************************
0057   * destructor
0058 **/
0059 DiskList::~DiskList()
0060 {
0061     dfProc->disconnect();
0062     if( dfProc->state() == QProcess::Running )
0063     {
0064         dfProc->terminate();
0065         dfProc->waitForFinished();
0066     }
0067     delete dfProc;
0068     qDeleteAll(*disks);
0069     delete disks;
0070 }
0071 
0072 /**
0073 Updated need to be disabled sometimes to avoid pulling the DiskEntry out from the popupmenu handler
0074 */
0075 void DiskList::setUpdatesDisabled(bool disable)
0076 {
0077     updatesDisabled = disable;
0078 }
0079 
0080 /***************************************************************************
0081   * saves the KConfig for special mount/umount scripts
0082 **/
0083 void DiskList::applySettings()
0084 {
0085     qCDebug(KDF);
0086 
0087     KConfigGroup group(config, QStringLiteral("DiskList"));
0088     QString key;
0089 
0090     DisksConstIterator itr = disksConstIteratorBegin();
0091     DisksConstIterator end = disksConstIteratorEnd();
0092     for (; itr != end; ++itr)
0093     {
0094         DiskEntry * disk = *itr;
0095 
0096         key = QLatin1String("Mount") + Separator + disk->deviceName() + Separator + disk->mountPoint();
0097         group.writePathEntry(key,disk->mountCommand());
0098 
0099         key = QLatin1String("Umount") + Separator + disk->deviceName() + Separator + disk->mountPoint();
0100         group.writePathEntry(key,disk->umountCommand());
0101 
0102         key = QLatin1String("Icon") + Separator + disk->deviceName() + Separator + disk->mountPoint();
0103         group.writePathEntry(key,disk->realIconName());
0104     }
0105     group.sync();
0106 }
0107 
0108 
0109 /***************************************************************************
0110   * reads the KConfig for special mount/umount scripts
0111 **/
0112 void DiskList::loadSettings()
0113 {
0114     qCDebug(KDF);
0115 
0116     const KConfigGroup group(config, QStringLiteral("DiskList"));
0117     QString key;
0118 
0119     DisksConstIterator itr = disksConstIteratorBegin();
0120     DisksConstIterator end = disksConstIteratorEnd();
0121     for (; itr != end; ++itr)
0122     {
0123         DiskEntry * disk = *itr;
0124 
0125         key = QLatin1String("Mount") + Separator + disk->deviceName() + Separator + disk->mountPoint();
0126         disk->setMountCommand(group.readPathEntry(key, QString()));
0127 
0128         key = QLatin1String("Umount") + Separator + disk->deviceName() + Separator + disk->mountPoint();
0129         disk->setUmountCommand(group.readPathEntry(key, QString()));
0130 
0131         key = QLatin1String("Icon") + Separator + disk->deviceName() + Separator + disk->mountPoint();
0132         QString icon=group.readPathEntry(key, QString());
0133         if (!icon.isEmpty())
0134             disk->setIconName(icon);
0135     }
0136 }
0137 
0138 
0139 static QString expandEscapes(const QString& s) {
0140     QString rc;
0141     for (int i = 0; i < s.length(); i++)
0142     {
0143         if (s[i] == QLatin1Char( '\\' ))
0144         {
0145             i++;
0146             QChar str=s.at(i);
0147             if( str == QLatin1Char( '\\' ))
0148                 rc += QLatin1Char( '\\' );
0149             else if( str == QLatin1Char( '0' ))
0150             {
0151                 rc += QLatin1Char( s.mid(i,3).toULongLong(nullptr, 8) );
0152                 i += 2;
0153             }
0154             else
0155             {
0156                 // give up and not process anything else because I'm too lazy
0157                 // to implement other escapes
0158                 rc += QLatin1Char( '\\' );
0159                 rc += s[i];
0160             }
0161         }
0162         else
0163         {
0164             rc += s[i];
0165         }
0166     }
0167     return rc;
0168 }
0169 
0170 /***************************************************************************
0171   * tries to figure out the possibly mounted fs
0172 **/
0173 int DiskList::readFSTAB()
0174 {
0175     qCDebug(KDF);
0176 
0177     if (readingDFStdErrOut || (dfProc->state() != QProcess::NotRunning))
0178         return -1;
0179 
0180     QFile f(FSTAB);
0181     if ( f.open(QIODevice::ReadOnly) )
0182     {
0183         QTextStream t (&f);
0184         QString s;
0185         DiskEntry *disk;
0186 
0187         //disks->clear(); // ############
0188 
0189         while (! t.atEnd())
0190         {
0191             s=t.readLine();
0192             s=s.simplified();
0193 
0194         if ( (!s.isEmpty() ) && (s.indexOf(Delimiter)!=0) )
0195         {
0196                 // not empty or commented out by '#'
0197                 qCDebug(KDF) << "GOT: [" << s << "]";
0198                 disk = new DiskEntry();
0199                 disk->setMounted(false);
0200         QFile path(QStringLiteral( "/dev/disk/by-uuid/" ));
0201         // We need to remove UUID=
0202         // TODO: Fix for other OS if using UUID and not using /dev/disk/by-uuid/
0203         if ( s.contains(QLatin1String( "UUID=" )) )
0204         {
0205             if (path.exists())
0206             {
0207                 QRegExp uuid( QLatin1String( "UUID=(\\S+)(\\s+)" ));
0208                 QString extracted ;
0209                 if (uuid.indexIn(s) != -1)
0210                 {
0211                     extracted = uuid.cap(1);
0212                 }
0213 
0214                 if (! extracted.isEmpty() )
0215                 {
0216                     QString device = path.fileName() + extracted;
0217                     QFile file(device);
0218 
0219                     if ( file.exists() )
0220                     {
0221                         QString filesym = file.symLinkTarget();
0222                         disk->setDeviceName(filesym);
0223                     }
0224                     else
0225                     {
0226                         qCDebug(KDF) << "The device does not seems to exist";
0227                         continue;
0228                     }
0229                 }
0230                 else
0231                 {
0232                     qCDebug(KDF) << "Invalid UUID";
0233                     continue;
0234                 }
0235             }
0236             else
0237             {
0238                 qCDebug(KDF) << "UUID OK but there is no /dev/disk/by-uuid/";
0239                 continue;
0240             }
0241         }
0242         else
0243         {
0244             disk->setDeviceName(expandEscapes(s.left(s.indexOf(Blank))));
0245         }
0246 
0247         s.remove(0,s.indexOf(Blank)+1 );
0248 #ifdef _OS_SOLARIS_
0249                 //device to fsck
0250                 s.remove(0,s.indexOf(Blank)+1 );
0251 #endif
0252         disk->setMountPoint(expandEscapes(s.left(s.indexOf(Blank))));
0253         s.remove(0,s.indexOf(Blank)+1 );
0254         disk->setFsType(s.left(s.indexOf(Blank)) );
0255         s.remove(0,s.indexOf(Blank)+1 );
0256                 disk->setMountOptions(s.left(s.indexOf(Blank)) );
0257                 s.remove(0,s.indexOf(Blank)+1 );
0258 
0259                 if ( (disk->deviceName() != QLatin1String( "none" ))
0260                         && (disk->fsType() != QLatin1String( "swap" ))
0261                         && (disk->fsType() != QLatin1String( "sysfs" ))
0262                         && (disk->fsType() != QLatin1String( "rootfs" ))
0263                         && (disk->fsType() != QLatin1String( "tmpfs" ))
0264                         && (disk->fsType() != QLatin1String( "debugfs" ))
0265                         && (disk->fsType() != QLatin1String( "devtmpfs" ))
0266                         && (disk->mountPoint() != QLatin1String( "/dev/swap" ))
0267                         && (disk->mountPoint() != QLatin1String( "/dev/pts" ))
0268                         && (disk->mountPoint() != QLatin1String( "/dev/shm" ))
0269                         && (!disk->mountPoint().startsWith(QLatin1String( "/sys/" )) )
0270                         && (!disk->mountPoint().startsWith(QLatin1String( "/proc/" )) ) )
0271                 {
0272                     replaceDeviceEntry(disk);
0273                 }
0274                 else
0275                 {
0276                     delete disk;
0277                 }
0278 
0279             } //if not empty
0280         } //while
0281         f.close();
0282     } //if f.open
0283 
0284     loadSettings(); //to get the mountCommands
0285 
0286     return 1;
0287 }
0288 
0289 
0290 /***************************************************************************
0291   * reads the df-commands results
0292 **/
0293 int DiskList::readDF()
0294 {
0295     qCDebug(KDF);
0296 
0297     if (readingDFStdErrOut || (dfProc->state() != QProcess::NotRunning))
0298         return -1;
0299 
0300     dfProc->clearProgram();
0301 
0302     QStringList dfenv;
0303     dfenv << QStringLiteral( "LANG=en_US" );
0304     dfenv << QStringLiteral( "LC_ALL=en_US" );
0305     dfenv << QStringLiteral( "LC_MESSAGES=en_US" );
0306     dfenv << QStringLiteral( "LC_TYPE=en_US" );
0307     dfenv << QStringLiteral( "LANGUAGE=en_US" );
0308     dfenv << QStringLiteral( "LC_ALL=POSIX" );
0309     dfProc->setEnvironment(dfenv);
0310     dfProc->setProgram(DF_Command,QString(DF_Args).split(QLatin1Char( ' ' )));
0311     dfProc->start();
0312 
0313     if (!dfProc->waitForStarted(-1))
0314         qFatal("%s", qPrintable(i18n("could not execute [%1]", QLatin1String(DF_Command))));
0315 
0316     return 1;
0317 }
0318 
0319 
0320 /***************************************************************************
0321   * is called, when the df-command has finished
0322 **/
0323 void DiskList::dfDone()
0324 {
0325     qCDebug(KDF);
0326 
0327     if (updatesDisabled)
0328         return; //Don't touch the data for now..
0329 
0330     readingDFStdErrOut=true;
0331 
0332     DisksConstIterator itr = disksConstIteratorBegin();
0333     DisksConstIterator end = disksConstIteratorEnd();
0334     for (; itr != end; ++itr)
0335     {
0336         DiskEntry * disk = *itr;
0337         disk->setMounted(false);  // set all devs unmounted
0338     }
0339 
0340     QString dfStringErrOut = QString::fromLatin1(dfProc->readAllStandardOutput());
0341     QTextStream t (&dfStringErrOut, QIODevice::ReadOnly);
0342 
0343     qCDebug(KDF) << t.status();
0344 
0345     QString s;
0346     while ( !t.atEnd() )
0347     {
0348         s = t.readLine();
0349         if ( s.left(10) == QLatin1String( "Filesystem" ) )
0350             break;
0351     }
0352     if ( t.atEnd() )
0353         qFatal("Error running df command... got [%s]",qPrintable(s));
0354 
0355     while ( !t.atEnd() )
0356     {
0357         QString u,v;
0358         DiskEntry *disk;
0359         s=t.readLine();
0360         s=s.simplified();
0361         if ( !s.isEmpty() )
0362         {
0363             disk = new DiskEntry(); //Q_CHECK_PTR(disk);
0364 
0365             if (!s.contains(Blank))      // devicename was too long, rest in next line
0366                 if ( !t.atEnd() )
0367                 {       // just appends the next line
0368                     v=t.readLine();
0369                     s.append(v );
0370                     s=s.simplified();
0371                 }//if silly linefeed
0372 
0373 
0374             disk->setDeviceName(s.left(s.indexOf(Blank)) );
0375             s.remove(0,s.indexOf(Blank)+1 );
0376 
0377             if (No_FS_Type)
0378             {
0379                 disk->setFsType(QStringLiteral( "?" ));
0380             }
0381             else
0382             {
0383                 disk->setFsType(s.left(s.indexOf(Blank)) );
0384                 s.remove(0,s.indexOf(Blank)+1 );
0385             };
0386 
0387             u=s.left(s.indexOf(Blank));
0388             disk->setKBSize(u.toULongLong() );
0389             s.remove(0,s.indexOf(Blank)+1 );
0390 
0391             u=s.left(s.indexOf(Blank));
0392             disk->setKBUsed(u.toULongLong() );
0393             s.remove(0,s.indexOf(Blank)+1 );
0394 
0395             u=s.left(s.indexOf(Blank));
0396             disk->setKBAvail(u.toULongLong() );
0397             s.remove(0,s.indexOf(Blank)+1 );
0398 
0399 
0400             s.remove(0,s.indexOf(Blank)+1 );  // delete the capacity 94%
0401             disk->setMountPoint(s);
0402 
0403             if ( (disk->kBSize() > 0)
0404                     && (disk->deviceName() != QLatin1String( "none" ))
0405                     && (disk->fsType() != QLatin1String( "swap" ))
0406                     && (disk->fsType() != QLatin1String( "sysfs" ))
0407                     && (disk->fsType() != QLatin1String( "rootfs" ))
0408                     && (disk->fsType() != QLatin1String( "tmpfs" ))
0409                     && (disk->fsType() != QLatin1String( "debugfs" ))
0410                     && (disk->fsType() != QLatin1String( "devtmpfs" ))
0411                     && (disk->mountPoint() != QLatin1String( "/dev/swap" ))
0412                     && (disk->mountPoint() != QLatin1String( "/dev/pts" ))
0413                     && (disk->mountPoint() != QLatin1String( "/dev/shm" ))
0414                     && (!disk->mountPoint().startsWith(QLatin1String( "/sys/" )) )
0415                     && (!disk->mountPoint().startsWith(QLatin1String( "/proc/" )) ) )
0416             {
0417                 disk->setMounted(true);    // it is now mounted (df lists only mounted)
0418                 replaceDeviceEntry(disk);
0419             }
0420             else
0421             {
0422                 delete disk;
0423             }
0424 
0425         }//if not header
0426     }//while further lines available
0427 
0428     readingDFStdErrOut=false;
0429     loadSettings(); //to get the mountCommands
0430     Q_EMIT readDFDone();
0431 }
0432 
0433 int DiskList::find( DiskEntry* item )
0434 {
0435 
0436     int pos = -1;
0437     int i = 0;
0438 
0439     DisksConstIterator itr = disksConstIteratorBegin();
0440     DisksConstIterator end = disksConstIteratorEnd();
0441     for (; itr != end; ++itr)
0442     {
0443         DiskEntry * disk = *itr;
0444         if ( *item==*disk )
0445         {
0446             pos = i;
0447             break;
0448         }
0449         i++;
0450     }
0451 
0452     return pos;
0453 }
0454 
0455 void DiskList::deleteAllMountedAt(const QString &mountpoint)
0456 {
0457     qCDebug(KDF);
0458 
0459     for (auto it = disksIteratorBegin(); it != disksIteratorEnd();) {
0460         DiskEntry *disk = *it;
0461         Q_ASSERT(disk);
0462         if (disk->mountPoint() == mountpoint) {
0463             it = disks->erase(it);
0464             disk->deleteLater();
0465         } else {
0466             ++it;
0467         }
0468     }
0469 }
0470 
0471 /***************************************************************************
0472   * updates or creates a new DiskEntry in the KDFList and TabListBox
0473 **/
0474 void DiskList::replaceDeviceEntry(DiskEntry * disk)
0475 {
0476 
0477     //
0478     // The 'disks' may already contain the 'disk'. If it do
0479     // we will replace some data. Otherwise 'disk' will be added to the list
0480     //
0481 
0482     int pos = -1;
0483     uint i = 0;
0484 
0485     DisksConstIterator itr = disksConstIteratorBegin();
0486     DisksConstIterator end = disksConstIteratorEnd();
0487     for (; itr != end; ++itr)
0488     {
0489         DiskEntry * item = *itr;
0490         if( disk->realCompare(*item) )
0491         {
0492             pos = i;
0493             break;
0494         }
0495         i++;
0496     }
0497 
0498     if ((pos == -1) && (disk->mounted()) )
0499         // no matching entry found for mounted disk
0500         if ((disk->fsType() == QLatin1String( "?" )) || (disk->fsType() == QLatin1String( "cachefs" )))
0501         {
0502             //search for fitting cachefs-entry in static /etc/vfstab-data
0503             DiskEntry* olddisk;
0504 
0505             DisksConstIterator itr = disksConstIteratorBegin();
0506             DisksConstIterator end = disksConstIteratorEnd();
0507             for (; itr != end; ++itr)
0508             {
0509                 int p;
0510                 // cachefs deviceNames have no / behind the host-column
0511                 // eg. /cache/cache/.cfs_mnt_points/srv:_home_jesus
0512                 //                                      ^    ^
0513                 olddisk = *itr;
0514 
0515                 QString odiskName = olddisk->deviceName();
0516                 int ci=odiskName.indexOf(QLatin1Char( ':' )); // goto host-column
0517                 while ((ci =odiskName.indexOf(QLatin1Char( '/' ),ci)) > 0)
0518                 {
0519                     odiskName.replace(ci,1,QStringLiteral( "_" ));
0520                 }//while
0521                 // check if there is something that is exactly the tail
0522                 // eg. [srv:/tmp3] is exact tail of [/cache/.cfs_mnt_points/srv:_tmp3]
0523                 if ( ( (p=disk->deviceName().lastIndexOf(odiskName
0524                           ,disk->deviceName().length()) )
0525                         != -1)
0526                         && (p + odiskName.length()
0527                             == disk->deviceName().length()) )
0528                 {
0529                     pos = disks->indexOf(disk); //store the actual position
0530                     disk->setDeviceName(olddisk->deviceName());
0531                 }
0532                 //else  olddisk=disks->next();
0533             }// while
0534         }// if fsType == "?" or "cachefs"
0535 
0536 
0537 #ifdef No_FS_Type
0538     if (pos != -1)
0539     {
0540         DiskEntry * olddisk = disks->at(pos);
0541         if (olddisk)
0542             disk->setFsType(olddisk->fsType());
0543     }
0544 #endif
0545 
0546     if (pos != -1)
0547     {  // replace
0548         DiskEntry * olddisk = disks->at(pos);
0549         if ( (olddisk->mountOptions().contains(QLatin1String( "user" ))) &&
0550                 ( disk->mountOptions().contains(QLatin1String( "user" ))) )
0551         {
0552             // add "user" option to new diskEntry
0553             QString s=disk->mountOptions();
0554             if (s.length()>0)
0555                 s.append(QLatin1String( "," ));
0556             s.append(QLatin1String( "user" ));
0557             disk->setMountOptions(s);
0558         }
0559         disk->setMountCommand(olddisk->mountCommand());
0560         disk->setUmountCommand(olddisk->umountCommand());
0561 
0562         // Same device name, but maybe one is a symlink and the other is its target
0563         // Keep the shorter one then, /dev/hda1 looks better than /dev/ide/host0/bus0/target0/lun0/part1
0564         // but redefine "shorter" to be the number of slashes in the path as a count on characters
0565         // breaks legitimate symlinks created by udev
0566         if ( disk->deviceName().count( QLatin1Char( '/' ) ) > olddisk->deviceName().count( QLatin1Char( '/' ) ) )
0567             disk->setDeviceName(olddisk->deviceName());
0568 
0569         //FStab after an older DF ... needed for critFull
0570         //so the DF-KBUsed survive a FStab lookup...
0571         //but also an unmounted disk may then have a kbused set...
0572         if ( (olddisk->mounted()) && (!disk->mounted()) )
0573         {
0574             disk->setKBSize(olddisk->kBSize());
0575             disk->setKBUsed(olddisk->kBUsed());
0576             disk->setKBAvail(olddisk->kBAvail());
0577         }
0578         if ( (olddisk->percentFull() != -1) &&
0579                 (olddisk->percentFull() <  Full_Percent) &&
0580                 (disk->percentFull() >= Full_Percent) )
0581         {
0582             qCDebug(KDF) << "Device " << disk->deviceName()
0583                          << " is critFull! " << olddisk->percentFull()
0584                          << "--" << disk->percentFull();
0585             Q_EMIT criticallyFull(disk);
0586         }
0587 
0588         //Take the diskentry from the list and delete it properly
0589         DiskEntry * tmp = disks->takeAt(pos);
0590         delete tmp;
0591 
0592         disks->insert(pos,disk);
0593     }
0594     else
0595     {
0596         disks->append(disk);
0597     }//if
0598 
0599 }
0600 
0601 
0602 
0603 #include "moc_disklist.cpp"