| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 Denis Shienkov <denis.shienkov@gmail.com> |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtSerialPort module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qserialportinfo.h" |
| #include "qserialportinfo_p.h" |
| #include "qserialport_p.h" |
| |
| #include <QtCore/qdatastream.h> |
| #include <QtCore/qvector.h> |
| #include <QtCore/qdir.h> |
| |
| #include <errno.h> |
| #include <sys/types.h> // kill |
| #include <signal.h> // kill |
| |
| #include <sys/sysctl.h> // sysctl, sysctlnametomib |
| |
| QT_BEGIN_NAMESPACE |
| |
| static QString deviceProperty(const QString &source, const QByteArray &pattern) |
| { |
| const int firstbound = source.indexOf(QLatin1String(pattern)); |
| if (firstbound == -1) |
| return QString(); |
| const int lastbound = source.indexOf(QLatin1Char(' '), firstbound); |
| return source.mid(firstbound + pattern.size(), lastbound - firstbound - pattern.size()); |
| } |
| |
| static QString deviceName(const QString &pnpinfo) |
| { |
| return deviceProperty(pnpinfo, "ttyname="); |
| } |
| |
| static QString deviceCount(const QString &pnpinfo) |
| { |
| return deviceProperty(pnpinfo, "ttyports="); |
| } |
| |
| static quint16 deviceProductIdentifier(const QString &pnpinfo, bool &hasIdentifier) |
| { |
| QString result = deviceProperty(pnpinfo, "product="); |
| return result.toInt(&hasIdentifier, 16); |
| } |
| |
| static quint16 deviceVendorIdentifier(const QString &pnpinfo, bool &hasIdentifier) |
| { |
| QString result = deviceProperty(pnpinfo, "vendor="); |
| return result.toInt(&hasIdentifier, 16); |
| } |
| |
| static QString deviceSerialNumber(const QString &pnpinfo) |
| { |
| QString serialNumber = deviceProperty(pnpinfo, "sernum="); |
| serialNumber.remove(QLatin1Char('"')); |
| return serialNumber; |
| } |
| |
| // A 'desc' string contains the both description and manufacturer |
| // properties, which are not possible to extract from the source |
| // string. Besides, this string can contains an other information, |
| // which should be excluded from the result. |
| static QString deviceDescriptionAndManufacturer(const QString &desc) |
| { |
| const int classindex = desc.indexOf(QLatin1String(", class ")); |
| if (classindex == -1) |
| return desc; |
| return desc.mid(0, classindex); |
| } |
| |
| struct NodeInfo |
| { |
| QString name; |
| QString value; |
| }; |
| |
| static QVector<int> mibFromName(const QString &name) |
| { |
| size_t mibsize = 0; |
| if (::sysctlnametomib(name.toLocal8Bit().constData(), nullptr, &mibsize) < 0 |
| || mibsize == 0) { |
| return QVector<int>(); |
| } |
| QVector<int> mib(mibsize); |
| if (::sysctlnametomib(name.toLocal8Bit().constData(), &mib[0], &mibsize) < 0) |
| return QVector<int>(); |
| |
| return mib; |
| } |
| |
| static QVector<int> nextOid(const QVector<int> &previousOid) |
| { |
| QVector<int> mib; |
| mib.append(0); // Magic undocumented code (CTL_UNSPEC ?) |
| mib.append(2); // Magic undocumented code |
| for (int code : previousOid) |
| mib.append(code); |
| |
| size_t requiredLength = 0; |
| if (::sysctl(&mib[0], mib.count(), nullptr, &requiredLength, nullptr, 0) < 0) |
| return QVector<int>(); |
| const size_t oidLength = requiredLength / sizeof(int); |
| QVector<int> oid(oidLength, 0); |
| if (::sysctl(&mib[0], mib.count(), &oid[0], &requiredLength, nullptr, 0) < 0) |
| return QVector<int>(); |
| |
| if (previousOid.first() != oid.first()) |
| return QVector<int>(); |
| |
| return oid; |
| } |
| |
| static NodeInfo nodeForOid(const QVector<int> &oid) |
| { |
| QVector<int> mib; |
| mib.append(0); // Magic undocumented code (CTL_UNSPEC ?) |
| mib.append(1); // Magic undocumented code |
| for (int code : oid) |
| mib.append(code); |
| |
| // query node name |
| size_t requiredLength = 0; |
| if (::sysctl(&mib[0], mib.count(), nullptr, &requiredLength, nullptr, 0) < 0) |
| return NodeInfo(); |
| QByteArray name(requiredLength, 0); |
| if (::sysctl(&mib[0], mib.count(), name.data(), &requiredLength, nullptr, 0) < 0) |
| return NodeInfo(); |
| |
| // query node value |
| requiredLength = 0; |
| if (::sysctl(&oid[0], oid.count(), nullptr, &requiredLength, nullptr, 0) < 0) |
| return NodeInfo(); |
| QByteArray value(requiredLength, 0); |
| if (::sysctl(&oid[0], oid.count(), value.data(), &requiredLength, nullptr, 0) < 0) |
| return NodeInfo(); |
| |
| // query value format |
| mib[1] = 4; // Magic undocumented code |
| requiredLength = 0; |
| if (::sysctl(&mib[0], mib.count(), nullptr, &requiredLength, nullptr, 0) < 0) |
| return NodeInfo(); |
| QByteArray buf(requiredLength, 0); |
| if (::sysctl(&mib[0], mib.count(), buf.data(), &requiredLength, nullptr, 0) < 0) |
| return NodeInfo(); |
| |
| QDataStream in(buf); |
| in.setByteOrder(QDataStream::LittleEndian); |
| quint32 kind = 0; |
| qint8 format = 0; |
| in >> kind >> format; |
| |
| NodeInfo result; |
| |
| // we need only the string-type value |
| if (format == 'A') { |
| result.name = QString::fromLocal8Bit(name.constData()); |
| result.value = QString::fromLocal8Bit(value.constData()); |
| } |
| |
| return result; |
| } |
| |
| static QList<NodeInfo> enumerateDesiredNodes(const QVector<int> &mib) |
| { |
| QList<NodeInfo> nodes; |
| |
| QVector<int> oid = mib; |
| |
| for (;;) { |
| const QVector<int> nextoid = nextOid(oid); |
| if (nextoid.isEmpty()) |
| break; |
| |
| const NodeInfo node = nodeForOid(nextoid); |
| if (!node.name.isEmpty()) { |
| if (node.name.endsWith(QLatin1String("\%desc")) |
| || node.name.endsWith(QLatin1String("\%pnpinfo"))) { |
| nodes.append(node); |
| } |
| } |
| |
| oid = nextoid; |
| } |
| |
| return nodes; |
| } |
| |
| QList<QSerialPortInfo> QSerialPortInfo::availablePorts() |
| { |
| const QVector<int> mib = mibFromName(QLatin1String("dev")); |
| if (mib.isEmpty()) |
| return QList<QSerialPortInfo>(); |
| |
| const QList<NodeInfo> nodes = enumerateDesiredNodes(mib); |
| if (nodes.isEmpty()) |
| return QList<QSerialPortInfo>(); |
| |
| QDir deviceDir(QLatin1String("/dev")); |
| if (!(deviceDir.exists() && deviceDir.isReadable())) |
| return QList<QSerialPortInfo>(); |
| |
| deviceDir.setNameFilters(QStringList() << QLatin1String("cua*") << QLatin1String("tty*")); |
| deviceDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks); |
| |
| QList<QSerialPortInfo> cuaCandidates; |
| QList<QSerialPortInfo> ttyCandidates; |
| |
| const auto portNames = deviceDir.entryList(); |
| for (const QString &portName : portNames) { |
| if (portName.endsWith(QLatin1String(".init")) |
| || portName.endsWith(QLatin1String(".lock"))) { |
| continue; |
| } |
| |
| QSerialPortInfoPrivate priv; |
| priv.portName = portName; |
| priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(portName); |
| |
| for (const NodeInfo &node : nodes) { |
| const int pnpinfoindex = node.name.indexOf(QLatin1String("\%pnpinfo")); |
| if (pnpinfoindex == -1) |
| continue; |
| |
| if (node.value.isEmpty()) |
| continue; |
| |
| QString ttyname = deviceName(node.value); |
| if (ttyname.isEmpty()) |
| continue; |
| |
| const QString ttyportscount = deviceCount(node.value); |
| if (ttyportscount.isEmpty()) |
| continue; |
| |
| const int count = ttyportscount.toInt(); |
| if (count == 0) |
| continue; |
| if (count > 1) { |
| bool matched = false; |
| for (int i = 0; i < count; ++i) { |
| const QString ends = QString(QLatin1String("%1.%2")).arg(ttyname).arg(i); |
| if (portName.endsWith(ends)) { |
| matched = true; |
| break; |
| } |
| } |
| |
| if (!matched) |
| continue; |
| } else { |
| if (!portName.endsWith(ttyname)) |
| continue; |
| } |
| |
| priv.serialNumber = deviceSerialNumber(node.value); |
| priv.vendorIdentifier = deviceVendorIdentifier(node.value, priv.hasVendorIdentifier); |
| priv.productIdentifier = deviceProductIdentifier(node.value, priv.hasProductIdentifier); |
| |
| const QString nodebase = node.name.mid(0, pnpinfoindex); |
| const QString descnode = QString(QLatin1String("%1\%desc")).arg(nodebase); |
| |
| // search for description and manufacturer properties |
| for (const NodeInfo &node : nodes) { |
| if (node.name != descnode) |
| continue; |
| |
| if (node.value.isEmpty()) |
| continue; |
| |
| // We can not separate the description and the manufacturer |
| // properties from the node value, so lets just duplicate it. |
| priv.description = deviceDescriptionAndManufacturer(node.value); |
| priv.manufacturer = priv.description; |
| break; |
| } |
| |
| break; |
| } |
| |
| if (portName.startsWith(QLatin1String("cua"))) |
| cuaCandidates.append(priv); |
| else if (portName.startsWith(QLatin1String("tty"))) |
| ttyCandidates.append(priv); |
| } |
| |
| QList<QSerialPortInfo> serialPortInfoList; |
| |
| for (const QSerialPortInfo &cuaCandidate : qAsConst(cuaCandidates)) { |
| const QString cuaPortName = cuaCandidate.portName(); |
| const QString cuaToken = deviceProperty(cuaPortName, "cua"); |
| for (const QSerialPortInfo &ttyCandidate : qAsConst(ttyCandidates)) { |
| const QString ttyPortName = ttyCandidate.portName(); |
| const QString ttyToken = deviceProperty(ttyPortName, "tty"); |
| if (cuaToken != ttyToken) |
| continue; |
| |
| serialPortInfoList.append(cuaCandidate); |
| serialPortInfoList.append(ttyCandidate); |
| } |
| } |
| |
| return serialPortInfoList; |
| } |
| |
| bool QSerialPortInfo::isBusy() const |
| { |
| QString lockFilePath = serialPortLockFilePath(portName()); |
| if (lockFilePath.isEmpty()) |
| return false; |
| |
| QFile reader(lockFilePath); |
| if (!reader.open(QIODevice::ReadOnly)) |
| return false; |
| |
| QByteArray pidLine = reader.readLine(); |
| pidLine.chop(1); |
| if (pidLine.isEmpty()) |
| return false; |
| |
| qint64 pid = pidLine.toLongLong(); |
| |
| if (pid && (::kill(pid, 0) == -1) && (errno == ESRCH)) |
| return false; // PID doesn't exist anymore |
| |
| return true; |
| } |
| |
| #if QT_DEPRECATED_SINCE(5, 2) |
| bool QSerialPortInfo::isValid() const |
| { |
| QFile f(systemLocation()); |
| return f.exists(); |
| } |
| #endif // QT_DEPRECATED_SINCE(5, 2) |
| |
| 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 |