chiark / gitweb /
machined: Move image discovery logic into src/shared, so that we can make use of...
[elogind.git] / src / shared / machine-image.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 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 <sys/statfs.h>
23
24 #include "strv.h"
25 #include "utf8.h"
26 #include "btrfs-util.h"
27 #include "path-util.h"
28 #include "machine-image.h"
29
30 static const char image_search_path[] =
31         "/var/lib/machines\0"
32         "/var/lib/container\0"
33         "/usr/local/lib/machines\0"
34         "/usr/lib/machines\0";
35
36 Image *image_unref(Image *i) {
37         if (!i)
38                 return NULL;
39
40         free(i->name);
41         free(i->path);
42         free(i);
43         return NULL;
44 }
45
46 static int image_new(
47                 ImageType t,
48                 const char *pretty,
49                 const char *path,
50                 const char *filename,
51                 bool read_only,
52                 usec_t crtime,
53                 usec_t mtime,
54                 Image **ret) {
55
56         _cleanup_(image_unrefp) Image *i = NULL;
57
58         assert(t >= 0);
59         assert(t < _IMAGE_TYPE_MAX);
60         assert(pretty);
61         assert(filename);
62         assert(ret);
63
64         i = new0(Image, 1);
65         if (!i)
66                 return -ENOMEM;
67
68         i->type = t;
69         i->read_only = read_only;
70         i->crtime = crtime;
71         i->mtime = mtime;
72
73         i->name = strdup(pretty);
74         if (!i->name)
75                 return -ENOMEM;
76
77         if (path)
78                 i->path = strjoin(path, "/", filename, NULL);
79         else
80                 i->path = strdup(filename);
81
82         if (!i->path)
83                 return -ENOMEM;
84
85         path_kill_slashes(i->path);
86
87         *ret = i;
88         i = NULL;
89
90         return 0;
91 }
92
93 static int image_make(
94                 const char *pretty,
95                 int dfd,
96                 const char *path,
97                 const char *filename,
98                 Image **ret) {
99
100         struct stat st;
101         bool read_only;
102         int r;
103
104         assert(filename);
105
106         /* We explicitly *do* follow symlinks here, since we want to
107          * allow symlinking trees into /var/lib/container/, and treat
108          * them normally. */
109
110         if (fstatat(dfd, filename, &st, 0) < 0)
111                 return -errno;
112
113         read_only =
114                 (path && path_startswith(path, "/usr")) ||
115                 (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
116
117         if (S_ISDIR(st.st_mode)) {
118
119                 if (!ret)
120                         return 1;
121
122                 if (!pretty)
123                         pretty = filename;
124
125                 /* btrfs subvolumes have inode 256 */
126                 if (st.st_ino == 256) {
127                         _cleanup_close_ int fd = -1;
128                         struct statfs sfs;
129
130                         fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
131                         if (fd < 0)
132                                 return -errno;
133
134                         if (fstatfs(fd, &sfs) < 0)
135                                 return -errno;
136
137                         if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) {
138                                 BtrfsSubvolInfo info;
139
140                                 /* It's a btrfs subvolume */
141
142                                 r = btrfs_subvol_get_info_fd(fd, &info);
143                                 if (r < 0)
144                                         return r;
145
146                                 r = image_new(IMAGE_SUBVOLUME,
147                                               pretty,
148                                               path,
149                                               filename,
150                                               info.read_only || read_only,
151                                               info.otime,
152                                               0,
153                                               ret);
154                                 if (r < 0)
155                                         return r;
156
157                                 return 1;
158                         }
159                 }
160
161                 /* It's just a normal directory. */
162
163                 r = image_new(IMAGE_DIRECTORY,
164                               pretty,
165                               path,
166                               filename,
167                               read_only,
168                               0,
169                               0,
170                               ret);
171                 if (r < 0)
172                         return r;
173
174                 return 1;
175
176         } else if (S_ISREG(st.st_mode) && endswith(filename, ".gpt")) {
177                 usec_t crtime = 0;
178
179                 /* It's a GPT block device */
180
181                 if (!ret)
182                         return 1;
183
184                 fd_getcrtime_at(dfd, filename, &crtime, 0);
185
186                 if (!pretty)
187                         pretty = strndupa(filename, strlen(filename) - 4);
188
189                 r = image_new(IMAGE_GPT,
190                               pretty,
191                               path,
192                               filename,
193                               !(st.st_mode & 0222) || read_only,
194                               crtime,
195                               timespec_load(&st.st_mtim),
196                               ret);
197                 if (r < 0)
198                         return r;
199
200                 return 1;
201         }
202
203         return 0;
204 }
205
206 int image_find(const char *name, Image **ret) {
207         const char *path;
208         int r;
209
210         assert(name);
211
212         /* There are no images with invalid names */
213         if (!image_name_is_valid(name))
214                 return 0;
215
216         NULSTR_FOREACH(path, image_search_path) {
217                 _cleanup_closedir_ DIR *d = NULL;
218
219                 d = opendir(path);
220                 if (!d) {
221                         if (errno == ENOENT)
222                                 continue;
223
224                         return -errno;
225                 }
226
227                 r = image_make(NULL, dirfd(d), path, name, ret);
228                 if (r == 0 || r == -ENOENT) {
229                         _cleanup_free_ char *gpt = NULL;
230
231                         gpt = strappend(name, ".gpt");
232                         if (!gpt)
233                                 return -ENOMEM;
234
235                         r = image_make(NULL, dirfd(d), path, gpt, ret);
236                         if (r == 0 || r == -ENOENT)
237                                 continue;
238                 }
239                 if (r < 0)
240                         return r;
241
242                 return 1;
243         }
244
245         if (streq(name, ".host"))
246                 return image_make(".host", AT_FDCWD, NULL, "/", ret);
247
248         return 0;
249 };
250
251 int image_discover(Hashmap *h) {
252         const char *path;
253         int r;
254
255         assert(h);
256
257         NULSTR_FOREACH(path, image_search_path) {
258                 _cleanup_closedir_ DIR *d = NULL;
259                 struct dirent *de;
260
261                 d = opendir(path);
262                 if (!d) {
263                         if (errno == ENOENT)
264                                 continue;
265
266                         return -errno;
267                 }
268
269                 FOREACH_DIRENT_ALL(de, d, return -errno) {
270                         _cleanup_(image_unrefp) Image *image = NULL;
271
272                         if (!image_name_is_valid(de->d_name))
273                                 continue;
274
275                         if (hashmap_contains(h, de->d_name))
276                                 continue;
277
278                         r = image_make(NULL, dirfd(d), path, de->d_name, &image);
279                         if (r == 0 || r == -ENOENT)
280                                 continue;
281                         if (r < 0)
282                                 return r;
283
284                         r = hashmap_put(h, image->name, image);
285                         if (r < 0)
286                                 return r;
287
288                         image = NULL;
289                 }
290         }
291
292         if (!hashmap_contains(h, ".host")) {
293                 _cleanup_(image_unrefp) Image *image = NULL;
294
295                 r = image_make(".host", AT_FDCWD, NULL, "/", &image);
296                 if (r < 0)
297                         return r;
298
299                 r = hashmap_put(h, image->name, image);
300                 if (r < 0)
301                         return r;
302
303                 image = NULL;
304
305         }
306
307         return 0;
308 }
309
310 void image_hashmap_free(Hashmap *map) {
311         Image *i;
312
313         while ((i = hashmap_steal_first(map)))
314                 image_unref(i);
315
316         hashmap_free(map);
317 }
318
319 static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
320         [IMAGE_DIRECTORY] = "directory",
321         [IMAGE_SUBVOLUME] = "subvolume",
322         [IMAGE_GPT] = "gpt",
323 };
324
325 DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);