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