89 lines
10 KiB
Markdown
89 lines
10 KiB
Markdown
# ANCIENT-MOD: running on old kernels (target: 4.19)
|
||
|
||
Upstream requires kernel ≥ 5.10 (see `README`, "REQUIREMENTS") and dropped the fallback code for
|
||
older kernels. This branch restores or adds fallbacks so the system can at least boot on 4.19.
|
||
Code touched by this effort is marked with `ANCIENT-MOD` comments where practical.
|
||
|
||
Legend: **revert** = reverse-applied an upstream commit (possibly adjusted), **new** = new code
|
||
because upstream code was refactored since the fallback was removed.
|
||
|
||
## Boot-critical
|
||
|
||
| Area | Kernel feature | Change | Where |
|
||
|---|---|---|---|
|
||
| Mount point checks (`is_mount_point_at()`, `path_is_mount_point()`), used by early `mount_setup()` for `/proc`, `/sys`, `/dev` with `MNT_FATAL` | `STATX_ATTR_MOUNT_ROOT` (5.8) | **new**: `xstatx_full()` emulates the attribute when it is required but missing: compares the mount id (from `/proc/self/fdinfo`) of the inode with its parent's (`..` for directories, parent path for others); same inode as parent ⇒ root ⇒ mount point. Replaces upstream fallback removed in `a98a6eb95c`. | `src/basic/stat-util.c` (`xstatx_emulate()`) |
|
||
| `chase()` (used everywhere), `path_get_mnt_id_at()`, `fds_inode_and_mount_same()`, `XSTATX_MNT_ID_BEST` | `STATX_MNT_ID` (5.8/5.10) | **new**: same emulation fills `stx_mnt_id` from `/proc/self/fdinfo/<fd>` `mnt_id:` (available since 3.15). Replaces removals in `7ecfa87c56`, `e5a3127339`. | `src/basic/stat-util.c` |
|
||
| cgroup2 mount | `memory_recursiveprot` (5.7) | **revert** (adjusted) of `80c5b63db5`: option is probed again instead of always passed; if it cannot be probed because `fsopen()` is missing (< 5.2) it is not used. | `src/shared/mount-setup.c` |
|
||
| `mount_option_supported()` | `fsopen()` (5.2) | **revert** of part of `c2198d0c3f`: return `-EAGAIN` ("don't know") when the new mount API is unavailable. | `src/basic/mountpoint-util.c` |
|
||
| `close_all_fds*()` (every fork/exec, `systemd-executor`) | `close_range()` (5.9) | **revert** of `c471cca093` and `d58c826c54`: fall back to iterating `/proc/self/fd` (or the frugal loop). | `src/basic/fd-util.c`, `src/basic/fd-util.h`, `src/test/test-fd-util.c` |
|
||
| `pidref_set_pid()` (every process PID1 tracks, `safe_fork()`, `posix_spawn_wrapper()`) | `pidfd_open()` (5.3) | **revert** (adjusted) of part of `6e14c46bac`: ENOSYS ⇒ PidRef without fd (plain PID). | `src/basic/pidref.c` |
|
||
| `pidfd_open()` shim (all pidfd users), `pidfd_waitid()` | `waitid(P_PIDFD)` (5.4) | **new**: vendor kernels (Android 4.19) backport `pidfd_open()` but not `waitid(P_PIDFD)`, so every wait failed with EINVAL (PID1 froze after generators). The shim probes once on a pidfd of self that `pidfd_send_signal()` and `poll()` work (without pidfd poll support a live process reports readable); if not, `pidfd_open()` returns ENOSYS so the plain-PID fallbacks above apply. Waits go through `pidfd_waitid()`, which probes `waitid(P_PIDFD)` (ECHILD = supported, EINVAL = not) and otherwise uses `waitid(P_PID)` with the known PID (safe for children: the PID cannot be reused before reaping). Keeping pidfds is required by logind, which since v258 refuses sessions whose leader has no pidfd (`NoSessionPIDFD`). | `src/libc/pidfd.c`, `src/include/override/sys/pidfd.h`, `src/basic/pidref.c`, `src/libsystemd/sd-event/sd-event.c` |
|
||
| `pidref_wait_for_terminate_full()` with timeout | pidfd (5.3) | **new**: without pidfd poll `waitid(P_PID, …, WNOHANG)` every 10 ms instead of returning `-ENOMEDIUM`. | `src/basic/pidref.c` |
|
||
| `pidfd_check_pidfs()`, `pidfd_info_mask_is_supported()`, `pidfd_get_inode_id_self_cached()` | pidfd (5.3) | **revert**/new: ENOSYS ⇒ "not supported" instead of hard error. | `src/basic/pidfd-util.c` |
|
||
| sd-event child sources (`sd_event_add_child()`, used by udevd, logind, …) | pidfd (5.3), `waitid(P_PIDFD)` (5.4) | **new** (upstream removed the non-pidfd path in `6e14c46bac` and later made the code pidfd-only): if `pidfd_open()` is unsupported the source works on the PID via `waitid(P_PID)` + SIGCHLD signalfd. SIGCHLD is blocked automatically in that case (upstream no longer requires callers to block it). `send_child_signal()` falls back to `kill()`/`rt_sigqueueinfo()`; `set_child_pidfd_own()` returns `-EOPNOTSUPP` without pidfd. | `src/libsystemd/sd-event/sd-event.c` |
|
||
| `pam_systemd` session creation | pidfd (5.3) | **revert** of part of `6e14c46bac`: fall back to `CreateSession` (PID based). Ineffective on its own: logind (Varlink and D-Bus) rejects leaders without pidfd, so sessions need a kernel with `pidfd_open()` + pidfd poll. | `src/login/pam_systemd.c` |
|
||
| Unit cgroup-empty tracking (`unit_check_cgroup_events()`) | `frozen` key in `cgroup.events` (5.2) | **new**: if the key is missing, read only `populated`. Without this every cgroup event read failed and stopped units were never noticed as empty. | `src/core/cgroup.c` |
|
||
| Freeze/thaw | cgroup v2 freezer (5.2) | **revert** of `3c86a935ad`: `cg_freezer_supported()` restored; freeze is `-EOPNOTSUPP` / no-op when `cgroup.freeze` is absent. | `src/basic/cgroup-util.[ch]`, `src/core/cgroup.c`, `src/core/unit.c` |
|
||
| Credentials (`LoadCredential=`, `SetCredential=`, `ImportCredential=` — used by many stock units) | new mount API `fsopen`/`fsmount`/`move_mount` (5.2) | **new**: if the credentials fs cannot be created because the API is missing, use the existing "plain directory" fallback (previously only used on EPERM). | `src/core/exec-credential.c` |
|
||
| `ProtectProc=` / `ProcSubset=` (used by logind, networkd, timesyncd, hostnamed, …) | per-instance procfs, textual `hidepid=`, `subset=` (5.8) | **new**: on kernels < 5.8 the options are ignored (they would either fail with EINVAL or, worse, change the host's shared `/proc` superblock). `/proc/sys` & friends are then still protected by the normal ProtectKernel* mounts. Upstream removed the old fallback in `0cba5bdcf1`. | `src/core/namespace.c` |
|
||
| `RootDirectory=` | `open_tree()` (5.2) | **new**: tolerate ENOSYS when cloning the root directory mount and use the classic bind mount path. | `src/core/namespace.c` |
|
||
| `futimens_opath()`, `access_fd()` | `AT_EMPTY_PATH` for `utimensat()`/`faccessat()` (5.8) | **revert** of `52bf232e82` (manually): fall back to `/proc/self/fd/<fd>`. | `src/basic/fs-util.c` |
|
||
|
||
## Networking
|
||
|
||
| Area | Kernel feature | Change | Where |
|
||
|---|---|---|---|
|
||
| `socket_bind_to_ifindex()` (DHCP client, resolved, networkd, …) | `SO_BINDTOIFINDEX` (5.1) | **revert** of `ddbbc98bd5` (manually): fall back to `SO_BINDTODEVICE`. | `src/basic/socket-util.c` |
|
||
| `rtnl_resolve_ifname_full()` (alternative names) | `IFLA_ALT_IFNAME` lookup (5.5) | **revert** of `42b09813f4`: treat EINVAL like ENODEV. | `src/libsystemd/sd-netlink/netlink-util.c` |
|
||
| networkd startup enumeration | nexthop (5.3) | **revert** of `034b69d8b8` (manually): ignore `-EOPNOTSUPP` from nexthop enumeration, otherwise networkd refuses to start. | `src/network/networkd-manager.c` |
|
||
| networkd permanent MAC address | `IFLA_PERM_ADDRESS` (5.6) | **revert** of `04f05d9bd7`: fall back to ethtool. | `src/network/networkd-link.c` |
|
||
| IPv6 DNAT (nspawn port forwarding / firewall-util) | nft concatenation fix (5.8) | **revert** of `58fb46ecd5` (manually): `-EOVERFLOW` ⇒ `-EOPNOTSUPP`. | `src/shared/firewall-util.c` |
|
||
|
||
## Other
|
||
|
||
| Area | Kernel feature | Change | Where |
|
||
|---|---|---|---|
|
||
| `SurviveFinalKillSignal=` | `user.*` xattrs on cgroupfs (5.7) | **revert** of `8292b859f3`: fall back to `trusted.survive_final_kill_signal`. | `src/core/cgroup.c`, `src/shared/killall.c` |
|
||
| Loop devices via `LOOP_SET_STATUS64` fallback | buffer cache invalidation bug (fixed in 5.0) | **revert** of `5e3887e6c1`: issue `BLKFLSBUF` when offset/size is set. | `src/shared/loop-util.c` |
|
||
|
||
## Known remaining gaps (not addressed)
|
||
|
||
- **cgroup v1 is gone upstream**: the kernel must boot with a cgroup v2 hierarchy. 4.19's cgroup v2
|
||
lacks the `cpuset` (5.0) and `freezer` (5.2) controllers, `cgroup.kill` (5.14, already graceful).
|
||
- **PSI** (4.20): `systemd-oomd` does not work; memory-pressure event sources are already optional.
|
||
- **BPF**: features needing BPF links / BPF LSM / bounded loops (5.3–5.7) — `RestrictFileSystems=`,
|
||
`RestrictNetworkInterfaces=`, `SocketBind*=`, `BindNetworkInterface=`, … — report "not supported"
|
||
(upstream probing, `ef12eadfc8`).
|
||
- **New mount API without fallback** (5.2/5.12): `PrivateBPF=`, `ExtensionImages=`/`ExtensionDirectories=`
|
||
(overlay via `fsopen`), `MountImages=`, runtime `systemctl bind`/`mount-image`, idmapped mounts,
|
||
mountfsd, nsresourced, sysext/confext, homed (also needs fscrypt v2 / 5.4), coredump's mount of
|
||
the crashed process' root.
|
||
- **`MS_NOSYMFOLLOW`** (5.10) is passed as a legacy `mount(2)` flag; older kernels silently ignore
|
||
unknown flags, so the mount just lacks `nosymfollow`.
|
||
- **Credentials** use a plain directory instead of a dedicated (noswap) tmpfs, i.e. less isolation.
|
||
- **Per-device IO weight** (`IODeviceWeight=`, BFQ per-device weight, 5.4): upstream dropped the
|
||
EINVAL workaround (`00c4851af0`), expect warnings when used.
|
||
- **udev net_id** for IP-over-InfiniBand (`dev_port`, 4.20): `d3d375a686` not reverted; multi-port
|
||
IPoIB devices may get colliding names.
|
||
- `test-event`'s `test_pidfd` inherently requires pidfds and fails without them; it and
|
||
`test_child_pidfd_wnowait` also call `waitid(P_PIDFD)` directly and fail without it.
|
||
- logind sessions require pidfds (see `pam_systemd` row); on kernels without `pidfd_open()` no
|
||
login session can be created.
|
||
- Anything else newer than 5.10 listed in `README` as "recommended baseline" is expected to fall
|
||
back gracefully upstream, but that has not been verified on a real old kernel.
|
||
|
||
## Testing done
|
||
|
||
- Full native test suite (`meson test -C build`) on a 7.x kernel.
|
||
- Selected tests (`test-stat-util`, `test-mountpoint-util`, `test-chase`, `test-fs-util`,
|
||
`test-fd-util`, `test-pidref`, `test-process-util`, `test-socket-util`, `test-mount-util`,
|
||
`test-udev-spawn`, `test-async`, `test-event` except `test_pidfd`) and `systemd --test --system`
|
||
(with the built generators) under an `LD_PRELOAD` shim that simulates an old kernel: strips
|
||
`STATX_MNT_ID*`/`STATX_ATTR_MOUNT_ROOT` from `statx()` results and makes `pidfd_open`,
|
||
`pidfd_send_signal`, `pidfd_spawn`, `close_range` and the new mount API return ENOSYS.
|
||
- `test-pidref`, `test-process-util`, `test-event` (except `test_pidfd`, `test_child_pidfd_wnowait`)
|
||
under an `LD_PRELOAD` shim that only makes `waitid(P_PIDFD)` fail with EINVAL (Android 4.19
|
||
vendor kernel case: pidfds stay enabled, waits use the `P_PID` fallback).
|
||
- Booted on an Android 4.19 vendor kernel (Xiaomi vince): reaches `graphical.target`; login
|
||
sessions not verified yet.
|