//=========================================== // PC-BSD source code // Copyright (c) 2015, PC-BSD Software/iXsystems // Available under the 3-clause BSD license // See the LICENSE file for full details //=========================================== #include #include "sysadm-general.h" #include "sysadm-update.h" #include "sysadm-global.h" #include "globals.h" #define UP_PIDFILE "/tmp/.updateInProgress" #define UP_RBFILE "/tmp/.trueos-update-staged" #define UP_UPFILE "/tmp/.updatesAvailable" #define UP_UPFILE_ERR "/tmp/.updateCheckError" #define UP_CONFFILE "/usr/local/etc/trueos.conf" using namespace sysadm; //PLEASE: Keep the functions in the same order as listed in pcbsd-general.h //Return the date/time that the last full check for updates was run QDateTime Update::lastFullCheck(){ return QFileInfo(UP_UPFILE).lastModified(); } QDateTime Update::rebootRequiredSince(){ if(!QFile::exists(UP_RBFILE)){ return QDateTime(); } //null time - no file exists return QFileInfo(UP_RBFILE).lastModified(); } // Return a list of updates available QJsonObject Update::checkUpdates(bool fast) { //NOTE: The "fast" option should only be used for automated/timed checks (to prevent doing this long check too frequently) QJsonObject retObject; //qDebug() << "Check for updates: fast=" << fast; //Quick check to ensure the tool is available if(!QFile::exists("/usr/local/bin/pc-updatemanager")){ return retObject; } //See if the check for updates is currently running - just return nothing at the moment for that if(DISPATCHER->isJobActive("sysadm_update_checkupdates")){ retObject.insert("status", "checkingforupdates"); return retObject; } //Check if the system is waiting to reboot if(QFile::exists(UP_RBFILE)){ retObject.insert("status","rebootrequired"); if(QFile::exists(UP_UPFILE)){ QFile::remove(UP_UPFILE); } //ensure the next fast update does a full check if(QFile::exists(UP_UPFILE_ERR)){ QFile::remove(UP_UPFILE_ERR); } //ensure the next fast update does a full check //Also add the information on what updates are pending retObject.insert("details", sysadm::General::readTextFile(UP_RBFILE).join("\n") ); return retObject; } //Check if an update is currently running if(QFile::exists(UP_PIDFILE)){ //See if the process is actually running if( General::RunQuickCommand(QString("pgrep -F ")+UP_PIDFILE) ){ //Success if return code == 0 retObject.insert("status","updaterunning"); if(QFile::exists(UP_UPFILE)){ QFile::remove(UP_UPFILE); } //ensure the next fast update does a full check return retObject; } } //Get the list of details from the update checks (fast/full) QStringList output; QDateTime cdt = QDateTime::currentDateTime(); QDateTime fdt = cdt.addDays(-1); //just enough to trip the checks below if needed bool lasterr = false; if(QFile::exists(UP_UPFILE)){ fdt = QFileInfo(UP_UPFILE).lastModified(); } else if(QFile::exists(UP_UPFILE_ERR)){ fdt = QFileInfo(UP_UPFILE_ERR).lastModified(); lasterr = true; } //qDebug() << "Time Stamps (now/file):" << cdt << fdt; //qDebug() << " - File earlier than now:" << (fdtDay time:" << fdt.secsTo(cdt); int secs = fdt.secsTo(cdt); if(fast && (secs<43200) && !lasterr ){ //Note: The "fast" check will only be used if the last full check was less than 12 hours earlier // (unless the last run ended in an error - then it will use the 5-minute check below) //qDebug() << " - UseFast Re-read"; if(lasterr){output = General::readTextFile(UP_UPFILE_ERR); } else{ output = General::readTextFile(UP_UPFILE); } }else if(secs<300){ //Note: This will re-use the previous check if it was less than 5 minutes ago (prevent hammering servers from user checks) //qDebug() << " - Use Fast Re-read (failsafe - less than 5 minute interval)"; if(lasterr){ output = General::readTextFile(UP_UPFILE_ERR); } else{ output = General::readTextFile(UP_UPFILE); } }else{ //qDebug() << " - Run full check"; QStringList cmds; cmds << "pc-updatemanager syncconf" << "pc-updatemanager pkgcheck"; DISPATCHER->queueProcess("sysadm_update_checkupdates", cmds ); retObject.insert("status", "checkingforupdates"); //qDebug() << " - Done starting check"; return retObject; /*General::RunCommand("pc-updatemanager syncconf"); //always resync the config file before starting an update check output.append( General::RunCommand("pc-updatemanager pkgcheck").split("\n") ); while(output.last().simplified()==""){ output.removeLast(); } if(!output.last().contains("ERROR:")){ //make sure there was network access available first - otherwise let it try again soon General::writeTextFile(UP_UPFILE, output); //save this check for later "fast" updates }*/ } //qDebug() << "pc-updatemanager checks:" << output; QString nameval; int pnum=1; for ( int i = 0; i < output.size(); i++) { if ( output.at(i).indexOf("NAME: ") != -1 ) nameval = output.at(i).section(" ", 1, 1); if ( output.at(i).indexOf("TYPE: SECURITYUPDATE") != -1 ) { QJsonObject itemvals; itemvals.insert("name", nameval); retObject.insert("security", itemvals); } if ( output.at(i).indexOf("TYPE: SYSTEMUPDATE") != -1 ) { QJsonObject itemvals; itemvals.insert("name", nameval); if ( output.size() > ( i + 1) ) itemvals.insert("tag", output.at(i+1).section(" ", 1, 1)); if ( output.size() > ( i + 2) ) itemvals.insert("version", output.at(i+2).section(" ", 1, 1)); retObject.insert("majorupgrade", itemvals); } if ( output.at(i).indexOf("TYPE: PATCH") != -1 ) { QJsonObject itemvals; itemvals.insert("name", nameval); if ( output.size() > ( i + 1) ) itemvals.insert("tag", output.at(i+1).section(" ", 1, 1)); if ( output.size() > ( i + 2) ) itemvals.insert("details", output.at(i+2).section(" ", 1, 1)); if ( output.size() > ( i + 3) ) itemvals.insert("date", output.at(i+3).section(" ", 1, 1)); if ( output.size() > ( i + 4) ) itemvals.insert("size", output.at(i+4).section(" ", 1, 1)); retObject.insert("patch"+QString::number(pnum), itemvals); pnum++; } if ( output.at(i).indexOf("TYPE: PKGUPDATE") != -1 ) { QJsonObject itemvals; itemvals.insert("name", nameval); retObject.insert("packageupdate", itemvals); } } if(retObject.isEmpty()){ retObject.insert("status", "noupdates"); }else{ // Update status that we have updates retObject.insert("status", "updatesavailable"); } retObject.insert("details", output.join("\n") ); //full details of the check for updates if(QFile::exists(UP_UPFILE)){ retObject.insert("last_check",QFileInfo(UP_UPFILE).lastModified().toString(Qt::ISODate) ); }else if(QFile::exists(UP_UPFILE_ERR)){ retObject.insert("last_check",QFileInfo(UP_UPFILE_ERR).lastModified().toString(Qt::ISODate) ); } return retObject; } void Update::saveCheckUpdateLog(QString log){ QStringList output = log.split("\n"); qDebug() << "Got Check Update Log:" << log << output; while(!output.isEmpty() && output.last().simplified().isEmpty()){ output.removeLast(); } if(output.isEmpty()){ return; } if(!output.last().contains("ERROR:")){ //make sure there was network access available first - otherwise let it try again soon General::writeTextFile(UP_UPFILE, output); //save this check for later "fast" updates if(QFile::exists(UP_UPFILE_ERR)){ QFile::remove(UP_UPFILE_ERR); } //remove any previous failed log }else{ General::writeTextFile(UP_UPFILE_ERR,output); //save this error return for later if(QFile::exists(UP_UPFILE)){ QFile::remove(UP_UPFILE); } //remove any previous good log } } // List available branches we can switch to QJsonObject Update::listBranches() { QJsonObject retObject; QStringList output = General::RunCommand("pc-updatemanager branches").split("\n"); bool inSection = false; for ( int i = 0; i < output.size(); i++) { if ( output.at(i).indexOf("-----------------") != -1 ) { inSection = true; continue; } if (!inSection) continue; if ( output.at(i).isEmpty() ) break; QString tmp = output.at(i).section(" ", 0, 0); QString tmp2 = output.at(i).section(" ", 1, 1); if ( tmp2 == "*" ) retObject.insert(tmp, "active"); else retObject.insert(tmp, "available"); } return retObject; } // Kickoff an update process QJsonObject Update::startUpdate(QJsonObject jsin) { QJsonObject retObject; QStringList keys = jsin.keys(); if (! keys.contains("target") ) { retObject.insert("error", "Missing required key 'target'"); return retObject; } // Save the target QString target; target = jsin.value("target").toString(); QString flags; if ( target == "chbranch" ) { if (! keys.contains("branch") ) { retObject.insert("error", "Missing required key 'branch'"); return retObject; } flags = "chbranch " + jsin.value("branch").toString(); } else if ( target == "pkgupdate" ) { flags = "pkgupdate"; /* } else if ( target == "fbsdupdate" ) { flags = "fbsdupdate"; } else if ( target == "fbsdupdatepkgs" ) { flags = "fbsdupdatepkgs";*/ } else if ( target == "standalone" ) { if (! keys.contains("tag") ) { retObject.insert("error", "Missing required key 'tag'"); return retObject; } flags = "install " + jsin.value("tag").toString(); } else { // Ruh-roh retObject.insert("error", "Unknown target key: " + target); return retObject; } // Create a unique ID for this queued action QString ID = QUuid::createUuid().toString(); // Queue the update action DISPATCHER->queueProcess("sysadm_update_runupdates::"+ID, "pc-updatemanager " + flags); if(QFile::exists(UP_UPFILE)){ QFile::remove(UP_UPFILE); } //ensure the next fast update does a full check // Return some details to user that the action was queued retObject.insert("command", "pc-updatemanger " + flags); retObject.insert("comment", "Task Queued"); retObject.insert("queueid", ID); return retObject; } // Stop an update process QJsonObject Update::stopUpdate() { //See if the update is running in the dispatcher QJsonObject ret; QJsonObject cup = DISPATCHER->listJobs(); QStringList ids = cup.keys().filter("sysadm_update_runupdates::"); if(!ids.isEmpty()){ //Found a dispatcher process - go ahead and request that it stop DISPATCHER->killJobs(ids); ret.insert("result","success"); }else if( QFile::exists(UP_PIDFILE) ){ //See if the process is actually running if( General::RunQuickCommand(QString("pgrep -F ")+UP_PIDFILE) ){ //Success if return code == 0 int rtc = General::RunQuickCommand( QString("pkill -F ")+UP_PIDFILE ); if(rtc==0){ ret.insert("result","success"); } else{ ret.insert("result","error: could not kill update process"); } } }else{ ret.insert("result","error: no update processes found"); } return ret; } QJsonObject Update::applyUpdates(){ QJsonObject ret; QProcess::startDetached("pc-updatemanager startupdate"); ret.insert("result","rebooting to complete updates"); return ret; } //SETTINGS OPTIONS QJsonObject Update::readSettings(){ QJsonObject ret; QStringList knownsettings; knownsettings << "PACKAGE_SET" << "PACKAGE_URL" << "AUTO_UPDATE" << "MAXBE" << "AUTO_UPDATE_REBOOT" << "CDN_TYPE"; QStringList info = General::readTextFile(UP_CONFFILE); for(int i=0; i=0){ info[i] = keys[index]+": "+vals[index]; keys.removeAt(index); vals.removeAt(index); } } // if the variable was not previously defined, just add it to the end for(int i=0; i