1
0
mirror of https://github.com/systemd/systemd synced 2026-03-14 00:54:46 +01:00

Compare commits

..

11 Commits

Author SHA1 Message Date
Daan De Meyer
8b6d8ec66f
udev: fix performance regression in resolving event dependencies (#40364)
Fixes #39583.
Fixes #39817.
2026-02-05 14:14:47 +01:00
Daan De Meyer
d2f55e7079
mkosi: update mkosi ref to 215a9497ccc089ba030da39e15e9e2371efad3cb (#40573)
* 215a9497cc fedora: Use N-1 key as well when querying rawhide GPG key
* 842a37ed6c Add MakeScriptsExecutable= setting to optionally try to
make scripts executable before bailing out
* 814f2004bb build(deps): bump github/codeql-action from 4.31.9 to
4.32.0
* d8f4f628bf build(deps): bump actions/checkout from 6.0.1 to 6.0.2
* 3e55361142 docs: remove superfluous definition colon
* 5901524c48 mkosi-tools: add libarchive-tools package.
* 968392f1b9 docs: Add information about gui mkosi-tools profile
* 0e2960c245 Add missing call to run_locale_gen()
* 41cd2067bc rpm: Set pkgverify_level to digest
* 86fe0f448a dnf: Give advanced users some control over plugins
* 50a1feee52 run: Improve sandbox command logging
* b1dffe1c3c Fix environment variable name for systemd-repart
* 07726068d9 Allow specifying "default" value for Initrds=
* 704f163ec0 Allow setting PORTABLE_PREFIXES= via Environment=
* e6588afb45 opensuse: More GPG key handling fixes
* c367f993dd opensuse: Fetch remote keys as well if RepositoryKeyFetch=
is enabled
* 31852c9314 ci: Use mkosi box for unit test CI as well
* e4229f5bf5 Make sure we pass the right context to
finalize_default_initrd()
* 9b431b783a tools: don't pull in virtiofsd in bookworm tools trees
* ae2d88d463 build(deps): bump github/codeql-action from 4.31.6 to
4.31.9
* 933401a8b6 build(deps): bump actions/checkout from 6.0.0 to 6.0.1
* 6bfeb4ac86 opensuse: Import GPG keys for all repositories
* 9829b9136f Add support for locale-gen
* 63ae86ec04 nixos: Use repository key fetching by default on nixos
* f01ca9904b docs: Reword dependencies vs tools tree requirement a bit
* ab47ba25ef docs: Minor correction on enabling unprivileged namespaces
* 7bd46a417e docs: Update unprivileged user namespace docs
2026-02-05 14:06:45 +01:00
Daan De Meyer
42c0a25a21 mkosi: update mkosi ref to 14d2d37a1923c03062f55454b2b61d0c64db6238
* 14d2d37a19 sandbox: Make sure we're dumpable before writing uidmap files
* 215a9497cc fedora: Use N-1 key as well when querying rawhide GPG key
* 842a37ed6c Add MakeScriptsExecutable= setting to optionally try to make scripts executable before bailing out
* 814f2004bb build(deps): bump github/codeql-action from 4.31.9 to 4.32.0
* d8f4f628bf build(deps): bump actions/checkout from 6.0.1 to 6.0.2
* 3e55361142 docs: remove superfluous definition colon
* 5901524c48 mkosi-tools: add libarchive-tools package.
* 968392f1b9 docs: Add information about gui mkosi-tools profile
* 0e2960c245 Add missing call to run_locale_gen()
* 41cd2067bc rpm: Set pkgverify_level to digest
* 86fe0f448a dnf: Give advanced users some control over plugins
* 50a1feee52 run: Improve sandbox command logging
* b1dffe1c3c Fix environment variable name for systemd-repart
* 07726068d9 Allow specifying "default" value for Initrds=
* 704f163ec0 Allow setting PORTABLE_PREFIXES= via Environment=
* e6588afb45 opensuse: More GPG key handling fixes
* c367f993dd opensuse: Fetch remote keys as well if RepositoryKeyFetch= is enabled
* 31852c9314 ci: Use mkosi box for unit test CI as well
* e4229f5bf5 Make sure we pass the right context to finalize_default_initrd()
* 9b431b783a tools: don't pull in virtiofsd in bookworm tools trees
* ae2d88d463 build(deps): bump github/codeql-action from 4.31.6 to 4.31.9
* 933401a8b6 build(deps): bump actions/checkout from 6.0.0 to 6.0.1
* 6bfeb4ac86 opensuse: Import GPG keys for all repositories
* 9829b9136f Add support for locale-gen
* 63ae86ec04 nixos: Use repository key fetching by default on nixos
* f01ca9904b docs: Reword dependencies vs tools tree requirement a bit
* ab47ba25ef docs: Minor correction on enabling unprivileged namespaces
* 7bd46a417e docs: Update unprivileged user namespace docs
2026-02-05 13:10:35 +01:00
Daan De Meyer
7bb0bd400f fetch-mkosi: Shorten commit message title
Currently github truncates the message so let's make it a little shorter
so it's not truncated.
2026-02-05 10:53:09 +01:00
gvenugo3
8950f64263 sleep: allow HibernateDelaySec and low-battery hibernation to work together
Previously, setting HibernateDelaySec= would disable ACPI battery trip
point (_BTP) alarms, forcing the system to rely solely on software
polling for battery checks. This could result in the battery draining
to 0% between polling intervals, causing data loss.

Now, when ACPI _BTP is available AND HibernateDelaySec= is set, both
mechanisms work together. The system will hibernate on whichever comes
first: low battery (instant hardware alarm) or the configured timeout.

This also properly respects HibernateOnACPower=no by resetting the
timer while on AC power, matching the documented behavior.

Fixes: https://github.com/systemd/systemd/issues/26498
2026-02-05 10:28:47 +01:00
Zbigniew Jędrzejewski-Szmek
2ac64e5715
Initial tweaks for the report tool (#40568) 2026-02-05 10:15:39 +01:00
Zbigniew Jędrzejewski-Szmek
dffcb61f7f metrics/report: add log messages, do not hang if no metrics are found
If the directory existed but was empty, we'd hang in the event loop.
2026-02-04 18:20:11 +01:00
Zbigniew Jędrzejewski-Szmek
ab4028c47a metrics/report: use _cleanup_, avoid goto 2026-02-04 17:57:24 +01:00
Zbigniew Jędrzejewski-Szmek
7a1e761070 pid1: shorten message about jobs skipped after failed condition checks
I was looking at some logs on a console, and because of the lengthy message,
the actually interesting part, i.e. what condition failed, didn't even fit
on the screen. Let's make the sentence legible but brief.
2026-02-04 16:33:26 +01:00
Yu Watanabe
cb16d47f30 udev: do not build full list of dependencies
Suppose the following dependent events are queued:

A(1000) -> C(1002)
                   \
                     --> E(1004)
                   /
B(1001) -> D(1003)

Here, each number is the seqnum of the event, and A -> B means that
event B depends on event A, that is, event B cannot be started until
event A is processed.

In this case, to know if event E can be started, we only need to check
if event C and D are processed, and not necessary to check the state of
event A or B.

However, the commit e1ae931064be9483aa98249294f6e195537f43d1 introduced
full dependency list (in the above example, all A, B, C, D are listed as
the blocker for E), but it is overkill and most information is not necessary.
Also, before the commit e1ae931064be9483aa98249294f6e195537f43d1, we
found blocker from the beginning of the queued events, but that's also
not necessary, as even A is processed, still C may be queued, and we
anyway need to check if C is processed or not.

This makes each event only stores the last blocker event for the event,
and finds the blocker from the end of the queue. With this change, the
memory cost can be reduced from O(n^2) to O(n). Also, as previously we
used Set for managing blockers, but now we only increment/decrement
the reference counter of events, so the speed should be also improved.

Fixes #39583 and #39817.
2026-02-04 16:04:10 +09:00
Yu Watanabe
59bc3e7cd6 udev: move functions
Nothing changed except for the functions are moved.
Preparation for the next commit, where worker_detach_event() will be
used in on_worker_exit().
2026-02-04 16:04:10 +09:00
10 changed files with 248 additions and 204 deletions

View File

@ -25,7 +25,7 @@ jobs:
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: systemd/mkosi@e3642f81d3a7f8f9310c0c734b2ba9dd41e50e14 - uses: systemd/mkosi@14d2d37a1923c03062f55454b2b61d0c64db6238
# Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space
# immediately, we remove the files in the background. However, we first move them to a different location # immediately, we remove the files in the background. However, we first move them to a different location

View File

@ -39,7 +39,7 @@ jobs:
GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml
ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false
- uses: systemd/mkosi@e3642f81d3a7f8f9310c0c734b2ba9dd41e50e14 - uses: systemd/mkosi@14d2d37a1923c03062f55454b2b61d0c64db6238
- name: Check that tabs are not used in Python code - name: Check that tabs are not used in Python code
run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py' run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py'

View File

@ -167,7 +167,7 @@ jobs:
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: systemd/mkosi@e3642f81d3a7f8f9310c0c734b2ba9dd41e50e14 - uses: systemd/mkosi@14d2d37a1923c03062f55454b2b61d0c64db6238
# Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space
# immediately, we remove the files in the background. However, we first move them to a different location # immediately, we remove the files in the background. However, we first move them to a different location

View File

@ -96,12 +96,13 @@
<para>If the system has no battery, it would be hibernated after <varname>HibernateDelaySec=</varname> <para>If the system has no battery, it would be hibernated after <varname>HibernateDelaySec=</varname>
has passed. If not set, then defaults to <literal>2h</literal>.</para> has passed. If not set, then defaults to <literal>2h</literal>.</para>
<para>If the system has battery and <varname>HibernateDelaySec=</varname> is not set, low-battery <para>If the system has battery, low-battery alarms (ACPI _BTP) are tried first for detecting
alarms (ACPI _BTP) are tried first for detecting battery percentage and wake up the system for hibernation. battery percentage and wake up the system for hibernation. If <varname>HibernateDelaySec=</varname>
If not available, or <varname>HibernateDelaySec=</varname> is set, the system would regularly wake is also set, an additional timer is configured so that the system hibernates on whichever comes
up to check the time and detect the battery percentage/discharging rate. The rate is used to first: low battery or the configured delay. If ACPI _BTP is not available, the system would
schedule the next detection. If that is also not available, <varname>SuspendEstimationSec=</varname> regularly wake up to check the time and detect the battery percentage/discharging rate. The rate
is used as last resort.</para> is used to schedule the next detection. If that is also not available,
<varname>SuspendEstimationSec=</varname> is used as last resort.</para>
<xi:include href="version-info.xml" xpointer="v239"/> <xi:include href="version-info.xml" xpointer="v239"/>
</listitem> </listitem>

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later # SPDX-License-Identifier: LGPL-2.1-or-later
[Config] [Config]
MinimumVersion=commit:e3642f81d3a7f8f9310c0c734b2ba9dd41e50e14 MinimumVersion=commit:14d2d37a1923c03062f55454b2b61d0c64db6238
Dependencies= Dependencies=
initrd initrd
minimal-base minimal-base

View File

@ -787,7 +787,7 @@ static void job_emit_done_message(Unit *u, uint32_t job_id, JobType t, JobResult
log_unit_struct( log_unit_struct(
u, u,
job_done_messages[result].log_level, job_done_messages[result].log_level,
LOG_MESSAGE("%s was skipped because no trigger condition checks were met.", LOG_MESSAGE("%s skipped, no trigger condition checks were met.",
ident), ident),
LOG_ITEM("JOB_ID=%" PRIu32, job_id), LOG_ITEM("JOB_ID=%" PRIu32, job_id),
LOG_ITEM("JOB_TYPE=%s", job_type_to_string(t)), LOG_ITEM("JOB_TYPE=%s", job_type_to_string(t)),
@ -798,7 +798,7 @@ static void job_emit_done_message(Unit *u, uint32_t job_id, JobType t, JobResult
log_unit_struct( log_unit_struct(
u, u,
job_done_messages[result].log_level, job_done_messages[result].log_level,
LOG_MESSAGE("%s was skipped because of an unmet condition check (%s=%s%s).", LOG_MESSAGE("%s skipped, unmet condition check %s=%s%s",
ident, ident,
condition_type_to_string(c->type), condition_type_to_string(c->type),
c->negate ? "!" : "", c->negate ? "!" : "",

View File

@ -79,16 +79,13 @@ static int metrics_on_query_reply(
log_info("Varlink timed out"); log_info("Varlink timed out");
else else
log_error("Varlink error: %s", error_id); log_error("Varlink error: %s", error_id);
} else {
goto finish; /* Collect metrics for later sorting */
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
return log_oom();
context->metrics[context->n_metrics++] = sd_json_variant_ref(parameters);
} }
/* Collect metrics for later sorting */
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
return log_oom();
context->metrics[context->n_metrics++] = sd_json_variant_ref(parameters);
finish:
if (!FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) { if (!FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) {
assert(context->n_open_connections > 0); assert(context->n_open_connections > 0);
context->n_open_connections--; context->n_open_connections--;
@ -111,7 +108,7 @@ static int metrics_call(const char *path, sd_event *event, sd_varlink **ret, Con
r = sd_varlink_connect_address(&vl, path); r = sd_varlink_connect_address(&vl, path);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Unable to connect to %s: %m", path); return log_error_errno(r, "Unable to connect to %s: %m", path);
(void) sd_varlink_set_userdata(vl, context); (void) sd_varlink_set_userdata(vl, context);
@ -121,15 +118,15 @@ static int metrics_call(const char *path, sd_event *event, sd_varlink **ret, Con
r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m"); return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
r = sd_varlink_bind_reply(vl, metrics_on_query_reply); r = sd_varlink_bind_reply(vl, metrics_on_query_reply);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to bind reply callback: %m"); return log_error_errno(r, "Failed to bind reply callback: %m");
r = sd_varlink_observe(vl, "io.systemd.Metrics.List", /* parameters= */ NULL); r = sd_varlink_observe(vl, "io.systemd.Metrics.List", /* parameters= */ NULL);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to issue io.systemd.Metrics.List call: %m"); return log_error_errno(r, "Failed to issue io.systemd.Metrics.List call: %m");
*ret = TAKE_PTR(vl); *ret = TAKE_PTR(vl);
@ -156,9 +153,6 @@ static void context_done(Context *context) {
static void metrics_output_sorted(Context *context) { static void metrics_output_sorted(Context *context) {
assert(context); assert(context);
if (context->n_metrics == 0)
return;
typesafe_qsort(context->metrics, context->n_metrics, metric_compare); typesafe_qsort(context->metrics, context->n_metrics, metric_compare);
FOREACH_ARRAY(m, context->metrics, context->n_metrics) FOREACH_ARRAY(m, context->metrics, context->n_metrics)
@ -167,26 +161,27 @@ static void metrics_output_sorted(Context *context) {
SD_JSON_FORMAT_PRETTY_AUTO | SD_JSON_FORMAT_COLOR_AUTO | SD_JSON_FORMAT_FLUSH, SD_JSON_FORMAT_PRETTY_AUTO | SD_JSON_FORMAT_COLOR_AUTO | SD_JSON_FORMAT_FLUSH,
stdout, stdout,
NULL); NULL);
if (context->n_metrics == 0)
log_warning("No reporting sockets found.");
} }
static int metrics_query(void) { static int metrics_query(void) {
_cleanup_closedir_ DIR *d = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_free_ char *metrics_path = NULL;
int r; int r;
_cleanup_free_ char *metrics_path = NULL;
r = runtime_directory_generic(arg_runtime_scope, "systemd/report", &metrics_path); r = runtime_directory_generic(arg_runtime_scope, "systemd/report", &metrics_path);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to determine metrics directory path: %m"); return log_error_errno(r, "Failed to determine metrics directory path: %m");
d = opendir(metrics_path); log_debug("Looking for reports in %s/", metrics_path);
if (!d) {
if (errno == ENOENT)
return 0;
return log_error_errno(errno, "Failed to open directory %s: %m", metrics_path); _cleanup_closedir_ DIR *d = opendir(metrics_path);
} if (!d)
return log_full_errno(errno == ENOENT ? LOG_WARNING : LOG_ERR, errno,
"Failed to open metrics directory %s: %m", metrics_path);
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
r = sd_event_default(&event); r = sd_event_default(&event);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to get event loop: %m"); return log_error_errno(r, "Failed to get event loop: %m");
@ -198,27 +193,25 @@ static int metrics_query(void) {
size_t n_varlinks = MAX_CONCURRENT_METRICS_SOCKETS; size_t n_varlinks = MAX_CONCURRENT_METRICS_SOCKETS;
sd_varlink **varlinks = new0(sd_varlink *, n_varlinks); sd_varlink **varlinks = new0(sd_varlink *, n_varlinks);
if (!varlinks) if (!varlinks)
return log_error_errno(ENOMEM, "Failed to allocate varlinks array: %m"); return log_oom();
CLEANUP_ARRAY(varlinks, n_varlinks, sd_varlink_unref_many); CLEANUP_ARRAY(varlinks, n_varlinks, sd_varlink_unref_many);
Context context = {}; _cleanup_(context_done) Context context = {};
FOREACH_DIRENT(de, d, return -errno) { FOREACH_DIRENT(de, d,
_cleanup_free_ char *p = NULL; return log_warning_errno(errno, "Failed to read %s: %m", metrics_path)) {
if (!IN_SET(de->d_type, DT_SOCK, DT_UNKNOWN)) if (!IN_SET(de->d_type, DT_SOCK, DT_UNKNOWN))
continue; continue;
p = path_join(metrics_path, de->d_name); _cleanup_free_ char *p = path_join(metrics_path, de->d_name);
if (!p) if (!p)
return log_oom(); return log_oom();
r = metrics_call(p, event, &varlinks[context.n_open_connections], &context); r = metrics_call(p, event, &varlinks[context.n_open_connections], &context);
if (r < 0) { if (r < 0)
log_error_errno(r, "Failed to connect to %s: %m", p);
continue; continue;
}
if (++context.n_open_connections >= MAX_CONCURRENT_METRICS_SOCKETS) { if (++context.n_open_connections >= MAX_CONCURRENT_METRICS_SOCKETS) {
log_warning("Too many concurrent metrics sockets, stop iterating"); log_warning("Too many concurrent metrics sockets, stop iterating");
@ -226,16 +219,14 @@ static int metrics_query(void) {
} }
} }
r = sd_event_loop(event); if (context.n_open_connections > 0) {
if (r < 0) { r = sd_event_loop(event);
context_done(&context); if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m"); return log_error_errno(r, "Failed to run event loop: %m");
} }
metrics_output_sorted(&context); metrics_output_sorted(&context);
context_done(&context);
return 0; return 0;
} }
@ -318,11 +309,7 @@ static int run(int argc, char *argv[]) {
if (r <= 0) if (r <= 0)
return r; return r;
r = metrics_query(); return metrics_query();
if (r < 0)
return r;
return 0;
} }
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

View File

@ -507,37 +507,90 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) {
} }
static int execute_s2h(const SleepConfig *sleep_config) { static int execute_s2h(const SleepConfig *sleep_config) {
_cleanup_close_ int tfd = -EBADF;
usec_t hibernate_timestamp = 0;
int r; int r;
assert(sleep_config); assert(sleep_config);
/* Only check if we have automated battery alarms if HibernateDelaySec= is not set, as in that case /* Always check if we have automated battery alarms, regardless of HibernateDelaySec= setting.
* we'll busy poll for the configured interval instead */ * This allows both low-battery hibernation AND timeout-based hibernation to work together. */
if (!timestamp_is_set(sleep_config->hibernate_delay_usec)) { r = check_wakeup_type();
r = check_wakeup_type(); if (r < 0)
log_warning_errno(r, "Failed to check hardware wakeup type, ignoring: %m");
else {
r = battery_trip_point_alarm_exists();
if (r < 0) if (r < 0)
log_warning_errno(r, "Failed to check hardware wakeup type, ignoring: %m"); log_warning_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m");
else { }
r = battery_trip_point_alarm_exists();
if (r < 0) if (r > 0) {
log_warning_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m"); /* We have hardware battery alarm support (ACPI _BTP). If HibernateDelaySec= is also set,
* set up an RTC alarm so we hibernate on whichever comes first: low battery or timeout. */
if (timestamp_is_set(sleep_config->hibernate_delay_usec)) {
tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC);
if (tfd < 0)
return log_error_errno(errno, "Error creating timerfd: %m");
hibernate_timestamp = usec_add(now(CLOCK_BOOTTIME), sleep_config->hibernate_delay_usec);
} }
} else
r = 0; /* Force fallback path */
if (r > 0) { /* If we have both wakeup alarms and battery trip point support, use them */ for (;;) {
log_debug("Attempting to suspend..."); if (tfd >= 0) {
r = execute(sleep_config, SLEEP_SUSPEND, NULL); struct itimerspec ts = {};
if (r < 0) usec_t time_left;
return r;
r = check_wakeup_type(); /* Handle HibernateOnACPower=no: reset timer while on AC power */
if (r < 0) if (!sleep_config->hibernate_on_ac_power && on_ac_power() > 0) {
return log_error_errno(r, "Failed to check hardware wakeup type: %m"); log_debug("On AC power with HibernateOnACPower=no, resetting hibernate timer");
hibernate_timestamp = usec_add(now(CLOCK_BOOTTIME), sleep_config->hibernate_delay_usec);
}
if (r == 0) time_left = usec_sub_unsigned(hibernate_timestamp, now(CLOCK_BOOTTIME));
/* For APM Timer wakeup, system should hibernate else wakeup */ if (time_left <= 0)
break; /* Timer expired, hibernate */
log_debug("Set timerfd wake alarm for %s",
FORMAT_TIMESPAN(time_left, USEC_PER_SEC));
timespec_store(&ts.it_value, time_left);
if (timerfd_settime(tfd, 0, &ts, NULL) < 0)
return log_error_errno(errno, "Error setting hibernate delay timer: %m");
}
log_debug("Attempting to suspend...");
r = execute(sleep_config, SLEEP_SUSPEND, NULL);
if (r < 0)
return r;
r = check_wakeup_type();
if (r < 0)
return log_error_errno(r, "Failed to check hardware wakeup type: %m");
if (r > 0) {
/* APM Timer wakeup - this means the battery alarm triggered, hibernate */
log_debug("Woken by APM Timer (battery low), proceeding to hibernate");
break;
}
if (tfd >= 0) {
/* Check if our HibernateDelaySec timer fired */
r = fd_wait_for_event(tfd, POLLIN, 0);
if (r < 0)
return log_error_errno(r, "Error polling timerfd: %m");
if (FLAGS_SET(r, POLLIN)) {
/* Timer fired - but respect HibernateOnACPower setting */
if (!sleep_config->hibernate_on_ac_power && on_ac_power() > 0) {
log_debug("Timer fired but on AC power with HibernateOnACPower=no, continuing suspend");
continue;
}
log_debug("HibernateDelaySec timeout reached, proceeding to hibernate");
break;
}
}
/* Manual wakeup - not battery alarm and not timer */
return 0; return 0;
}
} else { } else {
r = custom_timer_suspend(sleep_config); r = custom_timer_suspend(sleep_config);
if (r < 0) if (r < 0)

View File

@ -53,9 +53,14 @@ typedef enum EventState {
EVENT_QUEUED, EVENT_QUEUED,
EVENT_RUNNING, EVENT_RUNNING,
EVENT_LOCKED, EVENT_LOCKED,
EVENT_PROCESSED,
} EventState; } EventState;
typedef struct Event { typedef struct Event {
/* All events that have not been processed (state != EVENT_PROCESSED) are referenced by the Manager.
* Additionally, an event may be referenced by events blocked by this event. See event_find_blocker(). */
unsigned n_ref;
Manager *manager; Manager *manager;
Worker *worker; Worker *worker;
EventState state; EventState state;
@ -76,9 +81,8 @@ typedef struct Event {
char *whole_disk; char *whole_disk;
LIST_FIELDS(Event, same_disk); LIST_FIELDS(Event, same_disk);
bool dependencies_built; /* The last blocker for this event. This event must not be processed before the blocker is processed. */
Set *blocker_events; Event *blocker;
Set *blocking_events;
LIST_FIELDS(Event, event); LIST_FIELDS(Event, event);
} Event; } Event;
@ -101,21 +105,6 @@ typedef struct Worker {
Event *event; Event *event;
} Worker; } Worker;
static void event_clear_dependencies(Event *event) {
assert(event);
Event *e;
while ((e = set_steal_first(event->blocker_events)))
assert_se(set_remove(e->blocking_events, event) == event);
event->blocker_events = set_free(event->blocker_events);
while ((e = set_steal_first(event->blocking_events)))
assert_se(set_remove(e->blocker_events, event) == event);
event->blocking_events = set_free(event->blocking_events);
event->dependencies_built = false;
}
static void event_unset_whole_disk(Event *event) { static void event_unset_whole_disk(Event *event) {
Manager *manager = ASSERT_PTR(ASSERT_PTR(event)->manager); Manager *manager = ASSERT_PTR(ASSERT_PTR(event)->manager);
@ -140,6 +129,10 @@ static void event_unset_whole_disk(Event *event) {
event->whole_disk = mfree(event->whole_disk); event->whole_disk = mfree(event->whole_disk);
} }
static Event* event_free(Event *event);
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(Event, event, event_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(Event*, event_unref);
static Event* event_free(Event *event) { static Event* event_free(Event *event) {
if (!event) if (!event)
return NULL; return NULL;
@ -156,14 +149,25 @@ static Event* event_free(Event *event) {
if (event->worker) if (event->worker)
event->worker->event = NULL; event->worker->event = NULL;
event_clear_dependencies(event); event_unref(event->blocker);
sd_device_unref(event->dev); sd_device_unref(event->dev);
return mfree(event); return mfree(event);
} }
DEFINE_TRIVIAL_CLEANUP_FUNC(Event*, event_free); static Event* event_enter_processed(Event *event) {
if (!event)
return NULL;
if (event->state == EVENT_PROCESSED)
return NULL;
event->state = EVENT_PROCESSED;
return event_unref(event);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(Event*, event_enter_processed);
static Worker* worker_free(Worker *worker) { static Worker* worker_free(Worker *worker) {
if (!worker) if (!worker)
@ -176,7 +180,6 @@ static Worker* worker_free(Worker *worker) {
sd_event_source_unref(worker->timeout_warning_event_source); sd_event_source_unref(worker->timeout_warning_event_source);
sd_event_source_unref(worker->timeout_kill_event_source); sd_event_source_unref(worker->timeout_kill_event_source);
pidref_done(&worker->pidref); pidref_done(&worker->pidref);
event_free(worker->event);
return mfree(worker); return mfree(worker);
} }
@ -200,8 +203,8 @@ Manager* manager_free(Manager *manager) {
udev_rules_free(manager->rules); udev_rules_free(manager->rules);
hashmap_free(manager->workers); hashmap_free(manager->workers);
while (manager->events) LIST_FOREACH(event, event, manager->events)
event_free(manager->events); event_enter_processed(event);
prioq_free(manager->locked_events_by_time); prioq_free(manager->locked_events_by_time);
hashmap_free(manager->locked_events_by_disk); hashmap_free(manager->locked_events_by_disk);
@ -392,9 +395,85 @@ void manager_revert(Manager *manager) {
manager_kill_workers(manager, SIGTERM); manager_kill_workers(manager, SIGTERM);
} }
static int on_worker_timeout_kill(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
Manager *manager = ASSERT_PTR(worker->manager);
Event *event = ASSERT_PTR(worker->event);
(void) pidref_kill_and_sigcont(&worker->pidref, manager->config.timeout_signal);
worker->state = WORKER_KILLED;
log_device_error(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" killed.", worker->pidref.pid, event->seqnum);
return 0;
}
static int on_worker_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
Event *event = ASSERT_PTR(worker->event);
log_device_warning(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" is taking a long time.", worker->pidref.pid, event->seqnum);
return 0;
}
static void worker_attach_event(Worker *worker, Event *event) {
Manager *manager = ASSERT_PTR(ASSERT_PTR(worker)->manager);
assert(event);
assert(event->state == EVENT_QUEUED);
assert(!event->worker);
assert(IN_SET(worker->state, WORKER_UNDEF, WORKER_IDLE));
assert(!worker->event);
worker->state = WORKER_RUNNING;
worker->event = event;
event->state = EVENT_RUNNING;
event->worker = worker;
(void) event_reset_time_relative(
manager->event,
&worker->timeout_warning_event_source,
CLOCK_MONOTONIC,
udev_warn_timeout(manager->config.timeout_usec),
USEC_PER_SEC,
on_worker_timeout_warning,
worker,
EVENT_PRIORITY_WORKER_TIMER,
"worker-timeout-warn",
/* force_reset= */ true);
(void) event_reset_time_relative(
manager->event,
&worker->timeout_kill_event_source,
CLOCK_MONOTONIC,
manager_kill_worker_timeout(manager),
USEC_PER_SEC,
on_worker_timeout_kill,
worker,
EVENT_PRIORITY_WORKER_TIMER,
"worker-timeout-kill",
/* force_reset= */ true);
}
static Event* worker_detach_event(Worker *worker) {
assert(worker);
Event *event = TAKE_PTR(worker->event);
if (event)
assert_se(TAKE_PTR(event->worker) == worker);
if (worker->state != WORKER_KILLED)
worker->state = WORKER_IDLE;
(void) event_source_disable(worker->timeout_warning_event_source);
(void) event_source_disable(worker->timeout_kill_event_source);
return event;
}
static int on_worker_exit(sd_event_source *s, const siginfo_t *si, void *userdata) { static int on_worker_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(worker_freep) Worker *worker = ASSERT_PTR(userdata); _cleanup_(worker_freep) Worker *worker = ASSERT_PTR(userdata);
sd_device *dev = worker->event ? ASSERT_PTR(worker->event->dev) : NULL; _cleanup_(event_enter_processedp) Event *event = worker_detach_event(worker);
sd_device *dev = event ? ASSERT_PTR(event->dev) : NULL;
assert(si); assert(si);
@ -472,81 +551,6 @@ static int worker_new(Worker **ret, Manager *manager, sd_device_monitor *worker_
return 0; return 0;
} }
static int on_worker_timeout_kill(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
Manager *manager = ASSERT_PTR(worker->manager);
Event *event = ASSERT_PTR(worker->event);
(void) pidref_kill_and_sigcont(&worker->pidref, manager->config.timeout_signal);
worker->state = WORKER_KILLED;
log_device_error(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" killed.", worker->pidref.pid, event->seqnum);
return 0;
}
static int on_worker_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
Event *event = ASSERT_PTR(worker->event);
log_device_warning(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" is taking a long time.", worker->pidref.pid, event->seqnum);
return 0;
}
static void worker_attach_event(Worker *worker, Event *event) {
Manager *manager = ASSERT_PTR(ASSERT_PTR(worker)->manager);
assert(event);
assert(event->state == EVENT_QUEUED);
assert(!event->worker);
assert(IN_SET(worker->state, WORKER_UNDEF, WORKER_IDLE));
assert(!worker->event);
worker->state = WORKER_RUNNING;
worker->event = event;
event->state = EVENT_RUNNING;
event->worker = worker;
(void) event_reset_time_relative(
manager->event,
&worker->timeout_warning_event_source,
CLOCK_MONOTONIC,
udev_warn_timeout(manager->config.timeout_usec),
USEC_PER_SEC,
on_worker_timeout_warning,
worker,
EVENT_PRIORITY_WORKER_TIMER,
"worker-timeout-warn",
/* force_reset= */ true);
(void) event_reset_time_relative(
manager->event,
&worker->timeout_kill_event_source,
CLOCK_MONOTONIC,
manager_kill_worker_timeout(manager),
USEC_PER_SEC,
on_worker_timeout_kill,
worker,
EVENT_PRIORITY_WORKER_TIMER,
"worker-timeout-kill",
/* force_reset= */ true);
}
static Event* worker_detach_event(Worker *worker) {
assert(worker);
Event *event = TAKE_PTR(worker->event);
if (event)
assert_se(TAKE_PTR(event->worker) == worker);
if (worker->state != WORKER_KILLED)
worker->state = WORKER_IDLE;
(void) event_source_disable(worker->timeout_warning_event_source);
(void) event_source_disable(worker->timeout_kill_event_source);
return event;
}
static int worker_spawn(Manager *manager, Event *event) { static int worker_spawn(Manager *manager, Event *event) {
int r; int r;
@ -649,17 +653,21 @@ bool devpath_conflict(const char *a, const char *b) {
return *a == '/' || *b == '/' || *a == *b; return *a == '/' || *b == '/' || *a == *b;
} }
static int event_build_dependencies(Event *event) { static void event_find_blocker(Event *event) {
int r;
assert(event); assert(event);
/* lookup event for identical, parent, child device */ /* lookup event for identical, parent, child device */
if (event->dependencies_built) if (event->blocker && event->blocker->state != EVENT_PROCESSED)
return 0; /* Previously found blocker is not processed yet. */
return;
/* If we have not found blocker yet, or the previously found blocker has been processed, let's find
* (another) blocker for this event. */
LIST_FOREACH_BACKWARDS(event, e, (event->blocker ?: event)->event_prev) {
if (e->state == EVENT_PROCESSED)
continue;
LIST_FOREACH_BACKWARDS(event, e, event->event_prev) {
if (!streq_ptr(event->id, e->id) && if (!streq_ptr(event->id, e->id) &&
!devpath_conflict(event->devpath, e->devpath) && !devpath_conflict(event->devpath, e->devpath) &&
!devpath_conflict(event->devpath, e->devpath_old) && !devpath_conflict(event->devpath, e->devpath_old) &&
@ -667,22 +675,16 @@ static int event_build_dependencies(Event *event) {
!(event->devnode && streq_ptr(event->devnode, e->devnode))) !(event->devnode && streq_ptr(event->devnode, e->devnode)))
continue; continue;
r = set_ensure_put(&event->blocker_events, NULL, e);
if (r < 0)
return r;
r = set_ensure_put(&e->blocking_events, NULL, event);
if (r < 0) {
assert_se(set_remove(event->blocker_events, e) == e);
return r;
}
log_device_debug(event->dev, "SEQNUM=%" PRIu64 " blocked by SEQNUM=%" PRIu64, log_device_debug(event->dev, "SEQNUM=%" PRIu64 " blocked by SEQNUM=%" PRIu64,
event->seqnum, e->seqnum); event->seqnum, e->seqnum);
unref_and_replace_full(event->blocker, e, event_ref, event_unref);
return;
} }
event->dependencies_built = true; /* No new blocker is found, and if set, previously found blocker has been processed. Clear the
return 0; * previous blocker if set. */
event->blocker = event_unref(event->blocker);
} }
static bool manager_can_process_event(Manager *manager) { static bool manager_can_process_event(Manager *manager) {
@ -739,14 +741,10 @@ static int event_queue_start(Manager *manager) {
if (event->state != EVENT_QUEUED) if (event->state != EVENT_QUEUED)
continue; continue;
r = event_build_dependencies(event); event_find_blocker(event);
if (r < 0)
log_device_warning_errno(event->dev, r,
"Failed to check dependencies for event (SEQNUM=%"PRIu64", ACTION=%s), ignoring: %m",
event->seqnum, strna(device_action_to_string(event->action)));
/* do not start event if parent or child event is still running or queued */ /* do not start event if parent or child event is still running or queued */
if (!set_isempty(event->blocker_events)) if (event->blocker)
continue; continue;
r = event_run(event); r = event_run(event);
@ -927,11 +925,12 @@ static int event_queue_insert(Manager *manager, sd_device *dev) {
if (r < 0 && r != -ENOENT) if (r < 0 && r != -ENOENT)
return r; return r;
_cleanup_(event_freep) Event *event = new(Event, 1); _cleanup_(event_unrefp) Event *event = new(Event, 1);
if (!event) if (!event)
return -ENOMEM; return -ENOMEM;
*event = (Event) { *event = (Event) {
.n_ref = 1,
.dev = sd_device_ref(dev), .dev = sd_device_ref(dev),
.seqnum = seqnum, .seqnum = seqnum,
.action = action, .action = action,
@ -943,6 +942,9 @@ static int event_queue_insert(Manager *manager, sd_device *dev) {
.locked_event_prioq_index = PRIOQ_IDX_NULL, .locked_event_prioq_index = PRIOQ_IDX_NULL,
}; };
/* The kernel sometimes sends events in a wrong order, and we may receive an event with smaller
* SEQNUM after one with larger SEQNUM. To workaround the issue, let's reorder events if necessary. */
Event *prev = NULL; Event *prev = NULL;
LIST_FOREACH_BACKWARDS(event, e, manager->last_event) { LIST_FOREACH_BACKWARDS(event, e, manager->last_event) {
if (e->seqnum < event->seqnum) { if (e->seqnum < event->seqnum) {
@ -954,8 +956,9 @@ static int event_queue_insert(Manager *manager, sd_device *dev) {
"The event (SEQNUM=%"PRIu64") has been already queued.", "The event (SEQNUM=%"PRIu64") has been already queued.",
event->seqnum); event->seqnum);
/* Inserting an event in an earlier place may change dependency tree. Let's rebuild it later. */ /* The inserted event may be a blocker of an already queued event, hence the already found
event_clear_dependencies(e); * blocker may not be the last one. Let's find the last blocker again later. */
e->blocker = event_unref(e->blocker);
} }
LIST_INSERT_AFTER(event, manager->events, prev, event); LIST_INSERT_AFTER(event, manager->events, prev, event);
@ -1211,7 +1214,7 @@ static int on_worker_notify(sd_event_source *s, int fd, uint32_t revents, void *
return 0; return 0;
} }
_cleanup_(event_freep) Event *event = worker_detach_event(worker); _cleanup_(event_enter_processedp) Event *event = worker_detach_event(worker);
if (strv_contains(l, "TRY_AGAIN=1")) { if (strv_contains(l, "TRY_AGAIN=1")) {
/* Worker cannot lock the device. */ /* Worker cannot lock the device. */

View File

@ -43,7 +43,7 @@ def read_config():
def commit_file(files: list[Path], commit: str, changes: str): def commit_file(files: list[Path], commit: str, changes: str):
message = '\n'.join(( message = '\n'.join((
f'mkosi: update mkosi commit reference to {commit}', f'mkosi: update mkosi ref to {commit}',
'', '',
changes)) changes))