Attempting to roll back zfs transactions on a disk to recover a destroyed ZFS filesystem

Alan Somers asomers at freebsd.org
Thu Jul 11 15:04:34 UTC 2013


"zpool export" does not wipe the transaction history.  It does,
however, write new labels and some metadata, so there is a very slight
chance that it might overwrite some of the blocks that you're trying
to recover.  But it's probably safe.  An alternative, much more
complicated, solution would be to have ZFS open the device
non-exclusively.  This patch will do that.  Caveat programmer: I
haven't tested this patch in isolation.

Change 624068 by willa at willa_SpectraBSD on 2012/08/09 09:28:38

        Allow multiple opens of geoms used by vdev_geom.
        Also ignore the pool guid for spares when checking to decide whether
        it's ok to attach a vdev.

        This enables using hotspares to replace other devices, as well as
        using a given hotspare in multiple pools.

        We need to investigate alternative solutions in order to allow
        opening the geoms exclusive.

Affected files ...

... //SpectraBSD/stable/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev_geom.c#2
edit

Differences ...

==== //SpectraBSD/stable/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev_geom.c#2
(text) ====

@@ -179,49 +179,23 @@
                gp = g_new_geomf(&zfs_vdev_class, "zfs::vdev");
                gp->orphan = vdev_geom_orphan;
                gp->attrchanged = vdev_geom_attrchanged;
-               cp = g_new_consumer(gp);
-               error = g_attach(cp, pp);
-               if (error != 0) {
-                       printf("%s(%d): g_attach failed: %d\n", __func__,
-                              __LINE__, error);
-                       g_wither_geom(gp, ENXIO);
-                       return (NULL);
-               }
-               error = g_access(cp, 1, 0, 1);
-               if (error != 0) {
-                       printf("%s(%d): g_access failed: %d\n", __func__,
-                              __LINE__, error);
-                       g_wither_geom(gp, ENXIO);
-                       return (NULL);
-               }
-               ZFS_LOG(1, "Created geom and consumer for %s.", pp->name);
-       } else {
-               /* Check if we are already connected to this provider. */
-               LIST_FOREACH(cp, &gp->consumer, consumer) {
-                       if (cp->provider == pp) {
-                               ZFS_LOG(1, "Provider %s already in use by ZFS. "
-                                   "Failing attach.", pp->name);
-                               return (NULL);
-                       }
-               }
-               cp = g_new_consumer(gp);
-               error = g_attach(cp, pp);
-               if (error != 0) {
-                       printf("%s(%d): g_attach failed: %d\n",
-                              __func__, __LINE__, error);
-                       g_destroy_consumer(cp);
-                       return (NULL);
-               }
-               error = g_access(cp, 1, 0, 1);
-               if (error != 0) {
-                       printf("%s(%d): g_access failed: %d\n",
-                              __func__, __LINE__, error);
-                       g_detach(cp);
-                       g_destroy_consumer(cp);
-                       return (NULL);
-               }
-               ZFS_LOG(1, "Created consumer for %s.", pp->name);
+       }
+       cp = g_new_consumer(gp);
+       error = g_attach(cp, pp);
+       if (error != 0) {
+               printf("%s(%d): g_attach failed: %d\n", __func__,
+                      __LINE__, error);
+               g_wither_geom(gp, ENXIO);
+               return (NULL);
+       }
+       error = g_access(cp, /*r*/1, /*w*/0, /*e*/0);
+       if (error != 0) {
+               printf("%s(%d): g_access failed: %d\n", __func__,
+                      __LINE__, error);
+               g_wither_geom(gp, ENXIO);
+               return (NULL);
        }
+       ZFS_LOG(1, "Created consumer for %s.", pp->name);

        cp->private = vd;
        vd->vdev_tsd = cp;
@@ -251,7 +225,7 @@
        cp->private = NULL;

        gp = cp->geom;
-       g_access(cp, -1, 0, -1);
+       g_access(cp, -1, 0, 0);
        /* Destroy consumer on last close. */
        if (cp->acr == 0 && cp->ace == 0) {
                ZFS_LOG(1, "Destroyed consumer to %s.", cp->provider->name);
@@ -384,6 +358,18 @@
            cp->provider->name);
 }

+static inline boolean_t
+vdev_attach_ok(vdev_t *vd, uint64_t pool_guid, uint64_t vdev_guid)
+{
+       boolean_t pool_ok;
+       boolean_t vdev_ok;
+
+       /* Spares can be assigned to multiple pools. */
+       pool_ok = vd->vdev_isspare || pool_guid == spa_guid(vd->vdev_spa);
+       vdev_ok = vdev_guid == vd->vdev_guid;
+       return (pool_ok && vdev_ok);
+}
+
 static struct g_consumer *
 vdev_geom_attach_by_guids(vdev_t *vd)
 {
@@ -420,8 +406,7 @@
                                g_topology_lock();
                                g_access(zcp, -1, 0, 0);
                                g_detach(zcp);
-                               if (pguid != spa_guid(vd->vdev_spa) ||
-                                   vguid != vd->vdev_guid)
+                               if (!vdev_attach_ok(vd, pguid, vguid))
                                        continue;
                                cp = vdev_geom_attach(pp, vd);
                                if (cp == NULL) {
@@ -498,8 +483,10 @@
                        g_topology_unlock();
                        vdev_geom_read_guids(cp, &pguid, &vguid);
                        g_topology_lock();
-                       if (pguid != spa_guid(vd->vdev_spa) ||
-                           vguid != vd->vdev_guid) {
+                       if (vdev_attach_ok(vd, pguid, vguid)) {
+                               ZFS_LOG(1, "guids match for provider %s.",
+                                   vd->vdev_path);
+                       } else {
                                vdev_geom_close_locked(vd);
                                cp = NULL;
                                ZFS_LOG(1, "guid mismatch for provider %s: "
@@ -507,9 +494,6 @@
                                    (uintmax_t)spa_guid(vd->vdev_spa),
                                    (uintmax_t)vd->vdev_guid,
                                    (uintmax_t)pguid, (uintmax_t)vguid);
-                       } else {
-                               ZFS_LOG(1, "guids match for provider %s.",
-                                   vd->vdev_path);
                        }
                }
        }
@@ -601,8 +585,8 @@
                        g_topology_lock();
                }
                if (error != 0) {
-                       printf("ZFS WARNING: Unable to open %s for
writing (error=%d).\n",
-                           vd->vdev_path, error);
+                       printf("ZFS WARNING: Error %d opening %s for write.\n",
+                           error, vd->vdev_path);
                        vdev_geom_close_locked(vd);
                        cp = NULL;

On Thu, Jul 11, 2013 at 8:43 AM, Reid Linnemann <linnemannr at gmail.com> wrote:
> So recently I was trying to transfer a root-on-ZFS zpool from one pair of
> disks to a single, larger disk. As I am wont to do, I botched the transfer
> up and decided to destroy the ZFS filesystems on the destination and start
> again. Naturally I was up late working on this, being sloppy and drowsy
> without any coffee, and lo and behold I issued my 'zfs destroy -R' and
> immediately realized after pressing [ENTER[ that I had given it the
> source's zpool name. oops. Fortunately I was able to interrupt the
> procedure with only /usr being destroyed from the pool and I was able to
> send/receive the truly vital data in my /var partition to the new disk and
> re-deploy the base system to /usr on the new disk. The only thing I'm
> really missing at this point is all of the third-party software
> configuration I had in /usr/local/etc and my apache data in /usr/local/www.
>
> After a few minutes on Google I came across this wonderful page:
>
> http://www.solarisinternals.com/wiki/index.php/ZFS_forensics_scrollback_script
>
> where the author has published information about his python script which
> locates the uberblocks on the raw disk and shows the user the most recent
> transaction IDs, prompts the user for a transaction ID to roll back to, and
> zeroes out all uberblocks beyond that point. Theoretically, I should be
> able to use this script to get back to the transaction prior to my dreaded
> 'zfs destroy -R', then be able to recover the data I need (since no further
> writes have been done to the source disks).
>
> First, I know there's a problem in the script on FreeBSD in which the grep
> pattern for the od output expects a single space between the output
> elements. I've attached a patch that allows the output to be properly
> grepped in FreeBSD, so we can actually get to the transaction log.
>
> But now we are to my current problem. When attempting to roll back with
> this script, it tries to dd zero'd bytes to offsets into the disk device
> (/dev/ada1p3 in my case) where the uberblocks are located. But even
> with kern.geom.debugflags
> set to 0x10 (and I am runnign this as root) I get 'Operation not permitted'
> when the script tries to zero out the unwanted transactions. I'm fairly
> certain this is because the geom is in use by the ZFS subsystem, as it is
> still recognized as a part of the original pool. I'm hesitant to zfs export
> the pool, as I don't know if that wipes the transaction history on the
> pool. Does anyone have any ideas?
>
> Thanks,
> -Reid
>
> _______________________________________________
> freebsd-hackers at freebsd.org mailing list
> http://lists.freebsd.org/mailman/listinfo/freebsd-hackers
> To unsubscribe, send any mail to "freebsd-hackers-unsubscribe at freebsd.org"


More information about the freebsd-hackers mailing list