mirror of
https://github.com/systemd/systemd
synced 2026-03-14 00:54:46 +01:00
Compare commits
11 Commits
e047394797
...
8b6d8ec66f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b6d8ec66f | ||
|
|
d2f55e7079 | ||
|
|
42c0a25a21 | ||
|
|
7bb0bd400f | ||
|
|
8950f64263 | ||
|
|
2ac64e5715 | ||
|
|
dffcb61f7f | ||
|
|
ab4028c47a | ||
|
|
7a1e761070 | ||
|
|
cb16d47f30 | ||
|
|
59bc3e7cd6 |
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@ -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
|
||||||
|
|||||||
2
.github/workflows/linter.yml
vendored
2
.github/workflows/linter.yml
vendored
@ -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'
|
||||||
|
|||||||
2
.github/workflows/mkosi.yml
vendored
2
.github/workflows/mkosi.yml
vendored
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 ? "!" : "",
|
||||||
|
|||||||
@ -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 */
|
/* Collect metrics for later sorting */
|
||||||
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
|
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
|
||||||
return log_oom();
|
return log_oom();
|
||||||
context->metrics[context->n_metrics++] = sd_json_variant_ref(parameters);
|
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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.n_open_connections > 0) {
|
||||||
r = sd_event_loop(event);
|
r = sd_event_loop(event);
|
||||||
if (r < 0) {
|
if (r < 0)
|
||||||
context_done(&context);
|
|
||||||
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);
|
||||||
|
|||||||
@ -507,13 +507,14 @@ 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)
|
if (r < 0)
|
||||||
log_warning_errno(r, "Failed to check hardware wakeup type, ignoring: %m");
|
log_warning_errno(r, "Failed to check hardware wakeup type, ignoring: %m");
|
||||||
@ -522,10 +523,41 @@ static int execute_s2h(const SleepConfig *sleep_config) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
log_warning_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m");
|
log_warning_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m");
|
||||||
}
|
}
|
||||||
} else
|
|
||||||
r = 0; /* Force fallback path */
|
|
||||||
|
|
||||||
if (r > 0) { /* If we have both wakeup alarms and battery trip point support, use them */
|
if (r > 0) {
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (tfd >= 0) {
|
||||||
|
struct itimerspec ts = {};
|
||||||
|
usec_t time_left;
|
||||||
|
|
||||||
|
/* Handle HibernateOnACPower=no: reset timer while on AC power */
|
||||||
|
if (!sleep_config->hibernate_on_ac_power && on_ac_power() > 0) {
|
||||||
|
log_debug("On AC power with HibernateOnACPower=no, resetting hibernate timer");
|
||||||
|
hibernate_timestamp = usec_add(now(CLOCK_BOOTTIME), sleep_config->hibernate_delay_usec);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_left = usec_sub_unsigned(hibernate_timestamp, now(CLOCK_BOOTTIME));
|
||||||
|
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...");
|
log_debug("Attempting to suspend...");
|
||||||
r = execute(sleep_config, SLEEP_SUSPEND, NULL);
|
r = execute(sleep_config, SLEEP_SUSPEND, NULL);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
@ -534,10 +566,31 @@ static int execute_s2h(const SleepConfig *sleep_config) {
|
|||||||
r = check_wakeup_type();
|
r = check_wakeup_type();
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to check hardware wakeup type: %m");
|
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 (r == 0)
|
if (tfd >= 0) {
|
||||||
/* For APM Timer wakeup, system should hibernate else wakeup */
|
/* 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)
|
||||||
|
|||||||
@ -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. */
|
||||||
|
|||||||
@ -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))
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user