]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/btrfs-util.c
Update TODO
[thirdparty/systemd.git] / src / shared / btrfs-util.c
CommitLineData
d7c7c334
LP
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"
d7b8eec7
LP
36#include "selinux-util.h"
37#include "smack-util.h"
10f9c755 38#include "btrfs-ctree.h"
d7c7c334
LP
39#include "btrfs-util.h"
40
41static 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
52static 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
69static 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
86int btrfs_is_snapshot(int fd) {
87 struct stat st;
88 struct statfs sfs;
89
cd61c3bf
LP
90 /* On btrfs subvolumes always have the inode 256 */
91
92 if (fstat(fd, &st) < 0)
d7c7c334
LP
93 return -errno;
94
cd61c3bf 95 if (!S_ISDIR(st.st_mode) || st.st_ino != 256)
d7c7c334
LP
96 return 0;
97
cd61c3bf 98 if (fstatfs(fd, &sfs) < 0)
d7c7c334
LP
99 return -errno;
100
cd61c3bf 101 return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
d7c7c334
LP
102}
103
104int 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
f2cbe59e 128 r = copy_directory_fd(old_fd, new_path, true);
d7c7c334
LP
129 if (r < 0) {
130 btrfs_subvol_remove(new_path);
131 return r;
132 }
133
134 if (read_only) {
10f9c755 135 r = btrfs_subvol_set_read_only(new_path, true);
d7c7c334
LP
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
165int 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
d7b8eec7
LP
189int 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
d7c7c334
LP
207int 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
10f9c755 231int btrfs_subvol_set_read_only(const char *path, bool b) {
d7c7c334
LP
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
10f9c755 256int btrfs_subvol_get_read_only_fd(int fd) {
cd61c3bf
LP
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
d7c7c334
LP
265int 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
278int 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}
10f9c755
LP
325
326int 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
341int btrfs_subvol_get_info_fd(int fd, BtrfsSubvolInfo *ret) {
342 struct btrfs_ioctl_search_args args = {
343 /* Tree of tree roots */
b6b18498 344 .key.tree_id = BTRFS_ROOT_TREE_OBJECTID,
10f9c755
LP
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,
10f9c755
LP
355 };
356
10f9c755 357 uint64_t subvol_id;
b6b18498 358 bool found = false;
10f9c755
LP
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;
10f9c755 369
b6b18498
LP
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;
10f9c755 380
b6b18498
LP
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)) {
10f9c755 389
b6b18498
LP
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;
10f9c755 398
b6b18498 399 ri = (const struct btrfs_root_item *)(args.buf + sizeof(struct btrfs_ioctl_search_header));
10f9c755 400
b6b18498
LP
401 ret->otime = (usec_t) le64toh(ri->otime.sec) * USEC_PER_SEC +
402 (usec_t) le32toh(ri->otime.nsec) / NSEC_PER_USEC;
10f9c755 403
b6b18498
LP
404 ret->subvol_id = subvol_id;
405 ret->read_only = !!(le64toh(ri->flags) & BTRFS_ROOT_SUBVOL_RDONLY);
10f9c755 406
b6b18498
LP
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
420finish:
421 if (!found)
422 return -ENODATA;
423
424 return 0;
425}
426
427int 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
518finish:
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 }
10f9c755
LP
531
532 return 0;
533}