#include "fbgui.h" #include "sysinfo.h" #include "loggerengine.h" #include "downloadmanager.h" #include "javascriptinterface.h" #include #include #include #include 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(); }