/* * mdadm - manage Linux "md" devices aka RAID arrays. * * Copyright (C) 2013 Neil Brown * * 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: */ #include "mdadm.h" #include 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; int ret = 0; 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); close(fl); ret = fstat(fd, &dstb); close(fd); if (ret) { unlink(fname); free(fname); return 1; } 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 (stat(fname, &stb) != 0) { pr_err("Could not stat %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); close(fl); close(fd); free(fname); return 0; err: close(fd); close(fl); free(fname); return 1; }