#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" static QProcess _process; 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("icon")); SessionsIconHolder *iconHolder = SessionsIconHolder::get(); if (icon.startsWith("http://") || icon.startsWith("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(getAttribute("os", "param").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("win")) return iconHolder->getIcon("windows"); if (os.contains("linux")) return iconHolder->getIcon("linux"); } // Fallback to generic virtualizer icon if (imgtype() == VMWARE) return iconHolder->getIcon("vmware"); if (imgtype() == VBOX) return iconHolder->getIcon("virtualbox"); return QIcon(); } 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("image_name")); QDomElement path = doc.firstChildElement("settings") .firstChildElement("eintrag") .appendChild(doc.createElement("image_path")) .toElement(); if (QFileInfo(image).isRelative()) { // make path to image absolute path.setAttribute("param", Config::get(Config::BASEDIR) + "/" + image); } else { path.setAttribute("param", image); } return doc.toString(); } QString VSession::getAttribute(const QString &nodeName, const QString &attribute) const { QDomNode n = eintrag_.firstChildElement(nodeName); return eintrag_.firstChildElement(nodeName).attribute(attribute); } QList VSession::keywords() const { return this->keywords_; } void VSession::readKeywords() { QDomNode keywordsNode = eintrag_.namedItem("keywords"); for (QDomElement el(keywordsNode.firstChildElement("keyword")); !el.isNull(); el = el.nextSiblingElement("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("creator", "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(); } ImgType VSession::imgtype() const { QString s(getAttribute("virtualmachine")); if (s.compare("vmware") == 0) { return VMWARE; } else if (s.compare("virtualbox") == 0 || s.compare("vbox") == 0) { return VBOX; } else { return OTHER; } } bool VSession::isActive() const { QString value(getAttribute("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("filters").firstChildElement("filter")); if (!el.isNull()) { for (; !el.isNull(); el = el.nextSiblingElement("filter")) { if (el.attribute("type") != "LDAP") continue; if (UserLdapData::isAllowed(el.firstChildElement("key").text(), el.firstChildElement("value").text())) return true; } return false; } } return true; } bool VSession::isLocked() const { // default to false return getAttribute("locked").compare("true") == 0; } bool VSession::isValid() const { return !getAttribute("image_name").isEmpty(); } int VSession::priority() const { int prio = getAttribute("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 " << this->getAttribute("short_description", "param") << " ..."; } 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("command"); if (!command.isEmpty()) { return QProcess::startDetached(command); } // write xml to temporary file QTemporaryFile tmpfile(QDir::tempPath() + "/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::readXmlFile(const QString& filepath) { QList sessionList; QDomDocument doc; QFile file(filepath); QFile backup_file(TEMP_PATH_XML_LIST); if (!file.open(QIODevice::ReadOnly)) { if (g_debugMode) { qDebug() << "Cannot read file: " << file.fileName(); } return sessionList; } if (!doc.setContent(&file)) { if (g_debugMode) { qDebug() << "XML file not valid: " << file.fileName(); } file.close(); // try to use backup file if (!backup_file.open(QIODevice::ReadOnly)) { if (g_debugMode) { qDebug() << "Cannot read backup file " << TEMP_PATH_XML_LIST << " either"; } return sessionList; } if (!doc.setContent(&backup_file)) { if (g_debugMode) { qDebug() << "XML file not valid: " << backup_file.fileName(); } backup_file.close(); return sessionList; } if (g_debugMode) { qDebug() << "Used backup file " << TEMP_PATH_XML_LIST; } backup_file.close(); } else { file.close(); // file is valid --> create backup file QFile::remove(TEMP_PATH_XML_LIST); QFile::rename(filepath, TEMP_PATH_XML_LIST); if (!QFile::setPermissions(TEMP_PATH_XML_LIST, QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) { if (g_debugMode) { qDebug() << "Could not change permissions of file: " << TEMP_PATH_NEWS; } } } UserLdapData::init(); QDomElement settingsNode = doc.firstChildElement("settings"); for (QDomElement el(settingsNode.firstChildElement("eintrag")); !el.isNull(); el = el.nextSiblingElement("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 { ImgType type = imgtype(); return (type == VMWARE && getAttribute("os").endsWith("-64")) || (type == VBOX && getAttribute("os").endsWith("_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()) 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; }