Print this page
OS-1566 filesystem limits for ZFS datasets

@@ -641,10 +641,25 @@
                         return (EINVAL);
                 if (rbsa->origin->ds_phys->ds_guid != rbsa->fromguid)
                         return (ENODEV);
         }
 
+        /*
+         * Check filesystem and snapshot limits before receiving. We'll recheck
+         * again at the end, but might as well abort before receiving if we're
+         * already over the limit.
+         */
+        if (dd->dd_parent != NULL) {
+                err = dsl_dir_fscount_check(dd->dd_parent, 1, NULL);
+                if (err != 0)
+                        return (err);
+        }
+
+        err = dsl_snapcount_check(dd, 1, NULL);
+        if (err != 0)
+                return (err);
+
         return (0);
 }
 
 static void
 recv_new_sync(void *arg1, void *arg2, dmu_tx_t *tx)

@@ -723,10 +738,15 @@
                 }
         } else {
                 /* if full, most recent snapshot must be $ORIGIN */
                 if (ds->ds_phys->ds_prev_snap_txg >= TXG_INITIAL)
                         return (ENODEV);
+
+                /* Check snapshot limit before receiving */
+                err = dsl_snapcount_check(ds->ds_dir, 1, NULL);
+                if (err != 0)
+                        return (err);
         }
 
         /* temporary clone name must not exist */
         err = zap_lookup(ds->ds_dir->dd_pool->dp_meta_objset,
             ds->ds_dir->dd_phys->dd_child_dir_zapobj,

@@ -1545,27 +1565,46 @@
 
 struct recvendsyncarg {
         char *tosnap;
         uint64_t creation_time;
         uint64_t toguid;
+        boolean_t is_new;
 };
 
 static int
 recv_end_check(void *arg1, void *arg2, dmu_tx_t *tx)
 {
         dsl_dataset_t *ds = arg1;
         struct recvendsyncarg *resa = arg2;
 
-        return (dsl_dataset_snapshot_check(ds, resa->tosnap, tx));
+        if (resa->is_new) {
+                /* re-check the filesystem limit now that recv is complete */
+                dsl_dir_t *dd;
+                int err;
+
+                dd = ds->ds_dir;
+                if (dd->dd_parent != NULL) {
+                        err = dsl_dir_fscount_check(dd->dd_parent, 1, NULL);
+                        if (err != 0)
+                                return (err);
+                }
+        }
+
+        return (dsl_dataset_snapshot_check(ds, resa->tosnap, 1, tx));
 }
 
 static void
 recv_end_sync(void *arg1, void *arg2, dmu_tx_t *tx)
 {
         dsl_dataset_t *ds = arg1;
         struct recvendsyncarg *resa = arg2;
 
+        if (resa->is_new)
+                /* update the filesystem counts */
+                dsl_dir_fscount_adjust(ds->ds_dir->dd_parent, tx, 1, B_FALSE,
+                    B_TRUE);
+
         dsl_dataset_snapshot_sync(ds, resa->tosnap, tx);
 
         /* set snapshot's creation time and guid */
         dmu_buf_will_dirty(ds->ds_prev->ds_dbuf, tx);
         ds->ds_prev->ds_phys->ds_creation_time = resa->creation_time;

@@ -1622,10 +1661,11 @@
         }
 
         resa.creation_time = drc->drc_drrb->drr_creation_time;
         resa.toguid = drc->drc_drrb->drr_toguid;
         resa.tosnap = drc->drc_tosnap;
+        resa.is_new = B_FALSE;
 
         err = dsl_sync_task_do(ds->ds_dir->dd_pool,
             recv_end_check, recv_end_sync, ds, &resa, 3);
         if (err) {
                 /* swap back */

@@ -1657,10 +1697,11 @@
         txg_wait_synced(ds->ds_dir->dd_pool, 0);
 
         resa.creation_time = drc->drc_drrb->drr_creation_time;
         resa.toguid = drc->drc_drrb->drr_toguid;
         resa.tosnap = drc->drc_tosnap;
+        resa.is_new = B_TRUE;
 
         err = dsl_sync_task_do(ds->ds_dir->dd_pool,
             recv_end_check, recv_end_sync, ds, &resa, 3);
         if (err) {
                 /* clean up the fs we just recv'd into */