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