c06b8392a4db1c1c99709ae96c2411026a9c30df
[src/app-framework-main.git] / src / afm-user-daemon.c
1 /*
2  Copyright 2015, 2016, 2017 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #define _GNU_SOURCE
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <time.h>
23 #include <getopt.h>
24 #include <string.h>
25
26 #include <systemd/sd-bus.h>
27 #include <systemd/sd-event.h>
28 #include <json-c/json.h>
29
30 #include <afb/afb-ws-client.h>
31 #include <afb/afb-proto-ws.h>
32
33 #include "verbose.h"
34 #include "utils-jbus.h"
35 #include "utils-json.h"
36
37 #define AFM_USER_DBUS_PATH      "/org/AGL/afm/user"
38
39 /*
40  * name of the application
41  */
42 static const char appname[] = "afm-user-daemon";
43
44 /*
45  * string for printing version
46  */
47 static const char versionstr[] =
48         "\n"
49         "  %s  version="AFM_VERSION"\n"
50         "\n"
51         "  Copyright (C) 2015, 2016, 2017 \"IoT.bzh\"\n"
52         "  AFB comes with ABSOLUTELY NO WARRANTY.\n"
53         "  Licence Apache 2\n"
54         "\n";
55
56 /*
57  * string for printing usage
58  */
59 static const char usagestr[] =
60         "usage: %s [option(s)] afm-main-uri\n"
61         "\n"
62         "   -d           run as a daemon\n"
63         "   -u addr      address of user D-Bus to use\n"
64         "   -q           quiet\n"
65         "   -v           verbose\n"
66         "   -V           version\n"
67         "\n";
68
69 /*
70  * Option definition for getopt_long
71  */
72 static const char options_s[] = "hdqvVu:";
73 static struct option options_l[] = {
74         { "user-dbus",   required_argument, NULL, 'u' },
75         { "daemon",      no_argument,       NULL, 'd' },
76         { "quiet",       no_argument,       NULL, 'q' },
77         { "verbose",     no_argument,       NULL, 'v' },
78         { "help",        no_argument,       NULL, 'h' },
79         { "version",     no_argument,       NULL, 'V' },
80         { NULL, 0, NULL, 0 }
81 };
82
83 /*
84  * The methods propagated
85  */
86 static const char *methods[] = {
87         "runnables",
88         "detail",
89         "start",
90         "once",
91         "terminate",
92         "pause",
93         "resume",
94         "stop",
95         "continue",
96         "runners",
97         "state",
98         "install",
99         "uninstall",
100         NULL
101 };
102
103 /*
104  * Connections
105  */
106 static struct sd_event *evloop;
107 static struct jbus *user_bus;
108 static struct afb_proto_ws *pws;
109 static char *sessionid;
110 static const char *uri;
111
112 /*
113  * 
114  */
115 static void on_pws_hangup(void *closure);
116 static void on_pws_reply_success(void *closure, void *request, struct json_object *result, const char *info);
117 static void on_pws_reply_fail(void *closure, void *request, const char *status, const char *info);
118 static void on_pws_event_broadcast(void *closure, const char *event_name, struct json_object *data);
119
120 /* the callback interface for pws */
121 static struct afb_proto_ws_client_itf pws_itf = {
122         .on_reply_success = on_pws_reply_success,
123         .on_reply_fail = on_pws_reply_fail,
124         .on_event_broadcast = on_pws_event_broadcast,
125 };
126
127 static int try_connect_pws()
128 {
129         pws = afb_ws_client_connect_api(evloop, uri, &pws_itf, NULL);
130         if (pws == NULL) {
131                 fprintf(stderr, "connection to %s failed: %m\n", uri);
132                 return 0;
133         }
134         afb_proto_ws_on_hangup(pws, on_pws_hangup);
135         return 1;
136 }
137
138 static void attempt_connect_pws(int count);
139
140 static int timehand(sd_event_source *s, uint64_t usec, void *userdata)
141 {
142         sd_event_source_unref(s);
143         attempt_connect_pws((int)(intptr_t)userdata);
144         return 0;
145 }
146
147 static void attempt_connect_pws(int count)
148 {
149         sd_event_source *s;
150         if (!try_connect_pws()) {
151                 if (--count <= 0) {
152                         ERROR("Definitely disconnected");
153                         exit(1);
154                 }
155                 sd_event_add_time(evloop, &s, CLOCK_MONOTONIC, 5000000, 0, timehand, (void*)(intptr_t)count);
156         }
157 }
158
159 static void on_pws_reply_success(void *closure, void *request, struct json_object *result, const char *info)
160 {
161         struct sd_bus_message *smsg = request;
162         jbus_reply_j(smsg, result);
163 }
164
165 static void on_pws_reply_fail(void *closure, void *request, const char *status, const char *info)
166 {
167         struct sd_bus_message *smsg = request;
168         jbus_reply_error_s(smsg, status);
169 }
170
171 static void on_pws_event_broadcast(void *closure, const char *event_name, struct json_object *data)
172 {
173         jbus_send_signal_j(user_bus, "changed", data);
174 }
175
176 /* called when pws hangsup */
177 static void on_pws_hangup(void *closure)
178 {
179         struct afb_proto_ws *apw = pws;
180         pws = NULL;
181         afb_proto_ws_unref(apw);
182         attempt_connect_pws(10);
183 }
184
185 /* propagate the call to the service */
186 static void propagate(struct sd_bus_message *smsg, struct json_object *obj, void *closure)
187 {
188         int rc;
189         const char *verb = closure;
190
191         INFO("method %s propagated for %s", verb, json_object_to_json_string(obj));
192         if (!pws)
193                 jbus_reply_error_s(smsg, "disconnected");
194         else {
195                 rc = afb_proto_ws_client_call(pws, verb, obj, sessionid, smsg);
196                 if (rc < 0)
197                         ERROR("calling %s(%s) failed: %m\n", verb, json_object_to_json_string(obj));
198         } 
199 }
200
201 /*
202  * Tiny routine to daemonize the program
203  * Return 0 on success or -1 on failure.
204  */
205 static int daemonize()
206 {
207         int rc = fork();
208         if (rc < 0)
209                 return rc;
210         if (rc)
211                 _exit(0);
212         return 0;
213 }
214
215 /*
216  * Opens a sd-bus connection and returns it in 'ret'.
217  * The sd-bus connexion is intended to be for user if 'isuser'
218  * is not null. The adress is the default address when 'address'
219  * is NULL or, otherwise, the given address.
220  * It might be necessary to pass the address as an argument because
221  * library systemd uses secure_getenv to retrieves the default
222  * addresses and secure_getenv might return NULL in some cases.
223  */
224 static int open_bus(sd_bus **ret, int isuser, const char *address)
225 {
226         sd_bus *b;
227         int rc;
228
229         if (address == NULL)
230                 return (isuser ? sd_bus_default_user : sd_bus_default_system)(ret);
231
232         rc = sd_bus_new(&b);
233         if (rc < 0)
234                 return rc;
235
236         rc = sd_bus_set_address(b, address);
237         if (rc < 0)
238                 goto fail;
239
240         sd_bus_set_bus_client(b, 1);
241
242         rc = sd_bus_start(b);
243         if (rc < 0)
244                 goto fail;
245
246         *ret = b;
247         return 0;
248
249 fail:
250         sd_bus_unref(b);
251         return rc;
252 }
253
254 /*
255  * ENTRY POINT OF AFM-USER-DAEMON
256  */
257 int main(int ac, char **av)
258 {
259         int i, daemon = 0, rc;
260         struct sd_bus *usrbus;
261         const char *usr_bus_addr;
262         const char **iter;
263
264         LOGAUTH(appname);
265
266         /* first interpretation of arguments */
267         usr_bus_addr = NULL;
268         while ((i = getopt_long(ac, av, options_s, options_l, NULL)) >= 0) {
269                 switch (i) {
270                 case 'h':
271                         printf(usagestr, appname);
272                         return 0;
273                 case 'V':
274                         printf(versionstr, appname);
275                         return 0;
276                 case 'q':
277                         if (verbosity)
278                                 verbosity--;
279                         break;
280                 case 'v':
281                         verbosity++;
282                         break;
283                 case 'd':
284                         daemon = 1;
285                         break;
286                 case 'u':
287                         usr_bus_addr = optarg;
288                         break;
289                 case ':':
290                         ERROR("missing argument value");
291                         return 1;
292                 default:
293                         ERROR("unrecognized option");
294                         return 1;
295                 }
296         }
297
298         /* check argument count */
299         if (optind >= ac) {
300                 ERROR("Uri to the framework is missing");
301                 return 1;
302         }
303         if (optind + 1 != ac) {
304                 ERROR("Extra parameters found");
305                 return 1;
306         }
307         uri = av[optind];
308
309         /* init sessionid */
310         asprintf(&sessionid, "%d-%s", (int)getuid(), appname);
311
312         /* daemonize if requested */
313         if (daemon && daemonize()) {
314                 ERROR("daemonization failed");
315                 return 1;
316         }
317
318         /* get systemd objects */
319         rc = sd_event_new(&evloop);
320         if (rc < 0) {
321                 ERROR("can't create event loop");
322                 return 1;
323         }
324         rc = open_bus(&usrbus, 1, usr_bus_addr);
325         if (rc < 0) {
326                 ERROR("can't create user bus");
327                 return 1;
328         }
329         rc = sd_bus_attach_event(usrbus, evloop, 0);
330         if (rc < 0) {
331                 ERROR("can't attach user bus to event loop");
332                 return 1;
333         }
334
335         /* connect to framework */
336         if (!try_connect_pws()) {
337                 ERROR("connection to %s failed: %m\n", uri);
338                 return 1;
339         }
340
341         /* connect to the session bus */
342         user_bus = create_jbus(usrbus, AFM_USER_DBUS_PATH);
343         if (!user_bus) {
344                 ERROR("create_jbus failed");
345                 return 1;
346         }
347
348         /* init services */
349         for (iter = methods ; *iter ; iter ++) {
350                 if (jbus_add_service_j(user_bus, *iter, propagate, (void*)*iter)) {
351                         ERROR("adding services failed");
352                         return 1;
353                 }
354         }
355
356         /* start servicing */
357         if (jbus_start_serving(user_bus) < 0) {
358                 ERROR("can't start server");
359                 return 1;
360         }
361
362         /* run until error */
363         for(;;)
364                 sd_event_run(evloop, (uint64_t)-1);
365         return 0;
366 }
367