add more instance state handling code
[project/procd.git] / service.c
1 #include <libubox/avl-cmp.h>
2 #include "procd.h"
3 #include "service.h"
4
5 struct avl_tree services;
6 static struct blob_buf b;
7
8 static void
9 start_instance(struct service_instance *in)
10 {
11         in->restart = false;
12 }
13
14 static void
15 instance_timeout(struct uloop_timeout *t)
16 {
17         struct service_instance *in;
18
19         in = container_of(t, struct service_instance, timeout);
20         kill(in->proc.pid, SIGKILL);
21         uloop_process_delete(&in->proc);
22         in->proc.cb(&in->proc, -1);
23 }
24
25 static void
26 instance_exit(struct uloop_process *p, int ret)
27 {
28         struct service_instance *in;
29
30         in = container_of(p, struct service_instance, proc);
31         uloop_timeout_cancel(&in->timeout);
32         if (in->restart)
33                 start_instance(in);
34 }
35
36 static void
37 stop_instance(struct service_instance *in, bool restart)
38 {
39         if (!in->proc.pending)
40                 return;
41
42         kill(in->proc.pid, SIGTERM);
43 }
44
45 static bool
46 instance_config_changed(struct service_instance *in, struct service_instance *in_new)
47 {
48         int len = blob_pad_len(in->config);
49
50         if (len != blob_pad_len(in_new->config))
51                 return true;
52
53         if (memcmp(in->config, in_new->config, blob_pad_len(in->config)) != 0)
54                 return true;
55
56         return false;
57 }
58
59 static bool
60 update_instance(struct service_instance *in, struct service_instance *in_new)
61 {
62         bool changed = instance_config_changed(in, in_new);
63
64         in->config = in_new->config;
65         if (!changed)
66                 return false;
67
68         stop_instance(in, true);
69         return true;
70 }
71
72 static void
73 free_instance(struct service_instance *in)
74 {
75         uloop_process_delete(&in->proc);
76         uloop_timeout_cancel(&in->timeout);
77         free(in);
78 }
79
80 static void
81 init_instance(struct service_instance *in, struct blob_attr *config)
82 {
83         in->config = config;
84         in->timeout.cb = instance_timeout;
85         in->proc.cb = instance_exit;
86 }
87
88 static void
89 service_instance_add(struct service *s, struct blob_attr *attr)
90 {
91         struct service_instance *in;
92         const char *name = blobmsg_name(attr);
93
94         if (blobmsg_type(attr) != BLOBMSG_TYPE_TABLE)
95                 return;
96
97         in = calloc(1, sizeof(*in));
98         if (!in)
99                 return;
100
101         init_instance(in, attr);
102         vlist_add(&s->instances, &in->node, (void *) name);
103 }
104
105 static void
106 service_instance_update(struct vlist_tree *tree, struct vlist_node *node_new,
107                         struct vlist_node *node_old)
108 {
109         struct service_instance *in_o = NULL, *in_n = NULL;
110
111         if (node_old)
112                 in_o = container_of(node_old, struct service_instance, node);
113
114         if (node_new)
115                 in_n = container_of(node_new, struct service_instance, node);
116
117         if (in_o && in_n) {
118                 update_instance(in_o, in_n);
119                 free_instance(in_n);
120         } else if (in_o) {
121                 stop_instance(in_o, false);
122                 free_instance(in_o);
123         } else if (in_n) {
124                 start_instance(in_n);
125         }
126 }
127
128 static struct service *
129 service_alloc(const char *name)
130 {
131         struct service *s;
132
133         s = calloc(1, sizeof(*s));
134         vlist_init(&s->instances, avl_strcmp, service_instance_update);
135         s->instances.keep_old = true;
136
137         return s;
138 }
139
140 enum {
141         SERVICE_ATTR_NAME,
142         SERVICE_ATTR_SCRIPT,
143         SERVICE_ATTR_INSTANCES,
144         __SERVICE_ATTR_MAX
145 };
146
147 static const struct blobmsg_policy service_attrs[__SERVICE_ATTR_MAX] = {
148         [SERVICE_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING },
149         [SERVICE_ATTR_SCRIPT] = { "script", BLOBMSG_TYPE_STRING },
150         [SERVICE_ATTR_INSTANCES] = { "instances", BLOBMSG_TYPE_TABLE },
151 };
152
153
154 static int
155 service_update(struct service *s, struct blob_attr *config, struct blob_attr **tb)
156 {
157         struct blob_attr *old_config = s->config;
158         struct blob_attr *cur;
159         int rem;
160
161         /* only the pointer changes, the content stays the same,
162          * no avl update necessary */
163         s->name = s->avl.key = blobmsg_data(tb[SERVICE_ATTR_NAME]);
164         s->config = config;
165
166         if (tb[SERVICE_ATTR_INSTANCES]) {
167                 vlist_update(&s->instances);
168                 blobmsg_for_each_attr(cur, tb[SERVICE_ATTR_INSTANCES], rem) {
169                         service_instance_add(s, cur);
170                 }
171                 vlist_flush(&s->instances);
172         }
173
174         free(old_config);
175
176         return 0;
177 }
178
179 static void
180 service_delete(struct service *s)
181 {
182         vlist_flush_all(&s->instances);
183         avl_delete(&services, &s->avl);
184         free(s->config);
185         free(s);
186 }
187
188 static int
189 service_handle_set(struct ubus_context *ctx, struct ubus_object *obj,
190                    struct ubus_request_data *req, const char *method,
191                    struct blob_attr *msg)
192 {
193         struct blob_attr *tb[__SERVICE_ATTR_MAX], *cur;
194         struct service *s = NULL;
195         const char *name;
196         int ret = UBUS_STATUS_INVALID_ARGUMENT;
197
198         msg = blob_memdup(msg);
199         if (!msg)
200                 return UBUS_STATUS_UNKNOWN_ERROR;
201
202         blobmsg_parse(service_attrs, __SERVICE_ATTR_MAX, tb, blob_data(msg), blob_len(msg));
203         cur = tb[SERVICE_ATTR_NAME];
204         if (!cur)
205                 goto free;
206
207         name = blobmsg_data(cur);
208
209         s = avl_find_element(&services, name, s, avl);
210         if (s)
211                 return service_update(s, msg, tb);
212
213         s = service_alloc(name);
214         if (!s)
215                 return UBUS_STATUS_UNKNOWN_ERROR;
216
217         ret = service_update(s, msg, tb);
218         if (ret)
219                 goto free;
220
221         avl_insert(&services, &s->avl);
222
223         return 0;
224
225 free:
226         free(msg);
227         return ret;
228 }
229
230 static int
231 service_handle_list(struct ubus_context *ctx, struct ubus_object *obj,
232                     struct ubus_request_data *req, const char *method,
233                     struct blob_attr *msg)
234 {
235         struct service *s;
236
237         blob_buf_init(&b, 0);
238         avl_for_each_element(&services, s, avl) {
239                 void *c;
240
241                 c = blobmsg_open_table(&b, s->name);
242                 blobmsg_close_table(&b, c);
243         }
244
245         ubus_send_reply(ctx, req, b.head);
246
247         return 0;
248 }
249
250 enum {
251         SERVICE_DEL_NAME,
252         __SERVICE_DEL_MAX,
253 };
254
255 static const struct blobmsg_policy service_del_attrs[__SERVICE_DEL_MAX] = {
256         [SERVICE_DEL_NAME] = { "name", BLOBMSG_TYPE_STRING },
257 };
258
259
260 static int
261 service_handle_delete(struct ubus_context *ctx, struct ubus_object *obj,
262                     struct ubus_request_data *req, const char *method,
263                     struct blob_attr *msg)
264 {
265         struct blob_attr *tb[__SERVICE_DEL_MAX], *cur;
266         struct service *s, *tmp;
267
268         blobmsg_parse(service_del_attrs, __SERVICE_DEL_MAX, tb, blob_data(msg), blob_len(msg));
269
270         cur = tb[SERVICE_ATTR_NAME];
271         if (!cur) {
272                 avl_for_each_element_safe(&services, s, avl, tmp)
273                         service_delete(s);
274                 return 0;
275         }
276
277         s = avl_find_element(&services, blobmsg_data(cur), s, avl);
278         if (!s)
279                 return UBUS_STATUS_NOT_FOUND;
280
281         service_delete(s);
282         return 0;
283 }
284
285 static struct ubus_method main_object_methods[] = {
286         { .name = "list", .handler = service_handle_list },
287         { .name = "set", .handler = service_handle_set },
288         { .name = "delete", .handler = service_handle_delete },
289 };
290
291 static struct ubus_object_type main_object_type =
292         UBUS_OBJECT_TYPE("service", main_object_methods);
293
294 static struct ubus_object main_object = {
295         .name = "service",
296         .type = &main_object_type,
297         .methods = main_object_methods,
298         .n_methods = ARRAY_SIZE(main_object_methods),
299 };
300
301 void procd_init_service(struct ubus_context *ctx)
302 {
303         avl_init(&services, avl_strcmp, false, NULL);
304         ubus_add_object(ctx, &main_object);
305 }