summaryrefslogtreecommitdiff
path: root/utils/rbutilqt/base/utils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/rbutilqt/base/utils.cpp')
-rw-r--r--utils/rbutilqt/base/utils.cpp1062
1 files changed, 1062 insertions, 0 deletions
diff --git a/utils/rbutilqt/base/utils.cpp b/utils/rbutilqt/base/utils.cpp
new file mode 100644
index 0000000000..f2d3f04887
--- /dev/null
+++ b/utils/rbutilqt/base/utils.cpp
@@ -0,0 +1,1062 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2007 by Dominik Wenger
10 *
11 * All files in this archive are subject to the GNU General Public License.
12 * See the file COPYING in the source tree root for full license agreement.
13 *
14 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
15 * KIND, either express or implied.
16 *
17 ****************************************************************************/
18
19#include "utils.h"
20#include "rockboxinfo.h"
21#include "system.h"
22#include "rbsettings.h"
23#include "playerbuildinfo.h"
24#include "Logger.h"
25
26#if !defined(_UNICODE)
27#define _UNICODE
28#endif
29
30#include <QtCore>
31#include <QDebug>
32#include <cstdlib>
33#include <stdio.h>
34
35#if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
36#include <sys/statvfs.h>
37#endif
38#if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
39#include <stdio.h>
40#endif
41#if defined(Q_OS_LINUX)
42#include <mntent.h>
43#endif
44#if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
45#include <sys/param.h>
46#include <sys/ucred.h>
47#include <sys/mount.h>
48#endif
49#if defined(Q_OS_WIN32)
50#include <stdio.h>
51#include <tchar.h>
52#include <windows.h>
53#include <setupapi.h>
54#include <winioctl.h>
55#include <tlhelp32.h>
56#endif
57#if defined(Q_OS_MACX)
58#include <Carbon/Carbon.h>
59#include <CoreFoundation/CoreFoundation.h>
60#include <CoreServices/CoreServices.h>
61#include <IOKit/IOKitLib.h>
62#endif
63
64// recursive function to delete a dir with files
65bool Utils::recursiveRmdir( const QString &dirName )
66{
67 QString dirN = dirName;
68 QDir dir(dirN);
69 // make list of entries in directory
70 QStringList list = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
71 QFileInfo fileInfo;
72 QString curItem;
73 for(int i = 0; i < list.size(); i++){ // loop through all items of list
74 QString name = list.at(i);
75 curItem = dirN + "/" + name;
76 fileInfo.setFile(curItem);
77 if(fileInfo.isDir()) // is directory
78 recursiveRmdir(curItem); // call recRmdir() recursively for
79 // deleting subdirectory
80 else // is file
81 QFile::remove(curItem); // ok, delete file
82 }
83 dir.cdUp();
84 return dir.rmdir(dirN); // delete empty dir and return if (now empty)
85 // dir-removing was successfull
86}
87
88
89//! @brief resolves the given path, ignoring case.
90//! @param path absolute path to resolve.
91//! @return returns exact casing of path, empty string if path not found.
92QString Utils::resolvePathCase(QString path)
93{
94 int start;
95 QString realpath;
96#if QT_VERSION >= 0x050e00
97 QStringList elems = path.split("/", Qt::SkipEmptyParts);
98#else
99 QStringList elems = path.split("/", QString::SkipEmptyParts);
100#endif
101
102 if(path.isEmpty())
103 return QString();
104#if defined(Q_OS_WIN32)
105 // on windows we must make sure to start with the first entry (i.e. the
106 // drive letter) instead of a single / to make resolving work.
107 start = 1;
108 realpath = elems.at(0) + "/";
109#else
110 start = 0;
111 realpath = "/";
112#endif
113
114 for(int i = start; i < elems.size(); i++) {
115 QStringList direlems
116 = QDir(realpath).entryList(QDir::AllEntries|QDir::Hidden|QDir::System);
117 if(direlems.contains(elems.at(i), Qt::CaseInsensitive)) {
118 // need to filter using QRegExp as QStringList::filter(QString)
119 // matches any substring
120 QString expr = QString("^" + elems.at(i) + "$");
121 QRegExp rx = QRegExp(expr, Qt::CaseInsensitive);
122 QStringList a = direlems.filter(rx);
123
124 if(a.size() != 1)
125 return QString("");
126 if(!realpath.endsWith("/"))
127 realpath += "/";
128 realpath += a.at(0);
129 }
130 else
131 return QString("");
132 }
133 LOG_INFO() << "resolving path" << path << "->" << realpath;
134 return realpath;
135}
136
137
138QString Utils::filesystemType(QString path)
139{
140#if defined(Q_OS_LINUX)
141 FILE *mn = setmntent("/etc/mtab", "r");
142 if(!mn)
143 return QString("");
144
145 struct mntent *ent;
146 while((ent = getmntent(mn))) {
147 if(QString(ent->mnt_dir) == path) {
148 endmntent(mn);
149 LOG_INFO() << "device type is" << ent->mnt_type;
150 return QString(ent->mnt_type);
151 }
152 }
153 endmntent(mn);
154#endif
155
156#if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
157 int num;
158 struct statfs *mntinf;
159
160 num = getmntinfo(&mntinf, MNT_WAIT);
161 while(num--) {
162 if(QString(mntinf->f_mntonname) == path) {
163 LOG_INFO() << "device type is" << mntinf->f_fstypename;
164 return QString(mntinf->f_fstypename);
165 }
166 mntinf++;
167 }
168#endif
169
170#if defined(Q_OS_WIN32)
171 wchar_t t[64];
172 memset(t, 0, 32);
173 if(GetVolumeInformationW((LPCWSTR)path.utf16(),
174 NULL, 0, NULL, NULL, NULL, t, 64)) {
175 LOG_INFO() << "device type is" << t;
176 return QString::fromWCharArray(t);
177 }
178#endif
179 return QString("-");
180}
181
182
183QString Utils::filesystemName(QString path)
184{
185 QString name;
186#if defined(Q_OS_WIN32)
187 wchar_t volname[MAX_PATH+1];
188 bool res = GetVolumeInformationW((LPTSTR)path.utf16(), volname, MAX_PATH+1,
189 NULL, NULL, NULL, NULL, 0);
190 if(res) {
191 name = QString::fromWCharArray(volname);
192 }
193#endif
194#if defined(Q_OS_MACX)
195 // BSD label does not include folder.
196 QString bsd = Utils::resolveDevicename(path).remove("/dev/");
197 if(bsd.isEmpty()) {
198 return name;
199 }
200 OSStatus result;
201 ItemCount index = 1;
202
203 do {
204 FSVolumeRefNum volrefnum;
205 HFSUniStr255 volname;
206
207 result = FSGetVolumeInfo(kFSInvalidVolumeRefNum, index, &volrefnum,
208 kFSVolInfoFSInfo, NULL, &volname, NULL);
209
210 if(result == noErr) {
211 GetVolParmsInfoBuffer volparms;
212 /* PBHGetVolParmsSync() is not available for 64bit while
213 FSGetVolumeParms() is available in 10.5+. Thus we need to use
214 PBHGetVolParmsSync() for 10.4, and that also requires 10.4 to
215 always use 32bit.
216 Qt 4 supports 32bit on 10.6 Cocoa only.
217 */
218#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
219 if(FSGetVolumeParms(volrefnum, &volparms, sizeof(volparms)) == noErr)
220#else
221 HParamBlockRec hpb;
222 hpb.ioParam.ioNamePtr = NULL;
223 hpb.ioParam.ioVRefNum = volrefnum;
224 hpb.ioParam.ioBuffer = (Ptr)&volparms;
225 hpb.ioParam.ioReqCount = sizeof(volparms);
226 if(PBHGetVolParmsSync(&hpb) == noErr)
227#endif
228 {
229 if(volparms.vMServerAdr == 0) {
230 if(bsd == (char*)volparms.vMDeviceID) {
231 name = QString::fromUtf16((const ushort*)volname.unicode,
232 (int)volname.length);
233 break;
234 }
235 }
236 }
237 }
238 index++;
239 } while(result == noErr);
240#endif
241
242 LOG_INFO() << "Volume name of" << path << "is" << name;
243 return name;
244}
245
246
247//! @brief figure the free disk space on a filesystem
248//! @param path path on the filesystem to check
249//! @return size in bytes
250qulonglong Utils::filesystemFree(QString path)
251{
252 qulonglong size = filesystemSize(path, FilesystemFree);
253 LOG_INFO() << "free disk space for" << path << size;
254 return size;
255}
256
257
258qulonglong Utils::filesystemTotal(QString path)
259{
260 qulonglong size = filesystemSize(path, FilesystemTotal);
261 LOG_INFO() << "total disk space for" << path << size;
262 return size;
263}
264
265
266qulonglong Utils::filesystemSize(QString path, enum Utils::Size type)
267{
268 qulonglong size = 0;
269#if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
270 // the usage of statfs() is deprecated by the LSB so use statvfs().
271 struct statvfs fs;
272 int ret;
273
274 ret = statvfs(qPrintable(path), &fs);
275
276 if(ret == 0) {
277 if(type == FilesystemFree) {
278 size = (qulonglong)fs.f_frsize * (qulonglong)fs.f_bavail;
279 }
280 if(type == FilesystemTotal) {
281 size = (qulonglong)fs.f_frsize * (qulonglong)fs.f_blocks;
282 }
283 if(type == FilesystemClusterSize) {
284 size = (qulonglong)fs.f_frsize;
285 }
286 }
287#endif
288#if defined(Q_OS_WIN32)
289 BOOL ret;
290 ULARGE_INTEGER freeAvailBytes;
291 ULARGE_INTEGER totalNumberBytes;
292
293 ret = GetDiskFreeSpaceExW((LPCTSTR)path.utf16(), &freeAvailBytes,
294 &totalNumberBytes, NULL);
295 if(ret) {
296 if(type == FilesystemFree) {
297 size = freeAvailBytes.QuadPart;
298 }
299 if(type == FilesystemTotal) {
300 size = totalNumberBytes.QuadPart;
301 }
302 if(type == FilesystemClusterSize) {
303 DWORD sectorsPerCluster;
304 DWORD bytesPerSector;
305 DWORD freeClusters;
306 DWORD totalClusters;
307 ret = GetDiskFreeSpaceW((LPCTSTR)path.utf16(), &sectorsPerCluster,
308 &bytesPerSector, &freeClusters, &totalClusters);
309 if(ret) {
310 size = bytesPerSector * sectorsPerCluster;
311 }
312 }
313 }
314#endif
315 return size;
316}
317
318//! \brief searches for a Executable in the Environement Path
319QString Utils::findExecutable(QString name)
320{
321 //try autodetect tts
322#if defined(Q_OS_LINUX) || defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
323#if QT_VERSION >= 0x050e00
324 QStringList path = QString(getenv("PATH")).split(":", Qt::SkipEmptyParts);
325#else
326 QStringList path = QString(getenv("PATH")).split(":", QString::SkipEmptyParts);
327#endif
328#elif defined(Q_OS_WIN)
329#if QT_VERSION >= 0x050e00
330 QStringList path = QString(getenv("PATH")).split(";", Qt::SkipEmptyParts);
331#else
332 QStringList path = QString(getenv("PATH")).split(";", QString::SkipEmptyParts);
333#endif
334#endif
335 LOG_INFO() << "system path:" << path;
336 for(int i = 0; i < path.size(); i++)
337 {
338 QString executable = QDir::fromNativeSeparators(path.at(i)) + "/" + name;
339#if defined(Q_OS_WIN)
340 executable += ".exe";
341#if QT_VERSION >= 0x050e00
342 QStringList ex = executable.split("\"", Qt::SkipEmptyParts);
343#else
344 QStringList ex = executable.split("\"", QString::SkipEmptyParts);
345#endif
346 executable = ex.join("");
347#endif
348 if(QFileInfo(executable).isExecutable())
349 {
350 LOG_INFO() << "findExecutable: found" << executable;
351 return QDir::toNativeSeparators(executable);
352 }
353 }
354 LOG_INFO() << "findExecutable: could not find" << name;
355 return "";
356}
357
358
359/** @brief checks different Enviroment things. Ask if user wants to continue.
360 * @param permission if it should check for permission
361 * @return string with error messages if problems occurred, empty strings if none.
362 */
363QString Utils::checkEnvironment(bool permission)
364{
365 LOG_INFO() << "checking environment";
366 QString text = "";
367
368 // check permission
369 if(permission)
370 {
371#if defined(Q_OS_WIN32)
372 if(System::userPermissions() != System::ADMIN)
373 {
374 text += tr("<li>Permissions insufficient for bootloader "
375 "installation.\nAdministrator priviledges are necessary.</li>");
376 }
377#endif
378 }
379
380 // Check TargetId
381 RockboxInfo rbinfo(RbSettings::value(RbSettings::Mountpoint).toString());
382 QString installed = rbinfo.target();
383 if(!installed.isEmpty() && installed !=
384 RbSettings::value(RbSettings::CurrentPlatform).toString().split(".").at(0))
385 {
386 text += tr("<li>Target mismatch detected.<br/>"
387 "Installed target: %1<br/>Selected target: %2.</li>")
388 .arg(PlayerBuildInfo::instance()->value(
389 PlayerBuildInfo::DisplayName, installed).toString(),
390 PlayerBuildInfo::instance()->value(
391 PlayerBuildInfo::DisplayName).toString());
392 }
393
394 if(!text.isEmpty())
395 return tr("Problem detected:") + "<ul>" + text + "</ul>";
396 else
397 return text;
398}
399
400/** @brief Trim version string from filename to version part only.
401 * @param s Version string
402 * @return Version part of string if found, input string on error.
403 */
404QString Utils::trimVersionString(QString s)
405{
406 QRegExp r = QRegExp(".*([\\d\\.]+\\d+[a-z]?).*");
407 if(r.indexIn(s) != -1) {
408 return r.cap(1);
409 }
410 return s;
411}
412
413/** @brief Compare two version strings.
414 * @param s1 first version string
415 * @param s2 second version string
416 * @return 0 if strings identical, 1 if second is newer, -1 if first.
417 */
418int Utils::compareVersionStrings(QString s1, QString s2)
419{
420 LOG_INFO() << "comparing version strings" << s1 << "and" << s2;
421 QString a = s1.trimmed();
422 QString b = s2.trimmed();
423 // if strings are identical return 0.
424 if(a.isEmpty())
425 return 1;
426 if(b.isEmpty())
427 return -1;
428
429 while(!a.isEmpty() || !b.isEmpty()) {
430 // trim all leading non-digits and non-dots (dots are removed afterwards)
431 a.remove(QRegExp("^[^\\d\\.]*"));
432 b.remove(QRegExp("^[^\\d\\.]*"));
433
434 // trim all trailing non-digits for conversion (QString::toInt()
435 // requires this). Copy strings first as replace() changes the string.
436 QString numa = a;
437 QString numb = b;
438 numa.remove(QRegExp("\\D+.*$"));
439 numb.remove(QRegExp("\\D+.*$"));
440
441 // convert to number
442 bool ok1, ok2;
443 unsigned int vala = numa.toUInt(&ok1);
444 unsigned int valb = numb.toUInt(&ok2);
445 // if none of the numbers converted successfully we're at trailing garbage.
446 if(!ok1 && !ok2)
447 break;
448 if(!ok1)
449 return 1;
450 if(!ok2)
451 return -1;
452
453 // if numbers mismatch we have a decision.
454 if(vala != valb)
455 return (vala > valb) ? -1 : 1;
456
457 // trim leading digits.
458 a.remove(QRegExp("^\\d*"));
459 b.remove(QRegExp("^\\d*"));
460
461 // If only one of the following characters is a dot that one is
462 // "greater" then anything else. Make sure it's followed by a number,
463 // Otherwise it might be the end of the string or suffix. Do this
464 // before version addon characters check to avoid stopping too early.
465 bool adot = a.contains(QRegExp("^[a-zA-Z]*\\.[0-9]"));
466 bool bdot = b.contains(QRegExp("^[a-zA-Z]*\\.[0-9]"));
467 if(adot && !bdot)
468 return -1;
469 if(!adot && bdot)
470 return 1;
471 // if number is immediately followed by a character consider it as
472 // version addon (like 1.2.3b). In this case compare characters and end
473 // (version numbers like 1.2b.3 aren't handled).
474 QChar ltra;
475 QChar ltrb;
476 if(a.contains(QRegExp("^[a-zA-Z]")))
477 ltra = a.at(0);
478 if(b.contains(QRegExp("^[a-zA-Z]")))
479 ltrb = b.at(0);
480 if(ltra != ltrb)
481 return (ltra < ltrb) ? 1 : -1;
482
483 // both are identical or no addon characters, ignore.
484 // remove modifiers and following dot.
485 a.remove(QRegExp("^[a-zA-Z]*\\."));
486 b.remove(QRegExp("^[a-zA-Z]*\\."));
487 }
488
489 // no differences found.
490 return 0;
491}
492
493
494/** Resolve mountpoint to devicename / disk number
495 * @param path mountpoint path / drive letter
496 * @return devicename / disk number
497 */
498QString Utils::resolveDevicename(QString path)
499{
500 LOG_INFO() << "resolving device name" << path;
501#if defined(Q_OS_LINUX)
502 FILE *mn = setmntent("/etc/mtab", "r");
503 if(!mn)
504 return QString("");
505
506 struct mntent *ent;
507 while((ent = getmntent(mn))) {
508 // check for valid filesystem type.
509 // Linux can handle hfs (and hfsplus), so consider it a valid file
510 // system. Otherwise resolving the device name would fail, which in
511 // turn would make it impossible to warn about a MacPod.
512 if(QString(ent->mnt_dir) == path
513 && (QString(ent->mnt_type).contains("vfat", Qt::CaseInsensitive)
514 || QString(ent->mnt_type).contains("hfs", Qt::CaseInsensitive))) {
515 endmntent(mn);
516 LOG_INFO() << "device name is" << ent->mnt_fsname;
517 return QString(ent->mnt_fsname);
518 }
519 }
520 endmntent(mn);
521
522#endif
523
524#if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
525 int num;
526 struct statfs *mntinf;
527
528 num = getmntinfo(&mntinf, MNT_WAIT);
529 while(num--) {
530 // check for valid filesystem type. OS X can handle hfs (hfs+ is
531 // treated as hfs), BSD should be the same.
532 if(QString(mntinf->f_mntonname) == path
533 && (QString(mntinf->f_fstypename).contains("msdos", Qt::CaseInsensitive)
534 || QString(mntinf->f_fstypename).contains("hfs", Qt::CaseInsensitive))) {
535 LOG_INFO() << "device name is" << mntinf->f_mntfromname;
536 return QString(mntinf->f_mntfromname);
537 }
538 mntinf++;
539 }
540#endif
541
542#if defined(Q_OS_WIN32)
543 DWORD written;
544 HANDLE h;
545 TCHAR uncpath[MAX_PATH];
546 UCHAR buffer[0x400];
547 PVOLUME_DISK_EXTENTS extents = (PVOLUME_DISK_EXTENTS)buffer;
548
549 _stprintf(uncpath, _TEXT("\\\\.\\%c:"), path.toLatin1().at(0));
550 h = CreateFile(uncpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
551 NULL, OPEN_EXISTING, 0, NULL);
552 if(h == INVALID_HANDLE_VALUE) {
553 //LOG_INFO() << "error getting extents for" << uncpath;
554 return "";
555 }
556 // get the extents
557 if(DeviceIoControl(h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
558 NULL, 0, extents, sizeof(buffer), &written, NULL)) {
559 if(extents->NumberOfDiskExtents == 1) {
560 CloseHandle(h);
561 LOG_INFO() << "device name is" << extents->Extents[0].DiskNumber;
562 return QString("%1").arg(extents->Extents[0].DiskNumber);
563 }
564 LOG_INFO() << "resolving device name: volume spans multiple disks!";
565 }
566 CloseHandle(h);
567#endif
568 return QString("");
569
570}
571
572
573/** resolve device name to mount point / drive letter
574 * @param device device name / disk number
575 * @return mount point / drive letter
576 */
577QString Utils::resolveMountPoint(QString device)
578{
579 LOG_INFO() << "resolving mountpoint:" << device;
580
581#if defined(Q_OS_LINUX)
582 FILE *mn = setmntent("/etc/mtab", "r");
583 if(!mn)
584 return QString("");
585
586 struct mntent *ent;
587 while((ent = getmntent(mn))) {
588 // Check for valid filesystem. Allow hfs too, as an Ipod might be a
589 // MacPod.
590 if(QString(ent->mnt_fsname) == device) {
591 QString result;
592 if(QString(ent->mnt_type).contains("vfat", Qt::CaseInsensitive)
593 || QString(ent->mnt_type).contains("hfs", Qt::CaseInsensitive)) {
594 LOG_INFO() << "resolved mountpoint is:" << ent->mnt_dir;
595 result = QString(ent->mnt_dir);
596 }
597 else {
598 LOG_INFO() << "mountpoint is wrong filesystem!";
599 }
600 endmntent(mn);
601 return result;
602 }
603 }
604 endmntent(mn);
605
606#endif
607
608#if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
609 int num;
610 struct statfs *mntinf;
611
612 num = getmntinfo(&mntinf, MNT_WAIT);
613 while(num--) {
614 // Check for valid filesystem. Allow hfs too, as an Ipod might be a
615 // MacPod.
616 if(QString(mntinf->f_mntfromname) == device) {
617 if(QString(mntinf->f_fstypename).contains("msdos", Qt::CaseInsensitive)
618 || QString(mntinf->f_fstypename).contains("hfs", Qt::CaseInsensitive)) {
619 LOG_INFO() << "resolved mountpoint is:" << mntinf->f_mntonname;
620 return QString(mntinf->f_mntonname);
621 }
622 else {
623 LOG_INFO() << "mountpoint is wrong filesystem!";
624 return QString();
625 }
626 }
627 mntinf++;
628 }
629#endif
630
631#if defined(Q_OS_WIN32)
632 QString result;
633 unsigned int driveno = device.replace(QRegExp("^.*([0-9]+)"), "\\1").toInt();
634
635 int letter;
636 for(letter = 'A'; letter <= 'Z'; letter++) {
637 if(resolveDevicename(QString(letter)).toUInt() == driveno) {
638 result = letter;
639 LOG_INFO() << "resolved mountpoint is:" << result;
640 break;
641 }
642 }
643 if(!result.isEmpty())
644 return result + ":/";
645#endif
646 LOG_INFO() << "resolving mountpoint failed!";
647 return QString("");
648}
649
650
651QStringList Utils::mountpoints(enum MountpointsFilter type)
652{
653 QStringList supported;
654 QStringList tempList;
655#if defined(Q_OS_WIN32)
656 supported << "FAT32" << "FAT16" << "FAT12" << "FAT" << "HFS";
657 QFileInfoList list = QDir::drives();
658 for(int i=0; i<list.size();i++)
659 {
660 wchar_t t[32];
661 memset(t, 0, 32);
662 if(GetVolumeInformationW((LPCWSTR)list.at(i).absolutePath().utf16(),
663 NULL, 0, NULL, NULL, NULL, t, 32) == 0) {
664 // on error empty retrieved type -- don't rely on
665 // GetVolumeInformation not changing it.
666 memset(t, 0, sizeof(t));
667 }
668
669 QString fstype = QString::fromWCharArray(t);
670 if(type == MountpointsAll || supported.contains(fstype)) {
671 tempList << list.at(i).absolutePath();
672 LOG_INFO() << "Added:" << list.at(i).absolutePath()
673 << "type" << fstype;
674 }
675 else {
676 LOG_INFO() << "Ignored:" << list.at(i).absolutePath()
677 << "type" << fstype;
678 }
679 }
680
681#elif defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
682 supported << "vfat" << "msdos" << "hfs";
683 int num;
684 struct statfs *mntinf;
685
686 num = getmntinfo(&mntinf, MNT_WAIT);
687 while(num--) {
688 if(type == MountpointsAll || supported.contains(mntinf->f_fstypename)) {
689 tempList << QString(mntinf->f_mntonname);
690 LOG_INFO() << "Added:" << mntinf->f_mntonname
691 << "is" << mntinf->f_mntfromname << "type" << mntinf->f_fstypename;
692 }
693 else {
694 LOG_INFO() << "Ignored:" << mntinf->f_mntonname
695 << "is" << mntinf->f_mntfromname << "type" << mntinf->f_fstypename;
696 }
697 mntinf++;
698 }
699#elif defined(Q_OS_LINUX)
700 supported << "vfat" << "msdos" << "hfsplus";
701 FILE *mn = setmntent("/etc/mtab", "r");
702 if(!mn)
703 return QStringList("");
704
705 struct mntent *ent;
706 while((ent = getmntent(mn))) {
707 if(type == MountpointsAll || supported.contains(ent->mnt_type)) {
708 tempList << QString(ent->mnt_dir);
709 LOG_INFO() << "Added:" << ent->mnt_dir
710 << "is" << ent->mnt_fsname << "type" << ent->mnt_type;
711 }
712 else {
713 LOG_INFO() << "Ignored:" << ent->mnt_dir
714 << "is" << ent->mnt_fsname << "type" << ent->mnt_type;
715 }
716 }
717 endmntent(mn);
718
719#else
720#error Unknown Platform
721#endif
722 return tempList;
723}
724
725
726/** Check if a process with a given name is running
727 * @param names list of names to filter on. All processes if empty list.
728 * @return list of processname, process ID pairs.
729 */
730QMap<QString, QList<int> > Utils::findRunningProcess(QStringList names)
731{
732 QMap<QString, QList<int> > processlist;
733 QMap<QString, QList<int> > found;
734#if defined(Q_OS_WIN32)
735 HANDLE hdl;
736 PROCESSENTRY32 entry;
737 bool result;
738
739 hdl = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
740 if(hdl == INVALID_HANDLE_VALUE) {
741 LOG_ERROR() << "CreateToolhelp32Snapshot failed.";
742 return found;
743 }
744 entry.dwSize = sizeof(PROCESSENTRY32);
745 entry.szExeFile[0] = '\0';
746 if(!Process32First(hdl, &entry)) {
747 LOG_ERROR() << "Process32First failed.";
748 return found;
749 }
750
751 do {
752 int pid = entry.th32ProcessID; // FIXME: DWORD vs int!
753 QString name = QString::fromWCharArray(entry.szExeFile);
754 if(processlist.find(name) == processlist.end()) {
755 processlist.insert(name, QList<int>());
756 }
757 processlist[name].append(pid);
758 entry.dwSize = sizeof(PROCESSENTRY32);
759 entry.szExeFile[0] = '\0';
760 result = Process32Next(hdl, &entry);
761 } while(result);
762 CloseHandle(hdl);
763#endif
764#if defined(Q_OS_MACX)
765 ProcessSerialNumber psn = { 0, kNoProcess };
766 OSErr err;
767 do {
768 pid_t pid;
769 err = GetNextProcess(&psn);
770 err = GetProcessPID(&psn, &pid);
771 if(err == noErr) {
772 char buf[32] = {0};
773 ProcessInfoRec info;
774 memset(&info, 0, sizeof(ProcessInfoRec));
775 info.processName = (unsigned char*)buf;
776 info.processInfoLength = sizeof(ProcessInfoRec);
777 err = GetProcessInformation(&psn, &info);
778 if(err == noErr) {
779 // some processes start with nonprintable characters. Skip those.
780 int i;
781 for(i = 0; i < 32; i++) {
782 if(isprint(buf[i])) break;
783 }
784 // avoid adding duplicates.
785 QString name = QString::fromUtf8(&buf[i]);
786 if(processlist.find(name) == processlist.end()) {
787 processlist.insert(name, QList<int>());
788 }
789 processlist[name].append(pid);
790 }
791 }
792 } while(err == noErr);
793#endif
794#if defined(Q_OS_LINUX)
795 // not implemented for Linux!
796#endif
797 // Filter for names (unless empty)
798 if(names.size() > 0) {
799 for(int i = 0; i < names.size(); ++i) {
800 QStringList k(processlist.keys());
801#if defined(Q_OS_WIN32)
802 // the process name might be truncated. Allow the extension to be partial.
803 int index = k.indexOf(QRegExp(names.at(i) + "(\\.(e(x(e?)?)?)?)?",
804 Qt::CaseInsensitive));
805#else
806 int index = k.indexOf(names.at(i));
807#endif
808 if(index != -1) {
809 found.insert(k[index], processlist[k[index]]);
810 }
811 }
812 }
813 else {
814 found = processlist;
815 }
816 LOG_INFO() << "Looking for processes" << names << "found" << found;
817 return found;
818}
819
820
821/** Suspends/resumes processes
822 * @param pidlist a list of PIDs to suspend/resume
823 * @param suspend processes are suspended if true, or resumed when false
824 * @return a list of PIDs successfully suspended/resumed
825 */
826QList<int> Utils::suspendProcess(QList<int> pidlist, bool suspend)
827{
828 QList<int> result;
829#if defined(Q_OS_WIN32)
830 // Enable debug privilege
831 HANDLE hToken = NULL;
832 LUID seDebugValue;
833 TOKEN_PRIVILEGES tNext, tPrev;
834 DWORD sPrev;
835 if(LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &seDebugValue)) {
836 if(OpenProcessToken(GetCurrentProcess(),
837 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
838 memset(&tNext, 0, sizeof(tNext));
839 tNext.PrivilegeCount = 1;
840 tNext.Privileges[0].Luid = seDebugValue;
841 tNext.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
842 if(!AdjustTokenPrivileges(hToken, FALSE, &tNext, sizeof(tNext),
843 &tPrev, &sPrev) || GetLastError() != 0) {
844 CloseHandle(hToken);
845 hToken = NULL;
846 LOG_ERROR() << "AdjustTokenPrivileges(next) error" << GetLastError();
847 }
848 }
849 else {
850 LOG_ERROR() << "OpenProcessToken error" << GetLastError();
851 }
852 }
853 else {
854 LOG_ERROR() << "LookupPrivilegeValue error" << GetLastError();
855 }
856
857 // Suspend/resume threads
858 for(int i = 0; i < pidlist.size(); i++) {
859 HANDLE hdl = INVALID_HANDLE_VALUE;
860 THREADENTRY32 entry;
861 int n_fails = 0;
862
863 hdl = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
864 if(hdl == INVALID_HANDLE_VALUE) {
865 LOG_ERROR() << "CreateToolhelp32Snapshot error" << GetLastError();
866 continue;
867 }
868 entry.dwSize = sizeof(THREADENTRY32);
869 if(!Thread32First(hdl, &entry)) {
870 LOG_ERROR() << "Process32First error" << GetLastError();
871 CloseHandle(hdl);
872 continue;
873 }
874
875 do {
876 if(entry.th32OwnerProcessID != (DWORD)(pidlist[i]))
877 continue;
878 HANDLE thr = OpenThread(THREAD_SUSPEND_RESUME,
879 FALSE, entry.th32ThreadID);
880 if(!thr) {
881 LOG_ERROR() << "OpenThread" << entry.th32ThreadID
882 << "error" << GetLastError();
883 n_fails++;
884 continue;
885 }
886 if(suspend) {
887 // Execution of the specified thread is suspended and
888 // the thread's suspend count is incremented.
889 if(SuspendThread(thr) == (DWORD)(-1)) {
890 LOG_ERROR() << "SuspendThread" << entry.th32ThreadID
891 << "error" << GetLastError();
892 n_fails++;
893 }
894 }
895 else {
896 // Decrements a thread's suspend count. When the
897 // suspend count is decremented to zero, the
898 // execution of the thread is resumed.
899 if(ResumeThread(thr) == (DWORD)(-1)) {
900 LOG_ERROR() << "ResumeThread" << entry.th32ThreadID
901 << "error" << GetLastError();
902 n_fails++;
903 }
904 }
905 CloseHandle(thr);
906 } while(Thread32Next(hdl, &entry));
907 if (!n_fails)
908 result.append(pidlist[i]);
909 CloseHandle(hdl);
910 }
911
912 // Restore previous debug privilege
913 if (hToken) {
914 if(!AdjustTokenPrivileges(hToken, FALSE,
915 &tPrev, sPrev, NULL, NULL) || GetLastError() != 0) {
916 LOG_ERROR() << "AdjustTokenPrivileges(prev) error" << GetLastError();
917 }
918 CloseHandle(hToken);
919 }
920#endif
921#if defined(Q_OS_MACX)
922 int signal = suspend ? SIGSTOP : SIGCONT;
923 for(int i = 0; i < pidlist.size(); i++) {
924 pid_t pid = pidlist[i];
925 if(kill(pid, signal) != 0) {
926 LOG_ERROR() << "kill signal" << signal
927 << "for PID" << pid << "error:" << errno;
928 }
929 else {
930 result.append(pidlist[i]);
931 }
932 }
933#endif
934#if defined(Q_OS_LINUX)
935 // not implemented for Linux!
936#endif
937 LOG_INFO() << (suspend ? "Suspending" : "Resuming")
938 << "PIDs" << pidlist << "result" << result;
939 return result;
940}
941
942
943/** Eject device from PC.
944 * Request the OS to eject the player.
945 * @param device mountpoint of the device
946 * @return true on success, fals otherwise.
947 */
948bool Utils::ejectDevice(QString device)
949{
950#if defined(Q_OS_WIN32)
951 /* See http://support.microsoft.com/kb/165721 on the procedure to eject a
952 * device. */
953 bool success = false;
954 int i;
955 HANDLE hdl;
956 DWORD bytesReturned;
957 TCHAR volume[8];
958
959 /* CreateFile */
960 _stprintf(volume, _TEXT("\\\\.\\%c:"), device.toLatin1().at(0));
961 hdl = CreateFile(volume, GENERIC_READ | GENERIC_WRITE,
962 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
963 OPEN_EXISTING, 0, NULL);
964 if(hdl == INVALID_HANDLE_VALUE)
965 return false;
966
967 /* lock volume to make sure no other application is accessing the volume.
968 * Try up to 10 times. */
969 for(i = 0; i < 10; i++) {
970 if(DeviceIoControl(hdl, FSCTL_LOCK_VOLUME,
971 NULL, 0, NULL, 0, &bytesReturned, NULL))
972 break;
973 /* short break before retry */
974 Sleep(100);
975 }
976 if(i < 10) {
977 /* successfully locked, now dismount */
978 if(DeviceIoControl(hdl, FSCTL_DISMOUNT_VOLUME,
979 NULL, 0, NULL, 0, &bytesReturned, NULL)) {
980 /* make sure media can be removed. */
981 PREVENT_MEDIA_REMOVAL pmr;
982 pmr.PreventMediaRemoval = false;
983 if(DeviceIoControl(hdl, IOCTL_STORAGE_MEDIA_REMOVAL,
984 &pmr, sizeof(PREVENT_MEDIA_REMOVAL),
985 NULL, 0, &bytesReturned, NULL)) {
986 /* eject the media */
987 if(DeviceIoControl(hdl, IOCTL_STORAGE_EJECT_MEDIA,
988 NULL, 0, NULL, 0, &bytesReturned, NULL))
989 success = true;
990 }
991 }
992 }
993 /* close handle */
994 CloseHandle(hdl);
995 return success;
996
997#endif
998#if defined(Q_OS_MACX)
999 // FIXME: FSUnmountVolumeSync is deprecated starting with 10.8.
1000 // Use DADiskUnmount / DiskArbitration framework eventually.
1001 // BSD label does not include folder.
1002 QString bsd = Utils::resolveDevicename(device).remove("/dev/");
1003 OSStatus result;
1004 ItemCount index = 1;
1005 bool found = false;
1006
1007 do {
1008 FSVolumeRefNum volrefnum;
1009
1010 result = FSGetVolumeInfo(kFSInvalidVolumeRefNum, index, &volrefnum,
1011 kFSVolInfoFSInfo, NULL, NULL, NULL);
1012 if(result == noErr) {
1013 GetVolParmsInfoBuffer volparms;
1014 /* See above -- PBHGetVolParmsSync() is not available for 64bit,
1015 * and FSGetVolumeParms() on 10.5+ only. */
1016#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
1017 if(FSGetVolumeParms(volrefnum, &volparms, sizeof(volparms)) == noErr)
1018#else
1019 HParamBlockRec hpb;
1020 hpb.ioParam.ioNamePtr = NULL;
1021 hpb.ioParam.ioVRefNum = volrefnum;
1022 hpb.ioParam.ioBuffer = (Ptr)&volparms;
1023 hpb.ioParam.ioReqCount = sizeof(volparms);
1024 if(PBHGetVolParmsSync(&hpb) == noErr)
1025#endif
1026 {
1027 if(volparms.vMServerAdr == 0) {
1028 if(bsd == (char*)volparms.vMDeviceID) {
1029 pid_t dissenter;
1030 result = FSUnmountVolumeSync(volrefnum, 0, &dissenter);
1031 found = true;
1032 break;
1033 }
1034 }
1035 }
1036 }
1037 index++;
1038 } while(result == noErr);
1039 if(result == noErr && found)
1040 return true;
1041
1042#endif
1043#if defined(Q_OS_LINUX)
1044 (void)device;
1045#endif
1046 return false;
1047}
1048
1049
1050qint64 Utils::recursiveFolderSize(QString path)
1051{
1052 qint64 size = 0;
1053 QList<QFileInfo> items = QDir(path).entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
1054 for (auto item: items) {
1055 size += item.size();
1056 }
1057 QList<QString> folders = QDir(path).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
1058 for (auto folder: folders) {
1059 size += recursiveFolderSize(path + "/" + folder);
1060 }
1061 return size;
1062}