Handle device name resolving failures.
[kugel-rb.git] / rbutil / rbutilqt / base / autodetection.cpp
blob695cb6a204d0979f298356915de0b6cd89781b25
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
9 * Copyright (C) 2007 by Dominik Wenger
10 * $Id$
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
20 #include <QtCore>
21 #include "autodetection.h"
22 #include "rbsettings.h"
23 #include "systeminfo.h"
25 #include "../ipodpatcher/ipodpatcher.h"
26 #include "../sansapatcher/sansapatcher.h"
28 #if defined(Q_OS_LINUX) || defined(Q_OS_MACX)
29 #include <stdio.h>
30 #endif
31 #if defined(Q_OS_LINUX)
32 #include <mntent.h>
33 #endif
34 #if defined(Q_OS_MACX)
35 #include <sys/param.h>
36 #include <sys/ucred.h>
37 #include <sys/mount.h>
38 #endif
39 #if defined(Q_OS_WIN32)
40 #if defined(UNICODE)
41 #define _UNICODE
42 #endif
43 #include <stdio.h>
44 #include <tchar.h>
45 #include <windows.h>
46 #include <setupapi.h>
47 #include <winioctl.h>
48 #endif
50 #if defined(Q_OS_OPENBSD)
51 #include <sys/param.h>
52 #include <sys/mount.h>
53 #endif
55 #include "system.h"
56 #include "utils.h"
57 #include "rockboxinfo.h"
59 Autodetection::Autodetection(QObject* parent): QObject(parent)
63 bool Autodetection::detect()
65 m_device = "";
66 m_mountpoint = "";
67 m_errdev = "";
69 detectUsb();
71 // Try detection via rockbox.info / rbutil.log
72 QStringList mounts = mountpoints();
74 for(int i=0; i< mounts.size();i++)
76 // do the file checking
77 QDir dir(mounts.at(i));
78 qDebug() << "[Autodetect] paths to check:" << mounts;
79 if(dir.exists())
81 // check logfile first.
82 if(QFile(mounts.at(i) + "/.rockbox/rbutil.log").exists()) {
83 QSettings log(mounts.at(i) + "/.rockbox/rbutil.log",
84 QSettings::IniFormat, this);
85 if(!log.value("platform").toString().isEmpty()) {
86 if(m_device.isEmpty())
87 m_device = log.value("platform").toString();
88 m_mountpoint = mounts.at(i);
89 qDebug() << "[Autodetect] rbutil.log detected:" << m_device << m_mountpoint;
90 return true;
94 // check rockbox-info.txt afterwards.
95 RockboxInfo info(mounts.at(i));
96 if(info.success())
98 if(m_device.isEmpty())
100 m_device = info.target();
101 // special case for video64mb. This is a workaround, and
102 // should get replaced when autodetection is reworked.
103 if(m_device == "ipodvideo" && info.ram() == 64)
105 m_device = "ipodvideo64mb";
108 m_mountpoint = mounts.at(i);
109 qDebug() << "[Autodetect] rockbox-info.txt detected:"
110 << m_device << m_mountpoint;
111 return true;
114 // check for some specific files in root folder
115 QDir root(mounts.at(i));
116 QStringList rootentries = root.entryList(QDir::Files);
117 if(rootentries.contains("archos.mod", Qt::CaseInsensitive))
119 // archos.mod in root folder -> Archos Player
120 m_device = "player";
121 m_mountpoint = mounts.at(i);
122 return true;
124 if(rootentries.contains("ONDIOST.BIN", Qt::CaseInsensitive))
126 // ONDIOST.BIN in root -> Ondio FM
127 m_device = "ondiofm";
128 m_mountpoint = mounts.at(i);
129 return true;
131 if(rootentries.contains("ONDIOSP.BIN", Qt::CaseInsensitive))
133 // ONDIOSP.BIN in root -> Ondio SP
134 m_device = "ondiosp";
135 m_mountpoint = mounts.at(i);
136 return true;
138 if(rootentries.contains("ajbrec.ajz", Qt::CaseInsensitive))
140 qDebug() << "[Autodetect] ajbrec.ajz found. Trying detectAjbrec()";
141 if(detectAjbrec(mounts.at(i))) {
142 m_mountpoint = mounts.at(i);
143 qDebug() << "[Autodetect]" << m_device;
144 return true;
147 // detection based on player specific folders
148 QStringList rootfolders = root.entryList(QDir::Dirs
149 | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
150 if(rootfolders.contains("GBSYSTEM", Qt::CaseInsensitive))
152 // GBSYSTEM folder -> Gigabeat
153 m_device = "gigabeatf";
154 m_mountpoint = mounts.at(i);
155 return true;
157 #if defined(Q_OS_WIN32)
158 // on windows, try to detect the drive letter of an Ipod
159 if(rootfolders.contains("iPod_Control", Qt::CaseInsensitive))
161 // iPod_Control folder -> Ipod found
162 // detecting of the Ipod type is done below using ipodpatcher
163 m_mountpoint = mounts.at(i);
165 #endif
170 int n;
171 // try ipodpatcher
172 // initialize sector buffer. Needed.
173 ipod_sectorbuf = NULL;
174 ipod_alloc_buffer(&ipod_sectorbuf, BUFFER_SIZE);
175 struct ipod_t ipod;
176 n = ipod_scan(&ipod);
177 if(n == 1) {
178 qDebug() << "[Autodetect] Ipod found:" << ipod.modelstr << "at" << ipod.diskname;
179 // if the found ipod is a macpod also notice it as device with problem.
180 if(ipod.macpod)
181 m_errdev = ipod.targetname;
182 m_device = ipod.targetname;
183 // since resolveMountPoint is doing exact matches we need to select
184 // the correct partition.
185 QString mp(ipod.diskname);
186 #ifdef Q_OS_LINUX
187 mp.append("2");
188 #endif
189 #ifdef Q_OS_MACX
190 mp.append("s2");
191 #endif
192 m_mountpoint = resolveMountPoint(mp);
193 return true;
195 else {
196 qDebug() << "[Autodetect] ipodpatcher: no Ipod found." << n;
198 free(ipod_sectorbuf);
199 ipod_sectorbuf = NULL;
201 // try sansapatcher
202 // initialize sector buffer. Needed.
203 sansa_sectorbuf = NULL;
204 sansa_alloc_buffer(&sansa_sectorbuf, BUFFER_SIZE);
205 struct sansa_t sansa;
206 n = sansa_scan(&sansa);
207 if(n == 1) {
208 qDebug() << "[Autodetect] Sansa found:" << sansa.targetname << "at" << sansa.diskname;
209 m_device = QString("sansa%1").arg(sansa.targetname);
210 QString mp(sansa.diskname);
211 #ifdef Q_OS_LINUX
212 mp.append("1");
213 #endif
214 #ifdef Q_OS_MACX
215 mp.append("s1");
216 #endif
217 m_mountpoint = resolveMountPoint(mp);
218 return true;
220 else {
221 qDebug() << "[Autodetect] sansapatcher: no Sansa found." << n;
223 free(sansa_sectorbuf);
224 sansa_sectorbuf = NULL;
226 if(m_mountpoint.isEmpty() && m_device.isEmpty()
227 && m_errdev.isEmpty() && m_incompat.isEmpty())
228 return false;
229 return true;
233 QStringList Autodetection::mountpoints()
235 QStringList tempList;
236 #if defined(Q_OS_WIN32)
237 QFileInfoList list = QDir::drives();
238 for(int i=0; i<list.size();i++)
240 tempList << list.at(i).absolutePath();
241 qDebug() << "[Autodetection] Mounted on" << list.at(i).absolutePath();
244 #elif defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
245 int num;
246 struct statfs *mntinf;
248 num = getmntinfo(&mntinf, MNT_WAIT);
249 while(num--) {
250 tempList << QString(mntinf->f_mntonname);
251 qDebug() << "[Autodetection] Mounted on" << mntinf->f_mntonname
252 << "is" << mntinf->f_mntfromname << "type" << mntinf->f_fstypename;
253 mntinf++;
255 #elif defined(Q_OS_LINUX)
257 FILE *mn = setmntent("/etc/mtab", "r");
258 if(!mn)
259 return QStringList("");
261 struct mntent *ent;
262 while((ent = getmntent(mn))) {
263 tempList << QString(ent->mnt_dir);
264 qDebug() << "[Autodetection] Mounted on" << ent->mnt_dir
265 << "is" << ent->mnt_fsname << "type" << ent->mnt_type;
267 endmntent(mn);
269 #else
270 #error Unknown Plattform
271 #endif
272 return tempList;
276 /** resolve device name to mount point / drive letter
277 * @param device device name / disk number
278 * @return mount point / drive letter
280 QString Autodetection::resolveMountPoint(QString device)
282 qDebug() << "[Autodetect] resolving mountpoint:" << device;
284 #if defined(Q_OS_LINUX)
285 FILE *mn = setmntent("/etc/mtab", "r");
286 if(!mn)
287 return QString("");
289 struct mntent *ent;
290 while((ent = getmntent(mn))) {
291 // Check for valid filesystem. Allow hfs too, as an Ipod might be a
292 // MacPod.
293 if(QString(ent->mnt_fsname) == device) {
294 QString result;
295 if(QString(ent->mnt_type).contains("vfat", Qt::CaseInsensitive)
296 || QString(ent->mnt_type).contains("hfs", Qt::CaseInsensitive)) {
297 qDebug() << "[Autodetect] resolved mountpoint is:" << ent->mnt_dir;
298 result = QString(ent->mnt_dir);
300 else {
301 qDebug() << "[Autodetect] mountpoint is wrong filesystem!";
303 endmntent(mn);
304 return result;
307 endmntent(mn);
309 #endif
311 #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
312 int num;
313 struct statfs *mntinf;
315 num = getmntinfo(&mntinf, MNT_WAIT);
316 while(num--) {
317 // Check for valid filesystem. Allow hfs too, as an Ipod might be a
318 // MacPod.
319 if(QString(mntinf->f_mntfromname) == device) {
320 if(QString(mntinf->f_fstypename).contains("msdos", Qt::CaseInsensitive)
321 || QString(mntinf->f_fstypename).contains("hfs", Qt::CaseInsensitive)) {
322 qDebug() << "[Autodetect] resolved mountpoint is:" << mntinf->f_mntonname;
323 return QString(mntinf->f_mntonname);
325 else {
326 qDebug() << "[Autodetect] mountpoint is wrong filesystem!";
327 return QString();
330 mntinf++;
332 #endif
334 #if defined(Q_OS_WIN32)
335 QString result;
336 unsigned int driveno = device.replace(QRegExp("^.*([0-9]+)"), "\\1").toInt();
338 int letter;
339 for(letter = 'A'; letter <= 'Z'; letter++) {
340 if(resolveDevicename(QString(letter)).toUInt() == driveno) {
341 result = letter;
342 qDebug() << "[Autodetect] resolved mountpoint is:" << result;
343 break;
346 if(!result.isEmpty())
347 return result + ":/";
348 #endif
349 qDebug() << "[Autodetect] resolving mountpoint failed!";
350 return QString("");
354 /** Resolve mountpoint to devicename / disk number
355 * @param path mountpoint path / drive letter
356 * @return devicename / disk number
358 QString Autodetection::resolveDevicename(QString path)
360 qDebug() << "[Autodetect] resolving device name" << path;
361 #if defined(Q_OS_LINUX)
362 FILE *mn = setmntent("/etc/mtab", "r");
363 if(!mn)
364 return QString("");
366 struct mntent *ent;
367 while((ent = getmntent(mn))) {
368 // check for valid filesystem type.
369 // Linux can handle hfs (and hfsplus), so consider it a valid file
370 // system. Otherwise resolving the device name would fail, which in
371 // turn would make it impossible to warn about a MacPod.
372 if(QString(ent->mnt_dir) == path
373 && (QString(ent->mnt_type).contains("vfat", Qt::CaseInsensitive)
374 || QString(ent->mnt_type).contains("hfs", Qt::CaseInsensitive))) {
375 endmntent(mn);
376 qDebug() << "[Autodetect] device name is" << ent->mnt_fsname;
377 return QString(ent->mnt_fsname);
380 endmntent(mn);
382 #endif
384 #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD)
385 int num;
386 struct statfs *mntinf;
388 num = getmntinfo(&mntinf, MNT_WAIT);
389 while(num--) {
390 // check for valid filesystem type. OS X can handle hfs (hfs+ is
391 // treated as hfs), BSD should be the same.
392 if(QString(mntinf->f_mntonname) == path
393 && (QString(mntinf->f_fstypename).contains("msdos", Qt::CaseInsensitive)
394 || QString(mntinf->f_fstypename).contains("hfs", Qt::CaseInsensitive))) {
395 qDebug() << "[Autodetect] device name is" << mntinf->f_mntfromname;
396 return QString(mntinf->f_mntfromname);
398 mntinf++;
400 #endif
402 #if defined(Q_OS_WIN32)
403 DWORD written;
404 HANDLE h;
405 TCHAR uncpath[MAX_PATH];
406 UCHAR buffer[0x400];
407 PVOLUME_DISK_EXTENTS extents = (PVOLUME_DISK_EXTENTS)buffer;
409 _stprintf(uncpath, _TEXT("\\\\.\\%c:"), path.toAscii().at(0));
410 h = CreateFile(uncpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
411 NULL, OPEN_EXISTING, 0, NULL);
412 if(h == INVALID_HANDLE_VALUE) {
413 //qDebug() << "error getting extents for" << uncpath;
414 return "";
416 // get the extents
417 if(DeviceIoControl(h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
418 NULL, 0, extents, sizeof(buffer), &written, NULL)) {
419 if(extents->NumberOfDiskExtents > 1) {
420 qDebug() << "[Autodetect] resolving device name: volume spans multiple disks!";
421 return "";
423 qDebug() << "[Autodetect] device name is" << extents->Extents[0].DiskNumber;
424 return QString("%1").arg(extents->Extents[0].DiskNumber);
426 #endif
427 return QString("");
432 /** @brief detect devices based on usb pid / vid.
433 * @return true upon success, false otherwise.
435 bool Autodetection::detectUsb()
437 // usbids holds the mapping in the form
438 // ((VID<<16)|(PID)), targetname
439 // the ini file needs to hold the IDs as hex values.
440 QMap<int, QString> usbids = SystemInfo::usbIdMap(SystemInfo::MapDevice);
441 QMap<int, QString> usberror = SystemInfo::usbIdMap(SystemInfo::MapError);
442 QMap<int, QString> usbincompat = SystemInfo::usbIdMap(SystemInfo::MapIncompatible);
444 // usb pid detection
445 QList<uint32_t> attached;
446 attached = System::listUsbIds();
448 int i = attached.size();
449 while(i--) {
450 if(usbids.contains(attached.at(i))) {
451 m_device = usbids.value(attached.at(i));
452 qDebug() << "[USB] detected supported player" << m_device;
453 return true;
455 if(usberror.contains(attached.at(i))) {
456 m_errdev = usberror.value(attached.at(i));
457 qDebug() << "[USB] detected problem with player" << m_errdev;
458 return true;
460 QString idstring = QString("%1").arg(attached.at(i), 8, 16, QChar('0'));
461 if(!SystemInfo::platformValue(idstring, SystemInfo::CurName).toString().isEmpty()) {
462 m_incompat = idstring;
463 qDebug() << "[USB] detected incompatible player" << m_incompat;
464 return true;
467 return false;
471 bool Autodetection::detectAjbrec(QString root)
473 QFile f(root + "/ajbrec.ajz");
474 char header[24];
475 f.open(QIODevice::ReadOnly);
476 if(!f.read(header, 24)) return false;
478 // check the header of the file.
479 // recorder v1 had a 6 bytes sized header
480 // recorder v2, FM, Ondio SP and FM have a 24 bytes header.
482 // recorder v1 has the binary length in the first 4 bytes, so check
483 // for them first.
484 int len = (header[0]<<24) | (header[1]<<16) | (header[2]<<8) | header[3];
485 qDebug() << "[Autodetect] ABJREC possible bin length:" << len
486 << "file len:" << f.size();
487 if((f.size() - 6) == len)
488 m_device = "recorder";
490 // size didn't match, now we need to assume we have a headerlength of 24.
491 switch(header[11]) {
492 case 2:
493 m_device = "recorderv2";
494 break;
496 case 4:
497 m_device = "fmrecorder";
498 break;
500 case 8:
501 m_device = "ondiofm";
502 break;
504 case 16:
505 m_device = "ondiosp";
506 break;
508 default:
509 break;
511 f.close();
513 if(m_device.isEmpty()) return false;
514 return true;