fix(mounts): escape special characters in fstab mount paths#6911
fix(mounts): escape special characters in fstab mount paths#6911ekalinin wants to merge 1 commit into
Conversation
A vz + virtiofs mount whose host path contains a space (e.g. "/Volumes/External HD") was never mounted in the guest, and no error was reported. cloud-init's cc_mounts writes each mount into /etc/fstab with a bare "\t".join(fields) and does not octal-escape the mount point (canonical/cloud-init#3603). A space/tab in the path therefore produces an unparseable fstab line that mount(8) silently skips because of the nofail option, so the mount simply never appears. cc_mounts already creates the mount-point directory from the unescaped value, so only the fstab line needs repair. In boot.Linux/05-lima-mounts.sh, octal- escape the mount-point field of cloudconfig virtiofs entries (parsing with -F'\t' isolates the field reliably and makes the pass idempotent), then mount -t virtiofs -a. The existing SELinux remount now decodes the escaped mount point before calling mount(8), mirroring the unescape in boot.Linux/04-persistent-data-volume.sh. This complements, but does not depend on, the upstream cloud-init fix canonical/cloud-init#6911; distros will ship that only years from now. Fixes: lima-vm#5136 Link: abiosoft/colima#1471 Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
blackboxsw
left a comment
There was a problem hiding this comment.
Thank you for taking on this work and contributing to cloud-init.
I have a couple of nits inline on this before we land.
Also, given that this aspect of mount testing hasn't historically been supported, it would be good to have a simple integration test that covers this case. Your tests case documented in the PR looks straightforward and could be provided as a one-off tests/integration-tests/bugs/test_gh691.py that provides your example user-data, asserts no errors in cloud-init logs, asserts that the escaped values made it into /etc/fstab and that /mnt/Cdrom Drive exists.
Thank you again for your work here.
Thank you for the review and the helpful pointers! I've pushed changes addressing the inline nits, and added the integration test as suggested. A couple of notes:
Let me know if you'd like any changes. Thanks again for the guidance! |
cc_mounts wrote /etc/fstab entries by joining fields with tabs without any escaping, so a space (or tab/newline/backslash) in the device (fs_spec) or mount point (fs_file) produced an unparseable line and `mount -a` failed with "parse error at line N -- ignored". The documented `\040` workaround also failed, because the literal characters ended up in the created directory name instead of a space. Octal-escape the four characters that are significant in a whitespace-separated fstab field (`\134`, `\040`, `\011`, `\012`) when writing the fs_spec and fs_file fields (the only fields where escaping is meaningful), matching util-linux's mangle/unmangle behaviour. The mount point directory is still created from the unescaped value, and parse_fstab() unescapes the device token so re-runs stay idempotent. The escape/unescape helpers live in cloudinit.util and are reused by util.mounts(), which previously only undid `\040` for spaces and now decodes tabs, newlines and backslashes as well. Add unit tests plus a one-off integration test (test_gh6911.py) and document the behaviour in the cc_mounts examples. LP: #1861903 Fixes canonicalGH-3603
62a062b to
8e4984f
Compare
Mount points containing spaces (and tabs/newlines/backslashes) are now
octal-escaped (\040, \011, \012, \134) when written to /etc/fstab, so
mount -acan parse them. Directories are still created with real characters.LP: #1861903
Fixes GH-3603
Proposed Commit Message
Additional Context
Fixes GH-3603 (migrated from Launchpad LP: #1861903).
cc_mountsbuilt each/etc/fstabline with a bare"\t".join(entry), so anywhitespace inside a field (most commonly a space in the mount point) was written
verbatim. Because fstab fields are whitespace-separated, the resulting line is
unparseable and
mount -afails withparse error at line N -- ignored. Thedocumented user workaround (
\040in the YAML) didn't help either: the literalcharacters
\040ended up as the directory name instead of a space.This change octal-escapes the four characters significant in an fstab field
(backslash
\134, space\040, tab\011, newline\012), applied only tothe
fs_specandfs_filefields — the other fields never contain charactersthat need escaping. Backslash is escaped first so the escapes we introduce are
not re-escaped. This matches util-linux's
mangle/unmanglebehaviour.Notes:
- The escape/unescape helpers (
escape_fstab_field/unescape_fstab_field)live in
cloudinit.util;util.mounts()now reusesunescape_fstab_fieldinstead of its previous spaces-only
replace("\040", " "), so it alsodecodes escaped tabs, newlines and backslashes from
/proc/mounts.- The mount-point directory is still created from the unescaped config value
(
util.ensure_dir), so the real directory contains a real space.-
parse_fstab()unescapes the device token before using it as the dedup key,keeping the module idempotent (a re-run won't add a duplicate entry).
- A docs example (
cc_mounts/example3.yaml) and a note about automaticescaping were added.
Test Steps
Reproduces the bug and verifies the fix on a live system. No extra hardware is
needed: a network
fs_spec(contains:) is kept bycc_mountswithout thedevice existing, and
noautokeepsmount -afrom touching it at boot, so thetest isolates the fstab-escaping behaviour.
user-data.yaml:step that printed
parse error ... -- ignoredbefore the fix):Before the fix, step 2 shows a literal space in /etc/fstab and steps 3/mount -a
fail with parse error at line N -- ignored.
Unit tests:
New cases:
TestFstabHandling.test_fstab_mountpoint_with_spaces(space in boththe device and the mount point) and
test_fstab_mountpoint_with_spaces_idempotentin test_cc_mounts.py, plus
TestFstabEscaping(escape/unescape round-trip) intest_util.py.
Integration test:
tests/integration_tests/bugs/test_gh6911.pyboots the user-data above andasserts clean cloud-init logs, the escaped value in
/etc/fstab, and that/mnt/Cdrom Driveexists.Merge type