chiark / gitweb /
fbba3aab78d0f0073a65b34d7374cb8a8c7f24c0
[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]) {
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
70         assert(id);
71
72         /* First, try reading the D-Bus machine id, unless it is a symlink */
73         fd = open("/var/lib/dbus/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
74         if (fd >= 0) {
75
76                 k = loop_read(fd, id, 32, false);
77                 close_nointr_nofail(fd);
78
79                 if (k >= 32) {
80                         id[32] = '\n';
81                         id[33] = 0;
82
83                         log_info("Initializing machine ID from D-Bus machine ID.");
84                         return 0;
85                 }
86         }
87
88         /* If that didn't work, see if we are running in qemu/kvm and a
89          * machine ID was passed in via -uuid on the qemu/kvm command
90          * line */
91
92         r = detect_vm(&vm_id);
93         if (r > 0 && streq(vm_id, "kvm")) {
94                 char uuid[37];
95
96                 fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
97                 if (fd >= 0) {
98                         k = loop_read(fd, uuid, 36, false);
99                         close_nointr_nofail(fd);
100
101                         if (k >= 36) {
102                                 r = shorten_uuid(id, uuid);
103                                 if (r >= 0) {
104                                         log_info("Initializing machine ID from KVM UUID.");
105                                         return 0;
106                                 }
107                         }
108                 }
109         }
110
111         /* If that didn't work either, see if we are running in a
112          * container, and a machine ID was passed in via
113          * $container_uuid the way libvirt/LXC does it */
114         r = detect_container(NULL);
115         if (r > 0) {
116                 char *e;
117
118                 r = getenv_for_pid(1, "container_uuid", &e);
119                 if (r > 0) {
120                         if (strlen(e) >= 36) {
121                                 r = shorten_uuid(id, e);
122                                 if (r >= 0) {
123                                         log_info("Initializing machine ID from container UUID.");
124                                         free(e);
125                                         return 0;
126                                 }
127                         }
128
129                         free(e);
130                 }
131         }
132
133         /* If that didn't work, generate a random machine id */
134         r = sd_id128_randomize(&buf);
135         if (r < 0) {
136                 log_error("Failed to open /dev/urandom: %s", strerror(-r));
137                 return r;
138         }
139
140         for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) {
141                 q[0] = hexchar(*p >> 4);
142                 q[1] = hexchar(*p & 15);
143         }
144
145         id[32] = '\n';
146         id[33] = 0;
147
148         log_info("Initializing machine ID from random generator.");
149
150         return 0;
151 }
152
153 int machine_id_setup(void) {
154         int fd, r;
155         bool writable;
156         struct stat st;
157         char id[34]; /* 32 + \n + \0 */
158
159         RUN_WITH_UMASK(0000) {
160                 /* We create this 0444, to indicate that this isn't really
161                  * something you should ever modify. Of course, since the file
162                  * will be owned by root it doesn't matter much, but maybe
163                  * people look. */
164
165                 fd = open("/etc/machine-id", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
166                 if (fd >= 0)
167                         writable = true;
168                 else {
169                         fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
170                         if (fd < 0) {
171                                 log_error("Cannot open /etc/machine-id: %m");
172                                 return -errno;
173                         }
174
175                         writable = false;
176                 }
177         }
178
179         if (fstat(fd, &st) < 0) {
180                 log_error("fstat() failed: %m");
181                 r = -errno;
182                 goto finish;
183         }
184
185         if (S_ISREG(st.st_mode)) {
186                 if (loop_read(fd, id, 32, false) >= 32) {
187                         r = 0;
188                         goto finish;
189                 }
190         }
191
192         /* Hmm, so, the id currently stored is not useful, then let's
193          * generate one */
194
195         r = generate(id);
196         if (r < 0)
197                 goto finish;
198
199         if (S_ISREG(st.st_mode) && writable) {
200                 lseek(fd, 0, SEEK_SET);
201
202                 if (loop_write(fd, id, 33, false) == 33) {
203                         r = 0;
204                         goto finish;
205                 }
206         }
207
208         close_nointr_nofail(fd);
209         fd = -1;
210
211         /* Hmm, we couldn't write it? So let's write it to
212          * /run/machine-id as a replacement */
213
214         RUN_WITH_UMASK(0022) {
215                 r = write_string_file("/run/machine-id", id);
216         }
217         if (r < 0) {
218                 log_error("Cannot write /run/machine-id: %s", strerror(-r));
219
220                 unlink("/run/machine-id");
221                 goto finish;
222         }
223
224         /* And now, let's mount it over */
225         r = mount("/run/machine-id", "/etc/machine-id", NULL, MS_BIND, NULL) < 0 ? -errno : 0;
226         if (r < 0) {
227                 unlink("/run/machine-id");
228                 log_error("Failed to mount /etc/machine-id: %s", strerror(-r));
229         } else {
230                 log_info("Installed transient /etc/machine-id file.");
231
232                 /* Mark the mount read-only */
233                 if (mount(NULL, "/etc/machine-id", NULL,
234                           MS_BIND|MS_RDONLY|MS_REMOUNT, NULL) < 0)
235                         log_warning("Failed to make transient /etc/machine-id read-only");
236         }
237
238 finish:
239
240         if (fd >= 0)
241                 close_nointr_nofail(fd);
242
243         return r;
244 }