#include "utils/SysInfo.h" #include #include #include #include #include "HyperionConfig.h" #include #include #include #include #include #include SysInfo* SysInfo::_instance = nullptr; SysInfo::SysInfo() : QObject() { SysInfo::QUnixOSVersion v; findUnixOsVersion(v); _sysinfo.kernelType = kernelType(); _sysinfo.kernelVersion = kernelVersion(); _sysinfo.architecture = currentCpuArchitecture(); _sysinfo.wordSize = QString::number(QSysInfo::WordSize); _sysinfo.productType = v.productType; _sysinfo.productVersion = v.productVersion; _sysinfo.prettyName = v.prettyName; _sysinfo.hostName = QHostInfo::localHostName(); _sysinfo.domainName = QHostInfo::localDomainName(); } SysInfo::~SysInfo() { } SysInfo::HyperionSysInfo SysInfo::get() { if ( SysInfo::_instance == nullptr ) SysInfo::_instance = new SysInfo(); return SysInfo::_instance->_sysinfo; } QString SysInfo::kernelType() { #if defined(Q_OS_WIN) return QStringLiteral("winnt"); #elif defined(Q_OS_UNIX) struct utsname u; if (uname(&u) == 0) return QString::fromLatin1(u.sysname).toLower(); #endif return QString(); } QString SysInfo::kernelVersion() { struct utsname u; if (uname(&u) == 0) return QString::fromLocal8Bit(u.release).toLower(); return QString(); } QString SysInfo::machineHostName() { #if defined(Q_OS_LINUX) // gethostname(3) on Linux just calls uname(2), so do it ourselves and avoid a memcpy struct utsname u; if (uname(&u) == 0) return QString::fromLocal8Bit(u.nodename); #else char hostName[512]; if (gethostname(hostName, sizeof(hostName)) == -1) return QString(); hostName[sizeof(hostName) - 1] = '\0'; return QString::fromLocal8Bit(hostName); #endif return QString(); } QString SysInfo::currentCpuArchitecture() { #if defined(Q_OS_UNIX) long ret = -1; struct utsname u; if (ret == -1) ret = uname(&u); // we could use detectUnixVersion() above, but we only need a field no other function does if (ret != -1) { // the use of QT_BUILD_INTERNAL here is simply to ensure all branches build // as we don't often build on some of the less common platforms # if defined(Q_PROCESSOR_ARM) if (strcmp(u.machine, "aarch64") == 0) return QStringLiteral("arm64"); if (strncmp(u.machine, "armv", 4) == 0) return QStringLiteral("arm"); # endif # if defined(Q_PROCESSOR_POWER) // harmonize "powerpc" and "ppc" to "power" if (strncmp(u.machine, "ppc", 3) == 0) return QLatin1String("power") + QLatin1String(u.machine + 3); if (strncmp(u.machine, "powerpc", 7) == 0) return QLatin1String("power") + QLatin1String(u.machine + 7); if (strcmp(u.machine, "Power Macintosh") == 0) return QLatin1String("power"); # endif # if defined(Q_PROCESSOR_X86) // harmonize all "i?86" to "i386" if (strlen(u.machine) == 4 && u.machine[0] == 'i' && u.machine[2] == '8' && u.machine[3] == '6') return QStringLiteral("i386"); if (strcmp(u.machine, "amd64") == 0) // Solaris return QStringLiteral("x86_64"); # endif return QString::fromLatin1(u.machine); } #endif return QString(); } bool SysInfo::findUnixOsVersion(SysInfo::QUnixOSVersion &v) { if (readEtcOsRelease(v)) return true; if (readEtcLsbRelease(v)) return true; #if defined(Q_OS_LINUX) if (readEtcRedHatRelease(v)) return true; if (readEtcDebianVersion(v)) return true; #endif return false; } QByteArray SysInfo::getEtcFileFirstLine(const char *fileName) { QByteArray buffer = getEtcFileContent(fileName); if (buffer.isEmpty()) return QByteArray(); const char *ptr = buffer.constData(); int eol = buffer.indexOf("\n"); return QByteArray(ptr, eol).trimmed(); } bool SysInfo::readEtcRedHatRelease(SysInfo::QUnixOSVersion &v) { // /etc/redhat-release analysed should be a one line file // the format of its content is // i.e. "Red Hat Enterprise Linux Workstation release 6.5 (Santiago)" QByteArray line = getEtcFileFirstLine("/etc/redhat-release"); if (line.isEmpty()) return false; v.prettyName = QString::fromLatin1(line); const char keyword[] = "release "; int releaseIndex = line.indexOf(keyword); v.productType = QString::fromLatin1(line.mid(0, releaseIndex)).remove(QLatin1Char(' ')); int spaceIndex = line.indexOf(' ', releaseIndex + strlen(keyword)); v.productVersion = QString::fromLatin1(line.mid(releaseIndex + strlen(keyword), spaceIndex > -1 ? spaceIndex - releaseIndex - int(strlen(keyword)) : -1)); return true; } bool SysInfo::readEtcDebianVersion(SysInfo::QUnixOSVersion &v) { // /etc/debian_version analysed should be a one line file // the format of its content is // i.e. "jessie/sid" QByteArray line = getEtcFileFirstLine("/etc/debian_version"); if (line.isEmpty()) return false; v.productType = QStringLiteral("Debian"); v.productVersion = QString::fromLatin1(line); return true; } QString SysInfo::unquote(const char *begin, const char *end) { if (*begin == '"') { Q_ASSERT(end[-1] == '"'); return QString::fromLatin1(begin + 1, end - begin - 2); } return QString::fromLatin1(begin, end - begin); } QByteArray SysInfo::getEtcFileContent(const char *filename) { // we're avoiding QFile here int fd = open(filename, O_RDONLY); if (fd == -1) return QByteArray(); struct stat sbuf; if (::fstat(fd, &sbuf) == -1) { close(fd); return QByteArray(); } QByteArray buffer(sbuf.st_size, Qt::Uninitialized); buffer.resize(read(fd, buffer.data(), sbuf.st_size)); close(fd); return buffer; } bool SysInfo::readEtcFile(SysInfo::QUnixOSVersion &v, const char *filename, const QByteArray &idKey, const QByteArray &versionKey, const QByteArray &prettyNameKey) { QByteArray buffer = getEtcFileContent(filename); if (buffer.isEmpty()) return false; const char *ptr = buffer.constData(); const char *end = buffer.constEnd(); const char *eol; QByteArray line; for ( ; ptr != end; ptr = eol + 1) { // find the end of the line after ptr eol = static_cast(memchr(ptr, '\n', end - ptr)); if (!eol) eol = end - 1; line.setRawData(ptr, eol - ptr); if (line.startsWith(idKey)) { ptr += idKey.length(); v.productType = unquote(ptr, eol); continue; } if (line.startsWith(prettyNameKey)) { ptr += prettyNameKey.length(); v.prettyName = unquote(ptr, eol); continue; } if (line.startsWith(versionKey)) { ptr += versionKey.length(); v.productVersion = unquote(ptr, eol); continue; } } return true; } bool SysInfo::readEtcOsRelease(SysInfo::QUnixOSVersion &v) { return readEtcFile(v, "/etc/os-release", QByteArrayLiteral("ID="), QByteArrayLiteral("VERSION_ID="), QByteArrayLiteral("PRETTY_NAME=")); } bool SysInfo::readEtcLsbRelease(SysInfo::QUnixOSVersion &v) { bool ok = readEtcFile(v, "/etc/lsb-release", QByteArrayLiteral("DISTRIB_ID="), QByteArrayLiteral("DISTRIB_RELEASE="), QByteArrayLiteral("DISTRIB_DESCRIPTION=")); if (ok && (v.prettyName.isEmpty() || v.prettyName == v.productType)) { // some distributions have redundant information for the pretty name, // so try /etc/-release // we're still avoiding QFile here QByteArray distrorelease = "/etc/" + v.productType.toLatin1().toLower() + "-release"; int fd = open(distrorelease, O_RDONLY); if (fd != -1) { struct stat sbuf; if (::fstat(fd, &sbuf) != -1 && sbuf.st_size > v.prettyName.length()) { // file apparently contains interesting information QByteArray buffer(sbuf.st_size, Qt::Uninitialized); buffer.resize(read(fd, buffer.data(), sbuf.st_size)); v.prettyName = QString::fromLatin1(buffer.trimmed()); } close(fd); } } // some distributions have a /etc/lsb-release file that does not provide the values // we are looking for, i.e. DISTRIB_ID, DISTRIB_RELEASE and DISTRIB_DESCRIPTION. // Assuming that neither DISTRIB_ID nor DISTRIB_RELEASE were found, or contained valid values, // returning false for readEtcLsbRelease will allow further /etc/-release parsing. return ok && !(v.productType.isEmpty() && v.productVersion.isEmpty()); }