chiark / gitweb /
ca84f8a0043f14f5b29afd4d2eac126de6e2c72a
[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
38 Manager *manager_new(void) {
39         Manager *m;
40
41         m = new0(Manager, 1);
42         if (!m)
43                 return NULL;
44
45         m->bus_fd = -1;
46         m->epoll_fd = -1;
47
48         m->machines = hashmap_new(string_hash_func, string_compare_func);
49         m->machine_units = hashmap_new(string_hash_func, string_compare_func);
50
51         if (!m->machines || !m->machine_units) {
52                 manager_free(m);
53                 return NULL;
54         }
55
56         return m;
57 }
58
59 void manager_free(Manager *m) {
60         Machine *machine;
61
62         assert(m);
63
64         while ((machine = hashmap_first(m->machines)))
65                 machine_free(machine);
66
67         hashmap_free(m->machines);
68         hashmap_free(m->machine_units);
69
70         if (m->bus) {
71                 dbus_connection_flush(m->bus);
72                 dbus_connection_close(m->bus);
73                 dbus_connection_unref(m->bus);
74         }
75
76         if (m->bus_fd >= 0)
77                 close_nointr_nofail(m->bus_fd);
78
79         if (m->epoll_fd >= 0)
80                 close_nointr_nofail(m->epoll_fd);
81
82         free(m);
83 }
84
85 int manager_enumerate_machines(Manager *m) {
86         _cleanup_closedir_ DIR *d = NULL;
87         struct dirent *de;
88         int r = 0;
89
90         assert(m);
91
92         /* Read in machine data stored on disk */
93         d = opendir("/run/systemd/machines");
94         if (!d) {
95                 if (errno == ENOENT)
96                         return 0;
97
98                 log_error("Failed to open /run/systemd/machines: %m");
99                 return -errno;
100         }
101
102         FOREACH_DIRENT(de, d, return -errno) {
103                 struct Machine *machine;
104                 int k;
105
106                 if (!dirent_is_file(de))
107                         continue;
108
109                 k = manager_add_machine(m, de->d_name, &machine);
110                 if (k < 0) {
111                         log_error("Failed to add machine by file name %s: %s", de->d_name, strerror(-k));
112
113                         r = k;
114                         continue;
115                 }
116
117                 machine_add_to_gc_queue(machine);
118
119                 k = machine_load(machine);
120                 if (k < 0)
121                         r = k;
122         }
123
124         return r;
125 }
126
127 static int manager_connect_bus(Manager *m) {
128         DBusError error;
129         int r;
130         struct epoll_event ev = {
131                 .events = EPOLLIN,
132                 .data.u32 = FD_BUS,
133         };
134
135         assert(m);
136         assert(!m->bus);
137         assert(m->bus_fd < 0);
138
139         dbus_error_init(&error);
140
141         m->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
142         if (!m->bus) {
143                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
144                 r = -ECONNREFUSED;
145                 goto fail;
146         }
147
148         if (!dbus_connection_register_object_path(m->bus, "/org/freedesktop/machine1", &bus_manager_vtable, m) ||
149             !dbus_connection_register_fallback(m->bus, "/org/freedesktop/machine1/machine", &bus_machine_vtable, m) ||
150             !dbus_connection_add_filter(m->bus, bus_message_filter, m, NULL)) {
151                 r = log_oom();
152                 goto fail;
153         }
154
155         dbus_bus_add_match(m->bus,
156                            "type='signal',"
157                            "sender='org.freedesktop.systemd1',"
158                            "interface='org.freedesktop.systemd1.Manager',"
159                            "member='JobRemoved',"
160                            "path='/org/freedesktop/systemd1'",
161                            &error);
162         if (dbus_error_is_set(&error)) {
163                 log_error("Failed to add match for JobRemoved: %s", bus_error_message(&error));
164                 dbus_error_free(&error);
165         }
166
167         dbus_bus_add_match(m->bus,
168                            "type='signal',"
169                            "sender='org.freedesktop.systemd1',"
170                            "interface='org.freedesktop.systemd1.Manager',"
171                            "member='UnitRemoved',"
172                            "path='/org/freedesktop/systemd1'",
173                            &error);
174         if (dbus_error_is_set(&error)) {
175                 log_error("Failed to add match for UnitRemoved: %s", bus_error_message(&error));
176                 dbus_error_free(&error);
177         }
178
179         dbus_bus_add_match(m->bus,
180                            "type='signal',"
181                            "sender='org.freedesktop.systemd1',"
182                            "interface='org.freedesktop.DBus.Properties',"
183                            "member='PropertiesChanged'",
184                            &error);
185         if (dbus_error_is_set(&error)) {
186                 log_error("Failed to add match for PropertiesChanged: %s", bus_error_message(&error));
187                 dbus_error_free(&error);
188         }
189
190         dbus_bus_add_match(m->bus,
191                            "type='signal',"
192                            "sender='org.freedesktop.systemd1',"
193                            "interface='org.freedesktop.systemd1.Manager',"
194                            "member='Reloading',"
195                            "path='/org/freedesktop/systemd1'",
196                            &error);
197         if (dbus_error_is_set(&error)) {
198                 log_error("Failed to add match for Reloading: %s", bus_error_message(&error));
199                 dbus_error_free(&error);
200         }
201
202         r = bus_method_call_with_reply(
203                         m->bus,
204                         "org.freedesktop.systemd1",
205                         "/org/freedesktop/systemd1",
206                         "org.freedesktop.systemd1.Manager",
207                         "Subscribe",
208                         NULL,
209                         &error,
210                         DBUS_TYPE_INVALID);
211         if (r < 0) {
212                 log_error("Failed to enable subscription: %s", bus_error(&error, r));
213                 dbus_error_free(&error);
214         }
215
216         r = dbus_bus_request_name(m->bus, "org.freedesktop.machine1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
217         if (dbus_error_is_set(&error)) {
218                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
219                 r = -EIO;
220                 goto fail;
221         }
222
223         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
224                 log_error("Failed to acquire name.");
225                 r = -EEXIST;
226                 goto fail;
227         }
228
229         m->bus_fd = bus_loop_open(m->bus);
230         if (m->bus_fd < 0) {
231                 r = m->bus_fd;
232                 goto fail;
233         }
234
235         if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->bus_fd, &ev) < 0)
236                 goto fail;
237
238         return 0;
239
240 fail:
241         dbus_error_free(&error);
242
243         return r;
244 }
245
246 void manager_gc(Manager *m, bool drop_not_started) {
247         Machine *machine;
248
249         assert(m);
250
251         while ((machine = m->machine_gc_queue)) {
252                 LIST_REMOVE(gc_queue, m->machine_gc_queue, machine);
253                 machine->in_gc_queue = false;
254
255                 if (machine_check_gc(machine, drop_not_started) == 0) {
256                         machine_stop(machine);
257                         machine_free(machine);
258                 }
259         }
260 }
261
262 int manager_startup(Manager *m) {
263         int r;
264         Machine *machine;
265         Iterator i;
266
267         assert(m);
268         assert(m->epoll_fd <= 0);
269
270         m->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
271         if (m->epoll_fd < 0)
272                 return -errno;
273
274         /* Connect to the bus */
275         r = manager_connect_bus(m);
276         if (r < 0)
277                 return r;
278
279         /* Deserialize state */
280         manager_enumerate_machines(m);
281
282         /* Remove stale objects before we start them */
283         manager_gc(m, false);
284
285         /* And start everything */
286         HASHMAP_FOREACH(machine, m->machines, i)
287                 machine_start(machine, NULL);
288
289         return 0;
290 }
291
292 int manager_run(Manager *m) {
293         assert(m);
294
295         for (;;) {
296                 struct epoll_event event;
297                 int n;
298
299                 manager_gc(m, true);
300
301                 if (dbus_connection_dispatch(m->bus) != DBUS_DISPATCH_COMPLETE)
302                         continue;
303
304                 manager_gc(m, true);
305
306                 n = epoll_wait(m->epoll_fd, &event, 1, -1);
307                 if (n < 0) {
308                         if (errno == EINTR || errno == EAGAIN)
309                                 continue;
310
311                         log_error("epoll() failed: %m");
312                         return -errno;
313                 }
314
315                 if (n == 0)
316                         continue;
317
318                 switch (event.data.u32) {
319
320                 case FD_BUS:
321                         bus_loop_dispatch(m->bus_fd);
322                         break;
323
324                 default:
325                         assert_not_reached("Unknown fd");
326                 }
327         }
328
329         return 0;
330 }
331
332 int main(int argc, char *argv[]) {
333         Manager *m = NULL;
334         int r;
335
336         log_set_target(LOG_TARGET_AUTO);
337         log_set_facility(LOG_AUTH);
338         log_parse_environment();
339         log_open();
340
341         umask(0022);
342
343         if (argc != 1) {
344                 log_error("This program takes no arguments.");
345                 r = -EINVAL;
346                 goto finish;
347         }
348
349         /* Always create the directories people can create inotify
350          * watches in. Note that some applications might check for the
351          * existence of /run/systemd/seats/ to determine whether
352          * machined is available, so please always make sure this check
353          * stays in. */
354         mkdir_label("/run/systemd/machines", 0755);
355
356         m = manager_new();
357         if (!m) {
358                 r = log_oom();
359                 goto finish;
360         }
361
362         r = manager_startup(m);
363         if (r < 0) {
364                 log_error("Failed to fully start up daemon: %s", strerror(-r));
365                 goto finish;
366         }
367
368         log_debug("systemd-machined running as pid %lu", (unsigned long) getpid());
369
370         sd_notify(false,
371                   "READY=1\n"
372                   "STATUS=Processing requests...");
373
374         r = manager_run(m);
375
376         log_debug("systemd-machined stopped as pid %lu", (unsigned long) getpid());
377
378 finish:
379         sd_notify(false,
380                   "STATUS=Shutting down...");
381
382         if (m)
383                 manager_free(m);
384
385         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
386 }