1/*
2 * disklist.cpp
3 *
4 * Copyright (c) 1999 Michael Kropfberger <michael.kropfberger@gmx.net>
5 * 2009 Dario Andres Rodriguez <andresbajotierra@gmail.com>
6 *
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 */
22
23#include "disklist.h"
24#include "kdfutil.h"
25
26#include <math.h>
27#include <stdlib.h>
28
29#include <QtCore/QTextStream>
30#include <QtCore/QFile>
31#include <QRegExp>
32
33#include <kdebug.h>
34#include <kglobal.h>
35#include <kconfiggroup.h>
36#include <kdefakes.h>
37#include <kprocess.h>
38#include <klocale.h>
39
40static const QLatin1Char Blank = QLatin1Char( ' ' );
41static const QLatin1Char Delimiter = QLatin1Char( '#' );
42
43/***************************************************************************
44 * constructor
45**/
46DiskList::DiskList(QObject *parent)
47 : QObject(parent), dfProc(new KProcess(this))
48{
49 kDebug() ;
50
51 updatesDisabled = false;
52
53 if (No_FS_Type)
54 {
55 kDebug() << "df gives no FS_TYPE" ;
56 }
57
58 disks = new Disks();
59
60 // BackgroundProcesses ****************************************
61 dfProc->setOutputChannelMode(KProcess::MergedChannels);
62 connect(dfProc,SIGNAL(finished(int,QProcess::ExitStatus)),
63 this, SLOT(dfDone()) );
64
65 readingDFStdErrOut=false;
66 config = KGlobal::config();
67 loadSettings();
68}
69
70
71/***************************************************************************
72 * destructor
73**/
74DiskList::~DiskList()
75{
76 dfProc->disconnect();
77 if( dfProc->state() == QProcess::Running )
78 {
79 dfProc->terminate();
80 dfProc->waitForFinished();
81 }
82 delete dfProc;
83 //We have to delete the diskentries manually, otherwise they get leaked (?)
84 // (they aren't released on delete disks )
85 DisksIterator itr = disksIteratorBegin();
86 DisksIterator end = disksIteratorEnd();
87 while( itr != end )
88 {
89 DisksIterator prev = itr;
90 ++itr;
91
92 DiskEntry * disk = *prev;
93 disks->erase( prev );
94 delete disk;
95 }
96 delete disks;
97}
98
99/**
100Updated need to be disabled sometimes to avoid pulling the DiskEntry out from the popupmenu handler
101*/
102void DiskList::setUpdatesDisabled(bool disable)
103{
104 updatesDisabled = disable;
105}
106
107/***************************************************************************
108 * saves the KConfig for special mount/umount scripts
109**/
110void DiskList::applySettings()
111{
112 kDebug() ;
113
114 KConfigGroup group(config, "DiskList");
115 QString key;
116
117 DisksConstIterator itr = disksConstIteratorBegin();
118 DisksConstIterator end = disksConstIteratorEnd();
119 for (; itr != end; ++itr)
120 {
121 DiskEntry * disk = *itr;
122
123 key = QLatin1String("Mount") + Separator + disk->deviceName() + Separator + disk->mountPoint();
124 group.writePathEntry(key,disk->mountCommand());
125
126 key = QLatin1String("Umount") + Separator + disk->deviceName() + Separator + disk->mountPoint();
127 group.writePathEntry(key,disk->umountCommand());
128
129 key = QLatin1String("Icon") + Separator + disk->deviceName() + Separator + disk->mountPoint();
130 group.writePathEntry(key,disk->realIconName());
131 }
132 group.sync();
133}
134
135
136/***************************************************************************
137 * reads the KConfig for special mount/umount scripts
138**/
139void DiskList::loadSettings()
140{
141 kDebug() ;
142
143 const KConfigGroup group(config, "DiskList");
144 QString key;
145
146 DisksConstIterator itr = disksConstIteratorBegin();
147 DisksConstIterator end = disksConstIteratorEnd();
148 for (; itr != end; ++itr)
149 {
150 DiskEntry * disk = *itr;
151
152 key = QLatin1String("Mount") + Separator + disk->deviceName() + Separator + disk->mountPoint();
153 disk->setMountCommand(group.readPathEntry(key, QString()));
154
155 key = QLatin1String("Umount") + Separator + disk->deviceName() + Separator + disk->mountPoint();
156 disk->setUmountCommand(group.readPathEntry(key, QString()));
157
158 key = QLatin1String("Icon") + Separator + disk->deviceName() + Separator + disk->mountPoint();
159 QString icon=group.readPathEntry(key, QString());
160 if (!icon.isEmpty())
161 disk->setIconName(icon);
162 }
163}
164
165
166static QString expandEscapes(const QString& s) {
167 QString rc;
168 for (int i = 0; i < s.length(); i++)
169 {
170 if (s[i] == QLatin1Char( '\\' ))
171 {
172 i++;
173 QChar str=s.at(i);
174 if( str == QLatin1Char( '\\' ))
175 rc += QLatin1Char( '\\' );
176 else if( str == QLatin1Char( '0' ))
177 {
178 rc += QLatin1Char( s.mid(i,3).toULongLong(0, 8) );
179 i += 2;
180 }
181 else
182 {
183 // give up and not process anything else because I'm too lazy
184 // to implement other escapes
185 rc += QLatin1Char( '\\' );
186 rc += s[i];
187 }
188 }
189 else
190 {
191 rc += s[i];
192 }
193 }
194 return rc;
195}
196
197/***************************************************************************
198 * tries to figure out the possibly mounted fs
199**/
200int DiskList::readFSTAB()
201{
202 kDebug() ;
203
204 if (readingDFStdErrOut || (dfProc->state() != QProcess::NotRunning))
205 return -1;
206
207 QFile f(FSTAB);
208 if ( f.open(QIODevice::ReadOnly) )
209 {
210 QTextStream t (&f);
211 QString s;
212 DiskEntry *disk;
213
214 //disks->clear(); // ############
215
216 while (! t.atEnd())
217 {
218 s=t.readLine();
219 s=s.simplified();
220
221 if ( (!s.isEmpty() ) && (s.indexOf(Delimiter)!=0) )
222 {
223 // not empty or commented out by '#'
224 kDebug() << "GOT: [" << s << "]" ;
225 disk = new DiskEntry();
226 disk->setMounted(false);
227 QFile path(QLatin1String( "/dev/disk/by-uuid/" ));
228 // We need to remove UUID=
229 // TODO: Fix for other OS if using UUID and not using /dev/disk/by-uuid/
230 if ( s.contains(QLatin1String( "UUID=" )) )
231 {
232 if (path.exists())
233 {
234 QRegExp uuid( QLatin1String( "UUID=(\\S+)(\\s+)" ));
235 QString extracted ;
236 if (uuid.indexIn(s) != -1)
237 {
238 extracted = uuid.cap(1);
239 }
240
241 if (! extracted.isEmpty() )
242 {
243 QString device = path.fileName() + extracted;
244 QFile file(device);
245
246 if ( file.exists() )
247 {
248 QString filesym = file.symLinkTarget();
249 disk->setDeviceName(filesym);
250 }
251 else
252 {
253 kDebug() << "The device does not seems to exist" ;
254 continue;
255 }
256 }
257 else
258 {
259 kDebug() << "Invalid UUID" ;
260 continue;
261 }
262 }
263 else
264 {
265 kDebug() << "UUID OK but there is no /dev/disk/by-uuid/" ;
266 continue;
267 }
268 }
269 else
270 {
271 disk->setDeviceName(expandEscapes(s.left(s.indexOf(Blank))));
272 }
273
274 s=s.remove(0,s.indexOf(Blank)+1 );
275 // kDebug() << " deviceName: [" << disk->deviceName() << "]" ;
276#ifdef _OS_SOLARIS_
277 //device to fsck
278 s=s.remove(0,s.indexOf(Blank)+1 );
279#endif
280 disk->setMountPoint(expandEscapes(s.left(s.indexOf(Blank))));
281 s=s.remove(0,s.indexOf(Blank)+1 );
282 //kDebug() << " MountPoint: [" << disk->mountPoint() << "]" ;
283 //kDebug() << " Icon: [" << disk->iconName() << "]" ;
284 disk->setFsType(s.left(s.indexOf(Blank)) );
285 s=s.remove(0,s.indexOf(Blank)+1 );
286 //kDebug() << " FS-Type: [" << disk->fsType() << "]" ;
287 disk->setMountOptions(s.left(s.indexOf(Blank)) );
288 s=s.remove(0,s.indexOf(Blank)+1 );
289 //kDebug() << " Mount-Options: [" << disk->mountOptions() << "]" ;
290
291 if ( (disk->deviceName() != QLatin1String( "none" ))
292 && (disk->fsType() != QLatin1String( "swap" ))
293 && (disk->fsType() != QLatin1String( "sysfs" ))
294 && (disk->fsType() != QLatin1String( "rootfs" ))
295 && (disk->fsType() != QLatin1String( "tmpfs" ))
296 && (disk->fsType() != QLatin1String( "debugfs" ))
297 && (disk->fsType() != QLatin1String( "devtmpfs" ))
298 && (disk->mountPoint() != QLatin1String( "/dev/swap" ))
299 && (disk->mountPoint() != QLatin1String( "/dev/pts" ))
300 && (disk->mountPoint() != QLatin1String( "/dev/shm" ))
301 && (!disk->mountPoint().startsWith(QLatin1String( "/sys/" )) )
302 && (!disk->mountPoint().startsWith(QLatin1String( "/proc/" )) ) )
303 {
304 replaceDeviceEntry(disk);
305 }
306 else
307 {
308 delete disk;
309 }
310
311 } //if not empty
312 } //while
313 f.close();
314 } //if f.open
315
316 loadSettings(); //to get the mountCommands
317
318 // kDebug() << "DiskList::readFSTAB DONE" ;
319 return 1;
320}
321
322
323/***************************************************************************
324 * reads the df-commands results
325**/
326int DiskList::readDF()
327{
328 kDebug() ;
329
330 if (readingDFStdErrOut || (dfProc->state() != QProcess::NotRunning))
331 return -1;
332
333 dfProc->clearProgram();
334
335 QStringList dfenv;
336 dfenv << QLatin1String( "LANG=en_US" );
337 dfenv << QLatin1String( "LC_ALL=en_US" );
338 dfenv << QLatin1String( "LC_MESSAGES=en_US" );
339 dfenv << QLatin1String( "LC_TYPE=en_US" );
340 dfenv << QLatin1String( "LANGUAGE=en_US" );
341 dfenv << QLatin1String( "LC_ALL=POSIX" );
342 dfProc->setEnvironment(dfenv);
343 dfProc->setProgram(DF_Command,QString(DF_Args).split(QLatin1Char( ' ' )));
344 dfProc->start();
345
346 if (!dfProc->waitForStarted(-1))
347 qFatal("%s", qPrintable(i18n("could not execute [%1]", QLatin1String(DF_Command))));
348
349 return 1;
350}
351
352
353/***************************************************************************
354 * is called, when the df-command has finished
355**/
356void DiskList::dfDone()
357{
358 kDebug() ;
359
360 if (updatesDisabled)
361 return; //Don't touch the data for now..
362
363 readingDFStdErrOut=true;
364
365 DisksConstIterator itr = disksConstIteratorBegin();
366 DisksConstIterator end = disksConstIteratorEnd();
367 for (; itr != end; ++itr)
368 {
369 DiskEntry * disk = *itr;
370 disk->setMounted(false); // set all devs unmounted
371 }
372
373 QString dfStringErrOut = QString::fromLatin1(dfProc->readAllStandardOutput());
374 QTextStream t (&dfStringErrOut, QIODevice::ReadOnly);
375
376 kDebug() << t.status();
377
378 QString s;
379 while ( !t.atEnd() )
380 {
381 s = t.readLine();
382 if ( s.left(10) == QLatin1String( "Filesystem" ) )
383 break;
384 }
385 if ( t.atEnd() )
386 qFatal("Error running df command... got [%s]",qPrintable(s));
387
388 while ( !t.atEnd() )
389 {
390 QString u,v;
391 DiskEntry *disk;
392 s=t.readLine();
393 s=s.simplified();
394 if ( !s.isEmpty() )
395 {
396 disk = new DiskEntry(); //Q_CHECK_PTR(disk);
397
398 if (!s.contains(Blank)) // devicename was too long, rest in next line
399 if ( !t.atEnd() )
400 { // just appends the next line
401 v=t.readLine();
402 s=s.append(v );
403 s=s.simplified();
404 //kDebug() << "SPECIAL GOT: [" << s << "]" ;
405 }//if silly linefeed
406
407 //kDebug() << "EFFECTIVELY GOT " << s.length() << " chars: [" << s << "]" ;
408
409 disk->setDeviceName(s.left(s.indexOf(Blank)) );
410 s=s.remove(0,s.indexOf(Blank)+1 );
411 //kDebug() << " DeviceName: [" << disk->deviceName() << "]" ;
412
413 if (No_FS_Type)
414 {
415 //kDebug() << "THERE IS NO FS_TYPE_FIELD!" ;
416 disk->setFsType(QLatin1String( "?" ));
417 }
418 else
419 {
420 disk->setFsType(s.left(s.indexOf(Blank)) );
421 s=s.remove(0,s.indexOf(Blank)+1 );
422 };
423 //kDebug() << " FS-Type: [" << disk->fsType() << "]" ;
424 //kDebug() << " Icon: [" << disk->iconName() << "]" ;
425
426 u=s.left(s.indexOf(Blank));
427 disk->setKBSize(u.toULongLong() );
428 s=s.remove(0,s.indexOf(Blank)+1 );
429 //kDebug() << " Size: [" << disk->kBSize() << "]" ;
430
431 u=s.left(s.indexOf(Blank));
432 disk->setKBUsed(u.toULongLong() );
433 s=s.remove(0,s.indexOf(Blank)+1 );
434 //kDebug() << " Used: [" << disk->kBUsed() << "]" ;
435
436 u=s.left(s.indexOf(Blank));
437 disk->setKBAvail(u.toULongLong() );
438 s=s.remove(0,s.indexOf(Blank)+1 );
439 //kDebug() << " Avail: [" << disk->kBAvail() << "]" ;
440
441
442 s=s.remove(0,s.indexOf(Blank)+1 ); // delete the capacity 94%
443 disk->setMountPoint(s);
444 //kDebug() << " MountPoint: [" << disk->mountPoint() << "]" ;
445
446 if ( (disk->kBSize() > 0)
447 && (disk->deviceName() != QLatin1String( "none" ))
448 && (disk->fsType() != QLatin1String( "swap" ))
449 && (disk->fsType() != QLatin1String( "sysfs" ))
450 && (disk->fsType() != QLatin1String( "rootfs" ))
451 && (disk->fsType() != QLatin1String( "tmpfs" ))
452 && (disk->fsType() != QLatin1String( "debugfs" ))
453 && (disk->fsType() != QLatin1String( "devtmpfs" ))
454 && (disk->mountPoint() != QLatin1String( "/dev/swap" ))
455 && (disk->mountPoint() != QLatin1String( "/dev/pts" ))
456 && (disk->mountPoint() != QLatin1String( "/dev/shm" ))
457 && (!disk->mountPoint().startsWith(QLatin1String( "/sys/" )) )
458 && (!disk->mountPoint().startsWith(QLatin1String( "/proc/" )) ) )
459 {
460 disk->setMounted(true); // it is now mounted (df lists only mounted)
461 replaceDeviceEntry(disk);
462 }
463 else
464 {
465 delete disk;
466 }
467
468 }//if not header
469 }//while further lines available
470
471 readingDFStdErrOut=false;
472 loadSettings(); //to get the mountCommands
473 emit readDFDone();
474}
475
476int DiskList::find( DiskEntry* item )
477{
478
479 int pos = -1;
480 int i = 0;
481
482 DisksConstIterator itr = disksConstIteratorBegin();
483 DisksConstIterator end = disksConstIteratorEnd();
484 for (; itr != end; ++itr)
485 {
486 DiskEntry * disk = *itr;
487 if ( *item==*disk )
488 {
489 pos = i;
490 break;
491 }
492 i++;
493 }
494
495 return pos;
496}
497
498void DiskList::deleteAllMountedAt(const QString &mountpoint)
499{
500 kDebug() ;
501
502 DisksIterator itr = disksIteratorBegin();
503 DisksIterator end = disksIteratorEnd();
504 while( itr != end)
505 {
506 DisksIterator prev = itr;
507 ++itr;
508
509 DiskEntry * disk = *prev;
510 if (disk->mountPoint() == mountpoint )
511 {
512 disks->removeOne( disk );
513 delete disk;
514 }
515 }
516}
517
518/***************************************************************************
519 * updates or creates a new DiskEntry in the KDFList and TabListBox
520**/
521void DiskList::replaceDeviceEntry(DiskEntry * disk)
522{
523
524 //kDebug() << disk->deviceRealName() << " " << disk->realMountPoint() ;
525
526 //
527 // The 'disks' may already already contain the 'disk'. If it do
528 // we will replace some data. Otherwise 'disk' will be added to the list
529 //
530
531 int pos = -1;
532 uint i = 0;
533
534 DisksConstIterator itr = disksConstIteratorBegin();
535 DisksConstIterator end = disksConstIteratorEnd();
536 for (; itr != end; ++itr)
537 {
538 DiskEntry * item = *itr;
539 if( disk->realCompare(*item) )
540 {
541 pos = i;
542 break;
543 }
544 i++;
545 }
546
547 if ((pos == -1) && (disk->mounted()) )
548 // no matching entry found for mounted disk
549 if ((disk->fsType() == QLatin1String( "?" )) || (disk->fsType() == QLatin1String( "cachefs" )))
550 {
551 //search for fitting cachefs-entry in static /etc/vfstab-data
552 DiskEntry* olddisk;
553
554 DisksConstIterator itr = disksConstIteratorBegin();
555 DisksConstIterator end = disksConstIteratorEnd();
556 for (; itr != end; ++itr)
557 {
558 int p;
559 // cachefs deviceNames have no / behind the host-column
560 // eg. /cache/cache/.cfs_mnt_points/srv:_home_jesus
561 // ^ ^
562 olddisk = *itr;
563
564 QString odiskName = olddisk->deviceName();
565 int ci=odiskName.indexOf(QLatin1Char( ':' )); // goto host-column
566 while ((ci =odiskName.indexOf(QLatin1Char( '/' ),ci)) > 0)
567 {
568 odiskName.replace(ci,1,QLatin1String( "_" ));
569 }//while
570 // check if there is something that is exactly the tail
571 // eg. [srv:/tmp3] is exact tail of [/cache/.cfs_mnt_points/srv:_tmp3]
572 if ( ( (p=disk->deviceName().lastIndexOf(odiskName
573 ,disk->deviceName().length()) )
574 != -1)
575 && (p + odiskName.length()
576 == disk->deviceName().length()) )
577 {
578 pos = disks->indexOf(disk); //store the actual position
579 disk->setDeviceName(olddisk->deviceName());
580 }
581 //else olddisk=disks->next();
582 }// while
583 }// if fsType == "?" or "cachefs"
584
585
586#ifdef No_FS_Type
587 if (pos != -1)
588 {
589 DiskEntry * olddisk = disks->at(pos);
590 if (olddisk)
591 disk->setFsType(olddisk->fsType());
592 }
593#endif
594
595 if (pos != -1)
596 { // replace
597 DiskEntry * olddisk = disks->at(pos);
598 if ( (olddisk->mountOptions().contains(QLatin1String( "user" ))) &&
599 ( disk->mountOptions().contains(QLatin1String( "user" ))) )
600 {
601 // add "user" option to new diskEntry
602 QString s=disk->mountOptions();
603 if (s.length()>0)
604 s.append(QLatin1String( "," ));
605 s.append(QLatin1String( "user" ));
606 disk->setMountOptions(s);
607 }
608 disk->setMountCommand(olddisk->mountCommand());
609 disk->setUmountCommand(olddisk->umountCommand());
610
611 // Same device name, but maybe one is a symlink and the other is its target
612 // Keep the shorter one then, /dev/hda1 looks better than /dev/ide/host0/bus0/target0/lun0/part1
613 // but redefine "shorter" to be the number of slashes in the path as a count on characters
614 // breaks legitimate symlinks created by udev
615 if ( disk->deviceName().count( QLatin1Char( '/' ) ) > olddisk->deviceName().count( QLatin1Char( '/' ) ) )
616 disk->setDeviceName(olddisk->deviceName());
617
618 //FStab after an older DF ... needed for critFull
619 //so the DF-KBUsed survive a FStab lookup...
620 //but also an unmounted disk may then have a kbused set...
621 if ( (olddisk->mounted()) && (!disk->mounted()) )
622 {
623 disk->setKBSize(olddisk->kBSize());
624 disk->setKBUsed(olddisk->kBUsed());
625 disk->setKBAvail(olddisk->kBAvail());
626 }
627 if ( (olddisk->percentFull() != -1) &&
628 (olddisk->percentFull() < Full_Percent) &&
629 (disk->percentFull() >= Full_Percent) )
630 {
631 kDebug() << "Device " << disk->deviceName()
632 << " is critFull! " << olddisk->percentFull()
633 << "--" << disk->percentFull() << endl;
634 emit criticallyFull(disk);
635 }
636
637 //Take the diskentry from the list and delete it properly
638 DiskEntry * tmp = disks->takeAt(pos);
639 delete tmp;
640
641 disks->insert(pos,disk);
642 }
643 else
644 {
645 disks->append(disk);
646 }//if
647
648}
649
650#include "disklist.moc"
651
652