1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
9 * Copyright (C) 2007 by Dominik Wenger
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.
14 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
15 * KIND, either express or implied.
17 ****************************************************************************/
20 #include "rockboxinfo.h"
22 #include "rbsettings.h"
23 #include "systeminfo.h"
34 #if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
35 #include <sys/statvfs.h>
37 #if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
40 #if defined(Q_OS_LINUX)
43 #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
44 #include <sys/param.h>
45 #include <sys/ucred.h>
46 #include <sys/mount.h>
48 #if defined(Q_OS_WIN32)
56 #if defined(Q_OS_MACX)
57 #include <Carbon/Carbon.h>
58 #include <CoreFoundation/CoreFoundation.h>
59 #include <CoreServices/CoreServices.h>
60 #include <IOKit/IOKitLib.h>
63 // recursive function to delete a dir with files
64 bool Utils::recursiveRmdir( const QString
&dirName
)
66 QString dirN
= dirName
;
68 // make list of entries in directory
69 QStringList list
= dir
.entryList(QDir::AllEntries
| QDir::NoDotAndDotDot
);
71 QString curItem
, lstAt
;
72 for(int i
= 0; i
< list
.size(); i
++){ // loop through all items of list
73 QString name
= list
.at(i
);
74 curItem
= dirN
+ "/" + name
;
75 fileInfo
.setFile(curItem
);
76 if(fileInfo
.isDir()) // is directory
77 recursiveRmdir(curItem
); // call recRmdir() recursively for
78 // deleting subdirectory
80 QFile::remove(curItem
); // ok, delete file
83 return dir
.rmdir(dirN
); // delete empty dir and return if (now empty)
84 // dir-removing was successfull
88 //! @brief resolves the given path, ignoring case.
89 //! @param path absolute path to resolve.
90 //! @return returns exact casing of path, empty string if path not found.
91 QString
Utils::resolvePathCase(QString path
)
95 QStringList elems
= path
.split("/", QString::SkipEmptyParts
);
99 #if defined(Q_OS_WIN32)
100 // on windows we must make sure to start with the first entry (i.e. the
101 // drive letter) instead of a single / to make resolving work.
103 realpath
= elems
.at(0) + "/";
109 for(int i
= start
; i
< elems
.size(); i
++) {
111 = QDir(realpath
).entryList(QDir::AllEntries
|QDir::Hidden
|QDir::System
);
112 if(direlems
.contains(elems
.at(i
), Qt::CaseInsensitive
)) {
113 // need to filter using QRegExp as QStringList::filter(QString)
114 // matches any substring
115 QString expr
= QString("^" + elems
.at(i
) + "$");
116 QRegExp rx
= QRegExp(expr
, Qt::CaseInsensitive
);
117 QStringList a
= direlems
.filter(rx
);
121 if(!realpath
.endsWith("/"))
128 qDebug() << "[Utils] resolving path" << path
<< "->" << realpath
;
133 QString
Utils::filesystemName(QString path
)
136 #if defined(Q_OS_WIN32)
137 wchar_t volname
[MAX_PATH
+1];
138 bool res
= GetVolumeInformationW((LPTSTR
)path
.utf16(), volname
, MAX_PATH
+1,
139 NULL
, NULL
, NULL
, NULL
, 0);
141 name
= QString::fromWCharArray(volname
);
144 #if defined(Q_OS_MACX)
145 // BSD label does not include folder.
146 QString bsd
= Utils::resolveDevicename(path
).remove("/dev/");
154 FSVolumeRefNum volrefnum
;
155 HFSUniStr255 volname
;
157 result
= FSGetVolumeInfo(kFSInvalidVolumeRefNum
, index
, &volrefnum
,
158 kFSVolInfoFSInfo
, NULL
, &volname
, NULL
);
160 if(result
== noErr
) {
161 GetVolParmsInfoBuffer volparms
;
163 hpb
.ioParam
.ioNamePtr
= NULL
;
164 hpb
.ioParam
.ioVRefNum
= volrefnum
;
165 hpb
.ioParam
.ioBuffer
= (Ptr
)&volparms
;
166 hpb
.ioParam
.ioReqCount
= sizeof(volparms
);
168 if(PBHGetVolParmsSync(&hpb
) == noErr
) {
169 if(volparms
.vMServerAdr
== 0) {
170 if(bsd
== (char*)volparms
.vMDeviceID
) {
171 name
= QString::fromUtf16((const ushort
*)volname
.unicode
,
172 (int)volname
.length
);
179 } while(result
== noErr
);
182 qDebug() << "[Utils] Volume name of" << path
<< "is" << name
;
187 //! @brief figure the free disk space on a filesystem
188 //! @param path path on the filesystem to check
189 //! @return size in bytes
190 qulonglong
Utils::filesystemFree(QString path
)
192 qulonglong size
= filesystemSize(path
, FilesystemFree
);
193 qDebug() << "[Utils] free disk space for" << path
<< size
;
198 qulonglong
Utils::filesystemTotal(QString path
)
200 qulonglong size
= filesystemSize(path
, FilesystemTotal
);
201 qDebug() << "[Utils] total disk space for" << path
<< size
;
206 qulonglong
Utils::filesystemClusterSize(QString path
)
208 qulonglong size
= filesystemSize(path
, FilesystemClusterSize
);
209 qDebug() << "[Utils] cluster size for" << path
<< size
;
214 qulonglong
Utils::filesystemSize(QString path
, enum Utils::Size type
)
217 #if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
218 // the usage of statfs() is deprecated by the LSB so use statvfs().
222 ret
= statvfs(qPrintable(path
), &fs
);
225 if(type
== FilesystemFree
) {
226 size
= (qulonglong
)fs
.f_frsize
* (qulonglong
)fs
.f_bavail
;
228 if(type
== FilesystemTotal
) {
229 size
= (qulonglong
)fs
.f_frsize
* (qulonglong
)fs
.f_blocks
;
231 if(type
== FilesystemClusterSize
) {
232 size
= (qulonglong
)fs
.f_frsize
;
236 #if defined(Q_OS_WIN32)
238 ULARGE_INTEGER freeAvailBytes
;
239 ULARGE_INTEGER totalNumberBytes
;
241 ret
= GetDiskFreeSpaceExW((LPCTSTR
)path
.utf16(), &freeAvailBytes
,
242 &totalNumberBytes
, NULL
);
244 if(type
== FilesystemFree
) {
245 size
= freeAvailBytes
.QuadPart
;
247 if(type
== FilesystemTotal
) {
248 size
= totalNumberBytes
.QuadPart
;
250 if(type
== FilesystemClusterSize
) {
251 DWORD sectorsPerCluster
;
252 DWORD bytesPerSector
;
255 ret
= GetDiskFreeSpaceW((LPCTSTR
)path
.utf16(), §orsPerCluster
,
256 &bytesPerSector
, &freeClusters
, &totalClusters
);
258 size
= bytesPerSector
* sectorsPerCluster
;
266 //! \brief searches for a Executable in the Environement Path
267 QString
Utils::findExecutable(QString name
)
271 #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
272 QStringList path
= QString(getenv("PATH")).split(":", QString::SkipEmptyParts
);
273 #elif defined(Q_OS_WIN)
274 QStringList path
= QString(getenv("PATH")).split(";", QString::SkipEmptyParts
);
276 qDebug() << "[Utils] system path:" << path
;
277 for(int i
= 0; i
< path
.size(); i
++)
279 QString executable
= QDir::fromNativeSeparators(path
.at(i
)) + "/" + name
;
280 #if defined(Q_OS_WIN)
281 executable
+= ".exe";
282 QStringList ex
= executable
.split("\"", QString::SkipEmptyParts
);
283 executable
= ex
.join("");
285 if(QFileInfo(executable
).isExecutable())
287 qDebug() << "[Utils] findExecutable: found" << executable
;
288 return QDir::toNativeSeparators(executable
);
291 qDebug() << "[Utils] findExecutable: could not find" << name
;
296 /** @brief checks different Enviroment things. Ask if user wants to continue.
297 * @param permission if it should check for permission
298 * @return string with error messages if problems occurred, empty strings if none.
300 QString
Utils::checkEnvironment(bool permission
)
302 qDebug() << "[Utils] checking environment";
308 #if defined(Q_OS_WIN32)
309 if(System::userPermissions() != System::ADMIN
)
311 text
+= tr("<li>Permissions insufficient for bootloader "
312 "installation.\nAdministrator priviledges are necessary.</li>");
318 RockboxInfo
rbinfo(RbSettings::value(RbSettings::Mountpoint
).toString());
319 QString installed
= rbinfo
.target();
320 if(!installed
.isEmpty() && installed
!=
321 SystemInfo::value(SystemInfo::CurConfigureModel
).toString())
323 text
+= tr("<li>Target mismatch detected.<br/>"
324 "Installed target: %1<br/>Selected target: %2.</li>")
325 .arg(SystemInfo::platformValue(installed
, SystemInfo::CurPlatformName
).toString(),
326 SystemInfo::value(SystemInfo::CurPlatformName
).toString());
330 return tr("Problem detected:") + "<ul>" + text
+ "</ul>";
334 /** @brief Compare two version strings.
335 * @param s1 first version string
336 * @param s2 second version string
337 * @return 0 if strings identical, 1 if second is newer, -1 if first.
339 int Utils::compareVersionStrings(QString s1
, QString s2
)
341 qDebug() << "[Utils] comparing version strings" << s1
<< "and" << s2
;
342 QString a
= s1
.trimmed();
343 QString b
= s2
.trimmed();
344 // if strings are identical return 0.
350 while(!a
.isEmpty() || !b
.isEmpty()) {
351 // trim all leading non-digits and non-dots (dots are removed afterwards)
352 a
.remove(QRegExp("^[^\\d\\.]*"));
353 b
.remove(QRegExp("^[^\\d\\.]*"));
355 // trim all trailing non-digits for conversion (QString::toInt()
356 // requires this). Copy strings first as replace() changes the string.
359 numa
.remove(QRegExp("\\D+.*$"));
360 numb
.remove(QRegExp("\\D+.*$"));
364 int vala
= numa
.toUInt(&ok1
);
365 int valb
= numb
.toUInt(&ok2
);
366 // if none of the numbers converted successfully we're at trailing garbage.
374 // if numbers mismatch we have a decision.
376 return (vala
> valb
) ? -1 : 1;
378 // trim leading digits.
379 a
.remove(QRegExp("^\\d*"));
380 b
.remove(QRegExp("^\\d*"));
382 // If only one of the following characters is a dot that one is
383 // "greater" then anything else. Make sure it's followed by a number,
384 // Otherwise it might be the end of the string or suffix. Do this
385 // before version addon characters check to avoid stopping too early.
386 bool adot
= a
.contains(QRegExp("^[a-zA-Z]*\\.[0-9]"));
387 bool bdot
= b
.contains(QRegExp("^[a-zA-Z]*\\.[0-9]"));
392 // if number is immediately followed by a character consider it as
393 // version addon (like 1.2.3b). In this case compare characters and end
394 // (version numbers like 1.2b.3 aren't handled).
397 if(a
.contains(QRegExp("^[a-zA-Z]")))
399 if(b
.contains(QRegExp("^[a-zA-Z]")))
402 return (ltra
< ltrb
) ? 1 : -1;
404 // both are identical or no addon characters, ignore.
405 // remove modifiers and following dot.
406 a
.remove(QRegExp("^[a-zA-Z]*\\."));
407 b
.remove(QRegExp("^[a-zA-Z]*\\."));
410 // no differences found.
415 /** Resolve mountpoint to devicename / disk number
416 * @param path mountpoint path / drive letter
417 * @return devicename / disk number
419 QString
Utils::resolveDevicename(QString path
)
421 qDebug() << "[Utils] resolving device name" << path
;
422 #if defined(Q_OS_LINUX)
423 FILE *mn
= setmntent("/etc/mtab", "r");
428 while((ent
= getmntent(mn
))) {
429 // check for valid filesystem type.
430 // Linux can handle hfs (and hfsplus), so consider it a valid file
431 // system. Otherwise resolving the device name would fail, which in
432 // turn would make it impossible to warn about a MacPod.
433 if(QString(ent
->mnt_dir
) == path
434 && (QString(ent
->mnt_type
).contains("vfat", Qt::CaseInsensitive
)
435 || QString(ent
->mnt_type
).contains("hfs", Qt::CaseInsensitive
))) {
437 qDebug() << "[Utils] device name is" << ent
->mnt_fsname
;
438 return QString(ent
->mnt_fsname
);
445 #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
447 struct statfs
*mntinf
;
449 num
= getmntinfo(&mntinf
, MNT_WAIT
);
451 // check for valid filesystem type. OS X can handle hfs (hfs+ is
452 // treated as hfs), BSD should be the same.
453 if(QString(mntinf
->f_mntonname
) == path
454 && (QString(mntinf
->f_fstypename
).contains("msdos", Qt::CaseInsensitive
)
455 || QString(mntinf
->f_fstypename
).contains("hfs", Qt::CaseInsensitive
))) {
456 qDebug() << "[Utils] device name is" << mntinf
->f_mntfromname
;
457 return QString(mntinf
->f_mntfromname
);
463 #if defined(Q_OS_WIN32)
466 TCHAR uncpath
[MAX_PATH
];
468 PVOLUME_DISK_EXTENTS extents
= (PVOLUME_DISK_EXTENTS
)buffer
;
470 _stprintf(uncpath
, _TEXT("\\\\.\\%c:"), path
.toAscii().at(0));
471 h
= CreateFile(uncpath
, GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_WRITE
,
472 NULL
, OPEN_EXISTING
, 0, NULL
);
473 if(h
== INVALID_HANDLE_VALUE
) {
474 //qDebug() << "error getting extents for" << uncpath;
478 if(DeviceIoControl(h
, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
,
479 NULL
, 0, extents
, sizeof(buffer
), &written
, NULL
)) {
480 if(extents
->NumberOfDiskExtents
> 1) {
481 qDebug() << "[Utils] resolving device name: volume spans multiple disks!";
484 qDebug() << "[Utils] device name is" << extents
->Extents
[0].DiskNumber
;
485 return QString("%1").arg(extents
->Extents
[0].DiskNumber
);
493 /** resolve device name to mount point / drive letter
494 * @param device device name / disk number
495 * @return mount point / drive letter
497 QString
Utils::resolveMountPoint(QString device
)
499 qDebug() << "[Utils] resolving mountpoint:" << device
;
501 #if defined(Q_OS_LINUX)
502 FILE *mn
= setmntent("/etc/mtab", "r");
507 while((ent
= getmntent(mn
))) {
508 // Check for valid filesystem. Allow hfs too, as an Ipod might be a
510 if(QString(ent
->mnt_fsname
) == device
) {
512 if(QString(ent
->mnt_type
).contains("vfat", Qt::CaseInsensitive
)
513 || QString(ent
->mnt_type
).contains("hfs", Qt::CaseInsensitive
)) {
514 qDebug() << "[Utils] resolved mountpoint is:" << ent
->mnt_dir
;
515 result
= QString(ent
->mnt_dir
);
518 qDebug() << "[Utils] mountpoint is wrong filesystem!";
528 #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
530 struct statfs
*mntinf
;
532 num
= getmntinfo(&mntinf
, MNT_WAIT
);
534 // Check for valid filesystem. Allow hfs too, as an Ipod might be a
536 if(QString(mntinf
->f_mntfromname
) == device
) {
537 if(QString(mntinf
->f_fstypename
).contains("msdos", Qt::CaseInsensitive
)
538 || QString(mntinf
->f_fstypename
).contains("hfs", Qt::CaseInsensitive
)) {
539 qDebug() << "[Utils] resolved mountpoint is:" << mntinf
->f_mntonname
;
540 return QString(mntinf
->f_mntonname
);
543 qDebug() << "[Utils] mountpoint is wrong filesystem!";
551 #if defined(Q_OS_WIN32)
553 unsigned int driveno
= device
.replace(QRegExp("^.*([0-9]+)"), "\\1").toInt();
556 for(letter
= 'A'; letter
<= 'Z'; letter
++) {
557 if(resolveDevicename(QString(letter
)).toUInt() == driveno
) {
559 qDebug() << "[Utils] resolved mountpoint is:" << result
;
563 if(!result
.isEmpty())
564 return result
+ ":/";
566 qDebug() << "[Utils] resolving mountpoint failed!";
571 QStringList
Utils::mountpoints()
573 QStringList tempList
;
574 #if defined(Q_OS_WIN32)
575 QFileInfoList list
= QDir::drives();
576 for(int i
=0; i
<list
.size();i
++)
578 tempList
<< list
.at(i
).absolutePath();
579 qDebug() << "[Utils] Mounted on" << list
.at(i
).absolutePath();
582 #elif defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
584 struct statfs
*mntinf
;
586 num
= getmntinfo(&mntinf
, MNT_WAIT
);
588 tempList
<< QString(mntinf
->f_mntonname
);
589 qDebug() << "[Utils] Mounted on" << mntinf
->f_mntonname
590 << "is" << mntinf
->f_mntfromname
<< "type" << mntinf
->f_fstypename
;
593 #elif defined(Q_OS_LINUX)
595 FILE *mn
= setmntent("/etc/mtab", "r");
597 return QStringList("");
600 while((ent
= getmntent(mn
))) {
601 tempList
<< QString(ent
->mnt_dir
);
602 qDebug() << "[Utils] Mounted on" << ent
->mnt_dir
603 << "is" << ent
->mnt_fsname
<< "type" << ent
->mnt_type
;
608 #error Unknown Platform
614 /** Check if a process with a given name is running
615 * @param names list of names to check
616 * @return list of detected processes.
618 QStringList
Utils::findRunningProcess(QStringList names
)
620 QStringList processlist
;
622 #if defined(Q_OS_WIN32)
624 PROCESSENTRY32 entry
;
627 hdl
= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS
, 0);
628 if(hdl
== INVALID_HANDLE_VALUE
) {
629 qDebug() << "[Utils] CreateToolhelp32Snapshot failed.";
632 entry
.dwSize
= sizeof(PROCESSENTRY32
);
633 entry
.szExeFile
[0] = '\0';
634 if(!Process32First(hdl
, &entry
)) {
635 qDebug() << "[Utils] Process32First failed.";
639 processlist
.append(QString::fromWCharArray(entry
.szExeFile
));
641 entry
.dwSize
= sizeof(PROCESSENTRY32
);
642 entry
.szExeFile
[0] = '\0';
643 result
= Process32Next(hdl
, &entry
);
645 processlist
.append(QString::fromWCharArray(entry
.szExeFile
));
650 #if defined(Q_OS_MACX)
651 ProcessSerialNumber psn
= { 0, kNoProcess
};
655 err
= GetNextProcess(&psn
);
656 err
= GetProcessPID(&psn
, &pid
);
660 memset(&info
, 0, sizeof(ProcessInfoRec
));
661 info
.processName
= (unsigned char*)buf
;
662 info
.processInfoLength
= sizeof(ProcessInfoRec
);
663 err
= GetProcessInformation(&psn
, &info
);
665 // some processes start with nonprintable characters. Skip those.
667 for(i
= 0; i
< 32; i
++) {
668 if(isprint(buf
[i
])) break;
670 // avoid adding duplicates.
671 QString process
= QString::fromUtf8(&buf
[i
]);
672 if(!processlist
.contains(process
)) {
673 processlist
.append(process
);
678 } while(err
== noErr
);
680 // check for given names in list of processes
681 for(int i
= 0; i
< names
.size(); ++i
) {
682 #if defined(Q_OS_WIN32)
683 // the process name might be truncated. Allow the extension to be partial.
684 int index
= processlist
.indexOf(QRegExp(names
.at(i
) + "(\\.(e(x(e?)?)?)?)?"));
686 int index
= processlist
.indexOf(names
.at(i
));
689 found
.append(processlist
.at(index
));
692 qDebug() << "[Utils] Found listed processes running:" << found
;
697 /** Eject device from PC.
698 * Request the OS to eject the player.
699 * @param device mountpoint of the device
700 * @return true on success, fals otherwise.
702 bool Utils::ejectDevice(QString device
)
704 #if defined(Q_OS_WIN32)
705 /* See http://support.microsoft.com/kb/165721 on the procedure to eject a
707 bool success
= false;
714 _stprintf(volume
, _TEXT("\\\\.\\%c:"), device
.toAscii().at(0));
715 hdl
= CreateFile(volume
, GENERIC_READ
| GENERIC_WRITE
,
716 FILE_SHARE_READ
| FILE_SHARE_WRITE
, NULL
,
717 OPEN_EXISTING
, 0, NULL
);
718 if(hdl
== INVALID_HANDLE_VALUE
)
721 /* lock volume to make sure no other application is accessing the volume.
722 * Try up to 10 times. */
723 for(i
= 0; i
< 10; i
++) {
724 if(DeviceIoControl(hdl
, FSCTL_LOCK_VOLUME
,
725 NULL
, 0, NULL
, 0, &bytesReturned
, NULL
))
727 /* short break before retry */
731 /* successfully locked, now dismount */
732 if(DeviceIoControl(hdl
, FSCTL_DISMOUNT_VOLUME
,
733 NULL
, 0, NULL
, 0, &bytesReturned
, NULL
)) {
734 /* make sure media can be removed. */
735 PREVENT_MEDIA_REMOVAL pmr
;
736 pmr
.PreventMediaRemoval
= false;
737 if(DeviceIoControl(hdl
, IOCTL_STORAGE_MEDIA_REMOVAL
,
738 &pmr
, sizeof(PREVENT_MEDIA_REMOVAL
),
739 NULL
, 0, &bytesReturned
, NULL
)) {
740 /* eject the media */
741 if(DeviceIoControl(hdl
, IOCTL_STORAGE_EJECT_MEDIA
,
742 NULL
, 0, NULL
, 0, &bytesReturned
, NULL
))
752 #if defined(Q_OS_MACX)
753 // FIXME: FSUnmountVolumeSync is deprecated starting with 10.8.
754 // Use DADiskUnmount / DiskArbitration framework eventually.
755 // BSD label does not include folder.
756 QString bsd
= Utils::resolveDevicename(device
).remove("/dev/");
762 FSVolumeRefNum volrefnum
;
764 result
= FSGetVolumeInfo(kFSInvalidVolumeRefNum
, index
, &volrefnum
,
765 kFSVolInfoFSInfo
, NULL
, NULL
, NULL
);
766 if(result
== noErr
) {
767 GetVolParmsInfoBuffer volparms
;
769 hpb
.ioParam
.ioNamePtr
= NULL
;
770 hpb
.ioParam
.ioVRefNum
= volrefnum
;
771 hpb
.ioParam
.ioBuffer
= (Ptr
)&volparms
;
772 hpb
.ioParam
.ioReqCount
= sizeof(volparms
);
774 if(PBHGetVolParmsSync(&hpb
) == noErr
) {
775 if(volparms
.vMServerAdr
== 0) {
776 if(bsd
== (char*)volparms
.vMDeviceID
) {
778 result
= FSUnmountVolumeSync(volrefnum
, 0, &dissenter
);
786 } while(result
== noErr
);
787 if(result
== noErr
&& found
)
791 #if defined(Q_OS_LINUX)