]> git.ipfire.org Git - thirdparty/mdadm.git/blobdiff - sysfs.c
sysfs: avoid possible data corruption in sys_load.
[thirdparty/mdadm.git] / sysfs.c
diff --git a/sysfs.c b/sysfs.c
index 1dbc680f8bf0a4b51b9c4e0f6aec8ffd0fc5561c..8979ec4e23c80fd1812fe6b4f277cc4ad325d923 100644 (file)
--- a/sysfs.c
+++ b/sysfs.c
@@ -34,27 +34,46 @@ int load_sys(char *path, char *buf)
                return -1;
        n = read(fd, buf, 1024);
        close(fd);
-       if (n <=0 || n >= 1024)
+       if (n <0 || n >= 1024)
                return -1;
        buf[n] = 0;
-       if (buf[n-1] == '\n')
+       if (n && buf[n-1] == '\n')
                buf[n-1] = 0;
        return 0;
 }
 
-void sysfs_free(struct sysarray *sra)
+void sysfs_free(struct mdinfo *sra)
 {
-       if (!sra)
-               return;
-       while (sra->devs) {
-               struct sysdev *d = sra->devs;
-               sra->devs = d->next;
-               free(d);
+       while (sra) {
+               struct mdinfo *sra2 = sra->next;
+               while (sra->devs) {
+                       struct mdinfo *d = sra->devs;
+                       sra->devs = d->next;
+                       free(d);
+               }
+               free(sra);
+               sra = sra2;
        }
-       free(sra);
 }
 
-struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
+int sysfs_open(int devnum, char *devname, char *attr)
+{
+       char fname[50];
+       int fd;
+
+       sprintf(fname, "/sys/block/%s/md/", devnum2devname(devnum));
+       if (devname) {
+               strcat(fname, devname);
+               strcat(fname, "/");
+       }
+       strcat(fname, attr);
+       fd = open(fname, O_RDWR);
+       if (fd < 0 && errno == EACCES)
+               fd = open(fname, O_RDONLY);
+       return fd;
+}
+
+struct mdinfo *sysfs_read(int fd, int devnum, unsigned long options)
 {
        /* Longest possible name in sysfs, mounted at /sys, is
         *  /sys/block/md_dXXX/md/dev-XXXXX/block/dev
@@ -65,14 +84,15 @@ struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
        char buf[1024];
        char *base;
        char *dbase;
-       struct sysarray *sra;
-       struct sysdev *dev;
-       DIR *dir;
+       struct mdinfo *sra;
+       struct mdinfo *dev;
+       DIR *dir = NULL;
        struct dirent *de;
 
        sra = malloc(sizeof(*sra));
        if (sra == NULL)
                return sra;
+       sra->next = NULL;
 
        if (fd >= 0) {
                struct stat stb;
@@ -81,18 +101,18 @@ struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
                if (ioctl(fd, RAID_VERSION, &vers) != 0)
                        return NULL;
                if (major(stb.st_rdev)==9)
-                       sprintf(sra->name, "md%d", minor(stb.st_rdev));
+                       sprintf(sra->sys_name, "md%d", (int)minor(stb.st_rdev));
                else
-                       sprintf(sra->name, "md_d%d",
-                               minor(stb.st_rdev)>>MdpMinorShift);
+                       sprintf(sra->sys_name, "md_d%d",
+                               (int)minor(stb.st_rdev)>>MdpMinorShift);
        } else {
                if (devnum >= 0)
-                       sprintf(sra->name, "md%d", devnum);
+                       sprintf(sra->sys_name, "md%d", devnum);
                else
-                       sprintf(sra->name, "md_d%d",
+                       sprintf(sra->sys_name, "md_d%d",
                                -1-devnum);
        }
-       sprintf(fname, "/sys/block/%s/md/", sra->name);
+       sprintf(fname, "/sys/block/%s/md/", sra->sys_name);
        base = fname + strlen(fname);
 
        sra->devs = NULL;
@@ -100,23 +120,38 @@ struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
                strcpy(base, "metadata_version");
                if (load_sys(fname, buf))
                        goto abort;
-               if (strncmp(buf, "none", 4) == 0)
-                       sra->major_version = sra->minor_version = -1;
-               else
+               if (strncmp(buf, "none", 4) == 0) {
+                       sra->array.major_version =
+                               sra->array.minor_version = -1;
+                       strcpy(sra->text_version, "");
+               } else if (strncmp(buf, "external:", 9) == 0) {
+                       sra->array.major_version = -1;
+                       sra->array.minor_version = -2;
+                       strcpy(sra->text_version, buf+9);
+               } else {
                        sscanf(buf, "%d.%d",
-                              &sra->major_version, &sra->minor_version);
+                              &sra->array.major_version,
+                              &sra->array.minor_version);
+                       strcpy(sra->text_version, buf);
+               }
        }
        if (options & GET_LEVEL) {
                strcpy(base, "level");
                if (load_sys(fname, buf))
                        goto abort;
-               sra->level = map_name(pers, buf);
+               sra->array.level = map_name(pers, buf);
        }
        if (options & GET_LAYOUT) {
                strcpy(base, "layout");
                if (load_sys(fname, buf))
                        goto abort;
-               sra->layout = strtoul(buf, NULL, 0);
+               sra->array.layout = strtoul(buf, NULL, 0);
+       }
+       if (options & GET_DISKS) {
+               strcpy(base, "raid_disks");
+               if (load_sys(fname, buf))
+                       goto abort;
+               sra->array.raid_disks = strtoul(buf, NULL, 0);
        }
        if (options & GET_COMPONENT) {
                strcpy(base, "component_size");
@@ -130,7 +165,7 @@ struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
                strcpy(base, "chunk_size");
                if (load_sys(fname, buf))
                        goto abort;
-               sra->chunk = strtoul(buf, NULL, 0);
+               sra->array.chunk_size = strtoul(buf, NULL, 0);
        }
        if (options & GET_CACHE) {
                strcpy(base, "stripe_cache_size");
@@ -153,7 +188,7 @@ struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
        dir = opendir(fname);
        if (!dir)
                goto abort;
-       sra->spares = 0;
+       sra->array.spare_disks = 0;
 
        while ((de = readdir(dir)) != NULL) {
                char *ep;
@@ -169,43 +204,43 @@ struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
                        goto abort;
                dev->next = sra->devs;
                sra->devs = dev;
-               strcpy(dev->name, de->d_name);
+               strcpy(dev->sys_name, de->d_name);
 
                /* Always get slot, major, minor */
                strcpy(dbase, "slot");
                if (load_sys(fname, buf))
                        goto abort;
-               dev->role = strtoul(buf, &ep, 10);
-               if (*ep) dev->role = -1;
+               dev->disk.raid_disk = strtoul(buf, &ep, 10);
+               if (*ep) dev->disk.raid_disk = -1;
 
                strcpy(dbase, "block/dev");
                if (load_sys(fname, buf))
                        goto abort;
-               sscanf(buf, "%d:%d", &dev->major, &dev->minor);
+               sscanf(buf, "%d:%d", &dev->disk.major, &dev->disk.minor);
 
                if (options & GET_OFFSET) {
                        strcpy(dbase, "offset");
                        if (load_sys(fname, buf))
                                goto abort;
-                       dev->offset = strtoull(buf, NULL, 0);
+                       dev->data_offset = strtoull(buf, NULL, 0);
                }
                if (options & GET_SIZE) {
                        strcpy(dbase, "size");
                        if (load_sys(fname, buf))
                                goto abort;
-                       dev->size = strtoull(buf, NULL, 0);
+                       dev->component_size = strtoull(buf, NULL, 0) * 2;
                }
                if (options & GET_STATE) {
-                       dev->state = 0;
+                       dev->disk.state = 0;
                        strcpy(dbase, "state");
                        if (load_sys(fname, buf))
                                goto abort;
                        if (strstr(buf, "in_sync"))
-                               dev->state |= (1<<MD_DISK_SYNC);
+                               dev->disk.state |= (1<<MD_DISK_SYNC);
                        if (strstr(buf, "faulty"))
-                               dev->state |= (1<<MD_DISK_FAULTY);
-                       if (dev->state == 0)
-                               sra->spares++;
+                               dev->disk.state |= (1<<MD_DISK_FAULTY);
+                       if (dev->disk.state == 0)
+                               sra->array.spare_disks++;
                }
                if (options & GET_ERROR) {
                        strcpy(buf, "errors");
@@ -214,9 +249,12 @@ struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
                        dev->errors = strtoul(buf, NULL, 0);
                }
        }
+       closedir(dir);
        return sra;
 
  abort:
+       if (dir)
+               closedir(dir);
        sysfs_free(sra);
        return NULL;
 }
@@ -236,10 +274,10 @@ unsigned long long get_component_size(int fd)
        if (fstat(fd, &stb)) return 0;
        if (major(stb.st_rdev) == 9)
                sprintf(fname, "/sys/block/md%d/md/component_size",
-                       minor(stb.st_rdev));
+                       (int)minor(stb.st_rdev));
        else
                sprintf(fname, "/sys/block/md_d%d/md/component_size",
-                       minor(stb.st_rdev)>>MdpMinorShift);
+                       (int)minor(stb.st_rdev)>>MdpMinorShift);
        fd = open(fname, O_RDONLY);
        if (fd < 0)
                return 0;
@@ -251,14 +289,15 @@ unsigned long long get_component_size(int fd)
        return strtoull(fname, NULL, 10) * 2;
 }
 
-int sysfs_set_str(struct sysarray *sra, struct sysdev *dev,
+int sysfs_set_str(struct mdinfo *sra, struct mdinfo *dev,
                  char *name, char *val)
 {
        char fname[50];
        int n;
        int fd;
+
        sprintf(fname, "/sys/block/%s/md/%s/%s",
-               sra->name, dev?dev->name:"", name);
+               sra->sys_name, dev?dev->sys_name:"", name);
        fd = open(fname, O_WRONLY);
        if (fd < 0)
                return -1;
@@ -269,7 +308,7 @@ int sysfs_set_str(struct sysarray *sra, struct sysdev *dev,
        return 0;
 }
 
-int sysfs_set_num(struct sysarray *sra, struct sysdev *dev,
+int sysfs_set_num(struct mdinfo *sra, struct mdinfo *dev,
                  char *name, unsigned long long val)
 {
        char valstr[50];
@@ -277,7 +316,7 @@ int sysfs_set_num(struct sysarray *sra, struct sysdev *dev,
        return sysfs_set_str(sra, dev, name, valstr);
 }
 
-int sysfs_get_ll(struct sysarray *sra, struct sysdev *dev,
+int sysfs_get_ll(struct mdinfo *sra, struct mdinfo *dev,
                       char *name, unsigned long long *val)
 {
        char fname[50];
@@ -286,7 +325,7 @@ int sysfs_get_ll(struct sysarray *sra, struct sysdev *dev,
        int fd;
        char *ep;
        sprintf(fname, "/sys/block/%s/md/%s/%s",
-               sra->name, dev?dev->name:"", name);
+               sra->sys_name, dev?dev->sys_name:"", name);
        fd = open(fname, O_RDONLY);
        if (fd < 0)
                return -1;
@@ -300,3 +339,238 @@ int sysfs_get_ll(struct sysarray *sra, struct sysdev *dev,
                return -1;
        return 0;
 }
+
+int sysfs_set_array(struct mdinfo *sra,
+                   struct mdinfo *info)
+{
+       int rv = 0;
+       sra->array = info->array;
+
+       if (info->array.level < 0)
+               return 0; /* FIXME */
+       rv |= sysfs_set_str(sra, NULL, "level",
+                           map_num(pers, info->array.level));
+       rv |= sysfs_set_num(sra, NULL, "raid_disks", info->array.raid_disks);
+       rv |= sysfs_set_num(sra, NULL, "chunk_size", info->array.chunk_size);
+       rv |= sysfs_set_num(sra, NULL, "layout", info->array.layout);
+       rv |= sysfs_set_num(sra, NULL, "component_size", info->component_size/2);
+       rv |= sysfs_set_num(sra, NULL, "resync_start", info->resync_start);
+       sra->array = info->array;
+       return rv;
+}
+
+int sysfs_add_disk(struct mdinfo *sra, struct mdinfo *sd)
+{
+       char dv[100];
+       char nm[100];
+       struct mdinfo *sd2;
+       char *dname;
+       int rv;
+
+       sprintf(dv, "%d:%d", sd->disk.major, sd->disk.minor);
+       rv = sysfs_set_str(sra, NULL, "new_dev", dv);
+       if (rv)
+               return rv;
+
+       memset(nm, 0, sizeof(nm));
+       sprintf(dv, "/sys/dev/block/%d:%d", sd->disk.major, sd->disk.minor);
+       rv = readlink(dv, nm, sizeof(nm));
+       if (rv <= 0)
+               return -1;
+       nm[rv] = '\0';
+       dname = strrchr(nm, '/');
+       if (dname) dname++;
+       strcpy(sd->sys_name, "dev-");
+       strcpy(sd->sys_name+4, dname);
+
+       rv = sysfs_set_num(sra, sd, "offset", sd->data_offset);
+       rv |= sysfs_set_num(sra, sd, "size", (sd->component_size+1) / 2);
+       if (sra->array.level != LEVEL_CONTAINER) {
+               rv |= sysfs_set_num(sra, sd, "slot", sd->disk.raid_disk);
+//             rv |= sysfs_set_str(sra, sd, "state", "in_sync");
+       }
+       if (! rv) {
+               sd2 = malloc(sizeof(*sd2));
+               *sd2 = *sd;
+               sd2->next = sra->devs;
+               sra->devs = sd2;
+       }
+       return rv;
+}
+
+int sysfs_disk_to_sg(int fd)
+{
+       /* from an open block device, try find and open its corresponding
+        * scsi_generic interface
+        */
+       struct stat st;
+       char path[256];
+       char sg_path[256];
+       char sg_major_minor[8];
+       char *c;
+       DIR *dir;
+       struct dirent *de;
+       int major, minor, rv;
+
+       if (fstat(fd, &st))
+               return -1;
+
+       snprintf(path, sizeof(path), "/sys/dev/block/%d:%d/device",
+                major(st.st_rdev), minor(st.st_rdev));
+
+       dir = opendir(path);
+       if (!dir)
+               return -1;
+
+       de = readdir(dir);
+       while (de) {
+               if (strncmp("scsi_generic:", de->d_name,
+                           strlen("scsi_generic:")) == 0)
+                       break;
+               de = readdir(dir);
+       }
+       closedir(dir);
+
+       if (!de)
+               return -1;
+
+       snprintf(sg_path, sizeof(sg_path), "%s/%s/dev", path, de->d_name);
+       fd = open(sg_path, O_RDONLY);
+       if (fd < 0)
+               return fd;
+
+       rv = read(fd, sg_major_minor, sizeof(sg_major_minor));
+       close(fd);
+       if (rv < 0)
+               return -1;
+       else
+               sg_major_minor[rv - 1] = '\0';
+
+       c = strchr(sg_major_minor, ':');
+       *c = '\0';
+       c++;
+       major = strtol(sg_major_minor, NULL, 10);
+       minor = strtol(c, NULL, 10);
+       snprintf(path, sizeof(path), "/dev/.tmp.md.%d:%d:%d",
+                (int) getpid(), major, minor);
+       if (mknod(path, S_IFCHR|0600, makedev(major, minor))==0) {
+                       fd = open(path, O_RDONLY);
+                       unlink(path);
+                       return fd;
+       }
+
+       return -1;
+}
+
+int sysfs_disk_to_scsi_id(int fd, __u32 *id)
+{
+       /* from an open block device, try to retrieve it scsi_id */
+       struct stat st;
+       char path[256];
+       char *c1, *c2;
+       DIR *dir;
+       struct dirent *de;
+
+       if (fstat(fd, &st))
+               return 1;
+
+       snprintf(path, sizeof(path), "/sys/dev/block/%d:%d/device",
+                major(st.st_rdev), minor(st.st_rdev));
+
+       dir = opendir(path);
+       if (!dir)
+               return 1;
+
+       de = readdir(dir);
+       while (de) {
+               if (strncmp("scsi_disk:", de->d_name,
+                           strlen("scsi_disk:")) == 0)
+                       break;
+               de = readdir(dir);
+       }
+       closedir(dir);
+
+       if (!de)
+               return 1;
+
+       c1 = strchr(de->d_name, ':');
+       c1++;
+       c2 = strchr(c1, ':');
+       *c2 = '\0';
+       *id = strtol(c1, NULL, 10) << 24; /* host */
+       c1 = c2 + 1;
+       c2 = strchr(c1, ':');
+       *c2 = '\0';
+       *id |= strtol(c1, NULL, 10) << 16; /* channel */
+       c1 = c2 + 1;
+       c2 = strchr(c1, ':');
+       *c2 = '\0';
+       *id |= strtol(c1, NULL, 10) << 8; /* lun */
+       c1 = c2 + 1;
+       *id |= strtol(c1, NULL, 10); /* id */
+
+       return 0;
+}
+
+
+int sysfs_unique_holder(int devnum, long rdev)
+{
+       /* Check that devnum is a holder of rdev,
+        * and is the only holder.
+        * we should be locked against races by
+        * an O_EXCL on devnum
+        */
+       DIR *dir;
+       struct dirent *de;
+       char dirname[100];
+       char l;
+       int found = 0;
+       sprintf(dirname, "/sys/dev/block/%d:%d/holders",
+               major(rdev), minor(rdev));
+       dir = opendir(dirname);
+       errno = ENOENT;
+       if (!dir)
+               return 0;
+       l = strlen(dirname);
+       while ((de = readdir(dir)) != NULL) {
+               char buf[10];
+               int n;
+               int mj, mn;
+               char c;
+               int fd;
+
+               if (de->d_ino == 0)
+                       continue;
+               if (de->d_name[0] == '.')
+                       continue;
+               strcpy(dirname+l, "/");
+               strcat(dirname+l, de->d_name);
+               strcat(dirname+l, "/dev");
+               fd = open(dirname, O_RDONLY);
+               if (fd < 0) {
+                       errno = ENOENT;
+                       break;
+               }
+               n = read(fd, buf, sizeof(buf)-1);
+               close(fd);
+               buf[n] = 0;
+               if (sscanf(buf, "%d:%d%c", &mj, &mn, &c) != 3 ||
+                   c != '\n') {
+                       errno = ENOENT;
+                       break;
+               }
+               if (mj != MD_MAJOR)
+                       mn = -1-(mn>>6);
+
+               if (devnum != mn) {
+                       errno = EEXIST;
+                       break;
+               }
+               found = 1;
+       }
+       closedir(dir);
+       if (de)
+               return 0;
+       else
+               return found;
+}