--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+<?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>