return g_variant_new("(i)", ret);
}
+static GVariant *dbus_power_cancel_change_state_wait(GDBusConnection *conn,
+ const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
+ GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
+{
+ int ret = 0;
+ pid_t pid;
+ guint64 wait_callback_id;
+
+ pid = gdbus_connection_get_sender_pid(conn, sender);
+ if (pid == -1 || kill(pid, 0) == -1) {
+ _E("%d process does not exist, dbus ignored.", pid);
+ ret = -ESRCH;
+ goto out;
+ }
+
+ g_variant_get(param, "(t)", &wait_callback_id);
+
+ ret = cancel_change_state_wait(pid, wait_callback_id);
+
+out:
+ return g_variant_new("(i)", ret);
+}
+
static GVariant *dbus_power_change_state(GDBusConnection *conn,
const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
{ "AddChangeTransientStateWait", "t", "i", dbus_power_add_change_transient_state_wait },
{ "RemoveChangeTransientStateWait", "t", NULL, dbus_power_remove_change_transient_state_wait },
{ "ConfirmChangeStateWait", "t", "i", dbus_power_confirm_change_state_wait },
+ { "CancelChangeStateWait", "t", "i", dbus_power_cancel_change_state_wait },
{ "PowerChangeState", "t", "i", dbus_power_change_state },
{ "PowerChangeStateWithReason", "ti", NULL, dbus_power_change_state_with_reason },
{ "PowerGetState", NULL, "t", dbus_power_get_state },
#define POWER_CONF_FILE "/etc/deviced/power.conf"
#define DEFAULT_MAX_WAIT_SECOND 5 /* second */
#define MAX_DESC_LEN 256
+#define TRANSITION_CONFIRM 0
+#define TRANSITION_CANCEL 1
static int delayed_init_done = 0;
static uint64_t current = DEVICED_POWER_STATE_START; /* current power state */
GList *waitings;
int transient_step;
int max_wait_timer;
+ /**
+ * If the cancel is set during a transition,
+ * undo the transition instead of doing action
+ * on reaching the destination state.
+ */
+ int cancel;
} transition_context;
struct proc_info {
return -1;
}
-int confirm_change_state_wait(pid_t pid, guint64 transition_id)
+static int handle_change_state_wait(pid_t pid, guint64 transition_id, int transition_cancel)
{
struct proc_info target_pi = { .pid = pid };
struct change_state_wait target_csw = { .pi = &target_pi, .transition_id = transition_id };
return 0;
csw = l->data;
- _D("pid=%d(%s) confirmed id=%"PRIu64"(%s)", csw->pi->pid, csw->pi->comm, csw->transition_id, state_name(csw->state));
+ if (transition_cancel == TRANSITION_CANCEL)
+ CRITICAL_LOG("pid=%d(%s) cancelled id=%"PRIu64"(%s)", csw->pi->pid, csw->pi->comm, csw->transition_id, state_name(csw->state));
+ else
+ _D("pid=%d(%s) confirmed id=%"PRIu64"(%s)", csw->pi->pid, csw->pi->comm, csw->transition_id, state_name(csw->state));
transition_context.waitings = g_list_remove_link(transition_context.waitings, l);
free(g_steal_pointer(&l->data));
return 0;
}
+int confirm_change_state_wait(pid_t pid, guint64 transition_id)
+{
+ return handle_change_state_wait(pid, transition_id, TRANSITION_CONFIRM);
+}
+
+int cancel_change_state_wait(pid_t pid, guint64 transition_id)
+{
+ /* do not allow cancelling poweroff transition */
+ if (is_poweroff_state(transition_context.ti.next))
+ return handle_change_state_wait(pid, transition_id, TRANSITION_CONFIRM);
+
+ transition_context.cancel = 1;
+
+ return handle_change_state_wait(pid, transition_id, TRANSITION_CANCEL);
+}
+
static uint64_t alloc_unique_id(void)
{
static uint64_t id = 0;
{
struct trans_info *tail = NULL;
- if (!ti)
- return -1;
-
tail = g_queue_pop_tail(transition_queue);
if (!tail)
return -1;
- *ti = (struct trans_info) {
- .curr = tail->curr,
- .next = tail->next,
- .reason = tail->reason,
- .data = tail->data,
- };
+ if (ti)
+ *ti = *tail;
free(tail);
if (!ti_new)
return;
+ memcpy(ti_new, ti, sizeof(struct trans_info));
+
g_queue_push_head(transition_queue, memcpy(ti_new, ti, sizeof(struct trans_info)));
}
action_transition();
}
+static void cancel_transition(void)
+{
+ struct trans_info *ti = NULL;
+
+ struct trans_info poweroff = { .next = DEVICED_POWER_STATE_UNDEFINED };
+ struct trans_info undo = {
+ .curr = transition_context.ti.next,
+ .next = transition_context.ti.curr,
+ /**
+ * TODO: Is it needed that a reason for this cancelling transition, such as
+ * DEVICE_POWER_TRANSITION_REASON_CANCEL_TRANSITION. Or use the previous
+ * reason as it is?
+ */
+ .reason = transition_context.ti.reason,
+ .data = transition_context.ti.data,
+ };
+
+ assert(transition_context.ongoing);
+ assert(transition_context.cancel);
+
+ /**
+ * Discard all pending transitions except transition to the poweroff/reboot/exit state.
+ * Transition to the powerff/reboot/exit must remain intact in any case.
+ */
+ while ((ti = g_queue_peek_tail(transition_queue))) {
+ if (is_poweroff_state(ti->next)) {
+ /**
+ * Save the transition.
+ *
+ * Fix the poweroff.curr to next of the previous one. This is because the transition_queue always
+ * manage its entries be chaining each other by matching those previous and next state in order.
+ */
+ dequeue_transition(&poweroff);
+ poweroff.curr = undo.next;
+ } else {
+ /* Discard the transition */
+ CRITICAL_LOG("Discard pending transition, curr=%s, next=%s, reason=%d",
+ state_abbr_name(ti->curr), state_abbr_name(ti->next), ti->reason);
+ dequeue_transition(NULL);
+ }
+ }
+
+ /* Reserve transition for undoing. */
+ enqueue_transition(&undo);
+ /* Reserve transition for poweroff. */
+ if (is_poweroff_state(poweroff.next))
+ enqueue_transition(&poweroff);
+}
+
static void action_transition(void)
{
uint64_t state = get_next_state();
int index;
current = transition_context.ti.next;
- index = DEVICED_POWER_STATE_INDEX(current);
+ index = DEVICED_POWER_STATE_INDEX(transition_context.ti.next);
- if (action_on_state[index])
+ if (transition_context.cancel)
+ cancel_transition();
+ else if (action_on_state[index])
action_on_state[index](transition_context.ti.data);
// transition end
transition_context.ongoing = 0;
transition_context.transient_step = 0;
+ transition_context.cancel = 0;
transition_description(1);
}