chiark / gitweb /
core: do not use quotes around virt and arch
[elogind.git] / src / core / machine-id-setup.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 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 <unistd.h>
23 #include <stdio.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <sys/mount.h>
28
29 #include "systemd/sd-id128.h"
30
31 #include "machine-id-setup.h"
32 #include "macro.h"
33 #include "util.h"
34 #include "mkdir.h"
35 #include "log.h"
36 #include "virt.h"
37 #include "fileio.h"
38 #include "path-util.h"
39
40 static int shorten_uuid(char destination[34], const char source[36]) {
41         unsigned i, j;
42
43         assert(destination);
44         assert(source);
45
46         /* Converts a UUID into a machine ID, by lowercasing it and
47          * removing dashes. Validates everything. */
48
49         for (i = 0, j = 0; i < 36 && j < 32; i++) {
50                 int t;
51
52                 t = unhexchar(source[i]);
53                 if (t < 0)
54                         continue;
55
56                 destination[j++] = hexchar(t);
57         }
58
59         if (i != 36 || j != 32)
60                 return -EINVAL;
61
62         destination[32] = '\n';
63         destination[33] = 0;
64         return 0;
65 }
66
67 static int read_machine_id(int fd, char id[34]) {
68         char id_to_validate[34];
69         int r;
70
71         assert(fd >= 0);
72         assert(id);
73
74         /* Reads a machine ID from a file, validates it, and returns
75          * it. The returned ID ends in a newline. */
76
77         r = loop_read_exact(fd, id_to_validate, 33, false);
78         if (r < 0)
79                 return r;
80
81         if (id_to_validate[32] != '\n')
82                 return -EINVAL;
83
84         id_to_validate[32] = 0;
85
86         if (!id128_is_valid(id_to_validate))
87                 return -EINVAL;
88
89         memcpy(id, id_to_validate, 32);
90         id[32] = '\n';
91         id[33] = 0;
92         return 0;
93 }
94
95 static int write_machine_id(int fd, char id[34]) {
96         assert(fd >= 0);
97         assert(id);
98
99         if (lseek(fd, 0, SEEK_SET) < 0)
100                 return -errno;
101
102         return loop_write(fd, id, 33, false);
103 }
104
105 static int generate_machine_id(char id[34], const char *root) {
106         int fd, r;
107         unsigned char *p;
108         sd_id128_t buf;
109         char  *q;
110         const char *vm_id, *dbus_machine_id;
111
112         assert(id);
113
114         if (isempty(root))
115                 dbus_machine_id = "/var/lib/dbus/machine-id";
116         else
117                 dbus_machine_id = strjoina(root, "/var/lib/dbus/machine-id");
118
119         /* First, try reading the D-Bus machine id, unless it is a symlink */
120         fd = open(dbus_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
121         if (fd >= 0) {
122                 r = read_machine_id(fd, id);
123                 safe_close(fd);
124
125                 if (r >= 0) {
126                         log_info("Initializing machine ID from D-Bus machine ID.");
127                         return 0;
128                 }
129         }
130
131         if (isempty(root)) {
132                 /* If that didn't work, see if we are running in a container,
133                  * and a machine ID was passed in via $container_uuid the way
134                  * libvirt/LXC does it */
135                 r = detect_container(NULL);
136                 if (r > 0) {
137                         _cleanup_free_ char *e = NULL;
138
139                         r = getenv_for_pid(1, "container_uuid", &e);
140                         if (r > 0) {
141                                 r = shorten_uuid(id, e);
142                                 if (r >= 0) {
143                                         log_info("Initializing machine ID from container UUID.");
144                                         return 0;
145                                 }
146                         }
147
148                 } else {
149                         /* If we are not running in a container, see if we are
150                          * running in qemu/kvm and a machine ID was passed in
151                          * via -uuid on the qemu/kvm command line */
152
153                         r = detect_vm(&vm_id);
154                         if (r > 0 && streq(vm_id, "kvm")) {
155                                 char uuid[36];
156
157                                 fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
158                                 if (fd >= 0) {
159                                         r = loop_read_exact(fd, uuid, 36, false);
160                                         safe_close(fd);
161
162                                         if (r >= 0) {
163                                                 r = shorten_uuid(id, uuid);
164                                                 if (r >= 0) {
165                                                         log_info("Initializing machine ID from KVM UUID.");
166                                                         return 0;
167                                                 }
168                                         }
169                                 }
170                         }
171                 }
172         }
173
174         /* If that didn't work, generate a random machine id */
175         r = sd_id128_randomize(&buf);
176         if (r < 0)
177                 return log_error_errno(r, "Failed to open /dev/urandom: %m");
178
179         for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) {
180                 q[0] = hexchar(*p >> 4);
181                 q[1] = hexchar(*p & 15);
182         }
183
184         id[32] = '\n';
185         id[33] = 0;
186
187         log_info("Initializing machine ID from random generator.");
188
189         return 0;
190 }
191
192 int machine_id_setup(const char *root) {
193         const char *etc_machine_id, *run_machine_id;
194         _cleanup_close_ int fd = -1;
195         bool writable = true;
196         char id[34]; /* 32 + \n + \0 */
197         int r;
198
199         if (isempty(root))  {
200                 etc_machine_id = "/etc/machine-id";
201                 run_machine_id = "/run/machine-id";
202         } else {
203                 char *x;
204
205                 x = strjoina(root, "/etc/machine-id");
206                 etc_machine_id = path_kill_slashes(x);
207
208                 x = strjoina(root, "/run/machine-id");
209                 run_machine_id = path_kill_slashes(x);
210         }
211
212         RUN_WITH_UMASK(0000) {
213                 /* We create this 0444, to indicate that this isn't really
214                  * something you should ever modify. Of course, since the file
215                  * will be owned by root it doesn't matter much, but maybe
216                  * people look. */
217
218                 mkdir_parents(etc_machine_id, 0755);
219                 fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
220                 if (fd < 0) {
221                         int old_errno = errno;
222
223                         fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY);
224                         if (fd < 0) {
225                                 if (old_errno == EROFS && errno == ENOENT)
226                                         log_error_errno(errno,
227                                                   "System cannot boot: Missing /etc/machine-id and /etc is mounted read-only.\n"
228                                                   "Booting up is supported only when:\n"
229                                                   "1) /etc/machine-id exists and is populated.\n"
230                                                   "2) /etc/machine-id exists and is empty.\n"
231                                                   "3) /etc/machine-id is missing and /etc is writable.\n");
232                                 else
233                                         log_error_errno(errno, "Cannot open %s: %m", etc_machine_id);
234
235                                 return -errno;
236                         }
237
238                         writable = false;
239                 }
240         }
241
242         if (read_machine_id(fd, id) >= 0)
243                 return 0;
244
245         /* Hmm, so, the id currently stored is not useful, then let's
246          * generate one */
247
248         r = generate_machine_id(id, root);
249         if (r < 0)
250                 return r;
251
252         if (writable)
253                 if (write_machine_id(fd, id) >= 0)
254                         return 0;
255
256         fd = safe_close(fd);
257
258         /* Hmm, we couldn't write it? So let's write it to
259          * /run/machine-id as a replacement */
260
261         RUN_WITH_UMASK(0022) {
262                 r = write_string_file(run_machine_id, id);
263         }
264         if (r < 0) {
265                 (void) unlink(run_machine_id);
266                 return log_error_errno(r, "Cannot write %s: %m", run_machine_id);
267         }
268
269         /* And now, let's mount it over */
270         if (mount(run_machine_id, etc_machine_id, NULL, MS_BIND, NULL) < 0) {
271                 (void) unlink_noerrno(run_machine_id);
272                 return log_error_errno(errno, "Failed to mount %s: %m", etc_machine_id);
273         }
274
275         log_info("Installed transient %s file.", etc_machine_id);
276
277         /* Mark the mount read-only */
278         if (mount(NULL, etc_machine_id, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL) < 0)
279                 log_warning_errno(errno, "Failed to make transient %s read-only: %m", etc_machine_id);
280
281         return 0;
282 }
283
284 int machine_id_commit(const char *root) {
285         _cleanup_close_ int fd = -1, initial_mntns_fd = -1;
286         const char *etc_machine_id;
287         char id[34]; /* 32 + \n + \0 */
288         int r;
289
290         if (isempty(root))
291                 etc_machine_id = "/etc/machine-id";
292         else {
293                 char *x;
294
295                 x = strjoina(root, "/etc/machine-id");
296                 etc_machine_id = path_kill_slashes(x);
297         }
298
299         r = path_is_mount_point(etc_machine_id, false);
300         if (r < 0)
301                 return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id);
302         if (r == 0) {
303                 log_debug("%s is is not a mount point. Nothing to do.", etc_machine_id);
304                 return 0;
305         }
306
307         /* Read existing machine-id */
308         fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY);
309         if (fd < 0)
310                 return log_error_errno(errno, "Cannot open %s: %m", etc_machine_id);
311
312         r = read_machine_id(fd, id);
313         if (r < 0)
314                 return log_error_errno(r, "We didn't find a valid machine ID in %s.", etc_machine_id);
315
316         r = is_fd_on_temporary_fs(fd);
317         if (r < 0)
318                 return log_error_errno(r, "Failed to determine whether %s is on a temporary file system: %m", etc_machine_id);
319         if (r == 0) {
320                 log_error("%s is not on a temporary file system.", etc_machine_id);
321                 return -EROFS;
322         }
323
324         fd = safe_close(fd);
325
326         /* Store current mount namespace */
327         r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL);
328         if (r < 0)
329                 return log_error_errno(r, "Can't fetch current mount namespace: %m");
330
331         /* Switch to a new mount namespace, isolate ourself and unmount etc_machine_id in our new namespace */
332         if (unshare(CLONE_NEWNS) < 0)
333                 return log_error_errno(errno, "Failed to enter new namespace: %m");
334
335         if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0)
336                 return log_error_errno(errno, "Couldn't make-rslave / mountpoint in our private namespace: %m");
337
338         if (umount(etc_machine_id) < 0)
339                 return log_error_errno(errno, "Failed to unmount transient %s file in our private namespace: %m", etc_machine_id);
340
341         /* Update a persistent version of etc_machine_id */
342         fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
343         if (fd < 0)
344                 return log_error_errno(errno, "Cannot open for writing %s. This is mandatory to get a persistent machine-id: %m", etc_machine_id);
345
346         r = write_machine_id(fd, id);
347         if (r < 0)
348                 return log_error_errno(r, "Cannot write %s: %m", etc_machine_id);
349
350         fd = safe_close(fd);
351
352         /* Return to initial namespace and proceed a lazy tmpfs unmount */
353         r = namespace_enter(-1, initial_mntns_fd, -1, -1);
354         if (r < 0)
355                 return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id);
356
357         if (umount2(etc_machine_id, MNT_DETACH) < 0)
358                 return log_warning_errno(errno, "Failed to unmount transient %s file: %m.\nWe keep that mount until next reboot.", etc_machine_id);
359
360         return 0;
361 }