chiark / gitweb /
util: replace close_nointr_nofail() by a more useful safe_close()
[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 #include "path-util.h"
40
41 static int shorten_uuid(char destination[36], const char *source) {
42         unsigned i, j;
43
44         for (i = 0, j = 0; i < 36 && j < 32; i++) {
45                 int t;
46
47                 t = unhexchar(source[i]);
48                 if (t < 0)
49                         continue;
50
51                 destination[j++] = hexchar(t);
52         }
53
54         if (i == 36 && j == 32) {
55                 destination[32] = '\n';
56                 destination[33] = 0;
57                 return 0;
58         }
59
60         return -EINVAL;
61 }
62
63 static int generate(char id[34], const char *root) {
64         int fd, r;
65         unsigned char *p;
66         sd_id128_t buf;
67         char *q;
68         ssize_t k;
69         const char *vm_id;
70         _cleanup_free_ char *dbus_machine_id = NULL;
71
72         assert(id);
73
74         if (asprintf(&dbus_machine_id, "%s/var/lib/dbus/machine-id", root) < 0)
75                 return log_oom();
76
77         /* First, try reading the D-Bus machine id, unless it is a symlink */
78         fd = open(dbus_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
79         if (fd >= 0) {
80                 k = loop_read(fd, id, 33, false);
81                 safe_close(fd);
82
83                 if (k == 33 && id[32] == '\n') {
84
85                         id[32] = 0;
86                         if (id128_is_valid(id)) {
87                                 id[32] = '\n';
88                                 id[33] = 0;
89
90                                 log_info("Initializing machine ID from D-Bus machine ID.");
91                                 return 0;
92                         }
93                 }
94         }
95
96         /* If that didn't work, see if we are running in qemu/kvm and a
97          * machine ID was passed in via -uuid on the qemu/kvm command
98          * line */
99
100         r = detect_vm(&vm_id);
101         if (r > 0 && streq(vm_id, "kvm")) {
102                 char uuid[37];
103
104                 fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
105                 if (fd >= 0) {
106                         k = loop_read(fd, uuid, 36, false);
107                         safe_close(fd);
108
109                         if (k >= 36) {
110                                 r = shorten_uuid(id, uuid);
111                                 if (r >= 0) {
112                                         log_info("Initializing machine ID from KVM UUID.");
113                                         return 0;
114                                 }
115                         }
116                 }
117         }
118
119         /* If that didn't work either, see if we are running in a
120          * container, and a machine ID was passed in via
121          * $container_uuid the way libvirt/LXC does it */
122         r = detect_container(NULL);
123         if (r > 0) {
124                 _cleanup_free_ char *e = NULL;
125
126                 r = getenv_for_pid(1, "container_uuid", &e);
127                 if (r > 0) {
128                         if (strlen(e) >= 36) {
129                                 r = shorten_uuid(id, e);
130                                 if (r >= 0) {
131                                         log_info("Initializing machine ID from container UUID.");
132                                         return 0;
133                                 }
134                         }
135                 }
136         }
137
138         /* If that didn't work, generate a random machine id */
139         r = sd_id128_randomize(&buf);
140         if (r < 0) {
141                 log_error("Failed to open /dev/urandom: %s", strerror(-r));
142                 return r;
143         }
144
145         for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) {
146                 q[0] = hexchar(*p >> 4);
147                 q[1] = hexchar(*p & 15);
148         }
149
150         id[32] = '\n';
151         id[33] = 0;
152
153         log_info("Initializing machine ID from random generator.");
154
155         return 0;
156 }
157
158 int machine_id_setup(const char *root) {
159         _cleanup_close_ int fd = -1;
160         int r;
161         bool writable = false;
162         struct stat st;
163         char id[34]; /* 32 + \n + \0 */
164         char *etc_machine_id, *run_machine_id;
165
166         etc_machine_id = strappenda(root, "/etc/machine-id");
167         path_kill_slashes(etc_machine_id);
168
169         run_machine_id = strappenda(root, "/run/machine-id");
170         path_kill_slashes(run_machine_id);
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         fd = safe_close(fd);
220
221         /* Hmm, we couldn't write it? So let's write it to
222          * /run/machine-id as a replacement */
223
224         RUN_WITH_UMASK(0022) {
225                 r = write_string_file(run_machine_id, id);
226         }
227         if (r < 0) {
228                 log_error("Cannot write %s: %s", run_machine_id, strerror(-r));
229                 unlink(run_machine_id);
230                 return r;
231         }
232
233         /* And now, let's mount it over */
234         r = mount(run_machine_id, etc_machine_id, NULL, MS_BIND, NULL);
235         if (r < 0) {
236                 log_error("Failed to mount %s: %m", etc_machine_id);
237                 unlink_noerrno(run_machine_id);
238                 return -errno;
239         }
240
241         log_info("Installed transient %s file.", etc_machine_id);
242
243         /* Mark the mount read-only */
244         if (mount(NULL, etc_machine_id, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL) < 0)
245                 log_warning("Failed to make transient %s read-only: %m", etc_machine_id);
246
247         return 0;
248 }