#include "config.h" #include #include #include #include #include #include #include #include #include #include // available since Qt 4.7 #include #include #include // for getuid() #include // for getuid(), getpwuid() #include // for getpwuid() #include "globals.h" #include "vsession.h" #include "sessionsiconholder.h" #include "userldapdata.h" #include "virtualizer.h" static QProcess _process; static const QString VMWARE("vmware"); static const QString VIRTUALBOX("virtualbox"); struct IconMap { QString icon; QRegularExpression expr; IconMap(const QString &icon, const QString ®exp) : icon(icon), expr(regexp) {} }; // It's first match wins, so put a check for windows95 before windows9 static const QList iconMapping( { IconMap("dos", "^(ms)?dos"), IconMap("win311", "^win(dows)?3($|1)"), IconMap("win9x", "^win(dows)?(95|98|me|nt)"), IconMap("winxp", "^win(dows)?(xp|2003|2k3)"), IconMap("win2000", "^win(dows)?(2000|2k)"), // After win2k3! IconMap("win7", "^win(dows)?7"), IconMap("win8", "^win(dows)?8"), IconMap("win10", "^win(dows)?(9|10)"), // After win95! IconMap("osx", "^darwin"), IconMap("suse", "^opensuse"), }); // These match via .startsWith and need to exist as a resource with this exact name static const QStringList directOsNames( { "amiga", "atari", "beos", "debian", "fedora", "freebsd", "gentoo", "macos", "opensolaris", "os2", "osx", "redhat", "riscos", "solaris", "suse", "ubuntu", }); static const QRegularExpression cleanNameRegex("[^a-z0-9]"); bool VSession::init(const QDomElement& xml) { QDomElement settingsNode = this->doc_.createElement("settings"); this->doc_.appendChild(settingsNode); eintrag_ = this->doc_.importNode(xml, true).toElement(); return !settingsNode.appendChild(eintrag_).isNull() && !settingsNode.isNull(); } void VSession::addNodeWithAttribute(const QString& nodeName, const QString& value, const QString& attribute, bool replace) { QDomElement node = eintrag_.firstChildElement(nodeName); if (replace == false || node.isNull()) { // create a new node node = this->doc_.createElement(nodeName); eintrag_.appendChild(node); } node.setAttribute(attribute, value); } QIcon VSession::icon() const { QString icon(getAttribute(QStringLiteral("icon"))); SessionsIconHolder *iconHolder = SessionsIconHolder::get(); if (icon.startsWith(QStringLiteral("http://")) || icon.startsWith(QStringLiteral("https://"))) { // try to load icon from url QIcon url_icon(iconHolder->getIcon(QUrl(icon))); if (!url_icon.isNull()) return url_icon; } if (!icon.isEmpty()) { // See if we have a resource with this name, or if it's an absolute path QIcon res_icon(iconHolder->getIcon(icon)); if (!res_icon.isNull()) return res_icon; } // Everything failed, try to guess the OS QString os(this->os().toLower()); os = os.replace(cleanNameRegex, QString()); if (!os.isEmpty()) { // Now try known good OS names via .startsWith() for (const QString& str : directOsNames) { if (os.startsWith(str)) return iconHolder->getIcon(str); } // Fuzzy matching via regex for (const IconMap& map : iconMapping) { if (map.expr.match(os).hasMatch()) return iconHolder->getIcon(map.icon); } // Running out of ideas... if (os.startsWith(QStringLiteral("win"))) return iconHolder->getIcon(QStringLiteral("windows")); if (os.contains(QStringLiteral("linux"))) return iconHolder->getIcon(QStringLiteral("linux")); } // Fallback to generic virtualizer icon (if found) return iconHolder->getIcon(virtualizer()); } QString VSession::toXml() const { QDomDocument doc(doc_); QDomNode xmlNode = doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.insertBefore(xmlNode, doc.firstChild()); QString image(this->getAttribute(QStringLiteral("image_name"))); QDomElement path = doc.firstChildElement(QStringLiteral("settings")) .firstChildElement(QStringLiteral("eintrag")) .appendChild(doc.createElement(QStringLiteral("image_path"))) .toElement(); if (QFileInfo(image).isRelative()) { // make path to image absolute path.setAttribute(QStringLiteral("param"), Config::get(Config::BASEDIR) + "/" + image); } else { path.setAttribute(QStringLiteral("param"), image); } return doc.toString(); } QString VSession::getAttribute(const QString &nodeName, const QString &attribute) const { return eintrag_.firstChildElement(nodeName).attribute(attribute); } QList VSession::keywords() const { return this->keywords_; } void VSession::readKeywords() { QDomNode keywordsNode = eintrag_.namedItem(QStringLiteral("keywords")); for (QDomElement el(keywordsNode.firstChildElement(QStringLiteral("keyword"))); !el.isNull(); el = el.nextSiblingElement(QStringLiteral("keyword"))) { this->keywords_.append(el.text()); } } bool VSession::containsKeywords(const QList& keywords) const { for (int j = 0; j < keywords.length(); ++j) { if (!this->shortDescription().contains(keywords[j], Qt::CaseInsensitive) && !this->description().contains(keywords[j], Qt::CaseInsensitive) && !this->getAttribute(QStringLiteral("creator"), QStringLiteral("param")).contains(keywords[j], Qt::CaseInsensitive)) { bool match = false; for (int i = 0; i < this->keywords().length(); ++i) { if (this->keywords()[i].contains(keywords[j], Qt::CaseInsensitive)) { match = true; break; } } if (!match) return false; } } return true; } QString VSession::getNodeText(const QString& nodeName) const { return this->doc_.namedItem(nodeName).toText().data(); } bool VSession::isActive() const { QString value(getAttribute(QStringLiteral("active"))); // Is disabled completely if (value.compare("false") == 0) { if (g_debugMode) qDebug() << "'" << shortDescription() << "' not active. Reason: active == false"; return false; } // Filter by LDAP data if (!UserLdapData::isEmpty()) { QDomElement el(eintrag_.namedItem(QStringLiteral("filters")).firstChildElement(QStringLiteral("filter"))); if (!el.isNull()) { for (; !el.isNull(); el = el.nextSiblingElement(QStringLiteral("filter"))) { if (el.attribute(QStringLiteral("type")) != QStringLiteral("LDAP")) continue; if (UserLdapData::isAllowed(el.firstChildElement(QStringLiteral("key")).text(), el.firstChildElement(QStringLiteral("value")).text())) return true; } return false; } } return true; } bool VSession::isLocked() const { // default to false return getAttribute(QStringLiteral("locked")).compare(QStringLiteral("true")) == 0; } QString VSession::checkCanRunInternal() const { if (getAttribute(QStringLiteral("image_name")).isEmpty()) return QObject::trUtf8("XML error: image_name is empty"); const Virtualizer* virt = Virtualizer::get(virtualizer()); if (!virt->isAvailable) return QObject::trUtf8("Virtualizer '%1' is not enabled.").arg(virt->id); // Seems OK return QString(); } int VSession::priority() const { int prio = getAttribute(QStringLiteral("priority")).toInt(); if (g_templateHandling == TEMPLATES_BUMP && isTemplate()) { prio -= 500; } if (g_forLocationHandling != LOCATION_IGNORE && isForLocation()) { prio -= 1000; } return prio; } bool VSession::run() const { if (_process.state() != QProcess::NotRunning) { qDebug() << "Cannot start vsession while old one is still running"; return false; } if (g_debugMode) { qDebug() << "Sarting session " << shortDescription() << " ..."; } if (g_noVtx && needsVtx()) { QMessageBox::warning(nullptr, QObject::trUtf8("Warning"), QObject::trUtf8("The selected session is based on a 64 bit operating system," " but this computer doesn't seem to support this (VT-x/AMD-V not" " supported by CPU, or disabled in BIOS). You will probably get an" " error message while the virtualizer is initializing.")); } QString command = getAttribute(QStringLiteral("command")); if (!command.isEmpty()) { return QProcess::startDetached(command); } // write xml to temporary file QTemporaryFile tmpfile(QDir::tempPath() + QStringLiteral("/vmchooser-XXXXXX.xml")); if (!tmpfile.open() || tmpfile.write(this->toXml().toUtf8()) == -1) { qDebug() << "Error writing xml to file" << tmpfile.fileName(); return false; } // Docs say we should call fileName() before closing to prevent deletion QString tmpFileName = tmpfile.fileName(); tmpfile.setAutoRemove(false); tmpfile.close(); QObject::connect(&_process, QOverload::of(&QProcess::finished), QApplication::instance(), &QCoreApplication::quit); _process.start(Config::get(Config::RUNSCRIPT), QStringList(tmpFileName)); _process.waitForStarted(10); if (_process.state() == QProcess::Starting || _process.state() == QProcess::Running) return true; else return false; } int VSession::type() const { return Session::VSESSION; } QList VSession::loadFromXmlDocument(const QDomDocument& doc) { QList sessionList; if (doc.isNull()) return sessionList; UserLdapData::init(); QDomElement settingsNode = doc.firstChildElement(QStringLiteral("settings")); if (settingsNode.isNull()) return sessionList; for (QDomElement el(settingsNode.firstChildElement(QStringLiteral("eintrag"))); !el.isNull(); el = el.nextSiblingElement(QStringLiteral("eintrag"))) { VSession* e = new VSession; if (e->init(el) && e->isActive()) { e->readKeywords(); sessionList.append(e); } else { delete e; } } return sessionList; } bool VSession::needsVtx() const { QString type = virtualizer(); return (type == VMWARE && os().endsWith(QStringLiteral("-64"))) || (type == VIRTUALBOX && os().endsWith(QStringLiteral("_64"))); // Vbox 6.x DOES support 32bit VMs without VT-x, but if // the config enables any feature that cannot work without VT-x, it will silently enable it and then fail to start (i.e. IOAPIC) // TODO: qemu-kvm, ... } QVariant VSession::foregroundRole() const { if ((!g_noVtx || !needsVtx()) && canRun()) return Session::foregroundRole(); return QColor(180, 180, 180); } bool VSession::operator<(const Session& other) const { int p0 = this->priority(); int p1 = other.priority(); if (p0 < p1) return true; if (p0 == p1) { QString d0 = this->shortDescription(); QString d1 = other.shortDescription(); return d0.localeAwareCompare(d1) < 0; } return false; }