File indexing completed on 2024-11-10 05:13:55
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"