Skip to content

Commit e1fcfc3

Browse files
committed
ensure everything during the update process is executed in the chroot
1 parent f783d8e commit e1fcfc3

1 file changed

Lines changed: 81 additions & 70 deletions

File tree

src/lib/UpdateManager.cpp

Lines changed: 81 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
#include <QFileInfo>
77
#include <QSettings>
88
#include <QDir>
9-
#include <QThread>
109
#include <QUrl>
10+
#include <QThread>
1111
#include <algorithm>
1212
#include <climits>
1313

@@ -205,7 +205,7 @@ bool UpdateManager::applyUpdate() {
205205
Logger::instance().info("=== Starting System Update Process ===");
206206

207207
// Acquire lock file to prevent concurrent update runs.
208-
// This matches the original nuts shell script behaviour.
208+
// This matches the original nuts behaviour.
209209
const QString lockPath = "/var/run/nuts-cpp.lock";
210210
QFile lockFile(lockPath);
211211
if (lockFile.exists()) {
@@ -314,7 +314,7 @@ bool UpdateManager::applyUpdate() {
314314
Logger::instance().success("Cleanup crew finished.");
315315

316316
// Create nx-pkgmgr-policy symlinks.
317-
Logger::instance().info("--- Step: Installing package manager policy symlinks ---");
317+
Logger::instance().info("--- Step: Installing policy symlinks ---");
318318
{
319319
QString output, error;
320320
QStringList aptTools = {"apt", "apt-cache", "apt-cdrom", "apt-config", "apt-get", "apt-mark"};
@@ -563,7 +563,7 @@ bool UpdateManager::prepareUpdateTools() {
563563
}
564564

565565
m_pkgManagerPath = appRunPath;
566-
Logger::instance().info("Using package manager: " + m_pkgManagerPath);
566+
Logger::instance().info("Using dpkg tooling at: " + m_pkgManagerPath);
567567

568568
// dpkg → AppRun (matching original: ln -svf "$AIPKG_MANAGER" /usr/bin/dpkg)
569569
bool lnOk = m_sysInterface->executeInOverlay({"/usr/bin/ln", "-svf", appRunPath, "/usr/bin/dpkg"}, output, error);
@@ -664,7 +664,7 @@ bool UpdateManager::performPackageUpdates() {
664664
QString nvidiaDir = otaDir + "/nvidia";
665665

666666
Logger::instance().info("OTA updates dir: " + updatesDir);
667-
Logger::instance().info("dpkg manager path: " + m_pkgManagerPath);
667+
Logger::instance().info("dpkg tooling path: " + m_pkgManagerPath);
668668

669669
// Detect NVIDIA on the host via /proc (shared with chroot).
670670
bool isNvidia = QDir("/proc/driver/nvidia").exists();
@@ -675,94 +675,105 @@ bool UpdateManager::performPackageUpdates() {
675675
}
676676
Logger::instance().info(QString("NVIDIA hardware: %1").arg(isNvidia ? "yes" : "no"));
677677

678-
// Verify the updates directory exists and list its contents
679-
{
680-
QString output, error;
681-
bool dirExists = m_sysInterface->executeInOverlay(
682-
{"/usr/bin/test", "-d", updatesDir}, output, error);
683-
Logger::instance().info(QString("Updates dir exists: %1").arg(dirExists ? "yes" : "NO - this is a problem"));
684-
if (dirExists) {
685-
m_sysInterface->executeInOverlay({"/usr/bin/find", updatesDir, "-name", "*.deb"}, output, error);
686-
Logger::instance().debug("Debs found:\n" + output.trimmed());
687-
if (output.trimmed().isEmpty())
688-
Logger::instance().warning("No .deb files found in " + updatesDir);
689-
}
678+
QString output, error;
679+
680+
// Verify the dpkg tooling is present inside the chroot before attempting anything.
681+
if (!m_sysInterface->executeInOverlay({"/usr/bin/test", "-f", m_pkgManagerPath}, output, error)) {
682+
Logger::instance().error("dpkg tooling not found inside chroot: " + m_pkgManagerPath);
683+
return false;
690684
}
685+
Logger::instance().info("dpkg tooling confirmed present: " + m_pkgManagerPath);
691686

692-
// Phase 1: Unpack.
693-
Logger::instance().info("Phase 1: Unpacking OTA content...");
694-
{
695-
QString searchPaths = updatesDir;
696-
if (isNvidia)
697-
searchPaths += " " + nvidiaDir;
687+
// --- Phase 1: Collect .deb paths ---
688+
Logger::instance().info("--- Phase 1: Collecting .deb packages ---");
698689

699-
QString unpackCmd = QString(
700-
"export DEBIAN_FRONTEND=noninteractive && "
701-
"export TMPDIR=/tmp && "
702-
"find %1 -name '*.deb' -print0 | "
703-
"xargs -0 -n 120 %2 --force-all --unpack"
704-
).arg(searchPaths, m_pkgManagerPath);
690+
QStringList findArgs = {"/usr/bin/find", updatesDir, "-name", "*.deb", "-print0"};
691+
if (isNvidia)
692+
findArgs << nvidiaDir;
705693

706-
Logger::instance().debug("Unpack command: " + unpackCmd);
694+
bool findOk = m_sysInterface->executeInOverlay(findArgs, output, error);
695+
if (!findOk && output.trimmed().isEmpty()) {
696+
Logger::instance().error("find failed to enumerate .deb packages");
697+
if (!error.trimmed().isEmpty()) Logger::instance().error("find stderr: " + error.trimmed());
698+
return false;
699+
}
707700

708-
QString output, error;
709-
bool ok = m_sysInterface->executeInOverlay({"/bin/sh", "-c", unpackCmd}, output, error);
710-
if (!output.trimmed().isEmpty()) Logger::instance().debug("unpack stdout: " + output.trimmed());
711-
if (!error.trimmed().isEmpty()) Logger::instance().debug("unpack stderr: " + error.trimmed());
712-
if (!ok) {
713-
Logger::instance().error("Failed to unpack OTA content (exit non-zero)");
701+
// Split null-delimited output into individual paths.
702+
QStringList debs = output.split('\0', Qt::SkipEmptyParts);
703+
Logger::instance().info(QString("Found %1 .deb package(s).").arg(debs.size()));
704+
for (const QString& deb : debs)
705+
Logger::instance().debug(" deb: " + deb);
706+
707+
if (debs.isEmpty()) {
708+
Logger::instance().warning("No .deb packages found. Nothing to install.");
709+
return true;
710+
}
711+
712+
// --- Phase 2: Unpack in batches of 120 ---
713+
Logger::instance().info("--- Phase 2: Unpacking packages ---");
714+
715+
const int batchSize = 120;
716+
int totalBatches = (debs.size() + batchSize - 1) / batchSize;
717+
718+
for (int i = 0; i < debs.size(); i += batchSize) {
719+
QStringList batch = debs.mid(i, batchSize);
720+
int batchNum = (i / batchSize) + 1;
721+
Logger::instance().info(QString("Unpacking batch %1/%2 (%3 package(s))...")
722+
.arg(batchNum).arg(totalBatches).arg(batch.size()));
723+
724+
QStringList unpackArgs = {m_pkgManagerPath, "--force-all", "--unpack"};
725+
unpackArgs << batch;
726+
727+
if (!m_sysInterface->executeInOverlay(unpackArgs, output, error)) {
728+
Logger::instance().error(QString("Unpack failed on batch %1/%2").arg(batchNum).arg(totalBatches));
729+
if (!error.trimmed().isEmpty()) Logger::instance().error("unpack stderr: " + error.trimmed());
714730
return false;
715731
}
716-
Logger::instance().success("Phase 1 unpack complete.");
732+
Logger::instance().success(QString("Batch %1/%2 unpacked.").arg(batchNum).arg(totalBatches));
717733
}
718734

719-
// Phase 2: Configure loop until audit reports clean.
720-
Logger::instance().info("Phase 2: Configuring packages...");
721-
int maxPasses = 15;
722-
int pass = 1;
735+
Logger::instance().success("All packages unpacked.");
736+
737+
// --- Phase 3: Configure + audit loop ---
738+
Logger::instance().info("--- Phase 3: Configuring packages ---");
739+
740+
const int maxPasses = 15;
723741
QString lastAudit;
724742

725-
while (pass <= maxPasses) {
726-
Logger::instance().info(QString("Configuration pass %1/%2").arg(pass).arg(maxPasses));
743+
for (int pass = 1; pass <= maxPasses; ++pass) {
744+
Logger::instance().info(QString("Configuration pass %1/%2...").arg(pass).arg(maxPasses));
727745

728-
QString output, error;
729-
QStringList confArgs;
730-
confArgs << "/bin/sh" << "-c"
731-
<< QString("export DEBIAN_FRONTEND=noninteractive && export TMPDIR=/tmp && "
732-
"%1 --force-all --configure -a").arg(m_pkgManagerPath);
733-
bool confOk = m_sysInterface->executeInOverlay(confArgs, output, error);
734-
if (!output.trimmed().isEmpty()) Logger::instance().debug("configure stdout: " + output.trimmed());
735-
if (!error.trimmed().isEmpty()) Logger::instance().debug("configure stderr: " + error.trimmed());
736-
Logger::instance().debug(QString("configure exit: %1").arg(confOk ? "0" : "non-zero (expected, --force-all)"));
737-
738-
QStringList auditArgs;
739-
auditArgs << m_pkgManagerPath << "--audit";
740-
m_sysInterface->executeInOverlay(auditArgs, output, error);
741-
QString currentAudit = output.trimmed();
746+
// --configure -a: configure all unpacked packages; ignore non-zero exit (may be partial)
747+
m_sysInterface->executeInOverlay({m_pkgManagerPath, "--force-all", "--configure", "-a"}, output, error);
742748

743-
if (!currentAudit.isEmpty())
744-
Logger::instance().info("dpkg --audit output:\n" + currentAudit);
749+
// --audit: report packages in inconsistent state
750+
m_sysInterface->executeInOverlay({m_pkgManagerPath, "--audit"}, output, error);
751+
QString currentAudit = output.trimmed();
745752

746753
if (currentAudit.isEmpty()) {
747-
Logger::instance().success("Package configuration converged after " + QString::number(pass) + " pass(es).");
748-
m_sysInterface->executeInOverlay({"/usr/bin/rm", "-rf", "/tmp/pkgman-extracted"}, output, error);
749-
return true;
754+
Logger::instance().success(QString("Package configuration converged after %1 pass(es).").arg(pass));
755+
break;
756+
}
757+
758+
Logger::instance().info("dpkg --audit output:\n" + currentAudit);
759+
760+
if (pass > 1 && currentAudit == lastAudit) {
761+
Logger::instance().error("Package configuration stuck — no progress between passes.");
762+
return false;
750763
}
751764

752-
if (currentAudit == lastAudit && pass > 1) {
753-
Logger::instance().error("Package configuration stuck — no progress between passes. Aborting.");
754-
Logger::instance().error("Final dpkg --audit:\n" + currentAudit);
765+
if (pass == maxPasses) {
766+
Logger::instance().error(QString("Package configuration failed to converge after %1 passes.").arg(maxPasses));
755767
return false;
756768
}
757769

758770
lastAudit = currentAudit;
759-
pass++;
760771
QThread::sleep(1);
761772
}
762773

763-
Logger::instance().error(QString("Package configuration failed to converge after %1 passes.").arg(maxPasses));
764-
Logger::instance().error("Final dpkg --audit:\n" + lastAudit);
765-
return false;
774+
m_sysInterface->executeInOverlay({"/usr/bin/rm", "-rf", "/tmp/pkgman-extracted"}, output, error);
775+
Logger::instance().success("Package updates applied successfully.");
776+
return true;
766777
}
767778

768779
bool UpdateManager::runCleanupCrew() {
@@ -813,7 +824,7 @@ bool UpdateManager::runCleanupCrew() {
813824
if (!output.trimmed().isEmpty()) Logger::instance().info("CCU stdout: " + output.trimmed());
814825
if (!error.trimmed().isEmpty()) Logger::instance().info("CCU stderr: " + error.trimmed());
815826
if (!ok) {
816-
Logger::instance().error("Cleanup crew script exited with non-zero status");
827+
Logger::instance().error("Cleanup crew exited with non-zero status");
817828
return false;
818829
}
819830
Logger::instance().success("Cleanup crew completed successfully.");

0 commit comments

Comments
 (0)