public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH 0/5] ZFS support for single file restore
@ 2021-06-16 10:55 Stefan Reiter
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup-restore-image 1/5] debian: update control for bullseye Stefan Reiter
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Stefan Reiter @ 2021-06-16 10:55 UTC (permalink / raw)
  To: pbs-devel

This series adds support for single file restore from ZFS zpools. It uses the
standard ZFS utils, built manually from the already included ZFS subvolume in
proxmox-backup-restore-image. This is required, since the default zfsutils-linux
package is linked against libudev, which, in absence of systemd/udev in the VM,
causes all sorts of weird behaviour.

Should support all kinds of ZFS configurations, tested with striped, striped
mirror, and RAIDZ-1. I'll further continue testing, additionally with special
devices and such (it *should* work just fine, but you never know what funny
errors ZFS comes up with). Appreciate any help in that regard of course ;)


proxmox-backup-restore-image: Stefan Reiter (2):
  debian: update control for bullseye
  build custom ZFS tools without udev requirement

 debian/control                                |  2 +-
 src/Makefile                                  | 21 +++++++-
 src/build_initramfs.sh                        | 19 ++++++-
 src/init-shim-rs/src/main.rs                  | 10 ++++
 .../0001-remove-reference-to-libudev.patch    | 52 +++++++++++++++++++
 5 files changed, 101 insertions(+), 3 deletions(-)
 create mode 100644 src/patches/zfs/0001-remove-reference-to-libudev.patch

proxmox-backup: Stefan Reiter (3):
  file-restore: increase RAM for ZFS and disable ARC
  file-restore/disk: support ZFS pools
  file-restore/disk: support ZFS subvols with mountpoint=legacy

 src/bin/proxmox_file_restore/qemu_helper.rs |  15 +-
 src/bin/proxmox_restore_daemon/disk.rs      | 196 +++++++++++++++++++-
 2 files changed, 196 insertions(+), 15 deletions(-)

-- 
2.30.2




^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pbs-devel] [PATCH proxmox-backup-restore-image 1/5] debian: update control for bullseye
  2021-06-16 10:55 [pbs-devel] [PATCH 0/5] ZFS support for single file restore Stefan Reiter
@ 2021-06-16 10:55 ` Stefan Reiter
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup-restore-image 2/5] build custom ZFS tools without udev requirement Stefan Reiter
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Reiter @ 2021-06-16 10:55 UTC (permalink / raw)
  To: pbs-devel

python 2 is not available anymore

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
---
 debian/control | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/debian/control b/debian/control
index b87c903..55781a4 100644
--- a/debian/control
+++ b/debian/control
@@ -19,7 +19,7 @@ Build-Depends: apt-rdepends,
                libtool,
                lintian,
                perl-modules,
-               python-minimal,
+               python3-minimal,
                sed,
                sphinx-common,
                tar,
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pbs-devel] [PATCH proxmox-backup-restore-image 2/5] build custom ZFS tools without udev requirement
  2021-06-16 10:55 [pbs-devel] [PATCH 0/5] ZFS support for single file restore Stefan Reiter
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup-restore-image 1/5] debian: update control for bullseye Stefan Reiter
@ 2021-06-16 10:55 ` Stefan Reiter
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 3/5] file-restore: increase RAM for ZFS and disable ARC Stefan Reiter
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Reiter @ 2021-06-16 10:55 UTC (permalink / raw)
  To: pbs-devel

We already include the required sources with the zfsonlinux submodule,
so apply a patch to disable linking against libudev (as I couldn't find
a working configure flag for it?) and build the user space part as well.

Includes dependencies as well as 'strace' for the debug initramfs, which
proved quite useful for debugging.

The init-shim automatically creates the necessary /dev/zfs device node,
and additionally /dev/null to make rust's std::process::Command happy.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
---
 src/Makefile                                  | 21 +++++++-
 src/build_initramfs.sh                        | 19 ++++++-
 src/init-shim-rs/src/main.rs                  | 10 ++++
 .../0001-remove-reference-to-libudev.patch    | 52 +++++++++++++++++++
 4 files changed, 100 insertions(+), 2 deletions(-)
 create mode 100644 src/patches/zfs/0001-remove-reference-to-libudev.patch

diff --git a/src/Makefile b/src/Makefile
index a398ea1..053be5f 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -11,6 +11,8 @@ KERNEL_IMG=${BUILDDIR}/bzImage
 INITRAMFS_IMG=${INITRAMFS_BUILDDIR}/initramfs.img
 INITRAMFS_IMG_DBG=${INITRAMFS_BUILDDIR}/initramfs-debug.img
 
+ZFS_TOOLS=${BUILDDIR}/zfstools
+
 CONFIG=config-base
 
 RUST_SRC=$(wildcard ${SHIM_DIR}/**/*.rs) ${SHIM_DIR}/Cargo.toml
@@ -35,6 +37,10 @@ kernel.prepared: ${BUILDDIR}.prepared
 	touch $@
 
 zfs.prepared: kernel.prepared
+	cd ${BUILDDIR}/${ZFSONLINUX_SUBMODULE}; \
+	    for p in ../../patches/zfs/*.patch; do \
+	        patch -Np1 < $$p; \
+	    done
 	cd ${BUILDDIR}/${ZFSONLINUX_SUBMODULE}; \
 	    sh autogen.sh && \
 	    ./configure \
@@ -51,7 +57,20 @@ ${KERNEL_IMG}: zfs.prepared
 	cd ${BUILDDIR}/${KERNEL_SUBMODULE}; $(MAKE)
 	mv ${BUILDDIR}/${KERNEL_SUBMODULE}/arch/x86/boot/bzImage ${KERNEL_IMG}
 
-${INITRAMFS_IMG}: ${BUILDDIR}.prepared ${RUST_SRC} build_initramfs.sh
+${ZFS_TOOLS}: zfs.prepared
+	cd ${BUILDDIR}/${ZFSONLINUX_SUBMODULE}; \
+	    ./configure \
+	        --bindir=/usr/bin \
+	        --sbindir=/sbin \
+	        --libdir=/lib/"$(DEB_HOST_MULTIARCH)" \
+	        --with-zfsexecdir=/usr/lib/zfs-linux \
+	        --disable-systemd \
+	        --disable-pyzfs \
+	        --with-config=user
+	# absolute path required for 'make install'
+	$(MAKE) -C ${BUILDDIR}/${ZFSONLINUX_SUBMODULE} install DESTDIR=${PWD}/${ZFS_TOOLS}
+
+${INITRAMFS_IMG}: ${BUILDDIR}.prepared ${RUST_SRC} build_initramfs.sh ${ZFS_TOOLS}
 	cd ${SHIM_DIR}; cargo build --release
 	sh build_initramfs.sh
 
diff --git a/src/build_initramfs.sh b/src/build_initramfs.sh
index c4ee95c..29eeedb 100755
--- a/src/build_initramfs.sh
+++ b/src/build_initramfs.sh
@@ -19,9 +19,10 @@ add_pkgs() {
         LOCAL_DEPS=$(apt-rdepends -f Depends -s Depends "$pkg" | grep -v '^ ')
         DEPS="$DEPS $LOCAL_DEPS"
     done
-    # debconf and gcc are unnecessary
+    # debconf and gcc are unnecessary, libboost-regex doesn't install on bullseye
     DEPS=$(echo "$DEPS" |\
         sed -E 's/debconf(-2\.0)?//' |\
+        sed -E 's/libboost-regex//' |\
         sed -E 's/gcc-.{1,2}-base//')
     apt-get download $DEPS
     for deb in ./*.deb; do
@@ -47,8 +48,23 @@ add_pkgs "
     libstdc++6:amd64 \
     libssl1.1:amd64 \
     libacl1:amd64 \
+    libblkid1:amd64 \
+    libuuid1:amd64 \
+    zlib1g:amd64 \
 "
+
+# install custom ZFS tools (built without libudev)
+mkdir -p "$ROOT/sbin"
+cp -a ../zfstools/sbin/* "$ROOT/sbin/"
+cp -a ../zfstools/etc/* "$ROOT/etc/"
+cp -a ../zfstools/lib/* "$ROOT/lib/"
+cp -a ../zfstools/usr/* "$ROOT/usr/"
+
 rm -rf ${ROOT:?}/usr/share # contains only docs and debian stuff
+rm -rf ${ROOT:?}/usr/local/include # header files
+rm -rf ${ROOT:?}/usr/local/share # mostly ZFS tests
+rm -f ${ROOT:?}/lib/x86_64-linux-gnu/*.a # static libraries
+
 make_cpio "initramfs.img"
 
 # add debug helpers for debug initramfs, packages from above are included too
@@ -56,6 +72,7 @@ add_pkgs "
     util-linux:amd64 \
     busybox-static:amd64 \
     gdb:amd64 \
+    strace:amd64 \
 "
 # leave /usr/share here, it contains necessary stuff for gdb
 make_cpio "initramfs-debug.img"
diff --git a/src/init-shim-rs/src/main.rs b/src/init-shim-rs/src/main.rs
index 641218f..62d7e99 100644
--- a/src/init-shim-rs/src/main.rs
+++ b/src/init-shim-rs/src/main.rs
@@ -6,6 +6,10 @@ use std::process::Command;
 
 const URANDOM_MAJ: u64 = 1;
 const URANDOM_MIN: u64 = 9;
+const ZFS_MAJ: u64 = 10;
+const ZFS_MIN: u64 = 249;
+const NULL_MAJ: u64 = 1;
+const NULL_MIN: u64 = 3;
 
 /// Set up a somewhat normal linux userspace environment before starting the restore daemon, and
 /// provide error messages to the user if doing so fails.
@@ -22,6 +26,12 @@ fn main() {
     wrap_err("mknod /dev/urandom", || {
         do_mknod("/dev/urandom", URANDOM_MAJ, URANDOM_MIN)
     });
+    wrap_err("mknod /dev/zfs", || {
+        do_mknod("/dev/zfs", ZFS_MAJ, ZFS_MIN)
+    });
+    wrap_err("mknod /dev/null", || {
+        do_mknod("/dev/null", NULL_MAJ, NULL_MIN)
+    });
 
     if let Err(err) = run_agetty() {
         // not fatal
diff --git a/src/patches/zfs/0001-remove-reference-to-libudev.patch b/src/patches/zfs/0001-remove-reference-to-libudev.patch
new file mode 100644
index 0000000..467d9b5
--- /dev/null
+++ b/src/patches/zfs/0001-remove-reference-to-libudev.patch
@@ -0,0 +1,52 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Thu, 10 Jun 2021 10:50:22 +0200
+Subject: [PATCH] remove reference to libudev
+
+since there's no command line flag I can see...
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+---
+ config/user-libudev.m4 | 17 -----------------
+ config/user.m4         |  1 -
+ 2 files changed, 18 deletions(-)
+ delete mode 100644 config/user-libudev.m4
+
+diff --git a/config/user-libudev.m4 b/config/user-libudev.m4
+deleted file mode 100644
+index 8c3c1d7e0..000000000
+--- a/config/user-libudev.m4
++++ /dev/null
+@@ -1,17 +0,0 @@
+-dnl #
+-dnl # Check for libudev - needed for vdev auto-online and auto-replace
+-dnl #
+-AC_DEFUN([ZFS_AC_CONFIG_USER_LIBUDEV], [
+-	ZFS_AC_FIND_SYSTEM_LIBRARY(LIBUDEV, [libudev], [libudev.h], [], [udev], [], [user_libudev=yes], [user_libudev=no])
+-
+-	AS_IF([test "x$user_libudev" = xyes], [
+-	    AX_SAVE_FLAGS
+-
+-	    CFLAGS="$CFLAGS $LIBUDEV_CFLAGS"
+-	    LIBS="$LIBUDEV_LIBS $LIBS"
+-
+-	    AC_CHECK_FUNCS([udev_device_get_is_initialized])
+-
+-	    AX_RESTORE_FLAGS
+-	])
+-])
+diff --git a/config/user.m4 b/config/user.m4
+index c22067551..1b6d3a24e 100644
+--- a/config/user.m4
++++ b/config/user.m4
+@@ -18,7 +18,6 @@ AC_DEFUN([ZFS_AC_CONFIG_USER], [
+ 		ZFS_AC_CONFIG_USER_LIBBLKID
+ 	])
+ 	ZFS_AC_CONFIG_USER_LIBTIRPC
+-	ZFS_AC_CONFIG_USER_LIBUDEV
+ 	ZFS_AC_CONFIG_USER_LIBCRYPTO
+ 	ZFS_AC_CONFIG_USER_LIBAIO
+ 	ZFS_AC_CONFIG_USER_CLOCK_GETTIME
+-- 
+2.30.2
+
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 3/5] file-restore: increase RAM for ZFS and disable ARC
  2021-06-16 10:55 [pbs-devel] [PATCH 0/5] ZFS support for single file restore Stefan Reiter
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup-restore-image 1/5] debian: update control for bullseye Stefan Reiter
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup-restore-image 2/5] build custom ZFS tools without udev requirement Stefan Reiter
@ 2021-06-16 10:55 ` Stefan Reiter
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 4/5] file-restore/disk: support ZFS pools Stefan Reiter
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Reiter @ 2021-06-16 10:55 UTC (permalink / raw)
  To: pbs-devel

Even through best efforts at keeping it small, including the ZFS tools
in the initramfs seems to have exhausted the small overhead we had left
- give it a bit more RAM to compensate.

Also disable the ZFS ARC, as it's no use in such a memory constrained
environment, and we cache on the QEMU/rust layer anyway.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
---
 src/bin/proxmox_file_restore/qemu_helper.rs | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/src/bin/proxmox_file_restore/qemu_helper.rs b/src/bin/proxmox_file_restore/qemu_helper.rs
index c9d816ca..64d8e909 100644
--- a/src/bin/proxmox_file_restore/qemu_helper.rs
+++ b/src/bin/proxmox_file_restore/qemu_helper.rs
@@ -188,11 +188,10 @@ pub async fn start_vm(
         "-initrd",
         &ramfs_path,
         "-append",
-        if debug {
-            "debug panic=1"
-        } else {
-            "quiet panic=1"
-        },
+        &format!(
+            "{} panic=1 zfs_arc_min=0 zfs_arc_max=0",
+            if debug { "debug" } else { "quiet" }
+        ),
         "-daemonize",
         "-pidfile",
         &format!("/dev/fd/{}", pid_fd.as_raw_fd()),
@@ -240,9 +239,9 @@ pub async fn start_vm(
     } else {
         // add more RAM if many drives are given
         match id {
-            f if f < 10 => 128,
-            f if f < 20 => 192,
-            _ => 256,
+            f if f < 10 => 192,
+            f if f < 20 => 256,
+            _ => 384,
         }
     };
 
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 4/5] file-restore/disk: support ZFS pools
  2021-06-16 10:55 [pbs-devel] [PATCH 0/5] ZFS support for single file restore Stefan Reiter
                   ` (2 preceding siblings ...)
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 3/5] file-restore: increase RAM for ZFS and disable ARC Stefan Reiter
@ 2021-06-16 10:55 ` Stefan Reiter
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 5/5] file-restore/disk: support ZFS subvols with mountpoint=legacy Stefan Reiter
  2021-06-28 12:26 ` [pbs-devel] applied-series: [PATCH 0/5] ZFS support for single file restore Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Reiter @ 2021-06-16 10:55 UTC (permalink / raw)
  To: pbs-devel

Uses the ZFS utils to detect, import and mount zpools. These are
available as a new Bucket type 'zpool'.

Requires some minor changes to the existing disk and partiton detection
code, so the ZFS-specific part can use the information gathered in the
previous pass to associate drive names with their 'drive-xxxN.img.fidx'
node.

For detecting size, the zpool has to be imported. This is only done with
pools containing 5 or less disks, as anything else might take too long
(and should be seldomly found within VMs).

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
---

@Thomas: I changed my mind about the "guess the size from the disk sizes"
approach for a couple of reasons:
* it didn't seem terribly accurate, comparing the values with what ZFS reports
  even for simple striped setups
* it would require implementing the calculation for all RAID-Z modes and such,
  including handling of differently sized disks - possible, but probably not
  worth it, considering:
* the actual import/mount if done *right away* is actually surprisingly fast, I
  would assume since the initial scan on import already loads all required
  metadata into disk/QEMU caches

 src/bin/proxmox_restore_daemon/disk.rs | 153 ++++++++++++++++++++++++-
 1 file changed, 152 insertions(+), 1 deletion(-)

diff --git a/src/bin/proxmox_restore_daemon/disk.rs b/src/bin/proxmox_restore_daemon/disk.rs
index 1ff5468f..5b66dd2f 100644
--- a/src/bin/proxmox_restore_daemon/disk.rs
+++ b/src/bin/proxmox_restore_daemon/disk.rs
@@ -7,13 +7,17 @@ use std::collections::HashMap;
 use std::fs::{create_dir_all, File};
 use std::io::{BufRead, BufReader};
 use std::path::{Component, Path, PathBuf};
+use std::process::Command;
 
 use proxmox::const_regex;
 use proxmox::tools::fs;
 use proxmox_backup::api2::types::BLOCKDEVICE_NAME_REGEX;
+use proxmox_backup::tools::run_command;
 
 const_regex! {
     VIRTIO_PART_REGEX = r"^vd[a-z]+(\d+)$";
+    ZPOOL_POOL_NAME_REGEX = r"^ {3}pool: (.*)$";
+    ZPOOL_IMPORT_DISK_REGEX = r"^\t {2,4}(vd[a-z]+(?:\d+)?)\s+ONLINE$";
 }
 
 lazy_static! {
@@ -43,6 +47,7 @@ pub enum ResolveResult {
     BucketComponents(Vec<(String, u64)>),
 }
 
+#[derive(Clone)]
 struct PartitionBucketData {
     dev_node: String,
     number: i32,
@@ -50,6 +55,13 @@ struct PartitionBucketData {
     size: u64,
 }
 
+#[derive(Clone)]
+struct ZFSBucketData {
+    name: String,
+    mountpoint: Option<PathBuf>,
+    size: u64,
+}
+
 /// A "Bucket" represents a mapping found on a disk, e.g. a partition, a zfs dataset or an LV. A
 /// uniquely identifying path to a file then consists of four components:
 /// "/disk/bucket/component/path"
@@ -60,9 +72,11 @@ struct PartitionBucketData {
 ///   path: relative path of the file on the filesystem indicated by the other parts, may contain
 ///         more subdirectories
 /// e.g.: "/drive-scsi0/part/0/etc/passwd"
+#[derive(Clone)]
 enum Bucket {
     Partition(PartitionBucketData),
     RawFs(PartitionBucketData),
+    ZPool(ZFSBucketData),
 }
 
 impl Bucket {
@@ -81,6 +95,13 @@ impl Bucket {
                 }
             }
             Bucket::RawFs(_) => ty == "raw",
+            Bucket::ZPool(data) => {
+                if let Some(ref comp) = comp.get(0) {
+                    ty == "zpool" && comp.as_ref() == &data.name
+                } else {
+                    false
+                }
+            }
         })
     }
 
@@ -88,6 +109,7 @@ impl Bucket {
         match self {
             Bucket::Partition(_) => "part",
             Bucket::RawFs(_) => "raw",
+            Bucket::ZPool(_) => "zpool",
         }
     }
 
@@ -104,6 +126,7 @@ impl Bucket {
         Ok(match self {
             Bucket::Partition(data) => data.number.to_string(),
             Bucket::RawFs(_) => "raw".to_owned(),
+            Bucket::ZPool(data) => data.name.clone(),
         })
     }
 
@@ -111,6 +134,7 @@ impl Bucket {
         Ok(match type_string {
             "part" => 1,
             "raw" => 0,
+            "zpool" => 1,
             _ => bail!("invalid bucket type for component depth: {}", type_string),
         })
     }
@@ -118,6 +142,7 @@ impl Bucket {
     fn size(&self) -> u64 {
         match self {
             Bucket::Partition(data) | Bucket::RawFs(data) => data.size,
+            Bucket::ZPool(data) => data.size,
         }
     }
 }
@@ -162,6 +187,59 @@ impl Filesystems {
                 data.mountpoint = Some(mp.clone());
                 Ok(mp)
             }
+            Bucket::ZPool(data) => {
+                if let Some(mp) = &data.mountpoint {
+                    return Ok(mp.clone());
+                }
+
+                let mntpath = format!("/mnt/{}", &data.name);
+                create_dir_all(&mntpath)?;
+
+                // call ZFS tools to import and mount the pool with the root mount at 'mntpath'
+                let mut cmd = Command::new("/sbin/zpool");
+                cmd.args(
+                    [
+                        "import",
+                        "-f",
+                        "-o",
+                        "readonly=on",
+                        "-d",
+                        "/dev",
+                        "-R",
+                        &mntpath,
+                        &data.name,
+                    ]
+                    .iter(),
+                );
+                if let Err(msg) = run_command(cmd, None) {
+                    // ignore double import, this may happen if a previous attempt failed further
+                    // down below - this way we can at least try again
+                    if !msg
+                        .to_string()
+                        .contains("a pool with that name already exists")
+                    {
+                        return Err(msg);
+                    }
+                }
+
+                // 'mount -a' simply mounts all datasets that haven't been automounted, which
+                // should only be ones that we've imported just now
+                let mut cmd = Command::new("/sbin/zfs");
+                cmd.args(["mount", "-a"].iter());
+                run_command(cmd, None)?;
+
+                // Now that we have imported the pool, we can also query the size
+                let mut cmd = Command::new("/sbin/zpool");
+                cmd.args(["list", "-o", "size", "-Hp", &data.name].iter());
+                let size = run_command(cmd, None)?;
+                if let Ok(size) = size.trim().parse::<u64>() {
+                    data.size = size;
+                }
+
+                let mp = PathBuf::from(mntpath);
+                data.mountpoint = Some(mp.clone());
+                Ok(mp)
+            }
         }
     }
 
@@ -204,9 +282,11 @@ impl DiskState {
     pub fn scan() -> Result<Self, Error> {
         let filesystems = Filesystems::scan()?;
 
+        let mut disk_map = HashMap::new();
+        let mut drive_info = HashMap::new();
+
         // create mapping for virtio drives and .fidx files (via serial description)
         // note: disks::DiskManager relies on udev, which we don't have
-        let mut disk_map = HashMap::new();
         for entry in proxmox_backup::tools::fs::scan_subdir(
             libc::AT_FDCWD,
             "/sys/block",
@@ -230,6 +310,8 @@ impl DiskState {
                 }
             };
 
+            drive_info.insert(name.to_owned(), fidx.clone());
+
             // attempt to mount device directly
             let dev_node = format!("/dev/{}", name);
             let size = Self::make_dev_node(&dev_node, &sys_path)?;
@@ -281,11 +363,55 @@ impl DiskState {
                 });
 
                 parts.push(bucket);
+
+                drive_info.insert(part_name.to_owned(), fidx.clone());
             }
 
             disk_map.insert(fidx, parts);
         }
 
+        // After the above, every valid disk should have a device node in /dev, so we can query all
+        // of them for zpools
+        let mut cmd = Command::new("/sbin/zpool");
+        cmd.args(["import", "-d", "/dev"].iter());
+        let result = run_command(cmd, None).unwrap();
+        for (pool, disks) in Self::parse_zpool_import(&result) {
+            let mut bucket = Bucket::ZPool(ZFSBucketData {
+                name: pool.clone(),
+                size: 0,
+                mountpoint: None,
+            });
+
+            // anything more than 5 disks we assume to take too long to mount, so we don't
+            // automatically - this means that no size can be reported
+            if disks.len() <= 5 {
+                let mp = filesystems.ensure_mounted(&mut bucket);
+                info!(
+                    "zpool '{}' (on: {:?}) auto-mounted at '{:?}' (size: {}B)",
+                    &pool,
+                    &disks,
+                    mp,
+                    bucket.size()
+                );
+            } else {
+                info!(
+                    "zpool '{}' (on: {:?}) auto-mount skipped, too many disks",
+                    &pool, &disks
+                );
+            }
+
+            for disk in disks {
+                if let Some(fidx) = drive_info.get(&disk) {
+                    match disk_map.get_mut(fidx) {
+                        Some(v) => v.push(bucket.clone()),
+                        None => {
+                            disk_map.insert(fidx.to_owned(), vec![bucket.clone()]);
+                        }
+                    }
+                }
+            }
+        }
+
         Ok(Self {
             filesystems,
             disk_map,
@@ -419,4 +545,29 @@ impl DiskState {
         stat::mknod(path, stat::SFlag::S_IFBLK, stat::Mode::S_IRWXU, dev)?;
         Ok(())
     }
+
+    fn parse_zpool_import(data: &str) -> Vec<(String, Vec<String>)> {
+        let mut ret = Vec::new();
+        let mut disks = Vec::new();
+        let mut cur = "".to_string();
+        for line in data.lines() {
+            if let Some(groups) = (ZPOOL_POOL_NAME_REGEX.regex_obj)().captures(line) {
+                if let Some(name) = groups.get(1) {
+                    if !disks.is_empty() {
+                        ret.push((cur, disks.clone()));
+                    }
+                    disks.clear();
+                    cur = name.as_str().to_owned();
+                }
+            } else if let Some(groups) = (ZPOOL_IMPORT_DISK_REGEX.regex_obj)().captures(line) {
+                if let Some(disk) = groups.get(1) {
+                    disks.push(disk.as_str().to_owned());
+                }
+            }
+        }
+        if !disks.is_empty() && !cur.is_empty() {
+            ret.push((cur, disks));
+        }
+        ret
+    }
 }
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 5/5] file-restore/disk: support ZFS subvols with mountpoint=legacy
  2021-06-16 10:55 [pbs-devel] [PATCH 0/5] ZFS support for single file restore Stefan Reiter
                   ` (3 preceding siblings ...)
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 4/5] file-restore/disk: support ZFS pools Stefan Reiter
@ 2021-06-16 10:55 ` Stefan Reiter
  2021-06-28 12:26 ` [pbs-devel] applied-series: [PATCH 0/5] ZFS support for single file restore Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Stefan Reiter @ 2021-06-16 10:55 UTC (permalink / raw)
  To: pbs-devel

These require mounting using the regular 'mount' syscall.
Auto-generates an appropriate mount path.

Note that subvols with mountpoint=none cannot be mounted this way, and
would require setting the mountpoint property, which is not possible as
the zpools have to be imported with readonly=on.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
---
 src/bin/proxmox_restore_daemon/disk.rs | 43 ++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 6 deletions(-)

diff --git a/src/bin/proxmox_restore_daemon/disk.rs b/src/bin/proxmox_restore_daemon/disk.rs
index 5b66dd2f..9d0cbe32 100644
--- a/src/bin/proxmox_restore_daemon/disk.rs
+++ b/src/bin/proxmox_restore_daemon/disk.rs
@@ -228,6 +228,34 @@ impl Filesystems {
                 cmd.args(["mount", "-a"].iter());
                 run_command(cmd, None)?;
 
+                // detect any datasets with 'legacy' mountpoints
+                let mut cmd = Command::new("/sbin/zfs");
+                cmd.args(["list", "-Hpro", "name,mountpoint", &data.name].iter());
+                let mps = run_command(cmd, None)?;
+                for subvol in mps.lines() {
+                    let subvol = subvol.splitn(2, '\t').collect::<Vec<&str>>();
+                    if subvol.len() != 2 {
+                        continue;
+                    }
+                    let name = subvol[0];
+                    let mp = subvol[1];
+
+                    if mp == "legacy" {
+                        let mut newmp = PathBuf::from(format!(
+                            "{}/legacy-{}",
+                            &mntpath,
+                            name.replace('/', "_")
+                        ));
+                        let mut i = 1;
+                        while newmp.exists() {
+                            newmp.set_extension(i.to_string());
+                            i += 1;
+                        }
+                        create_dir_all(&newmp)?;
+                        self.do_mount(Some(name), newmp.to_string_lossy().as_ref(), "zfs")?;
+                    }
+                }
+
                 // Now that we have imported the pool, we can also query the size
                 let mut cmd = Command::new("/sbin/zpool");
                 cmd.args(["list", "-o", "size", "-Hp", &data.name].iter());
@@ -244,19 +272,14 @@ impl Filesystems {
     }
 
     fn try_mount(&self, source: &str, target: &str) -> Result<(), Error> {
-        use nix::mount::*;
-
         create_dir_all(target)?;
 
         // try all supported fs until one works - this is the way Busybox's 'mount' does it too:
         // https://git.busybox.net/busybox/tree/util-linux/mount.c?id=808d93c0eca49e0b22056e23d965f0d967433fbb#n2152
         // note that ZFS is intentionally left out (see scan())
-        let flags =
-            MsFlags::MS_RDONLY | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV;
         for fs in &self.supported_fs {
             let fs: &str = fs.as_ref();
-            let opts = FS_OPT_MAP.get(fs).copied();
-            match mount(Some(source), target, Some(fs), flags, opts) {
+            match self.do_mount(Some(source), target, fs) {
                 Ok(()) => {
                     info!("mounting '{}' succeeded, fstype: '{}'", source, fs);
                     return Ok(());
@@ -270,6 +293,14 @@ impl Filesystems {
 
         bail!("all mounts failed or no supported file system")
     }
+
+    fn do_mount(&self, source: Option<&str>, target: &str, fs: &str) -> Result<(), nix::Error> {
+        use nix::mount::*;
+        let flags =
+            MsFlags::MS_RDONLY | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV;
+        let opts = FS_OPT_MAP.get(fs).copied();
+        mount(source, target, Some(fs), flags, opts)
+    }
 }
 
 pub struct DiskState {
-- 
2.30.2





^ permalink raw reply	[flat|nested] 7+ messages in thread

* [pbs-devel] applied-series: [PATCH 0/5] ZFS support for single file restore
  2021-06-16 10:55 [pbs-devel] [PATCH 0/5] ZFS support for single file restore Stefan Reiter
                   ` (4 preceding siblings ...)
  2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 5/5] file-restore/disk: support ZFS subvols with mountpoint=legacy Stefan Reiter
@ 2021-06-28 12:26 ` Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Thomas Lamprecht @ 2021-06-28 12:26 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Stefan Reiter

On 16.06.21 12:55, Stefan Reiter wrote:
> This series adds support for single file restore from ZFS zpools. It uses the
> standard ZFS utils, built manually from the already included ZFS subvolume in
> proxmox-backup-restore-image. This is required, since the default zfsutils-linux
> package is linked against libudev, which, in absence of systemd/udev in the VM,
> causes all sorts of weird behaviour.
> 
> Should support all kinds of ZFS configurations, tested with striped, striped
> mirror, and RAIDZ-1. I'll further continue testing, additionally with special
> devices and such (it *should* work just fine, but you never know what funny
> errors ZFS comes up with). Appreciate any help in that regard of course ;)
> 
> 
> proxmox-backup-restore-image: Stefan Reiter (2):
>   debian: update control for bullseye
>   build custom ZFS tools without udev requirement
> 
>  debian/control                                |  2 +-
>  src/Makefile                                  | 21 +++++++-
>  src/build_initramfs.sh                        | 19 ++++++-
>  src/init-shim-rs/src/main.rs                  | 10 ++++
>  .../0001-remove-reference-to-libudev.patch    | 52 +++++++++++++++++++
>  5 files changed, 101 insertions(+), 3 deletions(-)
>  create mode 100644 src/patches/zfs/0001-remove-reference-to-libudev.patch
> 
> proxmox-backup: Stefan Reiter (3):
>   file-restore: increase RAM for ZFS and disable ARC
>   file-restore/disk: support ZFS pools
>   file-restore/disk: support ZFS subvols with mountpoint=legacy
> 
>  src/bin/proxmox_file_restore/qemu_helper.rs |  15 +-
>  src/bin/proxmox_restore_daemon/disk.rs      | 196 +++++++++++++++++++-
>  2 files changed, 196 insertions(+), 15 deletions(-)
> 



applied series, thanks!




^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2021-06-28 12:26 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-16 10:55 [pbs-devel] [PATCH 0/5] ZFS support for single file restore Stefan Reiter
2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup-restore-image 1/5] debian: update control for bullseye Stefan Reiter
2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup-restore-image 2/5] build custom ZFS tools without udev requirement Stefan Reiter
2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 3/5] file-restore: increase RAM for ZFS and disable ARC Stefan Reiter
2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 4/5] file-restore/disk: support ZFS pools Stefan Reiter
2021-06-16 10:55 ` [pbs-devel] [PATCH proxmox-backup 5/5] file-restore/disk: support ZFS subvols with mountpoint=legacy Stefan Reiter
2021-06-28 12:26 ` [pbs-devel] applied-series: [PATCH 0/5] ZFS support for single file restore Thomas Lamprecht

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal