chiark / gitweb /
machined: beef up machined image listing with creation/modification times of subvolumes
[elogind.git] / src / 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 "image.h"
29
30 static const char image_search_path[] =
31         "/var/lib/container\0"
32         "/var/lib/machine\0";
33
34 Image *image_unref(Image *i) {
35         if (!i)
36                 return NULL;
37
38         free(i->name);
39         free(i->path);
40         free(i);
41         return NULL;
42 }
43
44 static int image_new(
45                 ImageType t,
46                 const char *name,
47                 const char *path,
48                 bool read_only,
49                 usec_t crtime,
50                 usec_t mtime,
51                 Image **ret) {
52
53         _cleanup_(image_unrefp) Image *i = NULL;
54
55         assert(t >= 0);
56         assert(t < _IMAGE_TYPE_MAX);
57         assert(name);
58         assert(ret);
59
60         i = new0(Image, 1);
61         if (!i)
62                 return -ENOMEM;
63
64         i->type = t;
65         i->read_only = read_only;
66         i->crtime = crtime;
67         i->mtime = mtime;
68
69         i->name = strdup(name);
70         if (!i->name)
71                 return -ENOMEM;
72
73         if (path) {
74                 i->path = strjoin(path, "/", name, NULL);
75                 if (!i->path)
76                         return -ENOMEM;
77
78                 path_kill_slashes(i->path);
79         }
80
81         *ret = i;
82         i = NULL;
83
84         return 0;
85 }
86
87 static int image_make(int dfd, const char *name, const char *path, Image **ret) {
88         struct stat st;
89         int r;
90
91         assert(dfd >= 0);
92         assert(name);
93
94         /* We explicitly *do* follow symlinks here, since we want to
95          * allow symlinking trees into /var/lib/container/, and treat
96          * them normally. */
97
98         if (fstatat(dfd, name, &st, 0) < 0)
99                 return -errno;
100
101         if (S_ISDIR(st.st_mode)) {
102
103                 if (!ret)
104                         return 1;
105
106                 /* btrfs subvolumes have inode 256 */
107                 if (st.st_ino == 256) {
108                         _cleanup_close_ int fd = -1;
109                         struct statfs sfs;
110
111                         fd = openat(dfd, name, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
112                         if (fd < 0)
113                                 return -errno;
114
115                         if (fstatfs(fd, &sfs) < 0)
116                                 return -errno;
117
118                         if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) {
119                                 BtrfsSubvolInfo info;
120
121                                 /* It's a btrfs subvolume */
122
123                                 r = btrfs_subvol_get_info_fd(fd, &info);
124                                 if (r < 0)
125                                         return r;
126
127                                 r = image_new(IMAGE_SUBVOLUME,
128                                               name,
129                                               path,
130                                               info.read_only,
131                                               info.otime,
132                                               0,
133                                               ret);
134                                 if (r < 0)
135                                         return r;
136
137                                 return 1;
138                         }
139                 }
140
141                 /* It's just a normal directory. */
142
143                 r = image_new(IMAGE_DIRECTORY,
144                               name,
145                               path,
146                               false,
147                               0,
148                               0,
149                               ret);
150                 if (r < 0)
151                         return r;
152
153                 return 1;
154
155         } else if (S_ISREG(st.st_mode) && endswith(name, ".gpt")) {
156                 const char *truncated;
157                 usec_t crtime = 0;
158
159                 /* It's a GPT block device */
160
161                 if (!ret)
162                         return 1;
163
164                 fd_getcrtime_at(dfd, name, &crtime, 0);
165
166                 truncated = strndupa(name, strlen(name) - 4);
167
168                 r = image_new(IMAGE_GPT,
169                               truncated,
170                               path,
171                               !(st.st_mode & 0222),
172                               crtime,
173                               timespec_load(&st.st_mtim),
174                               ret);
175                 if (r < 0)
176                         return r;
177
178                 return 1;
179         }
180
181         return 0;
182 }
183
184 int image_find(const char *name, Image **ret) {
185         const char *path;
186         int r;
187
188         assert(name);
189
190         /* There are no images with invalid names */
191         if (!image_name_is_valid(name))
192                 return 0;
193
194         NULSTR_FOREACH(path, image_search_path) {
195                 _cleanup_closedir_ DIR *d = NULL;
196
197                 d = opendir(path);
198                 if (!d) {
199                         if (errno == ENOENT)
200                                 continue;
201
202                         return -errno;
203                 }
204
205                 r = image_make(dirfd(d), name, path, ret);
206                 if (r == 0 || r == -ENOENT)
207                         continue;
208                 if (r < 0)
209                         return r;
210
211                 return 1;
212         }
213
214         return 0;
215 };
216
217 int image_discover(Hashmap *h) {
218         const char *path;
219         int r;
220
221         assert(h);
222
223         NULSTR_FOREACH(path, image_search_path) {
224                 _cleanup_closedir_ DIR *d = NULL;
225                 struct dirent *de;
226
227                 d = opendir(path);
228                 if (!d) {
229                         if (errno == ENOENT)
230                                 return 0;
231
232                         return -errno;
233                 }
234
235                 FOREACH_DIRENT_ALL(de, d, return -errno) {
236                         _cleanup_(image_unrefp) Image *image = NULL;
237
238                         if (!image_name_is_valid(de->d_name))
239                                 continue;
240
241                         if (hashmap_contains(h, de->d_name))
242                                 continue;
243
244                         r = image_make(dirfd(d), de->d_name, path, &image);
245                         if (r == 0 || r == -ENOENT)
246                                 continue;
247                         if (r < 0)
248                                 return r;
249
250                         r = hashmap_put(h, image->name, image);
251                         if (r < 0)
252                                 return r;
253
254                         image = NULL;
255                 }
256         }
257
258         return 0;
259 }
260
261 void image_hashmap_free(Hashmap *map) {
262         Image *i;
263
264         while ((i = hashmap_steal_first(map)))
265                 image_unref(i);
266
267         hashmap_free(map);
268 }
269
270 static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
271         [IMAGE_DIRECTORY] = "directory",
272         [IMAGE_SUBVOLUME] = "subvolume",
273         [IMAGE_GPT] = "gpt",
274 };
275
276 DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);