I can’t just tell you what to do, since you need to understand some basics before proceeding. You don’t want to run commands blindly for something as critical as this.
You cannot import your pool normally, which would have allowed you to do useful things like changing pool and dataset properties, creating new snapshots, and automatically mounting the datasets without requiring special steps. That’s why you can’t rely on the same procedures when the pool is imported read-only.
It’s very possible to try a zfs send/recv from one pool to another, while your “tank” pool is imported read-only. This is because zfs send/recv (a block-based operation) does not require a dataset to be mounted. The problem with that is it would only be as useful as the latest snapshots that exist on each dataset, but you won’t be able to take a new snapshot. Another caveat is that metadata corruption or I/O errors would abort the entire send anyways, since you cannot use a “partial” ZFS replication on the destination.
Importing a pool as read-only is usually done for emergency operations. This means you need to take extra planning and steps to do what you want to do.
You can’t rely on all datasets being mounted automatically. Every mounted filesystem is read-only, which means the needed directories to be used as mount points for child datasets cannot be created. You might ask why you can’t just mount all child datasets on their own directories outside of a common (root) directory. The reason you can’t do this is because you would need to change the “mountpoint” property of these datasets, but you can’t do that because the pool is read-only.
That leaves you with a tedious option: Create the directory structure ahead of time and only mount grandchildren and children as needed. Copy the files from these mounted read-only filesystems and repeat the process for other children and their parents.
Import the pool as read-only, don’t let it automount anything, and set its “altroot” to /tank:
zpool import -f -o readonly=on -N -R /tank tank
The “altroot” property will default to /tank anyways, but this lets you see how you can use something different like /recovery if you wanted to.
List the dataset hierarchy, so you know what directory structure to create before mounting anything:
zfs list -t fs -o name -r tank
I’m assuming you’re not using zvols or VMs.
Here’s what that might look like:
tank
tank/child1
tank/child1/grandchildA
tank/child1/grandchildB
tank/child2
tank/child3
tank/child3/grandchildC
Use mkdir to create those directories under the root path /tank :
mkdir /tank
mkdir /tank/child1
mkdir /tank/child1/grandchildA
mkdir /tank/child1/grandchildB
mkdir /tank/child2
mkdir /tank/child3
mkdir /tank/child3/grandchildC
I skipped shell shortcuts and other options because it would only make these steps more complex than they need to be.
Now mount the first grandchild that you want to copy files from.
zfs mount tank/child1/grandchildA
The read-only filesystem contents can be found under the associated mount point /tank/child1/grandchildA/ .
You should then be able to use rsync to transfer everything to a backup destination path:
rsync -avxxhH --progress /tank/child1/grandchildA/ /mnt/backup/child1/grandchildA/
The slashes at the end of the paths are not optional.
Repeat the process with the other grandchildren, children, and parents.
You might bump into a lot of issues, especially if you don’t use sudo or you have special permissions and ACLs. The problem with a file-based solution is you do not get a perfect replica of a filesystem as the result. At least with rsync, you can issue the same command and it will skip files that already transferred successfully.
You might bump into serious problems if there is metadata corruption or I/O errors due to faulty or failing hardware.