File indexing completed on 2022-09-27 16:31:47

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