From 72c7df6b77de43be45654cb62a94edc29953cd7c Mon Sep 17 00:00:00 2001 From: Daniel Sullivan Date: Mon, 2 Oct 2017 01:45:28 -0500 Subject: [PATCH 1/2] Basic ZFS functionality Passes basic and real testsuites --- configure.ac | 15 +- snapper/Filesystem.cc | 6 + snapper/Makefile.am | 6 + snapper/Snapper.cc | 7 + snapper/Zfs.cc | 328 ++++++++++++++++++++++++++++++++++++++++++ snapper/Zfs.h | 94 ++++++++++++ snapper/ZfsUtils.cc | 163 +++++++++++++++++++++ snapper/ZfsUtils.h | 63 ++++++++ 8 files changed, 681 insertions(+), 1 deletion(-) create mode 100755 snapper/Zfs.cc create mode 100755 snapper/Zfs.h create mode 100644 snapper/ZfsUtils.cc create mode 100644 snapper/ZfsUtils.h diff --git a/configure.ac b/configure.ac index 03ee675d9..bd3822ec9 100644 --- a/configure.ac +++ b/configure.ac @@ -37,6 +37,8 @@ AC_PATH_PROG([LVSBIN], [lvs], [/sbin/lvs]) AC_PATH_PROG([LVCHANGEBIN], [lvchange], [/sbin/lvchange]) AC_PATH_PROG([LVMBIN], [lvm], [/sbin/lvm]) AC_PATH_PROG([LVRENAMEBIN], [lvrename], [/sbin/lvrename]) +AC_PATH_PROG([ZFSBIN], [zfs], [/sbin/zfs]) +AC_PATH_PROG([ZPOOLBIN], [zpool], [/sbin/zpool]) AC_DEFINE_UNQUOTED([CHSNAPBIN], ["$CHSNAPBIN"], [Path of chsnap program.]) AC_DEFINE_UNQUOTED([CPBIN], ["$CPBIN"], [Path of cp program.]) @@ -50,6 +52,8 @@ AC_DEFINE_UNQUOTED([LVSBIN], ["$LVSBIN"], [Path of lvs program.]) AC_DEFINE_UNQUOTED([LVCHANGEBIN], ["$LVCHANGEBIN"], [Path of lvchange program.]) AC_DEFINE_UNQUOTED([LVMBIN], ["$LVMBIN"], [Path of lvm program.]) AC_DEFINE_UNQUOTED([LVRENAMEBIN], ["$LVRENAMEBIN"], [Path of lvrename program.]) +AC_DEFINE_UNQUOTED([ZFSBIN], ["$ZFSBIN"], [Path of zfs program.]) +AC_DEFINE_UNQUOTED([ZPOOLBIN], ["$ZPOOLBIN"], [Path of zpool program.]) dnl Automake 1.11 enables silent compilation dnl Disable it by "configure --disable-silent-rules" or "make V=1" @@ -93,7 +97,16 @@ if test "x$with_lvm" = "xyes"; then AC_DEFINE(ENABLE_LVM, 1, [Enable LVM thin-provisioned snapshots support]) fi -if test "x$with_btrfs" != "xyes" -a "x$with_lvm" != "xyes" -a "x$with_ext4" != "xyes"; then +AC_ARG_ENABLE([zfs], AC_HELP_STRING([--disable-zfs],[Disable Zfs internal snapshots support]), + [with_zfs=$enableval],[with_zfs=yes]) + +AM_CONDITIONAL(ENABLE_ZFS, [test "x$with_zfs" = "xyes"]) + +if test "x$with_zfs" = "xyes"; then + AC_DEFINE(ENABLE_ZFS, 1, [Enable Zfs internal snapshots support]) +fi + +if test "x$with_btrfs" != "xyes" -a "x$with_lvm" != "xyes" -a "x$with_ext4" != "xyes" -a "x$with_zfs" != "xyes"; then AC_MSG_ERROR([You have to enable at least one snapshot type (remove some --disable-xxx parameter)]) fi diff --git a/snapper/Filesystem.cc b/snapper/Filesystem.cc index 204ae98c2..df9f8418f 100644 --- a/snapper/Filesystem.cc +++ b/snapper/Filesystem.cc @@ -45,6 +45,9 @@ #ifdef ENABLE_LVM #include "snapper/Lvm.h" #endif +#ifdef ENABLE_ZFS +#include "snapper/Zfs.h" +#endif #include "snapper/Snapper.h" #include "snapper/SnapperTmpl.h" #include "snapper/SnapperDefines.h" @@ -106,6 +109,9 @@ namespace snapper #endif #ifdef ENABLE_LVM &Lvm::create, +#endif +#ifdef ENABLE_ZFS + &Zfs::create, #endif NULL }; diff --git a/snapper/Makefile.am b/snapper/Makefile.am index 36467dadb..ae92247f0 100644 --- a/snapper/Makefile.am +++ b/snapper/Makefile.am @@ -51,6 +51,12 @@ libsnapper_la_SOURCES += \ LvmCache.cc LvmCache.h endif +if ENABLE_ZFS +libsnapper_la_SOURCES += \ + Zfs.cc Zfs.h \ + ZfsUtils.cc ZfsUtils.h +endif + if ENABLE_ROLLBACK libsnapper_la_SOURCES += \ MntTable.cc MntTable.h diff --git a/snapper/Snapper.cc b/snapper/Snapper.cc index 9159e11fe..2cbe56a92 100644 --- a/snapper/Snapper.cc +++ b/snapper/Snapper.cc @@ -23,6 +23,7 @@ #include "config.h" +#include "snapper/ZfsUtils.h" #include #include #include @@ -48,6 +49,7 @@ #include "snapper/Hooks.h" #include "snapper/Btrfs.h" #include "snapper/BtrfsUtils.h" +#include "snapper/ZfsUtils.h" #ifdef ENABLE_SELINUX #include "snapper/Selinux.h" #include "snapper/Regex.h" @@ -943,6 +945,11 @@ namespace snapper #endif "ext4," +#ifndef ENABLE_ZFS + "no-" +#endif + "zfs-" + #ifndef ENABLE_XATTRS "no-" #endif diff --git a/snapper/Zfs.cc b/snapper/Zfs.cc new file mode 100755 index 000000000..f843d42c2 --- /dev/null +++ b/snapper/Zfs.cc @@ -0,0 +1,328 @@ +/* + * Copyright (c) [2011-2013] Novell, Inc. + * + * 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. + */ + +/* ZFS Snapper 2017 Daniel Sullivan (mumblepins@gmail.com) + * + * TODO: + * - Deal with ACLs + * - implement bind and legacy mounts * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snapper/Log.h" +#include "snapper/Filesystem.h" +#include "snapper/Zfs.h" +#include "snapper/Snapper.h" +#include "snapper/SnapperTmpl.h" +#include "snapper/SystemCmd.h" +#include "snapper/SnapperDefines.h" + + +namespace snapper +{ + + + Filesystem* + Zfs::create(const string& fstype, const string& subvolume, const string& root_prefix) + { + if (fstype == "zfs") + return new Zfs(subvolume, root_prefix); + + return NULL; + } + + + Zfs::Zfs(const string& subvolume, const string& root_prefix) + : Filesystem(subvolume, root_prefix) + { + if (access(ZFSBIN, X_OK) != 0) + { + throw ProgramNotInstalledException(ZFSBIN " not installed"); + } + + bool found = false; + MtabData mtab_data; + + if (!getMtabData(subvolume, found, mtab_data)) + throw InvalidConfigException(); + + if (!found) + { + y2err("filesystem not mounted"); + throw InvalidConfigException(); + } + + zfs_subvol_fs = mtab_data.device; + zfs_snapshot_fs = zfs_subvol_fs + "/.snapshots"; + zfs_zpool = poolname(zfs_subvol_fs); + } + + + void + Zfs::evalConfigInfo(const ConfigInfo& config_info) { + // TODO: Add multiple "mount" types + } + + + void + Zfs::createConfig() const + { + + + SDir subvolume_dir = openSubvolumeDir(); + + int r1 = subvolume_dir.mkdir(".snapshots", 0700); + if (r1 != 0 && errno != EEXIST) + { + y2err("mkdir failed errno:" << errno << " (" << strerror(errno) << ")"); + SN_THROW(CreateConfigFailedException("mkdir failed")); + } + + SDir infos_dir = openInfosDir(); + + create_volume(infos_dir.fullname(), zfs_snapshot_fs); + } + + + void + Zfs::deleteConfig() const + { + SDir subvolume_dir = openSubvolumeDir(); + delete_volume(zfs_snapshot_fs); + subvolume_dir.unlink(".snapshots", AT_REMOVEDIR); + } + + + string + Zfs::snapshotDir(unsigned int num) const + { + return (subvolume == "/" ? "" : subvolume) + "/.snapshots/" + decString(num) + + "/snapshot"; + } + + + SDir + Zfs::openInfosDir() const + { + SDir subvolume_dir = openSubvolumeDir(); + SDir infos_dir(subvolume_dir, ".snapshots"); + + struct stat stat; + if (infos_dir.stat(&stat) != 0) + { + throw IOErrorException("stat on .snapshots failed"); + } + + if (stat.st_uid != 0) + { + y2err(".snapshots must have owner root"); + throw IOErrorException(".snapshots must have owner root"); + } + + if (stat.st_gid != 0 && stat.st_mode & S_IWGRP) + { + y2err(".snapshots must have group root or must not be group-writable"); + throw IOErrorException(".snapshots must have group root or must not be group-writable"); + } + + if (stat.st_mode & S_IWOTH) + { + y2err(".snapshots must not be world-writable"); + throw IOErrorException(".snapshots must not be world-writable"); + } + + return infos_dir; + } + + + SDir + Zfs::openSnapshotDir(unsigned int num) const + { + SDir info_dir = openInfoDir(num); + SDir snapshot_dir = info_dir; + if (mount_type == SYMLINK) + { + snapshot_dir = follow_symlink(info_dir, "snapshot"); + } + else + { + snapshot_dir = SDir(info_dir, "snapshot"); + } + return snapshot_dir; + } + + + string + Zfs::snapshotName(unsigned int num) const + { + return "snapper-" + decString(num); + } + + + void + Zfs::createSnapshot(unsigned int num, unsigned int num_parent, bool read_only, bool quota) const + { + if (num_parent != 0 || !read_only) + throw std::logic_error("not implemented"); + + SDir subvolume_dir = openSubvolumeDir(); + SDir info_dir = openInfoDir(num); + create_snapshot(zfs_subvol_fs, snapshotName(num)); + switch (mount_type) + { + case SYMLINK: + symlink("../../.zfs/snapshot/" + snapshotName(num), info_dir.fullname() + "/snapshot"); + break; + case BIND: + // TODO + break; + case LEGACY: + // TODO + break; + } + } + + + void + Zfs::deleteSnapshot(unsigned int num) const + { + + delete_volume(zfs_subvol_fs + "@" + snapshotName(num)); + + switch (mount_type) + { + case SYMLINK: + { + // need to delete symlink + SDir info_dir = openInfoDir(num); + info_dir.unlink("snapshot", 0); + } + break; + case BIND: + // TODO + break; + case LEGACY: + // TODO + break; + } + } + + + bool + Zfs::isSnapshotMounted(unsigned int num) const + { + switch (mount_type) + { + case SYMLINK: + // TODO + break; + case BIND: + // TODO + break; + case LEGACY: + // TODO + break; + } + return true; + } + + + void + Zfs::mountSnapshot(unsigned int num) const + { + switch (mount_type) + { + case SYMLINK: + // TODO + break; + case BIND: + // TODO + break; + case LEGACY: + // TODO + break; + } + } + + + void + Zfs::umountSnapshot(unsigned int num) const + { + switch (mount_type) + { + case SYMLINK: + // TODO + break; + case BIND: + // TODO + break; + case LEGACY: + // TODO + break; + } + } + + + bool + Zfs::isSnapshotReadOnly(unsigned int num) const + { + return true; + } + + + bool + Zfs::checkSnapshot(unsigned int num) const + { + try + { + //TODO make better + SDir info_dir = openInfoDir(num); + + struct stat stat; + int r = info_dir.stat("snapshot", &stat, AT_SYMLINK_NOFOLLOW); + return r == 0; + } + catch (const IOErrorException& e) + { + return false; + } + } + + + void + Zfs::sync() const { + // TODO + } + +} + + diff --git a/snapper/Zfs.h b/snapper/Zfs.h new file mode 100755 index 000000000..f3948707a --- /dev/null +++ b/snapper/Zfs.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) [2011-2013] Novell, Inc. + * + * 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. + */ + + +#ifndef SNAPPER_ZFS_H +#define SNAPPER_ZFS_H + + +#include "snapper/Filesystem.h" +#include "snapper/ZfsUtils.h" + + +namespace snapper +{ + + using namespace ZfsUtils; + + enum ZfsMountType + { + SYMLINK, BIND, LEGACY + }; + + class Zfs : public Filesystem + { + public: + + static Filesystem* create (const string& fstype, const string& subvolume, + const string& root_prefix); + + Zfs (const string& subvolume, const string& root_prefix); + + virtual void evalConfigInfo (const ConfigInfo& config_info); + + virtual string + fstype () const + { + return "zfs"; + } + + virtual void createConfig () const; + virtual void deleteConfig () const; + + + virtual string snapshotName (unsigned int num) const; + virtual string snapshotDir (unsigned int num) const; + + + virtual SDir openInfosDir () const; + virtual SDir openSnapshotDir (unsigned int num) const; + + virtual void createSnapshot (unsigned int num, unsigned int num_parent, + bool read_only, bool quota) const; + virtual void deleteSnapshot (unsigned int num) const; + + virtual bool isSnapshotMounted (unsigned int num) const; + virtual void mountSnapshot (unsigned int num) const; + virtual void umountSnapshot (unsigned int num) const; + + virtual bool isSnapshotReadOnly (unsigned int num) const; + + virtual bool checkSnapshot (unsigned int num) const; + + virtual void sync () const; + + private: + + string zfs_subvol_fs; + string zfs_snapshot_fs; + string zfs_zpool; + + ZfsMountType mount_type = SYMLINK; + }; +} + + +#endif diff --git a/snapper/ZfsUtils.cc b/snapper/ZfsUtils.cc new file mode 100644 index 000000000..cdc2da979 --- /dev/null +++ b/snapper/ZfsUtils.cc @@ -0,0 +1,163 @@ +/* + * Copyright (c) [2011-2013] Novell, Inc. + * + * 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 "config.h" + +#include +#include + +#include "snapper/Log.h" +#include "snapper/ZfsUtils.h" +#include "snapper/SystemCmd.h" +#include "snapper/Snapper.h" +#include "snapper/AppUtil.h" + + +namespace snapper +{ + namespace ZfsUtils + { + + + void + create_volume(const string& mountpoint, const string& volume) + { + + SystemCmd cmd(ZFSBIN " create -o mountpoint=" + + quote(mountpoint) + " " + + quote(volume)); + + if (cmd.retcode() != 0) + SN_THROW(ZfsCallException("Making ZFS subvol failed")); + } + + + void + create_snapshot(const string& fs, const string& name) + { + SystemCmd cmd(ZFSBIN " snapshot " + quote(fs + "@" + name)); + + if (cmd.retcode() != 0) + SN_THROW(ZfsCallException("Creating snapshot failed")); + } + + + void + delete_volume(const string& name) + { + SystemCmd cmd(ZFSBIN " destroy " + quote(name)); + + if (cmd.retcode() != 0) + SN_THROW(ZfsCallException("Deleting ZFS vol failed")); + } + + + uint_fast64_t + get_freeing(const string& pool) { + // TODO (for sync function) + } + + + bool + is_mounted(const string& name) + { + SystemCmd cmd(ZFSBIN " list -H -t filesystem -o mounted " + + name); + if (cmd.retcode() != 0) + { + SN_THROW(ZfsCallException("Error getting mount status of " + quote(name))); + } + + string output = cmd.stdout()[0]; + return output == "yes"; + } + + + string + get_mountpoint(const string& name) + { + SystemCmd cmd(ZFSBIN " list -H -t filesystem -o mountpoint " + + name); + if (cmd.retcode() != 0) + { + SN_THROW(ZfsCallException("Error getting mountpoint of " + quote(name))); + } + + return cmd.stdout()[0]; + } + + + bool + mount_vol(const string& name) + { + // returns true on success, false if already mounted, and + // throws exception for all other problems + SystemCmd cmd(ZFSBIN " mount " + quote(name)); + if (cmd.retcode() == 0) return true; + if (cmd.stderr().empty()) + SN_THROW(ZfsCallException("Error mounting " + quote(name))); + + + string str = cmd.stderr()[0]; + string check = "filesystem already mounted"; + if (str.size() >= check.size() && + str.compare(str.size() - check.size(), check.size(), check) == 0) + return true; + + + SN_THROW(ZfsCallException("Error mounting " + quote(name))); + return false; + } + + + SDir + follow_symlink(const string& base_path) + { + string bpath = base_path; + return SDir(realpath(bpath)); + } + + + SDir + follow_symlink(const SDir& dir, const string& name) + { + string bpath = dir.fullname() + "/" + name; + return SDir(realpath(bpath)); + } + + + string + poolname(const string& name) + { + string::size_type pos = name.find_first_of('/'); + string root; + if (pos == string::npos) + root = name; + root = string(name, 0, pos); + pos = root.find_first_of('@'); + if (pos == string::npos) + return root; + return string(root, 0, pos); + } + + } +} diff --git a/snapper/ZfsUtils.h b/snapper/ZfsUtils.h new file mode 100644 index 000000000..faae1e6aa --- /dev/null +++ b/snapper/ZfsUtils.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) [2011-2013] Novell, Inc. + * + * 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. + */ + +#ifndef ZFSUTILS_H +#define ZFSUTILS_H + +#include +#include "snapper/Exception.h" +#include "snapper/FileUtils.h" + +namespace snapper +{ + using std::string; + + + namespace ZfsUtils + { + + struct ZfsCallException : public Exception + { + + explicit + ZfsCallException (const string& msg) : Exception (msg) { } + }; + + + void create_volume (const string& mountpoint, const string& volume); + void create_snapshot (const string& fs, const string& name); + void delete_volume (const string& name); + + uint_fast64_t get_freeing (const string& pool); + + + bool is_mounted (const string& name); + bool mount_vol (const string& name); + + SDir follow_symlink (const string& base_path); + SDir follow_symlink (const SDir& dir, const string& name); + + string poolname (const string& name); + } +} + +#endif /* ZFSUTILS_H */ + From 7483e8db8fc2e1c916b5921e68bc39f42d0be531 Mon Sep 17 00:00:00 2001 From: Daniel Sullivan Date: Mon, 2 Oct 2017 02:08:00 -0500 Subject: [PATCH 2/2] Update Zfs.cc --- snapper/Zfs.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/snapper/Zfs.cc b/snapper/Zfs.cc index f843d42c2..988f898dc 100755 --- a/snapper/Zfs.cc +++ b/snapper/Zfs.cc @@ -21,6 +21,8 @@ /* ZFS Snapper 2017 Daniel Sullivan (mumblepins@gmail.com) * + * See issue https://github.com/openSUSE/snapper/issues/145 + * * TODO: * - Deal with ACLs * - implement bind and legacy mounts *