]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - super-ddf.c
Incremental: improve support for "DEVICE" based restriction in mdadm.conf
[thirdparty/mdadm.git] / super-ddf.c
index b7c614298bbbcd4d5a9798253009b7b9204db22b..400088eb3a1fadc7707fd27e1f739b2a99f28fd2 100644 (file)
@@ -47,6 +47,10 @@ unsigned long crc32(
 #define DDF_NOTFOUND (~0U)
 #define DDF_CONTAINER (DDF_NOTFOUND-1)
 
+/* Default for safe_mode_delay. Same value as for IMSM.
+ */
+static const int DDF_SAFE_MODE_DELAY = 4000;
+
 /* The DDF metadata handling.
  * DDF metadata lives at the end of the device.
  * The last 512 byte block provides an 'anchor' which is used to locate
@@ -439,6 +443,7 @@ struct ddf_super {
        struct ddf_header *active;
        struct phys_disk        *phys;
        struct virtual_disk     *virt;
+       char                    *conf;
        int pdsize, vdsize;
        unsigned int max_part, mppe, conf_rec_len;
        int currentdev;
@@ -791,11 +796,7 @@ static void *load_section(int fd, struct ddf_super *super, void *buf,
 
        if (len > 1024)
                return NULL;
-       if (buf) {
-               /* All pre-allocated sections are a single block */
-               if (len != 1)
-                       return NULL;
-       } else if (posix_memalign(&buf, 512, len<<9) != 0)
+       if (!buf && posix_memalign(&buf, 512, len<<9) != 0)
                buf = NULL;
 
        if (!buf)
@@ -879,7 +880,8 @@ static int load_ddf_headers(int fd, struct ddf_super *super, char *devname)
                        super->primary.openflag && !super->secondary.openflag)
                        )
                        super->active = &super->secondary;
-       } else if (devname)
+       } else if (devname &&
+                  be64_to_cpu(super->anchor.secondary_lba) != ~(__u64)0)
                pr_err("Failed to load secondary DDF header on %s\n",
                       devname);
        if (super->active == NULL)
@@ -1031,11 +1033,11 @@ static int load_ddf_local(int fd, struct ddf_super *super,
         * the conflist
         */
 
-       conf = load_section(fd, super, NULL,
+       conf = load_section(fd, super, super->conf,
                            super->active->config_section_offset,
                            super->active->config_section_length,
                            0);
-
+       super->conf = conf;
        vnum = 0;
        for (confsec = 0;
             confsec < be32_to_cpu(super->active->config_section_length);
@@ -1103,7 +1105,6 @@ static int load_ddf_local(int fd, struct ddf_super *super,
                if (i < max_virt_disks)
                        vcl->vcnum = i;
        }
-       free(conf);
 
        return 0;
 }
@@ -1201,6 +1202,7 @@ static void free_super_ddf(struct supertype *st)
                return;
        free(ddf->phys);
        free(ddf->virt);
+       free(ddf->conf);
        while (ddf->conflist) {
                struct vcl *v = ddf->conflist;
                ddf->conflist = v->next;
@@ -1517,6 +1519,7 @@ static void getinfo_super_ddf(struct supertype *st, struct mdinfo *info, char *m
 
 static void uuid_from_ddf_guid(const char *guid, int uuid[4]);
 static void uuid_from_super_ddf(struct supertype *st, int uuid[4]);
+static void _ddf_array_name(char *name, const struct ddf_super *ddf, int i);
 
 static unsigned int get_vd_num_of_subarray(struct supertype *st)
 {
@@ -1576,13 +1579,17 @@ static void brief_examine_subarrays_ddf(struct supertype *st, int verbose)
                struct virtual_entry *ve = &ddf->virt->entries[i];
                struct vcl vcl;
                char nbuf1[64];
+               char namebuf[17];
                if (all_ff(ve->guid))
                        continue;
                memcpy(vcl.conf.guid, ve->guid, DDF_GUID_LEN);
                ddf->currentconf =&vcl;
+               vcl.vcnum = i;
                uuid_from_super_ddf(st, info.uuid);
                fname_from_uuid(st, &info, nbuf1, ':');
-               printf("ARRAY container=%s member=%d UUID=%s\n",
+               _ddf_array_name(namebuf, ddf, i);
+               printf("ARRAY%s%s container=%s member=%d UUID=%s\n",
+                      namebuf[0] == '\0' ? "" : " /dev/md/", namebuf,
                       nbuf+5, i, nbuf1+5);
        }
 }
@@ -1596,6 +1603,8 @@ static void export_examine_super_ddf(struct supertype *st)
        printf("MD_METADATA=ddf\n");
        printf("MD_LEVEL=container\n");
        printf("MD_UUID=%s\n", nbuf+5);
+       printf("MD_DEVICES=%u\n",
+               be16_to_cpu(((struct ddf_super *)st->sb)->phys->used_pdes));
 }
 
 static int copy_metadata_ddf(struct supertype *st, int from, int to)
@@ -1670,6 +1679,51 @@ static void detail_super_ddf(struct supertype *st, char *homehost)
         */
 }
 
+static const char *vendors_with_variable_volume_UUID[] = {
+       "LSI      ",
+};
+
+static int volume_id_is_reliable(const struct ddf_super *ddf)
+{
+       int n = ARRAY_SIZE(vendors_with_variable_volume_UUID);
+       int i;
+       for (i = 0; i < n; i++)
+               if (!memcmp(ddf->controller.guid,
+                       vendors_with_variable_volume_UUID[i], 8))
+               return 0;
+       return 1;
+}
+
+static void uuid_of_ddf_subarray(const struct ddf_super *ddf,
+                                unsigned int vcnum, int uuid[4])
+{
+       char buf[DDF_GUID_LEN+18], sha[20], *p;
+       struct sha1_ctx ctx;
+       if (volume_id_is_reliable(ddf)) {
+               uuid_from_ddf_guid(ddf->virt->entries[vcnum].guid, uuid);
+               return;
+       }
+       /*
+        * Some fake RAID BIOSes (in particular, LSI ones) change the
+        * VD GUID at every boot. These GUIDs are not suitable for
+        * identifying an array. Luckily the header GUID appears to
+        * remain constant.
+        * We construct a pseudo-UUID from the header GUID and those
+        * properties of the subarray that we expect to remain constant.
+        */
+       memset(buf, 0, sizeof(buf));
+       p = buf;
+       memcpy(p, ddf->anchor.guid, DDF_GUID_LEN);
+       p += DDF_GUID_LEN;
+       memcpy(p, ddf->virt->entries[vcnum].name, 16);
+       p += 16;
+       *((__u16 *) p) = vcnum;
+       sha1_init_ctx(&ctx);
+       sha1_process_bytes(buf, sizeof(buf), &ctx);
+       sha1_finish_ctx(&ctx, sha);
+       memcpy(uuid, sha, 4*4);
+}
+
 static void brief_detail_super_ddf(struct supertype *st)
 {
        struct mdinfo info;
@@ -1681,7 +1735,7 @@ static void brief_detail_super_ddf(struct supertype *st)
        else if (vcnum == DDF_NOTFOUND)
                return;
        else
-               uuid_from_ddf_guid(ddf->virt->entries[vcnum].guid, info.uuid);
+               uuid_of_ddf_subarray(ddf, vcnum, info.uuid);
        fname_from_uuid(st, &info, nbuf,':');
        printf(" UUID=%s", nbuf + 5);
 }
@@ -1824,13 +1878,11 @@ static void uuid_from_super_ddf(struct supertype *st, int uuid[4])
         */
        struct ddf_super *ddf = st->sb;
        struct vcl *vcl = ddf->currentconf;
-       char *guid;
 
        if (vcl)
-               guid = vcl->conf.guid;
+               uuid_of_ddf_subarray(ddf, vcl->vcnum, uuid);
        else
-               guid = ddf->anchor.guid;
-       uuid_from_ddf_guid(guid, uuid);
+               uuid_from_ddf_guid(ddf->anchor.guid, uuid);
 }
 
 static void getinfo_super_ddf_bvd(struct supertype *st, struct mdinfo *info, char *map);
@@ -1902,6 +1954,17 @@ static void getinfo_super_ddf(struct supertype *st, struct mdinfo *info, char *m
        }
 }
 
+/* size of name must be at least 17 bytes! */
+static void _ddf_array_name(char *name, const struct ddf_super *ddf, int i)
+{
+       int j;
+       memcpy(name, ddf->virt->entries[i].name, 16);
+       name[16] = 0;
+       for(j = 0; j < 16; j++)
+               if (name[j] == ' ')
+                       name[j] = 0;
+}
+
 static void getinfo_super_ddf_bvd(struct supertype *st, struct mdinfo *info, char *map)
 {
        struct ddf_super *ddf = st->sb;
@@ -1977,13 +2040,9 @@ static void getinfo_super_ddf_bvd(struct supertype *st, struct mdinfo *info, cha
        sprintf(info->text_version, "/%s/%d",
                st->container_devnm,
                info->container_member);
-       info->safe_mode_delay = 200;
+       info->safe_mode_delay = DDF_SAFE_MODE_DELAY;
 
-       memcpy(info->name, ddf->virt->entries[info->container_member].name, 16);
-       info->name[16]=0;
-       for(j=0; j<16; j++)
-               if (info->name[j] == ' ')
-                       info->name[j] = 0;
+       _ddf_array_name(info->name, ddf, info->container_member);
 
        if (map)
                for (j = 0; j < map_disks; j++) {
@@ -2644,6 +2703,31 @@ static unsigned int find_unused_pde(const struct ddf_super *ddf)
        return DDF_NOTFOUND;
 }
 
+static void _set_config_size(struct phys_disk_entry *pde, const struct dl *dl)
+{
+       __u64 cfs, t;
+       cfs = min(dl->size - 32*1024*2ULL, be64_to_cpu(dl->primary_lba));
+       t = be64_to_cpu(dl->secondary_lba);
+       if (t != ~(__u64)0)
+               cfs = min(cfs, t);
+       /*
+        * Some vendor DDF structures interpret workspace_lba
+        * very differently then us. Make a sanity check on the value.
+        */
+       t = be64_to_cpu(dl->workspace_lba);
+       if (t < cfs) {
+               __u64 wsp = cfs - t;
+               if (wsp > 1024*1024*2ULL && wsp > dl->size / 16) {
+                       pr_err("%s: %x:%x: workspace size 0x%llx too big, ignoring\n",
+                              __func__, dl->major, dl->minor, wsp);
+               } else
+                       cfs = t;
+       }
+       pde->config_size = cpu_to_be64(cfs);
+       dprintf("%s: %x:%x config_size %llx, DDF structure is %llx blocks\n",
+               __func__, dl->major, dl->minor, cfs, dl->size-cfs);
+}
+
 /* add a device to a container, either while creating it or while
  * expanding a pre-existing container
  */
@@ -2763,8 +2847,10 @@ static int add_to_super_ddf(struct supertype *st,
        } while (0)
        __calc_lba(dd, ddf->dlist, workspace_lba, 32);
        __calc_lba(dd, ddf->dlist, primary_lba, 16);
-       __calc_lba(dd, ddf->dlist, secondary_lba, 32);
-       pde->config_size = dd->workspace_lba;
+       if (ddf->dlist == NULL ||
+           be64_to_cpu(ddf->dlist->secondary_lba) != ~(__u64)0)
+               __calc_lba(dd, ddf->dlist, secondary_lba, 32);
+       _set_config_size(pde, dd);
 
        sprintf(pde->path, "%17.17s","Information: nil") ;
        memset(pde->pad, 0xff, 6);
@@ -2823,21 +2909,13 @@ static int remove_from_super_ddf(struct supertype *st, mdu_disk_info_t *dk)
  */
 #define NULL_CONF_SZ   4096
 
-static char *null_aligned;
-static int __write_ddf_structure(struct dl *d, struct ddf_super *ddf, __u8 type,
-                                int update)
+static int __write_ddf_structure(struct dl *d, struct ddf_super *ddf, __u8 type)
 {
        unsigned long long sector;
        struct ddf_header *header;
-       int fd, i, n_config, conf_size;
+       int fd, i, n_config, conf_size, buf_size;
        int ret = 0;
-
-       if (null_aligned == NULL) {
-               if (posix_memalign((void **)&null_aligned, 4096, NULL_CONF_SZ)
-                   != 0)
-                       return 0;
-               memset(null_aligned, 0xff, NULL_CONF_SZ);
-       }
+       char *conf;
 
        fd = d->fd;
 
@@ -2853,6 +2931,8 @@ static int __write_ddf_structure(struct dl *d, struct ddf_super *ddf, __u8 type,
        default:
                return 0;
        }
+       if (sector == ~(__u64)0)
+               return 0;
 
        header->type = type;
        header->openflag = 1;
@@ -2876,6 +2956,13 @@ static int __write_ddf_structure(struct dl *d, struct ddf_super *ddf, __u8 type,
        /* Now write lots of config records. */
        n_config = ddf->max_part;
        conf_size = ddf->conf_rec_len * 512;
+       conf = ddf->conf;
+       buf_size = conf_size * (n_config + 1);
+       if (!conf) {
+               if (posix_memalign((void**)&conf, 512, buf_size) != 0)
+                       goto out;
+               ddf->conf = conf;
+       }
        for (i = 0 ; i <= n_config ; i++) {
                struct vcl *c;
                struct vd_config *vdc = NULL;
@@ -2900,21 +2987,11 @@ static int __write_ddf_structure(struct dl *d, struct ddf_super *ddf, __u8 type,
                                vdc->sec_elmnt_seq);
                        vdc->seqnum = header->seq;
                        vdc->crc = calc_crc(vdc, conf_size);
-                       if (write(fd, vdc, conf_size) < 0)
-                               break;
-               } else if (!update) {
-                       unsigned int togo = conf_size;
-                       while (togo > NULL_CONF_SZ) {
-                               if (write(fd, null_aligned, NULL_CONF_SZ) < 0)
-                                       break;
-                               togo -= NULL_CONF_SZ;
-                       }
-                       if (write(fd, null_aligned, togo) < 0)
-                               break;
+                       memcpy(conf + i*conf_size, vdc, conf_size);
                } else
-                       lseek(fd, conf_size, SEEK_CUR);
+                       memset(conf + i*conf_size, 0xff, conf_size);
        }
-       if (i <= n_config)
+       if (write(fd, conf, buf_size) != buf_size)
                goto out;
 
        d->disk.crc = calc_crc(&d->disk, 512);
@@ -2933,8 +3010,7 @@ out:
        return ret;
 }
 
-static int _write_super_to_disk(struct ddf_super *ddf, struct dl *d,
-                               int update)
+static int _write_super_to_disk(struct ddf_super *ddf, struct dl *d)
 {
        unsigned long long size;
        int fd = d->fd;
@@ -2972,10 +3048,10 @@ static int _write_super_to_disk(struct ddf_super *ddf, struct dl *d,
        ddf->anchor.seq = cpu_to_be32(0xFFFFFFFF); /* no sequencing in anchor */
        ddf->anchor.crc = calc_crc(&ddf->anchor, 512);
 
-       if (!__write_ddf_structure(d, ddf, DDF_HEADER_PRIMARY, update))
+       if (!__write_ddf_structure(d, ddf, DDF_HEADER_PRIMARY))
                return 0;
 
-       if (!__write_ddf_structure(d, ddf, DDF_HEADER_SECONDARY, update))
+       if (!__write_ddf_structure(d, ddf, DDF_HEADER_SECONDARY))
                return 0;
 
        lseek64(fd, (size-1)*512, SEEK_SET);
@@ -2986,7 +3062,7 @@ static int _write_super_to_disk(struct ddf_super *ddf, struct dl *d,
 }
 
 #ifndef MDASSEMBLE
-static int __write_init_super_ddf(struct supertype *st, int update)
+static int __write_init_super_ddf(struct supertype *st)
 {
        struct ddf_super *ddf = st->sb;
        struct dl *d;
@@ -3000,7 +3076,7 @@ static int __write_init_super_ddf(struct supertype *st, int update)
         */
        for (d = ddf->dlist; d; d=d->next) {
                attempts++;
-               successes += _write_super_to_disk(ddf, d, update);
+               successes += _write_super_to_disk(ddf, d);
        }
 
        return attempts != successes;
@@ -3061,7 +3137,7 @@ static int write_init_super_ddf(struct supertype *st)
                if (!currentconf)
                        for (d = ddf->dlist; d; d=d->next)
                                while (Kill(d->devname, NULL, 0, -1, 1) == 0);
-               return __write_init_super_ddf(st, 0);
+               return __write_init_super_ddf(st);
        }
 }
 
@@ -3656,7 +3732,6 @@ static struct mdinfo *container_content_ddf(struct supertype *st, char *subarray
        for (vc = ddf->conflist ; vc ; vc=vc->next)
        {
                unsigned int i;
-               unsigned int j;
                struct mdinfo *this;
                char *ep;
                __u32 *cptr;
@@ -3681,6 +3756,7 @@ static struct mdinfo *container_content_ddf(struct supertype *st, char *subarray
                this->array.md_minor      = -1;
                this->array.major_version = -1;
                this->array.minor_version = -2;
+               this->safe_mode_delay = DDF_SAFE_MODE_DELAY;
                cptr = (__u32 *)(vc->conf.guid + 16);
                this->array.ctime         = DECADE + __be32_to_cpu(*cptr);
                this->array.utime         = DECADE +
@@ -3697,12 +3773,7 @@ static struct mdinfo *container_content_ddf(struct supertype *st, char *subarray
                        this->array.state = 1;
                        this->resync_start = MaxSector;
                }
-               memcpy(this->name, ddf->virt->entries[i].name, 16);
-               this->name[16]=0;
-               for(j=0; j<16; j++)
-                       if (this->name[j] == ' ')
-                               this->name[j] = 0;
-
+               _ddf_array_name(this->name, ddf, i);
                memset(this->uuid, 0, sizeof(this->uuid));
                this->component_size = be64_to_cpu(vc->conf.blocks);
                this->array.size = this->component_size / 2;
@@ -3805,7 +3876,7 @@ static int store_super_ddf(struct supertype *st, int fd)
                }
                ofd = dl->fd;
                dl->fd = fd;
-               ret = (_write_super_to_disk(ddf, dl, 0) != 1);
+               ret = (_write_super_to_disk(ddf, dl) != 1);
                dl->fd = ofd;
                return ret;
        }
@@ -3846,10 +3917,10 @@ static int compare_super_ddf(struct supertype *st, struct supertype *tst)
        if (memcmp(first->anchor.guid, second->anchor.guid, DDF_GUID_LEN) != 0)
                return 2;
 
-       if (!be32_eq(first->anchor.seq, second->anchor.seq)) {
-               dprintf("%s: sequence number mismatch %u/%u\n", __func__,
-                       be32_to_cpu(first->anchor.seq),
-                       be32_to_cpu(second->anchor.seq));
+       if (!be32_eq(first->active->seq, second->active->seq)) {
+               dprintf("%s: sequence number mismatch %u<->%u\n", __func__,
+                       be32_to_cpu(first->active->seq),
+                       be32_to_cpu(second->active->seq));
                return 3;
        }
        if (first->max_part != second->max_part ||
@@ -4000,11 +4071,38 @@ static int ddf_open_new(struct supertype *c, struct active_array *a, char *inst)
 {
        struct ddf_super *ddf = c->sb;
        int n = atoi(inst);
+       struct mdinfo *dev;
+       struct dl *dl;
+       static const char faulty[] = "faulty";
+
        if (all_ff(ddf->virt->entries[n].guid)) {
                pr_err("%s: subarray %d doesn't exist\n", __func__, n);
                return -ENODEV;
        }
-       dprintf("ddf: open_new %d\n", n);
+       dprintf("%s: new subarray %d, GUID: %s\n", __func__, n,
+               guid_str(ddf->virt->entries[n].guid));
+       for (dev = a->info.devs; dev; dev = dev->next) {
+               for (dl = ddf->dlist; dl; dl = dl->next)
+                       if (dl->major == dev->disk.major &&
+                           dl->minor == dev->disk.minor)
+                               break;
+               if (!dl) {
+                       pr_err("%s: device %d/%d of subarray %d not found in meta data\n",
+                               __func__, dev->disk.major, dev->disk.minor, n);
+                       return -1;
+               }
+               if ((be16_to_cpu(ddf->phys->entries[dl->pdnum].state) &
+                       (DDF_Online|DDF_Missing|DDF_Failed)) != DDF_Online) {
+                       pr_err("%s: new subarray %d contains broken device %d/%d (%02x)\n",
+                               __func__, n, dl->major, dl->minor,
+                               be16_to_cpu(
+                                       ddf->phys->entries[dl->pdnum].state));
+                       if (write(dev->state_fd, faulty, sizeof(faulty)-1) !=
+                           sizeof(faulty) - 1)
+                               pr_err("Write to state_fd failed\n");
+                       dev->curr_state = DS_FAULTY;
+               }
+       }
        a->info.container_member = n;
        return 0;
 }
@@ -4261,7 +4359,7 @@ static void ddf_sync_metadata(struct supertype *st)
        if (!ddf->updates_pending)
                return;
        ddf->updates_pending = 0;
-       __write_init_super_ddf(st, 1);
+       __write_init_super_ddf(st);
        dprintf("ddf: sync_metadata\n");
 }
 
@@ -4947,7 +5045,7 @@ static struct mdinfo *ddf_activate_spare(struct active_array *a,
         * Create a metadata_update record to update the
         * phys_refnum and lba_offset values
         */
-       vc = find_vdcr(ddf, a->info.container_member, di->disk.raid_disk,
+       vc = find_vdcr(ddf, a->info.container_member, rv->disk.raid_disk,
                       &n_bvd, &vcl);
        if (vc == NULL)
                return NULL;