Merge lp:~sforshee/powerd/register-ack-api into lp:powerd

Proposed by Seth Forshee
Status: Merged
Approved by: Matt Fischer
Approved revision: 89
Merged at revision: 75
Proposed branch: lp:~sforshee/powerd/register-ack-api
Merge into: lp:powerd
Prerequisite: lp:~sforshee/powerd/hash-iterator
Diff against target: 877 lines (+643/-21)
11 files modified
CMakeLists.txt (+2/-1)
cli/CMakeLists.txt (+1/-0)
cli/client-test.c (+310/-0)
cli/powerd-cli.c (+10/-15)
cli/powerd-cli.h (+42/-0)
data/com.canonical.powerd.xml (+12/-0)
src/power-request.c (+36/-5)
src/powerd-client.c (+162/-0)
src/powerd-internal.h (+10/-0)
src/powerd-object.c (+56/-0)
src/powerd.cpp (+2/-0)
To merge this branch: bzr merge lp:~sforshee/powerd/register-ack-api
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Matt Fischer (community) Approve
Review via email: mp+176516@code.launchpad.net

Commit message

Add support for registering clients to acknowledge system state changes

Description of the change

Add support for registering clients to acknowledge system state changes

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~sforshee/powerd/register-ack-api updated
85. By Seth Forshee

powerd-object: Remove unneeded argument from ack dbus handler

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~sforshee/powerd/register-ack-api updated
86. By Seth Forshee

Add state argument to ackStateChange dbus API

Without any arguments to the ackStateChange API we have a
possibility for races. If two state changes happen back-to-back,
a client who sends the ack late for the first state change might
inadvertantly acknowledge the second state change. Explicilty
specifying which state change is being acknowledge will help
avoid such issues.

Also add a test to verify that acking with the wrong state fails
as expected.

Revision history for this message
Matt Fischer (mfisch) wrote :

Comments so far:

488 + powerd_warn("Timeout waiting for acks on state transition");

Should we list the offenders here?

powerd-client.c is a confusing name since it's still part of the service, but I'm okay keeping it if you like the name.

583 + powerd_error("Client needs ack while global state is %d", acks_needed);

Either the text here or the variable you are printing does not make sense. Are you trying to print the state?

Should we drop a client from the ack list if they don't send acks? What if they don't respond to 10 in a row for example?

Should we drop the name argument (this is pending some investigation by me)

Revision history for this message
Seth Forshee (sforshee) wrote :

On Wed, Jul 24, 2013 at 05:39:29PM -0000, Matthew Fischer wrote:
> Comments so far:
>
> 488 + powerd_warn("Timeout waiting for acks on state transition");
>
> Should we list the offenders here?

powerd_client_transition_finish() will print out the offenders.

> powerd-client.c is a confusing name since it's still part of the service, but I'm okay keeping it if you like the name.

I'm open to suggestions ;-)

> 583 + powerd_error("Client needs ack while global state is %d", acks_needed);
>
> Either the text here or the variable you are printing does not make sense. Are you trying to print the state?

The text is the problem. I'll change it.

> Should we drop a client from the ack list if they don't send acks? What if they don't respond to 10 in a row for example?

This might be a good idea. Could we go ahead and get it in without this
feature and make a work item for it?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~sforshee/powerd/register-ack-api updated
87. By Seth Forshee

powerd-cli: Print results for more client tests

powerd-cli still can't print results for every test, but it
should print resulsts whenever possible.

88. By Seth Forshee

powerd-cli: Remove some debug prints in client-test

89. By Seth Forshee

powerd-client: Reword confusing error message

Revision history for this message
Matt Fischer (mfisch) wrote :

Looks good.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2013-07-10 21:39:32 +0000
3+++ CMakeLists.txt 2013-07-24 20:12:24 +0000
4@@ -41,9 +41,10 @@
5 src/display.c
6 src/display-request.c
7 src/log.c
8- src/powerd-object.c
9 src/power-request.c
10 src/power-source.c
11+ src/powerd-client.c
12+ src/powerd-object.c
13 src/powerd-sensors.cpp
14 src/util.c
15 src/${GDBUS_NAME}.c
16
17=== modified file 'cli/CMakeLists.txt'
18--- cli/CMakeLists.txt 2013-06-06 21:43:31 +0000
19+++ cli/CMakeLists.txt 2013-07-24 20:12:24 +0000
20@@ -2,6 +2,7 @@
21 SRCS
22
23 powerd-cli.c
24+ client-test.c
25 )
26
27 link_directories(
28
29=== added file 'cli/client-test.c'
30--- cli/client-test.c 1970-01-01 00:00:00 +0000
31+++ cli/client-test.c 2013-07-24 20:12:24 +0000
32@@ -0,0 +1,310 @@
33+/*
34+ * Copyright 2013 Canonical Ltd.
35+ *
36+ * This file is part of powerd.
37+ *
38+ * powerd is free software; you can redistribute it and/or modify
39+ * it under the terms of the GNU General Public License as published by
40+ * the Free Software Foundation; version 3.
41+ *
42+ * powerd is distributed in the hope that it will be useful,
43+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
44+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45+ * GNU General Public License for more details.
46+ *
47+ * You should have received a copy of the GNU General Public License
48+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
49+ */
50+
51+#include <stdio.h>
52+#include <string.h>
53+#include <glib.h>
54+#include <glib-object.h>
55+#include <gio/gio.h>
56+
57+#include "powerd-cli.h"
58+
59+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
60+
61+static struct client_test_data {
62+ GDBusProxy *proxy;
63+ int state;
64+ int ack_delay;
65+ guint ack_timeout_id;
66+ powerd_cookie_t cookie;
67+} test_data;
68+
69+enum ack_status {
70+ NOT_ACKED,
71+ ACK_SUCCESS,
72+ ACK_FAILURE,
73+ ACK_TIMEOUT,
74+} ack_status;;
75+
76+static GMainLoop *main_loop;
77+
78+static gboolean run_next_test_handler(gpointer unused);
79+
80+static void reset_test_state(void)
81+{
82+ ack_status = NOT_ACKED;
83+}
84+
85+static void run_next_test(void)
86+{
87+ g_timeout_add(0, run_next_test_handler, NULL);
88+}
89+
90+static gboolean register_client(GDBusProxy *proxy)
91+{
92+ GVariant *ret;
93+ GError *error = NULL;
94+
95+ ret = g_dbus_proxy_call_sync(proxy, "registerClient",
96+ g_variant_new("(s)", "powerd-cli"),
97+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
98+ if (!ret) {
99+ cli_warn("registerClient failed: %s", error->message);
100+ g_error_free(error);
101+ return FALSE;
102+ }
103+
104+ g_variant_unref(ret);
105+ return TRUE;
106+}
107+
108+static gboolean unregister_client(GDBusProxy *proxy)
109+{
110+ GVariant *ret;
111+ GError *error = NULL;
112+
113+ ret = g_dbus_proxy_call_sync(proxy, "unregisterClient",
114+ g_variant_new("(s)", "powerd-cli"),
115+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
116+ if (!ret) {
117+ cli_warn("unregisterClient failed: %s", error->message);
118+ g_error_free(error);
119+ return FALSE;
120+ }
121+
122+ g_variant_unref(ret);
123+ return TRUE;
124+}
125+
126+static gboolean ack_state_change(GDBusProxy *proxy, int state)
127+{
128+ GVariant *ret;
129+ GError *error = NULL;
130+
131+ ret = g_dbus_proxy_call_sync(proxy, "ackStateChange",
132+ g_variant_new("(i)", state),
133+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
134+ if (!ret) {
135+ cli_warn("ackStateChange failed: %s", error->message);
136+ g_error_free(error);
137+ ack_status = ACK_FAILURE;
138+ return FALSE;
139+ }
140+
141+ g_variant_unref(ret);
142+ ack_status = ACK_SUCCESS;
143+ return TRUE;
144+}
145+
146+static gboolean send_ack_handler(gpointer unused)
147+{
148+ if (ack_state_change(test_data.proxy, test_data.state))
149+ run_next_test();
150+ return FALSE;
151+}
152+
153+static gboolean ack_timeout_handler(gpointer unused)
154+{
155+ test_data.ack_timeout_id = 0;
156+ ack_status = ACK_TIMEOUT;
157+ run_next_test();
158+ return FALSE;
159+}
160+
161+static void powerd_signal_handler(GDBusProxy *proxy, gchar *sender,
162+ gchar *signal, GVariant *params,
163+ gpointer unused)
164+{
165+ cli_debug("Received signal %s\n", signal);
166+ if (strcmp(signal, "SysPowerStateChange"))
167+ return;
168+
169+ /* Check if we're trying to time out */
170+ if (test_data.ack_delay < 0)
171+ return;
172+
173+ if (test_data.ack_timeout_id > 0) {
174+ g_source_remove(test_data.ack_timeout_id);
175+ test_data.ack_timeout_id = 0;
176+ }
177+
178+ if (test_data.ack_delay == 0) {
179+ /* immediate ack */
180+ ack_state_change(proxy, test_data.state);
181+ run_next_test();
182+ } else {
183+ /* delayed ack */
184+ g_timeout_add(test_data.ack_delay, send_ack_handler, NULL);
185+ }
186+}
187+
188+static gboolean register_test(gpointer unused)
189+{
190+ gboolean result;
191+
192+ result = do_test(register_client(test_data.proxy) == TRUE);
193+ if (!result)
194+ g_main_loop_quit(main_loop);
195+ else {
196+ run_next_test();
197+ }
198+ return FALSE;
199+}
200+
201+static gboolean ack_test_part1(gpointer unused)
202+{
203+ printf("Testing ack on active state transition\n");
204+
205+ test_data.state = POWERD_SYS_STATE_ACTIVE;
206+ test_data.ack_delay = 0;
207+ reset_test_state();
208+ test_data.ack_timeout_id = g_timeout_add(5000, ack_timeout_handler, NULL);
209+ requestSysState(test_data.state, &test_data.cookie);
210+ return FALSE;
211+}
212+
213+static gboolean ack_test_part2(gpointer unused)
214+{
215+ /* Check result of part 1 */
216+ do_test(ack_status == ACK_SUCCESS);
217+
218+ reset_test_state();
219+ printf("Testing ack on suspend state transition\n");
220+ test_data.state = POWERD_SYS_STATE_SUSPEND;
221+ test_data.ack_delay = 0;
222+ test_data.ack_timeout_id = g_timeout_add(5000, ack_timeout_handler, NULL);
223+ clearSysState(test_data.cookie);
224+ return FALSE;
225+}
226+
227+static gboolean ack_test_check_result(gpointer unused)
228+{
229+ do_test(ack_status == ACK_SUCCESS);
230+ run_next_test();
231+ return FALSE;
232+}
233+
234+static gboolean no_ack_test_part1(gpointer unused)
235+{
236+ printf("Testing active state transition with no ack\n");
237+
238+ reset_test_state();
239+ test_data.state = POWERD_SYS_STATE_ACTIVE;
240+ test_data.ack_delay = -1;
241+ test_data.ack_timeout_id = g_timeout_add(5000, ack_timeout_handler, NULL);
242+ requestSysState(test_data.state, &test_data.cookie);
243+ return FALSE;
244+}
245+
246+static gboolean no_ack_test_part2(gpointer unused)
247+{
248+ printf("Testing suspend state transition with no ack\n");
249+
250+ reset_test_state();
251+ test_data.state = POWERD_SYS_STATE_SUSPEND;
252+ test_data.ack_delay = -1;
253+ test_data.ack_timeout_id = g_timeout_add(5000, ack_timeout_handler, NULL);
254+ clearSysState(test_data.cookie);
255+ return FALSE;
256+}
257+
258+static gboolean bad_ack_test(gpointer unused)
259+{
260+ printf("Testing acknowledge of wrong state\n");
261+
262+ reset_test_state();
263+ test_data.state = POWERD_SYS_STATE_SUSPEND;
264+ test_data.ack_delay = 0;
265+ test_data.ack_timeout_id = g_timeout_add(5000, ack_timeout_handler, NULL);
266+ requestSysState(POWERD_SYS_STATE_ACTIVE, &test_data.cookie);
267+ return FALSE;
268+}
269+
270+static gboolean bad_ack_test_result(gpointer unused)
271+{
272+ do_test(ack_status == ACK_FAILURE);
273+ run_next_test();
274+ return FALSE;
275+}
276+
277+static gboolean unregister_test(gpointer unused)
278+{
279+ gboolean result;
280+
281+ result = do_test(unregister_client(test_data.proxy) == TRUE);
282+ if (!result)
283+ g_main_loop_quit(main_loop);
284+ else
285+ run_next_test();
286+ return FALSE;
287+}
288+
289+static gboolean no_unregister_test(gpointer unused)
290+{
291+ printf("Testing exit without unregistering\n");
292+ run_next_test();
293+ return FALSE;
294+}
295+
296+static GSourceFunc client_tests[] = {
297+ register_test,
298+ ack_test_part1,
299+ ack_test_part2,
300+ ack_test_check_result,
301+ no_ack_test_part1,
302+ no_ack_test_part2,
303+ bad_ack_test,
304+ bad_ack_test_result,
305+ unregister_test,
306+ register_test,
307+ no_unregister_test,
308+};
309+
310+static gboolean run_next_test_handler(gpointer unused)
311+{
312+ static int test_num = 0;
313+
314+ if (test_data.ack_timeout_id > 0) {
315+ g_source_remove(test_data.ack_timeout_id);
316+ test_data.ack_timeout_id = 0;
317+ }
318+
319+ if (test_num >= ARRAY_SIZE(client_tests)) {
320+ test_num = 0;
321+ g_main_loop_quit(main_loop);
322+ return FALSE;
323+ }
324+
325+ g_timeout_add(0, client_tests[test_num], NULL);
326+ test_num++;
327+ return FALSE;
328+}
329+
330+void powerd_cli_client_test(GDBusProxy *proxy)
331+{
332+ printf("Running client tests, cannot verify all results\n");
333+
334+ silent_errors = TRUE;
335+ test_data.proxy = proxy;
336+ main_loop = g_main_loop_new(NULL, FALSE);
337+ g_signal_connect(proxy, "g-signal", G_CALLBACK(powerd_signal_handler), NULL);
338+ g_timeout_add(0, run_next_test_handler, NULL);
339+ g_main_loop_run(main_loop);
340+ g_main_loop_unref(main_loop);
341+ silent_errors = FALSE;
342+}
343
344=== modified file 'cli/powerd-cli.c'
345--- cli/powerd-cli.c 2013-07-19 20:01:31 +0000
346+++ cli/powerd-cli.c 2013-07-24 20:12:24 +0000
347@@ -29,23 +29,13 @@
348 #include <uuid/uuid.h>
349 #include <powerd.h>
350
351-#define do_test(test) \
352- ({ \
353- gboolean result; \
354- printf("Test: " #test "\n"); \
355- result = (test); \
356- printf(" result: %s\n", result ? "PASSED" : "FAILED"); \
357- result; \
358- })
359+#include "powerd-cli.h"
360
361 #define TEST_NUM_SYS_REQUESTS 5
362 #define TEST_NUM_DISP_REQUESTS (POWERD_NUM_DISPLAY_STATES * 2)
363
364 /* Set to TRUE during tests to silence expected errors */
365-static gboolean silent_errors = FALSE;
366-
367-#define cli_warn(f, a...) do { if (!silent_errors) g_warning(f, ##a); } while (0)
368-#define cli_debug(f, a...) g_debug(f, ##a)
369+gboolean silent_errors = FALSE;
370
371 GDBusProxy *powerd_proxy = NULL;
372 char test_dbusname[128] = "";
373@@ -102,7 +92,7 @@
374 }
375 }
376
377-static gboolean
378+gboolean
379 requestSysState(int state,
380 powerd_cookie_t *cookie)
381 {
382@@ -312,7 +302,7 @@
383 }
384 }
385
386-static gboolean
387+gboolean
388 clearSysState(powerd_cookie_t cookie)
389 {
390 GVariant *ret = NULL;
391@@ -717,6 +707,7 @@
392 "exit, causing the request to be dropped.\n");
393 printf("clear-sys <cookie> - clear a System state request given a cookie\n");
394 printf("clear-disp <cookie> - clear a Display state request given a cookie\n");
395+ printf("client-test - test powerd registration / ack API\n");
396 printf("display <on|dc> [bright] [proximity] [disableab]\n"\
397 "\tMake a display state request with the input parameters.\n"\
398 "\tThe first argument represents the state of the display:\n"\
399@@ -763,7 +754,8 @@
400 (strcmp(argv[1],"display")) &&
401 (strcmp(argv[1],"help")) &&
402 (strcmp(argv[1],"clear-disp")) &&
403- (strcmp(argv[1],"clear-sys"))) {
404+ (strcmp(argv[1],"clear-sys")) &&
405+ (strcmp(argv[1],"client-test"))) {
406 fprintf(stderr,"Invalid option %s\n",argv[1]);
407 usage(argv[0]);
408 return -1;
409@@ -905,5 +897,8 @@
410 // Exit here without cleanup
411 return 0;
412 }
413+ else if (!strcmp(argv[1], "client-test")) {
414+ powerd_cli_client_test(powerd_proxy);
415+ }
416 return 0;
417 }
418
419=== added file 'cli/powerd-cli.h'
420--- cli/powerd-cli.h 1970-01-01 00:00:00 +0000
421+++ cli/powerd-cli.h 2013-07-24 20:12:24 +0000
422@@ -0,0 +1,42 @@
423+/*
424+ * Copyright 2013 Canonical Ltd.
425+ *
426+ * This file is part of powerd.
427+ *
428+ * powerd is free software; you can redistribute it and/or modify
429+ * it under the terms of the GNU General Public License as published by
430+ * the Free Software Foundation; version 3.
431+ *
432+ * powerd is distributed in the hope that it will be useful,
433+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
434+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
435+ * GNU General Public License for more details.
436+ *
437+ * You should have received a copy of the GNU General Public License
438+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
439+ */
440+
441+#ifndef __POWERD_CLI_H__
442+#define __POWERD_CLI_H__
443+
444+#include <powerd.h>
445+
446+#define do_test(test) \
447+ ({ \
448+ gboolean result; \
449+ printf("Test: " #test "\n"); \
450+ result = (test); \
451+ printf(" result: %s\n", result ? "PASSED" : "FAILED"); \
452+ result; \
453+ })
454+
455+extern gboolean silent_errors;
456+
457+#define cli_warn(f, a...) do { if (!silent_errors) g_warning(f, ##a); } while (0)
458+#define cli_debug(f, a...) g_debug(f, ##a)
459+
460+gboolean requestSysState(int state, powerd_cookie_t *cookie);
461+gboolean clearSysState(powerd_cookie_t cookie);
462+void powerd_cli_client_test(GDBusProxy *proxy);
463+
464+#endif /* __POWERD_CLI_H__ */
465
466=== modified file 'data/com.canonical.powerd.xml'
467--- data/com.canonical.powerd.xml 2013-07-17 22:39:08 +0000
468+++ data/com.canonical.powerd.xml 2013-07-24 20:12:24 +0000
469@@ -25,6 +25,18 @@
470 <arg type="u" name="flags" direction="in" />
471 </method>
472
473+ <method name="registerClient">
474+ <arg type="s" name="name" direction="in" />
475+ </method>
476+
477+ <method name="unregisterClient">
478+ <arg type="s" name="name" direction="in" />
479+ </method>
480+
481+ <method name="ackStateChange">
482+ <arg type="i" name="state" direction="in" />
483+ </method>
484+
485 <method name="clearDisplayState">
486 <arg type="s" name="cookie" direction="in" />
487 </method>
488
489=== modified file 'src/power-request.c'
490--- src/power-request.c 2013-07-19 22:55:17 +0000
491+++ src/power-request.c 2013-07-24 20:12:24 +0000
492@@ -36,7 +36,7 @@
493
494 #include "libsuspend.h"
495
496-#define NOTIFICATION_TIMEOUT_MSECS 100
497+#define NOTIFICATION_TIMEOUT_MSECS 2000
498
499 struct SysStateRequest {
500 gchar *owner;
501@@ -81,7 +81,9 @@
502 #define INVALID_STATE -1
503 static GQueue queued_state_changes;
504 static enum SysPowerStates pending_system_state = INVALID_STATE;
505+static guint state_machine_source_id;
506 static gint update_pending;
507+static gboolean ack_pending;
508
509 static const char power_request_wakelock_name[] = "powerd_power_request";
510 gint suspend_block_count;
511@@ -410,14 +412,21 @@
512 if (ret)
513 powerd_warn("Failed to prepare for suspend: %d", ret);
514 }
515+ ack_pending = powerd_client_transition_start(pending_system_state);
516 powerd_sys_state_signal_emit(pending_system_state);
517
518- *delay = NOTIFICATION_TIMEOUT_MSECS;
519+ *delay = ack_pending ? NOTIFICATION_TIMEOUT_MSECS : 0;
520 return STATE_TRANSITION_NOTIFY;
521 }
522
523 static enum state_transition_state process_state_notify(guint *delay)
524 {
525+ if (ack_pending) {
526+ powerd_warn("Timeout waiting for acks on state transition");
527+ ack_pending = FALSE;
528+ }
529+
530+ powerd_client_transition_finish(pending_system_state);
531 *delay = 0;
532 return STATE_TRANSITION_COMPLETE;
533 }
534@@ -463,6 +472,7 @@
535 {
536 guint delay;
537
538+ state_machine_source_id = 0;
539 do {
540 switch (cur_state_transition_state) {
541 case STATE_TRANSITION_DEQUEUE:
542@@ -480,9 +490,30 @@
543 } while (delay == 0 &&
544 cur_state_transition_state != STATE_TRANSITION_IDLE);
545
546- if (delay != 0 && cur_state_transition_state != STATE_TRANSITION_IDLE)
547- g_timeout_add(delay, delayed_state_transition_process, NULL);
548-
549+ if (delay != 0 && cur_state_transition_state != STATE_TRANSITION_IDLE) {
550+ state_machine_source_id = g_timeout_add(delay,
551+ delayed_state_transition_process,
552+ NULL);
553+ }
554+
555+}
556+
557+/* Must be called from main loop */
558+void power_request_transition_acked(void)
559+{
560+ if (cur_state_transition_state != STATE_TRANSITION_NOTIFY) {
561+ powerd_error("%s called while not waiting for acks", __func__);
562+ return;
563+ }
564+
565+ powerd_debug("All acks received for state transition");
566+ ack_pending = FALSE;
567+ if (state_machine_source_id) {
568+ g_source_remove(state_machine_source_id);
569+ state_machine_source_id = g_timeout_add(0,
570+ delayed_state_transition_process,
571+ NULL);
572+ }
573 }
574
575 static void check_queued_state_changes(void)
576
577=== added file 'src/powerd-client.c'
578--- src/powerd-client.c 1970-01-01 00:00:00 +0000
579+++ src/powerd-client.c 2013-07-24 20:12:24 +0000
580@@ -0,0 +1,162 @@
581+/*
582+ * Copyright 2013 Canonical Ltd.
583+ *
584+ * This file is part of powerd.
585+ *
586+ * powerd is free software; you can redistribute it and/or modify
587+ * it under the terms of the GNU General Public License as published by
588+ * the Free Software Foundation; version 3.
589+ *
590+ * powerd is distributed in the hope that it will be useful,
591+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
592+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
593+ * GNU General Public License for more details.
594+ *
595+ * You should have received a copy of the GNU General Public License
596+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
597+ */
598+
599+#include <errno.h>
600+#include <glib.h>
601+#include "powerd-internal.h"
602+#include "log.h"
603+
604+struct powerd_client {
605+ const char *dbus_name;
606+ const char *name;
607+ gboolean ack_pending;
608+};
609+
610+static GHashTable *client_hash;
611+static int acks_needed;
612+static int ack_state;
613+
614+static void client_ack(struct powerd_client *client)
615+{
616+ if (!client->ack_pending)
617+ return;
618+ client->ack_pending = FALSE;
619+
620+ if (acks_needed <= 0) {
621+ powerd_error("Client needs ack while total acks needed is %d",
622+ acks_needed);
623+ return;
624+ }
625+ if (--acks_needed == 0)
626+ power_request_transition_acked();
627+}
628+
629+/* Must be called from main loop */
630+int powerd_client_register(const char *dbus_name, const char *name)
631+{
632+ struct powerd_client *client;
633+
634+ if (!dbus_name || !name)
635+ return -EINVAL;
636+
637+ client = g_new0(struct powerd_client, 1);
638+ if (!client)
639+ return -ENOMEM;
640+
641+ client->dbus_name = g_strdup(dbus_name);
642+ client->name = g_strdup(name);
643+ g_hash_table_insert(client_hash, (gpointer)client->dbus_name, client);
644+ powerd_dbus_name_watch_add(dbus_name);
645+ return 0;
646+}
647+
648+/* Must be called from main loop */
649+void powerd_client_unregister(const char *dbus_name)
650+{
651+ struct powerd_client *client;
652+
653+ client = g_hash_table_lookup(client_hash, dbus_name);
654+ if (!client)
655+ return;
656+
657+ client_ack(client);
658+ powerd_dbus_name_watch_remove(client->dbus_name);
659+ g_hash_table_remove(client_hash, client->dbus_name);
660+}
661+
662+/*
663+ * Must be called from main loop. Returns TRUE if caller should
664+ * wait for acks.
665+ */
666+gboolean powerd_client_transition_start(int state)
667+{
668+ GHashTableIter iter;
669+ gpointer key, value;
670+
671+ acks_needed = 0;
672+ ack_state = state;
673+
674+ g_hash_table_iter_init(&iter, client_hash);
675+ while (g_hash_table_iter_next(&iter, &key, &value)) {
676+ struct powerd_client *client = value;
677+ client->ack_pending = TRUE;
678+ acks_needed++;
679+ }
680+ return acks_needed != 0;
681+}
682+
683+/* Must be called from main loop */
684+void powerd_client_transition_finish(int state)
685+{
686+ GHashTableIter iter;
687+ gpointer key, value;
688+
689+ if (state != ack_state) {
690+ powerd_warn("Attempt to finish client transition with wrong state");
691+ return;
692+ }
693+
694+ if (acks_needed)
695+ return;
696+ acks_needed = 0;
697+
698+ g_hash_table_iter_init(&iter, client_hash);
699+ while (g_hash_table_iter_next(&iter, &key, &value)) {
700+ struct powerd_client *client = value;
701+ if (client->ack_pending) {
702+ powerd_warn("Client %s (%s) failed to acknowledge state change",
703+ client->name, client->dbus_name);
704+ client->ack_pending = FALSE;
705+ }
706+ }
707+}
708+
709+/* Must be called from main loop */
710+int powerd_client_ack(const char *dbus_name, int state)
711+{
712+ struct powerd_client *client;
713+
714+ if (state != ack_state)
715+ return -EINVAL;
716+
717+ client = g_hash_table_lookup(client_hash, dbus_name);
718+ if (!client)
719+ return -ENODEV;
720+ client_ack(client);
721+ return 0;
722+}
723+
724+static void client_hash_destroy(gpointer data)
725+{
726+ struct powerd_client *client = data;
727+ g_free((gpointer)client->dbus_name);
728+ g_free((gpointer)client->name);
729+ g_free(client);
730+}
731+
732+int powerd_client_init(void)
733+{
734+ client_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
735+ client_hash_destroy);
736+ return 0;
737+}
738+
739+void powerd_client_deinit(void)
740+{
741+ g_hash_table_destroy(client_hash);
742+}
743
744=== modified file 'src/powerd-internal.h'
745--- src/powerd-internal.h 2013-07-19 23:09:47 +0000
746+++ src/powerd-internal.h 2013-07-24 20:12:24 +0000
747@@ -104,6 +104,7 @@
748 gboolean request_sys_state_internal(int state, uuid_t *cookie,
749 const gchar *owner);
750 gboolean clear_sys_state_internal(uuid_t cookie);
751+void power_request_transition_acked(void);
752 void update_system_state(void);
753 void power_request_init(void);
754 void power_request_deinit(void);
755@@ -149,6 +150,15 @@
756 int powerd_ps_init(void);
757 void powerd_ps_deinit(void);
758
759+/* Client functions */
760+int powerd_client_register(const char *dbus_name, const char *name);
761+void powerd_client_unregister(const char *dbus_name);
762+gboolean powerd_client_transition_start(int state);
763+void powerd_client_transition_finish(int state);
764+int powerd_client_ack(const char *dbus_name, int state);
765+int powerd_client_init(void);
766+void powerd_client_deinit(void);
767+
768 /* Utility functions */
769 int powerd_run_mainloop_sync(int (*func)(gpointer), gpointer data);
770 guint powerd_uuid_hash(gconstpointer key);
771
772=== modified file 'src/powerd-object.c'
773--- src/powerd-object.c 2013-07-17 22:39:08 +0000
774+++ src/powerd-object.c 2013-07-24 20:12:24 +0000
775@@ -21,6 +21,7 @@
776 #include <gio/gio.h>
777 #include <string.h>
778 #include <stdio.h>
779+#include <errno.h>
780 #include "powerd-internal.h"
781 #include "powerd-dbus.h"
782 #include "log.h"
783@@ -86,6 +87,54 @@
784 return;
785 }
786
787+static gboolean handle_register_client(PowerdSource *obj,
788+ GDBusMethodInvocation *invocation,
789+ const gchar *name)
790+{
791+ const char *owner;
792+ int ret;
793+
794+ owner = g_dbus_method_invocation_get_sender(invocation);
795+ ret = powerd_client_register(owner, name);
796+ if (ret)
797+ g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
798+ G_DBUS_ERROR_FAILED,
799+ "Could not register client");
800+ else
801+ g_dbus_method_invocation_return_value(invocation, NULL);
802+ return TRUE;
803+}
804+
805+static gboolean handle_unregister_client(PowerdSource *obj,
806+ GDBusMethodInvocation *invocation,
807+ const gchar *name)
808+{
809+ const char *owner = g_dbus_method_invocation_get_sender(invocation);
810+ powerd_client_unregister(owner);
811+ g_dbus_method_invocation_return_value(invocation, NULL);
812+ return TRUE;
813+}
814+
815+static gboolean handle_ack_state_change(PowerdSource *obj,
816+ GDBusMethodInvocation *invocation,
817+ int state)
818+{
819+ const char *owner;
820+ int ret;
821+
822+ owner = g_dbus_method_invocation_get_sender(invocation);
823+ ret = powerd_client_ack(owner, state);
824+ if (ret) {
825+ const char *msg = (ret == -EINVAL) ? "Invalid state" :
826+ "Client not registered";
827+ g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
828+ G_DBUS_ERROR_INVALID_ARGS, msg);
829+ } else {
830+ g_dbus_method_invocation_return_value(invocation, NULL);
831+ }
832+ return TRUE;
833+}
834+
835 void
836 powerd_bus_acquired_cb (GDBusConnection *connection,
837 const gchar *name,
838@@ -120,6 +169,12 @@
839 G_CALLBACK(handle_update_display_request), powerd_source);
840 g_signal_connect(G_OBJECT(powerd_source->priv->skel), "handle-clear-display-state",
841 G_CALLBACK(handle_clear_display_request), powerd_source);
842+ g_signal_connect(G_OBJECT(powerd_source->priv->skel), "handle-register-client",
843+ G_CALLBACK(handle_register_client), powerd_source);
844+ g_signal_connect(G_OBJECT(powerd_source->priv->skel), "handle-unregister-client",
845+ G_CALLBACK(handle_unregister_client), powerd_source);
846+ g_signal_connect(G_OBJECT(powerd_source->priv->skel), "handle-ack-state-change",
847+ G_CALLBACK(handle_ack_state_change), powerd_source);
848 g_signal_connect(G_OBJECT(powerd_source->priv->skel), "handle-list-display-requests",
849 G_CALLBACK(handle_list_display_requests), powerd_source);
850
851@@ -237,6 +292,7 @@
852 powerd_warn("%s vanished from dbus, clearing associated requests", name);
853 clear_sys_state_by_owner(name);
854 clear_disp_state_by_owner(name);
855+ powerd_client_unregister(name);
856 // This can return false if the hash entry was removed because the
857 // last request was dropped, in other words, this call will fail
858 // if the callers cleaned-up properly, so ignore any FALSE return
859
860=== modified file 'src/powerd.cpp'
861--- src/powerd.cpp 2013-07-18 17:31:38 +0000
862+++ src/powerd.cpp 2013-07-24 20:12:24 +0000
863@@ -471,6 +471,7 @@
864 powerd_debug("Auto-dim Timeout is %d seconds\n", dim_timeout);
865
866 libsuspend_init(0);
867+ powerd_client_init();
868 power_request_init();
869 display_request_init();
870 dbus_name_watch_init();
871@@ -509,5 +510,6 @@
872 powerd_backlight_deinit();
873 display_request_deinit();
874 power_request_deinit();
875+ powerd_client_deinit();
876 return g_exit_code;
877 }

Subscribers

People subscribed via source and target branches