]>
Commit | Line | Data |
---|---|---|
cd61c3bf LP |
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> | |
ebd93cb6 | 23 | #include <fcntl.h> |
cd61c3bf LP |
24 | |
25 | #include "strv.h" | |
26 | #include "utf8.h" | |
27 | #include "btrfs-util.h" | |
ebeccf9e | 28 | #include "path-util.h" |
ebd93cb6 | 29 | #include "copy.h" |
003dffde | 30 | #include "machine-image.h" |
cd61c3bf | 31 | |
c2ce6a3d | 32 | static const char image_search_path[] = |
42c6f2c9 | 33 | "/var/lib/machines\0" |
c2ce6a3d | 34 | "/var/lib/container\0" |
42c6f2c9 LP |
35 | "/usr/local/lib/machines\0" |
36 | "/usr/lib/machines\0"; | |
c2ce6a3d | 37 | |
cd61c3bf LP |
38 | Image *image_unref(Image *i) { |
39 | if (!i) | |
40 | return NULL; | |
41 | ||
42 | free(i->name); | |
43 | free(i->path); | |
44 | free(i); | |
45 | return NULL; | |
46 | } | |
47 | ||
c2ce6a3d | 48 | static int image_new( |
cd61c3bf | 49 | ImageType t, |
5fc7f358 | 50 | const char *pretty, |
cd61c3bf | 51 | const char *path, |
5fc7f358 | 52 | const char *filename, |
cd61c3bf | 53 | bool read_only, |
10f9c755 | 54 | usec_t crtime, |
cd61c3bf | 55 | usec_t mtime, |
c2ce6a3d | 56 | Image **ret) { |
cd61c3bf LP |
57 | |
58 | _cleanup_(image_unrefp) Image *i = NULL; | |
cd61c3bf | 59 | |
cd61c3bf LP |
60 | assert(t >= 0); |
61 | assert(t < _IMAGE_TYPE_MAX); | |
5fc7f358 LP |
62 | assert(pretty); |
63 | assert(filename); | |
c2ce6a3d | 64 | assert(ret); |
cd61c3bf | 65 | |
c2ce6a3d | 66 | i = new0(Image, 1); |
cd61c3bf LP |
67 | if (!i) |
68 | return -ENOMEM; | |
69 | ||
70 | i->type = t; | |
71 | i->read_only = read_only; | |
10f9c755 | 72 | i->crtime = crtime; |
cd61c3bf | 73 | i->mtime = mtime; |
cd61c3bf | 74 | |
5fc7f358 | 75 | i->name = strdup(pretty); |
cd61c3bf LP |
76 | if (!i->name) |
77 | return -ENOMEM; | |
78 | ||
5fc7f358 LP |
79 | if (path) |
80 | i->path = strjoin(path, "/", filename, NULL); | |
81 | else | |
82 | i->path = strdup(filename); | |
ebeccf9e | 83 | |
5fc7f358 LP |
84 | if (!i->path) |
85 | return -ENOMEM; | |
86 | ||
87 | path_kill_slashes(i->path); | |
cd61c3bf | 88 | |
c2ce6a3d | 89 | *ret = i; |
cd61c3bf | 90 | i = NULL; |
c2ce6a3d | 91 | |
cd61c3bf LP |
92 | return 0; |
93 | } | |
94 | ||
5fc7f358 LP |
95 | static int image_make( |
96 | const char *pretty, | |
97 | int dfd, | |
98 | const char *path, | |
99 | const char *filename, | |
100 | Image **ret) { | |
101 | ||
c2ce6a3d | 102 | struct stat st; |
5fc7f358 | 103 | bool read_only; |
cd61c3bf LP |
104 | int r; |
105 | ||
5fc7f358 | 106 | assert(filename); |
cd61c3bf | 107 | |
c2ce6a3d LP |
108 | /* We explicitly *do* follow symlinks here, since we want to |
109 | * allow symlinking trees into /var/lib/container/, and treat | |
110 | * them normally. */ | |
cd61c3bf | 111 | |
5fc7f358 | 112 | if (fstatat(dfd, filename, &st, 0) < 0) |
c2ce6a3d | 113 | return -errno; |
cd61c3bf | 114 | |
5fc7f358 LP |
115 | read_only = |
116 | (path && path_startswith(path, "/usr")) || | |
08ff5529 | 117 | (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS); |
86e339c8 | 118 | |
c2ce6a3d | 119 | if (S_ISDIR(st.st_mode)) { |
cd61c3bf | 120 | |
c2ce6a3d LP |
121 | if (!ret) |
122 | return 1; | |
cd61c3bf | 123 | |
5fc7f358 LP |
124 | if (!pretty) |
125 | pretty = filename; | |
126 | ||
c2ce6a3d LP |
127 | /* btrfs subvolumes have inode 256 */ |
128 | if (st.st_ino == 256) { | |
129 | _cleanup_close_ int fd = -1; | |
130 | struct statfs sfs; | |
cd61c3bf | 131 | |
5fc7f358 | 132 | fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY); |
c2ce6a3d LP |
133 | if (fd < 0) |
134 | return -errno; | |
cd61c3bf | 135 | |
c2ce6a3d LP |
136 | if (fstatfs(fd, &sfs) < 0) |
137 | return -errno; | |
cd61c3bf | 138 | |
c2ce6a3d | 139 | if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) { |
10f9c755 | 140 | BtrfsSubvolInfo info; |
cd61c3bf | 141 | |
c2ce6a3d | 142 | /* It's a btrfs subvolume */ |
cd61c3bf | 143 | |
10f9c755 LP |
144 | r = btrfs_subvol_get_info_fd(fd, &info); |
145 | if (r < 0) | |
146 | return r; | |
c2ce6a3d LP |
147 | |
148 | r = image_new(IMAGE_SUBVOLUME, | |
5fc7f358 | 149 | pretty, |
c2ce6a3d | 150 | path, |
5fc7f358 LP |
151 | filename, |
152 | info.read_only || read_only, | |
10f9c755 | 153 | info.otime, |
c2ce6a3d | 154 | 0, |
c2ce6a3d LP |
155 | ret); |
156 | if (r < 0) | |
157 | return r; | |
158 | ||
159 | return 1; | |
cd61c3bf | 160 | } |
c2ce6a3d | 161 | } |
cd61c3bf | 162 | |
c2ce6a3d | 163 | /* It's just a normal directory. */ |
cd61c3bf | 164 | |
c2ce6a3d | 165 | r = image_new(IMAGE_DIRECTORY, |
5fc7f358 | 166 | pretty, |
c2ce6a3d | 167 | path, |
5fc7f358 LP |
168 | filename, |
169 | read_only, | |
c2ce6a3d LP |
170 | 0, |
171 | 0, | |
172 | ret); | |
173 | if (r < 0) | |
174 | return r; | |
cd61c3bf | 175 | |
c2ce6a3d | 176 | return 1; |
cd61c3bf | 177 | |
5fc7f358 | 178 | } else if (S_ISREG(st.st_mode) && endswith(filename, ".gpt")) { |
10f9c755 | 179 | usec_t crtime = 0; |
cd61c3bf | 180 | |
c2ce6a3d | 181 | /* It's a GPT block device */ |
cd61c3bf | 182 | |
c2ce6a3d LP |
183 | if (!ret) |
184 | return 1; | |
cd61c3bf | 185 | |
5fc7f358 | 186 | fd_getcrtime_at(dfd, filename, &crtime, 0); |
10f9c755 | 187 | |
5fc7f358 LP |
188 | if (!pretty) |
189 | pretty = strndupa(filename, strlen(filename) - 4); | |
10f9c755 | 190 | |
c2ce6a3d | 191 | r = image_new(IMAGE_GPT, |
5fc7f358 | 192 | pretty, |
c2ce6a3d | 193 | path, |
5fc7f358 LP |
194 | filename, |
195 | !(st.st_mode & 0222) || read_only, | |
10f9c755 | 196 | crtime, |
c2ce6a3d | 197 | timespec_load(&st.st_mtim), |
c2ce6a3d LP |
198 | ret); |
199 | if (r < 0) | |
200 | return r; | |
cd61c3bf | 201 | |
c2ce6a3d LP |
202 | return 1; |
203 | } | |
cd61c3bf | 204 | |
c2ce6a3d LP |
205 | return 0; |
206 | } | |
cd61c3bf | 207 | |
c2ce6a3d LP |
208 | int image_find(const char *name, Image **ret) { |
209 | const char *path; | |
210 | int r; | |
cd61c3bf | 211 | |
c2ce6a3d | 212 | assert(name); |
cd61c3bf | 213 | |
c2ce6a3d LP |
214 | /* There are no images with invalid names */ |
215 | if (!image_name_is_valid(name)) | |
216 | return 0; | |
cd61c3bf | 217 | |
c2ce6a3d LP |
218 | NULSTR_FOREACH(path, image_search_path) { |
219 | _cleanup_closedir_ DIR *d = NULL; | |
cd61c3bf | 220 | |
c2ce6a3d LP |
221 | d = opendir(path); |
222 | if (!d) { | |
223 | if (errno == ENOENT) | |
224 | continue; | |
cd61c3bf | 225 | |
c2ce6a3d LP |
226 | return -errno; |
227 | } | |
cd61c3bf | 228 | |
5fc7f358 LP |
229 | r = image_make(NULL, dirfd(d), path, name, ret); |
230 | if (r == 0 || r == -ENOENT) { | |
231 | _cleanup_free_ char *gpt = NULL; | |
232 | ||
233 | gpt = strappend(name, ".gpt"); | |
234 | if (!gpt) | |
235 | return -ENOMEM; | |
236 | ||
237 | r = image_make(NULL, dirfd(d), path, gpt, ret); | |
238 | if (r == 0 || r == -ENOENT) | |
239 | continue; | |
240 | } | |
c2ce6a3d LP |
241 | if (r < 0) |
242 | return r; | |
cd61c3bf | 243 | |
c2ce6a3d LP |
244 | return 1; |
245 | } | |
246 | ||
5fc7f358 | 247 | if (streq(name, ".host")) |
27c88c4e | 248 | return image_make(".host", AT_FDCWD, NULL, "/", ret); |
5fc7f358 | 249 | |
c2ce6a3d LP |
250 | return 0; |
251 | }; | |
252 | ||
253 | int image_discover(Hashmap *h) { | |
254 | const char *path; | |
255 | int r; | |
256 | ||
257 | assert(h); | |
258 | ||
259 | NULSTR_FOREACH(path, image_search_path) { | |
260 | _cleanup_closedir_ DIR *d = NULL; | |
261 | struct dirent *de; | |
262 | ||
263 | d = opendir(path); | |
264 | if (!d) { | |
265 | if (errno == ENOENT) | |
a67a4c8c | 266 | continue; |
c2ce6a3d LP |
267 | |
268 | return -errno; | |
269 | } | |
270 | ||
271 | FOREACH_DIRENT_ALL(de, d, return -errno) { | |
272 | _cleanup_(image_unrefp) Image *image = NULL; | |
273 | ||
274 | if (!image_name_is_valid(de->d_name)) | |
275 | continue; | |
276 | ||
277 | if (hashmap_contains(h, de->d_name)) | |
278 | continue; | |
279 | ||
5fc7f358 | 280 | r = image_make(NULL, dirfd(d), path, de->d_name, &image); |
c2ce6a3d LP |
281 | if (r == 0 || r == -ENOENT) |
282 | continue; | |
283 | if (r < 0) | |
284 | return r; | |
285 | ||
286 | r = hashmap_put(h, image->name, image); | |
287 | if (r < 0) | |
288 | return r; | |
289 | ||
290 | image = NULL; | |
cd61c3bf LP |
291 | } |
292 | } | |
293 | ||
5fc7f358 LP |
294 | if (!hashmap_contains(h, ".host")) { |
295 | _cleanup_(image_unrefp) Image *image = NULL; | |
296 | ||
297 | r = image_make(".host", AT_FDCWD, NULL, "/", &image); | |
298 | if (r < 0) | |
299 | return r; | |
300 | ||
301 | r = hashmap_put(h, image->name, image); | |
302 | if (r < 0) | |
303 | return r; | |
304 | ||
305 | image = NULL; | |
306 | ||
307 | } | |
308 | ||
cd61c3bf LP |
309 | return 0; |
310 | } | |
311 | ||
312 | void image_hashmap_free(Hashmap *map) { | |
313 | Image *i; | |
314 | ||
315 | while ((i = hashmap_steal_first(map))) | |
316 | image_unref(i); | |
317 | ||
318 | hashmap_free(map); | |
319 | } | |
320 | ||
08682124 | 321 | int image_remove(Image *i) { |
08682124 LP |
322 | assert(i); |
323 | ||
324 | if (path_equal(i->path, "/") || | |
325 | path_startswith(i->path, "/usr")) | |
326 | return -EROFS; | |
327 | ||
ebd93cb6 LP |
328 | switch (i->type) { |
329 | ||
330 | case IMAGE_SUBVOLUME: | |
08682124 | 331 | return btrfs_subvol_remove(i->path); |
ebd93cb6 LP |
332 | |
333 | case IMAGE_DIRECTORY: | |
334 | case IMAGE_GPT: | |
08682124 | 335 | return rm_rf_dangerous(i->path, false, true, false); |
ebd93cb6 LP |
336 | |
337 | default: | |
338 | return -ENOTSUP; | |
339 | } | |
340 | } | |
341 | ||
342 | int image_rename(Image *i, const char *new_name) { | |
343 | _cleanup_free_ char *new_path = NULL, *nn = NULL; | |
344 | int r; | |
345 | ||
346 | assert(i); | |
347 | ||
348 | if (!image_name_is_valid(new_name)) | |
349 | return -EINVAL; | |
350 | ||
351 | if (path_equal(i->path, "/") || | |
352 | path_startswith(i->path, "/usr")) | |
353 | return -EROFS; | |
354 | ||
355 | r = image_find(new_name, NULL); | |
356 | if (r < 0) | |
357 | return r; | |
358 | if (r > 0) | |
359 | return -EEXIST; | |
360 | ||
361 | switch (i->type) { | |
362 | ||
363 | case IMAGE_SUBVOLUME: | |
364 | case IMAGE_DIRECTORY: | |
365 | new_path = file_in_same_dir(i->path, new_name); | |
366 | break; | |
367 | ||
368 | case IMAGE_GPT: { | |
369 | const char *fn; | |
370 | ||
371 | fn = strappenda(new_name, ".gpt"); | |
372 | new_path = file_in_same_dir(i->path, fn); | |
373 | break; | |
374 | } | |
375 | ||
376 | default: | |
377 | return -ENOTSUP; | |
378 | } | |
379 | ||
380 | if (!new_path) | |
381 | return -ENOMEM; | |
382 | ||
383 | nn = strdup(new_name); | |
384 | if (!nn) | |
385 | return -ENOMEM; | |
386 | ||
387 | if (renameat2(AT_FDCWD, i->path, AT_FDCWD, new_path, RENAME_NOREPLACE) < 0) | |
388 | return -errno; | |
389 | ||
390 | free(i->path); | |
391 | i->path = new_path; | |
392 | new_path = NULL; | |
393 | ||
394 | free(i->name); | |
395 | i->name = nn; | |
396 | nn = NULL; | |
397 | ||
398 | return 0; | |
399 | } | |
400 | ||
401 | int image_clone(Image *i, const char *new_name, bool read_only) { | |
402 | const char *new_path; | |
403 | int r; | |
404 | ||
405 | assert(i); | |
406 | ||
407 | if (!image_name_is_valid(new_name)) | |
408 | return -EINVAL; | |
409 | ||
410 | r = image_find(new_name, NULL); | |
411 | if (r < 0) | |
412 | return r; | |
413 | if (r > 0) | |
414 | return -EEXIST; | |
415 | ||
416 | switch (i->type) { | |
417 | ||
418 | case IMAGE_SUBVOLUME: | |
419 | case IMAGE_DIRECTORY: | |
420 | new_path = strappenda("/var/lib/container/", new_name); | |
421 | ||
422 | r = btrfs_subvol_snapshot(i->path, new_path, read_only, true); | |
423 | break; | |
424 | ||
425 | case IMAGE_GPT: | |
426 | new_path = strappenda("/var/lib/container/", new_name, ".gpt"); | |
427 | ||
428 | r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false); | |
429 | break; | |
430 | ||
431 | default: | |
432 | return -ENOTSUP; | |
433 | } | |
434 | ||
435 | if (r < 0) | |
436 | return r; | |
437 | ||
438 | return 0; | |
439 | } | |
440 | ||
441 | int image_read_only(Image *i, bool b) { | |
442 | int r; | |
443 | assert(i); | |
444 | ||
445 | if (path_equal(i->path, "/") || | |
446 | path_startswith(i->path, "/usr")) | |
447 | return -EROFS; | |
448 | ||
449 | switch (i->type) { | |
450 | ||
451 | case IMAGE_SUBVOLUME: | |
452 | r = btrfs_subvol_set_read_only(i->path, b); | |
453 | if (r < 0) | |
454 | return r; | |
455 | break; | |
456 | ||
457 | case IMAGE_GPT: { | |
458 | struct stat st; | |
459 | ||
460 | if (stat(i->path, &st) < 0) | |
461 | return -errno; | |
462 | ||
463 | if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0) | |
464 | return -errno; | |
465 | break; | |
466 | } | |
467 | ||
468 | case IMAGE_DIRECTORY: | |
469 | default: | |
470 | return -ENOTSUP; | |
471 | } | |
472 | ||
473 | return 0; | |
08682124 LP |
474 | } |
475 | ||
cd61c3bf LP |
476 | static const char* const image_type_table[_IMAGE_TYPE_MAX] = { |
477 | [IMAGE_DIRECTORY] = "directory", | |
478 | [IMAGE_SUBVOLUME] = "subvolume", | |
479 | [IMAGE_GPT] = "gpt", | |
480 | }; | |
481 | ||
482 | DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType); |