diff options
Diffstat (limited to 'src/fbgui/fbgui.cpp')
-rw-r--r-- | src/fbgui/fbgui.cpp | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/src/fbgui/fbgui.cpp b/src/fbgui/fbgui.cpp new file mode 100644 index 0000000..22a2ac6 --- /dev/null +++ b/src/fbgui/fbgui.cpp @@ -0,0 +1,492 @@ +#include "fbgui.h" +#include "sysinfo.h" +#include "loggerengine.h" +#include "downloadmanager.h" +#include "javascriptinterface.h" + +#include <iostream> +#include <QThread> +#include <QtWebKit> +#include <QxtCore> + +QThread dmThread; +QString logFilePath(""); +QString ipConfigFilePath(""); +QString binPath(""); +QUrl baseURL(""); +QString downloadPath(""); +int updateInterval = -1; +QString fileToTriggerURL(""); +QString serialLocation(""); +QString sessionID(""); +int debugMode = -1; + +//------------------------------------------------------------------------------------------- +/** + * A constructor. + * + * The constructor of the fbgui class. It initializes the main objects + * which are needed while the program is running. + * The appearance of the webView is here also defined. + * + * @see JavascriptInterface + * @see DownloadManager + */ +fbgui::fbgui() { +} +fbgui::~fbgui() { + dmThread.quit(); +} + + + +/** + * init function. + */ +void fbgui::init() { + // start fbgui + qxtLog->debug() << "Initializing fbgui..."; + + setupLayout(); + createActions(); + + // initialize javascript interface + JavascriptInterface* jsi = new JavascriptInterface( + _webView->page()->mainFrame()); + QObject::connect(jsi, SIGNAL(quitFbgui()), this, SLOT(close())); + QObject::connect(jsi, SIGNAL(shutDownClient()), this, + SLOT(performShutDown())); + QObject::connect(_webView->page()->mainFrame(), SIGNAL( + javaScriptWindowObjectCleared()), jsi, SLOT(attachToDOM())); + + // initialize download manager + DownloadManager* dm = new DownloadManager(); + QObject::connect(dm, SIGNAL(downloadInfo(const QString&, const double&)), + jsi, SLOT(downloadInfo(const QString&, const double&))); + QObject::connect(dm, SIGNAL(notify(const QString&)), jsi, + SLOT(notify(const QString&))); + QObject::connect(jsi, SIGNAL(requestFile(const QString&)), dm, + SLOT(downloadFile(const QString&))); + QObject::connect( + dm, + SIGNAL(updateProgress(const int&, const double&, const QString&)), + jsi, + SLOT(updateProgressBar(const int&, const double&, const QString&))); + QObject::connect(dm, SIGNAL(downloadQueueEmpty()), jsi, SLOT( + callbackOnFinished())); + QObject::connect(dm, SIGNAL(downloadQueueEmpty()), this, SLOT(loadSystem())); + + // move download manager to its own thread + dm->moveToThread(&dmThread); + dmThread.start(); + + // show "waiting for internet" page until triggered. + if (debugMode > -1) { + _webView->load(QUrl("qrc:/html/preload-debug.html")); + } else { + _webView->load(QUrl("qrc:/html/preload.html")); + } + + // watcher is not needed anymore since we guarantee internet connection with the networkDiscovery. + // start watching for fileToTriggerURL + //watchForTrigger(); + loadURL(); + + // set properties + setWindowTitle("fbgui"); + setAttribute(Qt::WA_QuitOnClose, true); + setWindowFlags(Qt::FramelessWindowHint); + showFullScreen(); + this->show(); +} +//------------------------------------------------------------------------------------------- +// Layout / actions setup +//------------------------------------------------------------------------------------------- +/** + * This method sets the used Layout. + * + * This method sets the used Layout. Possible layout are: + * - browser mode: only the browser is visible + * - debug mode: the screen is divided into the browser and a debug + * out console + */ +void fbgui::setupLayout() { + // setup layout of the gui: debug split or browser + _webView = new QWebView(this); + if (debugMode == 1) { + // split main window in browser & debug console + createDebugConsole(); + _splitter = new QSplitter(Qt::Vertical, this); + _splitter->addWidget(_webView); + _splitter->addWidget(_debugConsole); + setCentralWidget(_splitter); + } else + setCentralWidget(_webView); +} +//------------------------------------------------------------------------------------------- +/** + * This method enables a shortcut for closing the program. + * The shortcut itself is not configurable: CTRL + X + */ +void fbgui::createActions() { + _quit = new QAction(tr("&quit"), this); + _quit->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X)); + this->addAction(_quit); + connect(_quit, SIGNAL(triggered()), this, SLOT(close())); +} +//------------------------------------------------------------------------------------------- +// File system watching +//------------------------------------------------------------------------------------------- +/** + * This method sets a "watchdog" to a special file. + * + * This method sets a "watchdog" to a special file. If needed it creates the + * file which it has to watch over. It than connects a QFileSystemWatcher with + * this file. If changes happen to this file, the + * fbgui::checkForTrigger(const QString& dirname) method will be called. + * + */ +void fbgui::watchForTrigger() { + // check if fileToTriggerURL already exists + QFile file(fileToTriggerURL); + if (file.exists()) { + qxtLog->debug() << "[watcher] " << fileToTriggerURL << " found."; + // try to load URL + loadURL(); + } else { + // create it + if (file.open(QIODevice::WriteOnly)) { + qxtLog->debug() << "[gui] Created: " << fileToTriggerURL; + file.close(); + } else { + qxtLog->debug() << "[gui] Creation of " << fileToTriggerURL + << " failed!"; + qxtLog->debug() << "[gui] Exiting in 5 seconds..."; + QTimer::singleShot(5000, this, SLOT(close())); + } + } + // watch the path to trigger file + qxtLog->debug() << "[watcher] Watching " << fileToTriggerURL; + _watcher = new QFileSystemWatcher(QStringList(fileToTriggerURL), this); +QObject::connect(_watcher, SIGNAL(fileChanged(const QString&)), this, SLOT(prepareURLLoad())); + +} +//------------------------------------------------------------------------------------------- +/** + * This method checks if the trigger was valid. + * + * This method checks if the trigger was valid. If yes, + * we have received an IP Address an can load the main screen. + * If not, something some error happened. + * + * @see fbgui::checkHost() + * @see fbgui::loadURL() + */ +void fbgui::prepareURLLoad() { + qxtLog->debug() << "[watcher] " << fileToTriggerURL << " changed!"; + // disconnect _watcher, his job is done + qxtLog->debug() << "[watcher] disconnected."; + _watcher->disconnect(this); + _watcher->deleteLater(); + // try to load URL + loadURL(); +} +//------------------------------------------------------------------------------------------- +// Preparations for URL load +//------------------------------------------------------------------------------------------- +/** + * This method checks the existance of the host. + * + * This method checks if the host exists / can be found. + * The host is from the URL given through the configuration. + */ +bool fbgui::checkHost() const { + QHostInfo hostInfo = QHostInfo::fromName(baseURL.host()); + if (hostInfo.error() != QHostInfo::NoError) { + qxtLog->debug() << "[gui] Lookup of " << baseURL.host() << "failed."; + qxtLog->debug() << "[gui] Host can not be reached."; + return false; + } else { + qxtLog->debug() << "[gui] Lookup of " << baseURL.host() << " succeeded."; + return true; + } +} +//------------------------------------------------------------------------------------------- +/** + * This method tries loads the URL. + * + * This method loads the main screen via an POST request. If also disconnects the watcher + * of the file, (Watcher is set in the fbgui::watchForTrigger() method). + * and generates the POST data body. + * + * @see fbgui::watchForTrigger() + * @see fbgui::generatePOSTData() + */ +void fbgui::loadURL() { + if (checkHost()) { + qxtLog->debug() << "[gui] Loading URL: " << baseURL.toString() << " ..."; + + // Generate POST identification data needed by PBS. + QByteArray postData = generatePOSTData(); + QNetworkRequest req(baseURL); + + // show cursor again since user is about to interact. + //QWSServer::instance()->setCursorVisible(true); + QObject::connect(_webView, SIGNAL(loadFinished(bool)), this, SLOT(loadURLDone(bool))); + _webView->load(req, QNetworkAccessManager::PostOperation, postData); + } + // TODO: error page if no host. +} +void fbgui::loadURLDone(bool success) { + // done contains the success of the loading: false / true + if (!success) { + qxtLog->debug() << "[gui] Loading failed. URL: " + << _webView->url().toString(); + qxtLog->debug() << "[gui] You can quit with CTRL + X ..."; + // TODO handle failure properly... + } else { + qxtLog->debug() << "[gui] Loaded URL: " << _webView->url().toString(); + } +} +//------------------------------------------------------------------------------------------- +/** + * This method generates the POST data body. + * + * This method generates the POST data body. The body contains the + * MAC address, an hardwarehash and a specific serial number. + * The hardwarehash is a MD5 hash over the MAC address and the + * mainboard serial number. + * The specific serial number is set at the creation of the usb boot stick. + * This file has to be present on the directory specified in + * the configuration for this to work. + * + * @see SysInfo::getMACAddress() + * @see SysInfo::getMainboardSerial() + */ +QByteArray fbgui::generatePOSTData() { + qxtLog->debug() << "[gui] Generating POST data..."; + // use MAC address as base data + SysInfo si; + QByteArray data(si.getInfo("mac").toUtf8()); + // append mainboard serial to the mac address for more unique hardwarehash + QByteArray mbserial(si.getInfo("mbserial").toUtf8()); + if (!mbserial.isEmpty()) + data.append(mbserial); + else { + qxtLog->debug() + << "[gui] Mainboard serial was empty. Not appending to base hash data."; + } + qxtLog->debug() << "[post] Hashing: " << data; + // generate MD5 hash of data + QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Md5); + qxtLog->debug() << "[post] MD5 Hash: " << hash.toHex(); + + // fetch serial number from usb + QByteArray serial; + QFile file(serialLocation); + if (!file.open(QIODevice::ReadOnly)) { + qxtLog->debug() << "[post] No such file: " << file.fileName(); + } + // everything ok, read data + serial = file.readAll(); + file.close(); + serial.chop(1); // chop EOF + qxtLog->debug() << "[post] Serial number is: " << serial; + + // construct final byte array + QByteArray postData("mac="); + postData.append(si.getInfo("mac")); + postData.append("&hardwarehash=" + hash.toHex()); + postData.append("&serialnumber=" + serial); + qxtLog->debug() << "[post] POST data: " << postData; + return postData; +} + +//------------------------------------------------------------------------------------------- +// Shutdown / Reboot of the client +//------------------------------------------------------------------------------------------- +// TODO One function for reboot and shutdown, with parameter for the action. +// for example: doSystemCall(_REBOOT_); +/** + * This method performs the shutdown of the client. + * + * This method performs the shutdown of the client. It is triggered by the + * JavascriptInterface::shutDownClient() signal which will be emited in the + * JavascriptInterface::shutDown() method. + * This method writes the character 'o' in /proc/sysrq-trigger + * which will shutdown the computer immediatly. + * (See linux magic keys) + * + * @see JavascriptInterface::shutDownClient() + * @see JavascriptInterface::shutDown() + */ +void fbgui::performShutDown() { + QFile file("/proc/sysrq-trigger"); + if (file.open(QIODevice::WriteOnly)) { + file.write("o"); + file.close(); + } else { + qxtLog->debug() << "[gui] Could not open /proc/sysrq-trigger"; + } +} +//------------------------------------------------------------------------------------------- +/** + * This method performs the reboot of the client. + * + * This method performs the reboot of the client. It is triggered by the + * JavascriptInterface::rebootClient() signal which will be emited in the + * JavascriptInterface::reboot() method. + * This method writes the character 'b' in /proc/sysrq-trigger + * which will shutdown the computer immediatly. + * (See linux magic keys) + * + * @see JavascriptInterface::rebootClient() + * @see JavascriptInterface::reboot() + */ +void fbgui::performReboot() { + QFile file("/proc/sysrq-trigger"); + if (file.open(QIODevice::WriteOnly)) { + file.write("b"); + file.close(); + } else { + qxtLog->debug() << "[gui] Could not open /proc/sysrq-trigger"; + } +} +//------------------------------------------------------------------------------------------- +// Preparing Kernel Switch per kexec (initiating Stage 3) +//------------------------------------------------------------------------------------------- +void fbgui::loadSystem() { + //show loading system page. + //_webView->disconnect(this); + //QObject::connect(_webView, SIGNAL(loadFinished(bool)), this, SLOT(prepareKexec())); + _webView->load(QUrl("qrc:/html/loadsystem.html")); + QTimer::singleShot(1000, this, SLOT(prepareKexec())); +} +//------------------------------------------------------------------------------------------- +/** + * This method prepares kexec. + * + * The kernel command line file that should have been downloaded from the Preboot-Server + * and the ip config file (created by udhcpc) are merged into the final completed KCL. + * + * A process is then started to load the kernel, initramfs and kcl into kexec. + * The process tries to execute kexec -l with these parameters. + * + * If this succeeds, runKexec() is called + * + * @see fbgui::runKexec() + * + */ +void fbgui::prepareKexec() { + + qxtLog->debug() << "[gui] Preparing kexec ..."; + // try to read KCL file that was downloaded. + QFile file(downloadPath + "/kcl"); + if (!file.open(QIODevice::ReadOnly)) { + qxtLog->debug() << "[gui] No such file: " << file.fileName(); + } + // everything ok, read data. + QString kcl = file.readAll(); + file.close(); + qxtLog->debug() << "[gui] KCL from PBS: " << kcl; + + // try to read ipconfig file generated by udhcpc. + file.setFileName("/tmp/ip_config_fbgui"); + if (!file.open(QIODevice::ReadOnly)) { + qxtLog->debug() << "[gui] No such file: " << file.fileName(); + } + // everything ok, read data. + QString ipConfig = file.readAll(); + file.close(); + qxtLog->debug() << "[gui] IP config: " << ipConfig; + + // append ipConfig + kcl.append(" ip="); + kcl.append(ipConfig); + qxtLog->debug() << "[gui] Complete KCL: " << kcl; + + // load the kernel + initramfs + append of kcl into kexec. + QProcess *process = new QProcess(this); + QString cmdline = "kexec -l " + downloadPath.toUtf8() + "/kernel --initrd=" + + downloadPath.toUtf8() + "/initramfs --append=\"" + kcl.toUtf8() + + "\""; + qxtLog->debug() << "[gui] kexec cmdline: " << cmdline; + process->start(cmdline); + bool ret = process->waitForFinished(); + if (!ret) { + qxtLog->debug() << "[gui] Failed to execute: " << cmdline; + qxtLog->debug() << "[gui] Exiting in 5 seconds..."; + QTimer::singleShot(5000, this, SLOT(close())); + } else { + qxtLog->debug() << "[gui] Kexec load was successfull."; + if (debugMode < 0) { + // if process successfully finished, try to run kexec -e + runKexec(); + } else { + qxtLog->debug() + << "[gui] Skipping execution of kexec - open debug shell."; + qxtLog->debug() + << "[gui] To start the system execute \"kexec -e\" in your shell."; + close(); + } + } +} +//------------------------------------------------------------------------------------------- +/** + * This method tries to execute: kexec -e + * + * This method tries to execute: kexec -e + * + */ +void fbgui::runKexec() { + QProcess *process = new QProcess(this); + process->start("kexec -e"); + if (!process->waitForStarted()) { + qxtLog->debug() << "[gui] Failed to execute: kexec -e"; + qxtLog->debug() << "[gui] Exiting in 5 seconds..."; + QTimer::singleShot(5000, this, SLOT(close())); + //TODO: Handle failure properly... + } +} +//------------------------------------------------------------------------------------------- +// Debug console setup / control +//------------------------------------------------------------------------------------------- +/** + * This method creates a debug console as a widget. + * + * It is basicly a QTextEdit widget as provided by QT's Framework. + * An action to toggle this widget is implemented (CTRL + D). + * + * @see fbgui::toggleDebugConsole() + */ +void fbgui::createDebugConsole() { + // create the debug console widget + _debugConsole = new QTextEdit(this); + _debugConsole->setWindowFlags(Qt::FramelessWindowHint); + // fanciness + QPalette pal; + pal.setColor(QPalette::Base, Qt::black); + _debugConsole->setPalette(pal); + _debugConsole->setTextColor(Qt::white); + // enable custom logger engine + qxtLog->addLoggerEngine("fb_logger", new LoggerEngine_fb(_debugConsole)); + //qxtLog->initLoggerEngine("fb_logger"); + qxtLog->setMinimumLevel("fb_logger", QxtLogger::DebugLevel); + // CTRL + D toggles debug window + _toggleDebugConsole = new QAction(tr("&toggleDebug"), this); + _toggleDebugConsole->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D)); + addAction(_toggleDebugConsole); + connect(_toggleDebugConsole, SIGNAL(triggered()), this, SLOT( + toggleDebugConsole())); +} +//------------------------------------------------------------------------------------------- +/** + * This method toggles the debug console. + * + * Toggle the visibility of the debug console if the action _toggleDebugConsole is triggered. + * + * @see fbgui::createDebugConsole() + */ +void fbgui::toggleDebugConsole() { + (_debugConsole->isVisible()) ? _debugConsole->hide() : _debugConsole->show(); +} |