#include "aptmanager.h" #include #include #include #include #include #include #include #include AptManager::AptManager(QObject *parent) : QObject(parent) { } void AptManager::applyFullUpgrade() { internalUpdateProgress = 0; internalUpdateInfo = getUpdateInfo(); // these six vars are used to track how far along things are for updating the progress bar instUnpackList = instConfigList = internalUpdateInfo[0]; upgradeUnpackList = upgradeConfigList = internalUpdateInfo[1]; removeList = internalUpdateInfo[2]; numPackagesToPrep = instUnpackList.count() + upgradeUnpackList.count(); aptProcess = new QProcess(); aptProcess->setProgram("/usr/bin/lxqt-sudo"); // Note that the lubuntu-update-backend script sets LC_ALL=C in it already, so we don't need to add that here. aptProcess->setArguments(QStringList() << "/usr/libexec/lubuntu-update-backend" << "doupdate"); aptProcess->setProcessChannelMode(QProcess::MergedChannels); QObject::connect(aptProcess, &QProcess::readyRead, this, &AptManager::handleProcessBuffer); QObject::connect(aptProcess, QOverload::of(&QProcess::finished), this, &AptManager::handleProcessBuffer); aptProcess->start(); } void AptManager::keepConffile(QString conffile) { aptProcess->write("keep\n"); aptProcess->write(conffile.toUtf8() + "\n"); aptProcess->waitForBytesWritten(); } void AptManager::replaceConffile(QString conffile) { aptProcess->write("replace\n"); aptProcess->write(conffile.toUtf8() + "\n"); aptProcess->waitForBytesWritten(); } void AptManager::doneWithConffiles() { aptProcess->write("done\n"); aptProcess->waitForBytesWritten(); aptProcess->closeWriteChannel(); } void AptManager::handleProcessBuffer() { int maxWaitTime = 20; while (!aptProcess->canReadLine() && maxWaitTime > 0) { // this is horrible why doesn't QProcess have canReadLine signal QThread::msleep(20); maxWaitTime--; } QRegularExpression dlRegex("^Get:\\d+"); char lineBuf[2048]; while(aptProcess->canReadLine()) { aptProcess->readLine(lineBuf, 2048); QString line = QString(lineBuf); emit logLineReady(line); // this tells the main window to print the line to the log view QRegularExpressionMatch dlLineMatch = dlRegex.match(line); // yes, this gave me a headache also if (dlLineMatch.hasMatch()) { // Increments the progress counter for each package downloaded internalUpdateProgress++; } else if (line.count() >= 25 && line.left(24) == "Preparing to unpack .../" && numPackagesToPrep != 0) { internalUpdateProgress++; // Increments the progress counter for each package that is "prepared to unpack" numPackagesToPrep--; } else if (line.count() >= 10 && line.left(9) == "Unpacking") { /* * Increments the progress counter for each package that is unpacked * The package name may be suffixed with ":amd64" or some other * :architecture string, so we have to split that off * This and the subsequent progress updaters are complex since * we don't want any line that starts with "Unpacking" or some * other simple string to cause a progress increment. What if a * maintainer script says it's unpacking something? */ QStringList parts = line.split(' '); QString packageName; if (parts.count() >= 2) { packageName = parts[1].split(':')[0]; // strip off a trailing :amd64 if it's there if (instUnpackList.removeAll(packageName) || upgradeUnpackList.removeAll(packageName)) { internalUpdateProgress++; } } } else if (line.count() >= 11 && line.left(10) == "Setting up") { QStringList parts = line.split(' '); QString packageName; if (parts.count() >= 3) { packageName = parts[2].split(':')[0]; if (instConfigList.removeAll(packageName) || upgradeConfigList.removeAll(packageName)) { internalUpdateProgress++; } } } else if (line.count() >= 9 && line.left(8) == "Removing") { QStringList parts = line.split(' '); QString packageName; if (parts.count() >= 2) { packageName = parts[1].split(':')[0]; if (removeList.removeAll(packageName)) { internalUpdateProgress++; } } } else if (line == "Lubuntu Update !!! CONFIGURATION FILE LIST START\r\n") { // oh boy, conffile processing. Stop everything and get the full list of conffiles, busy-waiting as needed. QStringList conffileList; while (true) { while (!aptProcess->canReadLine()) { QThread::msleep(20); } aptProcess->readLine(lineBuf, 2048); QString confLine = QString(lineBuf); confLine = confLine.left(confLine.count() - 2); if (confLine == "Lubuntu Update !!! CONFIGURATION FILE LIST END") { emit conffileListReady(conffileList); // this triggers the main window to show the conffile handler window break; } else { conffileList.append(confLine); } } } double percentageDone = (static_cast(internalUpdateProgress) / (((internalUpdateInfo[0].count() + internalUpdateInfo[1].count()) * 4) + internalUpdateInfo[2].count())) * 100; if (percentageDone > 100.0) { percentageDone = 100.0; } emit progressUpdated(static_cast(percentageDone)); } if (aptProcess->state() == QProcess::NotRunning) { emit progressUpdated(100); // just in case the progress bar didn't fill all the way due to a previous partial download emit updateComplete(); aptProcess->deleteLater(); } } QList AptManager::getUpdateInfo() { /* * We use `apt-get -s full-upgrade` here rather than `apt list * --upgradable` because it tells us not only what packages need upgraded, * but also which packages need installed and removed, as well as which * packages have been held back. The only thing this doesn't tell us is * which packages are security updates, and for that we do use `apt list * --upgradable`. */ QList output; QProcess *checker = new QProcess(); QProcessEnvironment checkerEnv; checkerEnv.insert("LC_ALL", "C"); checker->setProcessEnvironment(checkerEnv); checker->setProgram("/usr/bin/apt-get"); checker->setArguments(QStringList() << "-s" << "full-upgrade"); checker->start(); if (checker->waitForFinished(60000)) { QString stdoutString = QString(checker->readAllStandardOutput()); QTextStream stdoutStream(&stdoutString); QString stdoutLine; /* * output[0] = packages to install * output[1] = packages to upgrade * output[2] = packages to remove * output[3] = held packages * output[5] = security-related updates, this is populated at the end */ for (int i = 0; i < 4;i++) { output.append(QStringList()); } bool gettingInstallPackages = false; bool gettingUpgradePackages = false; bool gettingUninstallPackages = false; bool gettingHeldPackages = false; bool gettingPackageList = false; while (stdoutStream.readLineInto(&stdoutLine)) { if (!gettingPackageList) { parseAptLine(stdoutLine, &gettingInstallPackages, &gettingUpgradePackages, &gettingUninstallPackages, &gettingHeldPackages, &gettingPackageList); } else { /* * Each line of output apt-get gives when displaying package * lists is intended by two spaces. A package name will always * consist of at least one character, so if there are less * than three characters or the line isn't indented with two * spaces, we know we're no longer reading a package list. */ if (stdoutLine.count() < 3 || stdoutLine.left(2) != " ") { gettingInstallPackages = false; gettingUpgradePackages = false; gettingUninstallPackages = false; gettingHeldPackages = false; gettingPackageList = false; parseAptLine(stdoutLine, &gettingInstallPackages, &gettingUpgradePackages, &gettingUninstallPackages, &gettingHeldPackages, &gettingPackageList); } else { QString packageLine = stdoutLine.trimmed(); QStringList packageNames = packageLine.split(' '); if (gettingInstallPackages) { for (int i = 0;i < packageNames.count();i++) { output[0].append(packageNames[i]); } } else if (gettingUpgradePackages) { for (int i = 0;i < packageNames.count();i++) { output[1].append(packageNames[i]); } } else if (gettingUninstallPackages) { for (int i = 0;i < packageNames.count();i++) { output[2].append(packageNames[i]); } } else if (gettingHeldPackages) { for (int i = 0;i < packageNames.count();i++) { output[3].append(packageNames[i]); } } } } } output.append(getSecurityUpdateList()); } checker->terminate(); checker->deleteLater(); return output; } void AptManager::parseAptLine(QString line, bool *gettingInstallPackages, bool *gettingUpgradePackages, bool *gettingUninstallPackages, bool *gettingHeldPackages, bool *gettingPackageList) { *gettingPackageList = true; if (line == "The following NEW packages will be installed:") { *gettingInstallPackages = true; } else if (line == "The following packages will be upgraded:") { *gettingUpgradePackages = true; } else if (line == "The following packages will be REMOVED:") { *gettingUninstallPackages = true; } else if (line == "The following packages have been kept back:") { *gettingHeldPackages = true; } else { *gettingPackageList = false; } } QStringList AptManager::getSecurityUpdateList() { QStringList updateList; QString distroName; // Find the distro name QFile lsbReleaseFile("/etc/lsb-release"); lsbReleaseFile.open(QIODevice::ReadOnly); QString contents = QString(lsbReleaseFile.readAll()); lsbReleaseFile.close(); QTextStream distroFinder(&contents); QString distroLine; while (distroFinder.readLineInto(&distroLine)) { // The line has to be at least 18 characters long - 16 for the string "DISTRIB_CODENAME", one for the = sign, and one for a codename with a length of at least one. if (distroLine.count() >= 18 && distroLine.left(16) == "DISTRIB_CODENAME") { QStringList distroParts = distroLine.split('='); if (distroParts.count() >= 2) { distroName = distroParts[1]; } } } // Now check for security updates QProcess *checker = new QProcess(); QProcessEnvironment checkerEnv; checkerEnv.insert("LC_ALL", "C"); checker->setProcessEnvironment(checkerEnv); checker->setProgram("/usr/bin/apt"); checker->setArguments(QStringList() << "list" << "--upgradable"); checker->start(); if (checker->waitForFinished(60000)) { QRegularExpression regex(QString("%1-security").arg(distroName)); QString stdoutString = checker->readAllStandardOutput(); QTextStream stdoutStream(&stdoutString); QString stdoutLine; while (stdoutStream.readLineInto(&stdoutLine)) { QRegularExpressionMatch match = regex.match(stdoutLine); if (match.hasMatch()) { // The package name we want is followed immediately by a single forward slash, so we can use that as our delimiter. QStringList updateParts = stdoutLine.split('/'); updateList.append(updateParts[0]); } } } checker->terminate(); checker->deleteLater(); return updateList; }