Support "eject" on OS X.
[maemo-rb.git] / rbutil / rbutilqt / base / utils.cpp
blobeec03fe0ad7009df1282b4f412110a7c6cb09cfc
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
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 ****************************************************************************/
19 #include "utils.h"
20 #include "rockboxinfo.h"
21 #include "system.h"
22 #include "rbsettings.h"
23 #include "systeminfo.h"
25 #ifdef UNICODE
26 #define _UNICODE
27 #endif
29 #include <QtCore>
30 #include <QDebug>
31 #include <cstdlib>
32 #include <stdio.h>
34 #if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
35 #include <sys/statvfs.h>
36 #endif
37 #if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
38 #include <stdio.h>
39 #endif
40 #if defined(Q_OS_LINUX)
41 #include <mntent.h>
42 #endif
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>
47 #endif
48 #if defined(Q_OS_WIN32)
49 #include <stdio.h>
50 #include <tchar.h>
51 #include <windows.h>
52 #include <setupapi.h>
53 #include <winioctl.h>
54 #include <tlhelp32.h>
55 #endif
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>
61 #endif
63 // recursive function to delete a dir with files
64 bool Utils::recursiveRmdir( const QString &dirName )
66 QString dirN = dirName;
67 QDir dir(dirN);
68 // make list of entries in directory
69 QStringList list = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
70 QFileInfo fileInfo;
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
79 else // is file
80 QFile::remove(curItem); // ok, delete file
82 dir.cdUp();
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)
93 int start;
94 QString realpath;
95 QStringList elems = path.split("/", QString::SkipEmptyParts);
97 if(path.isEmpty())
98 return QString();
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.
102 start = 1;
103 realpath = elems.at(0) + "/";
104 #else
105 start = 0;
106 realpath = "/";
107 #endif
109 for(int i = start; i < elems.size(); i++) {
110 QStringList direlems
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);
119 if(a.size() != 1)
120 return QString("");
121 if(!realpath.endsWith("/"))
122 realpath += "/";
123 realpath += a.at(0);
125 else
126 return QString("");
128 qDebug() << "[Utils] resolving path" << path << "->" << realpath;
129 return realpath;
133 QString Utils::filesystemName(QString path)
135 QString name;
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);
140 if(res) {
141 name = QString::fromWCharArray(volname);
143 #endif
144 #if defined(Q_OS_MACX)
145 // BSD label does not include folder.
146 QString bsd = Utils::resolveDevicename(path).remove("/dev/");
147 if(bsd.isEmpty()) {
148 return name;
150 OSStatus result;
151 ItemCount index = 1;
153 do {
154 FSVolumeRefNum volrefnum;
155 HFSUniStr255 volname;
157 result = FSGetVolumeInfo(kFSInvalidVolumeRefNum, index, &volrefnum,
158 kFSVolInfoFSInfo, NULL, &volname, NULL);
160 if(result == noErr) {
161 GetVolParmsInfoBuffer volparms;
162 HParamBlockRec hpb;
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);
173 break;
178 index++;
179 } while(result == noErr);
180 #endif
182 qDebug() << "[Utils] Volume name of" << path << "is" << name;
183 return 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;
194 return size;
198 qulonglong Utils::filesystemTotal(QString path)
200 qulonglong size = filesystemSize(path, FilesystemTotal);
201 qDebug() << "[Utils] total disk space for" << path << size;
202 return size;
206 qulonglong Utils::filesystemClusterSize(QString path)
208 qulonglong size = filesystemSize(path, FilesystemClusterSize);
209 qDebug() << "[Utils] cluster size for" << path << size;
210 return size;
214 qulonglong Utils::filesystemSize(QString path, enum Utils::Size type)
216 qlonglong size = 0;
217 #if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
218 // the usage of statfs() is deprecated by the LSB so use statvfs().
219 struct statvfs fs;
220 int ret;
222 ret = statvfs(qPrintable(path), &fs);
224 if(ret == 0) {
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;
235 #endif
236 #if defined(Q_OS_WIN32)
237 BOOL ret;
238 ULARGE_INTEGER freeAvailBytes;
239 ULARGE_INTEGER totalNumberBytes;
241 ret = GetDiskFreeSpaceExW((LPCTSTR)path.utf16(), &freeAvailBytes,
242 &totalNumberBytes, NULL);
243 if(ret) {
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;
253 DWORD freeClusters;
254 DWORD totalClusters;
255 ret = GetDiskFreeSpaceW((LPCTSTR)path.utf16(), &sectorsPerCluster,
256 &bytesPerSector, &freeClusters, &totalClusters);
257 if(ret) {
258 size = bytesPerSector * sectorsPerCluster;
262 #endif
263 return size;
266 //! \brief searches for a Executable in the Environement Path
267 QString Utils::findExecutable(QString name)
269 QString exepath;
270 //try autodetect tts
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);
275 #endif
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("");
284 #endif
285 if(QFileInfo(executable).isExecutable())
287 qDebug() << "[Utils] findExecutable: found" << executable;
288 return QDir::toNativeSeparators(executable);
291 qDebug() << "[Utils] findExecutable: could not find" << name;
292 return "";
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";
303 QString text = "";
305 // check permission
306 if(permission)
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>");
314 #endif
317 // Check TargetId
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());
329 if(!text.isEmpty())
330 return tr("Problem detected:") + "<ul>" + text + "</ul>";
331 else
332 return text;
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.
345 if(a.isEmpty())
346 return 1;
347 if(b.isEmpty())
348 return -1;
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.
357 QString numa = a;
358 QString numb = b;
359 numa.remove(QRegExp("\\D+.*$"));
360 numb.remove(QRegExp("\\D+.*$"));
362 // convert to number
363 bool ok1, ok2;
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.
367 if(!ok1 && !ok2)
368 break;
369 if(!ok1)
370 return 1;
371 if(!ok2)
372 return -1;
374 // if numbers mismatch we have a decision.
375 if(vala != valb)
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]"));
388 if(adot && !bdot)
389 return -1;
390 if(!adot && bdot)
391 return 1;
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).
395 QChar ltra;
396 QChar ltrb;
397 if(a.contains(QRegExp("^[a-zA-Z]")))
398 ltra = a.at(0);
399 if(b.contains(QRegExp("^[a-zA-Z]")))
400 ltrb = b.at(0);
401 if(ltra != ltrb)
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.
411 return 0;
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");
424 if(!mn)
425 return QString("");
427 struct mntent *ent;
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))) {
436 endmntent(mn);
437 qDebug() << "[Utils] device name is" << ent->mnt_fsname;
438 return QString(ent->mnt_fsname);
441 endmntent(mn);
443 #endif
445 #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
446 int num;
447 struct statfs *mntinf;
449 num = getmntinfo(&mntinf, MNT_WAIT);
450 while(num--) {
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);
459 mntinf++;
461 #endif
463 #if defined(Q_OS_WIN32)
464 DWORD written;
465 HANDLE h;
466 TCHAR uncpath[MAX_PATH];
467 UCHAR buffer[0x400];
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;
475 return "";
477 // get the extents
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!";
482 return "";
484 qDebug() << "[Utils] device name is" << extents->Extents[0].DiskNumber;
485 return QString("%1").arg(extents->Extents[0].DiskNumber);
487 #endif
488 return QString("");
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");
503 if(!mn)
504 return QString("");
506 struct mntent *ent;
507 while((ent = getmntent(mn))) {
508 // Check for valid filesystem. Allow hfs too, as an Ipod might be a
509 // MacPod.
510 if(QString(ent->mnt_fsname) == device) {
511 QString result;
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);
517 else {
518 qDebug() << "[Utils] mountpoint is wrong filesystem!";
520 endmntent(mn);
521 return result;
524 endmntent(mn);
526 #endif
528 #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
529 int num;
530 struct statfs *mntinf;
532 num = getmntinfo(&mntinf, MNT_WAIT);
533 while(num--) {
534 // Check for valid filesystem. Allow hfs too, as an Ipod might be a
535 // MacPod.
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);
542 else {
543 qDebug() << "[Utils] mountpoint is wrong filesystem!";
544 return QString();
547 mntinf++;
549 #endif
551 #if defined(Q_OS_WIN32)
552 QString result;
553 unsigned int driveno = device.replace(QRegExp("^.*([0-9]+)"), "\\1").toInt();
555 int letter;
556 for(letter = 'A'; letter <= 'Z'; letter++) {
557 if(resolveDevicename(QString(letter)).toUInt() == driveno) {
558 result = letter;
559 qDebug() << "[Utils] resolved mountpoint is:" << result;
560 break;
563 if(!result.isEmpty())
564 return result + ":/";
565 #endif
566 qDebug() << "[Utils] resolving mountpoint failed!";
567 return QString("");
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)
583 int num;
584 struct statfs *mntinf;
586 num = getmntinfo(&mntinf, MNT_WAIT);
587 while(num--) {
588 tempList << QString(mntinf->f_mntonname);
589 qDebug() << "[Utils] Mounted on" << mntinf->f_mntonname
590 << "is" << mntinf->f_mntfromname << "type" << mntinf->f_fstypename;
591 mntinf++;
593 #elif defined(Q_OS_LINUX)
595 FILE *mn = setmntent("/etc/mtab", "r");
596 if(!mn)
597 return QStringList("");
599 struct mntent *ent;
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;
605 endmntent(mn);
607 #else
608 #error Unknown Platform
609 #endif
610 return tempList;
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;
621 QStringList found;
622 #if defined(Q_OS_WIN32)
623 HANDLE hdl;
624 PROCESSENTRY32 entry;
625 bool result;
627 hdl = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
628 if(hdl == INVALID_HANDLE_VALUE) {
629 qDebug() << "[Utils] CreateToolhelp32Snapshot failed.";
630 return found;
632 entry.dwSize = sizeof(PROCESSENTRY32);
633 entry.szExeFile[0] = '\0';
634 if(!Process32First(hdl, &entry)) {
635 qDebug() << "[Utils] Process32First failed.";
636 return found;
639 processlist.append(QString::fromWCharArray(entry.szExeFile));
640 do {
641 entry.dwSize = sizeof(PROCESSENTRY32);
642 entry.szExeFile[0] = '\0';
643 result = Process32Next(hdl, &entry);
644 if(result) {
645 processlist.append(QString::fromWCharArray(entry.szExeFile));
647 } while(result);
648 CloseHandle(hdl);
649 #endif
650 #if defined(Q_OS_MACX)
651 ProcessSerialNumber psn = { 0, kNoProcess };
652 OSErr err;
653 do {
654 pid_t pid;
655 err = GetNextProcess(&psn);
656 err = GetProcessPID(&psn, &pid);
657 if(err == noErr) {
658 char buf[32] = {0};
659 ProcessInfoRec info;
660 memset(&info, 0, sizeof(ProcessInfoRec));
661 info.processName = (unsigned char*)buf;
662 info.processInfoLength = sizeof(ProcessInfoRec);
663 err = GetProcessInformation(&psn, &info);
664 if(err == noErr) {
665 // some processes start with nonprintable characters. Skip those.
666 int i;
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);
679 #endif
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?)?)?)?)?"));
685 #else
686 int index = processlist.indexOf(names.at(i));
687 #endif
688 if(index != -1) {
689 found.append(processlist.at(index));
692 qDebug() << "[Utils] Found listed processes running:" << found;
693 return 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
706 * device. */
707 bool success = false;
708 int i;
709 HANDLE hdl;
710 DWORD bytesReturned;
711 TCHAR volume[8];
713 /* CreateFile */
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)
719 return false;
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))
726 break;
727 /* short break before retry */
728 Sleep(100);
730 if(i < 10) {
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))
743 success = true;
747 /* close handle */
748 CloseHandle(hdl);
749 return success;
751 #endif
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/");
757 OSStatus result;
758 ItemCount index = 1;
759 bool found = false;
761 do {
762 FSVolumeRefNum volrefnum;
764 result = FSGetVolumeInfo(kFSInvalidVolumeRefNum, index, &volrefnum,
765 kFSVolInfoFSInfo, NULL, NULL, NULL);
766 if(result == noErr) {
767 GetVolParmsInfoBuffer volparms;
768 HParamBlockRec hpb;
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) {
777 pid_t dissenter;
778 result = FSUnmountVolumeSync(volrefnum, 0, &dissenter);
779 found = true;
780 break;
785 index++;
786 } while(result == noErr);
787 if(result == noErr && found)
788 return true;
790 #endif
791 return false;