1/****************************************************************************
2**
3** Copyright (C) 2011-2012 Denis Shienkov <denis.shienkov@gmail.com>
4** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com>
5** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org>
6** Contact: https://www.qt.io/licensing/
7**
8** This file is part of the QtSerialPort module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see https://www.qt.io/terms-conditions. For further
17** information use the contact form at https://www.qt.io/contact-us.
18**
19** GNU Lesser General Public License Usage
20** Alternatively, this file may be used under the terms of the GNU Lesser
21** General Public License version 3 as published by the Free Software
22** Foundation and appearing in the file LICENSE.LGPL3 included in the
23** packaging of this file. Please review the following information to
24** ensure the GNU Lesser General Public License version 3 requirements
25** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26**
27** GNU General Public License Usage
28** Alternatively, this file may be used under the terms of the GNU
29** General Public License version 2.0 or (at your option) the GNU General
30** Public license version 3 or any later version approved by the KDE Free
31** Qt Foundation. The licenses are as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33** included in the packaging of this file. Please review the following
34** information to ensure the GNU General Public License requirements will
35** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36** https://www.gnu.org/licenses/gpl-3.0.html.
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qserialportinfo.h"
43#include "qserialportinfo_p.h"
44#include "qserialport_p.h"
45
46#include <QtCore/qlockfile.h>
47#include <QtCore/qfile.h>
48#include <QtCore/qdir.h>
49#include <QtCore/qscopedpointer.h>
50
51#include <private/qcore_unix_p.h>
52
53#include <errno.h>
54#include <sys/types.h> // kill
55#include <signal.h> // kill
56
57#include "qtudev_p.h"
58
59QT_BEGIN_NAMESPACE
60
61static QStringList filteredDeviceFilePaths()
62{
63 static const QStringList deviceFileNameFilterList = QStringList()
64
65#ifdef Q_OS_LINUX
66 << QStringLiteral("ttyS*") // Standard UART 8250 and etc.
67 << QStringLiteral("ttyO*") // OMAP UART 8250 and etc.
68 << QStringLiteral("ttyUSB*") // Usb/serial converters PL2303 and etc.
69 << QStringLiteral("ttyACM*") // CDC_ACM converters (i.e. Mobile Phones).
70 << QStringLiteral("ttyGS*") // Gadget serial device (i.e. Mobile Phones with gadget serial driver).
71 << QStringLiteral("ttyMI*") // MOXA pci/serial converters.
72 << QStringLiteral("ttymxc*") // Motorola IMX serial ports (i.e. Freescale i.MX).
73 << QStringLiteral("ttyAMA*") // AMBA serial device for embedded platform on ARM (i.e. Raspberry Pi).
74 << QStringLiteral("ttyTHS*") // Serial device for embedded platform on ARM (i.e. Tegra Jetson TK1).
75 << QStringLiteral("rfcomm*") // Bluetooth serial device.
76 << QStringLiteral("ircomm*") // IrDA serial device.
77 << QStringLiteral("tnt*"); // Virtual tty0tty serial device.
78#elif defined(Q_OS_FREEBSD)
79 << QStringLiteral("cu*");
80#elif defined(Q_OS_QNX)
81 << QStringLiteral("ser*");
82#else
83 ;
84#endif
85
86 QStringList result;
87
88 QDir deviceDir(QStringLiteral("/dev"));
89 if (deviceDir.exists()) {
90 deviceDir.setNameFilters(deviceFileNameFilterList);
91 deviceDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks);
92 QStringList deviceFilePaths;
93 const auto deviceFileInfos = deviceDir.entryInfoList();
94 for (const QFileInfo &deviceFileInfo : deviceFileInfos) {
95 const QString deviceAbsoluteFilePath = deviceFileInfo.absoluteFilePath();
96
97#ifdef Q_OS_FREEBSD
98 // it is a quick workaround to skip the non-serial devices
99 if (deviceAbsoluteFilePath.endsWith(QLatin1String(".init"))
100 || deviceAbsoluteFilePath.endsWith(QLatin1String(".lock"))) {
101 continue;
102 }
103#endif
104
105 if (!deviceFilePaths.contains(str: deviceAbsoluteFilePath)) {
106 deviceFilePaths.append(t: deviceAbsoluteFilePath);
107 result.append(t: deviceAbsoluteFilePath);
108 }
109 }
110 }
111
112 return result;
113}
114
115QList<QSerialPortInfo> availablePortsByFiltersOfDevices(bool &ok)
116{
117 QList<QSerialPortInfo> serialPortInfoList;
118
119 const auto deviceFilePaths = filteredDeviceFilePaths();
120 for (const QString &deviceFilePath : deviceFilePaths) {
121 QSerialPortInfoPrivate priv;
122 priv.device = deviceFilePath;
123 priv.portName = QSerialPortInfoPrivate::portNameFromSystemLocation(source: deviceFilePath);
124 serialPortInfoList.append(t: priv);
125 }
126
127 ok = true;
128 return serialPortInfoList;
129}
130
131static bool isSerial8250Driver(const QString &driverName)
132{
133 return (driverName == QLatin1String("serial8250"));
134}
135
136static bool isValidSerial8250(const QString &systemLocation)
137{
138#ifdef Q_OS_LINUX
139 const mode_t flags = O_RDWR | O_NONBLOCK | O_NOCTTY;
140 const int fd = qt_safe_open(pathname: systemLocation.toLocal8Bit().constData(), flags);
141 if (fd != -1) {
142 struct serial_struct serinfo;
143 const int retval = ::ioctl(fd: fd, TIOCGSERIAL, &serinfo);
144 qt_safe_close(fd);
145 if (retval != -1 && serinfo.type != PORT_UNKNOWN)
146 return true;
147 }
148#else
149 Q_UNUSED(systemLocation);
150#endif
151 return false;
152}
153
154static bool isRfcommDevice(const QString &portName)
155{
156 if (!portName.startsWith(s: QLatin1String("rfcomm")))
157 return false;
158
159 bool ok;
160 const int portNumber = portName.midRef(position: 6).toInt(ok: &ok);
161 if (!ok || (portNumber < 0) || (portNumber > 255))
162 return false;
163 return true;
164}
165
166// provided by the tty0tty driver
167static bool isVirtualNullModemDevice(const QString &portName)
168{
169 return portName.startsWith(s: QLatin1String("tnt"));
170}
171
172// provided by the g_serial driver
173static bool isGadgetDevice(const QString &portName)
174{
175 return portName.startsWith(s: QLatin1String("ttyGS"));
176}
177
178static QString ueventProperty(const QDir &targetDir, const QByteArray &pattern)
179{
180 QFile f(QFileInfo(targetDir, QStringLiteral("uevent")).absoluteFilePath());
181 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text))
182 return QString();
183
184 const QByteArray content = f.readAll();
185
186 const int firstbound = content.indexOf(a: pattern);
187 if (firstbound == -1)
188 return QString();
189
190 const int lastbound = content.indexOf(c: '\n', from: firstbound);
191 return QString::fromLatin1(
192 str: content.mid(index: firstbound + pattern.size(),
193 len: lastbound - firstbound - pattern.size()))
194 .simplified();
195}
196
197static QString deviceName(const QDir &targetDir)
198{
199 return ueventProperty(targetDir, pattern: "DEVNAME=");
200}
201
202static QString deviceDriver(const QDir &targetDir)
203{
204 const QDir deviceDir(targetDir.absolutePath() + QLatin1String("/device"));
205 return ueventProperty(targetDir: deviceDir, pattern: "DRIVER=");
206}
207
208static QString deviceProperty(const QString &targetFilePath)
209{
210 QFile f(targetFilePath);
211 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text))
212 return QString();
213 return QString::fromLatin1(str: f.readAll()).simplified();
214}
215
216static QString deviceDescription(const QDir &targetDir)
217{
218 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("product")).absoluteFilePath());
219}
220
221static QString deviceManufacturer(const QDir &targetDir)
222{
223 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("manufacturer")).absoluteFilePath());
224}
225
226static quint16 deviceProductIdentifier(const QDir &targetDir, bool &hasIdentifier)
227{
228 QString result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("idProduct")).absoluteFilePath());
229 if (result.isEmpty())
230 result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("device")).absoluteFilePath());
231 return result.toInt(ok: &hasIdentifier, base: 16);
232}
233
234static quint16 deviceVendorIdentifier(const QDir &targetDir, bool &hasIdentifier)
235{
236 QString result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("idVendor")).absoluteFilePath());
237 if (result.isEmpty())
238 result = deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("vendor")).absoluteFilePath());
239 return result.toInt(ok: &hasIdentifier, base: 16);
240}
241
242static QString deviceSerialNumber(const QDir &targetDir)
243{
244 return deviceProperty(targetFilePath: QFileInfo(targetDir, QStringLiteral("serial")).absoluteFilePath());
245}
246
247QList<QSerialPortInfo> availablePortsBySysfs(bool &ok)
248{
249 QDir ttySysClassDir(QStringLiteral("/sys/class/tty"));
250
251 if (!(ttySysClassDir.exists() && ttySysClassDir.isReadable())) {
252 ok = false;
253 return QList<QSerialPortInfo>();
254 }
255
256 QList<QSerialPortInfo> serialPortInfoList;
257 ttySysClassDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
258 const auto fileInfos = ttySysClassDir.entryInfoList();
259 for (const QFileInfo &fileInfo : fileInfos) {
260 if (!fileInfo.isSymLink())
261 continue;
262
263 QDir targetDir(fileInfo.symLinkTarget());
264
265 QSerialPortInfoPrivate priv;
266
267 priv.portName = deviceName(targetDir);
268 if (priv.portName.isEmpty())
269 continue;
270
271 const QString driverName = deviceDriver(targetDir);
272 if (driverName.isEmpty()) {
273 if (!isRfcommDevice(portName: priv.portName)
274 && !isVirtualNullModemDevice(portName: priv.portName)
275 && !isGadgetDevice(portName: priv.portName)) {
276 continue;
277 }
278 }
279
280 priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(source: priv.portName);
281 if (isSerial8250Driver(driverName) && !isValidSerial8250(systemLocation: priv.device))
282 continue;
283
284 do {
285 if (priv.description.isEmpty())
286 priv.description = deviceDescription(targetDir);
287
288 if (priv.manufacturer.isEmpty())
289 priv.manufacturer = deviceManufacturer(targetDir);
290
291 if (priv.serialNumber.isEmpty())
292 priv.serialNumber = deviceSerialNumber(targetDir);
293
294 if (!priv.hasVendorIdentifier)
295 priv.vendorIdentifier = deviceVendorIdentifier(targetDir, hasIdentifier&: priv.hasVendorIdentifier);
296
297 if (!priv.hasProductIdentifier)
298 priv.productIdentifier = deviceProductIdentifier(targetDir, hasIdentifier&: priv.hasProductIdentifier);
299
300 if (!priv.description.isEmpty()
301 || !priv.manufacturer.isEmpty()
302 || !priv.serialNumber.isEmpty()
303 || priv.hasVendorIdentifier
304 || priv.hasProductIdentifier) {
305 break;
306 }
307 } while (targetDir.cdUp());
308
309 serialPortInfoList.append(t: priv);
310 }
311
312 ok = true;
313 return serialPortInfoList;
314}
315
316struct ScopedPointerUdevDeleter
317{
318 static inline void cleanup(struct ::udev *pointer)
319 {
320 ::udev_unref(udev: pointer);
321 }
322};
323
324struct ScopedPointerUdevEnumeratorDeleter
325{
326 static inline void cleanup(struct ::udev_enumerate *pointer)
327 {
328 ::udev_enumerate_unref(udev_enumerate: pointer);
329 }
330};
331
332struct ScopedPointerUdevDeviceDeleter
333{
334 static inline void cleanup(struct ::udev_device *pointer)
335 {
336 ::udev_device_unref(udev_device: pointer);
337 }
338};
339
340#ifndef LINK_LIBUDEV
341 Q_GLOBAL_STATIC(QLibrary, udevLibrary)
342#endif
343
344static QString deviceProperty(struct ::udev_device *dev, const char *name)
345{
346 return QString::fromLatin1(str: ::udev_device_get_property_value(udev_device: dev, key: name));
347}
348
349static QString deviceDriver(struct ::udev_device *dev)
350{
351 return QString::fromLatin1(str: ::udev_device_get_driver(udev_device: dev));
352}
353
354static QString deviceDescription(struct ::udev_device *dev)
355{
356 return deviceProperty(dev, name: "ID_MODEL").replace(before: QLatin1Char('_'), after: QLatin1Char(' '));
357}
358
359static QString deviceManufacturer(struct ::udev_device *dev)
360{
361 return deviceProperty(dev, name: "ID_VENDOR").replace(before: QLatin1Char('_'), after: QLatin1Char(' '));
362}
363
364static quint16 deviceProductIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
365{
366 return deviceProperty(dev, name: "ID_MODEL_ID").toInt(ok: &hasIdentifier, base: 16);
367}
368
369static quint16 deviceVendorIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
370{
371 return deviceProperty(dev, name: "ID_VENDOR_ID").toInt(ok: &hasIdentifier, base: 16);
372}
373
374static QString deviceSerialNumber(struct ::udev_device *dev)
375{
376 return deviceProperty(dev,name: "ID_SERIAL_SHORT");
377}
378
379static QString deviceName(struct ::udev_device *dev)
380{
381 return QString::fromLatin1(str: ::udev_device_get_sysname(udev_device: dev));
382}
383
384static QString deviceLocation(struct ::udev_device *dev)
385{
386 return QString::fromLatin1(str: ::udev_device_get_devnode(udev_device: dev));
387}
388
389QList<QSerialPortInfo> availablePortsByUdev(bool &ok)
390{
391 ok = false;
392
393#ifndef LINK_LIBUDEV
394 static bool symbolsResolved = resolveSymbols(udevLibrary());
395 if (!symbolsResolved)
396 return QList<QSerialPortInfo>();
397#endif
398
399 QScopedPointer<struct ::udev, ScopedPointerUdevDeleter> udev(::udev_new());
400
401 if (!udev)
402 return QList<QSerialPortInfo>();
403
404 QScopedPointer<udev_enumerate, ScopedPointerUdevEnumeratorDeleter>
405 enumerate(::udev_enumerate_new(udev: udev.data()));
406
407 if (!enumerate)
408 return QList<QSerialPortInfo>();
409
410 ::udev_enumerate_add_match_subsystem(udev_enumerate: enumerate.data(), subsystem: "tty");
411 ::udev_enumerate_scan_devices(udev_enumerate: enumerate.data());
412
413 udev_list_entry *devices = ::udev_enumerate_get_list_entry(udev_enumerate: enumerate.data());
414
415 QList<QSerialPortInfo> serialPortInfoList;
416 udev_list_entry *dev_list_entry;
417 udev_list_entry_foreach(dev_list_entry, devices) {
418
419 ok = true;
420
421 QScopedPointer<udev_device, ScopedPointerUdevDeviceDeleter>
422 dev(::udev_device_new_from_syspath(
423 udev: udev.data(), syspath: ::udev_list_entry_get_name(list_entry: dev_list_entry)));
424
425 if (!dev)
426 return serialPortInfoList;
427
428 QSerialPortInfoPrivate priv;
429
430 priv.device = deviceLocation(dev: dev.data());
431 priv.portName = deviceName(dev: dev.data());
432
433 udev_device *parentdev = ::udev_device_get_parent(udev_device: dev.data());
434
435 if (parentdev) {
436 const QString driverName = deviceDriver(dev: parentdev);
437 if (isSerial8250Driver(driverName) && !isValidSerial8250(systemLocation: priv.device))
438 continue;
439 priv.description = deviceDescription(dev: dev.data());
440 priv.manufacturer = deviceManufacturer(dev: dev.data());
441 priv.serialNumber = deviceSerialNumber(dev: dev.data());
442 priv.vendorIdentifier = deviceVendorIdentifier(dev: dev.data(), hasIdentifier&: priv.hasVendorIdentifier);
443 priv.productIdentifier = deviceProductIdentifier(dev: dev.data(), hasIdentifier&: priv.hasProductIdentifier);
444 } else {
445 if (!isRfcommDevice(portName: priv.portName)
446 && !isVirtualNullModemDevice(portName: priv.portName)
447 && !isGadgetDevice(portName: priv.portName)) {
448 continue;
449 }
450 }
451
452 serialPortInfoList.append(t: priv);
453 }
454
455 return serialPortInfoList;
456}
457
458QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
459{
460 bool ok;
461
462 QList<QSerialPortInfo> serialPortInfoList = availablePortsByUdev(ok);
463
464#ifdef Q_OS_LINUX
465 if (!ok)
466 serialPortInfoList = availablePortsBySysfs(ok);
467#endif
468
469 if (!ok)
470 serialPortInfoList = availablePortsByFiltersOfDevices(ok);
471
472 return serialPortInfoList;
473}
474
475#if QT_DEPRECATED_SINCE(5, 6)
476bool QSerialPortInfo::isBusy() const
477{
478 QString lockFilePath = serialPortLockFilePath(portName: portName());
479 if (lockFilePath.isEmpty())
480 return false;
481
482 QFile reader(lockFilePath);
483 if (!reader.open(flags: QIODevice::ReadOnly))
484 return false;
485
486 QByteArray pidLine = reader.readLine();
487 pidLine.chop(n: 1);
488 if (pidLine.isEmpty())
489 return false;
490
491 qint64 pid = pidLine.toLongLong();
492
493 if (pid && (::kill(pid: pid, sig: 0) == -1) && (errno == ESRCH))
494 return false; // PID doesn't exist anymore
495
496 return true;
497}
498#endif // QT_DEPRECATED_SINCE(5, 6)
499
500#if QT_DEPRECATED_SINCE(5, 2)
501bool QSerialPortInfo::isValid() const
502{
503 QFile f(systemLocation());
504 return f.exists();
505}
506#endif // QT_DEPRECATED_SINCE(5, 2)
507
508QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source)
509{
510 return (source.startsWith(c: QLatin1Char('/'))
511 || source.startsWith(s: QLatin1String("./"))
512 || source.startsWith(s: QLatin1String("../")))
513 ? source : (QLatin1String("/dev/") + source);
514}
515
516QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source)
517{
518 return source.startsWith(s: QLatin1String("/dev/"))
519 ? source.mid(position: 5) : source;
520}
521
522QT_END_NAMESPACE
523

source code of qtserialport/src/serialport/qserialportinfo_unix.cpp