]> git.ipfire.org Git - thirdparty/mdadm.git/commitdiff
Add --dump / --restore functionality.
authorNeilBrown <neilb@suse.de>
Thu, 16 May 2013 05:07:16 +0000 (15:07 +1000)
committerNeilBrown <neilb@suse.de>
Thu, 16 May 2013 05:07:16 +0000 (15:07 +1000)
This allows the metadata on a device to be saved and later restored.
This can be useful before experimenting on an array that is misbehaving.

Suggested-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: NeilBrown <neilb@suse.de>
Dump.c [new file with mode: 0644]
Makefile
ReadMe.c
mdadm.8.in
mdadm.c
mdadm.h
super-ddf.c
super-intel.c
super0.c
super1.c

diff --git a/Dump.c b/Dump.c
new file mode 100644 (file)
index 0000000..7bdbf6f
--- /dev/null
+++ b/Dump.c
@@ -0,0 +1,311 @@
+/*
+ * mdadm - manage Linux "md" devices aka RAID arrays.
+ *
+ * Copyright (C) 2013 Neil Brown <neilb@suse.de>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+
+ *    Author: Neil Brown
+ *    Email: <neilb@suse.de>
+ */
+
+#include "mdadm.h"
+#include <sys/dir.h>
+
+int Dump_metadata(char *dev, char *dir, struct context *c,
+                 struct supertype *st)
+{
+       /* create a new file in 'dir' named for the basename of 'dev'.
+        * Truncate to the same size as 'dev' and ask the metadata
+        * handler to copy metadata there.
+        * For every name in /dev/disk/by-id that points to this device,
+        * create a hardlink in 'dir'.
+        * Complain if any of those hardlinks cannot be created.
+        */
+       int fd, fl;
+       struct stat stb, dstb;
+       char *base;
+       char *fname = NULL;
+       unsigned long long size;
+       DIR *dirp;
+       struct dirent *de;
+
+       if (stat(dir, &stb) != 0 ||
+           (S_IFMT & stb.st_mode) != S_IFDIR) {
+               pr_err("--dump requires an existing directory, not: %s\n",
+                       dir);
+               return 16;
+       }
+
+       fd = dev_open(dev, O_RDONLY);
+       if (fd < 0) {
+               pr_err("Cannot open %s to dump metadata: %s\n",
+                      dev, strerror(errno));
+               return 1;
+       }
+       if (!get_dev_size(fd, dev, &size)) {
+               close(fd);
+               return 1;
+       }
+
+       if (st == NULL)
+               st = guess_super_type(fd, guess_array);
+       if (!st) {
+               pr_err("Cannot find RAID metadata on %s\n", dev);
+               close(fd);
+               return 1;
+       }
+
+       st->ignore_hw_compat = 1;
+       if (st->ss->load_super(st, fd, NULL) != 0) {
+               pr_err("No %s metadata found on %s\n",
+                      st->ss->name, dev);
+               close(fd);
+               return 1;
+       }
+       if (st->ss->copy_metadata == NULL) {
+               pr_err("%s metadata on %s cannot be copied\n",
+                      st->ss->name, dev);
+               close(fd);
+               return 1;
+       }
+
+       base = strrchr(dev, '/');
+       if (base)
+               base++;
+       else
+               base = dev;
+       xasprintf(&fname, "%s/%s", dir, base);
+       fl = open(fname, O_RDWR|O_CREAT|O_EXCL, 0666);
+       if (fl < 0) {
+               pr_err("Cannot create dump file %s: %s\n",
+                      fname, strerror(errno));
+               close(fd);
+               free(fname);
+               return 1;
+       }
+       if (ftruncate(fl, size) < 0) {
+               pr_err("failed to set size of dump file: %s\n",
+                      strerror(errno));
+               close(fd);
+               close(fl);
+               free(fname);
+               return 1;
+       }
+
+       if (st->ss->copy_metadata(st, fd, fl) != 0) {
+               pr_err("Failed to copy metadata from %s to %s\n",
+                      dev, fname);
+               close(fd);
+               close(fl);
+               unlink(fname);
+               free(fname);
+               return 1;
+       }
+       if (c->verbose >= 0)
+               printf("%s saved as %s.\n", dev, fname);
+       fstat(fd, &dstb);
+       close(fd);
+       close(fl);
+       if ((dstb.st_mode & S_IFMT) != S_IFBLK) {
+               /* Not a block device, so cannot create links */
+               free(fname);
+               return 0;
+       }
+       /* mostly done: just want to find some other names */
+       dirp = opendir("/dev/disk/by-id");
+       if (!dirp) {
+               free(fname);
+               return 0;
+       }
+       while ((de = readdir(dirp)) != NULL) {
+               char *p = NULL;
+               if (de->d_name[0] == '.')
+                       continue;
+               xasprintf(&p, "/dev/disk/by-id/%s", de->d_name);
+               if (stat(p, &stb) != 0 ||
+                   (stb.st_mode & S_IFMT) != S_IFBLK ||
+                   stb.st_rdev != dstb.st_rdev) {
+                       /* Not this one */
+                       free(p);
+                       continue;
+               }
+               free(p);
+               xasprintf(&p, "%s/%s", dir, de->d_name);
+               if (link(fname, p) == 0) {
+                       if (c->verbose >= 0)
+                               printf("%s also saved as %s.\n",
+                                      dev, p);
+               } else {
+                       pr_err("Could not save %s as %s!!\n",
+                                      dev, p);
+               }
+               free(p);
+       }
+       closedir(dirp);
+       free(fname);
+       return 0;
+}
+
+int Restore_metadata(char *dev, char *dir, struct context *c,
+                    struct supertype *st, int only)
+{
+       /* If 'dir' really is a directory we choose a name
+        * from it that matches a suitable name in /dev/disk/by-id,
+        * and copy metadata from the file to the device.
+        * If two names from by-id match and aren't both the same
+        * inode, we fail.  If none match and basename of 'dev'
+        * can be found  in dir, use that.
+        * If 'dir' is really a file then it is only permitted if
+        * 'only' is set (meaning there was only one device given)
+        * and the metadata is restored irrespective of file names.
+        */
+       int fd, fl;
+       struct stat stb, dstb;
+       char *fname = NULL;
+       unsigned long long size;
+
+       if (stat(dir, &stb) != 0) {
+               pr_err("%s does not exist: cannot restore from there.\n",
+                      dir);
+               return 16;
+       } else if ((S_IFMT & stb.st_mode) != S_IFDIR && !only) {
+               pr_err("--restore requires a directory when multiple devices given\n");
+               return 16;
+       }
+
+       fd = dev_open(dev, O_RDWR);
+       if (fd < 0) {
+               pr_err("Cannot open %s to restore metadata: %s\n",
+                      dev, strerror(errno));
+               return 1;
+       }
+       if (!get_dev_size(fd, dev, &size)) {
+               close(fd);
+               return 1;
+       }
+
+       if ((S_IFMT & stb.st_mode) == S_IFDIR) {
+               /* choose one name from the directory. */
+               DIR *d = opendir(dir);
+               struct dirent *de;
+               char *chosen = NULL;
+               unsigned int chosen_inode = 0;
+
+               fstat(fd, &dstb);
+
+               while (d && (de = readdir(d)) != NULL) {
+                       if (de->d_name[0] == '.')
+                               continue;
+                       xasprintf(&fname, "/dev/disk/by-id/%s", de->d_name);
+                       if (stat(fname, &stb) != 0) {
+                               free(fname);
+                               continue;
+                       }
+                       free(fname);
+                       if ((S_IFMT & stb.st_mode) != S_IFBLK)
+                               continue;
+                       if (stb.st_rdev != dstb.st_rdev)
+                               continue;
+                       /* This file is a good match for our device. */
+                       xasprintf(&fname, "%s/%s", dir, de->d_name);
+                       if (stat(fname, &stb) != 0) {
+                               /* Weird! */
+                               free(fname);
+                               continue;
+                       }
+                       if (chosen == NULL) {
+                               chosen = fname;
+                               chosen_inode = stb.st_ino;
+                               continue;
+                       }
+                       if (chosen_inode == stb.st_ino) {
+                               /* same, no need to change */
+                               free(fname);
+                               continue;
+                       }
+                       /* Oh dear, two names both match.  Must give up. */
+                       pr_err("Both %s and %s seem suitable for %s.  Please choose one.\n",
+                              chosen, fname, dev);
+                       free(fname);
+                       free(chosen);
+                       close(fd);
+                       closedir(d);
+                       return 1;
+               }
+               closedir(d);
+               if (!chosen) {
+                       /* One last chance: try basename of device */
+                       char *base = strrchr(dev, '/');
+                       if (base)
+                               base++;
+                       else
+                               base = dev;
+                       xasprintf(&fname, "%s/%s", dir, base);
+                       if (stat(fname, &stb) == 0)
+                               chosen = fname;
+                       else
+                               free(fname);
+               }
+               fname = chosen;
+       } else
+               fname = strdup(dir);
+
+       if (!fname) {
+               pr_err("Cannot find suitable file in %s for %s\n",
+                      dir, dev);
+               close(fd);
+               return 1;
+       }
+
+       fl = open(fname, O_RDONLY);
+       if (!fl) {
+               pr_err("Could not open %s for --restore.\n",
+                      fname);
+               goto err;
+       }
+       if (((unsigned long long)stb.st_size) != size) {
+               pr_err("%s is not the same size as %s - cannot restore.\n",
+                      fname, dev);
+               goto err;
+       }
+       if (st == NULL)
+               st = guess_super_type(fl, guess_array);
+       if (!st) {
+               pr_err("Cannot find metadata on %s\n", fname);
+               goto err;
+       }
+       st->ignore_hw_compat = 1;
+       if (st->ss->load_super(st, fl, NULL) != 0) {
+               pr_err("No %s metadata found on %s\n",
+                      st->ss->name, fname);
+               goto err;
+       }
+       if (st->ss->copy_metadata == NULL) {
+               pr_err("%s metadata on %s cannot be copied\n",
+                      st->ss->name, dev);
+               goto err;
+       }
+       if (st->ss->copy_metadata(st, fl, fd) != 0) {
+               pr_err("Failed to copy metadata from %s to %s\n",
+                      fname, dev);
+               goto err;
+       }
+       if (c->verbose >= 0)
+               printf("%s restored from %s.\n", dev, fname);
+       return 0;
+
+err:
+       close(fd);
+       close(fl);
+       free(fname);
+       return 1;
+}
index ad0819d39bc3977da9646fdac92e3e0bf6937dca..8cb8d5102fc1e651076978ca9d1f27d5d77bf471 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -107,7 +107,7 @@ endif
 OBJS =  mdadm.o config.o policy.o mdstat.o  ReadMe.o util.o maps.o lib.o \
        Manage.o Assemble.o Build.o \
        Create.o Detail.o Examine.o Grow.o Monitor.o dlink.o Kill.o Query.o \
-       Incremental.o \
+       Incremental.o Dump.o \
        mdopen.o super0.o super1.o super-ddf.o super-intel.o bitmap.o \
        super-mbr.o super-gpt.o \
        restripe.o sysfs.o sha1.o mapfile.o crc32.o sg_io.o msg.o xmalloc.o \
index c4bb7301d0946bd49636f62dcc559b7b29b69c60..cb0678ab97f8cec9af7d852a8b06252830d5c565 100644 (file)
--- a/ReadMe.c
+++ b/ReadMe.c
@@ -97,6 +97,9 @@ struct option long_options[] = {
     {"offroot", 0, 0, OffRootOpt},
     {"examine-badblocks", 0, 0, ExamineBB},
 
+    {"dump", 1, 0, Dump},
+    {"restore", 1, 0, Restore},
+
     /* synonyms */
     {"monitor",   0, 0, 'F'},
 
index c8766595439f36aac000217207d1f001b10f8972..c8559dae8e789a30e0c4ee826745a42980629977 100644 (file)
@@ -1448,6 +1448,12 @@ been configured.  Currently only
 .B 1.x
 metadata supports bad-blocks lists.
 
+.TP
+.BI \-\-dump= directory
+.TP
+.BI \-\-restore= directory
+Save metadata from lists devices, or restore metadata to listed devices.
+
 .TP
 .BR \-R ", " \-\-run
 start a partially assembled array.  If
@@ -2135,6 +2141,49 @@ Having
 without listing any devices will cause all devices listed in the
 config file to be examined.
 
+.TP
+.BI \-\-dump= directory
+If the device contains RAID metadata, a file will be created in the
+.I directory
+and the metadata will be written to it.  The file will be the same
+size as the device and have the metadata written in the file at the
+same locate that it exists in the device.  However the file will be "sparse" so
+that only those blocks containing metadata will be allocated. The
+total space used will be small.
+
+The file name used in the
+.I directory
+will be the base name of the device.   Further if any links appear in
+.I /dev/disk/by-id
+which point to the device, then hard links to the file will be created
+in
+.I directory
+based on these
+.I by-id
+names.
+
+Multiple devices can be listed and their metadata will all be stored
+in the one directory.
+
+.TP
+.BI \-\-restore= directory
+This is the reverse of
+.BR \-\-dump .
+.I mdadm
+will locate a file in the directory that has a name appropriate for
+the given device and will restore metadata from it.  Names that match
+.I /dev/disk/by-id
+names are preferred, however if two of those refer to different files,
+.I mdadm
+will not choose between them but will abort the operation.
+
+If a file name is given instead of a
+.I directory
+then
+.I mdadm
+will restore from that file to a single device, always provided the
+size of the file matches that of the device, and the file contains
+valid metadata.
 .TP
 .B \-\-stop
 The devices should be active md arrays which will be deactivated, as
diff --git a/mdadm.c b/mdadm.c
index e053db599fe2b0d245e871057dd7e9f284c16e1b..ff3fed72c1fe8d3188c49d43d0cbaaecb3c57177 100644 (file)
--- a/mdadm.c
+++ b/mdadm.c
@@ -37,6 +37,7 @@ static int misc_scan(char devmode, struct context *c);
 static int stop_scan(int verbose);
 static int misc_list(struct mddev_dev *devlist,
                     struct mddev_ident *ident,
+                    char *dump_directory,
                     struct supertype *ss, struct context *c);
 
 
@@ -94,6 +95,7 @@ int main(int argc, char *argv[])
        int rebuild_map = 0;
        char *remove_path = NULL;
        char *udev_filename = NULL;
+       char *dump_directory = NULL;
 
        int print_help = 0;
        FILE *outf;
@@ -234,6 +236,8 @@ int main(int argc, char *argv[])
                case 'X':
                case 'Q':
                case ExamineBB:
+               case Dump:
+               case Restore:
                        newmode = MISC;
                        break;
 
@@ -982,6 +986,8 @@ int main(int argc, char *argv[])
                case O(MISC, DetailPlatform):
                case O(MISC, KillSubarray):
                case O(MISC, UpdateSubarray):
+               case O(MISC, Dump):
+               case O(MISC, Restore):
                        if (opt == KillSubarray || opt == UpdateSubarray) {
                                if (c.subarray) {
                                        pr_err("subarray can only"
@@ -1006,6 +1012,14 @@ int main(int argc, char *argv[])
                                exit(2);
                        }
                        devmode = opt;
+                       if (opt == Dump || opt == Restore) {
+                               if (dump_directory != NULL) {
+                                       pr_err("dump/restore directory specified twice: %s and %s\n",
+                                              dump_directory, optarg);
+                                       exit(2);
+                               }
+                               dump_directory = optarg;
+                       }
                        continue;
                case O(MISC, UdevRules):
                        if (devmode && devmode != opt) {
@@ -1407,7 +1421,7 @@ int main(int argc, char *argv[])
                                exit(2);
                        }
                } else
-                       rv = misc_list(devlist, &ident, ss, &c);
+                       rv = misc_list(devlist, &ident, dump_directory, ss, &c);
                break;
        case MONITOR:
                if (!devlist && !c.scan) {
@@ -1721,12 +1735,13 @@ static int stop_scan(int verbose)
 
 static int misc_list(struct mddev_dev *devlist,
                     struct mddev_ident *ident,
+                    char *dump_directory,
                     struct supertype *ss, struct context *c)
 {
        struct mddev_dev *dv;
        int rv = 0;
 
-       for (dv=devlist ; dv; dv=dv->next) {
+       for (dv=devlist ; dv; dv=(rv & 16) ? NULL : dv->next) {
                int mdfd;
 
                switch(dv->disposition) {
@@ -1768,6 +1783,13 @@ static int misc_list(struct mddev_dev *devlist,
                        rv |= Update_subarray(dv->devname, c->subarray,
                                              c->update, ident, c->verbose);
                        continue;
+               case Dump:
+                       rv |= Dump_metadata(dv->devname, dump_directory, c, ss);
+                       continue;
+               case Restore:
+                       rv |= Restore_metadata(dv->devname, dump_directory, c, ss,
+                                              (dv == devlist && dv->next == NULL));
+                       continue;
                }
                mdfd = open_mddev(dv->devname, 1);
                if (mdfd>=0) {
diff --git a/mdadm.h b/mdadm.h
index 3139259db6b3c151dd43628afe4dfebd163d1bbe..3244c3da45a5867ef476b498c9022f8c2c7c87b1 100644 (file)
--- a/mdadm.h
+++ b/mdadm.h
@@ -339,6 +339,8 @@ enum special_options {
        KillOpt,
        DataOffset,
        ExamineBB,
+       Dump,
+       Restore,
 };
 
 enum prefix_standard {
@@ -630,7 +632,7 @@ struct reshape {
 
 /* A superswitch provides entry point the a metadata handler.
  *
- * The super_switch primarily operates on some "metadata" that
+ * The superswitch primarily operates on some "metadata" that
  * is accessed via the 'supertype'.
  * This metadata has one of three possible sources.
  * 1/ It is read from a single device.  In this case it may not completely
@@ -665,6 +667,7 @@ extern struct superswitch {
        void (*brief_examine_subarrays)(struct supertype *st, int verbose);
        void (*export_examine_super)(struct supertype *st);
        int (*examine_badblocks)(struct supertype *st, int fd, char *devname);
+       int (*copy_metadata)(struct supertype *st, int from, int to);
 
        /* Used to report details of an active array.
         * ->load_super was possibly given a 'component' string.
@@ -1177,6 +1180,10 @@ extern int ExamineBitmap(char *filename, int brief, struct supertype *st);
 extern int Write_rules(char *rule_name);
 extern int bitmap_update_uuid(int fd, int *uuid, int swap);
 extern unsigned long bitmap_sectors(struct bitmap_super_s *bsb);
+extern int Dump_metadata(char *dev, char *dir, struct context *c,
+                        struct supertype *st);
+extern int Restore_metadata(char *dev, char *dir, struct context *c,
+                           struct supertype *st, int only);
 
 extern int md_get_version(int fd);
 extern int get_linux_version(void);
index a78692c3995616e105024864a769aab402049f60..9192ccdcc77079d9aeea6ad3d45c65b0f65401b8 100644 (file)
@@ -1327,6 +1327,68 @@ static void export_examine_super_ddf(struct supertype *st)
        printf("MD_UUID=%s\n", nbuf+5);
 }
 
+static int copy_metadata_ddf(struct supertype *st, int from, int to)
+{
+       void *buf;
+       unsigned long long dsize, offset;
+       int bytes;
+       struct ddf_header *ddf;
+       int written = 0;
+
+       /* The meta consists of an anchor, a primary, and a secondary.
+        * This all lives at the end of the device.
+        * So it is easiest to find the earliest of primary and
+        * secondary, and copy everything from there.
+        *
+        * Anchor is 512 from end It contains primary_lba and secondary_lba
+        * we choose one of those
+        */
+
+       if (posix_memalign(&buf, 4096, 4096) != 0)
+               return 1;
+
+       if (!get_dev_size(from, NULL, &dsize))
+               goto err;
+
+       if (lseek64(from, dsize-512, 0) < 0)
+               goto err;
+       if (read(from, buf, 512) != 512)
+               goto err;
+       ddf = buf;
+       if (ddf->magic != DDF_HEADER_MAGIC ||
+           calc_crc(ddf, 512) != ddf->crc ||
+           (memcmp(ddf->revision, DDF_REVISION_0, 8) != 0 &&
+            memcmp(ddf->revision, DDF_REVISION_2, 8) != 0))
+               goto err;
+
+       offset = dsize - 512;
+       if ((__be64_to_cpu(ddf->primary_lba) << 9) < offset)
+               offset = __be64_to_cpu(ddf->primary_lba) << 9;
+       if ((__be64_to_cpu(ddf->secondary_lba) << 9) < offset)
+               offset = __be64_to_cpu(ddf->secondary_lba) << 9;
+
+       bytes = dsize - offset;
+
+       if (lseek64(from, offset, 0) < 0 ||
+           lseek64(to, offset, 0) < 0)
+               goto err;
+       while (written < bytes) {
+               int n = bytes - written;
+               if (n > 4096)
+                       n = 4096;
+               if (read(from, buf, n) != n)
+                       goto err;
+               if (write(to, buf, n) != n)
+                       goto err;
+               written += n;
+       }
+       free(buf);
+       return 0;
+err:
+       free(buf);
+       return 1;
+}
+
 static void detail_super_ddf(struct supertype *st, char *homehost)
 {
        /* FIXME later
@@ -4298,6 +4360,7 @@ struct superswitch super_ddf = {
        .add_to_super   = add_to_super_ddf,
        .remove_from_super = remove_from_super_ddf,
        .load_container = load_container_ddf,
+       .copy_metadata = copy_metadata_ddf,
 #endif
        .match_home     = match_home_ddf,
        .uuid_from_super= uuid_from_super_ddf,
index 2cb28b04b2dcf621611666d9b3fb0bc5b41a1ba8..e0b86b6c6a2b453916642e2e5b6b474a7ef6f94a 100644 (file)
@@ -1510,6 +1510,59 @@ static void export_examine_super_imsm(struct supertype *st)
        printf("MD_DEVICES=%u\n", mpb->num_disks);
 }
 
+static int copy_metadata_imsm(struct supertype *st, int from, int to)
+{
+       /* The second last 512byte sector of the device contains
+        * the "struct imsm_super" metadata.
+        * This contains mpb_size which is the size in bytes of the
+        * extended metadata.  This is located immediately before
+        * the imsm_super.
+        * We want to read all that, plus the last sector which
+        * may contain a migration record, and write it all
+        * to the target.
+        */
+       void *buf;
+       unsigned long long dsize, offset;
+       int sectors;
+       struct imsm_super *sb;
+       int written = 0;
+
+       if (posix_memalign(&buf, 4096, 4096) != 0)
+               return 1;
+
+       if (!get_dev_size(from, NULL, &dsize))
+               goto err;
+
+       if (lseek64(from, dsize-1024, 0) < 0)
+               goto err;
+       if (read(from, buf, 512) != 512)
+               goto err;
+       sb = buf;
+       if (strncmp((char*)sb->sig, MPB_SIGNATURE, MPB_SIG_LEN) != 0)
+               goto err;
+
+       sectors = mpb_sectors(sb) + 2;
+       offset = dsize - sectors * 512;
+       if (lseek64(from, offset, 0) < 0 ||
+           lseek64(to, offset, 0) < 0)
+               goto err;
+       while (written < sectors * 512) {
+               int n = sectors*512 - written;
+               if (n > 4096)
+                       n = 4096;
+               if (read(from, buf, n) != n)
+                       goto err;
+               if (write(to, buf, n) != n)
+                       goto err;
+               written += n;
+       }
+       free(buf);
+       return 0;
+err:
+       free(buf);
+       return 1;
+}
+
 static void detail_super_imsm(struct supertype *st, char *homehost)
 {
        struct mdinfo info;
@@ -10463,6 +10516,7 @@ struct superswitch super_imsm = {
        .reshape_super  = imsm_reshape_super,
        .manage_reshape = imsm_manage_reshape,
        .recover_backup = recover_backup_imsm,
+       .copy_metadata = copy_metadata_imsm,
 #endif
        .match_home     = match_home_imsm,
        .uuid_from_super= uuid_from_super_imsm,
index 1f4dc750722bafb7313fb6bf47890e270a2ce1de..58785e298bf826edd7d13646515aa4520e46a889 100644 (file)
--- a/super0.c
+++ b/super0.c
@@ -47,7 +47,6 @@ static unsigned long calc_sb0_csum(mdp_super_t *super)
        return newcsum;
 }
 
-
 static void super0_swap_endian(struct mdp_superblock_s *sb)
 {
        /* as super0 superblocks are host-endian, it is sometimes
@@ -281,6 +280,51 @@ static void export_examine_super0(struct supertype *st)
               + sb->events_lo);
 }
 
+static int copy_metadata0(struct supertype *st, int from, int to)
+{
+       /* Read 64K from the appropriate offset of 'from'
+        * and if it looks a little like a 0.90 superblock,
+        * write it to the same offset of 'to'
+        */
+       void *buf;
+       unsigned long long dsize, offset;
+       const int bufsize = 64*1024;
+       mdp_super_t *super;
+
+       if (posix_memalign(&buf, 4096, bufsize) != 0)
+               return 1;
+
+       if (!get_dev_size(from, NULL, &dsize))
+               goto err;
+
+       if (dsize < MD_RESERVED_SECTORS*512)
+               goto err;
+
+       offset = MD_NEW_SIZE_SECTORS(dsize>>9);
+
+       offset *= 512;
+
+       if (lseek64(from, offset, 0) < 0LL)
+               goto err;
+       if (read(from, buf, bufsize) != bufsize)
+               goto err;
+
+       if (lseek64(to, offset, 0) < 0LL)
+               goto err;
+       super = buf;
+       if (super->md_magic != MD_SB_MAGIC ||
+           super->major_version != 0 ||
+           calc_sb0_csum(super) != super->sb_csum)
+               goto err;
+       if (write(to, buf, bufsize) != bufsize)
+               goto err;
+       free(buf);
+       return 0;
+err:
+       free(buf);
+       return 1;
+}
+
 static void detail_super0(struct supertype *st, char *homehost)
 {
        mdp_super_t *sb = st->sb;
@@ -1201,6 +1245,7 @@ struct superswitch super0 = {
        .write_init_super = write_init_super0,
        .validate_geometry = validate_geometry0,
        .add_to_super = add_to_super0,
+       .copy_metadata = copy_metadata0,
 #endif
        .match_home = match_home0,
        .uuid_from_super = uuid_from_super0,
index 92e51f7c4560c6573d911c1a55f529651e763a80..89f441fc519cb44fe28fc3e5dd27384f9090c91d 100644 (file)
--- a/super1.c
+++ b/super1.c
@@ -597,6 +597,143 @@ static void export_examine_super1(struct supertype *st)
               (unsigned long long)__le64_to_cpu(sb->events));
 }
 
+static int copy_metadata1(struct supertype *st, int from, int to)
+{
+       /* Read superblock.  If it looks good, write it out.
+        * Then if a bitmap is present, copy that.
+        * And if a bad-block-list is present, copy that too.
+        */
+       void *buf;
+       unsigned long long dsize, sb_offset;
+       const int bufsize = 4*1024;
+       struct mdp_superblock_1 super, *sb;
+
+       if (posix_memalign(&buf, 4096, bufsize) != 0)
+               return 1;
+
+       if (!get_dev_size(from, NULL, &dsize))
+               goto err;
+
+       dsize >>= 9;
+       if (dsize < 24)
+               goto err;
+       switch(st->minor_version) {
+       case 0:
+               sb_offset = dsize;
+               sb_offset -= 8*2;
+               sb_offset &= ~(4*2-1);
+               break;
+       case 1:
+               sb_offset = 0;
+               break;
+       case 2:
+               sb_offset = 4*2;
+               break;
+       default:
+               goto err;
+       }
+
+       if (lseek64(from, sb_offset << 9, 0) < 0LL)
+               goto err;
+       if (read(from, buf, bufsize) != bufsize)
+               goto err;
+
+       sb = buf;
+       super = *sb; // save most of sb for when we reuse buf
+
+       if (__le32_to_cpu(super.magic) != MD_SB_MAGIC ||
+           __le32_to_cpu(super.major_version) != 1 ||
+           __le64_to_cpu(super.super_offset) != sb_offset ||
+           calc_sb_1_csum(sb) != super.sb_csum)
+               goto err;
+
+       if (lseek64(to, sb_offset << 9, 0) < 0LL)
+               goto err;
+       if (write(to, buf, bufsize) != bufsize)
+               goto err;
+
+       if (super.feature_map & __le32_to_cpu(MD_FEATURE_BITMAP_OFFSET)) {
+               unsigned long long bitmap_offset = sb_offset;
+               int bytes = 4096; // just an estimate.
+               int written = 0;
+               struct align_fd afrom, ato;
+
+               init_afd(&afrom, from);
+               init_afd(&ato, to);
+
+               bitmap_offset += (int32_t)__le32_to_cpu(super.bitmap_offset);
+
+               if (lseek64(from, bitmap_offset<<9, 0) < 0)
+                       goto err;
+               if (lseek64(to, bitmap_offset<<9, 0) < 0)
+                       goto err;
+
+               for (written = 0; written < bytes ; ) {
+                       int n = bytes - written;
+                       if (n > 4096)
+                               n = 4096;
+                       if (aread(&afrom, buf, n) != n)
+                               goto err;
+                       if (written == 0) {
+                               /* have the header, can calculate
+                                * correct bitmap bytes */
+                               bitmap_super_t *bms;
+                               int bits;
+                               bms = (void*)buf;
+                               bits = __le64_to_cpu(bms->sync_size) / (__le32_to_cpu(bms->chunksize)>>9);
+                               bytes = (bits+7) >> 3;
+                               bytes += sizeof(bitmap_super_t);
+                               bytes = ROUND_UP(bytes, 512);
+                               if (n > bytes)
+                                       n =  bytes;
+                       }
+                       if (awrite(&ato, buf, n) != n)
+                               goto err;
+                       written += n;
+               }
+       }
+
+       if (super.bblog_size != 0 &&
+           __le32_to_cpu(super.bblog_size) <= 100 &&
+           super.bblog_offset != 0 &&
+           (super.feature_map & __le32_to_cpu(MD_FEATURE_BAD_BLOCKS))) {
+               /* There is a bad block log */
+               unsigned long long bb_offset = sb_offset;
+               int bytes = __le32_to_cpu(super.bblog_size) * 512;
+               int written = 0;
+               struct align_fd afrom, ato;
+
+               init_afd(&afrom, from);
+               init_afd(&ato, to);
+
+               bb_offset += (int32_t)__le32_to_cpu(super.bblog_offset);
+
+               if (lseek64(from, bb_offset<<9, 0) < 0)
+                       goto err;
+               if (lseek64(to, bb_offset<<9, 0) < 0)
+                       goto err;
+
+               for (written = 0; written < bytes ; ) {
+                       int n = bytes - written;
+                       if (n > 4096)
+                               n = 4096;
+                       if (aread(&afrom, buf, n) != n)
+                               goto err;
+
+                       if (awrite(&ato, buf, n) != n)
+                               goto err;
+                       written += n;
+               }
+       }
+
+       free(buf);
+       return 0;
+
+err:
+       free(buf);
+       return 1;
+}
+
 static void detail_super1(struct supertype *st, char *homehost)
 {
        struct mdp_superblock_1 *sb = st->sb;
@@ -2109,6 +2246,7 @@ struct superswitch super1 = {
        .validate_geometry = validate_geometry1,
        .add_to_super = add_to_super1,
        .examine_badblocks = examine_badblocks_super1,
+       .copy_metadata = copy_metadata1,
 #endif
        .match_home = match_home1,
        .uuid_from_super = uuid_from_super1,