Skip to content

sysupgrade: install an archive from /tmp on an 18 MB XM510 - #2649

Merged
widgetii merged 2 commits into
masterfrom
sysupgrade-archive-room
Oct 21, 2026
Merged

widgetii merged 2 commits into
masterfrom
sysupgrade-archive-room

Conversation

@widgetii

Copy link
Copy Markdown
Member

Problem

Closes #2618. On an XM510 at its stock mem=18M, no archive staged in /tmp could be installed, which includes the WebUI's "install from a file". Confirmed on a lab XM510 + JX-H62, not only under emulation:

No room to unpack /tmp/openipc.xm510-nor-lite.tgz in /tmp: needs 4190 KB, 3532 KB free.
Use 'sysupgrade --channel nightly' instead -- it streams and stages no second copy. Aborting.

Fixing that refusal exposed two more problems on the same board.

Change

  1. The RAM guards never ran on this kernel. usable_ram() read only MemAvailable, which kernels before 3.14 don't report (XM510 3.0, XM530 3.10, hi3516cv100 3.0.8), and on those kernels it gave no opinion. It now estimates the figure with the same arithmetic as si_mem_available():

    • free pages above the low watermark (min_free_kbytes plus a quarter);
    • plus file page cache and reclaimable slab, each less the part reclaim can't take.

    check_unpack_ram, check_flash_ram and their majestic stop now work on those kernels. On the lab XM510's real /proc/meminfo the estimate gives 8,302 KB; that figure is pinned in a test.

  2. /tmp's cap refused what RAM could hold. A tmpfs defaults to half of RAM, 7,804 KB here, against 8,341 KB for an archive plus its images. A cap is a ceiling on pages as they're written, not memory set aside. When the cap is all that is short and RAM was measured to hold the unpack, grow_tmp() remounts /tmp with a larger size= for this run. A run that stays up gets the old cap back. With no RAM figure, or on a ramfs, the refusal stands.

  3. majestic was OOM-killed entering upgrade mode. On a freshly booted XM510 with the 4 MB archive in /tmp, majestic_api prepare tipped the board over. After drop_caches, majestic faulted its pages back with nothing left to reclaim (548 KB free, 40 KB of file cache, 4,264 KB of tmpfs), and the kernel killed it. The run died with it, leaving /tmp/sysupgrade.lock and a camera with no majestic until rebooted. Reproduced twice, traced to that curl. An archive in /tmp that won't unpack beside majestic now has majestic stopped up front, since check_unpack_ram would stop it later anyway, instead of being asked to prepare. With room, majestic still prepares as before.

This builds on #2648 (PID 1 hand-off), which is what makes the rootfs write itself safe on this board.

Hardware tested on

XM510 + JX-H62, xm510_lite from firmware master (kernel 3.0.101, mem=18M, 15.6 MB MemTotal). This branch's sysupgrade, comment-stripped as the build ships it, was installed in the overlay for the test and removed afterwards.

Evidence

Before, with the image's own sysupgrade:

No room to unpack /tmp/openipc.xm510-nor-lite.tgz in /tmp: needs 4190 KB, 3532 KB free.

With only fixes 1 and 2, on a freshly booted camera:

majestic invoked oom-killer ... Killed process 645 (majestic) total-vm:8692kB, anon-rss:1524kB, file-rss:3304kB
(sysupgrade gone; /tmp/sysupgrade.lock left; no majestic)

After, with all three fixes: sysupgrade -z --archive=/tmp/openipc.xm510-nor-lite.tgz, three runs, each starting about a minute after the previous reboot (a fresh majestic, the case that OOMed):

Short of memory by 978 KB with majestic running: stopping it, which gives back what it holds in RAM.
Raised the /tmp cap from 7804 KB to 8898 KB for the unpack: 5546 KB of RAM is there to back it.
RAM left to flash from: 3866 KB
Handing PID 1 over to the RAM root.
F1: booted after 46 s, kernel 495e6eae / rootfs b5d974d1 = images, majestic running, 0 OOM
F2: booted after 50 s, kernel 495e6eae / rootfs b5d974d1 = images, majestic running, 0 OOM
F3: booted after 45 s, kernel 495e6eae / rootfs b5d974d1 = images, majestic running, 0 OOM

After each reboot /tmp is back to its 7,804 KB cap.

Not tested: the WebUI upload itself (--web). On that path majestic has already entered upgrade mode before sysupgrade runs, so fix 3 doesn't apply; fixes 1 and 2 do.

Tests:

  • test_sysupgrade.sh: 328 checks pass. New or changed:
    • a full /tmp with no RAM figure is still refused;
    • a /tmp short only by its cap is raised when RAM backs it;
    • MemAvailable estimated as si_mem_available() (8,302 KB from the XM510's meminfo);
    • an incomplete meminfo is no opinion;
    • an archive that can't unpack beside majestic stops it before prepare;
    • one with room leaves majestic to prepare.
  • STRICT=1 test_shell_parse.sh and test_strip_shell_comments.sh: clean.

Scope

  • No kernel patches under general/package/all-patches/linux/ (those go to OpenIPC/linux)
  • No files specific to a single retail camera model (those go to OpenIPC/builder)
  • No probing or bring-up tooling (that goes to OpenIPC/ipctool)
  • Nothing under general/overlay/ or in a shared load_<vendor> script hardcodes a value specific to my board
  • Package sources come from an OpenIPC repository, and any version bump keeps at least the specificity of the pin it replaces (a new package should pin a full 40-character SHA)
  • No LD_PRELOAD, and no binaries that cannot be rebuilt from source
  • New code is selected by a defconfig, so CI actually builds it

No archive could be installed on an XM510 at its stock mem=18M -- the
WebUI's "install from a file" included (#2618). Three things stood in the
way, and each is fixed here:

- The RAM guards never ran. usable_ram() read only MemAvailable, which
  kernels before 3.14 do not report (XM510 3.0, XM530 3.10, hi3516cv100
  3.0.8). It now estimates it there, with the arithmetic si_mem_available()
  uses, so check_unpack_ram and check_flash_ram -- and the majestic stop
  they fall back on -- work on those kernels too.

- /tmp's cap refused it. A tmpfs defaults to half of RAM, 7804 KB here,
  against the 8341 KB an archive and its images take together, though the
  RAM behind it had room. When the cap is all that is short and RAM was
  measured to hold the unpack, grow_tmp() raises it for the run; a run
  that stays up gets the old cap back.

- majestic was OOM-killed entering upgrade mode. With the 4 MB archive in
  a tmpfs nothing can reclaim, a majestic started a minute earlier faulted
  its pages back after drop_caches during prepare and was killed, and the
  run died with it, leaving the lock and no majestic. An archive in /tmp
  that will not unpack beside majestic now has it stopped up front, as
  check_unpack_ram would stop it later anyway, instead of asked to prepare.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Allow sysupgrade archives in /tmp on low-memory cameras

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Estimate available RAM on older kernels so upgrade safety checks can run.
• Raise /tmp’s tmpfs cap when measured RAM can support the archive unpack.
• Stop majestic before preparation when a staged archive cannot unpack beside it.
Diagram

graph TD
  Archive["Staged archive"] --> EarlyCheck{"RAM short?"} -->|yes| Majestic["Stop majestic"] --> UnpackGuard["Unpack RAM guard"] --> CapGuard{"Cap short?"} -->|yes| Remount["Grow tmpfs cap"] --> Upgrade["Flash images"]
  EarlyCheck -->|no| UnpackGuard
  CapGuard -->|no| Upgrade
  Memory["Available RAM estimate"] --> EarlyCheck
  Memory --> UnpackGuard
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Increase the default /tmp cap system-wide
  • ➕ Avoids a per-upgrade remount and restoration path.
  • ➖ Raises the limit for every workload, including those without measured RAM to support it.
  • ➖ Does not address missing older-kernel RAM checks or majestic’s preparation-time OOM.

Recommendation: Keep the conditional sysupgrade remount: it changes the cap only for a measured, viable unpack and preserves the existing refusal when RAM is unknown. A global cap increase is simpler but loses that safeguard.

Files changed (2) +174 / -13

Bug fix (1) +98 / -12
sysupgradeMake archive upgrades viable under tight RAM and tmpfs limits +98/-12

Make archive upgrades viable under tight RAM and tmpfs limits

• Estimates available RAM when MemAvailable is absent, reusing that figure for upgrade guards. Extracts archive unpack sizing, conditionally raises and restores the /tmp cap, and stops majestic before preparation when a staged archive cannot unpack alongside it.

general/overlay/usr/sbin/sysupgrade

Tests (1) +76 / -1
test_sysupgrade.shCover older-kernel RAM estimates and constrained archive upgrades +76/-1

Cover older-kernel RAM estimates and constrained archive upgrades

• Stubs the low-memory watermark and tests the XM510-derived available-RAM estimate. Adds cases for unknown RAM, conditional /tmp cap growth, and stopping or preparing majestic according to unpack headroom.

.github/scripts/test_sysupgrade.sh

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Watchdogs can reset cameras during upgrades ✓ Resolved
Description
free_resources calls stop_majestic_for_memory before it assigns wdog_owner_pid, so the stop
function cannot enforce its watchdog-owner guard. On a camera where majestic holds an armed
watchdog, the early stop can leave it unfed through unpacking and flashing, while the later
ownership scan no longer sees the original owner and does not enable the flash-phase keeper.
Code

general/overlay/usr/sbin/sysupgrade[R1899-1900]

+		if majestic_short_for_archive; then
+			stop_majestic_for_memory "$majestic_short_kb"
Evidence
The guard reads a variable populated only later in the same function. The script documents that a
watchdog whose userspace owner disappears can remain armed, and starts its keeper only when the
earlier scan recorded such an owner.

general/overlay/usr/sbin/sysupgrade[1361-1371]
general/overlay/usr/sbin/sysupgrade[1899-1900]
general/overlay/usr/sbin/sysupgrade[1978-1979]
general/overlay/usr/sbin/sysupgrade[1821-1835]
general/overlay/usr/sbin/sysupgrade[3276-3283]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new early majestic stop runs before watchdog ownership is recorded, bypassing the guard against stopping its watchdog owner and preventing the later scan from enabling the keeper.
## Fix Focus Areas
- general/overlay/usr/sbin/sysupgrade[1899-1900]
- general/overlay/usr/sbin/sysupgrade[1978-1979]
- general/overlay/usr/sbin/sysupgrade[1368-1371]
## Recommended Fix
Record watchdog ownership before any possible early majestic stop, retain that original ownership for the flash-phase keeper, and make the early stop respect the existing owner guard.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Some archives lose their preparation path ✓ Resolved
Description
free_resources enters the new majestic_short_for_archive branch even if
stop_majestic_for_memory refuses or fails, bypassing both majestic_api prepare and its legacy
fallback. When an archive is measured short but majestic cannot be stopped, the later RAM check can
refuse an upgrade that could have proceeded after prepare freed the video pipeline.
Code

general/overlay/usr/sbin/sysupgrade[R1899-1901]

+		if majestic_short_for_archive; then
+			stop_majestic_for_memory "$majestic_short_kb"
+		elif majestic_api prepare; then
Evidence
The conditional selects prepare only when the shortfall predicate is false, regardless of the stop
command's result. The stop function has explicit refusal and failure returns, and the later unpack
guard refuses if memory remains short.

general/overlay/usr/sbin/sysupgrade[1899-1906]
general/overlay/usr/sbin/sysupgrade[1368-1396]
general/overlay/usr/sbin/sysupgrade[1450-1473]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A measured archive shortfall selects the early-stop branch even when stopping majestic fails, leaving neither the prepare request nor its legacy fallback attempted.
## Fix Focus Areas
- general/overlay/usr/sbin/sysupgrade[1899-1904]
- general/overlay/usr/sbin/sysupgrade[1368-1396]
## Recommended Fix
Branch on whether the early stop succeeds, and retain an appropriate preparation or clean refusal path when it does not; account for a stop failure that may already have terminated the supervisor.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. A no-reboot upgrade can keep a raised cap ✓ Resolved
Description
restore_resources attempts to restore the old /tmp size without checking whether the remount
succeeded. On a successful --no_reboot archive upgrade, unpacked images can still occupy more than
the old cap when restoration runs, so the remount fails and the larger cap remains for the running
camera.
Code

general/overlay/usr/sbin/sysupgrade[R2001-2002]

+	# Best effort: a /tmp still holding more than the old cap keeps the new one.
+	[ -n "$tmp_cap_kb" ] && mount -o remount,size=${tmp_cap_kb}k /tmp 2>&3
Evidence
The new restoration attempt precedes removal of even the unpack list on the no-reboot path. Unlike
an abort, that path does not call discard_unpacked, so its images can prevent shrinking the tmpfs
to its recorded original size.

general/overlay/usr/sbin/sysupgrade[2000-2003]
general/overlay/usr/sbin/sysupgrade[3233-3239]
general/overlay/usr/sbin/sysupgrade[1712-1722]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The no-reboot success path restores the old tmpfs cap while unpacked images can still exceed it, and the failed remount is silently ignored.
## Fix Focus Areas
- general/overlay/usr/sbin/sysupgrade[2001-2002]
- general/overlay/usr/sbin/sysupgrade[3233-3238]
## Recommended Fix
On a run that stays up, remove upgrade-owned unpacked files when they are no longer needed before restoring the cap. Check and report a remount failure if the cap still cannot be restored.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread general/overlay/usr/sbin/sysupgrade Outdated
Comment thread general/overlay/usr/sbin/sysupgrade Outdated
Comment thread general/overlay/usr/sbin/sysupgrade
Review of the archive change:

- The early stop ran before the watchdog owner was recorded, so a
  majestic that held the watchdog went unrecorded and the RAM-phase
  keeper never armed. The owner is now recorded first. Stopping an owning
  majestic there is allowed: its own stop gives the device back with the
  magic close, and where a driver ignores that, the recorded ownership
  arms the keeper from the pivot on.
- A stop that was refused or failed skipped prepare and its legacy
  fallback altogether. It now falls back to them.
- On a --no_reboot exit the unpacked images stayed in /tmp, so a cap
  raised for them could not be put back. They are discarded first, as
  die() does.
@widgetii
widgetii merged commit dff593d into master Oct 21, 2026
141 checks passed
@widgetii
widgetii deleted the sysupgrade-archive-room branch October 21, 2026 08:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

xm510: sysupgrade --archive from /tmp cannot fit at the board's mem=18M

1 participant