Eric Curtin 21ab2fe6ec shared/switch-root: sync only file systems becoming unreachable, not everything
switch_root() calls a blanket sync() before detaching the old root
file system, in order to make sure it is in a good state before it
becomes unreachable via MNT_DETACH/pivot_root().

A global sync() however flushes out *every* mounted file system on
the system, not just the ones we are actually about to detach. On
real-world systems that commonly have several additional mounted file
systems (separate /home, /var, additional data partitions, network
shares, removable media, ...) this needlessly delays switch_root() with
completely unrelated I/O. This matters in particular for
initrd-switch-root.service, which runs this code on the critical path
of pretty much every single boot with an initrd, and for soft-reboot.

Replace the global sync() with a new sync_departing_file_systems()
helper that walks /proc/self/mountinfo and calls syncfs() on every
file system except:

  - 'new_root' and anything mounted below it: these remain mounted
    and reachable after the transition and keep being synced normally
    as part of their regular life cycle, so they don't need to be
    force-flushed here.

  - API/pseudo file systems (proc, sysfs, cgroupfs, autofs, ...),
    network file systems, and overlayfs (which has no backing store
    of its own), as determined by the new fstype_is_worth_syncing()
    predicate. There is nothing meaningful to flush on any of these,
    and more importantly, opening an untriggered autofs mount point
    would needlessly trigger it, and opening a stale network mount
    could block for a long time - exactly what we are trying to avoid
    on this code path.

  - Any flavour of FUSE (plain 'fuse', 'fuseblk', or a
    'fuse.<subtype>', e.g. sshfs, rclone, gvfs, ntfs-3g, exfat-fuse,
    ...), classified via the new fstype_is_fuse() predicate in
    src/basic/mountpoint-util.c, plus a few other, non-FUSE guest/host
    file sharing file systems with the same "backed by a companion
    daemon/hypervisor that could be wedged" risk profile (virtiofs,
    vboxsf, vmhgfs). All I/O against any of these, including the
    syncfs() we'd otherwise issue, is routed through an arbitrary
    userspace daemon (or, for virtiofs/vboxsf/vmhgfs, the host/
    hypervisor side), which could hang indefinitely if wedged, dead,
    or otherwise unresponsive - there's no timeout on this code path.
    'fuseblk' might sound exempt given the name, and does wrap an
    actual block device, but that doesn't bound its syncfs() latency
    by the kernel block layer alone the way a native block device
    file system's is: the request is still serviced by the same FUSE
    daemon as any other FUSE variant, and can hang exactly the same
    way, so it is excluded here too, trading its comparatively minor
    data-safety benefit for avoiding that unbounded hang risk.

    '9p' (which can be used with a writeback cache and hence carry
    real dirty data, e.g. common in QEMU/KVM guests) and the
    shared-storage cluster file systems 'gfs', 'gfs2' and 'ocfs2'
    (which fstype_is_network() also happens to classify as "network"
    file systems, since they additionally rely on a networked
    distributed lock manager for coordination) are deliberately *not*
    excluded: unlike FUSE/virtiofs/etc., these are serviced by a
    mature, in-kernel client (talking directly to the hypervisor over
    a bounded virtio transport, or to real - if shared - block
    storage), not an arbitrary, potentially wedged userspace daemon,
    so they carry the same bounded, local sync latency any other
    block device backed file system already does here. Skipping them
    would needlessly sacrifice the data-safety guarantee the original
    blanket sync() gave them, without meaningfully improving safety.

  - Mount table entries that we can positively confirm are currently
    shadowed by another mount stacked on top of them at the same
    path: since we can only reach a file system by (re-)opening its
    target path, and that always resolves to whatever is currently on
    top, syncing by path alone could end up flushing the wrong
    superblock. Detect this via the new shared
    libmount_fs_id_matches_path() helper (factored out of, and now
    also used by, the pre-existing get_sub_mounts(), which needed the
    exact same check for the same reason). This same check is also
    applied to a mountinfo entry whose target is 'new_root' itself
    (not just anything strictly below it): comparing its mount ID
    against new_root's own, freshly determined mount ID tells apart
    the file system that is actually still reachable there (which we
    continue to skip) from a stale entry that merely shares the exact
    same path (e.g. if new_root wasn't already its own mount point
    and got bind-mounted onto itself earlier in switch_root()), which
    is departing just the same and must not be skipped just because
    of that coincidence.

Every failure mode that means we can no longer be sure we've covered
every departing file system correctly - libmount being unavailable,
/proc/self/mountinfo (or a specific entry in it) failing to parse,
being unable to tell whether a specific entry is currently shadowed,
or syncfs_path() itself failing for an otherwise-eligible entry - is
handled the exact same way: propagate the error up and let the sole
caller, switch_root(), fall back to one plain, global sync() to cover
everything, rather than deciding on and performing that fallback (or,
worse, silently skipping the affected file system without any
fallback at all) at each of these different spots individually. This
should be rare in practice, so it doesn't meaningfully undercut the
benefit of the targeted sync in the common case.

Everything else that's actually about to become unreachable (the old
root itself, but also any other, unrelated real file system that
happens to be mounted underneath it and gets detached along with it)
is still synced, so this keeps the same safety guarantee the original
blanket sync() gave for file systems that actually do go away here.
Uses the existing syncfs_path() helper for the actual open+syncfs.

sync_departing_file_systems() itself returns -EOPNOTSUPP if libmount
support isn't compiled in, handled the same way by switch_root() as
any of its other error returns.

Note we intentionally don't use O_PATH file descriptors here: syncfs()
requires a 'real' file descriptor and fails with EBADF on O_PATH ones.

Also note there remains an inherent, narrow TOCTOU race between the
mount-ID check described above and the open() syncfs_path() performs
right after it: if something else mounts something new on top of a
given 'path' in between, that open() could still end up triggering an
automount, or hanging on a stale mount, since there is no open()/
openat() equivalent of statx()'s AT_NO_AUTOMOUNT to prevent this for a
"real" (non-O_PATH) file descriptor. Unlike the other failure modes
handled here, a hanging open() can't be recovered from by falling back
to sync() afterwards, since control never returns to do so. Closing
this fully would require disproportionate effort (e.g. performing the
open() in a separate, killable/timeout-bounded process) for a window
that is already narrow, since this code only runs with most other
activity on the system already quiesced during the switch_root()
transition itself, so it is accepted as-is (see the comment at the
call site for details).

This mirrors the same reasoning already applied to the shutdown path
in src/shutdown/shutdown.c, which deliberately avoids a 'dumb' sync()
there for identical reasons.
2026-07-30 01:15:03 +09:00
2026-07-08 08:23:04 +02:00
2026-04-18 14:24:47 +01:00
2026-06-17 09:59:34 +00:00
2025-06-05 14:39:20 +02:00
2026-03-06 08:55:55 +01:00
2025-10-07 13:00:12 +01:00
2026-07-28 19:46:34 +09:00
2026-07-29 03:47:58 +09:00

Systemd

System and Service Manager

OBS Packages Status
Semaphore CI 2.0 Build Status
Coverity Scan Status
OSS-Fuzz Status
CIFuzz
CII Best Practices
Fossies codespell report
Translation status
Coverage Status
Packaging status
OpenSSF Scorecard

Details

Most documentation is available on systemd's website.

Assorted, older, general information about systemd can be found in the systemd Wiki.

Information about build requirements is provided in the README file.

Consult our NEWS file for information about what's new in the most recent systemd versions.

Please see the Code Map for information about this repository's layout and content.

Please see the Hacking guide for information on how to hack on systemd and test your modifications.

Please see our Contribution Guidelines for more information about filing GitHub Issues and posting GitHub Pull Requests.

When preparing patches for systemd, please follow our Coding Style Guidelines.

If you are looking for support, please contact our mailing list, join our IRC channel #systemd on libera.chat or Matrix channel

Stable branches with backported patches are available in the stable repo.

We have a security bug bounty program sponsored by the Sovereign Tech Fund hosted on YesWeHack

Repositories with distribution packages built from git main are available on OBS, and also repositories with packages built from the latest stable release

Description
No description provided
Readme Cite this repository 871 MiB
Languages
C 88.7%
Shell 5.4%
Python 4.7%
Meson 1.1%