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