22c3d81fc0d0690d33dd7b96b3e75ebe21972ecf
[src/app-framework-main.git] / src / afm-user-daemon.c
1 /*
2  Copyright (C) 2015-2018 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(void *closure, void *request, struct json_object *obj, const char *error, const char *info);
117 #if !defined(AFB_PROTO_WS_VERSION) || (AFB_PROTO_WS_VERSION < 3)
118 static void on_pws_reply_success(void *closure, void *request, struct json_object *result, const char *info)
119         { on_pws_reply(closure, request, result, NULL, info); }
120 static void on_pws_reply_fail(void *closure, void *request, const char *error, const char *info)
121         { on_pws_reply(closure, request, NULL, error, info); }
122 #endif
123 static void on_pws_event_broadcast(void *closure, const char *event_name, struct json_object *data);
124
125 /* the callback interface for pws */
126 static struct afb_proto_ws_client_itf pws_itf = {
127 #if !defined(AFB_PROTO_WS_VERSION) || (AFB_PROTO_WS_VERSION < 3)
128         .on_reply_success = on_pws_reply_success,
129         .on_reply_fail = on_pws_reply_fail,
130 #else
131         .on_reply = on_pws_reply,
132 #endif
133         .on_event_broadcast = on_pws_event_broadcast,
134 };
135
136 static int try_connect_pws()
137 {
138         pws = afb_ws_client_connect_api(evloop, uri, &pws_itf, NULL);
139         if (pws == NULL) {
140                 fprintf(stderr, "connection to %s failed: %m\n", uri);
141                 return 0;
142         }
143         afb_proto_ws_on_hangup(pws, on_pws_hangup);
144         return 1;
145 }
146
147 static void attempt_connect_pws(int count);
148
149 static int timehand(sd_event_source *s, uint64_t usec, void *userdata)
150 {
151         sd_event_source_unref(s);
152         attempt_connect_pws((int)(intptr_t)userdata);
153         return 0;
154 }
155
156 static void attempt_connect_pws(int count)
157 {
158         sd_event_source *s;
159         if (!try_connect_pws()) {
160                 if (--count <= 0) {
161                         ERROR("Definitely disconnected");
162                         exit(1);
163                 }
164                 sd_event_add_time(evloop, &s, CLOCK_MONOTONIC, 5000000, 0, timehand, (void*)(intptr_t)count);
165         }
166 }
167
168 static void on_pws_reply(void *closure, void *request, struct json_object *obj, const char *error, const char *info)
169 {
170         struct sd_bus_message *smsg = request;
171         if (error)
172                 jbus_reply_error_s(smsg, error);
173         else
174                 jbus_reply_j(smsg, obj);
175 }
176
177 static void on_pws_event_broadcast(void *closure, const char *event_name, struct json_object *data)
178 {
179         jbus_send_signal_j(user_bus, "changed", data);
180 }
181
182 /* called when pws hangsup */
183 static void on_pws_hangup(void *closure)
184 {
185         struct afb_proto_ws *apw = pws;
186         pws = NULL;
187         afb_proto_ws_unref(apw);
188         attempt_connect_pws(10);
189 }
190
191 /* propagate the call to the service */
192 static void propagate(struct sd_bus_message *smsg, struct json_object *obj, void *closure)
193 {
194         int rc;
195         const char *verb = closure;
196
197         INFO("method %s propagated for %s", verb, json_object_to_json_string(obj));
198         if (!pws)
199                 jbus_reply_error_s(smsg, "disconnected");
200         else {
201                 rc = afb_proto_ws_client_call(pws, verb, obj, sessionid, smsg);
202                 if (rc < 0)
203                         ERROR("calling %s(%s) failed: %m\n", verb, json_object_to_json_string(obj));
204         } 
205 }
206
207 /*
208  * Tiny routine to daemonize the program
209  * Return 0 on success or -1 on failure.
210  */
211 static int daemonize()
212 {
213         int rc = fork();
214         if (rc < 0)
215                 return rc;
216         if (rc)
217                 _exit(0);
218         return 0;
219 }
220
221 /*
222  * Opens a sd-bus connection and returns it in 'ret'.
223  * The sd-bus connexion is intended to be for user if 'isuser'
224  * is not null. The adress is the default address when 'address'
225  * is NULL or, otherwise, the given address.
226  * It might be necessary to pass the address as an argument because
227  * library systemd uses secure_getenv to retrieves the default
228  * addresses and secure_getenv might return NULL in some cases.
229  */
230 static int open_bus(sd_bus **ret, int isuser, const char *address)
231 {
232         sd_bus *b;
233         int rc;
234
235         if (address == NULL)
236                 return (isuser ? sd_bus_default_user : sd_bus_default_system)(ret);
237
238         rc = sd_bus_new(&b);
239         if (rc < 0)
240                 return rc;
241
242         rc = sd_bus_set_address(b, address);
243         if (rc < 0)
244                 goto fail;
245
246         sd_bus_set_bus_client(b, 1);
247
248         rc = sd_bus_start(b);
249         if (rc < 0)
250                 goto fail;
251
252         *ret = b;
253         return 0;
254
255 fail:
256         sd_bus_unref(b);
257         return rc;
258 }
259
260 /*
261  * ENTRY POINT OF AFM-USER-DAEMON
262  */
263 int main(int ac, char **av)
264 {
265         int i, daemon = 0, rc;
266         struct sd_bus *usrbus;
267         const char *usr_bus_addr;
268         const char **iter;
269
270         LOGAUTH(appname);
271
272         /* first interpretation of arguments */
273         usr_bus_addr = NULL;
274         while ((i = getopt_long(ac, av, options_s, options_l, NULL)) >= 0) {
275                 switch (i) {
276                 case 'h':
277                         printf(usagestr, appname);
278                         return 0;
279                 case 'V':
280                         printf(versionstr, appname);
281                         return 0;
282                 case 'q':
283                         if (verbosity)
284                                 verbosity--;
285                         break;
286                 case 'v':
287                         verbosity++;
288                         break;
289                 case 'd':
290                         daemon = 1;
291                         break;
292                 case 'u':
293                         usr_bus_addr = optarg;
294                         break;
295                 case ':':
296                         ERROR("missing argument value");
297                         return 1;
298                 default:
299                         ERROR("unrecognized option");
300                         return 1;
301                 }
302         }
303
304         /* check argument count */
305         if (optind >= ac) {
306                 ERROR("Uri to the framework is missing");
307                 return 1;
308         }
309         if (optind + 1 != ac) {
310                 ERROR("Extra parameters found");
311                 return 1;
312         }
313         uri = av[optind];
314
315         /* init sessionid */
316         asprintf(&sessionid, "%d-%s", (int)getuid(), appname);
317
318         /* daemonize if requested */
319         if (daemon && daemonize()) {
320                 ERROR("daemonization failed");
321                 return 1;
322         }
323
324         /* get systemd objects */
325         rc = sd_event_new(&evloop);
326         if (rc < 0) {
327                 ERROR("can't create event loop");
328                 return 1;
329         }
330         rc = open_bus(&usrbus, 1, usr_bus_addr);
331         if (rc < 0) {
332                 ERROR("can't create user bus");
333                 return 1;
334         }
335         rc = sd_bus_attach_event(usrbus, evloop, 0);
336         if (rc < 0) {
337                 ERROR("can't attach user bus to event loop");
338                 return 1;
339         }
340
341         /* connect to framework */
342         if (!try_connect_pws()) {
343                 ERROR("connection to %s failed: %m\n", uri);
344                 return 1;
345         }
346
347         /* connect to the session bus */
348         user_bus = create_jbus(usrbus, AFM_USER_DBUS_PATH);
349         if (!user_bus) {
350                 ERROR("create_jbus failed");
351                 return 1;
352         }
353
354         /* init services */
355         for (iter = methods ; *iter ; iter ++) {
356                 if (jbus_add_service_j(user_bus, *iter, propagate, (void*)*iter)) {
357                         ERROR("adding services failed");
358                         return 1;
359                 }
360         }
361
362         /* start servicing */
363         if (jbus_start_serving(user_bus) < 0) {
364                 ERROR("can't start server");
365                 return 1;
366         }
367
368         /* run until error */
369         for(;;)
370                 sd_event_run(evloop, (uint64_t)-1);
371         return 0;
372 }
373