From: Lennart Poettering Date: Fri, 12 Dec 2014 02:15:58 +0000 (+0100) Subject: shared: add new btrfs-util.[ch] helpers for doing common btrfs operation X-Git-Tag: v219~1033 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=d7c7c334f56edab8cfc102b657366277a65738cf;ds=sidebyside shared: add new btrfs-util.[ch] helpers for doing common btrfs operation --- diff --git a/.gitignore b/.gitignore index b0fc12fac..dbc56bca1 100644 --- a/.gitignore +++ b/.gitignore @@ -134,6 +134,7 @@ /test-async /test-barrier /test-boot-timestamp +/test-btrfs /test-bus-chat /test-bus-cleanup /test-bus-creds diff --git a/Makefile.am b/Makefile.am index 15e4484dd..417074945 100644 --- a/Makefile.am +++ b/Makefile.am @@ -891,7 +891,9 @@ libsystemd_shared_la_SOURCES = \ src/shared/memfd-util.h \ src/shared/uid-range.c \ src/shared/uid-range.h \ - src/shared/nss-util.h + src/shared/nss-util.h \ + src/shared/btrfs-util.c \ + src/shared/btrfs-util.h if HAVE_UTMP libsystemd_shared_la_SOURCES += \ @@ -1319,7 +1321,8 @@ manual_tests += \ test-install \ test-watchdog \ test-log \ - test-ipcrm + test-ipcrm \ + test-btrfs if HAVE_KMOD manual_tests += \ @@ -1756,6 +1759,12 @@ test_ipcrm_LDADD = \ libsystemd-shared.la \ -lrt +test_btrfs_SOURCES = \ + src/test/test-btrfs.c + +test_btrfs_LDADD = \ + libsystemd-shared.la + test_rtnl_manual_SOURCES = \ src/test/test-rtnl-manual.c diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c new file mode 100644 index 000000000..fcf543a46 --- /dev/null +++ b/src/shared/btrfs-util.c @@ -0,0 +1,294 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#ifdef HAVE_LINUX_BTRFS_H +#include +#endif + +#include "missing.h" +#include "util.h" +#include "path-util.h" +#include "macro.h" +#include "strv.h" +#include "copy.h" +#include "btrfs-util.h" + +static int validate_subvolume_name(const char *name) { + + if (!filename_is_valid(name)) + return -EINVAL; + + if (strlen(name) > BTRFS_SUBVOL_NAME_MAX) + return -E2BIG; + + return 0; +} + +static int open_parent(const char *path, int flags) { + _cleanup_free_ char *parent = NULL; + int r, fd; + + assert(path); + + r = path_get_parent(path, &parent); + if (r < 0) + return r; + + fd = open(parent, flags); + if (fd < 0) + return -errno; + + return fd; +} + +static int extract_subvolume_name(const char *path, const char **subvolume) { + const char *fn; + int r; + + assert(path); + assert(subvolume); + + fn = basename(path); + + r = validate_subvolume_name(fn); + if (r < 0) + return r; + + *subvolume = fn; + return 0; +} + +int btrfs_is_snapshot(int fd) { + struct stat st; + struct statfs sfs; + + if (fstatfs(fd, &sfs) < 0) + return -errno; + + if (!F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) + return 0; + + if (fstat(fd, &st) < 0) + return -errno; + + /* On btrfs subvolumes always have the inode 256 */ + + return S_ISDIR(st.st_mode) && st.st_ino == 256; +} + +int btrfs_subvol_snapshot(const char *old_path, const char *new_path, bool read_only, bool fallback_copy) { + struct btrfs_ioctl_vol_args_v2 args = { + .flags = read_only ? BTRFS_SUBVOL_RDONLY : 0, + }; + _cleanup_close_ int old_fd = -1, new_fd = -1; + const char *subvolume; + int r; + + assert(old_path); + + old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (old_fd < 0) + return -errno; + + r = btrfs_is_snapshot(old_fd); + if (r < 0) + return r; + if (r == 0) { + + if (fallback_copy) { + r = btrfs_subvol_make(new_path); + if (r < 0) + return r; + + r = copy_tree_fd(old_fd, new_path, true); + if (r < 0) { + btrfs_subvol_remove(new_path); + return r; + } + + if (read_only) { + r = btrfs_subvol_read_only(new_path, true); + if (r < 0) { + btrfs_subvol_remove(new_path); + return r; + } + } + + return 0; + } + + return -EISDIR; + } + + r = extract_subvolume_name(new_path, &subvolume); + if (r < 0) + return r; + + new_fd = open_parent(new_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (new_fd < 0) + return new_fd; + + strncpy(args.name, subvolume, sizeof(args.name)-1); + args.fd = old_fd; + + if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_subvol_make(const char *path) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_close_ int fd = -1; + const char *subvolume; + int r; + + assert(path); + + r = extract_subvolume_name(path, &subvolume); + if (r < 0) + return r; + + fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return fd; + + strncpy(args.name, subvolume, sizeof(args.name)-1); + + if (ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_subvol_remove(const char *path) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_close_ int fd = -1; + const char *subvolume; + int r; + + assert(path); + + r = extract_subvolume_name(path, &subvolume); + if (r < 0) + return r; + + fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return fd; + + strncpy(args.name, subvolume, sizeof(args.name)-1); + + if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_subvol_read_only(const char *path, bool b) { + _cleanup_close_ int fd = -1; + uint64_t flags, nflags; + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) + return -errno; + + if (b) + nflags = flags | BTRFS_SUBVOL_RDONLY; + else + nflags = flags & ~BTRFS_SUBVOL_RDONLY; + + if (flags == nflags) + return 0; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &nflags) < 0) + return -errno; + + return 0; +} + +int btrfs_reflink(int infd, int outfd) { + int r; + + assert(infd >= 0); + assert(outfd >= 0); + + r = ioctl(outfd, BTRFS_IOC_CLONE, infd); + if (r < 0) + return -errno; + + return 0; +} + +int btrfs_get_block_device(const char *path, dev_t *dev) { + struct btrfs_ioctl_fs_info_args fsi = {}; + _cleanup_close_ int fd = -1; + uint64_t id; + + assert(path); + assert(dev); + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0) + return -errno; + + /* We won't do this for btrfs RAID */ + if (fsi.num_devices != 1) + return 0; + + for (id = 1; id <= fsi.max_id; id++) { + struct btrfs_ioctl_dev_info_args di = { + .devid = id, + }; + struct stat st; + + if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { + if (errno == ENODEV) + continue; + + return -errno; + } + + if (stat((char*) di.path, &st) < 0) + return -errno; + + if (!S_ISBLK(st.st_mode)) + return -ENODEV; + + if (major(st.st_rdev) == 0) + return -ENODEV; + + *dev = st.st_rdev; + return 1; + } + + return -ENODEV; +} diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h new file mode 100644 index 000000000..28dbeb4e2 --- /dev/null +++ b/src/shared/btrfs-util.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +int btrfs_is_snapshot(int fd); + +int btrfs_subvol_make(const char *path); +int btrfs_subvol_remove(const char *path); +int btrfs_subvol_snapshot(const char *old_path, const char *new_path, bool read_only, bool fallback_copy); +int btrfs_subvol_read_only(const char *path, bool b); + +int btrfs_reflink(int infd, int outfd); + +int btrfs_get_block_device(const char *path, dev_t *dev); diff --git a/src/shared/copy.c b/src/shared/copy.c index b8b1ba186..f22a94099 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -22,6 +22,7 @@ #include #include "util.h" +#include "btrfs-util.h" #include "copy.h" int copy_bytes(int fdf, int fdt, off_t max_bytes) { @@ -187,20 +188,28 @@ static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, return r; } -static int fd_copy_directory(int df, const char *from, const struct stat *st, int dt, const char *to, dev_t original_device, bool merge) { +static int fd_copy_directory( + int df, + const char *from, + const struct stat *st, + int dt, + const char *to, + dev_t original_device, + bool merge) { + _cleanup_close_ int fdf = -1, fdt = -1; _cleanup_closedir_ DIR *d = NULL; struct dirent *de; bool created; int r; - assert(from); assert(st); assert(to); - fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fdf < 0) - return -errno; + if (from) + fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + else + fdf = fcntl(df, F_DUPFD_CLOEXEC, 3); d = fdopendir(fdf); if (!d) @@ -287,6 +296,22 @@ int copy_tree(const char *from, const char *to, bool merge) { return -ENOTSUP; } +int copy_tree_fd(int dirfd, const char *to, bool merge) { + + struct stat st; + + assert(dirfd >= 0); + assert(to); + + if (fstat(dirfd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge); +} + int copy_file_fd(const char *from, int fdt) { _cleanup_close_ int fdf = -1; diff --git a/src/shared/copy.h b/src/shared/copy.h index 62932112a..15faf548a 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -27,4 +27,5 @@ int copy_file_fd(const char *from, int to); int copy_file(const char *from, const char *to, int flags, mode_t mode); int copy_tree(const char *from, const char *to, bool merge); +int copy_tree_fd(int dirfd, const char *to, bool merge); int copy_bytes(int fdf, int fdt, off_t max_bytes); diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c new file mode 100644 index 000000000..7c4cc5544 --- /dev/null +++ b/src/test/test-btrfs.c @@ -0,0 +1,68 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "log.h" +#include "btrfs-util.h" +#include "fileio.h" + +int main(int argc, char *argv[]) { + int r; + + r = btrfs_subvol_make("/xxxtest"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = write_string_file("/xxxtest/afile", "ljsadhfljasdkfhlkjdsfha"); + if (r < 0) + log_error_errno(r, "Failed to write file: %m"); + + r = btrfs_subvol_snapshot("/xxxtest", "/xxxtest2", false, false); + if (r < 0) + log_error_errno(r, "Failed to make snapshot: %m"); + + r = btrfs_subvol_snapshot("/xxxtest", "/xxxtest3", true, false); + if (r < 0) + log_error_errno(r, "Failed to make snapshot: %m"); + + r = btrfs_subvol_remove("/xxxtest"); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + r = btrfs_subvol_remove("/xxxtest2"); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + r = btrfs_subvol_remove("/xxxtest3"); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + r = btrfs_subvol_snapshot("/etc", "/etc2", true, true); + if (r < 0) + log_error_errno(r, "Failed to make snapshot: %m"); + + r = btrfs_subvol_remove("/etc2"); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + return 0; +}