mirror of
https://github.com/systemd/systemd
synced 2026-04-12 01:55:10 +02:00
Compare commits
19 Commits
d3a1710bc2
...
ff6b70fe7e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff6b70fe7e | ||
|
|
8355eb6e11 | ||
|
|
92d87ac302 | ||
|
|
5401a1270a | ||
|
|
58b9e400a6 | ||
|
|
554ba32d30 | ||
|
|
dd80e5a348 | ||
|
|
a65ebc3ff9 | ||
|
|
fb0ae7436d | ||
|
|
8c65fe4fa1 | ||
|
|
6fe4a16f36 | ||
|
|
5168e9eada | ||
|
|
15384f6e1f | ||
|
|
7b2a10b594 | ||
|
|
768b507adc | ||
|
|
db426d147d | ||
|
|
c1b928c810 | ||
|
|
d1d72563e0 | ||
|
|
c2283986f9 |
59
.github/workflows/claude-review.yml
vendored
59
.github/workflows/claude-review.yml
vendored
@ -256,27 +256,34 @@ jobs:
|
||||
|
||||
## Phase 1: Review commits
|
||||
|
||||
List the directories in `worktrees/` — there is one per commit. Each
|
||||
worktree at `worktrees/<sha>/` contains the full source tree checked out at
|
||||
that commit, plus `commit.patch` (the diff) and `commit-message.txt`
|
||||
(the commit message). Spawn one
|
||||
review subagent per worktree, all in a single message so they run concurrently.
|
||||
Do NOT pre-compute diffs or read any other files before spawning — the subagents
|
||||
will do that themselves.
|
||||
First, list the directories in `worktrees/` and read `review-schema.json`.
|
||||
Then, spawn exactly one review subagent per worktree directory, all in a
|
||||
single message so they run concurrently. Do NOT batch or group multiple
|
||||
commits into a single agent. Do NOT read any other files before spawning —
|
||||
the subagents will do that themselves.
|
||||
|
||||
Each worktree at `worktrees/<sha>/` contains the full source tree checked
|
||||
out at that commit, plus `commit.patch` (the diff) and `commit-message.txt`
|
||||
(the commit message).
|
||||
|
||||
Each reviewer reviews design, code quality, style, potential bugs, and
|
||||
security implications.
|
||||
|
||||
Each subagent must be spawned with `model: "opus"`.
|
||||
|
||||
Each subagent prompt must include:
|
||||
- Instructions to read `pr-context.json` in the repository root for additional
|
||||
context.
|
||||
- Instructions to read `review-schema.json` in the repository root and
|
||||
return a JSON array matching the `comments` items schema from that file.
|
||||
- The contents of `review-schema.json` (paste it into each prompt so the
|
||||
agent doesn't have to read it separately).
|
||||
- The worktree path.
|
||||
- Instructions to read `commit-message.txt` and `commit.patch` in the
|
||||
worktree for the commit message and diff.
|
||||
- Instructions to verify every `line` and `start_line` value
|
||||
against the hunk ranges in `commit.patch` before returning.
|
||||
- Instructions to return ONLY a raw JSON array of findings. No markdown,
|
||||
no explanation, no code fences — just the JSON array. If there are no
|
||||
findings, return `[]`.
|
||||
|
||||
## Phase 2: Collect, deduplicate, and summarize
|
||||
|
||||
@ -290,26 +297,20 @@ jobs:
|
||||
populate the `resolve` array.
|
||||
- If `tracking_comment` is non-null, use it as the basis for your summary.
|
||||
|
||||
Trust the subagent findings — do NOT re-verify them by running your own
|
||||
bash, grep, sed, or awk commands against the source code. Phase 2 should
|
||||
only read `pr-context.json` and then produce the structured output.
|
||||
|
||||
Then:
|
||||
1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem).
|
||||
2. Drop low-confidence findings.
|
||||
3. Check the existing inline review comments from `pr-context.json`. Do NOT
|
||||
include a comment if one already exists on the same file about the same
|
||||
problem, even if the exact line numbers differ (lines shift between
|
||||
revisions). Also check for author replies that dismiss or reject a previous
|
||||
comment — do NOT re-raise an issue the PR author has already responded to
|
||||
disagreeing with.
|
||||
Populate the `resolve` array with the REST API `id` (integer) of your own
|
||||
review comment threads that should be resolved (user.login == "github-actions[bot]"
|
||||
and body starts with "Claude: "). Do not resolve threads from human reviewers.
|
||||
A thread should be resolved if:
|
||||
- The issue it raised has been addressed in the current PR (i.e. your review
|
||||
no longer flags it), or
|
||||
- The PR author (or another reviewer) left a reply disagreeing with or
|
||||
dismissing the comment.
|
||||
Only include the `id` of the **first** comment in each thread (the one that
|
||||
started the conversation). Do not resolve threads for issues that are still
|
||||
present and unaddressed.
|
||||
1. Collect all issues. Merge duplicates across agents (same file, same
|
||||
problem, lines within 3 of each other).
|
||||
2. Drop issues that already have a review comment on the same file about
|
||||
the same problem, or where the PR author replied disagreeing.
|
||||
3. Populate the `resolve` array with the `id` of your own review comment
|
||||
threads (user.login == "github-actions[bot]", body starts with
|
||||
"Claude: ") that should be resolved — either because the issue was
|
||||
fixed or because the author dismissed it. Use the first comment `id`
|
||||
in each thread. Do not resolve threads from human reviewers.
|
||||
4. Write a `summary` field in markdown for a top-level tracking comment.
|
||||
|
||||
**If no existing tracking comment was found (first run):**
|
||||
@ -486,7 +487,7 @@ jobs:
|
||||
...(c.side != null && { side: c.side }),
|
||||
...(c.start_line != null && { start_line: c.start_line }),
|
||||
...(c.start_side != null && { start_side: c.start_side }),
|
||||
body: `Claude: **${c.severity}**: ${c.body}`,
|
||||
body: c.body,
|
||||
});
|
||||
posted++;
|
||||
} catch (e) {
|
||||
|
||||
3
NEWS
3
NEWS
@ -22,6 +22,9 @@ CHANGES WITH 261 in spe:
|
||||
|
||||
New features:
|
||||
|
||||
* A new tmpfiles.d/root.conf has been added that sets permissions
|
||||
on the root directory (/) to 0555
|
||||
|
||||
* Networking to cloud IMDS services may be locked down for recognized
|
||||
clouds. This is recommended for secure installations, but typically
|
||||
conflicts with traditional IMDS clients such as cloud-init, which
|
||||
|
||||
9
TODO
9
TODO
@ -122,11 +122,12 @@ Features:
|
||||
* sysext: make systemd-{sys,conf}ext-sysroot.service work in the split '/var'
|
||||
configuration.
|
||||
|
||||
* sd-varlink: add fully async modes of the protocol upgrade stuff
|
||||
* introduce a concept of /etc/machine-info "TAGS=" field that allows tagging
|
||||
machines with zero, one or more roles, states or other forms of
|
||||
categorization. Then, add a way of using this in sysupdate to automatically
|
||||
enable certain transfers, one for each role.
|
||||
|
||||
* sd-varlink: optimize the read-byte-by-byte mode in case upgrade mode is
|
||||
enabled, via recvmsg() with MSG_SEEK: first read non-destrictively, look for
|
||||
NUL byte, and only then flush out
|
||||
* sd-varlink: add fully async modes of the protocol upgrade stuff
|
||||
|
||||
* repart: maybe remove iso9660/eltorito superblock from disk when booting via
|
||||
gpt, if there is one.
|
||||
|
||||
@ -84,6 +84,15 @@
|
||||
<citerefentry><refentrytitle>org.freedesktop.LogControl1</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for information about the D-Bus APIs <filename>systemd-logind</filename> provides.</para>
|
||||
|
||||
<para>In addition to the D-Bus interface, <filename>systemd-logind</filename> also provides a Varlink
|
||||
interface <constant>io.systemd.Shutdown</constant> for shutting down or rebooting the system. It
|
||||
supports <function>PowerOff</function>, <function>Reboot</function>, <function>Halt</function>,
|
||||
<function>KExec</function>, and <function>SoftReboot</function> methods. Each method accepts an
|
||||
optional <varname>skipInhibitors</varname> boolean parameter to bypass active block inhibitors
|
||||
(matching the <constant>SD_LOGIND_SKIP_INHIBITORS</constant> flag of the D-Bus interface). The
|
||||
interface can be queried with
|
||||
<command>varlinkctl introspect /run/systemd/io.systemd.Login io.systemd.Shutdown</command>.</para>
|
||||
|
||||
<para>For more information see
|
||||
<ulink url="https://systemd.io/INHIBITOR_LOCKS">Inhibitor Locks</ulink>.</para>
|
||||
|
||||
|
||||
@ -2896,7 +2896,13 @@ if git.found()
|
||||
'ls-files', ':/*.[ch]', ':/*.cc',
|
||||
check : false)
|
||||
if all_files.returncode() == 0
|
||||
all_files = files(all_files.stdout().split())
|
||||
existing_files = []
|
||||
foreach f : all_files.stdout().split()
|
||||
if fs.exists(f)
|
||||
existing_files += f
|
||||
endif
|
||||
endforeach
|
||||
all_files = files(existing_files)
|
||||
|
||||
custom_target(
|
||||
output : 'tags',
|
||||
|
||||
@ -1560,6 +1560,24 @@ fail:
|
||||
return r;
|
||||
}
|
||||
|
||||
int cg_get_keyed_attribute_uint64(const char *path, const char *attribute, const char *key, uint64_t *ret) {
|
||||
_cleanup_free_ char *val = NULL;
|
||||
int r;
|
||||
|
||||
assert(key);
|
||||
assert(ret);
|
||||
|
||||
r = cg_get_keyed_attribute(path, attribute, STRV_MAKE(key), &val);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = safe_atou64(val, ret);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to parse value '%s' of key '%s' in cgroup attribute '%s': %m", val, key, attribute);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cg_mask_to_string(CGroupMask mask, char **ret) {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
bool space = false;
|
||||
|
||||
@ -165,6 +165,7 @@ int cg_get_attribute_as_uint64(const char *path, const char *attribute, uint64_t
|
||||
int cg_get_attribute_as_bool(const char *path, const char *attribute);
|
||||
|
||||
int cg_get_keyed_attribute(const char *path, const char *attribute, char * const *keys, char **values);
|
||||
int cg_get_keyed_attribute_uint64(const char *path, const char *attribute, const char *key, uint64_t *ret);
|
||||
|
||||
int cg_get_owner(const char *path, uid_t *ret_uid);
|
||||
|
||||
|
||||
@ -285,19 +285,14 @@ static int process_cpu(Group *g, unsigned iteration) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
_cleanup_free_ char *val = NULL;
|
||||
uint64_t u;
|
||||
|
||||
r = cg_get_keyed_attribute(g->path, "cpu.stat", STRV_MAKE("usage_usec"), &val);
|
||||
r = cg_get_keyed_attribute_uint64(g->path, "cpu.stat", "usage_usec", &u);
|
||||
if (IN_SET(r, -ENOENT, -ENXIO))
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = safe_atou64(val, &u);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
new_usage = u * NSEC_PER_USEC;
|
||||
}
|
||||
|
||||
|
||||
@ -2996,9 +2996,8 @@ int unit_check_oomd_kill(Unit *u) {
|
||||
}
|
||||
|
||||
int unit_check_oom(Unit *u) {
|
||||
_cleanup_free_ char *oom_kill = NULL;
|
||||
bool increased;
|
||||
uint64_t c;
|
||||
uint64_t c = 0;
|
||||
int r;
|
||||
|
||||
CGroupRuntime *crt = unit_get_cgroup_runtime(u);
|
||||
@ -3013,33 +3012,25 @@ int unit_check_oom(Unit *u) {
|
||||
* back to reading oom_kill if we can't find the file or field. */
|
||||
|
||||
if (ctx->memory_oom_group) {
|
||||
r = cg_get_keyed_attribute(
|
||||
r = cg_get_keyed_attribute_uint64(
|
||||
crt->cgroup_path,
|
||||
"memory.events.local",
|
||||
STRV_MAKE("oom_group_kill"),
|
||||
&oom_kill);
|
||||
"oom_group_kill",
|
||||
&c);
|
||||
if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
|
||||
return log_unit_debug_errno(u, r, "Failed to read oom_group_kill field of memory.events.local cgroup attribute, ignoring: %m");
|
||||
}
|
||||
|
||||
if (isempty(oom_kill)) {
|
||||
r = cg_get_keyed_attribute(
|
||||
if (!ctx->memory_oom_group || r < 0) {
|
||||
r = cg_get_keyed_attribute_uint64(
|
||||
crt->cgroup_path,
|
||||
"memory.events",
|
||||
STRV_MAKE("oom_kill"),
|
||||
&oom_kill);
|
||||
"oom_kill",
|
||||
&c);
|
||||
if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
|
||||
return log_unit_debug_errno(u, r, "Failed to read oom_kill field of memory.events cgroup attribute: %m");
|
||||
}
|
||||
|
||||
if (!oom_kill)
|
||||
c = 0;
|
||||
else {
|
||||
r = safe_atou64(oom_kill, &c);
|
||||
if (r < 0)
|
||||
return log_unit_debug_errno(u, r, "Failed to parse memory.events cgroup oom field: %m");
|
||||
}
|
||||
|
||||
increased = c > crt->oom_kill_last;
|
||||
crt->oom_kill_last = c;
|
||||
|
||||
@ -3585,14 +3576,9 @@ static int unit_get_cpu_usage_raw(const Unit *u, const CGroupRuntime *crt, nsec_
|
||||
if (unit_has_host_root_cgroup(u))
|
||||
return procfs_cpu_get_usage(ret);
|
||||
|
||||
_cleanup_free_ char *val = NULL;
|
||||
uint64_t us;
|
||||
|
||||
r = cg_get_keyed_attribute(crt->cgroup_path, "cpu.stat", STRV_MAKE("usage_usec"), &val);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = safe_atou64(val, &us);
|
||||
r = cg_get_keyed_attribute_uint64(crt->cgroup_path, "cpu.stat", "usage_usec", &us);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@ -1238,7 +1238,6 @@ int bus_socket_take_fd(sd_bus *b) {
|
||||
int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) {
|
||||
struct iovec *iov;
|
||||
ssize_t k;
|
||||
size_t n;
|
||||
unsigned j;
|
||||
int r;
|
||||
|
||||
@ -1254,9 +1253,8 @@ int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
n = m->n_iovec * sizeof(struct iovec);
|
||||
iov = newa(struct iovec, n);
|
||||
memcpy_safe(iov, m->iovec, n);
|
||||
iov = newa(struct iovec, m->n_iovec);
|
||||
memcpy_safe(iov, m->iovec, sizeof(struct iovec) * m->n_iovec);
|
||||
|
||||
j = 0;
|
||||
iovec_advance(iov, &j, *idx);
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
#include "logind-seat.h"
|
||||
#include "logind-seat-dbus.h"
|
||||
#include "logind-session-dbus.h"
|
||||
#include "logind-shutdown.h"
|
||||
#include "logind-user.h"
|
||||
#include "logind-user-dbus.h"
|
||||
#include "logind-utmp.h"
|
||||
@ -76,10 +77,6 @@
|
||||
*/
|
||||
#define WALL_MESSAGE_MAX 4096U
|
||||
|
||||
#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled"
|
||||
|
||||
static void reset_scheduled_shutdown(Manager *m);
|
||||
|
||||
static int get_sender_session(
|
||||
Manager *m,
|
||||
sd_bus_message *message,
|
||||
@ -1866,24 +1863,6 @@ static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
}
|
||||
|
||||
static int have_multiple_sessions(
|
||||
Manager *m,
|
||||
uid_t uid) {
|
||||
|
||||
Session *session;
|
||||
|
||||
assert(m);
|
||||
|
||||
/* Check for other users' sessions. Greeter sessions do not
|
||||
* count, and non-login sessions do not count either. */
|
||||
HASHMAP_FOREACH(session, m->sessions)
|
||||
if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) &&
|
||||
session->user->user_record->uid != uid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int bus_manager_log_shutdown(
|
||||
Manager *m,
|
||||
const HandleActionData *a) {
|
||||
@ -2189,121 +2168,6 @@ int bus_manager_shutdown_or_sleep_now_or_later(
|
||||
return r;
|
||||
}
|
||||
|
||||
static int verify_shutdown_creds(
|
||||
Manager *m,
|
||||
sd_bus_message *message,
|
||||
const HandleActionData *a,
|
||||
uint64_t flags,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
||||
bool multiple_sessions, blocked, interactive;
|
||||
_unused_ bool error_or_denial = false;
|
||||
Inhibitor *offending = NULL;
|
||||
uid_t uid;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(a);
|
||||
assert(message);
|
||||
|
||||
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_creds_get_euid(creds, &uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = have_multiple_sessions(m, uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
multiple_sessions = r > 0;
|
||||
blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending);
|
||||
interactive = flags & SD_LOGIND_INTERACTIVE;
|
||||
|
||||
if (multiple_sessions) {
|
||||
r = bus_verify_polkit_async_full(
|
||||
message,
|
||||
a->polkit_action_multiple_sessions,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0) {
|
||||
/* If we get -EBUSY, it means a polkit decision was made, but not for
|
||||
* this action in particular. Assuming we are blocked on inhibitors,
|
||||
* ignore that error and allow the decision to be revealed below. */
|
||||
if (blocked && r == -EBUSY)
|
||||
error_or_denial = true;
|
||||
else
|
||||
return r;
|
||||
}
|
||||
if (r == 0)
|
||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||
}
|
||||
|
||||
if (blocked) {
|
||||
PolkitFlags polkit_flags = 0;
|
||||
|
||||
/* With a strong inhibitor, if the skip flag is not set, reject outright.
|
||||
* With a weak inhibitor, if root is asking and the root flag is set, reject outright.
|
||||
* All else, check polkit first. */
|
||||
if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) &&
|
||||
(offending->mode != INHIBIT_BLOCK_WEAK ||
|
||||
(uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS))))
|
||||
return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK,
|
||||
"Operation denied due to active block inhibitor");
|
||||
|
||||
/* We want to always ask here, even for root, to only allow bypassing if explicitly allowed
|
||||
* by polkit, unless a weak blocker is used, in which case it will be authorized. */
|
||||
if (offending->mode != INHIBIT_BLOCK_WEAK)
|
||||
polkit_flags |= POLKIT_ALWAYS_QUERY;
|
||||
|
||||
if (interactive)
|
||||
polkit_flags |= POLKIT_ALLOW_INTERACTIVE;
|
||||
|
||||
r = bus_verify_polkit_async_full(
|
||||
message,
|
||||
a->polkit_action_ignore_inhibit,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
polkit_flags,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||
}
|
||||
|
||||
if (!multiple_sessions && !blocked) {
|
||||
r = bus_verify_polkit_async_full(
|
||||
message,
|
||||
a->polkit_action,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||
}
|
||||
|
||||
/* If error_or_denial was set above, it means that a polkit denial or
|
||||
* error was deferred for a future call to bus_verify_polkit_async_full()
|
||||
* to catch. In any case, it also means that the payload guarded by
|
||||
* these polkit calls should never be executed, and hence we should
|
||||
* never reach this point. */
|
||||
assert(!error_or_denial);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_wall_message_timer(Manager *m, sd_bus_message* message) {
|
||||
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
||||
int r;
|
||||
@ -2442,10 +2306,17 @@ static int method_do_shutdown_or_sleep(
|
||||
} else if (!a)
|
||||
assert_se(a = handle_action_lookup(action));
|
||||
|
||||
r = verify_shutdown_creds(m, message, a, flags, error);
|
||||
r = manager_verify_shutdown_creds(m, message, /* link= */ NULL, a, flags, error);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
{
|
||||
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
|
||||
|
||||
(void) bus_query_sender_pidref(message, &pidref);
|
||||
log_shutdown_caller(&pidref, handle_action_to_string(a->handle));
|
||||
}
|
||||
|
||||
if (m->delayed_action)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
|
||||
"Action %s already in progress, refusing requested %s operation.",
|
||||
@ -2454,7 +2325,7 @@ static int method_do_shutdown_or_sleep(
|
||||
|
||||
/* reset case we're shorting a scheduled shutdown */
|
||||
m->unlink_nologin = false;
|
||||
reset_scheduled_shutdown(m);
|
||||
manager_reset_scheduled_shutdown(m);
|
||||
|
||||
m->scheduled_shutdown_timeout = 0;
|
||||
m->scheduled_shutdown_action = action;
|
||||
@ -2568,29 +2439,6 @@ static usec_t nologin_timeout_usec(usec_t elapse) {
|
||||
return LESS_BY(elapse, 5 * USEC_PER_MINUTE);
|
||||
}
|
||||
|
||||
static void reset_scheduled_shutdown(Manager *m) {
|
||||
assert(m);
|
||||
|
||||
m->scheduled_shutdown_timeout_source = sd_event_source_disable_unref(m->scheduled_shutdown_timeout_source);
|
||||
m->wall_message_timeout_source = sd_event_source_disable_unref(m->wall_message_timeout_source);
|
||||
m->nologin_timeout_source = sd_event_source_disable_unref(m->nologin_timeout_source);
|
||||
|
||||
m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID;
|
||||
m->scheduled_shutdown_timeout = USEC_INFINITY;
|
||||
m->scheduled_shutdown_uid = UID_INVALID;
|
||||
m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty);
|
||||
m->shutdown_dry_run = false;
|
||||
|
||||
if (m->unlink_nologin) {
|
||||
(void) unlink_or_warn("/run/nologin");
|
||||
m->unlink_nologin = false;
|
||||
}
|
||||
|
||||
(void) unlink(SHUTDOWN_SCHEDULE_FILE);
|
||||
|
||||
manager_send_changed(m, "ScheduledShutdown");
|
||||
}
|
||||
|
||||
static int update_schedule_file(Manager *m) {
|
||||
_cleanup_(unlink_and_freep) char *temp_path = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
@ -2669,7 +2517,7 @@ static int manager_scheduled_shutdown_handler(
|
||||
|
||||
bus_manager_log_shutdown(m, a);
|
||||
log_info("Running in dry run, suppressing action.");
|
||||
reset_scheduled_shutdown(m);
|
||||
manager_reset_scheduled_shutdown(m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -2683,7 +2531,7 @@ static int manager_scheduled_shutdown_handler(
|
||||
return 0;
|
||||
|
||||
error:
|
||||
reset_scheduled_shutdown(m);
|
||||
manager_reset_scheduled_shutdown(m);
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -2738,7 +2586,7 @@ void manager_load_scheduled_shutdown(Manager *m) {
|
||||
"TTY", &tty);
|
||||
|
||||
/* reset will delete the file */
|
||||
reset_scheduled_shutdown(m);
|
||||
manager_reset_scheduled_shutdown(m);
|
||||
|
||||
if (r == -ENOENT)
|
||||
return;
|
||||
@ -2784,7 +2632,7 @@ void manager_load_scheduled_shutdown(Manager *m) {
|
||||
|
||||
r = manager_setup_shutdown_timers(m);
|
||||
if (r < 0)
|
||||
return reset_scheduled_shutdown(m);
|
||||
return manager_reset_scheduled_shutdown(m);
|
||||
|
||||
(void) manager_setup_wall_message_timer(m);
|
||||
(void) update_schedule_file(m);
|
||||
@ -2819,7 +2667,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
|
||||
assert_se(a = handle_action_lookup(handle));
|
||||
assert(a->polkit_action);
|
||||
|
||||
r = verify_shutdown_creds(m, message, a, 0, error);
|
||||
r = manager_verify_shutdown_creds(m, message, /* link= */ NULL, a, 0, error);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
@ -2853,7 +2701,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
|
||||
r = update_schedule_file(m);
|
||||
|
||||
if (r < 0) {
|
||||
reset_scheduled_shutdown(m);
|
||||
manager_reset_scheduled_shutdown(m);
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -2913,7 +2761,7 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd
|
||||
}
|
||||
|
||||
cancel_delayed_action(m);
|
||||
reset_scheduled_shutdown(m);
|
||||
manager_reset_scheduled_shutdown(m);
|
||||
|
||||
return sd_bus_reply_method_return(message, "b", true);
|
||||
}
|
||||
@ -2969,7 +2817,7 @@ static int method_can_shutdown_or_sleep(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = have_multiple_sessions(m, uid);
|
||||
r = manager_have_multiple_sessions(m, uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
250
src/login/logind-shutdown.c
Normal file
250
src/login/logind-shutdown.c
Normal file
@ -0,0 +1,250 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-event.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "bus-common-errors.h"
|
||||
#include "bus-polkit.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "log.h"
|
||||
#include "login-util.h"
|
||||
#include "logind.h"
|
||||
#include "logind-dbus.h"
|
||||
#include "logind-inhibit.h"
|
||||
#include "logind-session.h"
|
||||
#include "logind-shutdown.h"
|
||||
#include "logind-user.h"
|
||||
#include "pidref.h"
|
||||
#include "process-util.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int manager_have_multiple_sessions(
|
||||
Manager *m,
|
||||
uid_t uid) {
|
||||
|
||||
Session *session;
|
||||
|
||||
assert(m);
|
||||
|
||||
/* Check for other users' sessions. Greeter sessions do not
|
||||
* count, and non-login sessions do not count either. */
|
||||
HASHMAP_FOREACH(session, m->sessions)
|
||||
if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) &&
|
||||
session->user->user_record->uid != uid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void log_shutdown_caller(const PidRef *caller, const char *method) {
|
||||
_cleanup_free_ char *comm = NULL, *unit = NULL;
|
||||
|
||||
assert(method);
|
||||
|
||||
if (!pidref_is_set(caller)) {
|
||||
return log_notice("%s requested from unknown client PID...", method);
|
||||
}
|
||||
|
||||
(void) pidref_get_comm(caller, &comm);
|
||||
(void) cg_pidref_get_unit(caller, &unit);
|
||||
|
||||
log_notice("%s requested from client PID " PID_FMT "%s%s%s%s%s%s...",
|
||||
method, caller->pid,
|
||||
comm ? " ('" : "", strempty(comm), comm ? "')" : "",
|
||||
unit ? " (unit " : "", strempty(unit), unit ? ")" : "");
|
||||
}
|
||||
|
||||
int manager_verify_shutdown_creds(
|
||||
Manager *m,
|
||||
sd_bus_message *message,
|
||||
sd_varlink *link,
|
||||
const HandleActionData *a,
|
||||
uint64_t flags,
|
||||
sd_bus_error *error) {
|
||||
|
||||
bool multiple_sessions, blocked, interactive;
|
||||
_unused_ bool error_or_denial = false;
|
||||
Inhibitor *offending = NULL;
|
||||
uid_t uid;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(a);
|
||||
assert(!!message != !!link); /* exactly one transport */
|
||||
assert(!link || !error); /* varlink doesn't use sd_bus_error */
|
||||
|
||||
if (message) {
|
||||
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
||||
|
||||
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_creds_get_euid(creds, &uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
r = sd_varlink_get_peer_uid(link, &uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = manager_have_multiple_sessions(m, uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
multiple_sessions = r > 0;
|
||||
blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending);
|
||||
interactive = flags & SD_LOGIND_INTERACTIVE;
|
||||
|
||||
if (multiple_sessions) {
|
||||
if (message)
|
||||
r = bus_verify_polkit_async_full(
|
||||
message,
|
||||
a->polkit_action_multiple_sessions,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
else
|
||||
r = varlink_verify_polkit_async_full(
|
||||
link,
|
||||
m->bus,
|
||||
a->polkit_action_multiple_sessions,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
|
||||
&m->polkit_registry);
|
||||
|
||||
if (r < 0) {
|
||||
/* If we get -EBUSY, it means a polkit decision was made, but not for
|
||||
* this action in particular. Assuming we are blocked on inhibitors,
|
||||
* ignore that error and allow the decision to be revealed below. */
|
||||
if (blocked && r == -EBUSY)
|
||||
error_or_denial = true;
|
||||
else
|
||||
return r;
|
||||
}
|
||||
if (r == 0)
|
||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||
}
|
||||
|
||||
if (blocked) {
|
||||
PolkitFlags polkit_flags = 0;
|
||||
|
||||
/* With a strong inhibitor, if the skip flag is not set, reject outright.
|
||||
* With a weak inhibitor, if root is asking and the root flag is set, reject outright.
|
||||
* All else, check polkit first. */
|
||||
if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) &&
|
||||
(offending->mode != INHIBIT_BLOCK_WEAK ||
|
||||
(uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS)))) {
|
||||
if (link)
|
||||
return sd_varlink_errorbo(
|
||||
link,
|
||||
"io.systemd.Shutdown.BlockedByInhibitor",
|
||||
SD_JSON_BUILD_PAIR_STRING("who", offending->who),
|
||||
SD_JSON_BUILD_PAIR_STRING("why", offending->why));
|
||||
if (error)
|
||||
return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK,
|
||||
"Operation denied due to active block inhibitor");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
/* We want to always ask here, even for root, to only allow bypassing if explicitly allowed
|
||||
* by polkit, unless a weak blocker is used, in which case it will be authorized. */
|
||||
if (offending->mode != INHIBIT_BLOCK_WEAK)
|
||||
polkit_flags |= POLKIT_ALWAYS_QUERY;
|
||||
|
||||
if (interactive)
|
||||
polkit_flags |= POLKIT_ALLOW_INTERACTIVE;
|
||||
|
||||
if (message)
|
||||
r = bus_verify_polkit_async_full(
|
||||
message,
|
||||
a->polkit_action_ignore_inhibit,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
polkit_flags,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
else
|
||||
r = varlink_verify_polkit_async_full(
|
||||
link,
|
||||
m->bus,
|
||||
a->polkit_action_ignore_inhibit,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
polkit_flags,
|
||||
&m->polkit_registry);
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||
}
|
||||
|
||||
if (!multiple_sessions && !blocked) {
|
||||
if (message)
|
||||
r = bus_verify_polkit_async_full(
|
||||
message,
|
||||
a->polkit_action,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
else
|
||||
r = varlink_verify_polkit_async_full(
|
||||
link,
|
||||
m->bus,
|
||||
a->polkit_action,
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
|
||||
&m->polkit_registry);
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||
}
|
||||
|
||||
/* If error_or_denial was set above, it means that a polkit denial or
|
||||
* error was deferred for a future call to bus_verify_polkit_async_full()
|
||||
* to catch. In any case, it also means that the payload guarded by
|
||||
* these polkit calls should never be executed, and hence we should
|
||||
* never reach this point. */
|
||||
assert(!error_or_denial);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void manager_reset_scheduled_shutdown(Manager *m) {
|
||||
assert(m);
|
||||
|
||||
m->scheduled_shutdown_timeout_source = sd_event_source_disable_unref(m->scheduled_shutdown_timeout_source);
|
||||
m->wall_message_timeout_source = sd_event_source_disable_unref(m->wall_message_timeout_source);
|
||||
m->nologin_timeout_source = sd_event_source_disable_unref(m->nologin_timeout_source);
|
||||
|
||||
m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID;
|
||||
m->scheduled_shutdown_timeout = USEC_INFINITY;
|
||||
m->scheduled_shutdown_uid = UID_INVALID;
|
||||
m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty);
|
||||
m->shutdown_dry_run = false;
|
||||
|
||||
if (m->unlink_nologin) {
|
||||
(void) unlink_or_warn("/run/nologin");
|
||||
m->unlink_nologin = false;
|
||||
}
|
||||
|
||||
(void) unlink(SHUTDOWN_SCHEDULE_FILE);
|
||||
|
||||
manager_send_changed(m, "ScheduledShutdown");
|
||||
}
|
||||
17
src/login/logind-shutdown.h
Normal file
17
src/login/logind-shutdown.h
Normal file
@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "logind-forward.h"
|
||||
|
||||
#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled"
|
||||
|
||||
int manager_have_multiple_sessions(Manager *m, uid_t uid);
|
||||
|
||||
void log_shutdown_caller(const PidRef *caller, const char *method);
|
||||
|
||||
/* manager_verify_shutdown_creds() takes *either* a "message" or "link" depending on if it is used
|
||||
* to validate a D-Bus or Varlink shutdown request. When varlink is used the sd_bus_error *error
|
||||
* must be NULL */
|
||||
int manager_verify_shutdown_creds(Manager *m, sd_bus_message *message, sd_varlink *link, const HandleActionData *a, uint64_t flags, sd_bus_error *error);
|
||||
|
||||
void manager_reset_scheduled_shutdown(Manager *m);
|
||||
@ -4,15 +4,19 @@
|
||||
#include "sd-event.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-polkit.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "json-util.h"
|
||||
#include "logind-session.h"
|
||||
#include "login-util.h"
|
||||
#include "logind.h"
|
||||
#include "logind-dbus.h"
|
||||
#include "logind-seat.h"
|
||||
#include "logind-session.h"
|
||||
#include "logind-shutdown.h"
|
||||
#include "logind-user.h"
|
||||
#include "logind-varlink.h"
|
||||
#include "strv.h"
|
||||
@ -20,6 +24,7 @@
|
||||
#include "user-record.h"
|
||||
#include "user-util.h"
|
||||
#include "varlink-io.systemd.Login.h"
|
||||
#include "varlink-io.systemd.Shutdown.h"
|
||||
#include "varlink-io.systemd.service.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
@ -336,6 +341,101 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete
|
||||
return sd_varlink_reply(link, NULL);
|
||||
}
|
||||
|
||||
static int setup_wall_message_timer(Manager *m, sd_varlink *link) {
|
||||
uid_t uid = UID_INVALID;
|
||||
int r;
|
||||
|
||||
(void) sd_varlink_get_peer_uid(link, &uid);
|
||||
m->scheduled_shutdown_uid = uid;
|
||||
|
||||
_cleanup_free_ char *tty = NULL;
|
||||
pid_t pid = 0;
|
||||
r = sd_varlink_get_peer_pid(link, &pid);
|
||||
if (r >= 0)
|
||||
(void) get_ctty(pid, /* ret_devnr= */ NULL, &tty);
|
||||
|
||||
r = free_and_strdup_warn(&m->scheduled_shutdown_tty, tty);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return manager_setup_wall_message_timer(m);
|
||||
}
|
||||
|
||||
static int manager_do_shutdown_action(sd_varlink *link, sd_json_variant *parameters, HandleAction action) {
|
||||
Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link));
|
||||
int skip_inhibitors = -1;
|
||||
int r;
|
||||
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "skipInhibitors", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, 0, 0 },
|
||||
VARLINK_DISPATCH_POLKIT_FIELD,
|
||||
{}
|
||||
};
|
||||
|
||||
r = sd_varlink_dispatch(link, parameters, dispatch_table, &skip_inhibitors);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
uint64_t flags = skip_inhibitors > 0 ? SD_LOGIND_SKIP_INHIBITORS : 0;
|
||||
|
||||
const HandleActionData *a = handle_action_lookup(action);
|
||||
assert(a);
|
||||
|
||||
r = manager_verify_shutdown_creds(m, /* message= */ NULL, link, a, flags, /* error= */ NULL);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
{
|
||||
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
|
||||
|
||||
(void) varlink_get_peer_pidref(link, &pidref);
|
||||
log_shutdown_caller(&pidref, handle_action_to_string(action));
|
||||
}
|
||||
|
||||
if (m->delayed_action)
|
||||
return sd_varlink_error(link, "io.systemd.Shutdown.AlreadyInProgress", /* parameters= */ NULL);
|
||||
|
||||
/* Reset in case we're short-circuiting a scheduled shutdown */
|
||||
m->unlink_nologin = false;
|
||||
manager_reset_scheduled_shutdown(m);
|
||||
|
||||
m->scheduled_shutdown_timeout = 0;
|
||||
m->scheduled_shutdown_action = action;
|
||||
|
||||
(void) setup_wall_message_timer(m, link);
|
||||
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
r = bus_manager_shutdown_or_sleep_now_or_later(m, a, &error);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to execute %s: %s",
|
||||
handle_action_to_string(action),
|
||||
bus_error_message(&error, r));
|
||||
return sd_varlink_error_errno(link, r);
|
||||
}
|
||||
|
||||
return sd_varlink_reply(link, NULL);
|
||||
}
|
||||
|
||||
static int vl_method_power_off(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return manager_do_shutdown_action(link, parameters, HANDLE_POWEROFF);
|
||||
}
|
||||
|
||||
static int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return manager_do_shutdown_action(link, parameters, HANDLE_REBOOT);
|
||||
}
|
||||
|
||||
static int vl_method_halt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return manager_do_shutdown_action(link, parameters, HANDLE_HALT);
|
||||
}
|
||||
|
||||
static int vl_method_kexec(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return manager_do_shutdown_action(link, parameters, HANDLE_KEXEC);
|
||||
}
|
||||
|
||||
static int vl_method_soft_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return manager_do_shutdown_action(link, parameters, HANDLE_SOFT_REBOOT);
|
||||
}
|
||||
|
||||
int manager_varlink_init(Manager *m, int fd) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
|
||||
_unused_ _cleanup_close_ int fd_close = fd;
|
||||
@ -358,14 +458,20 @@ int manager_varlink_init(Manager *m, int fd) {
|
||||
r = sd_varlink_server_add_interface_many(
|
||||
s,
|
||||
&vl_interface_io_systemd_Login,
|
||||
&vl_interface_io_systemd_Shutdown,
|
||||
&vl_interface_io_systemd_service);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add Login interface to varlink server: %m");
|
||||
return log_error_errno(r, "Failed to add varlink interfaces: %m");
|
||||
|
||||
r = sd_varlink_server_bind_method_many(
|
||||
s,
|
||||
"io.systemd.Login.CreateSession", vl_method_create_session,
|
||||
"io.systemd.Login.ReleaseSession", vl_method_release_session,
|
||||
"io.systemd.Shutdown.PowerOff", vl_method_power_off,
|
||||
"io.systemd.Shutdown.Reboot", vl_method_reboot,
|
||||
"io.systemd.Shutdown.Halt", vl_method_halt,
|
||||
"io.systemd.Shutdown.KExec", vl_method_kexec,
|
||||
"io.systemd.Shutdown.SoftReboot", vl_method_soft_reboot,
|
||||
"io.systemd.service.Ping", varlink_method_ping,
|
||||
"io.systemd.service.SetLogLevel", varlink_method_set_log_level,
|
||||
"io.systemd.service.GetEnvironment", varlink_method_get_environment);
|
||||
|
||||
@ -21,6 +21,7 @@ systemd_logind_extract_sources = files(
|
||||
'logind-session-dbus.c',
|
||||
'logind-session-device.c',
|
||||
'logind-session.c',
|
||||
'logind-shutdown.c',
|
||||
'logind-user-dbus.c',
|
||||
'logind-user.c',
|
||||
'logind-utmp.c',
|
||||
|
||||
@ -624,7 +624,7 @@ int oomd_select_by_swap_usage(Hashmap *h, uint64_t threshold_usage, OomdCGroupCo
|
||||
|
||||
int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) {
|
||||
_cleanup_(oomd_cgroup_context_unrefp) OomdCGroupContext *ctx = NULL;
|
||||
_cleanup_free_ char *p = NULL, *val = NULL;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
bool is_root;
|
||||
int r;
|
||||
|
||||
@ -678,13 +678,9 @@ int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) {
|
||||
else if (r < 0)
|
||||
return log_debug_errno(r, "Error getting memory.swap.current from %s: %m", path);
|
||||
|
||||
r = cg_get_keyed_attribute(path, "memory.stat", STRV_MAKE("pgscan"), &val);
|
||||
r = cg_get_keyed_attribute_uint64(path, "memory.stat", "pgscan", &ctx->pgscan);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Error getting pgscan from memory.stat under %s: %m", path);
|
||||
|
||||
r = safe_atou64(val, &ctx->pgscan);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Error converting pgscan value to uint64_t: %m");
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(ctx);
|
||||
|
||||
@ -15,4 +15,11 @@ executables += [
|
||||
'report-basic.c',
|
||||
),
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-report-cgroup',
|
||||
'sources' : files(
|
||||
'report-cgroup.c',
|
||||
'report-cgroup-server.c',
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
131
src/report/report-cgroup-server.c
Normal file
131
src/report/report-cgroup-server.c
Normal file
@ -0,0 +1,131 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "ansi-color.h"
|
||||
#include "build.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "pretty-print.h"
|
||||
#include "report-cgroup.h"
|
||||
#include "varlink-io.systemd.Metrics.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
static int vl_server(void) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL;
|
||||
_cleanup_(cgroup_context_freep) CGroupContext *ctx = NULL;
|
||||
int r;
|
||||
|
||||
ctx = new0(CGroupContext, 1);
|
||||
if (!ctx)
|
||||
return log_oom();
|
||||
|
||||
r = varlink_server_new(&vs, SD_VARLINK_SERVER_INHERIT_USERDATA, ctx);
|
||||
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 help(void) {
|
||||
_cleanup_free_ char *url = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-report-cgroup", "8", &url);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%s [OPTIONS...]\n"
|
||||
"\n%sReport cgroup metrics.%s\n"
|
||||
"\n%sOptions:%s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
url);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
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;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return vl_server();
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
495
src/report/report-cgroup.c
Normal file
495
src/report/report-cgroup.c
Normal file
@ -0,0 +1,495 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-json.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "extract-word.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "log.h"
|
||||
#include "metrics.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "report-cgroup.h"
|
||||
#include "string-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
typedef struct CGroupInfo {
|
||||
char *unit;
|
||||
char *path;
|
||||
uint64_t io_rbytes;
|
||||
uint64_t io_rios;
|
||||
int io_stat_cached; /* 0 = not attempted, > 0 = cached, < 0 = -errno */
|
||||
} CGroupInfo;
|
||||
|
||||
static CGroupInfo *cgroup_info_free(CGroupInfo *info) {
|
||||
if (!info)
|
||||
return NULL;
|
||||
free(info->unit);
|
||||
free(info->path);
|
||||
return mfree(info);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(CGroupInfo*, cgroup_info_free);
|
||||
|
||||
static void cgroup_info_array_free(CGroupInfo **infos, size_t n) {
|
||||
FOREACH_ARRAY(i, infos, n)
|
||||
cgroup_info_free(*i);
|
||||
free(infos);
|
||||
}
|
||||
|
||||
static void cgroup_context_flush(CGroupContext *ctx) {
|
||||
assert(ctx);
|
||||
cgroup_info_array_free(ctx->cgroups, ctx->n_cgroups);
|
||||
ctx->cgroups = NULL;
|
||||
ctx->n_cgroups = 0;
|
||||
ctx->cache_populated = false;
|
||||
}
|
||||
|
||||
CGroupContext *cgroup_context_free(CGroupContext *ctx) {
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
cgroup_context_flush(ctx);
|
||||
return mfree(ctx);
|
||||
}
|
||||
|
||||
static int walk_cgroups_recursive(const char *path, CGroupInfo ***infos, size_t *n_infos) {
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(infos);
|
||||
assert(n_infos);
|
||||
|
||||
/* Collect any unit cgroup we encounter */
|
||||
_cleanup_free_ char *name = NULL;
|
||||
r = cg_path_get_unit(path, &name);
|
||||
if (r >= 0) {
|
||||
_cleanup_(cgroup_info_freep) CGroupInfo *info = new(CGroupInfo, 1);
|
||||
if (!info)
|
||||
return log_oom();
|
||||
|
||||
*info = (CGroupInfo) {
|
||||
.unit = TAKE_PTR(name),
|
||||
.path = strdup(path),
|
||||
};
|
||||
if (!info->path)
|
||||
return log_oom();
|
||||
|
||||
if (!GREEDY_REALLOC(*infos, *n_infos + 1))
|
||||
return log_oom();
|
||||
|
||||
(*infos)[(*n_infos)++] = TAKE_PTR(info);
|
||||
return 0; /* Unit cgroups are leaf nodes for our purposes */
|
||||
}
|
||||
|
||||
/* Stop at delegation boundaries — don't descend into delegated subtrees */
|
||||
r = cg_is_delegated(path);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to check delegation for '%s': %m", path);
|
||||
if (r > 0)
|
||||
return 0;
|
||||
|
||||
r = cg_enumerate_subgroups(path, &d);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to enumerate cgroup '%s': %m", path);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *fn = NULL, *child = NULL;
|
||||
|
||||
r = cg_read_subgroup(d, &fn);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read subgroup from '%s': %m", path);
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
child = path_join(empty_to_root(path), fn);
|
||||
if (!child)
|
||||
return log_oom();
|
||||
|
||||
path_simplify(child);
|
||||
|
||||
r = walk_cgroups_recursive(child, infos, n_infos);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int walk_cgroups(CGroupContext *ctx, CGroupInfo ***ret, size_t *ret_n) {
|
||||
int r;
|
||||
|
||||
assert(ctx);
|
||||
assert(ret);
|
||||
assert(ret_n);
|
||||
|
||||
/* Return cached result if available */
|
||||
if (ctx->cache_populated) {
|
||||
*ret = ctx->cgroups;
|
||||
*ret_n = ctx->n_cgroups;
|
||||
return 0;
|
||||
}
|
||||
|
||||
CGroupInfo **infos = NULL;
|
||||
size_t n_infos = 0;
|
||||
CLEANUP_ARRAY(infos, n_infos, cgroup_info_array_free);
|
||||
|
||||
r = walk_cgroups_recursive("", &infos, &n_infos);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
ctx->cgroups = TAKE_PTR(infos);
|
||||
ctx->n_cgroups = TAKE_GENERIC(n_infos, size_t, 0);
|
||||
ctx->cache_populated = true;
|
||||
|
||||
*ret = ctx->cgroups;
|
||||
*ret_n = ctx->n_cgroups;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpu_usage_build_json(MetricFamilyContext *context, void *userdata) {
|
||||
CGroupContext *ctx = ASSERT_PTR(userdata);
|
||||
CGroupInfo **cgroups;
|
||||
size_t n_cgroups;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
|
||||
r = walk_cgroups(ctx, &cgroups, &n_cgroups);
|
||||
if (r < 0)
|
||||
return 0; /* Skip metric on failure */
|
||||
|
||||
FOREACH_ARRAY(c, cgroups, n_cgroups) {
|
||||
uint64_t us;
|
||||
|
||||
r = cg_get_keyed_attribute_uint64((*c)->path, "cpu.stat", "usage_usec", &us);
|
||||
if (r < 0)
|
||||
continue;
|
||||
|
||||
r = metric_build_send_unsigned(
|
||||
context,
|
||||
(*c)->unit,
|
||||
us * NSEC_PER_USEC,
|
||||
/* fields= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int memory_usage_build_json(MetricFamilyContext *context, void *userdata) {
|
||||
CGroupContext *ctx = ASSERT_PTR(userdata);
|
||||
CGroupInfo **cgroups;
|
||||
size_t n_cgroups;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
|
||||
r = walk_cgroups(ctx, &cgroups, &n_cgroups);
|
||||
if (r < 0)
|
||||
return 0;
|
||||
|
||||
FOREACH_ARRAY(c, cgroups, n_cgroups) {
|
||||
uint64_t current = 0, limit = UINT64_MAX;
|
||||
|
||||
r = cg_get_attribute_as_uint64((*c)->path, "memory.current", ¤t);
|
||||
if (r >= 0) {
|
||||
/* Walk up the cgroup tree to find the tightest memory limit */
|
||||
_cleanup_free_ char *path_buf = strdup((*c)->path);
|
||||
if (!path_buf)
|
||||
return log_oom();
|
||||
|
||||
for (char *p = path_buf;;) {
|
||||
uint64_t high, max;
|
||||
|
||||
r = cg_get_attribute_as_uint64(p, "memory.max", &max);
|
||||
if (r >= 0 && max < limit)
|
||||
limit = max;
|
||||
|
||||
r = cg_get_attribute_as_uint64(p, "memory.high", &high);
|
||||
if (r >= 0 && high < limit)
|
||||
limit = high;
|
||||
|
||||
/* Move to parent */
|
||||
const char *e;
|
||||
r = path_find_last_component(p, /* accept_dot_dot= */ false, &e, NULL);
|
||||
if (r <= 0)
|
||||
break;
|
||||
p[e - p] = '\0';
|
||||
}
|
||||
|
||||
if (limit != UINT64_MAX && limit > current) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL;
|
||||
r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "available"));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = metric_build_send_unsigned(
|
||||
context,
|
||||
(*c)->unit,
|
||||
limit - current,
|
||||
fields);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL;
|
||||
r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "current"));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = metric_build_send_unsigned(
|
||||
context,
|
||||
(*c)->unit,
|
||||
current,
|
||||
fields);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
uint64_t val;
|
||||
r = cg_get_attribute_as_uint64((*c)->path, "memory.peak", &val);
|
||||
if (r >= 0) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL;
|
||||
r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "peak"));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = metric_build_send_unsigned(
|
||||
context,
|
||||
(*c)->unit,
|
||||
val,
|
||||
fields);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse io.stat for a cgroup once, summing both rbytes= and rios= fields in a
|
||||
* single pass to avoid reading the file twice. */
|
||||
static int io_stat_parse(const char *cgroup_path, uint64_t *ret_rbytes, uint64_t *ret_rios) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
uint64_t rbytes = 0, rios = 0;
|
||||
int r;
|
||||
|
||||
r = cg_get_path(cgroup_path, "io.stat", &path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
f = fopen(path, "re");
|
||||
if (!f)
|
||||
return -errno;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *line = NULL;
|
||||
const char *p;
|
||||
|
||||
r = read_line(f, LONG_LINE_MAX, &line);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
p = line;
|
||||
p += strcspn(p, WHITESPACE);
|
||||
p += strspn(p, WHITESPACE);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *word = NULL;
|
||||
|
||||
r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
const char *v;
|
||||
uint64_t val;
|
||||
|
||||
v = startswith(word, "rbytes=");
|
||||
if (v && safe_atou64(v, &val) >= 0) {
|
||||
rbytes += val;
|
||||
continue;
|
||||
}
|
||||
|
||||
v = startswith(word, "rios=");
|
||||
if (v && safe_atou64(v, &val) >= 0)
|
||||
rios += val;
|
||||
}
|
||||
}
|
||||
|
||||
*ret_rbytes = rbytes;
|
||||
*ret_rios = rios;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ensure_io_stat_cached(CGroupInfo *info) {
|
||||
int r;
|
||||
|
||||
assert(info);
|
||||
|
||||
if (info->io_stat_cached > 0)
|
||||
return 0;
|
||||
if (info->io_stat_cached < 0)
|
||||
return info->io_stat_cached;
|
||||
|
||||
r = io_stat_parse(info->path, &info->io_rbytes, &info->io_rios);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
log_debug_errno(r, "Failed to parse IO stats for '%s': %m", info->path);
|
||||
info->io_stat_cached = r;
|
||||
return r;
|
||||
}
|
||||
|
||||
info->io_stat_cached = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int io_read_bytes_build_json(MetricFamilyContext *context, void *userdata) {
|
||||
CGroupContext *ctx = ASSERT_PTR(userdata);
|
||||
CGroupInfo **cgroups;
|
||||
size_t n_cgroups;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
|
||||
r = walk_cgroups(ctx, &cgroups, &n_cgroups);
|
||||
if (r < 0)
|
||||
return 0;
|
||||
|
||||
FOREACH_ARRAY(c, cgroups, n_cgroups) {
|
||||
if (ensure_io_stat_cached(*c) < 0)
|
||||
continue;
|
||||
|
||||
r = metric_build_send_unsigned(
|
||||
context,
|
||||
(*c)->unit,
|
||||
(*c)->io_rbytes,
|
||||
/* fields= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int io_read_operations_build_json(MetricFamilyContext *context, void *userdata) {
|
||||
CGroupContext *ctx = ASSERT_PTR(userdata);
|
||||
CGroupInfo **cgroups;
|
||||
size_t n_cgroups;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
|
||||
r = walk_cgroups(ctx, &cgroups, &n_cgroups);
|
||||
if (r < 0)
|
||||
return 0;
|
||||
|
||||
FOREACH_ARRAY(c, cgroups, n_cgroups) {
|
||||
if (ensure_io_stat_cached(*c) < 0)
|
||||
continue;
|
||||
|
||||
r = metric_build_send_unsigned(
|
||||
context,
|
||||
(*c)->unit,
|
||||
(*c)->io_rios,
|
||||
/* fields= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tasks_current_build_json(MetricFamilyContext *context, void *userdata) {
|
||||
CGroupContext *ctx = ASSERT_PTR(userdata);
|
||||
CGroupInfo **cgroups;
|
||||
size_t n_cgroups;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
|
||||
r = walk_cgroups(ctx, &cgroups, &n_cgroups);
|
||||
if (r < 0)
|
||||
return 0;
|
||||
|
||||
FOREACH_ARRAY(c, cgroups, n_cgroups) {
|
||||
uint64_t val;
|
||||
|
||||
r = cg_get_attribute_as_uint64((*c)->path, "pids.current", &val);
|
||||
if (r < 0)
|
||||
continue;
|
||||
|
||||
r = metric_build_send_unsigned(
|
||||
context,
|
||||
(*c)->unit,
|
||||
val,
|
||||
/* fields= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const MetricFamily cgroup_metric_family_table[] = {
|
||||
/* Keep metrics ordered alphabetically */
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "CpuUsage",
|
||||
.description = "Per unit metric: CPU usage in nanoseconds",
|
||||
.type = METRIC_FAMILY_TYPE_COUNTER,
|
||||
.generate = cpu_usage_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadBytes",
|
||||
.description = "Per unit metric: IO bytes read",
|
||||
.type = METRIC_FAMILY_TYPE_COUNTER,
|
||||
.generate = io_read_bytes_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadOperations",
|
||||
.description = "Per unit metric: IO read operations",
|
||||
.type = METRIC_FAMILY_TYPE_COUNTER,
|
||||
.generate = io_read_operations_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "MemoryUsage",
|
||||
.description = "Per unit metric: memory usage in bytes",
|
||||
.type = METRIC_FAMILY_TYPE_GAUGE,
|
||||
.generate = memory_usage_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "TasksCurrent",
|
||||
.description = "Per unit metric: current number of tasks",
|
||||
.type = METRIC_FAMILY_TYPE_GAUGE,
|
||||
.generate = tasks_current_build_json,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return metrics_method_describe(cgroup_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) {
|
||||
CGroupContext *ctx = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = metrics_method_list(cgroup_metric_family_table, link, parameters, flags, userdata);
|
||||
|
||||
cgroup_context_flush(ctx);
|
||||
|
||||
return r;
|
||||
}
|
||||
20
src/report/report-cgroup.h
Normal file
20
src/report/report-cgroup.h
Normal file
@ -0,0 +1,20 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "shared-forward.h"
|
||||
|
||||
#define METRIC_IO_SYSTEMD_CGROUP_PREFIX "io.systemd.CGroup."
|
||||
|
||||
typedef struct CGroupInfo CGroupInfo;
|
||||
|
||||
typedef struct CGroupContext {
|
||||
CGroupInfo **cgroups;
|
||||
size_t n_cgroups;
|
||||
bool cache_populated;
|
||||
} CGroupContext;
|
||||
|
||||
CGroupContext *cgroup_context_free(CGroupContext *ctx);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(CGroupContext*, cgroup_context_free);
|
||||
|
||||
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);
|
||||
@ -100,7 +100,8 @@ static int dnssec_rsa_verify_raw(
|
||||
return -EIO;
|
||||
e = m = NULL;
|
||||
|
||||
assert((size_t) RSA_size(rpubkey) == signature_size);
|
||||
if ((size_t) RSA_size(rpubkey) != signature_size)
|
||||
return -EINVAL;
|
||||
|
||||
epubkey = EVP_PKEY_new();
|
||||
if (!epubkey)
|
||||
@ -230,9 +231,11 @@ static int dnssec_ecdsa_verify_raw(
|
||||
|
||||
if (EC_KEY_set_public_key(eckey, p) <= 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"EC_POINT_bn2point failed: 0x%lx", ERR_get_error());
|
||||
"EC_KEY_set_public_key failed: 0x%lx", ERR_get_error());
|
||||
|
||||
assert(EC_KEY_check_key(eckey) == 1);
|
||||
if (EC_KEY_check_key(eckey) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"EC_KEY_check_key failed: 0x%lx", ERR_get_error());
|
||||
|
||||
r = BN_bin2bn(signature_r, signature_r_size, NULL);
|
||||
if (!r)
|
||||
|
||||
@ -1036,7 +1036,8 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) {
|
||||
dns_server_unref(m->current_dns_server);
|
||||
m->current_dns_server = dns_server_ref(s);
|
||||
|
||||
if (m->unicast_scope)
|
||||
/* Skip flushing the cache if server stale feature is enabled. */
|
||||
if (m->unicast_scope && m->stale_retention_usec == 0)
|
||||
dns_cache_flush(&m->unicast_scope->cache);
|
||||
|
||||
(void) manager_send_changed(m, "CurrentDNSServer");
|
||||
@ -1155,6 +1156,10 @@ void dns_server_flush_cache(DnsServer *s) {
|
||||
if (!scope)
|
||||
return;
|
||||
|
||||
/* Skip flushing the cache if server stale feature is enabled. */
|
||||
if (s->manager->stale_retention_usec > 0)
|
||||
return;
|
||||
|
||||
dns_cache_flush(&scope->cache);
|
||||
}
|
||||
|
||||
|
||||
@ -230,6 +230,7 @@ shared_sources = files(
|
||||
'varlink-io.systemd.Resolve.c',
|
||||
'varlink-io.systemd.Resolve.Hook.c',
|
||||
'varlink-io.systemd.Resolve.Monitor.c',
|
||||
'varlink-io.systemd.Shutdown.c',
|
||||
'varlink-io.systemd.Udev.c',
|
||||
'varlink-io.systemd.Unit.c',
|
||||
'varlink-io.systemd.UserDatabase.c',
|
||||
|
||||
57
src/shared/varlink-io.systemd.Shutdown.c
Normal file
57
src/shared/varlink-io.systemd.Shutdown.c
Normal file
@ -0,0 +1,57 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "bus-polkit.h"
|
||||
#include "varlink-io.systemd.Shutdown.h"
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
PowerOff,
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
|
||||
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
Reboot,
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
|
||||
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
Halt,
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
|
||||
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
KExec,
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
|
||||
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
SoftReboot,
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
|
||||
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(AlreadyInProgress);
|
||||
static SD_VARLINK_DEFINE_ERROR(
|
||||
BlockedByInhibitor,
|
||||
SD_VARLINK_FIELD_COMMENT("Who is holding the inhibitor"),
|
||||
SD_VARLINK_DEFINE_FIELD(who, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Why the inhibitor is held"),
|
||||
SD_VARLINK_DEFINE_FIELD(why, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
|
||||
|
||||
SD_VARLINK_DEFINE_INTERFACE(
|
||||
io_systemd_Shutdown,
|
||||
"io.systemd.Shutdown",
|
||||
SD_VARLINK_INTERFACE_COMMENT("APIs for shutting down or rebooting the system."),
|
||||
SD_VARLINK_SYMBOL_COMMENT("Power off the system"),
|
||||
&vl_method_PowerOff,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Reboot the system"),
|
||||
&vl_method_Reboot,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Halt the system"),
|
||||
&vl_method_Halt,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Reboot the system via kexec"),
|
||||
&vl_method_KExec,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Reboot userspace only"),
|
||||
&vl_method_SoftReboot,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Another shutdown or sleep operation is already in progress"),
|
||||
&vl_error_AlreadyInProgress,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Operation denied due to active block inhibitor"),
|
||||
&vl_error_BlockedByInhibitor);
|
||||
6
src/shared/varlink-io.systemd.Shutdown.h
Normal file
6
src/shared/varlink-io.systemd.Shutdown.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-varlink-idl.h"
|
||||
|
||||
extern const sd_varlink_interface vl_interface_io_systemd_Shutdown;
|
||||
@ -51,7 +51,7 @@ foreach p : out.stdout().split()
|
||||
#
|
||||
# Also, backslashes get mangled, so skip test. See
|
||||
# https://github.com/mesonbuild/meson/issues/1564.
|
||||
if p.contains('\\')
|
||||
if p.contains('\\') or not fs.exists(p)
|
||||
continue
|
||||
endif
|
||||
fuzzer = fs.name(fs.parent(p))
|
||||
|
||||
@ -30,6 +30,13 @@ REPORT=/usr/lib/systemd/systemd-report
|
||||
"$REPORT" describe-metrics io.systemd piff
|
||||
"$REPORT" describe-metrics piff
|
||||
|
||||
# test io.systemd.CGroup Metrics
|
||||
systemctl start systemd-report-cgroup.socket
|
||||
varlinkctl info /run/systemd/report/io.systemd.CGroup
|
||||
varlinkctl list-methods /run/systemd/report/io.systemd.CGroup
|
||||
varlinkctl --more call /run/systemd/report/io.systemd.CGroup io.systemd.Metrics.List {}
|
||||
varlinkctl --more call /run/systemd/report/io.systemd.CGroup io.systemd.Metrics.Describe {}
|
||||
|
||||
# test io.systemd.Network Metrics
|
||||
varlinkctl info /run/systemd/report/io.systemd.Network
|
||||
varlinkctl list-methods /run/systemd/report/io.systemd.Network
|
||||
|
||||
@ -6,6 +6,7 @@ endif
|
||||
|
||||
files = [['README' ],
|
||||
['home.conf' ],
|
||||
['root.conf' ],
|
||||
['journal-nocow.conf' ],
|
||||
['portables.conf', 'ENABLE_PORTABLED'],
|
||||
['systemd-network.conf', 'ENABLE_NETWORKD' ],
|
||||
|
||||
10
tmpfiles.d/root.conf
Normal file
10
tmpfiles.d/root.conf
Normal file
@ -0,0 +1,10 @@
|
||||
# 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.
|
||||
|
||||
# See tmpfiles.d(5) for details.
|
||||
|
||||
z / 555 - - -
|
||||
@ -724,6 +724,8 @@ units = [
|
||||
'file' : 'systemd-repart@.service',
|
||||
'conditions' : ['ENABLE_REPART'],
|
||||
},
|
||||
{ 'file' : 'systemd-report-cgroup.socket' },
|
||||
{ 'file' : 'systemd-report-cgroup@.service.in' },
|
||||
{
|
||||
'file' : 'systemd-resolved.service.in',
|
||||
'conditions' : ['ENABLE_RESOLVE'],
|
||||
|
||||
@ -13,7 +13,7 @@ Documentation=man:systemd-logind.service(8)
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/io.systemd.Login
|
||||
Symlinks=/run/varlink/registry/io.systemd.Login
|
||||
Symlinks=/run/varlink/registry/io.systemd.Login /run/varlink/registry/io.systemd.Shutdown /run/systemd/io.systemd.Shutdown
|
||||
FileDescriptorName=varlink
|
||||
SocketMode=0666
|
||||
Service=systemd-logind.service
|
||||
|
||||
25
units/systemd-report-cgroup.socket
Normal file
25
units/systemd-report-cgroup.socket
Normal file
@ -0,0 +1,25 @@
|
||||
# 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=CGroup Report Varlink Socket
|
||||
DefaultDependencies=no
|
||||
Before=sockets.target shutdown.target
|
||||
Conflicts=shutdown.target
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/report/io.systemd.CGroup
|
||||
FileDescriptorName=varlink
|
||||
SocketMode=0666
|
||||
Accept=yes
|
||||
MaxConnectionsPerSource=16
|
||||
RemoveOnStop=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
42
units/systemd-report-cgroup@.service.in
Normal file
42
units/systemd-report-cgroup@.service.in
Normal file
@ -0,0 +1,42 @@
|
||||
# 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=CGroup Report Service
|
||||
DefaultDependencies=no
|
||||
Conflicts=shutdown.target
|
||||
Before=shutdown.target
|
||||
|
||||
[Service]
|
||||
CapabilityBoundingSet=
|
||||
DeviceAllow=
|
||||
DynamicUser=yes
|
||||
IPAddressDeny=any
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
PrivateDevices=yes
|
||||
PrivateIPC=yes
|
||||
PrivateNetwork=yes
|
||||
PrivateTmp=disconnected
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_UNIX
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
RuntimeMaxSec=1min
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service
|
||||
ExecStart={{LIBEXECDIR}}/systemd-report-cgroup
|
||||
Loading…
x
Reference in New Issue
Block a user