Revision control

Copy as Markdown

// Copyright (C) 2011-2012 Denis Shienkov <denis.shienkov@gmail.com>
// Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com>
// Copyright (C) 2012 Laszlo Papp <lpapp@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qserialportinfo.h"
#include "qserialportinfo_p.h"
#include "qserialport_p.h"
#include <QtCore/qlockfile.h>
#include <QtCore/qfile.h>
#include <QtCore/qdir.h>
#include <QtCore/qtenvironmentvariables.h>
#include <private/qcore_unix_p.h>
#include <memory>
#include <errno.h>
#include <sys/types.h> // kill
#include <signal.h> // kill
#include "qtudev_p.h"
QT_BEGIN_NAMESPACE
constexpr char SkipUdevEnvVarName[] = "QT_SERIALPORT_SKIP_UDEV_LOOKUP";
static QStringList filteredDeviceFilePaths()
{
static const QStringList deviceFileNameFilterList = QStringList()
#ifdef Q_OS_LINUX
<< QStringLiteral("ttyS*") // Standard UART 8250 and etc.
<< QStringLiteral("ttyO*") // OMAP UART 8250 and etc.
<< QStringLiteral("ttyUSB*") // Usb/serial converters PL2303 and etc.
<< QStringLiteral("ttyACM*") // CDC_ACM converters (i.e. Mobile Phones).
<< QStringLiteral("ttyGS*") // Gadget serial device (i.e. Mobile Phones with gadget serial driver).
<< QStringLiteral("ttyMI*") // MOXA pci/serial converters.
<< QStringLiteral("ttymxc*") // Motorola IMX serial ports (i.e. Freescale i.MX).
<< QStringLiteral("ttyAMA*") // AMBA serial device for embedded platform on ARM (i.e. Raspberry Pi).
<< QStringLiteral("ttyTHS*") // Serial device for embedded platform on ARM (i.e. Tegra Jetson TK1).
<< QStringLiteral("rfcomm*") // Bluetooth serial device.
<< QStringLiteral("ircomm*") // IrDA serial device.
<< QStringLiteral("tnt*"); // Virtual tty0tty serial device.
#elif defined(Q_OS_FREEBSD)
<< QStringLiteral("cu*");
#elif defined(Q_OS_QNX)
<< QStringLiteral("ser*");
#else
;
#endif
QStringList result;
QDir deviceDir(QStringLiteral("/dev"));
if (deviceDir.exists()) {
deviceDir.setNameFilters(deviceFileNameFilterList);
deviceDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks);
QStringList deviceFilePaths;
const auto deviceFileInfos = deviceDir.entryInfoList();
for (const QFileInfo &deviceFileInfo : deviceFileInfos) {
const QString deviceAbsoluteFilePath = deviceFileInfo.absoluteFilePath();
#ifdef Q_OS_FREEBSD
// it is a quick workaround to skip the non-serial devices
if (deviceAbsoluteFilePath.endsWith(QLatin1String(".init"))
|| deviceAbsoluteFilePath.endsWith(QLatin1String(".lock"))) {
continue;
}
#endif
if (!deviceFilePaths.contains(deviceAbsoluteFilePath)) {
deviceFilePaths.append(deviceAbsoluteFilePath);
result.append(deviceAbsoluteFilePath);
}
}
}
return result;
}
QList<QSerialPortInfo> availablePortsByFiltersOfDevices(bool &ok)
{
QList<QSerialPortInfo> serialPortInfoList;
const auto deviceFilePaths = filteredDeviceFilePaths();
for (const QString &deviceFilePath : deviceFilePaths) {
QSerialPortInfoPrivate priv;
priv.device = deviceFilePath;
priv.portName = QSerialPortInfoPrivate::portNameFromSystemLocation(deviceFilePath);
serialPortInfoList.append(priv);
}
ok = true;
return serialPortInfoList;
}
static bool isSerial8250Driver(const QString &driverName)
{
return (driverName == QLatin1String("serial8250"));
}
static bool isValidSerial8250(const QString &systemLocation)
{
#ifdef Q_OS_LINUX
const mode_t flags = O_RDWR | O_NONBLOCK | O_NOCTTY;
const int fd = qt_safe_open(systemLocation.toLocal8Bit().constData(), flags);
if (fd != -1) {
struct serial_struct serinfo;
const int retval = ::ioctl(fd, TIOCGSERIAL, &serinfo);
qt_safe_close(fd);
if (retval != -1 && serinfo.type != PORT_UNKNOWN)
return true;
}
#else
Q_UNUSED(systemLocation);
#endif
return false;
}
static bool isRfcommDevice(QStringView portName)
{
if (!portName.startsWith(QLatin1String("rfcomm")))
return false;
bool ok;
const int portNumber = portName.mid(6).toInt(&ok);
if (!ok || (portNumber < 0) || (portNumber > 255))
return false;
return true;
}
// provided by the tty0tty driver
static bool isVirtualNullModemDevice(const QString &portName)
{
return portName.startsWith(QLatin1String("tnt"));
}
// provided by the g_serial driver
static bool isGadgetDevice(const QString &portName)
{
return portName.startsWith(QLatin1String("ttyGS"));
}
static QString ueventProperty(const QDir &targetDir, const QByteArray &pattern)
{
QFile f(QFileInfo(targetDir, QStringLiteral("uevent")).absoluteFilePath());
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return QString();
const QByteArray content = f.readAll();
const qsizetype firstbound = content.indexOf(pattern);
if (firstbound == -1)
return QString();
qsizetype lastbound = content.indexOf('\n', firstbound);
if (lastbound == -1)
lastbound = content.size();
return QString::fromLatin1(
content.mid(firstbound + pattern.size(),
lastbound - firstbound - pattern.size()))
.simplified();
}
static QString deviceName(const QDir &targetDir)
{
return ueventProperty(targetDir, "DEVNAME=");
}
static QString deviceDriver(const QDir &targetDir)
{
const QDir deviceDir(targetDir.absolutePath() + QLatin1String("/device"));
return ueventProperty(deviceDir, "DRIVER=");
}
static QString deviceProperty(const QString &targetFilePath)
{
QFile f(targetFilePath);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
return QString();
return QString::fromLatin1(f.readAll()).simplified();
}
static QString deviceDescription(const QDir &targetDir)
{
return deviceProperty(QFileInfo(targetDir, QStringLiteral("product")).absoluteFilePath());
}
static QString deviceManufacturer(const QDir &targetDir)
{
return deviceProperty(QFileInfo(targetDir, QStringLiteral("manufacturer")).absoluteFilePath());
}
static quint16 deviceProductIdentifier(const QDir &targetDir, bool &hasIdentifier)
{
QString result = deviceProperty(QFileInfo(targetDir, QStringLiteral("idProduct")).absoluteFilePath());
if (result.isEmpty())
result = deviceProperty(QFileInfo(targetDir, QStringLiteral("device")).absoluteFilePath());
return result.toInt(&hasIdentifier, 16);
}
static quint16 deviceVendorIdentifier(const QDir &targetDir, bool &hasIdentifier)
{
QString result = deviceProperty(QFileInfo(targetDir, QStringLiteral("idVendor")).absoluteFilePath());
if (result.isEmpty())
result = deviceProperty(QFileInfo(targetDir, QStringLiteral("vendor")).absoluteFilePath());
return result.toInt(&hasIdentifier, 16);
}
static QString deviceSerialNumber(const QDir &targetDir)
{
return deviceProperty(QFileInfo(targetDir, QStringLiteral("serial")).absoluteFilePath());
}
QList<QSerialPortInfo> availablePortsBySysfs(bool &ok)
{
QDir ttySysClassDir(QStringLiteral("/sys/class/tty"));
if (!(ttySysClassDir.exists() && ttySysClassDir.isReadable())) {
ok = false;
return QList<QSerialPortInfo>();
}
QList<QSerialPortInfo> serialPortInfoList;
ttySysClassDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
const auto fileInfos = ttySysClassDir.entryInfoList();
for (const QFileInfo &fileInfo : fileInfos) {
if (!fileInfo.isSymLink())
continue;
QDir targetDir(fileInfo.symLinkTarget());
QSerialPortInfoPrivate priv;
priv.portName = deviceName(targetDir);
if (priv.portName.isEmpty())
continue;
const QString driverName = deviceDriver(targetDir);
if (driverName.isEmpty()) {
if (!isRfcommDevice(priv.portName)
&& !isVirtualNullModemDevice(priv.portName)
&& !isGadgetDevice(priv.portName)) {
continue;
}
}
priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(priv.portName);
if (isSerial8250Driver(driverName) && !isValidSerial8250(priv.device))
continue;
do {
if (priv.description.isEmpty())
priv.description = deviceDescription(targetDir);
if (priv.manufacturer.isEmpty())
priv.manufacturer = deviceManufacturer(targetDir);
if (priv.serialNumber.isEmpty())
priv.serialNumber = deviceSerialNumber(targetDir);
if (!priv.hasVendorIdentifier)
priv.vendorIdentifier = deviceVendorIdentifier(targetDir, priv.hasVendorIdentifier);
if (!priv.hasProductIdentifier)
priv.productIdentifier = deviceProductIdentifier(targetDir, priv.hasProductIdentifier);
if (!priv.description.isEmpty()
|| !priv.manufacturer.isEmpty()
|| !priv.serialNumber.isEmpty()
|| priv.hasVendorIdentifier
|| priv.hasProductIdentifier) {
break;
}
} while (targetDir.cdUp());
serialPortInfoList.append(priv);
}
ok = true;
return serialPortInfoList;
}
struct udev_deleter {
void operator()(struct ::udev *pointer) const
{
::udev_unref(pointer);
}
void operator()(struct ::udev_enumerate *pointer) const
{
::udev_enumerate_unref(pointer);
}
void operator()(struct ::udev_device *pointer) const
{
::udev_device_unref(pointer);
}
};
template <typename T>
using udev_ptr = std::unique_ptr<T, udev_deleter>;
#ifndef LINK_LIBUDEV
Q_GLOBAL_STATIC(QLibrary, udevLibrary)
#endif
static QString deviceProperty(struct ::udev_device *dev, const char *name)
{
return QString::fromLatin1(::udev_device_get_property_value(dev, name));
}
static QString deviceDriver(struct ::udev_device *dev)
{
return QString::fromLatin1(::udev_device_get_driver(dev));
}
static QString deviceDescription(struct ::udev_device *dev)
{
return deviceProperty(dev, "ID_MODEL").replace(QLatin1Char('_'), QLatin1Char(' '));
}
static QString deviceManufacturer(struct ::udev_device *dev)
{
return deviceProperty(dev, "ID_VENDOR").replace(QLatin1Char('_'), QLatin1Char(' '));
}
static quint16 deviceProductIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
{
return deviceProperty(dev, "ID_MODEL_ID").toInt(&hasIdentifier, 16);
}
static quint16 deviceVendorIdentifier(struct ::udev_device *dev, bool &hasIdentifier)
{
return deviceProperty(dev, "ID_VENDOR_ID").toInt(&hasIdentifier, 16);
}
static QString deviceSerialNumber(struct ::udev_device *dev)
{
return deviceProperty(dev,"ID_SERIAL_SHORT");
}
static QString deviceName(struct ::udev_device *dev)
{
return QString::fromLatin1(::udev_device_get_sysname(dev));
}
static QString deviceLocation(struct ::udev_device *dev)
{
return QString::fromLatin1(::udev_device_get_devnode(dev));
}
QList<QSerialPortInfo> availablePortsByUdev(bool &ok)
{
ok = false;
#ifndef LINK_LIBUDEV
static bool symbolsResolved = resolveSymbols(udevLibrary());
if (!symbolsResolved)
return QList<QSerialPortInfo>();
#endif
const udev_ptr<struct ::udev> udev(::udev_new());
if (!udev)
return QList<QSerialPortInfo>();
const udev_ptr<udev_enumerate> enumerate(::udev_enumerate_new(udev.get()));
if (!enumerate)
return QList<QSerialPortInfo>();
::udev_enumerate_add_match_subsystem(enumerate.get(), "tty");
::udev_enumerate_scan_devices(enumerate.get());
udev_list_entry *devices = ::udev_enumerate_get_list_entry(enumerate.get());
QList<QSerialPortInfo> serialPortInfoList;
udev_list_entry *dev_list_entry;
udev_list_entry_foreach(dev_list_entry, devices) {
ok = true;
const udev_ptr<udev_device>
dev(::udev_device_new_from_syspath(
udev.get(), ::udev_list_entry_get_name(dev_list_entry)));
if (!dev)
continue;
QSerialPortInfoPrivate priv;
priv.device = deviceLocation(dev.get());
priv.portName = deviceName(dev.get());
udev_device *parentdev = ::udev_device_get_parent(dev.get());
if (parentdev) {
const QString driverName = deviceDriver(parentdev);
if (isSerial8250Driver(driverName) && !isValidSerial8250(priv.device))
continue;
priv.description = deviceDescription(dev.get());
priv.manufacturer = deviceManufacturer(dev.get());
priv.serialNumber = deviceSerialNumber(dev.get());
priv.vendorIdentifier = deviceVendorIdentifier(dev.get(), priv.hasVendorIdentifier);
priv.productIdentifier = deviceProductIdentifier(dev.get(), priv.hasProductIdentifier);
} else {
if (!isRfcommDevice(priv.portName)
&& !isVirtualNullModemDevice(priv.portName)
&& !isGadgetDevice(priv.portName)) {
continue;
}
}
serialPortInfoList.append(priv);
}
return serialPortInfoList;
}
QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
{
bool ok = false;
QList<QSerialPortInfo> serialPortInfoList;
const bool skipUdevLookup = qEnvironmentVariableIsSet(SkipUdevEnvVarName);
if (!skipUdevLookup)
serialPortInfoList = availablePortsByUdev(ok);
#ifdef Q_OS_LINUX
if (!ok)
serialPortInfoList = availablePortsBySysfs(ok);
#endif
if (!ok)
serialPortInfoList = availablePortsByFiltersOfDevices(ok);
return serialPortInfoList;
}
QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source)
{
return (source.startsWith(QLatin1Char('/'))
|| source.startsWith(QLatin1String("./"))
|| source.startsWith(QLatin1String("../")))
? source : (QLatin1String("/dev/") + source);
}
QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source)
{
return source.startsWith(QLatin1String("/dev/"))
? source.mid(5) : source;
}
QT_END_NAMESPACE