#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
+#include <mntent.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <asm/types.h>
+ #include <boost/algorithm/string.hpp>
#include "snapper/Log.h"
#include "snapper/Filesystem.h"
#include "snapper/SnapperTmpl.h"
#include "snapper/SystemCmd.h"
#include "snapper/SnapperDefines.h"
+ #include "snapper/Regex.h"
+ #include "config.h"
+#define BTRFS_IOCTL_MAGIC 0x94
+#define BTRFS_PATH_NAME_MAX 4087
+#define BTRFS_SUBVOL_NAME_MAX 4039
+#define BTRFS_SUBVOL_RDONLY (1ULL << 1)
+
+#define BTRFS_IOC_SUBVOL_CREATE _IOW(BTRFS_IOCTL_MAGIC, 14, struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, struct btrfs_ioctl_vol_args_v2)
+
+struct btrfs_ioctl_vol_args
+{
+ __s64 fd;
+ char name[BTRFS_PATH_NAME_MAX + 1];
+};
+
+struct btrfs_ioctl_vol_args_v2
+{
+ __s64 fd;
+ __u64 transid;
+ __u64 flags;
+ __u64 unused[4];
+ char name[BTRFS_SUBVOL_NAME_MAX + 1];
+};
+
+
namespace snapper
{
}
+ SDir
+ Filesystem::openSubvolumeDir() const
+ {
+ SDir subvolume_dir(subvolume);
+
+ return subvolume_dir;
+ }
+
+
+ SDir
+ Filesystem::openInfoDir(unsigned int num) const
+ {
+ SDir infos_dir = openInfosDir();
+ SDir info_dir(infos_dir, decString(num));
+
+ return info_dir;
+ }
+
+
+ #ifdef ENABLE_BTRFS
+ Filesystem*
+ Btrfs::create(const string& fstype, const string& subvolume)
+ {
+ if (fstype == "btrfs")
+ return new Btrfs(subvolume);
+
+ return NULL;
+ }
+
+
Btrfs::Btrfs(const string& subvolume)
: Filesystem(subvolume)
{
bool
Btrfs::checkSnapshot(unsigned int num) const
{
- return checkDir(snapshotDir(num));
+ try
+ {
+ SDir info_dir = openInfoDir(num);
+
+ struct stat stat;
+ int r = info_dir.stat("snapshot", &stat, AT_SYMLINK_NOFOLLOW);
+ // check st_ino == 256 is copied from btrfsprogs
+ return r == 0 && stat.st_ino == 256 && S_ISDIR(stat.st_mode);
+ }
+ catch (const IOErrorException& e)
+ {
+ return false;
+ }
+ }
+
+
+ bool
+ Btrfs::create_subvolume(int fddst, const string& name) const
+ {
+ struct btrfs_ioctl_vol_args args;
+ memset(&args, 0, sizeof(args));
+
+ strncpy(args.name, name.c_str(), BTRFS_PATH_NAME_MAX);
+
+ return ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE, &args) == 0;
+ }
+
+
+ bool
+ Btrfs::create_snapshot(int fd, int fddst, const string& name) const
+ {
+ struct btrfs_ioctl_vol_args_v2 args;
+ memset(&args, 0, sizeof(args));
+
+ args.fd = fd;
+ args.flags |= BTRFS_SUBVOL_RDONLY;
+ strncpy(args.name, name.c_str(), BTRFS_SUBVOL_NAME_MAX);
+
+ return ioctl(fddst, BTRFS_IOC_SNAP_CREATE_V2, &args) == 0;
+ }
+
+
+ bool
+ Btrfs::delete_subvolume(int fd, const string& name) const
+ {
+ struct btrfs_ioctl_vol_args args;
+ memset(&args, 0, sizeof(args));
+
+ strncpy(args.name, name.c_str(), BTRFS_PATH_NAME_MAX);
+
+ return ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args) == 0;
}
+ // ENABLE_BTRFS
+ #endif
+
++
+ #ifdef ENABLE_EXT4
+ Filesystem*
+ Ext4::create(const string& fstype, const string& subvolume)
+ {
+ if (fstype == "ext4")
+ return new Ext4(subvolume);
+
+ return NULL;
+ }
Ext4::Ext4(const string& subvolume)
{
return checkNormalFile(snapshotFile(num));
}
+ // ENABLE_EXT4
+ #endif
+
++
+ #ifdef ENABLE_LVM
+ Filesystem*
+ Lvm::create(const string& fstype, const string& subvolume)
+ {
+ if (fstype == "lvm")
+ return new Lvm(subvolume, "auto");
+
+ Regex rx("^lvm\\(([_a-z0-9]+)\\)$");
+ if (rx.match(fstype))
+ return new Lvm(subvolume, rx.cap(1));
+
+ return NULL;
+ }
+
+
+ Lvm::Lvm(const string& subvolume, const string& mount_type)
+ : Filesystem(subvolume), mount_type(mount_type)
+ {
+ if (access(LVCREATE, X_OK) != 0)
+ {
+ throw ProgramNotInstalledException(LVCREATE " not installed");
+ }
+
+ bool found = false;
+ MtabData mtab_data;
+
+ if (!getMtabData(subvolume, found, mtab_data))
+ throw InvalidConfigException();
+
+ if (!found)
+ {
+ y2err("filesystem not mounted");
+ throw InvalidConfigException();
+ }
+
+ if (!detectLvmNames(mtab_data))
+ throw InvalidConfigException();
+
+ mount_options = mtab_data.options;
+ mount_options.erase(remove(mount_options.begin(), mount_options.end(), "rw"),
+ mount_options.end());
+ mount_options.push_back("noatime");
+ if (mount_type == "xfs")
+ mount_options.push_back("nouuid");
+ }
+
+
+ void
+ Lvm::createConfig() const
+ {
+ int r1 = mkdir((subvolume + "/.snapshots").c_str(), 0700);
+ if (r1 != 0 && errno != EEXIST)
+ {
+ y2err("mkdir failed errno:" << errno << " (" << strerror(errno) << ")");
+ throw CreateConfigFailedException("mkdir failed");
+ }
+ }
+
+
+ void
+ Lvm::deleteConfig() const
+ {
+ int r1 = rmdir((subvolume + "/.snapshots").c_str());
+ if (r1 != 0)
+ {
+ y2err("rmdir failed errno:" << errno << " (" << strerror(errno) << ")");
+ throw DeleteConfigFailedException("rmdir failed");
+ }
+ }
+
+
+ string
+ Lvm::infosDir() const
+ {
+ return (subvolume == "/" ? "" : subvolume) + "/.snapshots";
+ }
+
+
+ string
+ Lvm::snapshotDir(unsigned int num) const
+ {
+ return (subvolume == "/" ? "" : subvolume) + "/.snapshots/" + decString(num) +
+ "/snapshot";
+ }
+
+
++ SDir
++ Lvm::openInfosDir() const
++ {
++ SDir subvolume_dir = openSubvolumeDir();
++ SDir infos_dir(subvolume_dir, ".snapshots");
++
++ struct stat stat;
++ if (infos_dir.stat(".", &stat, AT_SYMLINK_NOFOLLOW) != 0)
++ {
++ throw IOErrorException();
++ }
++
++ if (stat.st_uid != 0 || stat.st_gid != 0)
++ {
++ y2err("owner of .snapshots wrong");
++ throw IOErrorException();
++ }
++
++ return infos_dir;
++ }
++
++
++ SDir
++ Lvm::openSnapshotDir(unsigned int num) const
++ {
++ SDir info_dir = openInfoDir(num);
++ SDir snapshot_dir(info_dir, "snapshot");
++
++ return snapshot_dir;
++ }
++
++
+ string
+ Lvm::snapshotLvName(unsigned int num) const
+ {
+ return lv_name + "-snapshot" + decString(num);
+ }
+
+
+ void
+ Lvm::createSnapshot(unsigned int num) const
+ {
+ sync(); // TODO looks like a bug that this is needed (with ext4)
+
+ SystemCmd cmd(LVCREATE " --snapshot --name " + quote(snapshotLvName(num)) + " " +
+ quote(vg_name + "/" + lv_name));
+ if (cmd.retcode() != 0)
+ throw CreateSnapshotFailedException();
+
+ int r1 = mkdir(snapshotDir(num).c_str(), 0700);
+ if (r1 != 0 && errno != EEXIST)
+ {
+ y2err("mkdir failed errno:" << errno << " (" << strerror(errno) << ")");
+ throw CreateSnapshotFailedException();
+ }
+ }
+
+
+ void
+ Lvm::deleteSnapshot(unsigned int num) const
+ {
+ SystemCmd cmd(LVREMOVE " --force " + quote(vg_name + "/" + snapshotLvName(num)));
+ if (cmd.retcode() != 0)
+ throw DeleteSnapshotFailedException();
+
+ rmdir(snapshotDir(num).c_str());
+ }
+
+
+ bool
+ Lvm::isSnapshotMounted(unsigned int num) const
+ {
+ bool mounted = false;
+ MtabData mtab_data;
+
+ if (!getMtabData(snapshotDir(num), mounted, mtab_data))
+ throw IsSnapshotMountedFailedException();
+
+ return mounted;
+ }
+
+
+ void
+ Lvm::mountSnapshot(unsigned int num) const
+ {
+ if (isSnapshotMounted(num))
+ return;
+
+ if (!mount(getDevice(num), snapshotDir(num), mount_type, mount_options))
+ throw MountSnapshotFailedException();
+ }
+
+
+ void
+ Lvm::umountSnapshot(unsigned int num) const
+ {
+ if (!isSnapshotMounted(num))
+ return;
+
+ if (!umount(snapshotDir(num)))
+ throw UmountSnapshotFailedException();
+ }
+
+
+ bool
+ Lvm::checkSnapshot(unsigned int num) const
+ {
+ return checkAnything(getDevice(num));
+ }
+
+
+ bool
+ Lvm::detectLvmNames(const MtabData& mtab_data)
+ {
+ Regex rx("^/dev/mapper/(.+[^-])-([^-].+)$");
+ if (rx.match(mtab_data.device))
+ {
+ vg_name = boost::replace_all_copy(rx.cap(1), "--", "-");
+ lv_name = boost::replace_all_copy(rx.cap(2), "--", "-");
+ return true;
+ }
+
+ y2err("could not detect lvm names from '" << mtab_data.device << "'");
+ return false;
+ }
+
+
+ string
+ Lvm::getDevice(unsigned int num) const
+ {
+ return "/dev/mapper/" + boost::replace_all_copy(vg_name, "-", "--") + "-" +
+ boost::replace_all_copy(snapshotLvName(num), "-", "--");
+ }
+ // ENABLE_LVM
+ #endif
}
#include <string>
+ #include <vector>
+#include "snapper/FileUtils.h"
+ #include "config.h"
+
namespace snapper
{
using std::string;
};
+
+ #ifdef ENABLE_BTRFS
class Btrfs : public Filesystem
{
public:
virtual bool checkSnapshot(unsigned int num) const;
+ private:
+
+ bool create_subvolume(int fddst, const string& name) const;
+ bool create_snapshot(int fd, int fddst, const string& name) const;
+ bool delete_subvolume(int fd, const string& name) const;
+
};
+ #endif
+
+ #ifdef ENABLE_EXT4
class Ext4 : public Filesystem
{
public:
virtual bool checkSnapshot(unsigned int num) const;
+ private:
+
+ vector<string> mount_options;
+
};
+ #endif
+
++
+ #ifdef ENABLE_LVM
+ class Lvm : public Filesystem
+ {
+ public:
+
+ static Filesystem* create(const string& fstype, const string& subvolume);
+
+ Lvm(const string& subvolume, const string& mount_type);
+
+ virtual string fstype() const { return "lvm(" + mount_type + ")"; }
+
+ virtual void createConfig() const;
+ virtual void deleteConfig() const;
+
+ virtual string infosDir() const;
+ virtual string snapshotDir(unsigned int num) const;
+ virtual string snapshotLvName(unsigned int num) const;
+
++ virtual SDir openInfosDir() const;
++ virtual SDir openSnapshotDir(unsigned int num) const;
++
+ virtual void createSnapshot(unsigned int num) 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 checkSnapshot(unsigned int num) const;
+
+ private:
+
+ const string mount_type;
+
+ bool detectLvmNames(const MtabData& mtab_data);
+
+ string getDevice(unsigned int num) const;
+
+ string vg_name;
+ string lv_name;
+
+ vector<string> mount_options;
+
+ };
+ #endif
}