chiark / gitweb /
machine-id: add --root option to operate on an alternate fs tree
[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 <stdlib.h>
27 #include <fcntl.h>
28 #include <sys/mount.h>
29
30 #include <systemd/sd-id128.h>
31
32 #include "machine-id-setup.h"
33 #include "macro.h"
34 #include "util.h"
35 #include "mkdir.h"
36 #include "log.h"
37 #include "virt.h"
38 #include "fileio.h"
39
40 static int shorten_uuid(char destination[36], const char *source) {
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         ssize_t k;
68         const char *vm_id;
69         _cleanup_free_ char *dbus_machine_id = NULL;
70
71         assert(id);
72
73         if (asprintf(&dbus_machine_id, "%s/var/lib/dbus/machine-id", root) < 0)
74                 return log_oom();
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                 k = loop_read(fd, id, 33, false);
80                 close_nointr_nofail(fd);
81
82                 if (k == 33 && id[32] == '\n') {
83
84                         id[32] = 0;
85                         if (id128_is_valid(id)) {
86                                 id[32] = '\n';
87                                 id[33] = 0;
88
89                                 log_info("Initializing machine ID from D-Bus machine ID.");
90                                 return 0;
91                         }
92                 }
93         }
94
95         /* If that didn't work, see if we are running in qemu/kvm and a
96          * machine ID was passed in via -uuid on the qemu/kvm command
97          * line */
98
99         r = detect_vm(&vm_id);
100         if (r > 0 && streq(vm_id, "kvm")) {
101                 char uuid[37];
102
103                 fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
104                 if (fd >= 0) {
105                         k = loop_read(fd, uuid, 36, false);
106                         close_nointr_nofail(fd);
107
108                         if (k >= 36) {
109                                 r = shorten_uuid(id, uuid);
110                                 if (r >= 0) {
111                                         log_info("Initializing machine ID from KVM UUID.");
112                                         return 0;
113                                 }
114                         }
115                 }
116         }
117
118         /* If that didn't work either, see if we are running in a
119          * container, and a machine ID was passed in via
120          * $container_uuid the way libvirt/LXC does it */
121         r = detect_container(NULL);
122         if (r > 0) {
123                 _cleanup_free_ char *e = NULL;
124
125                 r = getenv_for_pid(1, "container_uuid", &e);
126                 if (r > 0) {
127                         if (strlen(e) >= 36) {
128                                 r = shorten_uuid(id, e);
129                                 if (r >= 0) {
130                                         log_info("Initializing machine ID from container UUID.");
131                                         return 0;
132                                 }
133                         }
134                 }
135         }
136
137         /* If that didn't work, generate a random machine id */
138         r = sd_id128_randomize(&buf);
139         if (r < 0) {
140                 log_error("Failed to open /dev/urandom: %s", strerror(-r));
141                 return r;
142         }
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 int machine_id_setup(const char *root) {
158         _cleanup_close_ int fd = -1;
159         int r;
160         bool writable = false;
161         struct stat st;
162         char id[34]; /* 32 + \n + \0 */
163         _cleanup_free_ char *etc_machine_id = NULL;
164         _cleanup_free_ char *run_machine_id = NULL;
165
166         if (asprintf(&etc_machine_id, "%s/etc/machine-id", root) < 0)
167                 return log_oom();
168
169         if (asprintf(&run_machine_id, "%s/run/machine-id", root) < 0)
170                 return log_oom();
171
172         RUN_WITH_UMASK(0000) {
173                 /* We create this 0444, to indicate that this isn't really
174                  * something you should ever modify. Of course, since the file
175                  * will be owned by root it doesn't matter much, but maybe
176                  * people look. */
177
178                 fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
179                 if (fd >= 0)
180                         writable = true;
181                 else {
182                         fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY);
183                         if (fd < 0) {
184                                 log_error("Cannot open %s: %m", etc_machine_id);
185                                 return -errno;
186                         }
187
188                         writable = false;
189                 }
190         }
191
192         if (fstat(fd, &st) < 0) {
193                 log_error("fstat() failed: %m");
194                 return -errno;
195         }
196
197         if (S_ISREG(st.st_mode))
198                 if (loop_read(fd, id, 33, false) == 33 && id[32] == '\n') {
199                         id[32] = 0;
200
201                         if (id128_is_valid(id))
202                                 return 0;
203                 }
204
205         /* Hmm, so, the id currently stored is not useful, then let's
206          * generate one */
207
208         r = generate(id, root);
209         if (r < 0)
210                 return r;
211
212         if (S_ISREG(st.st_mode) && writable) {
213                 lseek(fd, 0, SEEK_SET);
214
215                 if (loop_write(fd, id, 33, false) == 33)
216                         return 0;
217         }
218
219         close_nointr_nofail(fd);
220         fd = -1;
221
222         /* Hmm, we couldn't write it? So let's write it to
223          * /run/machine-id as a replacement */
224
225         RUN_WITH_UMASK(0022) {
226                 r = write_string_file(run_machine_id, id);
227         }
228         if (r < 0) {
229                 log_error("Cannot write %s: %s", run_machine_id, strerror(-r));
230                 unlink(run_machine_id);
231                 return r;
232         }
233
234         /* And now, let's mount it over */
235         r = mount(run_machine_id, etc_machine_id, NULL, MS_BIND, NULL);
236         if (r < 0) {
237                 log_error("Failed to mount %s: %m", etc_machine_id);
238                 unlink_noerrno(run_machine_id);
239                 return -errno;
240         }
241
242         log_info("Installed transient %s file.", etc_machine_id);
243
244         /* Mark the mount read-only */
245         if (mount(NULL, etc_machine_id, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL) < 0)
246                 log_warning("Failed to make transient %s read-only: %m", etc_machine_id);
247
248         return 0;
249 }