Print this page
FITS: generating send-streams in portable format
This commit adds the command 'zfs fits-send', analogous to zfs send. The
generated send stream is compatible with the stream generated with that
from 'btrfs send' and can in principle easily be received to any filesystem.

@@ -170,10 +170,11 @@
 #include <sys/zfs_onexit.h>
 #include <sys/zvol.h>
 #include <sys/dsl_scan.h>
 #include <sharefs/share.h>
 #include <sys/dmu_objset.h>
+#include <sys/fits.h>
 
 #include "zfs_namecheck.h"
 #include "zfs_prop.h"
 #include "zfs_deleg.h"
 #include "zfs_comutil.h"

@@ -5129,10 +5130,118 @@
         dmu_objset_rele(tosnap, FTAG);
         return (error);
 }
 
 /*
+ * inputs:
+ * zc_name      name of snapshot to send
+ * zc_cookie    file descriptor to send stream to
+ * zc_obj       fromorigin flag (mutually exclusive with zc_fromobj)
+ * zc_sendobj   objsetid of snapshot to send
+ * zc_fromobj   objsetid of incremental fromsnap (may be zero)
+ * zc_guid      if set, estimate size of stream only.  zc_cookie is ignored.
+ *              output size in zc_objset_type.
+ *
+ * outputs: none
+ */
+static int
+zfs_ioc_fits_send(zfs_cmd_t *zc)
+{
+        objset_t *fromsnap = NULL;
+        objset_t *tosnap;
+        int error;
+        offset_t off;
+        dsl_dataset_t *ds;
+        dsl_dataset_t *dsfrom = NULL;
+        spa_t *spa;
+        file_t *fp;
+        dsl_pool_t *dp;
+        boolean_t estimate = (zc->zc_guid != 0);
+
+        error = spa_open(zc->zc_name, &spa, FTAG);
+        if (error)
+                return (error);
+
+        dp = spa_get_dsl(spa);
+        rw_enter(&dp->dp_config_rwlock, RW_READER);
+        error = dsl_dataset_hold_obj(dp, zc->zc_sendobj, FTAG, &ds);
+        rw_exit(&dp->dp_config_rwlock);
+        spa_close(spa, FTAG);
+        if (error)
+                return (error);
+
+        error = dmu_objset_from_ds(ds, &tosnap);
+        if (error) {
+                dsl_dataset_rele(ds, FTAG);
+                return (error);
+        }
+
+        if (zc->zc_fromobj != 0) {
+                rw_enter(&dp->dp_config_rwlock, RW_READER);
+                error = dsl_dataset_hold_obj(dp, zc->zc_fromobj, FTAG, &dsfrom);
+                rw_exit(&dp->dp_config_rwlock);
+                if (error) {
+                        dsl_dataset_rele(ds, FTAG);
+                        return (error);
+                }
+                error = dmu_objset_from_ds(dsfrom, &fromsnap);
+                if (error) {
+                        dsl_dataset_rele(dsfrom, FTAG);
+                        dsl_dataset_rele(ds, FTAG);
+                        return (error);
+                }
+        }
+
+        if (zc->zc_obj) {
+                dsl_pool_t *dp = ds->ds_dir->dd_pool;
+
+                if (fromsnap != NULL) {
+                        dsl_dataset_rele(dsfrom, FTAG);
+                        dsl_dataset_rele(ds, FTAG);
+                        return (EINVAL);
+                }
+
+                if (dsl_dir_is_clone(ds->ds_dir)) {
+                        rw_enter(&dp->dp_config_rwlock, RW_READER);
+                        error = dsl_dataset_hold_obj(dp,
+                            ds->ds_dir->dd_phys->dd_origin_obj, FTAG, &dsfrom);
+                        rw_exit(&dp->dp_config_rwlock);
+                        if (error) {
+                                dsl_dataset_rele(ds, FTAG);
+                                return (error);
+                        }
+                        error = dmu_objset_from_ds(dsfrom, &fromsnap);
+                        if (error) {
+                                dsl_dataset_rele(dsfrom, FTAG);
+                                dsl_dataset_rele(ds, FTAG);
+                                return (error);
+                        }
+                }
+        }
+
+        fp = getf(zc->zc_cookie);
+        if (fp == NULL) {
+                dsl_dataset_rele(ds, FTAG);
+                if (dsfrom)
+                        dsl_dataset_rele(dsfrom, FTAG);
+                return (EBADF);
+        }
+
+        off = fp->f_offset;
+        error = fits_send(tosnap, fromsnap, zc->zc_cookie, fp->f_vnode, &off);
+
+        if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
+                fp->f_offset = off;
+        releasef(zc->zc_cookie);
+
+        if (dsfrom)
+                dsl_dataset_rele(dsfrom, FTAG);
+        dsl_dataset_rele(ds, FTAG);
+        return (error);
+}
+
+/*
  * Determine approximately how large a zfs send stream will be -- the number
  * of bytes that will be written to the fd supplied to zfs_ioc_send_new().
  *
  * innvl: {
  *     (optional) "fromsnap" -> full snap name to send an incremental from

@@ -5420,10 +5529,12 @@
             zfs_ioc_userspace_one, zfs_secpolicy_userspace_one);
         zfs_ioctl_register_dataset_read_secpolicy(ZFS_IOC_USERSPACE_MANY,
             zfs_ioc_userspace_many, zfs_secpolicy_userspace_many);
         zfs_ioctl_register_dataset_read_secpolicy(ZFS_IOC_SEND,
             zfs_ioc_send, zfs_secpolicy_send);
+        zfs_ioctl_register_dataset_read_secpolicy(ZFS_IOC_FITS_SEND,
+            zfs_ioc_fits_send, zfs_secpolicy_send);
 
         zfs_ioctl_register_dataset_modify(ZFS_IOC_SET_PROP, zfs_ioc_set_prop,
             zfs_secpolicy_none);
         zfs_ioctl_register_dataset_modify(ZFS_IOC_DESTROY, zfs_ioc_destroy,
             zfs_secpolicy_destroy);