chiark / gitweb /
machined: split out machine registration stuff from logind
[elogind.git] / src / machine / machined.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <pwd.h>
24 #include <fcntl.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/epoll.h>
28
29 #include <systemd/sd-daemon.h>
30
31 #include "machined.h"
32 #include "dbus-common.h"
33 #include "dbus-loop.h"
34 #include "strv.h"
35 #include "conf-parser.h"
36 #include "mkdir.h"
37 #include "cgroup-util.h"
38
39 Manager *manager_new(void) {
40         Manager *m;
41
42         m = new0(Manager, 1);
43         if (!m)
44                 return NULL;
45
46         m->bus_fd = -1;
47         m->epoll_fd = -1;
48
49         m->machines = hashmap_new(string_hash_func, string_compare_func);
50         m->machine_units = hashmap_new(string_hash_func, string_compare_func);
51
52         if (!m->machines || !m->machine_units) {
53                 manager_free(m);
54                 return NULL;
55         }
56
57         return m;
58 }
59
60 void manager_free(Manager *m) {
61         Machine *machine;
62
63         assert(m);
64
65         while ((machine = hashmap_first(m->machines)))
66                 machine_free(machine);
67
68         hashmap_free(m->machines);
69         hashmap_free(m->machine_units);
70
71         if (m->bus) {
72                 dbus_connection_flush(m->bus);
73                 dbus_connection_close(m->bus);
74                 dbus_connection_unref(m->bus);
75         }
76
77         if (m->bus_fd >= 0)
78                 close_nointr_nofail(m->bus_fd);
79
80         if (m->epoll_fd >= 0)
81                 close_nointr_nofail(m->epoll_fd);
82
83         free(m);
84 }
85
86 int manager_add_machine(Manager *m, const char *name, Machine **_machine) {
87         Machine *machine;
88
89         assert(m);
90         assert(name);
91
92         machine = hashmap_get(m->machines, name);
93         if (machine) {
94                 if (_machine)
95                         *_machine = machine;
96
97                 return 0;
98         }
99
100         machine = machine_new(m, name);
101         if (!m)
102                 return -ENOMEM;
103
104         if (_machine)
105                 *_machine = machine;
106
107         return 0;
108 }
109
110 int manager_enumerate_machines(Manager *m) {
111         _cleanup_closedir_ DIR *d = NULL;
112         struct dirent *de;
113         int r = 0;
114
115         assert(m);
116
117         /* Read in machine data stored on disk */
118         d = opendir("/run/systemd/machines");
119         if (!d) {
120                 if (errno == ENOENT)
121                         return 0;
122
123                 log_error("Failed to open /run/systemd/machines: %m");
124                 return -errno;
125         }
126
127         FOREACH_DIRENT(de, d, return -errno) {
128                 struct Machine *machine;
129                 int k;
130
131                 if (!dirent_is_file(de))
132                         continue;
133
134                 k = manager_add_machine(m, de->d_name, &machine);
135                 if (k < 0) {
136                         log_error("Failed to add machine by file name %s: %s", de->d_name, strerror(-k));
137
138                         r = k;
139                         continue;
140                 }
141
142                 machine_add_to_gc_queue(machine);
143
144                 k = machine_load(machine);
145                 if (k < 0)
146                         r = k;
147         }
148
149         return r;
150 }
151
152 int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) {
153         _cleanup_free_ char *unit = NULL;
154         Machine *mm;
155         int r;
156
157         assert(m);
158         assert(pid >= 1);
159         assert(machine);
160
161         r = cg_pid_get_unit(pid, &unit);
162         if (r < 0)
163                 return r;
164
165         mm = hashmap_get(m->machine_units, unit);
166         if (!mm)
167                 return 0;
168
169         *machine = mm;
170         return 1;
171 }
172
173 static int manager_connect_bus(Manager *m) {
174         DBusError error;
175         int r;
176         struct epoll_event ev = {
177                 .events = EPOLLIN,
178                 .data.u32 = FD_BUS,
179         };
180
181         assert(m);
182         assert(!m->bus);
183         assert(m->bus_fd < 0);
184
185         dbus_error_init(&error);
186
187         m->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
188         if (!m->bus) {
189                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
190                 r = -ECONNREFUSED;
191                 goto fail;
192         }
193
194         if (!dbus_connection_register_object_path(m->bus, "/org/freedesktop/machine1", &bus_manager_vtable, m) ||
195             !dbus_connection_register_fallback(m->bus, "/org/freedesktop/machine1/machine", &bus_machine_vtable, m) ||
196             !dbus_connection_add_filter(m->bus, bus_message_filter, m, NULL)) {
197                 r = log_oom();
198                 goto fail;
199         }
200
201         dbus_bus_add_match(m->bus,
202                            "type='signal',"
203                            "sender='org.freedesktop.systemd1',"
204                            "interface='org.freedesktop.systemd1.Manager',"
205                            "member='JobRemoved',"
206                            "path='/org/freedesktop/systemd1'",
207                            &error);
208         if (dbus_error_is_set(&error)) {
209                 log_error("Failed to add match for JobRemoved: %s", bus_error_message(&error));
210                 dbus_error_free(&error);
211         }
212
213         dbus_bus_add_match(m->bus,
214                            "type='signal',"
215                            "sender='org.freedesktop.systemd1',"
216                            "interface='org.freedesktop.DBus.Properties',"
217                            "member='PropertiesChanged'",
218                            &error);
219         if (dbus_error_is_set(&error)) {
220                 log_error("Failed to add match for PropertiesChanged: %s", bus_error_message(&error));
221                 dbus_error_free(&error);
222         }
223
224         r = bus_method_call_with_reply(
225                         m->bus,
226                         "org.freedesktop.systemd1",
227                         "/org/freedesktop/systemd1",
228                         "org.freedesktop.systemd1.Manager",
229                         "Subscribe",
230                         NULL,
231                         &error,
232                         DBUS_TYPE_INVALID);
233         if (r < 0) {
234                 log_error("Failed to enable subscription: %s", bus_error(&error, r));
235                 dbus_error_free(&error);
236         }
237
238         r = dbus_bus_request_name(m->bus, "org.freedesktop.machine1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
239         if (dbus_error_is_set(&error)) {
240                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
241                 r = -EIO;
242                 goto fail;
243         }
244
245         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
246                 log_error("Failed to acquire name.");
247                 r = -EEXIST;
248                 goto fail;
249         }
250
251         m->bus_fd = bus_loop_open(m->bus);
252         if (m->bus_fd < 0) {
253                 r = m->bus_fd;
254                 goto fail;
255         }
256
257         if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->bus_fd, &ev) < 0)
258                 goto fail;
259
260         return 0;
261
262 fail:
263         dbus_error_free(&error);
264
265         return r;
266 }
267
268 void manager_gc(Manager *m, bool drop_not_started) {
269         Machine *machine;
270
271         assert(m);
272
273         while ((machine = m->machine_gc_queue)) {
274                 LIST_REMOVE(Machine, gc_queue, m->machine_gc_queue, machine);
275                 machine->in_gc_queue = false;
276
277                 if (machine_check_gc(machine, drop_not_started) == 0) {
278                         machine_stop(machine);
279                         machine_free(machine);
280                 }
281         }
282 }
283
284 int manager_startup(Manager *m) {
285         int r;
286         Machine *machine;
287         Iterator i;
288
289         assert(m);
290         assert(m->epoll_fd <= 0);
291
292         m->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
293         if (m->epoll_fd < 0)
294                 return -errno;
295
296         /* Connect to the bus */
297         r = manager_connect_bus(m);
298         if (r < 0)
299                 return r;
300
301         /* Deserialize state */
302         manager_enumerate_machines(m);
303
304         /* Remove stale objects before we start them */
305         manager_gc(m, false);
306
307         /* And start everything */
308         HASHMAP_FOREACH(machine, m->machines, i)
309                 machine_start(machine);
310
311         return 0;
312 }
313
314 int manager_run(Manager *m) {
315         assert(m);
316
317         for (;;) {
318                 struct epoll_event event;
319                 int n;
320
321                 manager_gc(m, true);
322
323                 if (dbus_connection_dispatch(m->bus) != DBUS_DISPATCH_COMPLETE)
324                         continue;
325
326                 manager_gc(m, true);
327
328                 n = epoll_wait(m->epoll_fd, &event, 1, -1);
329                 if (n < 0) {
330                         if (errno == EINTR || errno == EAGAIN)
331                                 continue;
332
333                         log_error("epoll() failed: %m");
334                         return -errno;
335                 }
336
337                 if (n == 0)
338                         continue;
339
340                 switch (event.data.u32) {
341
342                 case FD_BUS:
343                         bus_loop_dispatch(m->bus_fd);
344                         break;
345
346                 default:
347                         assert_not_reached("Unknown fd");
348                 }
349         }
350
351         return 0;
352 }
353
354 int main(int argc, char *argv[]) {
355         Manager *m = NULL;
356         int r;
357
358         log_set_target(LOG_TARGET_AUTO);
359         log_set_facility(LOG_AUTH);
360         log_parse_environment();
361         log_open();
362
363         umask(0022);
364
365         if (argc != 1) {
366                 log_error("This program takes no arguments.");
367                 r = -EINVAL;
368                 goto finish;
369         }
370
371         /* Always create the directories people can create inotify
372          * watches in. Note that some applications might check for the
373          * existence of /run/systemd/seats/ to determine whether
374          * machined is available, so please always make sure this check
375          * stays in. */
376         mkdir_label("/run/systemd/machines", 0755);
377
378         m = manager_new();
379         if (!m) {
380                 r = log_oom();
381                 goto finish;
382         }
383
384         r = manager_startup(m);
385         if (r < 0) {
386                 log_error("Failed to fully start up daemon: %s", strerror(-r));
387                 goto finish;
388         }
389
390         log_debug("systemd-machined running as pid %lu", (unsigned long) getpid());
391
392         sd_notify(false,
393                   "READY=1\n"
394                   "STATUS=Processing requests...");
395
396         r = manager_run(m);
397
398         log_debug("systemd-machined stopped as pid %lu", (unsigned long) getpid());
399
400 finish:
401         sd_notify(false,
402                   "STATUS=Shutting down...");
403
404         if (m)
405                 manager_free(m);
406
407         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
408 }