From: Arvin Schnell Date: Thu, 17 Sep 2015 16:11:35 +0000 (+0200) Subject: - added program to create a subvolume X-Git-Tag: v0.3.3~58^2~7 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6e2c686c48fdfc69c56fd88c65f63ef52dbb4d43;p=thirdparty%2Fsnapper.git - added program to create a subvolume --- diff --git a/client/.gitignore b/client/.gitignore index 896fc183..f37ca1b4 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -2,3 +2,4 @@ snapper systemd-helper installation-helper +mksubvolume diff --git a/client/Makefile.am b/client/Makefile.am index aecddbde..8652a015 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -18,6 +18,17 @@ snapper_SOURCES = \ snapper_LDADD = ../snapper/libsnapper.la utils/libutils.la ../dbus/libdbus.la +if ENABLE_ROLLBACK + +sbin_PROGRAMS = mksubvolume + +mksubvolume_SOURCES = \ + mksubvolume.cc + +mksubvolume_LDADD = ../snapper/libsnapper.la utils/libutils.la + +endif + libexecdir = /usr/lib/snapper libexec_PROGRAMS = installation-helper systemd-helper diff --git a/client/mksubvolume.cc b/client/mksubvolume.cc new file mode 100644 index 00000000..4b2c6b92 --- /dev/null +++ b/client/mksubvolume.cc @@ -0,0 +1,356 @@ +/* + * Copyright (c) [2015] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact Novell, Inc. + * + * To contact Novell about this file by physical or electronic mail, you may + * find current contact information at www.novell.com. + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "snapper/BtrfsUtils.h" +#include "snapper/AppUtil.h" +#include "snapper/MntTable.h" +#include "utils/GetOpts.h" + + +using namespace std; + +using namespace snapper; +using namespace BtrfsUtils; + + +string target; + +bool set_nocow = false; +bool verbose = false; + + +void +do_tmp_mount(const libmnt_fs* fs, const char* tmp_mountpoint, const string& subvol_option) +{ + if (verbose) + cout << "do-tmp-mount" << endl; + + libmnt_fs* x = mnt_copy_fs(NULL, fs); + if (!x) + throw runtime_error("mnt_copy_fs failed"); + + struct libmnt_context* cxt = mnt_new_context(); + mnt_context_set_fs(cxt, x); + mnt_context_set_target(cxt, tmp_mountpoint); + if (!subvol_option.empty()) + mnt_context_set_options(cxt, ("subvol=" + subvol_option).c_str()); + else + mnt_context_set_options(cxt, "subvolid=5"); + + int ret = mnt_context_mount(cxt); + if (ret != 0) + throw runtime_error(sformat("mnt_context_mount failed, ret:%d", ret)); + + mnt_free_context(cxt); + mnt_free_fs(x); +} + + +void +do_create_subvolume(const char* tmp_mountpoint, const string& subvolume_name) +{ + if (verbose) + cout << "do-create-subvolume" << endl; + + string parent = sformat("%s/%s", tmp_mountpoint, dirname(subvolume_name).c_str()); + + if (mkdir(parent.c_str(), 0777) != 0 && errno != EEXIST) + throw runtime_error_with_errno("mkdir failed", errno); + + int fd = open(parent.c_str(), O_RDONLY); + if (fd < 0) + throw runtime_error_with_errno("open failed", errno); + + create_subvolume(fd, basename(subvolume_name)); + + close(fd); +} + + +void +do_tmp_umount(const libmnt_fs* fs, const char* tmp_mountpoint) +{ + if (verbose) + cout << "do-tmp-umount" << endl; + + system("/sbin/udevadm settle --timeout 20"); + + libmnt_fs* x = mnt_copy_fs(NULL, fs); + if (!x) + throw runtime_error("mnt_copy_fs failed"); + + struct libmnt_context* cxt = mnt_new_context(); + mnt_context_set_fs(cxt, x); + mnt_context_set_target(cxt, tmp_mountpoint); + + int ret = mnt_context_umount(cxt); + if (ret != 0) + throw runtime_error(sformat("mnt_context_umount failed, ret:%d", ret)); + + mnt_free_context(cxt); + mnt_free_fs(x); +} + + +void +do_add_fstab_and_mount(MntTable& mnt_table, const libmnt_fs* fs, const string& subvol_option, + const string& subvolume_name) +{ + if (verbose) + cout << "do-add-fstab-and-mount" << endl; + + libmnt_fs* x = mnt_copy_fs(NULL, fs); + if (!x) + throw runtime_error("mnt_copy_fs failed"); + + mnt_fs_set_target(x, target.c_str()); + + string y = subvolume_name; + if (!subvol_option.empty()) + y.insert(0, subvol_option + "/"); + + char* options = mnt_fs_strdup_options(x); + mnt_optstr_remove_option(&options, "defaults"); + mnt_optstr_set_option(&options, "subvol", y.c_str()); + mnt_fs_set_options(x, options); + free(options); + + // Caution: mnt_context_mount may change the source of x so the fstab + // functions must be called first. + mnt_table.add_fs(x); + mnt_table.replace_file(); + + if (mkdir(target.c_str(), 0777) != 0 && errno != EEXIST) + throw runtime_error_with_errno("mkdir failed", errno); + + struct libmnt_context* cxt = mnt_new_context(); + mnt_context_set_fs(cxt, x); + + int ret = mnt_context_mount(cxt); + if (ret != 0) + throw runtime_error(sformat("mnt_context_mount failed, ret:%d", ret)); + + mnt_free_context(cxt); + + mnt_free_fs(x); +} + + +void +do_set_nocow() +{ + if (!set_nocow) + return; + + if (verbose) + cout << "do-set-nocow" << endl; + + int fd = open(target.c_str(), O_RDONLY); + if (fd == -1) + { + throw runtime_error_with_errno("open failed", errno); + } + + unsigned long flags = 0; + + if (ioctl(fd, EXT2_IOC_GETFLAGS, &flags) == -1) + { + close(fd); + throw runtime_error_with_errno("ioctl(EXT2_IOC_GETFLAGS) failed", errno); + } + + flags |= FS_NOCOW_FL; + + if (ioctl(fd, EXT2_IOC_SETFLAGS, &flags) == -1) + { + close(fd); + throw runtime_error_with_errno("ioctl(EXT2_IOC_SETFLAGS) failed", errno); + } + + close(fd); +} + + +void +doit() +{ + if (verbose) + cout << "target:" << target << endl; + + if (access(target.c_str(), F_OK) == 0) + throw runtime_error("target exists"); + + if (access(dirname(target).c_str(), F_OK) != 0) + throw runtime_error("parent of target exists"); + + MntTable mnt_table("/"); + mnt_table.parse_fstab(); + + // Find filesystem. + + libmnt_fs* fs = mnt_table.find_target_up(target, MNT_ITER_FORWARD); + string fs_fstype = mnt_fs_get_fstype(fs); + string fs_target = mnt_fs_get_target(fs); + + if (verbose) + { + cout << "fs-device:" << mnt_fs_get_source(fs) << endl; + cout << "fs-fstype:" << fs_fstype << endl; + cout << "fs-target:" << fs_target << endl; + cout << "fs-options:" << mnt_fs_get_options(fs) << endl; + } + + if (fs_fstype != "btrfs") + throw runtime_error("filesystem is not btrfs"); + + if (fs_target == target) + throw runtime_error("target exists in fstab"); + + // Get default subvolume of filesystem. + + int fd = open(fs_target.c_str(), O_RDONLY); + if (fd == -1) + throw runtime_error_with_errno("open failed", errno); + + subvolid_t default_subvolume_id = get_default_id(fd); + if (verbose) + cout << "default-subvolume-id:" << default_subvolume_id << endl; + + string default_subvolume_name = get_subvolume(fd, default_subvolume_id); + if (verbose) + cout << "default-subvolume-name:" << default_subvolume_name << endl; + + close(fd); + + // Determine subvol mount-option for tmp mount. The '@' is used on SLE. + + string subvol_option = boost::starts_with(default_subvolume_name, "@/") ? "@" : ""; + if (verbose) + cout << "subvol-option:" << subvol_option << endl; + + // Determine name for new subvolume. + + string subvolume_name = target.substr(fs_target.size() + (fs_target == "/" ? 0 : 1)); + if (verbose) + cout << "subvolume-name:" << subvolume_name << endl; + + // Execute all steps. + + char* tmp_mountpoint = strdup("/tmp/mksubvolume-XXXXXX"); + if (!mkdtemp(tmp_mountpoint)) + throw runtime_error_with_errno("mkdtemp failed", errno); + + do_tmp_mount(fs, tmp_mountpoint, subvol_option); + + try + { + do_create_subvolume(tmp_mountpoint, subvolume_name); + } + catch (...) + { + // Rethrow the original exception, not a potential exception of do_tmp_umount. + try + { + do_tmp_umount(fs, tmp_mountpoint); + rmdir(tmp_mountpoint); + free(tmp_mountpoint); + } + catch (...) + { + } + + throw; + } + + do_tmp_umount(fs, tmp_mountpoint); + rmdir(tmp_mountpoint); + free(tmp_mountpoint); + + do_add_fstab_and_mount(mnt_table, fs, subvol_option, subvolume_name); + + do_set_nocow(); +} + + +void usage() __attribute__ ((__noreturn__)); + +void +usage() +{ + cerr << "usage: --target=target [--nocow] [--verbose]" << endl; + exit(EXIT_FAILURE); +} + + +int +main(int argc, char** argv) +{ + setlocale(LC_ALL, ""); + + const struct option options[] = { + { "target", required_argument, 0, 't' }, + { "nocow", no_argument, 0, 0 }, + { "verbose", no_argument, 0, 'v' }, + { 0, 0, 0, 0 } + }; + + GetOpts getopts; + + getopts.init(argc, argv); + + GetOpts::parsed_opts opts = getopts.parse(options); + + GetOpts::parsed_opts::const_iterator opt; + + if ((opt = opts.find("target")) != opts.end()) + target = opt->second; + else + usage(); + + if ((opt = opts.find("nocow")) != opts.end()) + set_nocow = true; + + if ((opt = opts.find("verbose")) != opts.end()) + verbose = true; + + try + { + doit(); + exit(EXIT_SUCCESS); + } + catch (const std::exception& e) + { + cerr << "failure (" << e.what() << ")" << endl; + exit(EXIT_FAILURE); + } +} diff --git a/configure.ac b/configure.ac index c8b44703..1a9b0f4c 100644 --- a/configure.ac +++ b/configure.ac @@ -167,6 +167,7 @@ AC_OUTPUT( doc/snapper-zypp-plugin.xml:doc/snapper-zypp-plugin.xml.in doc/snapper-zypp-plugin.conf.xml:doc/snapper-zypp-plugin.conf.xml.in doc/pam_snapper.xml:doc/pam_snapper.xml.in + doc/mksubvolume.xml:doc/mksubvolume.xml.in po/Makefile testsuite/Makefile testsuite-real/Makefile diff --git a/doc/.gitignore b/doc/.gitignore index d67edd82..a1730765 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -4,15 +4,18 @@ snapper-configs.xml snapper-zypp-plugin.xml snapper-zypp-plugin.conf.xml pam_snapper.xml +mksubvolume.xml snapper.8 snapperd.8 snapper-configs.5 snapper-zypp-plugin.8 snapper-zypp-plugin.conf.5 pam_snapper.8 +mksubvolume.8 snapper.html snapperd.html snapper-configs.html snapper-zypp-plugin.html snapper-zypp-plugin.conf.html pam_snapper.html +mksubvolume.html diff --git a/doc/Makefile.am b/doc/Makefile.am index b69a57ce..73d2d766 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -12,6 +12,10 @@ if HAVE_ZYPP man_MANS += snapper-zypp-plugin.conf.5 snapper-zypp-plugin.8 endif +if ENABLE_ROLLBACK +man_MANS += mksubvolume.8 +endif + .xml.5: .xml $(XSLTPROC) --nonet manpages.xsl $< diff --git a/doc/mksubvolume.xml.in b/doc/mksubvolume.xml.in new file mode 100644 index 00000000..e1706dae --- /dev/null +++ b/doc/mksubvolume.xml.in @@ -0,0 +1,75 @@ + + + + + 2015-09-17 + + + + mksubvolume + 8 + 2015-09-17 + @VERSION@ + Filesystem Snapshot Management + + + + mksubvolume + Command-line program to create btrfs subvolume + + + + + mksubvolume + --target=path + --nocow + --verbose + + + + + DESCRIPTION + Mksubvolume is a command-line program to create a btrfs subvolume + including adding a fstab entry and mounting the subvolume. + + + + OPTIONS + + + + + Path of the subvolume to create. + + + + + + Set the no-copy-on-write flag for the subvolume. + + + + + + Increase verbosity. + + + + + + + AUTHORS + Arvin Schnell aschnell@suse.com + + + + SEE ALSO + + snapper5, + btrfs8, + fstab5, + mount8 + + + + diff --git a/snapper.spec.in b/snapper.spec.in index c874dff7..7e19de14 100644 --- a/snapper.spec.in +++ b/snapper.spec.in @@ -27,6 +27,7 @@ BuildRequires: gcc-c++ BuildRequires: libacl-devel BuildRequires: libtool BuildRequires: libxml2-devel +BuildRequires: e2fsprogs-devel %if 0%{?suse_version} > 1230 BuildRequires: libbtrfs-devel %endif @@ -109,10 +110,12 @@ rm -rf "$RPM_BUILD_ROOT" %defattr(-,root,root) %{prefix}/bin/snapper %{prefix}/sbin/snapperd +%{prefix}/sbin/mksubvolume %{prefix}/lib/snapper %doc %{_mandir}/*/snapper.8* %doc %{_mandir}/*/snapperd.8* %doc %{_mandir}/*/snapper-configs.5* +%doc %{_mandir}/*/mksubvolume.8* %config(noreplace) %{_sysconfdir}/logrotate.d/snapper /etc/cron.hourly/suse.de-snapper /etc/cron.daily/suse.de-snapper