Skip to content

fix: disable active swap partitions in umount_all_existing() - #4857

Open
Kolgrim33 wants to merge 1 commit into
archlinux:masterfrom
Kolgrim33:fix/swapoff-existing-partitions
Open

Kolgrim33 wants to merge 1 commit into
archlinux:masterfrom
Kolgrim33:fix/swapoff-existing-partitions

Conversation

@Kolgrim33

Copy link
Copy Markdown

PR Description:

When an existing swap partition is active on a target disk, lsblk --json reports "[SWAP]" in its mountpoints list.

Previously, DeviceHandler.umount_all_existing() passed FilesystemType.LINUX_SWAP partitions to umount(partition.path, recursive=True), which attempted to execute umount -R [SWAP] (failing with exit code 32) and never disabled active swap on the partition—causing subsequent parted operations to fail with Device or resource busy.

This PR:

  • Adds a swapoff() helper in archinstall.lib.disk.utils that disables swap when [SWAP] is present in lsblk_info.mountpoints.
  • Calls swapoff(partition.path) for FilesystemType.LINUX_SWAP partitions in DeviceHandler.umount_all_existing().
  • Filters out Path('[SWAP]') in umount() so pseudo-mountpoints are never passed to umount.
  • Adds unit tests in tests/test_swapoff_existing.py.

Tests and Checks

  • I have tested the code!

@Kolgrim33
Kolgrim33 requested a review from Torxed as a code owner October 25, 2026 18:55
When an existing swap partition is active on a target disk, lsblk
reports '[SWAP]' in its mountpoints array.

Previously, DeviceHandler.umount_all_existing() called umount() on
LINUX_SWAP partitions, causing umount() to execute 'umount -R [SWAP]'
(which fails with exit code 32) and leaving swap active on the device so
subsequent partition table modifications fail with 'Device or resource
busy'.

Add a swapoff() helper in archinstall.lib.disk.utils, call it for
LINUX_SWAP partitions in umount_all_existing(), and filter out '[SWAP]'
in umount().
@Kolgrim33
Kolgrim33 force-pushed the fix/swapoff-existing-partitions branch from 17594da to b15f2d8 Compare October 25, 2026 18:59

@h8d13 h8d13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks okay to me.

Closes: #4235

@h8d13

h8d13 commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Not sure about the policy here with tests, we used not to add too many and just rely on repro then re-run.

@xander-lin xander-lin left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — the swap branch in umount_all_existing() is correct, and a plain swap partition is handled now. Below is why the filter in umount() does not cover the same failure, and the layouts the patch cannot reach.

1. [SWAP] reaches the model through two paths, and the filter is downstream of both

Filtering at utils.py:179 addresses the last consumer of the marker. It is not the only one, and it is not the one that fires first.

Path A — via mountpoint. For an active swap partition lsblk reports mountpoints == ['[SWAP]'], and _PartitionInfo.from_partition() copies that straight into the model (models/device.py:620). from_existing_partition() then takes the first entry as the mountpoint (models/device.py:969), and Installer._mount_partition() checks if part_mod.mountpoint: before it ever reaches elif part_mod.is_swap(): (installer.py:377, :394). On this branch, installing onto a disk whose swap was left active produces:

mount: /mnt/[SWAP]: unknown filesystem type 'swap'
DiskError: Could not mount /dev/loop1p2

The swap branch in _mount_partition is therefore dead code for any partition that was active at scan time — is_swap() is true, but the mountpoint branch wins.

Path B — via mountpoints. Luks2.lock() iterates the child's mountpoints and passes each one to umount() (luks.py:173-175), which is the only call site in the tree that hands umount() a mountpoint rather than a device. umount() then calls get_lsblk_info() on that value (utils.py:177) before the filter on line 179 is evaluated, so the marker raises there first.

Both paths come from the same place: a formatted string from lsblk is being carried in fields that are supposed to hold paths. Sanitising mountpoint and mountpoints at the LsblkInfo boundary leaves no consumer to guard, and covers path A and path B at once.

2. swapoff() decides state from the same display string

lsblk_info = get_lsblk_info(path)
if Path('[SWAP]') not in lsblk_info.mountpoints:
	return

lsblk prints [SWAP] for a swap partition but only the mount path for a swap file, so this returns silently for swapfiles — and a silent return from a cleanup helper is indistinguishable from "nothing to do". The kernel reports the state directly: /proc/swaps lists the area itself in its first column (/dev/dm-0, /mnt/swapfile), which is also what swapon --show=NAME returns. Deciding from that keeps the answer independent of how lsblk chooses to render something.

3. lock() closes the mapper without deactivating swap

Deactivating swap is a precondition for cryptsetup close, not only for umount. lock() unwinds mounts and closes in the same loop (luks.py:171-179), so a mapper that still has swap on it fails at close. The same loop also cannot reach LVM-on-LUKS, where the swap sits on a logical volume one level below the mapper — there children is the LV set and the loop's single level does not go deeper.

4. Scope note

umount_all_existing() iterates the partitions found on the physical device rather than the ones in the layout; that predates this PR and I am not asking to change the granularity here. It is worth stating explicitly, though: swap or LUKS containers outside the final layout are deactivated by cleanup and never re-activated, and genfstab takes its swap entries from /proc/swaps, so "deactivated" is an intermediate state rather than a result.

Related

#4745 ("Handle active swap on existing partitions", open since Aug 31) targets the same cleanup and sanitises LsblkInfo, which is the boundary described above. Two implementations for the same bug should probably be consolidated rather than merged separately.

None of this blocks the unencrypted-swap fix. If the layouts above are out of scope for #4235, the Fixes should stay open until they are covered.

This branch has not been deployed

No deployments
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.

4 participants