/**************************************************************************** ** Modern Linbo GUI ** Copyright (C) 2020 Dorian Zedler ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU Affero General Public License as published ** by the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License ** along with this program. If not, see . ****************************************************************************/ #include "linbobackend.h" #define LINBO_CMD(arg) QStringList("linbo_cmd") << (arg) using namespace std; LinboBackend::LinboBackend(QObject *parent) : QObject(parent) { this->setState(Initializing); this->logger = new LinboLogger(this); this->config = new LinboConfig(this); this->autostartTimer = new QTimer(this); this->autostartTimer->setSingleShot(true); this->autostartRemainingTimeRefreshTimer = new QTimer(this); this->autostartRemainingTimeRefreshTimer->setSingleShot(true); this->autostartRemainingTimeRefreshTimer->setInterval(10); this->autostartElapsedTimer = new QElapsedTimer(); this->currentOs = nullptr; // read start.conf this->logger->log("Starting to parse start.conf", LinboLogger::LinboGuiInfo); ifstream input; input.open( "start.conf", ios_base::in ); QString tmp_qstring; while( !input.eof() ) { // *** Image description section *** // entry in start tab char line[500]; input.getline(line,500,'\n'); tmp_qstring = QString::fromAscii( line, -1 ).stripWhiteSpace(); if ( tmp_qstring.startsWith("#") || tmp_qstring.isEmpty() ) continue; tmp_qstring = tmp_qstring.section("#",0,0).stripWhiteSpace(); // Strip comment if(tmp_qstring.lower().compare("[os]") == 0) { LinboOs* tmpOs = read_os(&input); if(!tmpOs->getName().isEmpty()) { this->operatingSystems.append(tmpOs); if(tmpOs->getAutostart() && this->currentOs == nullptr) this->currentOs = tmpOs; // check if this is an additional/incremental image for an existing OS /* TODO unsigned int i; // Being checked later. for(i = 0; i < elements.size(); i++ ) { if(tmp_os.get_name().lower().compare(elements[i].get_name().lower()) == 0) { elements[i].image_history.push_back(tmp_image); break; } } if(i==elements.size()) { // Not included yet -> new image tmp_os.image_history.push_back(tmp_image); elements.push_back(tmp_os); }*/ } else { tmpOs->deleteLater(); } } else if(tmp_qstring.lower().compare("[linbo]") == 0) { read_globals(&input, config); } else if(tmp_qstring.lower().compare("[partition]") == 0) { LinboDiskPartition* tmpPartition = read_partition(&input); if(!tmpPartition->getPath().isEmpty()) { diskPartitions.append(tmpPartition); } else { tmpPartition->deleteLater(); } } } input.close(); this->logger->log("Finished to parsing start.conf", LinboLogger::LinboGuiInfo); this->logger->log("Loading global configuration", LinboLogger::LinboGuiInfo); // load global config QStringList command; // ascynchorons commands are logged to logger this->asynchronosProcess = new QProcess(); connect( asynchronosProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readFromStdout()) ); connect( asynchronosProcess, SIGNAL(readyReadStandardError()), this, SLOT(readFromStderr()) ); // synchronos commands are not logged this->synchronosProcess = new QProcess(); // client ip this->config->setIpAddress(this->executeCommand(true, "ip")); // mac address this->config->setMacAddress(this->executeCommand(true, "mac")); // Version this->config->setVersion(this->executeCommand(true, "version").stripWhiteSpace()); // hostname this->config->setHostname(this->executeCommand(true, "hostname")); // CPU this->config->setCpu(this->executeCommand(true, "cpu")); // Memory this->config->setRamSize(this->executeCommand(true, "memory")); // Cache Size this->config->setCacheSize(this->executeCommand(true, "size")); // Harddisk Size QRegExp *removePartition = new QRegExp("[0-9]{1,2}") ; QString hd = this->config->getCache(); // e.g. turn /dev/sda1 into /dev/sda hd.remove( *removePartition ); this->config->setHddSize(this->executeCommand(true, "size", hd)); this->logger->log("Finished loading global configuration", LinboLogger::LinboGuiInfo); // default select first OS if no other OS has been selected yet if(this->operatingSystems.length() > 0 && this->currentOs == nullptr) this->currentOs = this->operatingSystems[0]; // triger autostart if(this->currentOs->getAutostart()) this->executeAutostart(); else this->setState(Idle); } // -------------------- // - Public functions - // -------------------- void LinboBackend::executeAutostart() { this->autostartTimer->setInterval(this->currentOs->getAutostartTimeout() * 1000); this->autostartElapsedTimer->restart(); } void handleAutostartTimerTimeout() { } void LinboBackend::shutdown() { this->executeCommand(false, "busybox", QStringList("poweroff")); } void LinboBackend::reboot() { this->executeCommand(false, "busybox", QStringList("reboot")); } bool LinboBackend::startCurrentOs() { LinboOs* os = this->currentOs; if(os == nullptr || this->state != Idle || !this->currentOs->getStartbutton()) return false; this->setState(Starting); return true; this->executeCommand( false, "start", os->getBootPartition(), os->getRootPartition(), os->getKernel(), os->getInitrd(), os->getKernelOptions(), this->config->getCache() ); return true; } bool LinboBackend::syncCurrentOs() { LinboOs* os = this->currentOs; if(os == nullptr || this->state != Idle || !this->currentOs->getSyncbutton()) return false; this->setState(Syncing); this->executeCommand( false, "syncstart", this->config->getServer(), this->config->getCache(), os->getBaseImage()->getName(), os->getDifferentialImage()->getName(), os->getBootPartition(), os->getRootPartition(), os->getKernel(), os->getInitrd(), os->getKernelOptions() ); return true; } bool LinboBackend::reinstallCurrentOs() { LinboOs* os = this->currentOs; if(os == nullptr || this->state != Idle || !this->currentOs->getNewbutton()) return false; this->setState(Reinstalling); this->executeCommand( false, "syncr", this->config->getServer(), this->config->getCache(), os->getBaseImage()->getName(), os->getDifferentialImage()->getName(), os->getBootPartition(), os->getRootPartition(), os->getKernel(), os->getInitrd(), os->getKernelOptions(), QString("force") ); return true; } bool LinboBackend::partitionDrive(bool format) { if(this->state != Root) return false; this->setState(Partitioning); QStringList commandArgs = QStringList(format ? "partition":"partition_noformat"); for( int i=0; i < this->diskPartitions.length(); i++) { LinboDiskPartition* p = this->diskPartitions[i]; commandArgs.append( this->buildCommand( p->getPath(), QString::number(p->getSize()), p->getId(), QString((p->getBootable())?"bootable":"\" \""), p->getFstype() ) ); } this->executeCommand(false, this->linboCmdCommand, commandArgs); return true; } bool LinboBackend::initializeCache() { if(this->state != Root) return false; this->setState(InitializingCache); QStringList commandArgs = this->buildCommand("initcache", config->getServer(), config->getCache()); if( this->config->getDownloadType().isEmpty() ) commandArgs.append(this->config->getDownloadType()); else commandArgs.append("rsync"); for(int i = 0; i < this->operatingSystems.length(); i++) { LinboOs* os = this->operatingSystems[i]; commandArgs.append(this->buildCommand(os->getBaseImage()->getName(), os->getDifferentialImage()->getName())); /* TODO ?? for(unsigned int j = 0; j < os[i].image_history.size(); j++) { saveappend( command, os[i].image_history[j].get_image() ); }*/ } this->executeCommand(false, this->linboCmdCommand, commandArgs); return true; } bool LinboBackend::updateLinbo() { if(this->state != Root) return false; this->executeCommand("update", this->config->getServer(), this->config->getCache()); return true; } LinboLogger* LinboBackend::getLogger() { return this->logger; } LinboConfig* LinboBackend::getConfig() { return this->config; } QList LinboBackend::getOperatingSystems() { return this->operatingSystems; } LinboOs* LinboBackend::getCurrentOs() { return this->currentOs; } void LinboBackend::setCurrentOs(LinboOs* os) { if(this->state != Idle || !this->operatingSystems.contains(os) || this->currentOs == os) return; this->currentOs = os; emit this->currentOsChanged(os); } double LinboBackend::getAutostartTimeoutProgress() { return this->autostartElapsedTimer->elapsed() / this->autostartTimer->interval(); } int LinboBackend::getAutostartTimeoutRemainingSeconds() { return (this->autostartTimer->interval() - this->autostartElapsedTimer->elapsed()) / 1000; } // ----------- // - Helpers - // ----------- QString LinboBackend::executeCommand(bool waitForFinished, QString command, QStringList commandArgs) { this->logger->log("Executing " + QString(waitForFinished ? "synchronos":"asynchronos") + ": " + command + " " + commandArgs.join(" "), LinboLogger::LinboGuiInfo); if(waitForFinished) { // clear old output this->synchronosProcess->readAll(); synchronosProcess->start(command, commandArgs); synchronosProcess->waitForStarted(); while( !synchronosProcess->waitForFinished(10000) ) {} return this->synchronosProcess->readAllStandardOutput(); } else { asynchronosProcess->start(command, commandArgs); asynchronosProcess->waitForStarted(); return ""; } } void LinboBackend::readFromStdout() { QString stdOut = this->asynchronosProcess->readAllStandardOutput(); QStringList lines = stdOut.split("\n"); for(QString line : lines) { this->logger->log(line, LinboLogger::StdOut); } } void LinboBackend::readFromStderr() { QString stdOut = this->asynchronosProcess->readAllStandardError(); QStringList lines = stdOut.split("\n"); for(QString line : lines) { this->logger->log(line, LinboLogger::StdErr); } } LinboBackend::LinboState LinboBackend::getState() { return this->state; } void LinboBackend::setState(LinboState state) { if(this->state == state) return; this->state = state; emit this->stateChanged(this->state); } // Return true unless beginning of new section '[' is found. bool LinboBackend::read_pair(ifstream* input, QString& key, QString& value) { char line[1024]; if(input->peek() == '[') return false; // Next section found. input->getline(line,1024,'\n'); QString s = QString::fromAscii( line, -1 ).stripWhiteSpace(); key = s.section("=",0,0).stripWhiteSpace().lower(); if(s.startsWith("#")||key.isEmpty()) { key = QString(""); value = QString(""); } else { value=s.section("=",1).section("#",0,0).stripWhiteSpace(); } return true; } bool LinboBackend::toBool(const QString& value) { if(value.startsWith("yes",false)) return true; if(value.startsWith("true",false)) return true; if(value.startsWith("enable",false)) return true; return false; } LinboOs* LinboBackend::read_os(ifstream* input) { LinboOs* os = new LinboOs(this); QString key, value; while(!input->eof() && read_pair(input, key, value)) { if(key.compare("name") == 0) os->setName(value); else if(key.compare("description") == 0) os->setDescription(value); else if(key.compare("version") == 0) os->setVersion(value); else if(key.compare("iconname") == 0) os->setIconName(value); else if(key.compare("image") == 0) os->setDifferentialImage(new LinboImage(value, os)); else if(key.compare("baseimage") == 0) os->setBaseImage(new LinboImage(value, os)); else if(key.compare("boot") == 0) os->setBootPartition(value); else if(key.compare("root") == 0) os->setRootPartition(value); else if(key.compare("kernel") == 0) os->setKernel(value); else if(key.compare("initrd") == 0) os->setInitrd(value); else if(key.compare("append") == 0) os->setKernelOptions(value); else if(key.compare("syncenabled") == 0) os->setSyncButton(toBool(value)); else if(key.compare("startenabled") == 0) os->setStartButton(toBool(value)); else if((key.compare("remotesyncenabled") == 0) || (key.compare("newenabled") == 0)) os->setNewButton(toBool(value)); else if(key.compare("defaultaction") == 0) os->setDefaultAction(os->startActionFromString(value)); else if(key.compare("autostart") == 0) os->setAutostart(toBool(value)); else if(key.compare("autostarttimeout") == 0) os->setAutostartTimeout(value.toInt()); else if(key.compare("hidden") == 0) os->setHidden(toBool(value)); } return os; } LinboDiskPartition* LinboBackend::read_partition(ifstream* input) { LinboDiskPartition* partition = new LinboDiskPartition(this); QString key, value; while(!input->eof() && read_pair(input, key, value)) { if(key.compare("dev") == 0) partition->setPath(value); else if(key.compare("size") == 0) partition->setSize(value.toInt()); else if(key.compare("id") == 0) partition->setId(value); else if(key.compare("fstype") == 0) partition->setFstype(value); else if(key.startsWith("bootable", false)) partition->setBootable(toBool(value)); } return partition; } void LinboBackend::read_globals( ifstream* input, LinboConfig* config ) { QString key, value; while(!input->eof() && read_pair(input, key, value)) { if(key.compare("server") == 0) config->setServer(value); else if(key.compare("cache") == 0) config->setCache(value); else if(key.compare("roottimeout") == 0) config->setRootTimeout((unsigned int)value.toInt()); else if(key.compare("group") == 0) config->setHostgroup(value); else if(key.compare("autopartition") == 0) config->setAutopartition(toBool(value)); else if(key.compare("autoinitcache") == 0) config->setAutoInitCache(toBool(value)); else if(key.compare("autoformat") == 0) config->setAutoFormat(toBool(value)); else if(key.compare("backgroundfontcolor") == 0) config->setBackgroundFontcolor(value); else if(key.compare("consolefontcolorstdout") == 0) config->setConsoleFontcolorStdout(value); else if(key.compare("consolefontcolorstderr") == 0) config->setConsoleFontcolorStderr(value); else if(key.compare("usemulticast") == 0) { if( (unsigned int)value.toInt() == 0 ) config->setDownloadType("rsync"); else config->setDownloadType("multicast"); } else if(key.compare("downloadtype") == 0) config->setDownloadType(value); } }