summaryrefslogblamecommitdiffstats
path: root/src/vsession.cpp
blob: b86f3147a2a43cacd9841435511487200b19e753 (plain) (tree)
1
2
3
4
5
6
7
8
9

                   




                       
                  
                      
                
                      
                                              

                             


                                                  

                     
                               
                         
                        
 

                         


                                              






































                                                                                   




                                                                                  






                                                            
                                                 



                                                  
                                   




                                        
                              
                                                       
                                                               
                                                                                                    

                                                       
                              
                           

                          
                                                                                
                                                  
                               
                            
     
                                             
                                     
                                               
                        










                                                        



                                                                  
     

                                                      
 

                                 
                           



                                                         



                                                                         


                                        
                                                                                               
            
                                                          
     




                                                                 
                                                                     

 
                                           
                           


                               

                                                                                   
                         
                                                                    

                                          

 
                                                                       
                                                 

                                                                                  
                                                                                                                                        
                               

                                                                                     

                                 

                 

                             


                

 



                                                              
                                 
                                                          
                             
                                      
                                                                                                          
                     
     

                                   
                                                                                                                  
                           

                                                                                        
                             
                                                                                                                                                      


                                
         
     





                                 
                                                                                       

 







                                                                                 


                                
                                                                
                                                               

                    
                                                                      


                     

 
                            



                                                                           
                      
                                                                       
     
 
                                
                                                                 



                                                                                             

     
                                                              



                                                
                                  
                                                                                       
                          

                                                                      

                     

                                                                            
                                 
                    
 
                                                                                                                                                  
                                                                             

                                                                                        








                             
                                                                        
                                
                     
                           
 
                         
                                                                                 

                              
                                                                                   
                         
                                                                    
                                   
                                           
                              


                                  

         
                       

 
                                 
                                 

                                                                                                                                    
                                                                                                                                    
                          

 
                                           
                                              

                                         

 











                                                      
#include "config.h"

#include <QtXml>
#include <QDir>
#include <QApplication>
#include <QProcess>
#include <QDate>
#include <QThread>
#include <QStringList>
#include <QIcon>
#include <QMessageBox>
#include <QHostInfo> // available since Qt 4.7
#include <QList>
#include <QRegularExpression>
#include <unistd.h> // for getuid()
#include <sys/types.h> // for getuid(), getpwuid()
#include <pwd.h> // 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 &regexp)
        : icon(icon), expr(regexp) {}
};

// It's first match wins, so put a check for windows95 before windows9
static const QList<IconMap> 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<QString> 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<QString>& 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<int, QProcess::ExitStatus>::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<Session*> VSession::loadFromXmlDocument(const QDomDocument& doc) {
    QList<Session*> 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;
}