blob: f6d6ea67be057fc33c9d0df584ffd6cee027cfc5 [file] [log] [blame]
/****************************************************************************
**
** 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