chiark / gitweb /
machined: add support for reporting image size via btrfs quota
[elogind.git] / src / shared / btrfs-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 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 <stdlib.h>
23 #include <sys/vfs.h>
24 #include <sys/stat.h>
25
26 #ifdef HAVE_LINUX_BTRFS_H
27 #include <linux/btrfs.h>
28 #endif
29
30 #include "missing.h"
31 #include "util.h"
32 #include "path-util.h"
33 #include "macro.h"
34 #include "strv.h"
35 #include "copy.h"
36 #include "selinux-util.h"
37 #include "smack-util.h"
38 #include "btrfs-ctree.h"
39 #include "btrfs-util.h"
40
41 static int validate_subvolume_name(const char *name) {
42
43         if (!filename_is_valid(name))
44                 return -EINVAL;
45
46         if (strlen(name) > BTRFS_SUBVOL_NAME_MAX)
47                 return -E2BIG;
48
49         return 0;
50 }
51
52 static int open_parent(const char *path, int flags) {
53         _cleanup_free_ char *parent = NULL;
54         int r, fd;
55
56         assert(path);
57
58         r = path_get_parent(path, &parent);
59         if (r < 0)
60                 return r;
61
62         fd = open(parent, flags);
63         if (fd < 0)
64                 return -errno;
65
66         return fd;
67 }
68
69 static int extract_subvolume_name(const char *path, const char **subvolume) {
70         const char *fn;
71         int r;
72
73         assert(path);
74         assert(subvolume);
75
76         fn = basename(path);
77
78         r = validate_subvolume_name(fn);
79         if (r < 0)
80                 return r;
81
82         *subvolume = fn;
83         return 0;
84 }
85
86 int btrfs_is_snapshot(int fd) {
87         struct stat st;
88         struct statfs sfs;
89
90         /* On btrfs subvolumes always have the inode 256 */
91
92         if (fstat(fd, &st) < 0)
93                 return -errno;
94
95         if (!S_ISDIR(st.st_mode) || st.st_ino != 256)
96                 return 0;
97
98         if (fstatfs(fd, &sfs) < 0)
99                 return -errno;
100
101         return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
102 }
103
104 int btrfs_subvol_snapshot(const char *old_path, const char *new_path, bool read_only, bool fallback_copy) {
105         struct btrfs_ioctl_vol_args_v2 args = {
106                 .flags = read_only ? BTRFS_SUBVOL_RDONLY : 0,
107         };
108         _cleanup_close_ int old_fd = -1, new_fd = -1;
109         const char *subvolume;
110         int r;
111
112         assert(old_path);
113
114         old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
115         if (old_fd < 0)
116                 return -errno;
117
118         r = btrfs_is_snapshot(old_fd);
119         if (r < 0)
120                 return r;
121         if (r == 0) {
122
123                 if (fallback_copy) {
124                         r = btrfs_subvol_make(new_path);
125                         if (r < 0)
126                                 return r;
127
128                         r = copy_directory_fd(old_fd, new_path, true);
129                         if (r < 0) {
130                                 btrfs_subvol_remove(new_path);
131                                 return r;
132                         }
133
134                         if (read_only) {
135                                 r = btrfs_subvol_set_read_only(new_path, true);
136                                 if (r < 0) {
137                                         btrfs_subvol_remove(new_path);
138                                         return r;
139                                 }
140                         }
141
142                         return 0;
143                 }
144
145                 return -EISDIR;
146         }
147
148         r = extract_subvolume_name(new_path, &subvolume);
149         if (r < 0)
150                 return r;
151
152         new_fd = open_parent(new_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
153         if (new_fd < 0)
154                 return new_fd;
155
156         strncpy(args.name, subvolume, sizeof(args.name)-1);
157         args.fd = old_fd;
158
159         if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &args) < 0)
160                 return -errno;
161
162         return 0;
163 }
164
165 int btrfs_subvol_make(const char *path) {
166         struct btrfs_ioctl_vol_args args = {};
167         _cleanup_close_ int fd = -1;
168         const char *subvolume;
169         int r;
170
171         assert(path);
172
173         r = extract_subvolume_name(path, &subvolume);
174         if (r < 0)
175                 return r;
176
177         fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
178         if (fd < 0)
179                 return fd;
180
181         strncpy(args.name, subvolume, sizeof(args.name)-1);
182
183         if (ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0)
184                 return -errno;
185
186         return 0;
187 }
188
189 int btrfs_subvol_make_label(const char *path) {
190         int r;
191
192         assert(path);
193
194         r = mac_selinux_create_file_prepare(path, S_IFDIR);
195         if (r < 0)
196                 return r;
197
198         r = btrfs_subvol_make(path);
199         mac_selinux_create_file_clear();
200
201         if (r < 0)
202                 return r;
203
204         return mac_smack_fix(path, false, false);
205 }
206
207 int btrfs_subvol_remove(const char *path) {
208         struct btrfs_ioctl_vol_args args = {};
209         _cleanup_close_ int fd = -1;
210         const char *subvolume;
211         int r;
212
213         assert(path);
214
215         r = extract_subvolume_name(path, &subvolume);
216         if (r < 0)
217                 return r;
218
219         fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
220         if (fd < 0)
221                 return fd;
222
223         strncpy(args.name, subvolume, sizeof(args.name)-1);
224
225         if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args) < 0)
226                 return -errno;
227
228         return 0;
229 }
230
231 int btrfs_subvol_set_read_only(const char *path, bool b) {
232         _cleanup_close_ int fd = -1;
233         uint64_t flags, nflags;
234
235         fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
236         if (fd < 0)
237                 return -errno;
238
239         if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0)
240                 return -errno;
241
242         if (b)
243                 nflags = flags | BTRFS_SUBVOL_RDONLY;
244         else
245                 nflags = flags & ~BTRFS_SUBVOL_RDONLY;
246
247         if (flags == nflags)
248                 return 0;
249
250         if (ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &nflags) < 0)
251                 return -errno;
252
253         return 0;
254 }
255
256 int btrfs_subvol_get_read_only_fd(int fd) {
257         uint64_t flags;
258
259         if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0)
260                 return -errno;
261
262         return !!(flags & BTRFS_SUBVOL_RDONLY);
263 }
264
265 int btrfs_reflink(int infd, int outfd) {
266         int r;
267
268         assert(infd >= 0);
269         assert(outfd >= 0);
270
271         r = ioctl(outfd, BTRFS_IOC_CLONE, infd);
272         if (r < 0)
273                 return -errno;
274
275         return 0;
276 }
277
278 int btrfs_get_block_device(const char *path, dev_t *dev) {
279         struct btrfs_ioctl_fs_info_args fsi = {};
280         _cleanup_close_ int fd = -1;
281         uint64_t id;
282
283         assert(path);
284         assert(dev);
285
286         fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
287         if (fd < 0)
288                 return -errno;
289
290         if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0)
291                 return -errno;
292
293         /* We won't do this for btrfs RAID */
294         if (fsi.num_devices != 1)
295                 return 0;
296
297         for (id = 1; id <= fsi.max_id; id++) {
298                 struct btrfs_ioctl_dev_info_args di = {
299                         .devid = id,
300                 };
301                 struct stat st;
302
303                 if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) {
304                         if (errno == ENODEV)
305                                 continue;
306
307                         return -errno;
308                 }
309
310                 if (stat((char*) di.path, &st) < 0)
311                         return -errno;
312
313                 if (!S_ISBLK(st.st_mode))
314                         return -ENODEV;
315
316                 if (major(st.st_rdev) == 0)
317                         return -ENODEV;
318
319                 *dev = st.st_rdev;
320                 return 1;
321         }
322
323         return -ENODEV;
324 }
325
326 int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) {
327         struct btrfs_ioctl_ino_lookup_args args = {
328                 .objectid = BTRFS_FIRST_FREE_OBJECTID
329         };
330
331         assert(fd >= 0);
332         assert(ret);
333
334         if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args) < 0)
335                 return -errno;
336
337         *ret = args.treeid;
338         return 0;
339 }
340
341 int btrfs_subvol_get_info_fd(int fd, BtrfsSubvolInfo *ret) {
342         struct btrfs_ioctl_search_args args = {
343                 /* Tree of tree roots */
344                 .key.tree_id = BTRFS_ROOT_TREE_OBJECTID,
345
346                 /* Look precisely for the subvolume items */
347                 .key.min_type = BTRFS_ROOT_ITEM_KEY,
348                 .key.max_type = BTRFS_ROOT_ITEM_KEY,
349
350                 /* No restrictions on the other components */
351                 .key.min_offset = 0,
352                 .key.max_offset = (uint64_t) -1,
353                 .key.min_transid = 0,
354                 .key.max_transid = (uint64_t) -1,
355         };
356
357         uint64_t subvol_id;
358         bool found = false;
359         int r;
360
361         assert(fd >= 0);
362         assert(ret);
363
364         r = btrfs_subvol_get_id_fd(fd, &subvol_id);
365         if (r < 0)
366                 return r;
367
368         args.key.min_objectid = args.key.max_objectid = subvol_id;
369
370         for (;;) {
371                 const struct btrfs_ioctl_search_header *sh;
372                 unsigned i;
373
374                 args.key.nr_items = 256;
375                 if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0)
376                         return -errno;
377
378                 if (args.key.nr_items <= 0)
379                         break;
380
381                 for (i = 0,
382                      sh = (const struct btrfs_ioctl_search_header*) args.buf;
383                      i < args.key.nr_items;
384                      i++,
385                      args.key.min_type = sh->type,
386                      args.key.min_offset = sh->offset,
387                      args.key.min_objectid = sh->objectid,
388                      sh = (const struct btrfs_ioctl_search_header*) ((uint8_t*) sh + sizeof(struct btrfs_ioctl_search_header) + sh->len)) {
389
390                         const struct btrfs_root_item *ri;
391
392                         if (sh->objectid != subvol_id)
393                                 continue;
394                         if (sh->type != BTRFS_ROOT_ITEM_KEY)
395                                 continue;
396                         if (sh->len < offsetof(struct btrfs_root_item, otime) + sizeof(struct btrfs_timespec))
397                                 continue;
398
399                         ri = (const struct btrfs_root_item *)(args.buf + sizeof(struct btrfs_ioctl_search_header));
400
401                         ret->otime = (usec_t) le64toh(ri->otime.sec) * USEC_PER_SEC +
402                                 (usec_t) le32toh(ri->otime.nsec) / NSEC_PER_USEC;
403
404                         ret->subvol_id = subvol_id;
405                         ret->read_only = !!(le64toh(ri->flags) & BTRFS_ROOT_SUBVOL_RDONLY);
406
407                         assert_cc(sizeof(ri->uuid) == sizeof(ret->uuid));
408                         memcpy(&ret->uuid, ri->uuid, sizeof(ret->uuid));
409                         memcpy(&ret->parent_uuid, ri->parent_uuid, sizeof(ret->parent_uuid));
410
411                         found = true;
412                         goto finish;
413                 }
414
415                 args.key.min_offset++;
416                 if (!args.key.min_offset) /* overflow */
417                         break;
418         }
419
420 finish:
421         if (!found)
422                 return -ENODATA;
423
424         return 0;
425 }
426
427 int btrfs_subvol_get_quota_fd(int fd, BtrfsQuotaInfo *ret) {
428
429         struct btrfs_ioctl_search_args args = {
430                 /* Tree of quota items */
431                 .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID,
432
433                 /* Look precisely for the quota items */
434                 .key.min_type = BTRFS_QGROUP_STATUS_KEY,
435                 .key.max_type = BTRFS_QGROUP_LIMIT_KEY,
436
437                 .key.min_objectid = 0,
438                 .key.max_objectid = 0,
439
440                 /* No restrictions on the other components */
441                 .key.min_transid = 0,
442                 .key.max_transid = (uint64_t) -1,
443         };
444
445         uint64_t subvol_id;
446         bool found_info = false, found_limit = false;
447         int r;
448
449         assert(fd >= 0);
450         assert(ret);
451
452         r = btrfs_subvol_get_id_fd(fd, &subvol_id);
453         if (r < 0)
454                 return r;
455
456         args.key.min_offset = args.key.max_offset = subvol_id;
457
458         for (;;) {
459                 const struct btrfs_ioctl_search_header *sh;
460                 unsigned i;
461
462                 args.key.nr_items = 256;
463                 if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0)
464                         return -errno;
465
466                 if (args.key.nr_items <= 0)
467                         break;
468
469                 for (i = 0,
470                      sh = (const struct btrfs_ioctl_search_header*) args.buf;
471                      i < args.key.nr_items;
472                      i++,
473                      args.key.min_type = sh->type,
474                      args.key.min_offset = sh->offset,
475                      args.key.min_objectid = sh->objectid,
476                      sh = (const struct btrfs_ioctl_search_header*) ((uint8_t*) sh + sizeof(struct btrfs_ioctl_search_header) + sh->len)) {
477
478                         const void *body;
479
480                         if (sh->objectid != 0)
481                                 continue;
482                         if (sh->offset != subvol_id)
483                                 continue;
484
485                         body = (uint8_t*) sh + sizeof(struct btrfs_ioctl_search_header);
486
487                         if (sh->type == BTRFS_QGROUP_INFO_KEY) {
488                                 const struct btrfs_qgroup_info_item *qii = body;
489
490                                 ret->referred = le64toh(qii->rfer);
491                                 ret->exclusive = le64toh(qii->excl);
492
493                                 found_info = true;
494
495                         } else if (sh->type == BTRFS_QGROUP_LIMIT_KEY) {
496                                 const struct btrfs_qgroup_limit_item *qli = body;
497
498                                 ret->referred_max = le64toh(qli->max_rfer);
499                                 ret->exclusive_max = le64toh(qli->max_excl);
500
501                                 if (ret->referred_max == 0)
502                                         ret->referred_max = (uint64_t) -1;
503                                 if (ret->exclusive_max == 0)
504                                         ret->exclusive_max = (uint64_t) -1;
505
506                                 found_limit = true;
507                         }
508
509                         if (found_info && found_limit)
510                                 goto finish;
511                 }
512
513                 args.key.min_offset++;
514                 if (!args.key.min_offset)
515                         break;
516         }
517
518 finish:
519         if (!found_limit && !found_info)
520                 return -ENODATA;
521
522         if (!found_info) {
523                 ret->referred = (uint64_t) -1;
524                 ret->exclusive = (uint64_t) -1;
525         }
526
527         if (!found_limit) {
528                 ret->referred_max = (uint64_t) -1;
529                 ret->exclusive_max = (uint64_t) -1;
530         }
531
532         return 0;
533 }