14 Commits
Author SHA1 Message Date
SymbX b5d8617b03 revert changes to apply with system where kernel is 4.19
Make a Github release / release (push) Skipped
2026-09-26 19:04:39 +03:00
Roy-Magne Mo 4ed4b428ba po: Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (286 of 286 strings)

Co-authored-by: Roy-Magne Mo <rmo@sunnmore.net>
Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/nn/
Translation: systemd/main
2026-09-26 10:45:43 +02:00
Jean Capiez aa5c0f0f77 hwdb: add PCPanel controllers
PCPanel makes USB desktop controllers (knobs, sliders, buttons) used to
control audio levels, streaming software and lighting. They are driven
from userspace through hidraw, like the other controllers in this file.

PCPanel reuses the vendor IDs of the microcontroller makers
(STMicroelectronics 0483, Microchip 04d8) with its own product IDs.
2026-09-26 14:16:23 +09:00
Fredrik Hugosson a446e8ff2c analyze: Fix leading zero bug in decimals for tool tip in svg plot
Change-Id: If572cc593aa730de87510982613791d94d8f26b5
2026-09-25 17:53:15 +02:00
Lennart Poettering c25226b820 report: add metrics source that reports current disk space data 2026-09-25 16:43:13 +02:00
Daan De Meyer 9336b9ce23 ci: fix the review deadline hook
The hook that enforces the review deadline used bash's [[ test, but hook
commands run under /bin/sh, which is dash on the runners. Every tool call was
denied with "[[: not found", so all six lenses returned empty findings and
the run reported a clean review in 73 seconds. Use a POSIX test.

An empty result from an agent that never got a tool call through looks exactly
like a clean review, so require at least one successful tool call before a lens
counts as completed. A run like that now fails every lens, which also stops it
from raising the review count.
2026-09-25 15:28:15 +02:00
Daan De Meyer a3335922ce ci: stop clobbering concurrently added labels in the PR labeler
The "Pull Request Labeler" workflow read the full label set of a pull
request and wrote it back with setLabels(), which is a full replace. Other
workflows label the same pull request at the same time: claude-review.yml
adds the claude-review label on the pull_request_target "opened" event,
which is the very same event that starts the labeler. Whenever that add
landed between the labeler's listLabelsOnIssue() and its setLabels(), the
label was dropped again and the review never ran.

Only ever add or remove the individual labels the labeler owns, so
concurrent changes by other workflows survive. removeLabel() returns 404
when the label is already gone, which is exactly what a racing run causes,
so swallow that status.
2026-09-25 15:10:12 +02:00
Luca Boccassi 17e3db12ca test: coco credential transport on snp (#43827) 2026-09-25 13:33:03 +02:00
Daan De Meyer 4754cd2db6 sd-future: make sd_fiber_await() report failures rather then future results
sd_fiber_await() returned the target future's result on completion and an error
on interruption, so a negative value could mean either that the target future
resolved with an error or that the wait never finished. Most callers had to check
the target future's state afterwards to tell the two apart. Even worse, a target
resolving with -ECANCELED was indistinguishable from a cancelled wait.

Let's return 0 once the target future has resolved and a negative error only when
the wait itself did not complete. The target future's outcome stays available through
sd_future_result(), which callers have to additionally inspect now.
2026-09-25 12:57:37 +02:00
Robert Beckett 4ca7ac0275 terminal-util: don't query terminal size on virtual consoles
terminal_fix_size() queries the terminal for its dimensions with CSI 18
and DSR. On a virtual console that is pointless: the kernel renders the
screen itself, so TIOCGWINSZ is already authoritative.

It is also slow. PID 1 queries /dev/console from console_setup(), and
writes there go through redirected_tty_write(), so while plymouth holds a
TIOCCONS redirect the query never reaches the VT and no reply is ever
generated. Each query then waits out CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC.
Previously the write failed immediately with EAGAIN — the kernel forces
O_NONBLOCK on /dev/console — so the query was abandoned at no cost; since
ba10d789a8 it is retried until the timeout instead. On a Steam Deck
booting with console=tty1 and plymouth the two queries cost 667ms, about a
quarter of userspace boot time, and no log level shows it because main()
runs console_setup() between log_close() and log_open().

So skip the query on a virtual console and keep the dimensions the kernel
gave us, reporting success rather than an error: declining to ask is not a
failure, and the "dimensions already match" path below returns 0 for the
same reason.

Note this also skips the query on a /dev/ttyN that would have answered,
and does not help a serial console behind a TIOCCONS redirect.

Fixes: ba10d789a8
Fixes: d91a184509
2026-09-25 11:29:57 +02:00
Paul Meyer 0d1ae05a7f test: strip directory from subtest name in summary
The integration tests are started with the absolute path of the test
unit as $0, so run_subtests() derives absolute paths for the subtests
and the summary prints them in full. Show just the script name, the
directory is the same for every entry and only adds noise.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-09-25 08:15:24 +02:00
Paul Meyer d6f96ac311 test: coco credential rejection on snp
Not all channels used for credential transport in systemd are trusted
on SNP: while cmdline and initrd are measured and should be used,
smbios and fwcfg are untrusted and the guest should ignore creds from
those delivery paths.

Implement an offensive credential test that tries to inject credentials
via those untrusted channels and check the guest won't consume those
credentials.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-09-25 08:15:24 +02:00
Paul Meyer 28a1975396 test: coco credential transport on snp
Check that credentials passed via systemd-vmspawn --set-credential and
on the command line via systemd.set_credential= are successfully
delivered on a SNP coco guest.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-09-25 08:15:24 +02:00
Paul Meyer 671206e266 test: split coco test per-platform
Grow TEST-94-COCO from a single inline check into the reusable structure
that can be used by multiple future subtests:

- lib.sh: code shared between subtests on the host side.
- guest-test.sh: the testcase_coco_* checks.
- guest-test-runner.sh: runs each testcase_coco_* check, ships guest
  side testcase results over vsock.

Helpers live under test/units/TEST-94-COCO/. The vsock records are
best-effort detail, the aggregate exit status stays the authoritative
pass/fail.

Split the test into subtests per platform/boot flow.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-09-25 08:15:24 +02:00
67 changed files with 2401 additions and 476 deletions
+40 -17
View File
@@ -134,18 +134,29 @@ jobs:
]);
/* A README.md that still carries the AI review notice has not been
* looked at by its author yet, so there is nothing to review. */
const readme = await github.rest.repos.getContent({
owner,
repo,
path: "README.md",
ref: pr.data.head.sha,
});
if (Buffer.from(readme.data.content, "base64").toString()
.includes("Remove this line to confirm you've reviewed this PR before submitting.")) {
console.log("README.md still carries the review notice, skipping.");
core.setOutput("skip", "true");
return;
* looked at by its author yet, so there is nothing to review. An
* explicit "@claude review" answers the notice, so only the automatic
* triggers are skipped, and a check that cannot run reviews anyway. */
if (context.eventName === "pull_request_target") {
let notice = false;
try {
const readme = await github.rest.repos.getContent({
owner,
repo,
path: "README.md",
ref: pr.data.head.sha,
});
notice = Buffer.from(readme.data.content, "base64").toString()
.includes("Remove this line to confirm you've reviewed this PR before submitting.");
} catch (e) {
console.log(`Failed to read README.md, assuming it has been reviewed: ${e.message}`);
}
if (notice) {
console.log("README.md still carries the review notice, skipping.");
core.setOutput("skip", "true");
return;
}
}
/* Find or create tracking comment. */
@@ -207,6 +218,7 @@ jobs:
fs.writeFileSync("pr-context.json", JSON.stringify(prContext, null, 2));
- name: Upload PR context
if: steps.context.outputs.skip != 'true'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: pr-context.json
@@ -354,7 +366,7 @@ jobs:
"hooks": [
{
"type": "command",
"command": "[[ $(date +%s) -lt ${REVIEW_DEADLINE:-0} ]] || { echo 'Time is up: stop using tools and return your findings now.' >&2; exit 2; }"
"command": "[ \"$(date +%s)\" -lt \"${REVIEW_DEADLINE:-0}\" ] || { echo 'Time is up: stop using tools and return your findings now.' >&2; exit 2; }"
}
]
}
@@ -408,10 +420,21 @@ jobs:
-p "$prompt" >"$OUTPUT/$name.log") || status=$?
jq -Rc 'fromjson? | select(.type == "result" and .subtype == "success") | .structured_output | select(. != null)' \
"$OUTPUT/$name.log" | tail -n 1 >"$OUTPUT/$name.json" || :
if [[ -s "$OUTPUT/$name.json" ]]; then
# An agent whose every tool call failed still returns an empty findings
# array, which is indistinguishable from a clean review. Require that it
# got at least one tool result other than its own structured output.
local worked
worked="$(jq -Rn '
[inputs | fromjson?] as $events
| [$events[] | select(.type == "assistant") | .message.content[]?
| select(.type == "tool_use" and .name == "StructuredOutput") | .id] as $ignore
| [$events[] | select(.type == "user") | .message.content[]?
| select(.type == "tool_result" and .is_error != true and ([.tool_use_id] - $ignore | length > 0))]
| length' <"$OUTPUT/$name.log")"
if [[ -s "$OUTPUT/$name.json" ]] && (( worked > 0 )); then
echo "${name}: done (status ${status})"
else
echo "${name}: no result (status ${status})"
echo "${name}: no result (status ${status}, ${worked} tool call(s) succeeded)"
rm -f "$OUTPUT/$name.json"
fi
}
@@ -746,12 +769,12 @@ jobs:
}
/* An unterminated fence would swallow everything after it, so cut
* it off before anything else is measured or truncated. */
const fences = [...c.body.matchAll(/^```/gm)];
const fences = [...c.body.matchAll(/^ {0,3}`{3,}/gm)];
if (fences.length % 2 === 1) {
console.log(` Dropping unterminated code fence in comment on ${c.path}:${c.line}.`);
c = { ...c, body: c.body.slice(0, fences.at(-1).index).trimEnd() || "(empty)" };
}
const prose = c.body.replace(/^```[\s\S]*?^```[^\n]*/gm, "");
const prose = c.body.replace(/^ {0,3}`{3,}[\s\S]*?^ {0,3}`{3,}[^\n]*/gm, "");
if (prose.length > MAX_PROSE_LENGTH) {
console.log(` Truncating comment on ${c.path}:${c.line}: ${prose.length} characters of prose exceeds ${MAX_PROSE_LENGTH}.`);
/* Cutting inside a fence would leave a suggestion missing code,
+48 -25
View File
@@ -48,14 +48,33 @@ jobs:
if: startsWith(github.event_name, 'pull_request') && github.event.action != 'closed' && !github.event.pull_request.draft
with:
script: |
/* Never read the full label set and write it back: other workflows
* label the same pull request concurrently and setLabels() would
* clobber whatever they added in the meantime. Only ever add or
* remove the individual labels this step owns. */
const removeLabel = async (name) => {
try {
await github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: name,
});
} catch (error) {
/* Somebody else removed it first. */
if (error.status !== 404) {
throw error;
}
}
};
response = await github.rest.issues.listLabelsOnIssue({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
original = new Set(response.data.map(l => l.name));
labels = new Set(original);
labels = new Set(response.data.map(l => l.name));
good_to_merge = new Set([
"good-to-merge/waiting-for-ci 👍",
@@ -85,24 +104,22 @@ jobs:
}
}
`, { id: context.payload.pull_request.node_id });
} else if (Array.from(labels).filter(l => good_to_merge.has(l)).length == 0) {
labels.add("please-review");
} else if (!Array.from(good_to_merge).some(l => labels.has(l)) && !labels.has("please-review")) {
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["please-review"],
});
}
for (const label of ["reviewed/needs-rework 🔨",
"ci-fails/needs-rework 🔥",
"ci-failure-appears-unrelated",
"needs-rebase"]) {
labels.delete(label);
}
if (labels.size != original.size || Array.from(labels).some(l => !original.has(l))) {
await github.rest.issues.setLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: Array.from(labels),
});
if (labels.has(label)) {
await removeLabel(label);
}
}
- name: Add please-review label on command in issue comment
@@ -128,8 +145,7 @@ jobs:
repo: context.repo.repo,
});
original = new Set(response.data.map(l => l.name));
labels = new Set(original);
labels = new Set(response.data.map(l => l.name));
for (const label of ["please-review",
"reviewed/needs-rework 🔨",
@@ -144,14 +160,21 @@ jobs:
"dont-merge 💣",
"squash-on-merge",
"quick-review 🏃‍♂️"]) {
labels.delete(label);
}
if (!labels.has(label)) {
continue;
}
if (labels.size != original.size || Array.from(labels).some(l => !original.has(l))) {
await github.rest.issues.setLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: Array.from(labels),
});
try {
await github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
});
} catch (error) {
/* Somebody else removed it first. */
if (error.status !== 404) {
throw error;
}
}
}
+80
View File
@@ -0,0 +1,80 @@
# 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` |
| `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). | `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.
- 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.
- **Not** booted on a real 4.19 kernel yet.
+15
View File
@@ -253,6 +253,21 @@ usb:v17CCp1130*
usb:v17CCp2420*
ID_AV_PRODUCTION_CONTROLLER=1
####################
# PCPanel
####################
# PCPanel Mini
usb:v0483pA3C4*
ID_AV_PRODUCTION_CONTROLLER=1
# PCPanel Pro
usb:v0483pA3C5*
ID_AV_PRODUCTION_CONTROLLER=1
# PCPanel RGB
usb:v04D8pEB52*
ID_AV_PRODUCTION_CONTROLLER=1
####################
# Pioneer
####################
+4
View File
@@ -1209,6 +1209,10 @@ manpages = [
'ENABLE_RANDOMSEED'],
['systemd-remount-fs.service', '8', ['systemd-remount-fs'], ''],
['systemd-repart', '8', ['systemd-repart.service'], 'ENABLE_REPART'],
['systemd-report-diskspace@.service',
'8',
['systemd-report-diskspace', 'systemd-report-diskspace.socket'],
'HAVE_LIBMOUNT'],
['systemd-report-files@.service',
'8',
['systemd-report-files', 'systemd-report-files.socket'],
+111
View File
@@ -0,0 +1,111 @@
<?xml version="1.0"?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-report-diskspace_.service" xmlns:xi="http://www.w3.org/2001/XInclude"
conditional='HAVE_LIBMOUNT'>
<refentryinfo>
<title>systemd-report-diskspace@.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-report-diskspace@.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-report-diskspace@.service</refname>
<refname>systemd-report-diskspace.socket</refname>
<refname>systemd-report-diskspace</refname>
<refpurpose>Report size and free disk space of mounted file systems as system report metrics</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-report-diskspace@.service</filename></para>
<para><filename>systemd-report-diskspace.socket</filename></para>
<para><filename>/usr/lib/systemd/systemd-report-diskspace</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-report-diskspace@.service</command> is a metrics source for
<citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry>. It
reports the size of the mounted file systems and how much disk space is still available on them, by
implementing the <constant>io.systemd.Metrics</constant> Varlink interface on a socket linked into the
<filename>/run/systemd/report/</filename> directory.</para>
<para>All mount points of the system are enumerated. Mount points of file systems that are not backed by
a block device (i.e. pseudo file systems such as <literal>proc</literal>, <literal>sysfs</literal> or
<literal>tmpfs</literal>, network file systems, <literal>overlay</literal>, …) are skipped, as are
read-only mount points. For each remaining mount point two gauge metrics are reported, with the mount
point path as metric object:</para>
<variablelist>
<varlistentry>
<term><literal>io.systemd.DiskSpace.SizeBytes</literal></term>
<listitem><para>The total size of the file system, in bytes. This matches the
<literal>1K-blocks</literal> column of
<citerefentry project='man-pages'><refentrytitle>df</refentrytitle><manvolnum>1</manvolnum></citerefentry>
(scaled to bytes).</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>io.systemd.DiskSpace.FreeBytes</literal></term>
<listitem><para>The disk space still available to unprivileged users on the file system, in
bytes. This matches the <literal>Avail</literal> column of
<citerefentry project='man-pages'><refentrytitle>df</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
i.e. space reserved for the superuser (as many file systems do) is not counted as
free.</para></listitem>
</varlistentry>
</variablelist>
<para>The following metric fields are set on both metrics:</para>
<variablelist>
<varlistentry>
<term><varname>fileSystemType</varname></term>
<listitem><para>The file system type, for example <literal>ext4</literal> or
<literal>btrfs</literal>. Always set.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>source</varname></term>
<listitem><para>The mount source, typically the device node the file system is mounted from, for
example <literal>/dev/sda1</literal>. Omitted if unknown.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>originatingSource</varname></term>
<listitem><para>If the block device the file system is mounted from is a stacked one (for example a
LUKS or other device mapper volume), the device node of the block device it ultimately originates
from, for example <literal>/dev/sda2</literal> for a file system mounted from
<literal>/dev/mapper/root</literal>. Omitted if the source is not a stacked block device, or its
origin cannot be determined. Note that stacked devices backed by multiple block devices (e.g. RAID)
are not covered by this.</para></listitem>
</varlistentry>
</variablelist>
<para>A file system that is mounted multiple times (for example via bind mounts, or as multiple
subvolumes of the same volume) is reported only once, under the shortest of its mount point paths (and
among equally long ones, the alphabetically first). Mount points that are shadowed by another mount on
top of them, or that the service cannot access, are not considered.</para>
</refsect1>
<!-- Note: we do not document the command line switches here. The systemd-report-diskspace binary itself
should not be invoked by users, but only as a service. Use systemd-report(1) to acquire metrics. -->
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry project='man-pages'><refentrytitle>df</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry project='man-pages'><refentrytitle>statfs</refentrytitle><manvolnum>2</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+345 -220
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -35,6 +35,7 @@ enable systemd-nsresourced.socket
enable systemd-pstore.service
enable systemd-report-basic.socket
enable systemd-report-cgroup.socket
enable systemd-report-diskspace.socket
enable systemd-report-files.socket
enable systemd-report-hwmon.socket
enable systemd-report-sign-plain.socket
+2 -2
View File
@@ -177,8 +177,8 @@ static void plot_tooltip(const UnitTimes *ut) {
assert(ut->name);
svg("%s:\n", ut->name);
svg("Activating: %"PRI_USEC".%.3"PRI_USEC"\n", ut->activating / USEC_PER_SEC, ut->activating % USEC_PER_SEC);
svg("Activated: %"PRI_USEC".%.3"PRI_USEC"\n", ut->activated / USEC_PER_SEC, ut->activated % USEC_PER_SEC);
svg("Activating: %"PRI_USEC".%.6"PRI_USEC"\n", ut->activating / USEC_PER_SEC, ut->activating % USEC_PER_SEC);
svg("Activated: %"PRI_USEC".%.6"PRI_USEC"\n", ut->activated / USEC_PER_SEC, ut->activated % USEC_PER_SEC);
UnitDependency i;
FOREACH_ARGUMENT(i, UNIT_AFTER, UNIT_BEFORE, UNIT_REQUIRES, UNIT_REQUISITE, UNIT_WANTS, UNIT_CONFLICTS, UNIT_UPHOLDS)
+17
View File
@@ -206,6 +206,23 @@ bool cg_kill_supported(void) {
return (supported = false);
}
/* ANCIENT-MOD: cgroup v2 freezer is available since kernel 5.2 */
bool cg_freezer_supported(void) {
static thread_local int supported = -1;
if (supported >= 0)
return supported;
if (cg_is_available() <= 0)
return (supported = false);
if (access("/sys/fs/cgroup/init.scope/cgroup.freeze", F_OK) >= 0)
return (supported = true);
if (errno != ENOENT)
log_debug_errno(errno, "Failed to check whether cgroup freezer is available, assuming not: %m");
return (supported = false);
}
int cg_enumerate_subgroups(const char *path, DIR **ret) {
_cleanup_free_ char *fs = NULL;
DIR *d;
+1
View File
@@ -235,6 +235,7 @@ int cg_mask_from_string(const char *s, CGroupMask *ret);
int cg_mask_to_string(CGroupMask mask, char **ret);
bool cg_kill_supported(void);
bool cg_freezer_supported(void);
DECLARE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
+80 -16
View File
@@ -260,9 +260,10 @@ int close_all_fds_frugal(const int except[], size_t n_except) {
assert(except || n_except == 0);
/* This is the inner fallback core of close_all_fds(). This never calls malloc() or so and hence is
* safe to be called in signal handler context. Most users should call close_all_fds(), but when we
* assume we are called from signal handler context, then use this simpler call instead. */
/* This is the inner fallback core of close_all_fds(). This never calls malloc() or opendir() or so
* and hence is safe to be called in signal handler context. Most users should call close_all_fds(),
* but when we assume we are called from signal handler context, then use this simpler call
* instead. */
max_fd = get_max_fd();
if (max_fd < 0)
@@ -288,6 +289,44 @@ int close_all_fds_frugal(const int except[], size_t n_except) {
return r;
}
int close_all_fds_by_proc(const int except[], size_t n_except) {
_cleanup_closedir_ DIR *d = NULL;
int r = 0;
d = opendir("/proc/self/fd");
if (!d)
return close_all_fds_frugal(except, n_except); /* ultimate fallback if /proc/ is not available */
FOREACH_DIRENT(de, d, return -errno) {
int fd = -EBADF, q;
if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN))
continue;
fd = parse_fd(de->d_name);
if (fd < 0)
/* Let's better ignore this, just in case */
continue;
if (fd < 3)
continue;
if (fd == dirfd(d))
continue;
if (fd_in_set(fd, except, n_except))
continue;
q = close_nointr(fd);
if (q != -EBADF) /* Valgrind has its own FD and doesn't want to have it closed */
RET_GATHER(r, q);
}
return r;
}
static bool have_close_range = true; /* Assume we live in the future */
static int close_all_fds_special_case(const int except[], size_t n_except) {
assert(n_except == 0 || except);
@@ -295,6 +334,9 @@ static int close_all_fds_special_case(const int except[], size_t n_except) {
* nicely, since we won't need sorting for them. Returns > 0 if the special casing worked, 0
* otherwise. */
if (!have_close_range)
return 0;
if (n_except == 1 && except[0] < 0) /* Minor optimization: if we only got one fd, and it's invalid,
* we got none */
n_except = 0;
@@ -303,22 +345,31 @@ static int close_all_fds_special_case(const int except[], size_t n_except) {
case 0:
/* Close everything. Yay! */
if (close_range(3, INT_MAX, 0) < 0)
return -errno;
return 1;
if (close_range(3, INT_MAX, 0) >= 0)
return 1;
if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) {
have_close_range = false;
return 0;
}
return -errno;
case 1:
/* Close all but exactly one, then we don't need no sorting. This is a pretty common
* case, hence let's handle it specially. */
if (except[0] > 3 && close_range(3, except[0] - 1, 0) < 0)
return -errno;
if ((except[0] <= 3 || close_range(3, except[0]-1, 0) >= 0) &&
(except[0] >= INT_MAX || close_range(MAX(3, except[0]+1), -1, 0) >= 0))
return 1;
if (except[0] < INT_MAX && close_range(MAX(3, except[0] + 1), -1, 0) < 0)
return -errno;
if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) {
have_close_range = false;
return 0;
}
return 1;
return -errno;
default:
return 0;
@@ -350,6 +401,9 @@ int close_all_fds(const int except[], size_t n_except) {
if (r > 0) /* special case worked! */
return 0;
if (!have_close_range)
return close_all_fds_by_proc(except, n_except);
_cleanup_free_ int *sorted_malloc = NULL;
size_t n_sorted;
int *sorted;
@@ -369,7 +423,7 @@ int close_all_fds(const int except[], size_t n_except) {
sorted = newa(int, n_sorted);
if (!sorted) /* Fallback on OOM. */
return close_all_fds_frugal(except, n_except);
return close_all_fds_by_proc(except, n_except);
memcpy(sorted, except, n_except * sizeof(int));
@@ -391,8 +445,13 @@ int close_all_fds(const int except[], size_t n_except) {
continue;
/* Close everything between the start and end fds (both of which shall stay open) */
if (close_range(start + 1, end - 1, 0) < 0)
return -errno;
if (close_range(start + 1, end - 1, 0) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return -errno;
have_close_range = false;
return close_all_fds_by_proc(except, n_except);
}
}
/* The loop succeeded. Let's now close everything beyond the end */
@@ -400,8 +459,13 @@ int close_all_fds(const int except[], size_t n_except) {
if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */
return 0;
if (close_range(sorted[n_sorted-1] + 1, INT_MAX, 0) < 0)
return -errno;
if (close_range(sorted[n_sorted-1] + 1, INT_MAX, 0) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return -errno;
have_close_range = false;
return close_all_fds_by_proc(except, n_except);
}
return 0;
}
+1
View File
@@ -127,6 +127,7 @@ int get_max_fd(void);
int close_all_fds(const int except[], size_t n_except);
int close_all_fds_without_malloc(const int except[], size_t n_except);
int close_all_fds_by_proc(const int except[], size_t n_except);
int close_all_fds_frugal(const int except[], size_t n_except);
int pack_fds(int fds[], size_t n);
+28 -2
View File
@@ -322,7 +322,20 @@ int futimens_opath(int fd, const struct timespec ts[2]) {
assert(fd >= 0);
return RET_NERRNO(utimensat(fd, "", ts, AT_EMPTY_PATH));
if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0)
return 0;
if (errno != EINVAL)
return -errno;
/* ANCIENT-MOD: AT_EMPTY_PATH is supported since kernel 5.8, fall back to /proc/self/fd/ */
if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, /* flags= */ 0) < 0) {
if (errno != ENOENT)
return -errno;
return proc_fd_enoent_errno();
}
return 0;
}
int stat_warn_permissions(const char *path, const struct stat *st) {
@@ -371,7 +384,20 @@ int access_fd(int fd, int mode) {
assert(fd >= 0);
return RET_NERRNO(faccessat(fd, "", mode, AT_EMPTY_PATH));
if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
return 0;
if (errno != EINVAL)
return -errno;
/* ANCIENT-MOD: AT_EMPTY_PATH is supported since kernel 5.8, fall back to /proc/self/fd/ */
if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) {
if (errno != ENOENT)
return -errno;
return proc_fd_enoent_errno();
}
return 0;
}
int touch_fd(int fd, usec_t stamp) {
+2
View File
@@ -681,6 +681,8 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu
if (fd < 0) {
if (errno == ENODEV)
return log_debug_errno(errno, "Kernel does not support file system type '%s'.", fstype);
if (ERRNO_IS_NOT_SUPPORTED(errno))
return -EAGAIN; /* ANCIENT-MOD: new mount API not available (kernel < 5.2) → don't know */
return log_debug_errno(errno, "Failed to open superblock context for '%s': %m", fstype);
}
+10 -2
View File
@@ -28,8 +28,11 @@ int pidfd_check_pidfs(int pid_fd) {
_cleanup_close_ int our_fd = -EBADF;
if (pid_fd < 0) {
our_fd = pidfd_open(getpid_cached(), /* flags= */ 0);
if (our_fd < 0)
if (our_fd < 0) {
if (ERRNO_IS_NOT_SUPPORTED(errno)) /* ANCIENT-MOD: kernel < 5.3 */
return (have_pidfs = false);
return -errno;
}
pid_fd = our_fd;
}
@@ -102,6 +105,8 @@ int pidfd_info_mask_is_supported(uint64_t mask) {
if (cached == 0) {
_cleanup_close_ int pidfd = r = RET_NERRNO(pidfd_open(getpid_cached(), /* flags= */ 0));
if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) /* ANCIENT-MOD: kernel < 5.3 */
return (cached = -EOPNOTSUPP);
if (r < 0)
return r;
@@ -334,8 +339,11 @@ int pidfd_get_inode_id_self_cached(uint64_t *ret) {
return initialized;
_cleanup_close_ int fd = pidfd_open(getpid_cached(), 0);
if (fd < 0)
if (fd < 0) {
if (ERRNO_IS_NOT_SUPPORTED(errno)) /* ANCIENT-MOD: kernel < 5.3 */
return (initialized = -EOPNOTSUPP);
return -errno;
}
r = pidfd_get_inode_id(fd, &cached);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
+27 -8
View File
@@ -106,9 +106,10 @@ int pidref_set_pid_full(PidRef *pidref, pid_t pid, unsigned flags) {
fd = pidfd_open(pid, flags);
if (fd < 0) {
/* Graceful fallback in case the kernel is out of fds.
/* Graceful fallback in case the kernel is out of fds or lacks pidfd_open() (ANCIENT-MOD: < 5.3).
* The PIDFD_THREAD flag is supported since kernel v6.9 (64bef697d33b75fc06c5789b3f8108680271529f). */
if (!(ERRNO_IS_RESOURCE(errno) || (errno == EINVAL && FLAGS_SET(flags, PIDFD_THREAD))))
if (!(ERRNO_IS_RESOURCE(errno) || ERRNO_IS_NOT_SUPPORTED(errno) ||
(errno == EINVAL && FLAGS_SET(flags, PIDFD_THREAD))))
return log_debug_errno(errno, "Failed to open pidfd for pid " PID_FMT ": %m", pid);
fd = -EBADF;
@@ -468,16 +469,34 @@ int pidref_wait_for_terminate_full(PidRef *pidref, usec_t timeout, siginfo_t *re
if (pidref->pid == 1 || pidref_is_self(pidref))
return -ECHILD;
if (pidref->fd < 0 && (timeout != USEC_INFINITY || fiber_ops_is_set()))
return -ENOMEDIUM;
usec_t ts = timeout == USEC_INFINITY ? USEC_INFINITY : usec_add(now(CLOCK_MONOTONIC), timeout);
/* ANCIENT-MOD: no pidfd (kernel < 5.3), poll with waitid(WNOHANG) to honour the timeout */
if (pidref->fd < 0 && ts != USEC_INFINITY)
for (;;) {
siginfo_t si = {};
r = RET_NERRNO(waitid(P_PID, pidref->pid, &si, WEXITED|WNOHANG));
if (r == -EINTR)
continue;
if (r < 0)
return r;
if (si.si_pid != 0) {
if (ret_si)
*ret_si = si;
return 0;
}
if (now(CLOCK_MONOTONIC) >= ts)
return -ETIMEDOUT;
(void) usleep_safe(10 * USEC_PER_MSEC);
}
/* Poll the pidfd before waitid() if either there's a finite timeout (so we can honor it) or
* we're on a fiber (so fd_wait_for_event() can suspend us instead of blocking the event loop
* inside waitid()). Otherwise let waitid() block directly. The precondition above guarantees
* pidref->fd >= 0 in both cases. */
bool poll_first = ts != USEC_INFINITY || fiber_ops_is_set();
* inside waitid()). Otherwise let waitid() block directly. */
bool poll_first = pidref->fd >= 0 && (ts != USEC_INFINITY || fiber_ops_is_set());
for (;;) {
if (poll_first) {
+13 -1
View File
@@ -1419,13 +1419,25 @@ int socket_bind_to_ifname(int fd, const char *ifname) {
}
int socket_bind_to_ifindex(int fd, int ifindex) {
char ifname[IF_NAMESIZE];
int r;
assert(fd >= 0);
if (ifindex <= 0)
/* Drop binding */
return RET_NERRNO(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, NULL, 0));
return setsockopt_int(fd, SOL_SOCKET, SO_BINDTOIFINDEX, ifindex);
r = setsockopt_int(fd, SOL_SOCKET, SO_BINDTOIFINDEX, ifindex);
if (r != -ENOPROTOOPT)
return r;
/* ANCIENT-MOD: fall back to SO_BINDTODEVICE on kernels < 5.1 which didn't have SO_BINDTOIFINDEX */
r = format_ifname(ifindex, ifname);
if (r < 0)
return r;
return socket_bind_to_ifname(fd, ifname);
}
int socket_autobind(int fd, char **ret_name) {
+111
View File
@@ -11,14 +11,17 @@
#include "dirent-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "filesystems.h"
#include "fs-util.h"
#include "hash-funcs.h"
#include "log.h"
#include "mountpoint-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "siphash24.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "time-util.h"
@@ -395,6 +398,102 @@ static const char* statx_attribute_to_name(uint64_t attr);
DEFINE_STATX_BITS_TO_STRING(statx_mask, unsigned, statx_mask_one_to_name, "0x%x");
DEFINE_STATX_BITS_TO_STRING(statx_attributes, uint64_t, statx_attribute_to_name, "0x%" PRIx64);
/* ANCIENT-MOD: emulation of STATX_MNT_ID and STATX_ATTR_MOUNT_ROOT for kernels < 5.8 */
static int fd_fdinfo_mnt_id(int fd, uint64_t *ret) {
char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
_cleanup_free_ char *p = NULL;
int r;
assert(fd >= 0);
assert(ret);
xsprintf(path, "/proc/self/fdinfo/%i", fd);
r = get_proc_field(path, "mnt_id", &p);
if (r == -ENOENT)
return proc_fd_enoent_errno();
if (r == -ENODATA)
return -EUNATCH;
if (r < 0)
return r;
return safe_atou64(p, ret);
}
static int fd_open_parent(int fd, const struct statx *sx) {
_cleanup_free_ char *p = NULL, *d = NULL;
int r;
if (FLAGS_SET(sx->stx_mask, STATX_TYPE) ? S_ISDIR(sx->stx_mode) : fd_verify_directory(fd) >= 0)
return RET_NERRNO(openat(fd, "..", O_PATH|O_DIRECTORY|O_CLOEXEC));
r = fd_get_path(fd, &p);
if (r < 0)
return r;
r = path_extract_directory(p, &d);
if (r < 0)
return r;
return RET_NERRNO(open(d, O_PATH|O_DIRECTORY|O_CLOEXEC));
}
static int xstatx_emulate(
int fd,
const char *path,
int statx_flags,
bool want_mnt_id,
bool want_mount_root,
struct statx *sx) {
_cleanup_close_ int target_fd = -EBADF, parent_fd = -EBADF;
uint64_t mnt_id, parent_mnt_id;
int r;
assert(sx);
if (isempty(path) && fd >= 0)
target_fd = RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3));
else
target_fd = RET_NERRNO(openat(fd, isempty(path) ? "." : path,
O_PATH|O_CLOEXEC|(FLAGS_SET(statx_flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0)));
if (target_fd < 0)
return target_fd;
r = fd_fdinfo_mnt_id(target_fd, &mnt_id);
if (r < 0)
return r;
if (want_mnt_id) {
sx->stx_mnt_id = mnt_id;
sx->stx_mask |= STATX_MNT_ID;
}
if (!want_mount_root)
return 0;
bool is_root = false;
parent_fd = fd_open_parent(target_fd, sx);
if (parent_fd < 0)
log_debug_errno(parent_fd, "Failed to open parent to determine mount root, assuming not a mount root: %m");
else {
struct stat a, b;
r = fd_fdinfo_mnt_id(parent_fd, &parent_mnt_id);
if (r < 0)
return r;
if (mnt_id != parent_mnt_id)
is_root = true;
else if (fstat(target_fd, &a) >= 0 && fstat(parent_fd, &b) >= 0)
is_root = stat_inode_same(&a, &b);
}
SET_FLAG(sx->stx_attributes, STATX_ATTR_MOUNT_ROOT, is_root);
sx->stx_attributes_mask |= STATX_ATTR_MOUNT_ROOT;
return 0;
}
int xstatx_full(int fd,
const char *path,
int statx_flags,
@@ -439,6 +538,18 @@ int xstatx_full(int fd,
&sx) < 0)
return negative_errno();
bool want_mnt_id =
(FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) && !(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))) ||
(FLAGS_SET(request_mask, STATX_MNT_ID) && !FLAGS_SET(sx.stx_mask, STATX_MNT_ID));
bool want_mount_root =
FLAGS_SET(mandatory_attributes, STATX_ATTR_MOUNT_ROOT) &&
!FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT);
if (want_mnt_id || want_mount_root) {
r = xstatx_emulate(fd, path, statx_flags, want_mnt_id, want_mount_root, &sx);
if (r < 0)
log_debug_errno(r, "Failed to emulate statx() mount fields, ignoring: %m");
}
if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) &&
!(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE)))
return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "statx() did not return either STATX_MNT_ID or STATX_MNT_ID_UNIQUE.");
+22 -7
View File
@@ -2681,17 +2681,32 @@ int terminal_get_size(
return r;
}
static int fd_is_vc(int fd) {
_cleanup_free_ char *name = NULL;
int r;
assert(fd >= 0);
r = getttyname_harder(fd, &name);
if (r < 0)
return r;
return tty_is_vc_resolve(name);
}
int terminal_fix_size(int input_fd, int output_fd) {
unsigned rows, columns;
int r;
/* Tries to update the current terminal dimensions to the ones reported via ANSI sequences.
*
* Why bother with this? The ioctl() information is often incorrect on serial terminals (since
* there's no handshake or protocol to determine the right dimensions in RS232), but since the ANSI
* sequences are interpreted by the final terminal instead of an intermediary tty driver they should
* be more accurate.
*/
/* Don't ask a virtual console at all. There is no intermediary here that could be wrong about the
* dimensions: the kernel renders the screen itself, so its winsize is authoritative. */
r = fd_is_vc(input_fd);
if (r < 0)
log_debug_errno(r, "Failed to determine whether terminal is a virtual console, assuming it is not: %m");
else if (r > 0) {
log_debug("Not querying dimensions of a virtual console, kernel dimensions are authoritative.");
return 0;
}
struct winsize ws = {};
if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
+41 -4
View File
@@ -937,12 +937,39 @@ static void cgroup_delegate_xattr_apply(Unit *u) {
}
static void cgroup_survive_xattr_apply(Unit *u) {
int r;
assert(u);
if (u->survive_final_kill_signal)
unit_set_xattr_graceful(u, "user.survive_final_kill_signal", "1", 1);
else
CGroupRuntime *crt = unit_get_cgroup_runtime(u);
if (!crt)
return;
if (u->survive_final_kill_signal) {
r = cg_set_xattr(
crt->cgroup_path,
"user.survive_final_kill_signal",
"1",
1,
/* flags= */ 0);
/* user xattr support was added in kernel v5.7 */
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
r = cg_set_xattr(
crt->cgroup_path,
"trusted.survive_final_kill_signal",
"1",
1,
/* flags= */ 0);
if (r < 0)
log_unit_debug_errno(u,
r,
"Failed to set 'survive_final_kill_signal' xattr on control "
"group %s, ignoring: %m",
empty_to_root(crt->cgroup_path));
} else {
unit_remove_xattr_graceful(u, "user.survive_final_kill_signal");
unit_remove_xattr_graceful(u, "trusted.survive_final_kill_signal");
}
}
static void cgroup_xattr_apply(Unit *u) {
@@ -3189,6 +3216,13 @@ static int unit_check_cgroup_events(Unit *u) {
"cgroup.events",
STRV_MAKE("populated", "frozen"),
values);
if (r == -ENXIO)
/* ANCIENT-MOD: the "frozen" key exists only since kernel 5.2 (cgroup v2 freezer) */
r = cg_get_keyed_attribute(
crt->cgroup_path,
"cgroup.events",
STRV_MAKE("populated"),
values);
if (r < 0)
return r;
@@ -3203,7 +3237,7 @@ static int unit_check_cgroup_events(Unit *u) {
/* Disregard freezer state changes due to operations not initiated by us.
* See: https://github.com/systemd/systemd/pull/13512/files#r416469963 and
* https://github.com/systemd/systemd/pull/13512#issuecomment-573007207 */
if (IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_FREEZING_BY_PARENT, FREEZER_THAWING))
if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_FREEZING_BY_PARENT, FREEZER_THAWING))
unit_freezer_complete(u, streq(values[1], "0") ? FREEZER_RUNNING : FREEZER_FROZEN);
free_many_charp(values, ELEMENTSOF(values));
@@ -4122,6 +4156,9 @@ int unit_cgroup_freezer_action(Unit *u, FreezerAction action) {
assert(action >= 0);
assert(action < _FREEZER_ACTION_MAX);
if (!cg_freezer_supported())
return 0;
unit_next_freezer_state(u, action, &next, &objective);
CGroupRuntime *crt = unit_get_cgroup_runtime(u);
+3 -2
View File
@@ -1052,8 +1052,9 @@ static int setup_credentials_internal(
}
mfd = fsmount_credentials_fs(&fs_fd);
if (ERRNO_IS_NEG_PRIVILEGE(mfd) && !dir_mounted) {
log_debug_errno(mfd, "Lacking privilege to mount credentials fs, falling back to plain directory.");
if ((ERRNO_IS_NEG_PRIVILEGE(mfd) || ERRNO_IS_NEG_NOT_SUPPORTED(mfd)) && !dir_mounted) {
/* ANCIENT-MOD: also fall back if the new mount API is unavailable (kernel < 5.2) */
log_debug_errno(mfd, "Lacking privilege or kernel support to mount credentials fs, falling back to plain directory.");
return setup_credentials_plain_dir(context, cred_dir);
}
if (mfd < 0)
+28 -4
View File
@@ -7,6 +7,7 @@
#include <stdlib.h>
#include <sys/file.h>
#include <sys/mount.h>
#include <sys/utsname.h>
#include <unistd.h>
#include "alloc-util.h"
@@ -1562,6 +1563,25 @@ static int mount_private_cgroup2fs(const MountEntry *m, const NamespaceParameter
return mount_private_apivfs("cgroup2", mount_entry_path(m), "/sys/fs/cgroup", /* opts= */ NULL, p->runtime_scope);
}
/* ANCIENT-MOD: before kernel 5.8 procfs options are per pid namespace (shared with the host /proc) and
* textual hidepid= / subset= do not exist, hence ignore ProtectProc=/ProcSubset= there. */
static bool procfs_per_instance_options_supported(void) {
static int cached = -1;
struct utsname uts;
if (cached >= 0)
return cached;
if (uname(&uts) < 0)
return (cached = true);
cached = strverscmp_improved(uts.release, "5.8") >= 0;
if (!cached)
log_debug("Kernel %s does not support per-instance procfs options, ignoring ProtectProc=/ProcSubset=.", uts.release);
return cached;
}
static int mount_procfs(const MountEntry *m, const NamespaceParameters *p) {
_cleanup_free_ char *opts = NULL;
int r;
@@ -1569,8 +1589,9 @@ static int mount_procfs(const MountEntry *m, const NamespaceParameters *p) {
assert(m);
assert(p);
if (p->protect_proc != PROTECT_PROC_DEFAULT ||
p->proc_subset != PROC_SUBSET_ALL) {
if ((p->protect_proc != PROTECT_PROC_DEFAULT ||
p->proc_subset != PROC_SUBSET_ALL) &&
procfs_per_instance_options_supported()) {
opts = strjoin("hidepid=",
p->protect_proc == PROTECT_PROC_DEFAULT ? "off" : protect_proc_to_string(p->protect_proc));
@@ -2663,7 +2684,9 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
* as well now. */
.propagation = MS_SLAVE,
});
if (_root_mount_fd < 0 && !ERRNO_IS_NEG_PRIVILEGE(_root_mount_fd) && _root_mount_fd != -EINVAL)
/* ANCIENT-MOD: also tolerate missing open_tree() (kernel < 5.2) */
if (_root_mount_fd < 0 && !ERRNO_IS_NEG_PRIVILEGE(_root_mount_fd) &&
!ERRNO_IS_NEG_NOT_SUPPORTED(_root_mount_fd) && _root_mount_fd != -EINVAL)
return log_debug_errno(_root_mount_fd, "Failed to clone specified directory: %m");
root_mount_fd = _root_mount_fd;
@@ -2866,7 +2889,8 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
/* In case /proc is successfully mounted with pid tree subset only (ProcSubset=pid), the protective
mounts to non-pid /proc paths would fail. But the pid only option may have failed gracefully, so
let's try the mounts but it's not fatal if they don't succeed. */
bool ignore_protect_proc = p->ignore_protect_paths || p->proc_subset == PROC_SUBSET_PID;
bool ignore_protect_proc = p->ignore_protect_paths ||
(p->proc_subset == PROC_SUBSET_PID && procfs_per_instance_options_supported());
if (p->protect_kernel_tunables) {
r = append_static_mounts(&ml,
protect_kernel_tunables_proc_table,
+1 -1
View File
@@ -6654,7 +6654,7 @@ int unit_freezer_action(Unit *u, FreezerAction action) {
assert(u);
assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));
if (!unit_can_freeze(u))
if (!cg_freezer_supported() || !unit_can_freeze(u))
return -EOPNOTSUPP;
if (u->job)
+4 -6
View File
@@ -123,14 +123,12 @@ int bus_call_suspend(
if (r < 0)
return sd_bus_error_set_errno(reterr_error, r);
/* An interruption takes precedence even if the reply arrived in the same tick: the wait
* consumed the queued cancellation or timeout, and returning the reply instead would drop it. */
r = sd_fiber_await(f);
/* If the future isn't resolved, the suspend was interrupted before a reply arrived (fiber
* cancelled, fiber-wide SD_FIBER_TIMEOUT scope expired, …). There's no reply to extract,
* so surface the resume error directly. When the future is resolved, future_get_bus_reply()
* recovers either the reply or the detailed sd_bus_error from the error reply. */
if (sd_future_state(f) != SD_FUTURE_RESOLVED)
if (r < 0)
return sd_bus_error_set_errno(reterr_error, r);
/* Recovers either the reply or the detailed sd_bus_error from the error reply. */
return future_get_bus_reply(f, reterr_error, ret_reply);
}
+1
View File
@@ -299,6 +299,7 @@ static int interrupted_calls_fiber(void *userdata) {
}
ASSERT_OK_ZERO(sd_fiber_await(caller));
ASSERT_OK_ZERO(sd_future_result(caller));
ASSERT_NULL(s->c->pending_call);
ASSERT_NULL(s->c->waiter);
}
@@ -189,6 +189,8 @@ static int request_exit(void *userdata) {
/* Wait for all client fibers to complete before requesting exit */
ASSERT_OK(sd_fiber_await(args->client1));
ASSERT_OK(sd_fiber_await(args->client2));
ASSERT_OK(sd_future_result(args->client1));
ASSERT_OK(sd_future_result(args->client2));
ASSERT_OK(sd_bus_new(&bus));
+4
View File
@@ -411,6 +411,10 @@ int event_run_suspend(sd_event *e, uint64_t timeout) {
if (r < 0)
return r;
r = sd_future_result(group);
if (r < 0)
return r;
r = sd_event_prepare(e);
if (r == 0)
r = sd_event_wait(e, 0);
+46 -14
View File
@@ -3,6 +3,7 @@
#include <linux/magic.h>
#include <malloc.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/timerfd.h>
#include <sys/wait.h>
#include <threads.h>
@@ -51,9 +52,20 @@ static bool EVENT_SOURCE_WATCH_PIDFD(const sd_event_source *s) {
/* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */
return s &&
s->type == SOURCE_CHILD &&
s->child.pidfd >= 0 &&
(s->child.options & ~WNOWAIT) == WEXITED;
}
/* ANCIENT-MOD: child sources without pidfd (kernel < 5.3) fall back to waitid(P_PID) */
static int child_waitid(sd_event_source *s, siginfo_t *si, int options) {
assert(s);
if (s->child.pidfd >= 0)
return waitid(P_PIDFD, s->child.pidfd, si, options);
return waitid(P_PID, s->child.pid, si, options);
}
static bool event_source_is_online(sd_event_source *s) {
assert(s);
return s->enabled != SD_EVENT_OFF && !s->ratelimited;
@@ -1098,10 +1110,12 @@ static sd_event_source* source_free(sd_event_source *s) {
if (s->child.process_owned) {
assert(s->child.pid > 0);
assert(s->child.pidfd >= 0);
if (!s->child.exited) {
r = RET_NERRNO(pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0));
if (s->child.pidfd >= 0)
r = RET_NERRNO(pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0));
else
r = RET_NERRNO(kill(s->child.pid, SIGKILL));
if (r < 0 && r != -ESRCH)
log_debug_errno(r, "Failed to kill process " PID_FMT ", ignoring: %m",
s->child.pid);
@@ -1111,7 +1125,7 @@ static sd_event_source* source_free(sd_event_source *s) {
siginfo_t si = {};
/* Reap the child if we can */
(void) waitid(P_PIDFD, s->child.pidfd, &si, WEXITED);
(void) child_waitid(s, &si, WEXITED);
}
}
@@ -1648,10 +1662,22 @@ _public_ int sd_event_add_child(
* PID, and make regular waitid() handling race-free. */
s->child.pidfd = pidfd_open(pid, 0);
if (s->child.pidfd < 0)
return -errno;
if (s->child.pidfd < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return -errno;
s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */
/* Without pidfd we depend on SIGCHLD via signalfd, which only works if it is blocked */
r = signal_is_blocked(SIGCHLD);
if (r < 0)
return r;
if (r == 0) {
log_debug("pidfd_open() unavailable, blocking SIGCHLD to watch child processes.");
r = sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD);
if (r < 0)
return r;
}
} else
s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */
s->wakeup = WAKEUP_EVENT_SOURCE;
s->child.options = options;
@@ -3264,7 +3290,6 @@ _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, cons
assert_return(s->type == SOURCE_CHILD, -EDOM);
assert_return(!event_origin_changed(s->event), -ECHILD);
assert_return(SIGNAL_VALID(sig), -EINVAL);
assert(s->child.pidfd >= 0);
/* If we already have seen indication the process exited refuse sending a signal early. */
if (s->child.exited)
@@ -3276,14 +3301,20 @@ _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, cons
if (si)
copy = *si;
return RET_NERRNO(pidfd_send_signal(s->child.pidfd, sig, si ? &copy : NULL, flags));
if (s->child.pidfd >= 0)
return RET_NERRNO(pidfd_send_signal(s->child.pidfd, sig, si ? &copy : NULL, flags));
if (flags != 0)
return -EOPNOTSUPP;
if (si)
return RET_NERRNO(syscall(SYS_rt_sigqueueinfo, s->child.pid, sig, &copy));
return RET_NERRNO(kill(s->child.pid, sig));
}
_public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
assert_return(!event_origin_changed(s->event), -ECHILD);
assert(s->child.pidfd >= 0);
return s->child.pidfd_owned;
}
@@ -3292,7 +3323,9 @@ _public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
assert_return(!event_origin_changed(s->event), -ECHILD);
assert(s->child.pidfd >= 0);
if (s->child.pidfd < 0)
return -EOPNOTSUPP;
s->child.pidfd_owned = own;
return 0;
@@ -3764,7 +3797,6 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori
HASHMAP_FOREACH(s, e->child_sources) {
assert(s->type == SOURCE_CHILD);
assert(s->child.pidfd >= 0);
if (s->priority > threshold)
continue;
@@ -3784,7 +3816,7 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori
continue;
zero(s->child.siginfo);
if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo,
if (child_waitid(s, &s->child.siginfo,
WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | (s->child.options & ~WNOWAIT)) < 0)
return negative_errno();
@@ -3798,7 +3830,7 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori
* from the queue, since there's no benefit in leaving it queued. */
assert(s->child.options & (WSTOPPED|WCONTINUED));
(void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED)));
(void) child_waitid(s, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED)));
}
r = source_set_pending(s, true);
@@ -4281,7 +4313,7 @@ static int source_dispatch(sd_event_source *s) {
/* Now, reap the PID for good (unless WNOWAIT was specified by the caller). */
if (zombie) {
(void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|WEXITED|(s->child.options & WNOWAIT));
(void) child_waitid(s, &s->child.siginfo, WNOHANG|WEXITED|(s->child.options & WNOWAIT));
if (!FLAGS_SET(s->child.options, WNOWAIT))
s->child.waited = true;
}
+14 -1
View File
@@ -54,6 +54,10 @@ static ssize_t fiber_io_operation(
if (r < 0)
return r;
r = sd_future_result(io);
if (r < 0)
return r;
return func(fd, args);
}
@@ -227,9 +231,13 @@ int sd_fiber_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
if (r < 0)
return r;
r = sd_fiber_await(io);
if (r < 0)
return r;
/* future_new_io resolves with the revents mask on success; translate any positive value
* (e.g. POLLOUT) back to the connect(2) success status. */
r = sd_fiber_await(io);
r = sd_future_result(io);
return r > 0 ? 0 : r;
}
@@ -445,6 +453,11 @@ int sd_fiber_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *time
r = sd_fiber_await(group);
if (r < 0 && r != -ETIME)
return r;
if (r >= 0) {
r = sd_future_result(group);
if (r < 0)
return r;
}
/* Always sweep fds with a non-blocking ppoll(): the timer and an fd readiness can resolve in
* the same event-loop tick (or the fd can become ready between the timer firing and us being
+7 -4
View File
@@ -822,7 +822,11 @@ int sd_fiber_sleep(uint64_t usec) {
if (r < 0)
return r;
return sd_fiber_await(timer);
r = sd_fiber_await(timer);
if (r < 0)
return r;
return sd_future_result(timer);
}
static int fiber_wait_callback(sd_future *target, void *userdata) {
@@ -861,7 +865,7 @@ int sd_fiber_await(sd_future *target) {
assert_return(sd_future_get_event(target) == sd_future_get_event(f), -EINVAL);
if (sd_future_state(target) == SD_FUTURE_RESOLVED)
return sd_future_result(target);
return 0;
/* The fiber is executing inside event dispatch, so its loop cannot have finished yet.
* Waiting during exit is supported by the slot's exit source. */
@@ -879,8 +883,7 @@ int sd_fiber_await(sd_future *target) {
if (sd_future_state(target) != SD_FUTURE_RESOLVED)
return -EBUSY;
/* Read the result while the slot still pins the target: releasing it may drop the last ref. */
return sd_future_result(target);
return 0;
}
sd_future* sd_future_cancel_wait_unref(sd_future *f) {
+18 -9
View File
@@ -379,16 +379,17 @@ TEST(fiber_wait_for_completed) {
ASSERT_OK_EQ(sd_future_result(target), 100);
}
/* Test: awaiting an already-resolved future returns the future's result directly */
/* Test: awaiting an already-resolved future completes immediately */
static int await_resolved_fiber(void *userdata) {
sd_future *target = userdata;
ASSERT_EQ((int) sd_future_state(target), (int) SD_FUTURE_RESOLVED);
ASSERT_OK_EQ(sd_fiber_await(target), 77);
ASSERT_OK_ZERO(sd_fiber_await(target));
ASSERT_OK_EQ(sd_future_result(target), 77);
return 0;
}
TEST(fiber_await_resolved_returns_result) {
TEST(fiber_await_resolved) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
ASSERT_OK(sd_event_new(&e));
ASSERT_OK(sd_event_set_exit_on_idle(e, true));
@@ -1667,13 +1668,14 @@ static int await_result_fiber(void *userdata) {
_cleanup_(sd_future_unrefp) sd_future *awaited = NULL;
ASSERT_OK(sd_future_new_defer(sd_fiber_get_event(), result, &awaited));
ASSERT_EQ(sd_fiber_await(awaited), result); /* Pending. */
ASSERT_EQ(sd_fiber_await(awaited), result); /* Already resolved. */
ASSERT_OK_ZERO(sd_fiber_await(awaited)); /* Pending. */
ASSERT_EQ(sd_future_result(awaited), result);
ASSERT_OK_ZERO(sd_fiber_await(awaited)); /* Already resolved. */
ASSERT_OK_ZERO(sd_fiber_yield());
/* An already-resolved future must leave an unrelated pending interruption alone. */
ASSERT_OK(sd_fiber_resume(sd_fiber_get_current(), -ECANCELED));
ASSERT_EQ(sd_fiber_await(awaited), result);
ASSERT_OK_ZERO(sd_fiber_await(awaited));
ASSERT_ERROR(sd_fiber_yield(), ECANCELED);
}
@@ -1693,7 +1695,14 @@ TEST(fiber_await_result) {
}
static int await_borrowed_fiber(void *userdata) {
return sd_fiber_await(userdata);
_cleanup_(sd_future_unrefp) sd_future *target = sd_future_ref(ASSERT_PTR(userdata));
int r;
r = sd_fiber_await(target);
if (r < 0)
return r;
return sd_future_result(target);
}
TEST(fiber_await_last_reference) {
@@ -1709,11 +1718,11 @@ TEST(fiber_await_last_reference) {
ASSERT_OK(sd_fiber_new(e, "await-borrowed", await_borrowed_fiber, target,
/* destroy= */ NULL, &waiter));
/* Suspend in await, so its non-floating callback slot holds a reference to target. */
/* Suspend in await, so the waiter's own reference and the wait's slot keep target alive. */
ASSERT_OK_POSITIVE(sd_event_run(e, 0));
ASSERT_EQ(sd_future_state(waiter), SD_FUTURE_PENDING);
/* Leave the slot as the sole owner. Await must read the result before freeing it. */
/* Drop our reference while the wait is in flight: the waiter still reads the result. */
ASSERT_OK(sd_future_resolve(target, result));
target = sd_future_unref(target);
+21 -12
View File
@@ -404,7 +404,7 @@ TEST(future_group_cancels_parent_on_child_error) {
ASSERT_ERROR(s.group_result, EINVAL);
}
/* Awaiting a group must return the child's error instead of cancelling the waiting parent. */
/* Awaiting a group must complete with the child's error instead of cancelling the waiting parent. */
static int await_gets_error_driver(void *userdata) {
_cleanup_(sd_future_unrefp) sd_future *group = NULL, *errorer = NULL;
int *await_result = ASSERT_PTR(userdata);
@@ -413,7 +413,8 @@ static int await_gets_error_driver(void *userdata) {
ASSERT_OK(sd_future_new_defer(sd_fiber_get_event(), -EINVAL, &errorer));
ASSERT_OK(sd_future_group_add(group, errorer));
*await_result = sd_fiber_await(group);
ASSERT_OK_ZERO(sd_fiber_await(group));
*await_result = sd_future_result(group);
ASSERT_OK_ZERO(sd_fiber_yield());
return 0;
}
@@ -462,10 +463,9 @@ typedef struct AddResolvedState {
static int add_resolved_driver(void *userdata) {
AddResolvedState *s = ASSERT_PTR(userdata);
/* Drive the pre-built child to completion. The defer resolves with -EINVAL, which
* makes sd_fiber_await return -EINVAL — we don't ASSERT_OK that. The child is now
* RESOLVED and we can add it to the group. */
(void) sd_fiber_await(s->child);
/* Drive the pre-built child to completion. The defer resolves with -EINVAL, but the wait
* itself succeeds. The child is now RESOLVED and we can add it to the group. */
ASSERT_OK_ZERO(sd_fiber_await(s->child));
ASSERT_EQ(sd_future_state(s->child), SD_FUTURE_RESOLVED);
s->add_result = sd_future_group_add(s->group, s->child);
@@ -473,7 +473,8 @@ static int add_resolved_driver(void *userdata) {
/* Await drives the loop one more tick so the defer that wraps the RESOLVED child can
* fire group_child_resolved and settle the group. */
s->await_result = sd_fiber_await(s->group);
ASSERT_OK_ZERO(sd_fiber_await(s->group));
s->await_result = sd_future_result(s->group);
return 0;
}
@@ -510,7 +511,8 @@ typedef struct AddManyState {
static int add_many_driver(void *userdata) {
AddManyState *s = ASSERT_PTR(userdata);
ASSERT_OK(sd_future_group_add_many(s->group, s->a, s->b, s->c));
s->join_result = sd_fiber_await(s->group);
ASSERT_OK_ZERO(sd_fiber_await(s->group));
s->join_result = sd_future_result(s->group);
return 0;
}
@@ -744,12 +746,12 @@ static int parent_await_driver(void *userdata) {
s->ready = true;
ASSERT_EQ(sd_fiber_suspend(), s->result);
/* Parent cancellation does not replace the group's result. A later await of the resolved
* group returns that result, just like sd_future_result(). */
/* Parent cancellation does not replace the group's result, and a later await of the
* resolved group completes immediately. */
if (s->result == -ECANCELED) {
ASSERT_EQ(sd_future_state(s->group), SD_FUTURE_RESOLVED);
ASSERT_ERROR(sd_future_result(s->group), EIO);
ASSERT_ERROR(sd_fiber_await(s->group), EIO);
ASSERT_OK_ZERO(sd_fiber_await(s->group));
}
return 0;
}
@@ -830,7 +832,14 @@ TEST(future_group_external_cancel_leaves_parent_running) {
}
static int peer_await_driver(void *userdata) {
return sd_fiber_await(userdata);
sd_future *group = ASSERT_PTR(userdata);
int r;
r = sd_fiber_await(group);
if (r < 0)
return r;
return sd_future_result(group);
}
TEST(future_group_peer_await_does_not_suppress_parent_cancel) {
+2 -1
View File
@@ -117,7 +117,8 @@ int rtnl_resolve_ifname_full(
r = sd_netlink_call(*rtnl, message, 0, &reply);
if (r >= 0)
return parse_newlink_message(reply, ret_name, ret_altnames);
if (r != -ENODEV)
/* The kernels older than 76c9ac0ee878f6693d398d3a95ccaf85e1f597a6 (v5.5) return -EINVAL. */
if (!IN_SET(r, -ENODEV, -EINVAL))
return r;
}
+1 -1
View File
@@ -866,7 +866,7 @@ static int create_session_message(
if (!avoid_pidfd) {
pidfd = pidfd_open(getpid_cached(), 0);
if (pidfd < 0)
if (pidfd < 0 && !ERRNO_IS_NOT_SUPPORTED(errno)) /* ANCIENT-MOD: kernel < 5.3 */
return -errno;
}
+41 -4
View File
@@ -2448,6 +2448,38 @@ static int link_update_driver(Link *link, sd_netlink_message *message) {
return 1; /* needs reconfigure */
}
static int link_update_permanent_hardware_address_from_ethtool(Link *link, sd_netlink_message *message) {
int r;
assert(link);
assert(link->manager);
assert(message);
if (link->ethtool_permanent_hw_addr_read)
return 0;
/* When udevd is running, read the permanent hardware address after the interface is
* initialized by udevd. Otherwise, ethtool may not work correctly. See issue #22538.
* When udevd is not running, read the value when the interface is detected. */
if (udev_available() && !link->dev)
return 0;
/* If the interface does not have a hardware address, then it will not have a permanent address either. */
r = netlink_message_read_hw_addr(message, IFLA_ADDRESS, NULL);
if (r == -ENODATA)
return 0;
if (r < 0)
return log_link_debug_errno(link, r, "Failed to read IFLA_ADDRESS attribute: %m");
link->ethtool_permanent_hw_addr_read = true;
r = ethtool_get_permanent_hw_addr(&link->manager->ethtool_fd, link->ifname, &link->permanent_hw_addr);
if (r < 0)
log_link_debug_errno(link, r, "Permanent hardware address not found, continuing without: %m");
return 0;
}
static int link_update_permanent_hardware_address(Link *link, sd_netlink_message *message) {
int r;
@@ -2459,10 +2491,15 @@ static int link_update_permanent_hardware_address(Link *link, sd_netlink_message
return 0;
r = netlink_message_read_hw_addr(message, IFLA_PERM_ADDRESS, &link->permanent_hw_addr);
if (r == -ENODATA)
return 0;
if (r < 0)
return log_link_debug_errno(link, r, "Failed to read IFLA_PERM_ADDRESS attribute: %m");
if (r < 0) {
if (r != -ENODATA)
return log_link_debug_errno(link, r, "Failed to read IFLA_PERM_ADDRESS attribute: %m");
/* Fallback to ethtool for kernels older than v5.6 (f74877a5457d34d604dba6dbbb13c4c05bac8b93). */
r = link_update_permanent_hardware_address_from_ethtool(link, message);
if (r < 0)
return r;
}
if (link->permanent_hw_addr.length > 0)
log_link_debug(link, "Saved permanent hardware address: %s", HW_ADDR_TO_STR(&link->permanent_hw_addr));
+6 -1
View File
@@ -52,6 +52,7 @@
#include "networkd-wwan-bus.h"
#include "ordered-set.h"
#include "qdisc.h"
#include "selinux-util.h"
#include "set.h"
#include "stat-util.h"
#include "string-util.h"
@@ -1149,8 +1150,12 @@ int manager_enumerate(Manager *m) {
if (r < 0)
return log_error_errno(r, "Could not enumerate neighbors: %m");
/* ANCIENT-MOD: NextHop support is added in kernel v5.3 (65ee00a9409f751188a8cdc0988167858eb4a536),
* and older kernels return -EOPNOTSUPP, or -EINVAL if SELinux is enabled. */
r = manager_enumerate_nexthop(m);
if (r < 0)
if (r == -EOPNOTSUPP || (r == -EINVAL && mac_selinux_enforcing()))
log_debug_errno(r, "Could not enumerate nexthops, ignoring: %m");
else if (r < 0)
return log_error_errno(r, "Could not enumerate nexthops: %m");
r = manager_enumerate_routes(m);
+9
View File
@@ -29,6 +29,15 @@ executables += [
'report-cgroup-server.c',
),
},
libexec_template + {
'name' : 'systemd-report-diskspace',
'sources' : files(
'report-diskspace.c',
'report-diskspace-server.c',
),
'dependencies' : libmount_cflags,
'conditions' : ['HAVE_LIBMOUNT'],
},
libexec_template + {
'name' : 'systemd-report-files',
'sources' : files(
+95
View File
@@ -0,0 +1,95 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "sd-varlink.h"
#include "build.h"
#include "dlopen-note.h"
#include "log.h"
#include "main-func.h"
#include "report-diskspace.h"
#include "varlink-io.systemd.Metrics.h"
#include "varlink-util.h"
#include "verbs.h"
COMMAND(
"systemd-report-diskspace\0",
"Report disk space metrics.",
);
static int vl_server(void) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL;
int r;
r = varlink_server_new(&vs, SD_VARLINK_SERVER_INHERIT_USERDATA, /* userdata= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
r = sd_varlink_server_add_interface(vs, &vl_interface_io_systemd_Metrics);
if (r < 0)
return log_error_errno(r, "Failed to add Varlink interface: %m");
r = sd_varlink_server_bind_method_many(
vs,
"io.systemd.Metrics.List", vl_method_list_metrics,
"io.systemd.Metrics.Describe", vl_method_describe_metrics);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
r = sd_varlink_server_loop_auto(vs);
if (r < 0)
return log_error_errno(r, "Failed to run Varlink event loop: %m");
return 0;
}
static int parse_argv(int argc, char *argv[]) {
int r;
assert(argc >= 0);
assert(argv);
OptionParser opts = { argc, argv };
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
OPTION_COMMON_HELP:
return command_print_help();
OPTION_COMMON_VERSION:
return version();
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(SD_JSON_FORMAT_OFF);
}
if (option_parser_get_n_args(&opts) > 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"This program takes no arguments.");
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
if (r < 0)
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"This program can only run as a Varlink service.");
return 1;
}
static int run(int argc, char *argv[]) {
int r;
LIBMOUNT_NOTE(required);
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
return vl_server();
}
DEFINE_MAIN_FUNCTION(run);
+366
View File
@@ -0,0 +1,366 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <sys/statvfs.h>
#include "sd-device.h"
#include "sd-json.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "blockdev-util.h"
#include "device-util.h"
#include "devnum-util.h"
#include "fd-util.h"
#include "hash-funcs.h"
#include "hashmap.h"
#include "json-util.h"
#include "libmount-util.h"
#include "log.h"
#include "metrics.h"
#include "mountpoint-util.h"
#include "path-util.h"
#include "report-diskspace.h"
#include "sort-util.h"
#include "stat-util.h"
/* Reports the size of and the disk space still available on all mounted, writable, block device backed file
* systems, i.e. roughly what df(1) shows. Pseudo file systems (procfs, sysfs, tmpfs, …), network file
* systems and other file systems not backed by a block device are skipped, as are read-only mounts: their
* free space is not going to change, and typically is not meaningful anyway (squashfs, erofs, DDIs, …).
*
* A file system (i.e. a superblock, identified by its device number) that is mounted in multiple places
* (bind mounts, btrfs subvolumes, …) is reported only once, under its shortest mount path. */
typedef struct FileSystem {
dev_t devno; /* the hashmap key: device number of the superblock */
struct libmnt_fs *fs; /* the mount we report the file system under, owned by the mount table */
int fd; /* O_PATH fd to the mount point */
uint64_t free_bytes;
uint64_t size_bytes;
} FileSystem;
static FileSystem* file_system_free(FileSystem *f) {
if (!f)
return NULL;
safe_close(f->fd);
return mfree(f);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(FileSystem*, file_system_free);
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
file_system_hash_ops,
dev_t, devt_hash_func, devt_compare_func,
FileSystem, file_system_free);
static const char* file_system_path(const FileSystem *f) {
assert(f);
assert(f->fs);
return ASSERT_PTR(sym_mnt_fs_get_target(f->fs));
}
/* Among the mount points of the same file system we prefer the shortest path, and among equally short ones
* the alphabetically earlier. */
static bool path_is_preferred(const char *a, const char *b) {
assert(a);
assert(b);
int ret = CMP(strlen(a), strlen(b));
if (ret != 0)
return ret < 0;
return path_compare(a, b) < 0;
}
static int file_system_compare(FileSystem *const *a, FileSystem *const *b) {
assert(a);
assert(b);
return path_compare(file_system_path(*a), file_system_path(*b));
}
static int mount_collect(Hashmap **file_systems, struct libmnt_fs *fs) {
int r;
assert(file_systems);
assert(fs);
const char *target = sym_mnt_fs_get_target(fs);
if (!target)
return 0;
const char *fstype = sym_mnt_fs_get_fstype(fs);
if (!fstype)
return 0;
if (!fstype_is_blockdev_backed(fstype)) {
log_debug("Mount '%s' of type %s is not backed by a block device, skipping.", target, fstype);
return 0;
}
/* This covers both file systems that are read-only as a whole, and read-only bind mounts of
* otherwise writable file systems, since libmount searches both the superblock and the per-mount
* options here. */
r = sym_mnt_fs_get_option(fs, "ro", /* value= */ NULL, /* valsz= */ NULL);
if (r < 0)
return log_debug_errno(r, "Failed to check whether '%s' is mounted read-only: %m", target);
if (r == 0) {
log_debug("Mount '%s' is read-only, skipping.", target);
return 0;
}
dev_t devno = sym_mnt_fs_get_devno(fs);
if (devno == 0) {
log_debug("Mount '%s' has no device number, skipping.", target);
return 0;
}
FileSystem *existing = hashmap_get(*file_systems, &devno);
if (existing && !path_is_preferred(target, file_system_path(existing))) {
log_debug("File system mounted on '%s' is already known as '%s', skipping.",
target, file_system_path(existing));
return 0;
}
/* Mount points below a directory we lack access to (or that have been unmounted since the mount
* table was read) are simply not considered. */
_cleanup_close_ int fd = open(target, O_PATH|O_CLOEXEC);
if (fd < 0) {
log_debug_errno(errno, "Failed to open mount point '%s', ignoring: %m", target);
return 0;
}
/* Make sure the path still refers to the mount we are looking at, and not to something that was
* mounted on top of it since, shadowing it. Also catches mount points that are hidden entirely. */
int mnt_id;
r = path_get_mnt_id_at(fd, /* path= */ NULL, &mnt_id);
if (r < 0) {
log_debug_errno(r, "Failed to get mount ID of '%s', ignoring: %m", target);
return 0;
}
if (mnt_id != sym_mnt_fs_get_id(fs)) {
log_debug("Mount '%s' is shadowed by another mount, skipping.", target);
return 0;
}
struct statfs sfs;
r = xstatfsat(fd, /* path= */ NULL, &sfs);
if (r < 0) {
log_debug_errno(r, "Failed to statfs() '%s', ignoring: %m", target);
return 0;
}
/* Already excluded above based on the mount table, but let's check again on the authoritative data,
* in case things changed in the meantime. */
if (FLAGS_SET(sfs.f_flags, ST_RDONLY)) {
log_debug("Mount '%s' is read-only, skipping.", target);
return 0;
}
if (sfs.f_blocks == 0) {
log_debug("Mount '%s' has zero size, skipping.", target);
return 0;
}
/* Report the space available to unprivileged users as free, matching what df(1) shows as "Avail".
* Space reserved for the superuser (typically 5% on ext4) is hence not counted as free. */
uint64_t size_bytes, free_bytes;
if (!MUL_SAFE(&free_bytes, (uint64_t) sfs.f_frsize, (uint64_t) sfs.f_bavail) ||
!MUL_SAFE(&size_bytes, (uint64_t) sfs.f_frsize, (uint64_t) sfs.f_blocks)) {
log_debug("Sizes of '%s' overflow, skipping.", target);
return 0;
}
_cleanup_(file_system_freep) FileSystem *f = new(FileSystem, 1);
if (!f)
return log_oom();
*f = (FileSystem) {
.devno = devno,
.fs = fs,
.fd = TAKE_FD(fd),
.free_bytes = free_bytes,
.size_bytes = size_bytes,
};
/* Replace the entry we already have for this file system, if any, by the better one */
file_system_free(hashmap_remove(*file_systems, &f->devno));
if (hashmap_ensure_put(file_systems, &file_system_hash_ops, &f->devno, f) < 0)
return log_oom();
TAKE_PTR(f);
return 0;
}
/* If the file system's backing block device is a stacked one (LUKS-style DM, …), returns the device node of
* the block device it originates from, chased down through all layers. Returns NULL (and success) if the
* backing device is not a stacked block device, or is not a block device at all. */
static int file_system_get_originating_source(const FileSystem *f, char **ret) {
int r;
assert(f);
assert(ret);
/* NB: We do not use f->devno here, but acquire the devno again from the fd. That's because on btrfs
* we want the devnum of the backing device node, and not of the anonymous superblock */
dev_t devno;
r = get_block_device_fd(f->fd, &devno);
if (r < 0) {
return log_debug_errno(r, "Failed to get backing block device of '%s', ignoring: %m", file_system_path(f));
*ret = NULL;
return 0;
}
if (devno == 0) {
log_debug("Path '%s' has no backing block device, ignoring.", file_system_path(f));
*ret = NULL;
return 0;
}
/* The mount's device number is the backing block device's (except for file systems that use
* anonymous device numbers, such as btrfs, which we hence cannot trace this way). */
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
r = sd_device_new_from_devnum(&dev, 'b', devno);
if (r < 0) {
log_debug_errno(r, "Failed to get block device " DEVNUM_FORMAT_STR " backing '%s', ignoring: %m",
DEVNUM_FORMAT_VAL(devno), file_system_path(f));
*ret = NULL;
return 0;
}
_cleanup_(sd_device_unrefp) sd_device *origin = NULL;
r = block_device_get_originating(dev, &origin, /* recursive= */ true);
if (r < 0) {
if (r != -ENOENT) /* -ENOENT means: not stacked */
log_device_debug_errno(dev, r, "Failed to determine originating block device, ignoring: %m");
*ret = NULL;
return 0;
}
const char *node;
r = sd_device_get_devname(origin, &node);
if (r < 0) {
log_device_debug_errno(origin, r, "Failed to get device node of originating block device, ignoring: %m");
*ret = NULL;
return 0;
}
return strdup_to(ret, node);
}
static int file_system_send(
const MetricFamily mf[static 2],
sd_varlink *link,
const FileSystem *f) {
int r;
assert(mf && mf[0].name && mf[1].name);
assert(link);
assert(f);
_cleanup_free_ char *originating_source = NULL;
r = file_system_get_originating_source(f, &originating_source);
if (r < 0)
return log_error_errno(r, "Failed to determine originating source: %m");
_cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL;
r = sd_json_buildo(
&fields,
JSON_BUILD_PAIR_STRING_NON_EMPTY("source", sym_mnt_fs_get_source(f->fs)),
JSON_BUILD_PAIR_STRING_NON_EMPTY("originatingSource", originating_source),
SD_JSON_BUILD_PAIR_STRING("fileSystemType", sym_mnt_fs_get_fstype(f->fs)));
if (r < 0)
return log_error_errno(r, "Failed to build metric fields: %m");
r = metric_build_send_unsigned(mf + 0, link, file_system_path(f), f->free_bytes, fields);
if (r < 0)
return r;
return metric_build_send_unsigned(mf + 1, link, file_system_path(f), f->size_bytes, fields);
}
static int diskspace_generate(
const MetricFamily mf[static 2],
sd_varlink *link,
void *userdata) {
int r;
assert(mf);
assert(link);
_cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL;
_cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL;
r = libmount_parse_mountinfo(/* source= */ NULL, &table, &iter);
if (r < 0)
return log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m");
/* First pass: collect all file systems we care about, keyed by superblock */
_cleanup_hashmap_free_ Hashmap *file_systems = NULL;
for (;;) {
struct libmnt_fs *fs;
r = sym_mnt_table_next_fs(table, iter, &fs);
if (r == 1)
break;
if (r < 0)
return log_error_errno(r, "Failed to get next entry from /proc/self/mountinfo: %m");
r = mount_collect(&file_systems, fs);
if (r < 0)
return r;
}
/* Second pass: report them, sorted by mount path for stable output */
_cleanup_free_ FileSystem **sorted = NULL;
size_t n = 0;
FileSystem *f;
HASHMAP_FOREACH(f, file_systems) {
if (!GREEDY_REALLOC(sorted, n + 1))
return log_oom();
sorted[n++] = f;
}
typesafe_qsort(sorted, n, file_system_compare);
FOREACH_ARRAY(i, sorted, n) {
r = file_system_send(mf, link, *i);
if (r < 0)
return r;
}
return 0;
}
static const MetricFamily diskspace_metric_family_table[] = {
/* Keep metrics ordered alphabetically. Both are generated by the same function, attached to the
* first of them. */
{
"io.systemd.DiskSpace.FreeBytes",
"Per file system metric: disk space available to unprivileged users in bytes, on writable "
"block device backed file systems (object=mount point, fileSystemType=file system type, "
"source=backing device, originatingSource=device the backing device is stacked on, if any)",
METRIC_FAMILY_TYPE_GAUGE,
.generate = diskspace_generate,
},
{
"io.systemd.DiskSpace.SizeBytes",
"Per file system metric: file system size in bytes, on writable block device backed file "
"systems (object=mount point, fileSystemType=file system type, source=backing device, "
"originatingSource=device the backing device is stacked on, if any)",
METRIC_FAMILY_TYPE_GAUGE,
},
{}
};
int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return metrics_method_describe(diskspace_metric_family_table, link, parameters, flags, userdata);
}
int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return metrics_method_list(diskspace_metric_family_table, link, parameters, flags, userdata);
}
+7
View File
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "forward.h"
int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+12
View File
@@ -1024,6 +1024,7 @@ static int fw_nftables_add_local_dnat_internal(
sd_netlink_message *messages[3] = {};
CLEANUP_ELEMENTS(messages, sd_netlink_message_unref_array_clear);
static bool ipv6_supported = true;
uint32_t data[5], key[2], dlen;
size_t msgcnt = 0;
int r;
@@ -1032,6 +1033,9 @@ static int fw_nftables_add_local_dnat_internal(
assert(add || !previous_remote);
assert(IN_SET(af, AF_INET, AF_INET6));
if (!ipv6_supported && af == AF_INET6)
return -EOPNOTSUPP;
if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP))
return -EPROTONOSUPPORT;
@@ -1085,6 +1089,14 @@ static int fw_nftables_add_local_dnat_internal(
assert(msgcnt < ELEMENTSOF(messages));
r = sd_nfnl_call_batch(nfnl, messages, msgcnt, NFNL_DEFAULT_TIMEOUT_USECS);
if (r == -EOVERFLOW && af == AF_INET6) {
/* ANCIENT-MOD: The current implementation of DNAT in systemd requires kernel's
* fdb9c405e35bdc6e305b9b4e20ebc141ed14fc81 (v5.8), and the older kernel returns
* -EOVERFLOW. Let's treat the error as -EOPNOTSUPP. */
log_debug_errno(r, "The current implementation of IPv6 DNAT in systemd requires kernel 5.8 or newer, ignoring: %m");
ipv6_supported = false;
return -EOPNOTSUPP;
}
if (r < 0)
return r;
+3
View File
@@ -64,6 +64,9 @@ static bool is_in_survivor_cgroup(const PidRef *pid) {
}
r = cg_get_xattr_bool(cgroup_path, "user.survive_final_kill_signal");
/* user xattr support was added to kernel v5.7, try with the trusted namespace as a fallback */
if (ERRNO_IS_NEG_XATTR_ABSENT(r))
r = cg_get_xattr_bool(cgroup_path, "trusted.survive_final_kill_signal");
if (r < 0 && !ERRNO_IS_NEG_XATTR_ABSENT(r))
log_debug_errno(r,
"Failed to get survive_final_kill_signal xattr of %s, ignoring: %m",
+2
View File
@@ -12,6 +12,7 @@
DLSYM_PROTOTYPE(mnt_free_iter) = NULL;
DLSYM_PROTOTYPE(mnt_free_table) = NULL;
DLSYM_PROTOTYPE(mnt_fs_get_devno) = NULL;
DLSYM_PROTOTYPE(mnt_fs_get_fs_options) = NULL;
DLSYM_PROTOTYPE(mnt_fs_get_fstype) = NULL;
DLSYM_PROTOTYPE(mnt_fs_get_id) = NULL;
@@ -154,6 +155,7 @@ int dlopen_libmount(int log_level) {
log_level,
DLSYM_ARG(mnt_free_iter),
DLSYM_ARG(mnt_free_table),
DLSYM_ARG(mnt_fs_get_devno),
DLSYM_ARG(mnt_fs_get_fs_options),
DLSYM_ARG(mnt_fs_get_fstype),
DLSYM_ARG(mnt_fs_get_id),
+1
View File
@@ -16,6 +16,7 @@
extern DLSYM_PROTOTYPE(mnt_free_iter);
extern DLSYM_PROTOTYPE(mnt_free_table);
extern DLSYM_PROTOTYPE(mnt_fs_get_devno);
extern DLSYM_PROTOTYPE(mnt_fs_get_fs_options);
extern DLSYM_PROTOTYPE(mnt_fs_get_fstype);
extern DLSYM_PROTOTYPE(mnt_fs_get_id);
+16
View File
@@ -5,6 +5,7 @@
#endif
#include <fcntl.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <sys/file.h>
#include <sys/ioctl.h>
@@ -200,6 +201,21 @@ static int loop_configure_fallback(int fd, const struct loop_config *c) {
random_u64_range(UINT64_C(240) * USEC_PER_MSEC * n_attempts/64));
}
/* Work around a kernel bug, where changing offset/size of the loopback device doesn't correctly
* invalidate the buffer cache. For details see:
*
* https://android.googlesource.com/platform/system/apex/+/bef74542fbbb4cd629793f4efee8e0053b360570
*
* This was fixed in kernel 5.0, see:
*
* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5db470e229e22b7eda6e23b5566e532c96fb5bc3
*
* We'll run the work-around here in the legacy LOOP_SET_STATUS64 codepath. In the LOOP_CONFIGURE
* codepath above it should not be necessary. */
if (c->info.lo_offset != 0 || c->info.lo_sizelimit != 0)
if (ioctl(fd, BLKFLSBUF, 0) < 0)
log_debug_errno(errno, "Failed to issue BLKFLSBUF ioctl, ignoring: %m");
/* If a block size is requested then try to configure it. If that doesn't work, ignore errors, but
* afterwards, let's validate what is in effect, and if it doesn't match what we want, fail */
if (c->block_size != 0) {
+22 -13
View File
@@ -43,26 +43,35 @@ typedef struct MountPoint {
} MountPoint;
static int cgroupfs_mount_options(int priority, const char *type, char **ret) {
_cleanup_free_ char *opts = NULL;
int r;
assert(type);
assert(streq(type, "cgroup2"));
assert(ret);
/* memory_hugetlb_accounting mount option is since kernel v6.7 (8cba9576df601c384abd334a503c3f6e1e29eefb). */
/* ANCIENT-MOD: memory_recursiveprot is probed (kernel >= 5.7) instead of being always passed.
* memory_hugetlb_accounting mount option is since kernel v6.7 (8cba9576df601c384abd334a503c3f6e1e29eefb). */
r = mount_option_supported("cgroup2", "memory_hugetlb_accounting", /* value= */ NULL);
if (r <= 0) {
if (r < 0)
log_full_errno(priority, r, "Failed to determine whether cgroupfs supports 'memory_hugetlb_accounting' mount option, assuming not: %m");
else
log_debug("'memory_hugetlb_accounting' not supported by cgroupfs, not using mount option.");
*ret = NULL;
return 0;
FOREACH_STRING(o, "memory_recursiveprot", "memory_hugetlb_accounting") {
r = mount_option_supported("cgroup2", o, /* value= */ NULL);
if (r == -EAGAIN)
/* No fsopen(), i.e. kernel < 5.2, hence none of these options exist either */
log_debug("Cannot probe cgroupfs mount option '%s', kernel too old, not using it.", o);
else if (r < 0 && streq(o, "memory_recursiveprot")) {
log_full_errno(priority, r, "Failed to determine whether cgroupfs supports '%s' mount option, assuming it does: %m", o);
if (!strextend_with_separator(&opts, ",", o))
return log_oom_full(priority);
} else if (r < 0)
log_full_errno(priority, r, "Failed to determine whether cgroupfs supports '%s' mount option, assuming not: %m", o);
else if (r == 0)
log_debug("'%s' not supported by cgroupfs, not using mount option.", o);
else if (!strextend_with_separator(&opts, ",", o))
return log_oom_full(priority);
}
return strdup_to(ret, "memory_hugetlb_accounting");
*ret = TAKE_PTR(opts);
return 0;
}
int mount_cgroupfs(const char *path) {
@@ -82,7 +91,7 @@ int mount_cgroupfs(const char *path) {
return r;
/* These options shall be kept in sync with those in mount_table below. */
if (!strprepend_with_separator(&opts, ",", "nsdelegate,memory_recursiveprot"))
if (!strprepend_with_separator(&opts, ",", "nsdelegate"))
return log_oom();
return mount_nofollow_verbose(LOG_ERR, "cgroup2", path, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, opts);
@@ -200,7 +209,7 @@ static const MountPoint mount_table[] = {
.what = "cgroup2",
.where = "/sys/fs/cgroup",
.type = "cgroup2",
.options = "nsdelegate,memory_recursiveprot",
.options = "nsdelegate",
.options_fn = cgroupfs_mount_options,
.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV,
.mode = MNT_FATAL|MNT_IN_CONTAINER|MNT_CHECK_WRITABLE,
+5 -7
View File
@@ -977,16 +977,14 @@ static int qmp_client_call_suspend(
if (r < 0)
return r;
/* An interruption takes precedence even if the reply arrived in the same tick: the wait
* consumed the queued cancellation or timeout, and returning the reply instead would drop it. */
r = sd_fiber_await(call);
/* If the future isn't resolved, the suspend was interrupted before a reply arrived (fiber
* cancelled, fiber-wide SD_FIBER_TIMEOUT scope expired, …). There's no reply to extract,
* so surface the resume error directly. When the future is resolved, future_get_qmp_reply()
* already encodes success (1), QMP-level error (-EIO with the desc captured if asked for),
* and no-reply (negative future result) — pass it through. */
if (sd_future_state(call) != SD_FUTURE_RESOLVED)
if (r < 0)
return r;
/* future_get_qmp_reply() encodes success (1), QMP-level error (-EIO with the desc captured if
* asked for), and no-reply (negative future result) — pass it through. */
return future_get_qmp_reply(call, ret_result, ret_error_desc);
}
+6 -5
View File
@@ -141,11 +141,12 @@ sd_future* sd_fiber_get_awaiting(sd_future *f);
int sd_fiber_yield(void);
int sd_fiber_sleep(uint64_t usec);
/* Return the target's result on completion, or a wait error if the calling fiber is interrupted or
* the wait fails. An interrupted wait does not imply that the target has resolved; once it has,
* sd_future_result() gives its outcome independently of the wait's return value. An already-resolved
* target returns its result without consuming a pending interruption. The target must belong to the
* calling fiber's event loop. */
/* Suspend until the target has resolved. Returns 0 once it has, and a negative error only if the wait
* itself did not complete: the calling fiber was interrupted (-ECANCELED, -ETIME), woken for an
* unrelated reason (-EBUSY), or the wait could not be set up. An interrupted wait does not imply that
* the target has resolved. The target's outcome is available through sd_future_result() afterwards;
* the caller must hold its own reference to read it. An already-resolved target returns 0 without
* consuming a pending interruption. The target must belong to the calling fiber's event loop. */
int sd_fiber_await(sd_future *target);
int sd_fiber_suspend(void);
int sd_fiber_resume(sd_future *f, int result);
+8 -2
View File
@@ -343,13 +343,19 @@ TEST(close_all_fds) {
_exit(EXIT_SUCCESS);
}
r = ASSERT_OK(pidref_safe_fork("(caf-nomalloc)", flags, NULL));
ASSERT_OK(r = pidref_safe_fork("(caf-nomalloc)", flags, NULL));
if (r == 0) {
test_close_all_fds_inner(close_all_fds_without_malloc);
_exit(EXIT_SUCCESS);
}
r = ASSERT_OK(pidref_safe_fork("(caf-frugal)", flags, NULL));
ASSERT_OK(r = pidref_safe_fork("(caf-proc)", flags, NULL));
if (r == 0) {
test_close_all_fds_inner(close_all_fds_by_proc);
_exit(EXIT_SUCCESS);
}
ASSERT_OK(r = pidref_safe_fork("(caf-frugal)", flags, NULL));
if (r == 0) {
test_close_all_fds_inner(close_all_fds_frugal);
_exit(EXIT_SUCCESS);
+3 -2
View File
@@ -419,7 +419,7 @@ static int qmp_client_call_fiber(void *userdata) {
sd_json_variant *status = ASSERT_NOT_NULL(sd_json_variant_by_key(result, "status"));
ASSERT_STREQ(sd_json_variant_string(status), "running");
/* QMP-level error: future resolves with -EIO, sd_fiber_await() returns -EIO, and
/* QMP-level error: the wait completes, the future resolves with -EIO, and
* future_get_qmp_reply() also returns -EIO (mirroring future_get_bus_reply()) — with the
* detailed description captured via error_desc on top, and result left NULL. */
f = sd_future_unref(f);
@@ -427,7 +427,7 @@ static int qmp_client_call_fiber(void *userdata) {
error_desc = mfree(error_desc);
ASSERT_OK(qmp_client_call_future(client, "stop", NULL, &f));
ASSERT_ERROR(sd_fiber_await(f), EIO);
ASSERT_OK_ZERO(sd_fiber_await(f));
ASSERT_ERROR(sd_future_result(f), EIO);
ASSERT_ERROR(future_get_qmp_reply(f, &result, &error_desc), EIO);
@@ -541,6 +541,7 @@ static int qmp_client_call_interrupted_fiber(void *userdata) {
ASSERT_NOT_NULL(result);
ASSERT_NULL(error_desc);
ASSERT_OK_ZERO(sd_fiber_await(mock));
ASSERT_OK_ZERO(sd_future_result(mock));
ASSERT_OK_ZERO(sd_fiber_yield());
ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0));
return 0;
+32
View File
@@ -217,6 +217,38 @@ TEST(terminal_get_size_dsr) {
log_notice("terminal size via ioctl: rows=%u columns=%u", ws.ws_row, ws.ws_col);
}
TEST(terminal_fix_size_vc) {
struct winsize before = {}, after = {};
int r;
/* On a virtual console terminal_fix_size() must not query the terminal at all: it should report
* success without touching the dimensions the kernel gave us. */
_cleanup_close_ int fd = open("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return (void) log_notice_errno(errno, "Can't open /dev/tty1, skipping: %m");
_cleanup_free_ char *name = NULL;
r = getttyname_harder(fd, &name);
if (r < 0)
return (void) log_notice_errno(r, "Can't get name of /dev/tty1, skipping: %m");
if (!tty_is_vc_resolve(name))
return (void) log_notice("/dev/tty1 is not a virtual console, skipping.");
if (ioctl(fd, TIOCGWINSZ, &before) < 0)
return (void) log_notice_errno(errno, "Can't get terminal size via ioctl, skipping: %m");
usec_t n = now(CLOCK_MONOTONIC);
r = terminal_fix_size(fd, fd);
log_info("%s took %s", __func__+5,
FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), n), USEC_PER_MSEC));
ASSERT_OK_ZERO(r);
ASSERT_OK_ERRNO(ioctl(fd, TIOCGWINSZ, &after));
ASSERT_EQ(before.ws_row, after.ws_row);
ASSERT_EQ(before.ws_col, after.ws_col);
}
TEST(terminal_fix_size) {
int r;
+8 -1
View File
@@ -160,12 +160,19 @@ as root. The host must have coco enabled in firmware and in the kernel (for SEV-
e.g. `kvm_amd.sev_snp=1`, with `/dev/sev` accessible). On a host without the requested
technology, or when running in `mkosi vm` mode, the tests are skipped.
Inside each guest, `guest-test-runner.sh` runs the guest-side checks and reports
their results back to the host over a vsock socket, while the guest's overall
pass/fail is relayed separately through its exit status. The host side collects
both to produce the subtest result.
```
meson test
└─ integration-test-wrapper.py
└─ mkosi boot ──► systemd-nspawn container
└─ TEST-94-COCO.sh
└─ systemd-vmspawn --coco=<type> ──► confidential VM
└─ TEST-94-COCO.*.sh
└─ systemd-vmspawn --coco=<type> ──► confidential VM
└─ guest-test-runner.sh
```
## Running the integration tests without building systemd from source
@@ -109,6 +109,7 @@ INTROSPECTABLE=(
systemd-report
systemd-report-basic
systemd-report-cgroup
systemd-report-diskspace
systemd-report-files
systemd-report-hwmon
systemd-report-sign-plain
+99
View File
@@ -93,6 +93,105 @@ fi
"$REPORT" describe io.systemd.HWMon
"$REPORT" metrics io.systemd.HWMon
# test io.systemd.DiskSpace Metrics
# Mount a scratch ext4 file system and check that it is reported with plausible numbers, that a tmpfs next to
# it is not, that additional bind mounts of it do not result in additional rows but the shortest writable
# mount path wins regardless of mount order, that it disappears from the report once remounted read-only,
# and that a device mapper device stacked on top of it is reported with the loop device as originating
# source.
diskspace_mount_tests() {
# Loop devices and device mapper are not available in containers
if systemd-detect-virt --quiet --container; then
echo "Skipping io.systemd.DiskSpace mount tests in container."
return
fi
DISKSPACE_WORK="$(mktemp -d /var/tmp/report-diskspace.XXXXXX)"
diskspace_cleanup() {
set +e
umount "$DISKSPACE_WORK/tmpfs" "$DISKSPACE_WORK/a" "$DISKSPACE_WORK/b" "$DISKSPACE_WORK/longer" "$DISKSPACE_WORK/ext4" "$DISKSPACE_WORK/dm"
dmsetup remove report-diskspace
systemd-dissect --detach "$DISKSPACE_WORK/scratch.raw"
rm -rf "$DISKSPACE_WORK"
set -e
}
trap diskspace_cleanup EXIT
mkdir "$DISKSPACE_WORK/ext4" "$DISKSPACE_WORK/a" "$DISKSPACE_WORK/b" "$DISKSPACE_WORK/longer" "$DISKSPACE_WORK/dm" "$DISKSPACE_WORK/tmpfs"
truncate -s 64M "$DISKSPACE_WORK/scratch.raw"
mkfs.ext4 -q "$DISKSPACE_WORK/scratch.raw"
DISKSPACE_LOOP="$(systemd-dissect --attach --loop-ref=report-diskspace "$DISKSPACE_WORK/scratch.raw")"
mount "$DISKSPACE_LOOP" "$DISKSPACE_WORK/ext4"
# "a" sorts before "b" and is just as short, but is read-only, hence "b" must be picked
mount --bind -o ro "$DISKSPACE_WORK/ext4" "$DISKSPACE_WORK/a"
mount --bind "$DISKSPACE_WORK/ext4" "$DISKSPACE_WORK/b"
# "longer" is writable too, but has a longer path, hence must not win even though it is mounted last
mount --bind "$DISKSPACE_WORK/ext4" "$DISKSPACE_WORK/longer"
mount -t tmpfs tmpfs "$DISKSPACE_WORK/tmpfs"
dd if=/dev/urandom of="$DISKSPACE_WORK/ext4/fill" bs=1M count=8
sync -f "$DISKSPACE_WORK/ext4"
diskspace_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.DiskSpace io.systemd.Metrics.List {} | tr -d '\036')"
# Exactly one row per metric for the scratch file system, reported under the shortest writable mount path
[[ "$(echo "$diskspace_metrics" | jq -r --arg s "$DISKSPACE_LOOP" 'select(.name == "io.systemd.DiskSpace.SizeBytes" and .fields.source == $s) | .object')" == "$DISKSPACE_WORK/b" ]]
[[ "$(echo "$diskspace_metrics" | jq -r --arg s "$DISKSPACE_LOOP" 'select(.name == "io.systemd.DiskSpace.FreeBytes" and .fields.source == $s) | .object')" == "$DISKSPACE_WORK/b" ]]
diskspace_size="$(echo "$diskspace_metrics" | jq -r --arg o "$DISKSPACE_WORK/b" 'select(.name == "io.systemd.DiskSpace.SizeBytes" and .object == $o) | .value')"
diskspace_free="$(echo "$diskspace_metrics" | jq -r --arg o "$DISKSPACE_WORK/b" 'select(.name == "io.systemd.DiskSpace.FreeBytes" and .object == $o) | .value')"
# The file system cannot be larger than the image, and at least the 8M we wrote must be missing from the
# free space.
[[ "$diskspace_size" -gt 0 ]]
[[ "$diskspace_size" -le $((64 * 1024 * 1024)) ]]
[[ "$diskspace_free" -gt 0 ]]
[[ $((diskspace_free + 8 * 1024 * 1024)) -le "$diskspace_size" ]]
# df(1) derives the same two numbers from statfs(), hence they must match exactly.
read -r diskspace_df_size diskspace_df_avail < <(df -B1 --output=size,avail "$DISKSPACE_WORK/ext4" | sed 1d)
[[ "$diskspace_size" == "$diskspace_df_size" ]]
[[ "$diskspace_free" == "$diskspace_df_avail" ]]
# Both rows must carry the file system type as field, and no originating source, as a loop device is not
# stacked on anything
[[ "$(echo "$diskspace_metrics" | jq -r --arg o "$DISKSPACE_WORK/b" 'select(.object == $o) | .fields.fileSystemType' | sort -u)" == "ext4" ]]
[[ "$(echo "$diskspace_metrics" | jq -r --arg o "$DISKSPACE_WORK/b" 'select(.object == $o) | .fields | has("originatingSource") | tostring' | sort -u)" == "false" ]]
# The tmpfs may not be reported
(! echo "$diskspace_metrics" | jq -r '.object' | grep -Fx "$DISKSPACE_WORK/tmpfs" >/dev/null)
# Once remounted read-only the file system must no longer be reported, regardless of the mount path
mount -o remount,ro "$DISKSPACE_WORK/ext4"
diskspace_metrics="$(varlinkctl call --more --graceful=io.systemd.Metrics.NoSuchMetric /run/systemd/report/io.systemd.DiskSpace io.systemd.Metrics.List {} | tr -d '\036')"
(! echo "$diskspace_metrics" | jq -r '.fields.source' | grep -Fx "$DISKSPACE_LOOP" >/dev/null)
# Now stack a (linear) device mapper device on top of the loop device, and mount the file system through
# that: the DM device must be reported as source, and the loop device it sits on as originating source. The
# loop device must be unmounted first, as DM needs exclusive access to it.
umount "$DISKSPACE_WORK/a" "$DISKSPACE_WORK/b" "$DISKSPACE_WORK/longer" "$DISKSPACE_WORK/ext4"
dmsetup create report-diskspace --table "0 $(blockdev --getsz "$DISKSPACE_LOOP") linear $DISKSPACE_LOOP 0"
udevadm wait --timeout=30 --settle /dev/mapper/report-diskspace
mount /dev/mapper/report-diskspace "$DISKSPACE_WORK/dm"
diskspace_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.DiskSpace io.systemd.Metrics.List {} | tr -d '\036')"
[[ "$(echo "$diskspace_metrics" | jq -r --arg o "$DISKSPACE_WORK/dm" 'select(.object == $o) | .fields.source' | sort -u)" == "/dev/mapper/report-diskspace" ]]
[[ "$(echo "$diskspace_metrics" | jq -r --arg o "$DISKSPACE_WORK/dm" 'select(.object == $o) | .fields.originatingSource' | sort -u)" == "$DISKSPACE_LOOP" ]]
diskspace_cleanup
trap - EXIT
}
# The socket and service are only built if libmount is available
if ! systemctl cat systemd-report-diskspace.socket &>/dev/null; then
echo "systemd-report-diskspace.socket is not installed, skipping io.systemd.DiskSpace tests."
else
systemctl start systemd-report-diskspace.socket
varlinkctl info /run/systemd/report/io.systemd.DiskSpace
varlinkctl list-methods /run/systemd/report/io.systemd.DiskSpace
# varlinkctl emits a JSON-SEQ stream for --more; strip the record separators so that plain jq can consume it
diskspace_describe="$(varlinkctl call --more /run/systemd/report/io.systemd.DiskSpace io.systemd.Metrics.Describe {} | tr -d '\036')"
echo "$diskspace_describe" | jq -r 'select(.name == "io.systemd.DiskSpace.FreeBytes") | .type' | grep -wx gauge >/dev/null
echo "$diskspace_describe" | jq -r 'select(.name == "io.systemd.DiskSpace.SizeBytes") | .type' | grep -wx gauge >/dev/null
diskspace_mount_tests
"$REPORT" describe io.systemd.DiskSpace
"$REPORT" metrics io.systemd.DiskSpace
fi
# test io.systemd.Basic Metrics
# ensure the socket is running, as some distros don't enable it by default
systemctl start systemd-report-basic.socket
+21 -82
View File
@@ -1,20 +1,23 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Launch a confidential VM with systemd-vmspawn --coco= and verify the guest reports the expected
# confidential-virtualization type. Runs only on coco-capable hardware: the harness --coco=any flag gates
# the run (skipped otherwise), binds /dev/kvm + the coco device(s) into this boot-mode container, and
# passes the resolved technology via $COCO_TYPE.
# Confidential Computing test.
#
# The confidential guest boots the same mkosi test image. A one-shot unit injected into the guest checks
# systemd-detect-virt --cvm and reports the verdict via systemd's exit-status notification over vsock
# (SuccessAction=exit): the guest PID1 sends EXIT_STATUS to vmspawn's vmm notify socket and vmspawn exits
# with it.
# These only run on coco hardware and if the test environment isn't already virtualized, see
# the harness --coco= flag and the documentation in test/integration-tests/README.md.
#
# Subtests are split per platform and launch path to reduce the number of required boots.
# Test code runs on both sides:
# - host: test vmspawn by running confidential guests. TEST-94-COCO/lib.sh provides helpers.
# - guest: test guest side component coco behavior. Assertions via TEST-94-COCO/guest-test{,-runner}.sh.
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
# shellcheck source=test/units/test-control.sh
. "$(dirname "$0")"/test-control.sh
if [[ -v ASAN_OPTIONS ]]; then
echo "vmspawn launches QEMU which doesn't work under ASan, skipping" | tee --append /skipped
@@ -31,83 +34,19 @@ if ! find_qemu_binary; then
exit 77
fi
# The integration-test-wrapper resolves the host's coco technology on the
# bare-metal host (where /sys/module is readable) and passes it via $COCO_TYPE.
# The harness binds the guest image artifacts read-only at /work/vm-images.
export IMAGE_DIR=/work/vm-images
if [[ ! -f "$IMAGE_DIR/image.raw" || ! -f "$IMAGE_DIR/image.vmlinuz" || ! -f "$IMAGE_DIR/image.initrd" ]]; then
echo "image artifacts not found in $IMAGE_DIR, skipping" | tee --append /skipped
exit 77
fi
# The integration-test-wrapper resolves the host's coco technology (where /sys/module is readable) and
# passes it via $COCO_TYPE.
if [[ -z "${COCO_TYPE:-}" ]]; then
echo "COCO_TYPE not set (run via the integration harness with --coco), skipping" | tee --append /skipped
exit 77
fi
echo "Host coco type: $COCO_TYPE"
# The harness binds the guest image read-only at /work/vm-images (the 'vm-images' test option).
IMAGE_DIR=/work/vm-images
if [[ ! -f "$IMAGE_DIR/image.raw" || ! -f "$IMAGE_DIR/image.vmlinuz" || ! -f "$IMAGE_DIR/image.initrd" ]]; then
echo "guest image not found in $IMAGE_DIR, skipping" | tee --append /skipped
exit 77
fi
echo "Guest image directory: $IMAGE_DIR"
MACHINE="coco-guest-$$"
WORKDIR="$(mktemp -d)"
at_exit() {
set +e
machinectl terminate "$MACHINE" 2>/dev/null || :
rm -rf "$WORKDIR"
}
trap at_exit EXIT INT TERM
# One-shot unit for the guest: verify systemd-detect-virt --cvm matches the type vmspawn launched.
# Created via the systemd.extra-unit.* credential and pulled into the boot by a multi-user.target drop-in.
GUEST_PASS=123
GUEST_UNIT="$(cat <<'EOF'
[Unit]
Description=coco guest self-check
After=basic.target
SuccessAction=exit
SuccessActionExitStatus=@GUEST_PASS@
FailureAction=exit
FailureActionExitStatus=1
[Service]
Type=oneshot
ExecStart=bash -c 'test "$(systemd-detect-virt --cvm)" = "@COCO_TYPE@"'
EOF
)"
GUEST_UNIT="${GUEST_UNIT//@COCO_TYPE@/$COCO_TYPE}"
GUEST_UNIT="${GUEST_UNIT//@GUEST_PASS@/$GUEST_PASS}"
GUEST_DROPIN="$(cat <<'EOF'
[Unit]
Wants=coco-guest.service
After=coco-guest.service
EOF
)"
# Launch the confidential guest and capture its console output.
# Direct linux boot should work on all coco platforms (the only boot path supported by SNP).
vmspawn_rc=0
timeout -k 30 300 systemd-vmspawn \
--machine="$MACHINE" \
--coco="$COCO_TYPE" \
--ram=1G \
--ephemeral \
--image="$IMAGE_DIR/image.raw" \
--linux="$IMAGE_DIR/image.vmlinuz" \
--initrd="$IMAGE_DIR/image.initrd" \
--tpm=no \
--console=read-only \
--set-credential="systemd.extra-unit.coco-guest.service:$GUEST_UNIT" \
--set-credential="systemd.unit-dropin.multi-user.target:$GUEST_DROPIN" \
selinux=0 systemd.firstboot=no rw \
2>&1 | tee "$WORKDIR/console.log" || vmspawn_rc="${PIPESTATUS[0]}"
if [[ "$vmspawn_rc" -eq 124 || "$vmspawn_rc" -eq 137 ]]; then
echo "systemd-vmspawn was killed by timeout (exit $vmspawn_rc)" >&2
cat "$WORKDIR/console.log" >&2
exit 1
elif [[ "$vmspawn_rc" -ne "$GUEST_PASS" ]]; then
echo "guest coco self-check did not pass: vmspawn exit $vmspawn_rc (expected $GUEST_PASS)" >&2
cat "$WORKDIR/console.log" >&2
exit 1
fi
echo "Guest correctly reported confidential virtualization: $COCO_TYPE"
touch /testok
run_subtests_and_exit
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# SEV-SNP, direct kernel boot: launch confidential guests with --linux=/--initrd=.
set -eux
set -o pipefail
# shellcheck source=test/units/TEST-94-COCO/lib.sh
. "$(dirname "$0")"/TEST-94-COCO/lib.sh
if [[ "${COCO_TYPE:?}" != "sev-snp" ]]; then
echo "host coco type is '$COCO_TYPE', not sev-snp, skipping"
exit 77
fi
MACHINE="coco-snp-direct-$$"
WORKDIR="$(mktemp -d)"
mkdir -p "$WORKDIR/honest" "$WORKDIR/hostile"
at_exit() {
set +e
jobs -p | xargs -r kill 2>/dev/null
machinectl terminate "$MACHINE-honest" 2>/dev/null || :
machinectl terminate "$MACHINE-hostile" 2>/dev/null || :
rm -rf "$WORKDIR"
}
trap at_exit EXIT
# General test case for SNP direct boot, covers:
#
# - detect_virt: systemd-detect-virt --cvm detects SNP as the CVM type
# - creds_vmspawn: credential transport by vmspawn is working (via initrd for SNP)
# - creds_cmdline: credential transport via cmdline is working (cmdline is measured on SNP)
vmspawn_boot_coco "$MACHINE-honest" "$COCO_TYPE" "$WORKDIR/honest" 'detect_virt creds_vmspawn creds_cmdline' \
--image="$IMAGE_DIR/image.raw" \
--linux="$IMAGE_DIR/image.vmlinuz" \
--initrd="$IMAGE_DIR/image.initrd" \
--set-credential="$COCO_CRED_TRUSTED_ID:$COCO_CRED_TRUSTED_VALUE" \
selinux=0 systemd.firstboot=no rw \
"systemd.set_credential=$COCO_CRED_CMDLINE_ID:$COCO_CRED_CMDLINE_VALUE"
echo "SEV-SNP direct-boot guest correctly reported confidential virtualization and trusted credentials"
# Offensive test:
#
# - creds_hostile_*: ensure the guest isn't tricked into consuming untrusted/unmeasured credentials.
QEMU_EXTRA=(
-smbios "type=11,value=io.systemd.credential:$COCO_CRED_SMBIOS_ID=$COCO_CRED_SMBIOS_VALUE"
-smbios "type=11,value=io.systemd.credential:$COCO_CRED_TRUSTED_ID=$COCO_CRED_SMBIOS_VALUE"
-fw_cfg "name=opt/io.systemd.credentials/$COCO_CRED_FWCFG_ID,string=$COCO_CRED_FWCFG_VALUE"
)
SYSTEMD_VMSPAWN_QEMU_EXTRA="${QEMU_EXTRA[*]}" \
vmspawn_boot_coco "$MACHINE-hostile" "$COCO_TYPE" "$WORKDIR/hostile" \
'detect_virt creds_vmspawn creds_hostile_fwcfg creds_hostile_snp_smbios creds_hostile_shadow' \
--image="$IMAGE_DIR/image.raw" \
--linux="$IMAGE_DIR/image.vmlinuz" \
--initrd="$IMAGE_DIR/image.initrd" \
--set-credential="$COCO_CRED_TRUSTED_ID:$COCO_CRED_TRUSTED_VALUE" \
selinux=0 systemd.firstboot=no rw
echo "SEV-SNP direct-boot guest correctly ignored the host-injected credentials"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# TDX, direct kernel boot: launch a confidential guest with --linux=/--initrd=.
set -eux
set -o pipefail
# shellcheck source=test/units/TEST-94-COCO/lib.sh
. "$(dirname "$0")"/TEST-94-COCO/lib.sh
if [[ "${COCO_TYPE:?}" != "tdx" ]]; then
echo "host coco type is '$COCO_TYPE', not tdx, skipping"
exit 77
fi
MACHINE="coco-tdx-direct-$$"
WORKDIR="$(mktemp -d)"
at_exit() {
set +e
jobs -p | xargs -r kill 2>/dev/null
machinectl terminate "$MACHINE" 2>/dev/null || :
rm -rf "$WORKDIR"
}
trap at_exit EXIT
vmspawn_boot_coco "$MACHINE" "$COCO_TYPE" "$WORKDIR" 'detect_virt' \
--image="$IMAGE_DIR/image.raw" \
--linux="$IMAGE_DIR/image.vmlinuz" \
--initrd="$IMAGE_DIR/image.initrd" \
selinux=0 systemd.firstboot=no rw
echo "TDX direct-boot guest correctly reported confidential virtualization"
+26
View File
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck shell=bash
# Every constant here is consumed by whichever side sources this file, never by this file itself.
# shellcheck disable=SC2034
#
# Fixtures for the confidential computing integration test (TEST-94-COCO). Shared by the host-side
# boot scenarios, which deliver these credentials over trusted and untrusted channels alike, and by
# the guest-side checks, which assert where each one ended up, so the two sides cannot drift apart.
if [[ "${BASH_SOURCE[0]}" -ef "$0" ]]; then
echo >&2 "This file should not be executed directly"
exit 1
fi
# Name of the guest self-check unit lib.sh injects.
COCO_GUEST_UNIT="coco-guest.service"
# Credentials by channel
COCO_CRED_TRUSTED_ID="coco.trusted"
COCO_CRED_TRUSTED_VALUE="trusted-via-vmspawn"
COCO_CRED_CMDLINE_ID="coco.cmdline"
COCO_CRED_CMDLINE_VALUE="trusted-via-cmdline"
COCO_CRED_SMBIOS_ID="coco.hostile.smbios"
COCO_CRED_SMBIOS_VALUE="injected-via-smbios"
COCO_CRED_FWCFG_ID="coco.hostile.fwcfg"
COCO_CRED_FWCFG_VALUE="injected-via-fwcfg"
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Runs inside the confidential guest launched by TEST-94-COCO.sh. Runs the requested guest-side checks
# (testcase_coco_* from guest-test.sh), ships a per-check result record to the host over the vsock
# result socket, and lets the injected unit's SuccessAction/FailureAction relay the aggregate verdict
# to the host via vmspawn's exit status.
set -eux
set -o pipefail
_COCO_DIR="$(dirname "$(readlink -f "$0")")"
# util.sh (assert_*) lives one level up in the shared units dir.
# shellcheck source=test/units/util.sh
. "$(dirname "$_COCO_DIR")/util.sh"
# shellcheck source=test/units/TEST-94-COCO/guest-test.sh
. "$_COCO_DIR/guest-test.sh"
# Ship the accumulated result records to the host's vsock listener (CID 2 = host). Best-effort: the
# aggregate verdict travels independently via the unit's exit status, so a vsock hiccup never masks a
# pass/fail — it only drops the per-check breakdown.
_coco_ship_results() {
local records="${1:?}"
[[ -n "${COCO_RESULT_PORT:-}" ]] || return 0
socat -u "OPEN:$records,rdonly" "VSOCK-CONNECT:2:$COCO_RESULT_PORT" ||
echo >&2 "WARNING: failed to ship coco results over vsock port $COCO_RESULT_PORT"
}
# Run the guest-side checks named by $COCO_TESTCASES (testcase_coco_* names without the prefix),
# record pass/fail per check, and ship the records. Each named check is something its boot scenario
# exists to prove, so an unknown name or an empty request fails the aggregate rather than letting the
# boot pass without having verified anything.
_coco_run_tests() {
local testcases testcase rc result failed=0 records
records="$(mktemp)"
read -ra testcases <<<"${COCO_TESTCASES:-}"
if [[ "${#testcases[@]}" -eq 0 ]]; then
echo >&2 "No coco testcases requested via \$COCO_TESTCASES"
return 1
fi
testcases=("${testcases[@]/#/testcase_coco_}")
for testcase in "${testcases[@]}"; do
if ! declare -F "$testcase" >/dev/null; then
echo >&2 "Requested coco testcase '$testcase' is not defined"
printf 'ID=%s RESULT=missing\n' "$testcase" >>"$records"
failed=1
continue
fi
# Subshell so a testcase's RETURN trap can't fire twice (see run_testcases in test-control.sh).
# Bash disables 'set -e' throughout a function called in a context that tests its exit status,
# so '("$testcase") || rc=$?' would let a testcase run past its first failed assertion and
# report only its last one. Reading $? from a standalone call avoids that context; the inner
# 'set -e' re-arms what the surrounding 'set +e' turned off.
set +e
(set -e; "$testcase")
rc=$?
set -e
if [[ "$rc" -eq 0 ]]; then
result=pass
else
result=fail
failed=1
fi
echo "coco check $testcase: $result (rc=$rc)"
printf 'ID=%s RESULT=%s\n' "$testcase" "$result" >>"$records"
done
_coco_ship_results "$records"
return "$failed"
}
_coco_run_tests
+96
View File
@@ -0,0 +1,96 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck shell=bash
#
# Guest-side checks for the confidential computing integration test (TEST-94-COCO). Sourced by
# guest-test-runner.sh inside the confidential guest; each testcase_coco_* function is one
# sub-subtest, and a boot scenario names the checks it requires via $COCO_TESTCASES.
if [[ "${BASH_SOURCE[0]}" -ef "$0" ]]; then
echo >&2 "This file should not be executed directly"
exit 1
fi
# shellcheck source=test/units/TEST-94-COCO/fixtures.sh
. "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/fixtures.sh"
# _coco_assert_cred_absent ID
# Assert no credential named ID reached the guest at all, neither trusted nor encrypted.
_coco_assert_cred_absent() {
local id="${1:?}"
local system_creds_dir=/run/credentials/@system
local encrypted_creds_dir=/run/credentials/@encrypted
assert_fail test -e "$system_creds_dir/$id"
assert_fail test -e "$encrypted_creds_dir/$id"
}
# _coco_assert_smbios_injected ID
# Assert the guest received an SMBIOS type #11 OEM string carrying credential ID. The scenario
# injected it, so a miss means the injection was lost on the way in and concluding anything from the
# credential's absence afterwards would be meaningless.
_coco_assert_smbios_injected() {
local id="${1:?}" raw
[[ -d /sys/firmware/dmi/entries ]] || modprobe dmi-sysfs || :
for raw in /sys/firmware/dmi/entries/11-*/raw; do
[[ -e "$raw" ]] || continue
# The entry is a blob of NUL-separated strings: --text to search it anyway, --fixed-strings
# because credential names contain dots. Redirect rather than -q, which would exit early and
# risk SIGPIPE on the reader.
if grep --text --fixed-strings "io.systemd.credential:$id=" "$raw" >/dev/null; then
return 0
fi
done
echo "FAIL: no SMBIOS OEM string for credential '$id' in the guest's DMI type #11 tables" >&2
return 1
}
# Assert the guest observes the confidential-virtualization technology the host launched it with.
# COCO_TYPE is provided by the injected guest unit (Environment=, see lib.sh).
testcase_coco_detect_virt() {
assert_eq "$(systemd-detect-virt --cvm)" "${COCO_TYPE:?}"
}
# Assert a credential handed to systemd-vmspawn --set-credential lands in the trusted bucket.
#
# SNP: creds should be delivered via the initrd cpio channel.
testcase_coco_creds_vmspawn() {
assert_eq "$(systemd-creds --system cat "$COCO_CRED_TRUSTED_ID")" "$COCO_CRED_TRUSTED_VALUE"
assert_not_in "$COCO_CRED_TRUSTED_ID" "$(</proc/cmdline)"
}
# Assert a credential passed on the kernel command line lands in the trusted bucket.
#
# Credentials passed on the cmdline should be accepted on every coco platform.
testcase_coco_creds_cmdline() {
assert_in "systemd.set_credential=$COCO_CRED_CMDLINE_ID:$COCO_CRED_CMDLINE_VALUE" "$(</proc/cmdline)"
assert_eq "$(systemd-creds --system cat "$COCO_CRED_CMDLINE_ID")" "$COCO_CRED_CMDLINE_VALUE"
}
# fw_cfg is unmeasured on every platform and import_credentials_qemu() drops it for any confidential
# guest, so this applies to SNP and TDX alike.
testcase_coco_creds_hostile_fwcfg() {
local fwcfg_dir=/sys/firmware/qemu_fw_cfg/by_name
[[ -d "$fwcfg_dir" ]] || modprobe qemu_fw_cfg || :
assert_ok test -e "$fwcfg_dir/opt/io.systemd.credentials/$COCO_CRED_FWCFG_ID/raw"
_coco_assert_cred_absent "$COCO_CRED_FWCFG_ID"
}
# SNP only: the SMBIOS tables are not covered by the launch measurement, so
# import_credentials_smbios() bails out. Under TDX they are measured into RTMR0 and thus trusted.
testcase_coco_creds_hostile_snp_smbios() {
_coco_assert_smbios_injected "$COCO_CRED_SMBIOS_ID"
_coco_assert_cred_absent "$COCO_CRED_SMBIOS_ID"
}
# A host-injected SMBIOS string must not displace a credential the measured channel already
# delivered: boot-channel credentials import first and names are never overwritten, on every
# platform. The probe proves the overwrite was attempted.
testcase_coco_creds_hostile_shadow() {
_coco_assert_smbios_injected "$COCO_CRED_TRUSTED_ID"
assert_eq "$(systemd-creds --system cat "$COCO_CRED_TRUSTED_ID")" "$COCO_CRED_TRUSTED_VALUE"
}
+113
View File
@@ -0,0 +1,113 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck shell=bash
#
# Host-side helpers for the confidential computing integration test (TEST-94-COCO).
# Sourced by the TEST-94-COCO.sh driver.
if [[ "${BASH_SOURCE[0]}" -ef "$0" ]]; then
echo >&2 "This file should not be executed directly"
exit 1
fi
# shellcheck source=test/units/TEST-94-COCO/fixtures.sh
. "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/fixtures.sh"
# Exit status the guest test runner reports on success (its unit's SuccessActionExitStatus=). vmspawn
# forwards the guest PID1's EXIT_STATUS over the vsock notify socket and exits with it.
_COCO_GUEST_PASS=123
# vsock port the host listens on for the guest's per-check result records, distinct from vmspawn's own
# (kernel-assigned) notify port. The host binds CID_ANY:PORT; the guest connects to CID 2 (host):PORT.
_COCO_RESULT_PORT=4321
# _coco_guest_unit COCO_TYPE UNITS_DIR TESTCASES
# Emit the one-shot unit that runs the guest test runner: passes the coco type, result port and
# the requested checks via the environment, and relays the aggregate verdict via
# SuccessAction/EXIT_STATUS.
_coco_guest_unit() {
local coco_type="${1:?}" units_dir="${2:?}" testcases="${3:?}"
cat <<EOF
[Unit]
Description=coco guest self-check
After=basic.target
SuccessAction=exit
SuccessActionExitStatus=$_COCO_GUEST_PASS
FailureAction=exit
FailureActionExitStatus=1
[Service]
Type=oneshot
Environment=COCO_TYPE=$coco_type
Environment=COCO_RESULT_PORT=$_COCO_RESULT_PORT
Environment="COCO_TESTCASES=$testcases"
ExecStart=$units_dir/guest-test-runner.sh
EOF
}
# _coco_guest_dropin
# Emit the multi-user.target drop-in that pulls the guest self-check unit into the boot.
_coco_guest_dropin() {
cat <<EOF
[Unit]
Wants=$COCO_GUEST_UNIT
After=$COCO_GUEST_UNIT
EOF
}
# vmspawn_boot_coco MACHINE COCO_TYPE WORKDIR TESTCASES [systemd-vmspawn args...]
# Boot a confidential guest with systemd-vmspawn, running the coco guest test runner. TESTCASES is the
# space-separated list of guest checks (testcase_coco_* names without the prefix).
# The caller supplies the boot-path-specific launch args — --image/--linux/--initrd and the guest
# kernel command line — plus any extra flags via "$@", so each per-boot-path scenario
# stays configurable. Per-check records shipped by the guest are collected over a vsock
# socket into WORKDIR/results and echoed; returns 0 iff the guest reported the success
# aggregate, otherwise dumps the console.
vmspawn_boot_coco() {
local machine="${1:?}" coco_type="${2:?}" workdir="${3:?}" testcases="${4:?}"
shift 4
local units_dir results console guest_unit guest_dropin rc=0 listener_pid=""
units_dir="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
results="$workdir/results"
console="$workdir/console.log"
: >"$results"
guest_unit="$(_coco_guest_unit "$coco_type" "$units_dir" "$testcases")"
guest_dropin="$(_coco_guest_dropin)"
# Collect the per-check records the guest ships over vsock (single connection, guest closes when
# done). Best-effort: the aggregate exit status below is the authoritative pass/fail, so a vsock
# hiccup only costs the per-check breakdown, never the verdict.
socat -u "VSOCK-LISTEN:$_COCO_RESULT_PORT" "OPEN:$results,creat" &
listener_pid=$!
timeout -k 30 300 systemd-vmspawn \
--machine="$machine" \
--coco="$coco_type" \
--ram=1G \
--ephemeral \
--tpm=no \
--console=read-only \
--set-credential="systemd.extra-unit.$COCO_GUEST_UNIT:$guest_unit" \
--set-credential="systemd.unit-dropin.multi-user.target:$guest_dropin" \
"$@" \
2>&1 | tee "$console" || rc="${PIPESTATUS[0]}"
kill "$listener_pid" 2>/dev/null || :
wait "$listener_pid" 2>/dev/null || :
if [[ -s "$results" ]]; then
echo "coco guest per-check results:"
cat "$results"
fi
if [[ "$rc" -eq 124 || "$rc" -eq 137 ]]; then
echo "systemd-vmspawn was killed by timeout (exit $rc)" >&2
cat "$console" >&2
return 1
fi
if [[ "$rc" -ne "$_COCO_GUEST_PASS" ]]; then
echo "coco guest test runner failed: vmspawn exit $rc (expected $_COCO_GUEST_PASS)" >&2
cat "$console" >&2
return 1
fi
return 0
}
+2
View File
@@ -84,6 +84,8 @@ _show_summary() {(
_record_subtest_rc() {
local subtest="${1:?}" rc="${2:?}"
subtest="${subtest##*/}"
if [[ $rc -eq $_SUBTEST_SKIP_RC ]]; then
echo "Subtest $subtest skipped"
_SKIPPED_TESTS+=("$subtest")
+5
View File
@@ -588,3 +588,8 @@ systemd.network.xml ./refsect1[title="[ModemManager] Section Options"]/variablel
systemd-report-hwmon@.service.xml ./refsect1[title="Description"]/variablelist/varlistentry[term="chip"]
systemd-report-hwmon@.service.xml ./refsect1[title="Description"]/variablelist/varlistentry[term="sensor"]
systemd-report-hwmon@.service.xml ./refsect1[title="Description"]/variablelist/varlistentry[term="label"]
systemd-report-diskspace@.service.xml ./refsect1[title="Description"]/variablelist[1]/varlistentry[term="io.systemd.DiskSpace.SizeBytes"]
systemd-report-diskspace@.service.xml ./refsect1[title="Description"]/variablelist[1]/varlistentry[term="io.systemd.DiskSpace.FreeBytes"]
systemd-report-diskspace@.service.xml ./refsect1[title="Description"]/variablelist[2]/varlistentry[term="source"]
systemd-report-diskspace@.service.xml ./refsect1[title="Description"]/variablelist[2]/varlistentry[term="originatingSource"]
systemd-report-diskspace@.service.xml ./refsect1[title="Description"]/variablelist[2]/varlistentry[term="fileSystemType"]
+8
View File
@@ -794,6 +794,14 @@ units = [
},
{ 'file' : 'systemd-report-cgroup.socket' },
{ 'file' : 'systemd-report-cgroup@.service.in' },
{
'file' : 'systemd-report-diskspace.socket',
'conditions' : ['HAVE_LIBMOUNT'],
},
{
'file' : 'systemd-report-diskspace@.service.in',
'conditions' : ['HAVE_LIBMOUNT'],
},
{ 'file' : 'systemd-report-files.socket' },
{ 'file' : 'systemd-report-files@.service.in' },
{ 'file' : 'systemd-report-hwmon.socket' },
+29
View File
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Disk Space Report Varlink Socket
Documentation=man:systemd-report-diskspace@.service(8)
DefaultDependencies=no
Before=sockets.target shutdown.target
Conflicts=shutdown.target
[Socket]
ListenStream=/run/systemd/report/io.systemd.DiskSpace
FileDescriptorName=varlink
SocketMode=0666
Accept=yes
MaxConnectionsPerSource=16
RemoveOnStop=yes
XAttrEntryPoint=user.varlink=entrypoint
XAttrListen=user.varlink=listen
XAttrAccept=user.varlink=server
[Install]
WantedBy=sockets.target
@@ -0,0 +1,43 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Disk Space Report Service
Documentation=man:systemd-report-diskspace@.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
Before=shutdown.target
[Service]
# Note that DynamicUser=, ProtectSystem=, ProtectHome= and PrivateTmp= are deliberately not used here: they
# make the host's file systems appear read-only, or shadow them with private mounts, in the service's mount
# namespace, and hence would hide from this service exactly what it is supposed to report on.
CapabilityBoundingSet=
DeviceAllow=
IPAddressDeny=any
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
PrivateDevices=yes
PrivateIPC=yes
PrivateNetwork=yes
ProtectControlGroups=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
RuntimeMaxSec=1min
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service
ExecStart={{LIBEXECDIR}}/systemd-report-diskspace