]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- added program to create a subvolume
authorArvin Schnell <aschnell@suse.de>
Thu, 17 Sep 2015 16:11:35 +0000 (18:11 +0200)
committerArvin Schnell <aschnell@suse.de>
Thu, 17 Sep 2015 16:11:35 +0000 (18:11 +0200)
client/.gitignore
client/Makefile.am
client/mksubvolume.cc [new file with mode: 0644]
configure.ac
doc/.gitignore
doc/Makefile.am
doc/mksubvolume.xml.in [new file with mode: 0644]
snapper.spec.in

index 896fc1837857c2bbcd4be3bb44a46bd37aac4ed4..f37ca1b44a16c9f3b07d5a74bf2225a439b71f98 100644 (file)
@@ -2,3 +2,4 @@
 snapper
 systemd-helper
 installation-helper
+mksubvolume
index aecddbdeab65d7ce4abcc13001c2d11234e32706..8652a01515cb403e6420b524073676d260710bc6 100644 (file)
@@ -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 (file)
index 0000000..4b2c6b9
--- /dev/null
@@ -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 <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ext2fs/ext2_fs.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include <iostream>
+#include <boost/algorithm/string.hpp>
+
+#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);
+    }
+}
index c8b4470323825802cf7071a7842f5153cadc46b2..1a9b0f4cbc07c110023dd8762b2f4d03eca86e50 100644 (file)
@@ -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
index d67edd82d248ae1329763d1a3686e2fb2a3300f2..a173076552544c80515b71d765c549eb5af4ff4e 100644 (file)
@@ -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
index b69a57ce50c4a729b482398583218fb0214d38f0..73d2d766172b7feeec5eb73c6e268ee8f9295910 100644 (file)
@@ -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 (file)
index 0000000..e1706da
--- /dev/null
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<refentry id='mksubvolume8'>
+
+  <refentryinfo>
+    <date>2015-09-17</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>mksubvolume</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo class='date'>2015-09-17</refmiscinfo>
+    <refmiscinfo class='version'>@VERSION@</refmiscinfo>
+    <refmiscinfo class='manual'>Filesystem Snapshot Management</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>mksubvolume</refname>
+    <refpurpose>Command-line program to create btrfs subvolume</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv id='synopsis'>
+    <cmdsynopsis>
+      <command>mksubvolume</command>
+      <arg choice='req'>--target=<replaceable>path</replaceable></arg>
+      <arg choice='opt'>--nocow</arg>
+      <arg choice='opt'>--verbose</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1 id='description'>
+    <title>DESCRIPTION</title>
+    <para>Mksubvolume is a command-line program to create a btrfs subvolume
+    including adding a fstab entry and mounting the subvolume.</para>
+  </refsect1>
+
+  <refsect1 id='global_options'>
+    <title>OPTIONS</title>
+    <variablelist>
+      <varlistentry>
+       <term><option>--target <replaceable>path</replaceable></option></term>
+       <listitem>
+         <para>Path of the subvolume to create.</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term><option>--nocow</option></term>
+       <listitem>
+         <para>Set the no-copy-on-write flag for the subvolume.</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term><option>-v, --verbose</option></term>
+       <listitem>
+         <para>Increase verbosity.</para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1 id='authors'>
+    <title>AUTHORS</title>
+    <para>Arvin Schnell <email>aschnell@suse.com</email></para>
+  </refsect1>
+
+  <refsect1 id='see_also'>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry><refentrytitle>snapper</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry role="nolink"><refentrytitle>btrfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry role="nolink"><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry role="nolink"><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+
+</refentry>
index c874dff76ee570c58266c1d4136fc5a7bf589a08..7e19de144fbc4c93ab5c11c862e4851cf5e521a4 100644 (file)
@@ -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