logind: cancel long-press timers when the Button is freed
The four *_long_press_event_source timers are Manager-scoped but armed
by start_long_press() with the specific Button that started the press as
their userdata. button_free() unrefs only the Button's own io and check
event sources, never these timers. If the input device backing the
Button is unplugged while a long press is in progress,
manager_process_button_device()'s REMOVE branch calls button_free() and
the still-armed timer fires up to LONG_PRESS_DURATION (5s) later,
dereferencing the freed Button as userdata (b->manager, b->seat).
Cancel each long-press timer the Button owns (matched by userdata) in
button_free().
Also unref all four sources in manager_free() instead of just the reboot
one, for symmetry at daemon teardown.
Follow-up for 952b26c75d
This commit is contained in:
@@ -88,12 +88,28 @@ Button* button_new(Manager *m, const char *name) {
|
||||
return b;
|
||||
}
|
||||
|
||||
static void stop_long_press(Button *b, sd_event_source **e) {
|
||||
assert(b);
|
||||
assert(e);
|
||||
|
||||
/* The long press timers are Manager-scoped but armed with the Button that started the
|
||||
* press as userdata (see start_long_press()). Cancel the one this Button owns before it is
|
||||
* freed. */
|
||||
if (*e && sd_event_source_get_userdata(*e) == b)
|
||||
*e = sd_event_source_unref(*e);
|
||||
}
|
||||
|
||||
Button *button_free(Button *b) {
|
||||
if (!b)
|
||||
return NULL;
|
||||
|
||||
hashmap_remove(b->manager->buttons, b->name);
|
||||
|
||||
stop_long_press(b, &b->manager->power_key_long_press_event_source);
|
||||
stop_long_press(b, &b->manager->reboot_key_long_press_event_source);
|
||||
stop_long_press(b, &b->manager->suspend_key_long_press_event_source);
|
||||
stop_long_press(b, &b->manager->hibernate_key_long_press_event_source);
|
||||
|
||||
sd_event_source_unref(b->io_event_source);
|
||||
sd_event_source_unref(b->check_event_source);
|
||||
|
||||
|
||||
@@ -141,7 +141,10 @@ static Manager* manager_free(Manager *m) {
|
||||
sd_event_source_unref(m->console_active_event_source);
|
||||
sd_event_source_unref(m->lid_switch_ignore_event_source);
|
||||
|
||||
sd_event_source_unref(m->power_key_long_press_event_source);
|
||||
sd_event_source_unref(m->reboot_key_long_press_event_source);
|
||||
sd_event_source_unref(m->suspend_key_long_press_event_source);
|
||||
sd_event_source_unref(m->hibernate_key_long_press_event_source);
|
||||
|
||||
#if ENABLE_UTMP
|
||||
sd_event_source_unref(m->utmp_event_source);
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-event.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "hash-funcs.h"
|
||||
#include "hashmap.h"
|
||||
#include "logind.h"
|
||||
#include "logind-action.h"
|
||||
#include "logind-button.h"
|
||||
#include "logind-session.h"
|
||||
#include "logind-user.h"
|
||||
#include "sleep-config.h"
|
||||
#include "test-tables.h"
|
||||
#include "tests.h"
|
||||
#include "time-util.h"
|
||||
|
||||
static void test_sleep_handle_action(void) {
|
||||
for (HandleAction action = _HANDLE_ACTION_SLEEP_FIRST; action < _HANDLE_ACTION_SLEEP_LAST; action++) {
|
||||
@@ -21,6 +29,46 @@ static void test_sleep_handle_action(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static int dummy_time_handler(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Verify that button_free() cancels the Manager-scoped long-press timer the Button armed, and at the same
|
||||
* time leaves a timer armed by a different Button alone. */
|
||||
static void test_button_free_cancels_long_press(void) {
|
||||
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
|
||||
_cleanup_(hashmap_freep) Hashmap *buttons = NULL;
|
||||
_cleanup_free_ Manager *m = NULL;
|
||||
Button *b, *b2;
|
||||
|
||||
assert_se(m = new0(Manager, 1));
|
||||
assert_se(sd_event_new(&e) >= 0);
|
||||
assert_se(buttons = hashmap_new(&string_hash_ops));
|
||||
m->event = e;
|
||||
m->buttons = buttons;
|
||||
|
||||
assert_se(b = button_new(m, "test-button"));
|
||||
assert_se(b2 = button_new(m, "other-button"));
|
||||
|
||||
/* Arm a long-press timer owned by b, exactly as start_long_press() does: the source lives in
|
||||
* a Manager-scoped field and carries the Button as userdata. */
|
||||
assert_se(sd_event_add_time_relative(e, &m->power_key_long_press_event_source, CLOCK_MONOTONIC,
|
||||
10 * USEC_PER_SEC, 0, dummy_time_handler, b) >= 0);
|
||||
/* ... and another one owned by b2. */
|
||||
assert_se(sd_event_add_time_relative(e, &m->reboot_key_long_press_event_source, CLOCK_MONOTONIC,
|
||||
10 * USEC_PER_SEC, 0, dummy_time_handler, b2) >= 0);
|
||||
|
||||
/* Freeing b must cancel the timer b armed. */
|
||||
button_free(b);
|
||||
assert_se(!m->power_key_long_press_event_source);
|
||||
|
||||
/* ... but freeing b must not touch a timer owned by another Button. */
|
||||
assert_se(m->reboot_key_long_press_event_source);
|
||||
|
||||
button_free(b2);
|
||||
assert_se(!m->reboot_key_long_press_event_source);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
test_setup_logging(LOG_DEBUG);
|
||||
|
||||
@@ -33,6 +81,7 @@ int main(int argc, char **argv) {
|
||||
test_table(UserState, user_state, USER_STATE);
|
||||
|
||||
test_sleep_handle_action();
|
||||
test_button_free_cancels_long_press();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user