From: Waldemar Kozaczuk <jwkozac...@gmail.com>
Committer: Waldemar Kozaczuk <jwkozac...@gmail.com>
Branch: master

zfs: allow mounting and building on host

This requires OpenZFS installed on host (see
https://openzfs.github.io/openzfs-docs/Getting%20Started/Fedora/index.html
for Fedora and
https://openzfs.github.io/openzfs-docs/Getting%20Started/Ubuntu/index.html
for Ubuntu).

In essence this patch adds new script zfs-image-on-host.sh that allows
mounting and build ZFS images using OpenZFS without running OSv. It also
modifies the build script to support new option: '--use-openzfs' that
delegates to zfs-image-on-host.sh to build the ZFS image. Please see
examples above:

./scripts/build image=native-example fs=zfs -j$(nproc) --use-openzfs

./scripts/build image=native-example fs=rofs_with_zfs -j$(nproc) --use-openzfs

./scripts/build image=native-example fs=rofs -j$(nproc) --use-openzfs 
--create-zfs-disk

Fixes #1068

Signed-off-by: Waldemar Kozaczuk <jwkozac...@gmail.com>

---
diff --git a/modules/zfs-tools/usr.manifest b/modules/zfs-tools/usr.manifest
--- a/modules/zfs-tools/usr.manifest
+++ b/modules/zfs-tools/usr.manifest
@@ -1,4 +1,5 @@
 [manifest]
 /zpool.so: zpool.so
+/zfs.so: zfs.so
 /libzfs.so: libzfs.so
 /libuutil.so: libuutil.so
diff --git a/scripts/build b/scripts/build
--- a/scripts/build
+++ b/scripts/build
@@ -38,6 +38,7 @@ usage() {
          --append-manifest             Append build/<mode>/append.manifest to 
usr.manifest
          --create-disk                 Instead of usr.img create kernel-less 
disk.img
          --create-zfs-disk             Create extra empty disk with ZFS 
filesystem
+         --use-openzfs                 Build and manipulate ZFS images using 
on host OpenZFS tools
 
        Examples:
          ./scripts/build -j4 fs=rofs image=native-example   # Create image 
with native-example app
@@ -79,7 +80,7 @@ do
        case $i in
        --help|-h)
                usage ;;
-       
image=*|modules=*|fs=*|usrskel=*|check|--append-manifest|--create-disk|--create-zfs-disk)
 ;;
+       
image=*|modules=*|fs=*|usrskel=*|check|--append-manifest|--create-disk|--create-zfs-disk|--use-openzfs)
 ;;
        clean)
                stage1_args=clean ;;
        arch=*)
@@ -163,11 +164,13 @@ do
                vars[create_disk]="true";;
        --create-zfs-disk)
                vars[create_zfs_disk]="true";;
+       --use-openzfs)
+               vars[use_openzfs]="true";;
        esac
 done
 
 # fs_size_mb is in megabytes (1024*1024 bytes)
-fs_size_mb=${vars[fs_size_mb]-256}
+fs_size_mb=${vars[fs_size_mb]-512}
 # fs_size is in bytes
 fs_size=${vars[fs_size]-$(($fs_size_mb*1024*1024))}
 # size must be a multiple of 512. Round it down
@@ -316,10 +319,17 @@ fi
 create_zfs_disk() {
        cp $bare $raw_disk.raw
        "$SRC"/scripts/imgedit.py setpartition "-f raw ${raw_disk}.raw" 2 
$partition_offset $partition_size
-       qemu-img convert -f raw -O qcow2 $raw_disk.raw $qcow2_disk.img
-       qemu-img resize $qcow2_disk.img ${image_size}b >/dev/null 2>&1
-       "$SRC"/scripts/upload_manifest.py --arch=$arch -o $qcow2_disk.img -m 
usr.manifest -D libgcc_s_dir="$libgcc_s_dir"
-       #"$SRC"/scripts/zfs-image-on-host.sh build $qcow2_disk.img 
$partition_offset osv zfs
+       if [[ ${vars[use_openzfs]} == "true" ]]; then
+               #We use raw disk on purpose so that zfs-image-on-host.sh can 
use loop device which is faster to copy files to
+               qemu-img resize ${raw_disk}.raw ${image_size}b >/dev/null 2>&1
+               "$SRC"/scripts/zfs-image-on-host.sh build ${raw_disk}.raw 1 osv 
zfs true
+               qemu-img convert -f raw -O qcow2 $raw_disk.raw $qcow2_disk.img
+       else
+               qemu-img convert -f raw -O qcow2 $raw_disk.raw $qcow2_disk.img
+               qemu-img resize $qcow2_disk.img ${image_size}b >/dev/null 2>&1
+               "$SRC"/scripts/upload_manifest.py --arch=$arch -o 
$qcow2_disk.img -m usr.manifest -D libgcc_s_dir="$libgcc_s_dir"
+       fi
+       rm ${raw_disk}.raw
 }
 
 create_rofs_disk() {
@@ -332,18 +342,23 @@ create_rofs_disk() {
 
 create_zfs_filesystem() {
        local image_path=$1
-       local device_path=$2
-       local qemu_arch=$arch
-       if [[ "$qemu_arch" == 'aarch64' ]]; then
-               console=''
-               zfs_builder_name='zfs_builder.img'
+       if [[ ${vars[use_openzfs]} == "true" ]]; then
+               local partition=$3
+               "$SRC"/scripts/zfs-image-on-host.sh build $image_path 
$partition osv zfs false
        else
-               qemu_arch='x86_64'
-               console='--console=serial'
-               zfs_builder_name='zfs_builder-stripped.elf'
+               local device_path=$2
+               local qemu_arch=$arch
+               if [[ "$qemu_arch" == 'aarch64' ]]; then
+                       console=''
+                       zfs_builder_name='zfs_builder.img'
+               else
+                       qemu_arch='x86_64'
+                       console='--console=serial'
+                       zfs_builder_name='zfs_builder-stripped.elf'
+               fi
+               "$SRC"/scripts/run.py -k --kernel-path $zfs_builder_name 
--arch=$qemu_arch --vnc none -m 512 -c1 -i ${image_path} --block-device-cache 
unsafe \
+                       -s -e "${console} --norandom --nomount --noinit 
--preload-zfs-library /tools/mkfs.so ${device_path}; /zfs.so set 
compression=off osv"
        fi
-       "$SRC"/scripts/run.py -k --kernel-path $zfs_builder_name 
--arch=$qemu_arch --vnc none -m 512 -c1 -i ${image_path} \
-               --block-device-cache unsafe -s -e "${console} --norandom 
--nomount --noinit --preload-zfs-library /tools/mkfs.so ${device_path}; /zfs.so 
set compression=off osv"
 }
 
 if [[ "$arch" == 'aarch64' ]]; then
@@ -374,7 +389,7 @@ rofs_with_zfs)
        zfs_partition_offset=$((partition_offset + partition_size))
        zfs_partition_size=$((image_size-zfs_partition_offset))
        "$SRC"/scripts/imgedit.py setpartition "$qcow2_disk.img" 3 
$zfs_partition_offset $zfs_partition_size
-       create_zfs_filesystem $qcow2_disk.img "/dev/vblk0.2";;
+       create_zfs_filesystem $qcow2_disk.img "/dev/vblk0.2" 2;;
 ramfs|virtiofs)
        # No need to create extra fs like above: ramfs is already created (as 
the
        # bootfs) and virtio-fs is specified with virtiofsd at run time
@@ -398,7 +413,7 @@ if [[ ${vars[create_zfs_disk]} == "true" ]]; then
        "$SRC"/scripts/imgedit.py setpartition "-f raw ${raw_disk}.raw" 2 
$partition_offset $partition_size
        qemu-img convert -f raw -O qcow2 $raw_disk.raw $qcow2_disk.img
        qemu-img resize $qcow2_disk.img ${image_size}b >/dev/null 2>&1
-       create_zfs_filesystem $qcow2_disk.img "/dev/vblk0.1"
+       create_zfs_filesystem $qcow2_disk.img "/dev/vblk0.1" 1
 fi
 
 # Support "build check"
diff --git a/scripts/imgedit.py b/scripts/imgedit.py
--- a/scripts/imgedit.py
+++ b/scripts/imgedit.py
@@ -198,3 +198,11 @@ def size(self):
     f.seek(partition + 12)
     f.write(struct.pack('I', size // 512))
     f.close()
+elif cmd == 'getpartition_offset':
+    img = args[0]
+    partition = int(args[1])
+    partition = 0x1be + ((partition - 1) * 0x10)
+    with nbd_file(img) as f:
+        f.seek(partition + 8)
+        (offset,) = struct.unpack("I", f.read(4))
+        print(offset * 512)
diff --git a/scripts/zfs-image-on-host.sh b/scripts/zfs-image-on-host.sh
--- a/scripts/zfs-image-on-host.sh
+++ b/scripts/zfs-image-on-host.sh
@@ -0,0 +1,227 @@
+#!/bin/bash
+
+
+argv0=${0##*/}
+usage() {
+       cat <<-EOF
+       Manipulate ZFS images on host using OpenZFS - mount, unmount and build.
+
+       Usage: ${argv0} mount <image_path> <partition> <pool_name> <filesystem> 
|
+                                   build <image_path> <partition> <pool_name> 
<filesystem> <populate_image> |
+                                   unmount <pool_name>
+
+       Where:
+         image_path      path to a qcow2 or raw ZFS image; defaults to 
build/last/usr.img
+         partition       partition of disk above; defaults to 1
+         pool_name       name of ZFS pool; defaults to osv
+         filesystem      name of ZFS filesystem; defaults to zfs
+         populate_image  boolean value to indicate if the image should be 
populated with content
+                         from build/last/usr.manifest; defaults to true but 
only used with 'build' command
+
+       Examples:
+         ${argv0} mount                                     # Mount OSv image 
from build/last/usr.img under /zfs
+         ${argv0} mount build/last/zfs_disk.img 1           # Mount OSv image 
from build/last/zfs_disk.img 2nd partition under /zfs
+         ${argv0} unmount                                   # Unmount OSv 
image from /zfs
+       EOF
+       exit ${1:-0}
+}
+
+connect_image_to_nbd_device() {
+       # Check if we have something connected to nbd0
+       if [[ "" == $(lsblk | grep nbd0 | grep " 0B") ]]; then
+               echo "The device /dev/nbd0 is busy. Please disconnect it or run 
$0 unmount"
+               return -1
+       fi
+       local image_path="$1"
+       sudo qemu-nbd --connect /dev/nbd0 ${image_path} 1>/dev/null
+       echo "Connected device /dev/nbd0 to the image ${image_path}"
+
+        # Identify nbd device if it maps to a specific ZFS partition 
+       local partition=$2
+        local nbd_device=$(lsblk | grep part | grep -o "nbd0\S*" | head -n 
$partition | tail -1)
+       if [[ "" == "$nbd_device" ]]; then
+               nbd_device=nbd0
+               echo "Assuming /dev/nbd0 as a device to import from"
+       fi
+       device_path="/dev/$nbd_device"
+}
+
+connect_image_to_loop_device() {
+       local image_path="$1"
+       local partition=$(($2+1))
+       local partition_offset=$($OSV_ROOT/scripts/imgedit.py 
getpartition_offset "-f raw $image_path" $partition)
+       device_path=$(sudo losetup -o $partition_offset -f --show ${image_path})
+       echo "Connected device $device_path to the image ${image_path}"
+}
+
+connect_image() {
+       local image_path="$1"
+       local image_format=$(qemu-img info ${image_path} | grep "file format")
+
+       if [[ "$image_format" == *"qcow2"* ]]; then
+               connect_image_to_nbd_device $image_path $2
+       elif [[ "$image_format" == *"raw"* ]]; then
+               connect_image_to_loop_device $image_path $2
+       else
+               echo "The file $image_path does not seem to be a valid qcow2 
nor raw image!"
+               return -1
+       fi
+}
+
+mount_image() {
+       connect_image $1 $2
+
+       local pool_name="$3"
+       if [[ "" != $(zpool list -H ${pool_name} 2>/dev/null | grep 
${pool_name}) ]]; then
+               echo "There seems to be ${pool_name} already imported. Please 
export it first or run $0 unmount"
+               return -1
+       fi
+       sudo zpool import -d ${device_path} ${pool_name}
+       echo "Imported pool ${pool_name}"
+
+       local filesystem="$4"
+       local dataset="$pool_name/$filesystem"
+       if [[ "" != $(df | grep "$dataset") ]]; then
+               echo "There seems to be ${filesystem} already mounted. Please 
'zfs unmount ${dataset}' it first or run $0 unmount"
+               return -1
+       fi
+       sudo zfs mount ${dataset}
+       echo "Mounted ${dataset} at /${filesystem}"
+}
+
+unmount_image() {
+       local pool_name="$1"
+
+        local zfs_unmounted=false
+       if [[ "" != $(df | grep "$pool_name") ]]; then
+               sudo zfs umount ${pool_name}
+               zfs_unmounted=true
+               echo "Unmounted ${pool_name} from /${filesystem}"
+       fi
+
+       local zpool_exported=false
+       if [[ "" != $(zpool list -H ${pool_name} 2>/dev/null | grep 
${pool_name}) ]]; then
+               sudo zpool export ${pool_name}
+               zpool_exported=true
+               echo "Exported pool ${pool_name}"
+       fi
+
+        if [[ "$zpool_exported" != "true" || "$zfs_unmounted" != "true" ]]; 
then
+               echo "Skipping to disconnect devices!"
+               return -1
+       fi
+
+       # Try NBD sevice
+       if [[ "" == $(lsblk | grep nbd0 | grep " 0B") ]]; then
+               sudo qemu-nbd --disconnect /dev/nbd0 1>/dev/null
+               echo "Disconnected device /dev/nbd0 from the image"
+       fi
+
+       # Try loopback device
+       if [[ "" != $(lsblk | grep loop) ]]; then
+               local device_path=$(losetup -ln | cut -d ' ' -f 1)
+               sudo losetup -d $device_path 1>/dev/null
+               echo "Disconnected device $device_path from the image"
+       fi
+}
+
+build_image() {
+       connect_image $1 $2
+        local vdev=${device_path:5}
+
+       local pool_name="$3"
+       if [[ "" != $(zpool list -H ${pool_name} 2>/dev/null | grep 
${pool_name}) ]]; then
+               echo "There seems to be ${pool_name} already imported. Please 
export it first or run $0 unmount"
+               return -1
+       fi
+
+       local filesystem="$4"
+       sudo zpool create -df -R / ${pool_name} ${vdev}
+       sudo zpool set feature@lz4_compress=enabled ${pool_name}
+
+       local dataset="$pool_name/$filesystem"
+       sudo zfs create -u -o relatime=on ${dataset}
+       sudo zfs set mountpoint=/ ${pool_name}
+       sudo zfs set mountpoint=/${filesystem} ${pool_name}/${filesystem}
+       sudo zfs set canmount=noauto ${pool_name}
+       sudo zfs set canmount=noauto ${pool_name}/${filesystem}
+       sudo zfs set compression=lz4 ${pool_name}
+       sudo zfs mount ${dataset}
+
+       local populate_image=$5
+       if [[ "true" == "$populate_image" ]]; then
+               pushd "$OSV_ROOT/build/release" && sudo 
"$OSV_ROOT/scripts/export_manifest.py" -m usr.manifest -e /zfs/ -D 
libgcc_s_dir="$libgcc_s_dir" && popd
+       fi
+
+       sudo zfs set compression=off ${pool_name}
+
+       unmount_image $pool_name
+}
+
+sudo modprobe nbd
+sudo modprobe zfs
+if [[ $? != 0 ]]; then
+       echo "OpenZFS does not seem to be installed!"
+       exit 1
+fi
+
+OSV_ROOT="$(readlink -f $(dirname $0)/..)"
+
+command=$1
+shift
+case $command in 
+       mount)
+               image_path=${1:-$OSV_ROOT/build/last/usr.img}
+               partition=${2:-1}
+               pool_name=${3:-osv}
+               filesystem=${4:-zfs}
+               mount_image $image_path $partition $pool_name $filesystem
+               ;;
+       unmount)
+               pool_name=${1:-osv}
+               unmount_image $pool_name
+               ;;
+       build)
+               image_path=${1:-$OSV_ROOT/build/last/usr.img}
+               partition=${2:-1}
+               pool_name=${3:-osv}
+               filesystem=${4:-zfs}
+               populate_image=${5:-true}
+               build_image $image_path $partition $pool_name $filesystem 
$populate_image
+               ;;
+       *)
+               usage
+               ;;
+esac
+#
+# Once an image is mounted there are many useful commands one can use to 
inspect it
+# -------------------
+# Dataset related
+# ------------------
+# List all datasets
+#   zfs list
+#
+# List datasets named 'osv/zfs'
+#   zfs list osv/zfs
+#
+# List properties of the 'osv/zfs' filesystem
+#   zfs get all osv/zfs
+#
+# -------------------
+# Pool related
+# -------------------
+# List history of the 'osv' pool
+#   sudo zpool history osv
+#   sudo zpool history osv -l  # With extra details
+#   sudo zpool history osv -li # With more details
+#
+# List events of the 'osv' pool
+#   sudo zpool events osv
+#   sudo zpool events osv -v   # With way more details
+#
+# List properties of the 'osv' pool
+#   zpool get all osv
+#
+# List all pools
+#   zpool list
+#   zpool list -v # With vdevs

-- 
You received this message because you are subscribed to the Google Groups "OSv 
Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to osv-dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/osv-dev/000000000000511b2505e40177f5%40google.com.

Reply via email to