From dd0781e50555c32ff2f808ec46f4b03a5693ea47 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Fri, 4 Jun 2004 12:03:19 +0000 Subject: [PATCH] mdadm-1.6.0 --- ANNOUNCE-1.6.0 | 36 +++++++ Build.c | 4 +- ChangeLog | 27 ++++- Create.c | 3 +- Detail.c | 35 ++++--- Examine.c | 10 +- Manage.c | 21 ++++ Monitor.c | 17 ++-- ReadMe.c | 38 +++++-- TODO | 12 +++ config.c | 34 ++++++- mdadm.8 | 170 +++++++++++++++++++++++++++++-- mdadm.c | 266 ++++++++++++++++++++++++++++++++++++++++++++----- mdadm.conf.5 | 24 +++++ mdadm.h | 28 ++++-- mdadm.spec | 2 +- mdassemble | Bin 0 -> 62213 bytes mdstat.c | 27 ++++- t | 1 + util.c | 83 +++++++++++---- 20 files changed, 736 insertions(+), 102 deletions(-) create mode 100644 ANNOUNCE-1.6.0 create mode 100755 mdassemble create mode 100644 t diff --git a/ANNOUNCE-1.6.0 b/ANNOUNCE-1.6.0 new file mode 100644 index 00000000..4461fe31 --- /dev/null +++ b/ANNOUNCE-1.6.0 @@ -0,0 +1,36 @@ +Subject: ANNOUNCE: mdadm 1.6.0 - A tool for managing Soft RAID under Linux + + +I am pleased to announce the availability of + mdadm version 1.6.0 +It is available at + http://www.cse.unsw.edu.au/~neilb/source/mdadm/ +and + http://www.{countrycode}.kernel.org/pub/linux/utils/raid/mdadm/ + +as a source tar-ball and (at the first site) as an SRPM, and as an RPM for i386. + +mdadm is a tool for creating, managing and monitoring +device arrays using the "md" driver in Linux, also +known as Software RAID arrays. + +Release 1.6.0 adds: + - --grow which (in 2.6.7-rc1-mm1 and hopefully 2.6.8) allows raid1/4/5/6 + arrays to change the active size of the underlying devices, and allows + raid1 arrays to change the number of active drives. + - Allows --build to buld raid1 and multipath arrays. + - adds "degraded" and "recovering" as possibilities for the status line + in --detail + - fixes a bug in 1.5.0 which stopped resync status messages from being + generated in --monitor mode + - Further support for partitionable arrays included "--auto=" option + and "auto=" config file entry which instructs mdadm to create the necessary + device files after allocating an unused array number. + - assorted minor fixes and improvements. + +Development of mdadm is sponsored by CSE@UNSW: + The School of Computer Science and Engineering +at + The University of New South Wales + +NeilBrown 4 Jun 2004 diff --git a/Build.c b/Build.c index 0179807f..3e182f8c 100644 --- a/Build.c +++ b/Build.c @@ -35,7 +35,7 @@ int Build(char *mddev, int mdfd, int chunk, int level, int raiddisks, - mddev_dev_t devlist) + mddev_dev_t devlist, int assume_clean) { /* Build a linear or raid0 arrays without superblocks * We cannot really do any checks, we just do it. @@ -91,6 +91,8 @@ int Build(char *mddev, int mdfd, int chunk, int level, array.md_minor = MINOR(stb.st_rdev); array.not_persistent = 1; array.state = 0; /* not clean, but no errors */ + if (assume_clean) + array.state |= 1; array.active_disks = raiddisks; array.working_disks = raiddisks; array.spare_disks = 0; diff --git a/ChangeLog b/ChangeLog index 7c8fe054..a505a74f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,28 @@ -Changes Prior to this release +Changes Prior to 1.6.0 release + - Device name given in -Eb is determined by examining /dev rather + than assuming /dev/md%d + - Fix bug in --monitor where an array could be held open an so + could not be stopped without killing mdadm. + - Add --grow mode. Currently only --size and --raid-disks can be + changed. Both require kernel support which, at the time of + writing, is not in a release kernel yet. + - Don't print out "errors" or "no-errors" in -D and -E, as the bit + is never set or used. + - Use md event notification in 2.6.??? to make --monitor mode + respond instantly to events. + - Add --auto= option and auto= configfile entry to tell mdadm to + create device files as needed. This is particularly useful + with partitioned arrays where the major device number can change. + - When generating --brief listing, if the standard name doesn't + exist, search /dev for one rather than using a temp name. + - Allow --build to build raid1 and multipath arrays. + - Add "--assume-clean" for Create and Build, particularly for raid1 + Note: this is dangerous. Only use it if you are certain. + - Fix bug so that Rebuild status monitoring works again. + - Add "degraded" and "recovering" options to the "Status:" + entry for --detail + +Changes Prior to 1.5.0 release - new commands "mdassemble" which is a stripped-down equivalent of "mdadm -As", that can be compiled with dietlibc. Thanks to Luca Berra . @@ -323,4 +347,3 @@ Changes Prior to 0.5 release the --help output, is not wholy correct. After I get --follow working properly, I plan to revise the various documentation and/or the code to make sure the two match. - diff --git a/Create.c b/Create.c index a536a75b..fb9857b5 100644 --- a/Create.c +++ b/Create.c @@ -68,7 +68,7 @@ int Create(char *mddev, int mdfd, if (md_get_version(mdfd) < 9000) { - fprintf(stderr, Name ": Create requires md driver verison 0.90.0 or later\n"); + fprintf(stderr, Name ": Create requires md driver version 0.90.0 or later\n"); return 1; } if (level == UnSet) { @@ -351,6 +351,7 @@ int Create(char *mddev, int mdfd, if (ioctl(mdfd, RUN_ARRAY, ¶m)) { fprintf(stderr, Name ": RUN_ARRAY failed: %s\n", strerror(errno)); + Manage_runstop(mddev, mdfd, -1); return 1; } fprintf(stderr, Name ": array %s started.\n", mddev); diff --git a/Detail.c b/Detail.c index 419c45cb..5028ae29 100644 --- a/Detail.c +++ b/Detail.c @@ -46,6 +46,7 @@ int Detail(char *dev, int brief, int test) char *c; char *devices = NULL; int spares = 0; + struct stat stb; mdp_super_t super; int have_super = 0; @@ -79,6 +80,8 @@ int Detail(char *dev, int brief, int test) close(fd); return rv; } + if (fstat(fd, &stb) != 0 && !S_ISBLK(stb.st_mode)) + stb.st_rdev = 0; rv = 0; /* Ok, we have some info to print... */ c = map_num(pers, array.level); @@ -87,6 +90,15 @@ int Detail(char *dev, int brief, int test) else { unsigned long array_size; unsigned long long larray_size; + struct mdstat_ent *ms = mdstat_read(0); + struct mdstat_ent *e; + int devnum = array.md_minor; + if (MAJOR(stb.st_rdev) != MD_MAJOR) + devnum = -1 - devnum; + + for (e=ms; e; e=e->next) + if (e->devnum == devnum) + break; #ifdef BLKGETSIZE64 if (ioctl(fd, BLKGETSIZE64, &larray_size)==0) ; @@ -106,7 +118,7 @@ int Detail(char *dev, int brief, int test) printf(" Creation Time : %.24s\n", ctime(&atime)); printf(" Raid Level : %s\n", c?c:"-unknown-"); if (larray_size) - printf(" Array Size : %llu%s\n", (larray_size>>10), human_size(larray_size)); + printf(" Array Size : %llu%s\n", (larray_size>>10), human_size(larray_size)); if (array.level >= 1) printf(" Device Size : %d%s\n", array.size, human_size((long long)array.size<<10)); printf(" Raid Devices : %d\n", array.raid_disks); @@ -117,9 +129,10 @@ int Detail(char *dev, int brief, int test) printf("\n"); atime = array.utime; printf(" Update Time : %.24s\n", ctime(&atime)); - printf(" State : %s, %serrors\n", + printf(" State : %s%s%s\n", (array.state&(1<percent >= 0) ? ", recovering": ""); printf(" Active Devices : %d\n", array.active_disks); printf("Working Devices : %d\n", array.working_disks); printf(" Failed Devices : %d\n", array.failed_disks); @@ -142,17 +155,11 @@ int Detail(char *dev, int brief, int test) } printf("\n"); - { - struct mdstat_ent *ms = mdstat_read(); - struct mdstat_ent *e; - for (e=ms; e; e=e->next) - if (e->devnum == array.md_minor) { - if (e->percent >= 0) - printf(" Rebuild Status : %d%% complete\n\n", e->percent); - break; - } - free_mdstat(ms); - } + + if (e && e->percent >= 0) + printf(" Rebuild Status : %d%% complete\n\n", e->percent); + free_mdstat(ms); + printf(" Number Major Minor RaidDevice State\n"); } for (d= 0; dsuper.level); char *d; - printf("ARRAY /dev/md%d level=%s num-devices=%d UUID=", - ap->super.md_minor, c?c:"-unknown-", ap->super.raid_disks); + printf("ARRAY %s level=%s num-devices=%d UUID=", + get_md_name(ap->super.md_minor), + c?c:"-unknown-", ap->super.raid_disks); if (spares) printf(" spares=%d", spares); if (ap->super.minor_version >= 90) printf("%08x:%08x:%08x:%08x", ap->super.set_uuid0, ap->super.set_uuid1, diff --git a/Manage.c b/Manage.c index 0d8ad8d2..624c775d 100644 --- a/Manage.c +++ b/Manage.c @@ -114,6 +114,27 @@ int Manage_runstop(char *devname, int fd, int runstop) return 0; } +int Manage_resize(char *devname, int fd, long long size, int raid_disks) +{ + mdu_array_info_t info; + if (ioctl(fd, GET_ARRAY_INFO, &info) != 0) { + fprintf(stderr, Name ": Cannot get array information for %s: %s\n", + devname, strerror(errno)); + return 1; + } + if (size >= 0) + info.size = size; + if (raid_disks > 0) + info.raid_disks = raid_disks; + if (ioctl(fd, SET_ARRAY_INFO, &info) != 0) { + fprintf(stderr, Name ": Cannot set device size/shape for %s: %s\n", + devname, strerror(errno)); + return 1; + } + return 0; +} + + int Manage_subdevs(char *devname, int fd, mddev_dev_t devlist) { diff --git a/Monitor.c b/Monitor.c index 2d3693bd..021a9677 100644 --- a/Monitor.c +++ b/Monitor.c @@ -185,11 +185,11 @@ int Monitor(mddev_dev_t devlist, if (mdstat) free_mdstat(mdstat); - mdstat = mdstat_read(); + mdstat = mdstat_read(oneshot?0:1); for (st=statelist; st; st=st->next) { mdu_array_info_t array; - struct mdstat_ent *mse; + struct mdstat_ent *mse = NULL, *mse2; char *dev = st->devname; int fd; unsigned int i; @@ -228,16 +228,18 @@ int Monitor(mddev_dev_t devlist, struct stat stb; if (fstat(fd, &stb) == 0 && (S_IFMT&stb.st_mode)==S_IFBLK) { - if (MINOR(stb.st_rdev) == 9) + if (MAJOR(stb.st_rdev) == MD_MAJOR) st->devnum = MINOR(stb.st_rdev); else st->devnum = -1- (MINOR(stb.st_rdev)>>6); } } - for (mse = mdstat ; mse ; mse=mse->next) - if (mse->devnum == st->devnum) - mse->devnum = MAXINT; /* flag it as "used" */ + for (mse2 = mdstat ; mse2 ; mse2=mse2->next) + if (mse2->devnum == st->devnum) { + mse2->devnum = MAXINT; /* flag it as "used" */ + mse = mse2; + } if (st->utime == array.utime && st->failed == array.failed_disks && @@ -349,6 +351,7 @@ int Monitor(mddev_dev_t devlist, free(st); continue; } + close(fd); st->utime = 0; st->next = statelist; st->err = 1; @@ -414,7 +417,7 @@ int Monitor(mddev_dev_t devlist, if (oneshot) break; else - sleep(period); + mdstat_wait(period); } test = 0; } diff --git a/ReadMe.c b/ReadMe.c index 7cd82400..6ba33ba1 100644 --- a/ReadMe.c +++ b/ReadMe.c @@ -29,7 +29,7 @@ #include "mdadm.h" -char Version[] = Name " - v1.5.0 - 22 Jan 2004\n"; +char Version[] = Name " - v1.6.0 - 4 June 2004\n"; /* * File: ReadMe.c * @@ -58,7 +58,7 @@ char Version[] = Name " - v1.5.0 - 22 Jan 2004\n"; */ /* - * mdadm has 6 major modes of operation: + * mdadm has 7 major modes of operation: * 1/ Create * This mode is used to create a new array with a superblock * It can progress in several step create-add-add-run @@ -84,9 +84,13 @@ char Version[] = Name " - v1.5.0 - 22 Jan 2004\n"; * Also query will treat it as either * 6/ Monitor * This mode never exits but just monitors arrays and reports changes. + * 7/ Grow + * This mode allows for changing of key attributes of a raid array, such + * as size, number of devices, and possibly even layout. + * At the time if writing, there is only minimal support. */ -char short_options[]="-ABCDEFGQhVvbc:l:p:m:n:x:u:c:d:z:U:sarfRSow1t"; +char short_options[]="-ABCDEFGQhVvbc:l:p:m:n:x:u:c:d:z:U:sa::rfRSow1t"; struct option long_options[] = { {"manage", 0, 0, '@'}, {"misc", 0, 0, '#'}, @@ -96,7 +100,7 @@ struct option long_options[] = { {"detail", 0, 0, 'D'}, {"examine", 0, 0, 'E'}, {"follow", 0, 0, 'F'}, - {"grow", 0, 0, 'G'}, /* not yet implemented */ + {"grow", 0, 0, 'G'}, {"zero-superblock", 0, 0, 'K'}, /* deliberately no a short_option */ {"query", 0, 0, 'Q'}, @@ -119,7 +123,9 @@ struct option long_options[] = { {"raid-devices",1, 0, 'n'}, {"spare-disks",1,0, 'x'}, {"spare-devices",1,0, 'x'}, - {"size" ,1, 0, 'z'}, + {"size", 1, 0, 'z'}, + {"auto", 2, 0, 'a'}, /* also for --assemble */ + {"assume-clean",0,0, 3 }, /* For assemble */ {"uuid", 1, 0, 'u'}, @@ -213,6 +219,8 @@ char OptionHelp[] = " --size= -z : Size (in K) of each drive in RAID1/4/5/6 - optional\n" " --force -f : Honour devices as listed on command line. Don't\n" " : insert a missing drive for RAID5.\n" +" --auto(=p) -a : Automatically allocate new (partitioned) md array if needed.\n" +" --assume-clean : Assume the array is already in-sync. This is dangerous.\n" "\n" " For assemble:\n" " --uuid= -u : uuid of array to assemble. Devices which don't\n" @@ -223,6 +231,7 @@ char OptionHelp[] = " --scan -s : scan config file for missing information\n" " --force -f : Assemble the array even if some superblocks appear out-of-date\n" " --update= -U : Update superblock: one of sparc2.2, super-minor or summaries\n" +" --auto(=p) -a : Automatically allocate new (partitioned) md array if needed.\n" "\n" " For detail or examine:\n" " --brief -b : Just print device name and UUID\n" @@ -401,7 +410,7 @@ char Help_monitor[] = "If no mail address or program are specified, then mdadm reports all\n" "state changes to stdout.\n" "\n" -"Options that are valid with the monitor (--F --follow) mode are:\n" +"Options that are valid with the monitor (-F --follow) mode are:\n" " --mail= -m : Address to mail alerts of failure to\n" " --program= -p : Program to run when an event is detected\n" " --alert= : same as --program\n" @@ -413,6 +422,22 @@ char Help_monitor[] = " --test -t : Generate a TestMessage event against each array at startup\n" ; +char Help_grow[] = +"Usage: mdadm --grow device options\n" +"\n" +"This usage causes mdadm to attempt to reconfigure a running array.\n" +"This is only possibly if the kernel being used supports a particular\n" +"reconfiguration. This version only supports changing the number of\n" +"devices in a RAID1, and changing the active size of all devices in\n" +"a RAID1/4/5/6.\n" +"\n" +"Options that are valid with the grow (-F --grow) mode are:\n" +" --size= -z : Change the active size of devices in an array.\n" +" : This is useful if all devices have been replaced\n" +" : with larger devices.\n" +" --raid-disks= -n : Change the number of active devices in a RAID1\n" +" : array.\n" +; @@ -494,4 +519,5 @@ mapping_t modes[] = { { "manage", MANAGE}, { "misc", MISC}, { "monitor", MONITOR}, + { "grow", GROW}, }; diff --git a/TODO b/TODO index 4cfa1ebe..a282744a 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,15 @@ +2004-june-02 + * Don't print 'errors' flag, it is meaningless. DONE + * Handle new superblock format + * create device file on demand, particularly partitionable devices. DONE + BUT figure a way to create the partition devices. + auto=partN + * Use Event: interface to listen for events. DONE, untested + * Make sure mdadm -As can assemble multi-level RAIDs ok. + * --build to build raid1 or multipath arrays + clean or not ??? + +---------------------------------------------------------------------------- * mdadm --monitor to monitor failed multipath paths and re-instate them. * Maybe make "--help" fit in 80x24 and have a --long-help with more info. DONE diff --git a/config.c b/config.c index 067beb6c..1671d266 100644 --- a/config.c +++ b/config.c @@ -32,6 +32,7 @@ #include #include #include +#include /* * Read the config file @@ -230,6 +231,7 @@ void load_partitions(void) cdevlist = cd; } } + fclose(f); } @@ -272,6 +274,7 @@ void arrayline(char *line) mis.devices = NULL; mis.devname = NULL; mis.spare_group = NULL; + mis.autof = 0; for (w=dl_next(line); w!=line; w=dl_next(w)) { if (w[0] == '/') { @@ -326,13 +329,41 @@ void arrayline(char *line) } else if (strncasecmp(w, "spares=", 7) == 0 ) { /* for warning if not all spares present */ mis.spare_disks = atoi(w+7); + } else if (strncasecmp(w, "auto=", 5) == 0 ) { + /* whether to create device special files as needed */ + if (strcasecmp(w+5, "no")==0) + mis.autof = 0; + else if (strcasecmp(w+5,"yes")==0 || strcasecmp(w+5,"md")==0) + mis.autof = -1; + else { + /* There might be digits, and maybe a hypen, at the end */ + char *e = w+5 + strlen(w+5); + int num = 4; + int len; + while (e > w+5 && isdigit(e[-1])) + e--; + if (*e) { + num = atoi(e); + if (num <= 0) num = 1; + } + if (e > w+5 && e[-1] == '-') + e--; + len = e - (w+5); + if ((len == 3 && strncasecmp(w+5,"mdp",3)==0) || + (len == 1 && strncasecmp(w+5,"p",1)==0) || + (len >= 4 && strncasecmp(w+5,"part",4)==0)) + mis.autof = num; + else + fprintf(stderr, Name ": auto type of \"%s\" ignored for %s\n", + w+5, mis.devname?mis.devname:"unlabeled-array"); + } } else { fprintf(stderr, Name ": unrecognised word on ARRAY line: %s\n", w); } } if (mis.devname == NULL) - fprintf(stderr, Name ": ARRAY line with a device\n"); + fprintf(stderr, Name ": ARRAY line with no device\n"); else if (mis.uuid_set == 0 && mis.devices == NULL && mis.super_minor < 0) fprintf(stderr, Name ": ARRAY line %s has no identity information.\n", mis.devname); else { @@ -420,6 +451,7 @@ void load_conffile(char *conffile) free_line(line); } + fclose(f); /* printf("got file\n"); */ } diff --git a/mdadm.8 b/mdadm.8 index 38bcb737..6e20b7ea 100644 --- a/mdadm.8 +++ b/mdadm.8 @@ -1,5 +1,5 @@ .\" -*- nroff -*- -.TH MDADM 8 "" v1.5.0 +.TH MDADM 8 "" v1.6.0 .SH NAME mdadm \- manage MD devices .I aka @@ -76,7 +76,7 @@ configuration file, at all. It has a different configuration file with a different format and an different purpose. .SH MODES -mdadm has 6 major modes of operation: +mdadm has 7 major modes of operation: .TP .B Assemble Assemble the parts of a previously created @@ -114,6 +114,12 @@ only meaningful for raid1, 4, 5, 6 or multipath arrays as only these have interesting state. raid0 or linear never have missing, spare, or failed drives, so there is nothing to monitor. +.TP +.B "Grow" +Grow (or shrink) an array, or otherwise reshape it in some way. +Currently supported growth options including changing the active size +of componenet devices in RAID level 1/4/5/6 and changing the number of +active devices in RAID1. .SH OPTIONS @@ -152,6 +158,10 @@ Select .B Monitor mode. +.TP +.BR -G ", " --grow +Change the size or shape of an active array. + .TP .BR -h ", " --help Display help message or, after above option, mode specific help @@ -261,13 +271,17 @@ Specify the number of active devices in the array. This, plus the number of spare devices (see below) must equal the number of .I component-devices (including "\fBmissing\fP" devices) -that are listed on the command line. Setting a value of 1 is probably +that are listed on the command line for +.BR --create . +Setting a value of 1 is probably a mistake and so requires that .B --force be specified first. A value of 1 will then be allowed for linear, multipath, raid0 and raid1. It is never allowed for raid4 or raid5. .br -Note that this number cannot be changed once the array has been created. +This number can only be changed using +.B --grow +for RAID1 arrays, and only on kernels which provide necessary support. .TP .BR -x ", " --spare-devices= @@ -288,6 +302,63 @@ If this is not specified size, though if there is a variance among the drives of greater than 1%, a warning is issued. +This value can be set with +.B --grow +for RAID level 1/4/5/6. If the array was created with a size smaller +than the currently active drives, the extra space can be accessed +using +.BR --grow . + +.TP +.BR --assume-clean +Tell +.I mdadm +that the array pre-existed and is known to be clean. This is only +really useful for Building RAID1 array. Only use this if you really +know what you are doing. This is currently only supported for --build. + +.TP +.BR -R ", " --run +Insist that +.I mdadm +run the array, even if some of the components +appear to be active in another array or filesystem. Normally +.I mdadm +will ask for confirmation before including such components in an +array. This option causes that question to be suppressed. + +.TP +.BR -f ", " --force +Insist that +.I mdadm +accept the geometry and layout specified without question. Normally +.I mdadm +will not allow creation of an array with only one device, and will try +to create a raid5 array with one missing drive (as this makes the +initial resync work faster). With +.BR --force , +.I mdadm +will not try to be so clever. + +.TP +.BR -a ", " "--auto{=no,yes,md,mdp,part,p}{NN}" +Instruct mdadm to create the device file if needed, and to allocate +an unused minor number. "yes" or "md" causes a non-partitionable array +to be used. "mdp", "part" or "p" causes a partitionable array (2.6 and +later) to be used. The argumentment can also come immediately after +"-a". e.g. "-ap". + +For partitionable arrays, +.I mdadm +will create the device file for the whole array and for the first 4 +partitions. A different number of partitions can be specified at the +end of this option (e.g. +.BR --auto=p7 ). +If the device name ends with a digit, the partition names add an +underscore, a 'p', and a number, e.g. "/dev/home1_p3". If there is no +trailing digit, then the partition names just have a number added, +e.g. "/dev/scratch3". + .SH For assemble: .TP @@ -326,6 +397,10 @@ With .B --run an attempt will be made to start it anyway. +.TP +.BR -a ", " "--auto{=no,yes,md,mdp,part}" +See this option under Create and Build options. + .TP .BR -U ", " --update= Update the superblock on each device while assembling the array. The @@ -504,7 +579,7 @@ listed in the configuration file are assembled. If precisely one device is listed, but .B --scan -is not given, that +is not given, then .I mdadm acts as though .B --scan @@ -545,6 +620,46 @@ may work for RAID1, 4, 5 or 6), give the .B --run flag. +If an +.B auto +option is given, either on the command line (--auto) or in the +configuration file (e.g. auto=part), then +.I mdadm +will create the md device if necessary or will re-create it if it +doesn't look usable as it is. + +This can be useful for handling partitioned devices (which don't have +a stable device number - it can change after a reboot) and when using +"udev" to manage your +.B /dev +tree (udev cannot handle md devices because of the unusual device +initialisation conventions). + +If the option to "auto" is "mdp" or "part" or (on the command line +only) "p", then mdadm will create a partitionable array, using the +first free one that is not inuse, and does not already have an entry +in /dev (apart from numeric /dev/md* entries). + +If the option to "auto" is "yes" or "md" or (on the command line) +nothing, then mdadm will create a traditional, non-partitionable md +array. + +It is expected that the "auto" functionality will be used to create +device entries with meaningful names such as "/dev/md/home" or +"/dev/md/root", rather than names based on the numerical array number. + +When using this option to create a partitionable array, the device +files for the first 4 partitions are also created. If a different +number is required it can be simply appended to the auto option. +e.g. "auto=part8". Partition names are created by appending a digit +string to the device name, with an intervening "_p" if the device name +ends with a digit. + +The +.B --auto +option is also available in Build and Create modes. As those modes do +not use a config file, the "auto=" config option does not apply to +these modes. .SH BUILD MODE @@ -584,6 +699,12 @@ Usage: This usage will initialise a new md array, associate some devices with it, and activate the array. +This the +.B --auto +option is given (as described in more detail in the section on +Assemble mode), then the md device will be created with a suitable +device number if necessary. + As devices are added, they are checked to see if they contain raid superblocks or filesystems. They are also checked to see if the variance in device size exceeds 1%. @@ -625,7 +746,7 @@ option. The General Management options that are valid with --create are: .TP .B --run -insist of running the array even if some devices look like they might +insist on running the array even if some devices look like they might be in use. .TP @@ -921,6 +1042,43 @@ first. If the removal succeeds but the adding fails, then it is added back to the original array. +.SH GROW MODE +The GROW mode is used for changing the size or shape of an active +array. +For this to work, the kernel must support the necessary change. +Various types of growth may be added during 2.6 development, possibly +including restructuring a raid5 array to have more active devices. + +Currently the only support available is to change the "size" attribute +for arrays with redundancy, and the raid-disks attribute of RAID1 +arrays. + +Normally when an array is build the "size" it taken from the smallest +of the drives. If all the small drives in an arrays are, one at a +time, removed and replaced with larger drives, then you could have an +array of large drives with only a small amount used. In this +situation, changing the "size" with "GROW" mode will allow the extra +space to start being used. If the size is increased in this way, a +"resync" process will start to make sure the new parts of the array +are synchronised. + +Note that when an array changes size, any filesystem that may be +stored in the array will not automatically grow to use the space. The +filesystem will need to be explicitly told to use the extra space. + +A RAID1 array can work with any number of devices from 1 upwards +(though 1 is not very useful). There may be times which you want to +increase or decrease the number of active devices. Note that this is +different to hot-add or hot-remove which changes the number of +inactive devices. + +When reducing the number of devices in a RAID1 array, the slots which +are to be removed from the array must already be vacant. That is, the +devices that which were in those slots must be failed and removed. + +When the number of devices is increased, any hot spares that are +present may be activated immediately. + .SH EXAMPLES .B " mdadm --query /dev/name-of-device" diff --git a/mdadm.c b/mdadm.c index d17e0b17..827f334d 100644 --- a/mdadm.c +++ b/mdadm.c @@ -29,10 +29,157 @@ #include "mdadm.h" #include "md_p.h" +#include -int open_mddev(char *dev) + +void make_parts(char *dev, int cnt) { - int mdfd = open(dev, O_RDWR, 0); + /* make 'cnt' partition devices for 'dev' + * We use the major/minor from dev and add 1..cnt + * If dev ends with a digit, we add "_p%d" else "%d" + * If the name exists, we use it's owner/mode, + * else that of dev + */ + struct stat stb; + int major, minor; + int i; + char *name = malloc(strlen(dev) + 20); + int dig = isdigit(dev[strlen(dev)-1]); + + if (stat(dev, &stb)!= 0) + return; + if (!S_ISBLK(stb.st_mode)) + return; + major = MAJOR(stb.st_rdev); + minor = MINOR(stb.st_rdev); + for (i=1; i <= cnt ; i++) { + struct stat stb2; + sprintf(name, "%s%s%d", dev, dig?"_p":"", i); + if (stat(name, &stb2)==0) { + if (!S_ISBLK(stb2.st_mode)) + continue; + if (stb2.st_rdev == MKDEV(major, minor+i)) + continue; + unlink(name); + } else { + stb2 = stb; + } + mknod(name, S_IFBLK | 0600, MKDEV(major, minor+i)); + chown(name, stb2.st_uid, stb2.st_gid); + chmod(name, stb2.st_mode & 07777); + } +} + +/* + * Open a given md device, and check that it really is one. + * If 'autof' is given, then we need to create, or recreate, the md device. + * If the name already exists, and is not a block device, we fail. + * If it exists and is not an md device, is not the right type (partitioned or not), + * or is currently in-use, we remove the device, but remember the owner and mode. + * If it now doesn't exist, we find a few md array and create the device. + * Default ownership is user=0, group=0 perm=0600 + */ +int open_mddev(char *dev, int autof) +{ + int mdfd; + struct stat stb; + int major = MD_MAJOR; + int minor; + int must_remove = 0; + struct mdstat_ent *mdlist; + int num; + + if (autof) { + /* autof is set, so we need to check that the name is ok, + * and possibly create one if not + */ + stb.st_mode = 0; + if (lstat(dev, &stb)==0 && ! S_ISBLK(stb.st_mode)) { + fprintf(stderr, Name ": %s is not a block device.\n", + dev); + return -1; + } + /* check major number is correct */ + if (autof>0) + major = get_mdp_major(); + if (stb.st_mode && MAJOR(stb.st_rdev) != major) + must_remove = 1; + if (stb.st_mode && !must_remove) { + mdu_array_info_t array; + /* looks ok, see if it is available */ + mdfd = open(dev, O_RDWR, 0); + if (mdfd < 0) { + fprintf(stderr, Name ": error opening %s: %s\n", + dev, strerror(errno)); + return -1; + } else if (md_get_version(mdfd) <= 0) { + fprintf(stderr, Name ": %s does not appear to be an md device\n", + dev); + close(mdfd); + return -1; + } + if (ioctl(mdfd, GET_ARRAY_INFO, &array)==0) { + /* already active */ + must_remove = 1; + close(mdfd); + } else { + if (autof > 0) + make_parts(dev, autof); + return mdfd; + } + } + /* Ok, need to find a minor that is not in use. + * Easiest to read /proc/mdstat, and hunt through for + * an unused number + */ + mdlist = mdstat_read(0); + for (num= (autof>0)?-1:0 ; ; num+= (autof>2)?-1:1) { + struct mdstat_ent *me; + for (me=mdlist; me; me=me->next) + if (me->devnum == num) + break; + if (!me) { + /* doesn't exist if mdstat. + * make sure it is new to /dev too + */ + char *dn; + if (autof > 0) + minor = (-1-num) << MdpMinorShift; + else + minor = num; + dn = map_dev(major,minor); + if (dn==NULL || is_standard(dn)) { + /* this number only used by a 'standard' name, + * so it is safe to use + */ + break; + } + } + } + /* 'num' is the number to use, >=0 for md, <0 for mdp */ + if (must_remove) { + /* never remove a device name that ends /mdNN or /dNN, + * that would be confusing + */ + if (is_standard(dev)) { + fprintf(stderr, Name ": --auto refusing to remove %s as it looks like a standard name.\n", + dev); + return -1; + } + unlink(dev); + } + + if (mknod(dev, S_IFBLK|0600, MKDEV(major, minor))!= 0) { + fprintf(stderr, Name ": failed to create %s\n", dev); + return -1; + } + if (must_remove) { + chown(dev, stb.st_uid, stb.st_gid); + chmod(dev, stb.st_mode & 07777); + } + make_parts(dev,autof); + } + mdfd = open(dev, O_RDWR, 0); if (mdfd < 0) fprintf(stderr, Name ": error opening %s: %s\n", dev, strerror(errno)); @@ -57,7 +204,7 @@ int main(int argc, char *argv[]) int rv; int chunk = 0; - int size = 0; + int size = -1; int level = UnSet; int layout = UnSet; int raiddisks = 0; @@ -79,6 +226,8 @@ int main(int argc, char *argv[]) int brief = 0; int force = 0; int test = 0; + int assume_clean = 0; + int autof = 0; /* -1 for non-partitions, 1 or more to create partitions */ char *mailaddr = NULL; char *program = NULL; @@ -114,6 +263,7 @@ int main(int argc, char *argv[]) case MANAGE : help_text = Help_manage; break; case MISC : help_text = Help_misc; break; case MONITOR : help_text = Help_monitor; break; + case GROW : help_text = Help_grow; break; } fputs(help_text,stderr); exit(0); @@ -150,6 +300,7 @@ int main(int argc, char *argv[]) case 'B': newmode = BUILD; break; case 'C': newmode = CREATE; break; case 'F': newmode = MONITOR;break; + case 'G': newmode = GROW; break; case '#': case 'D': @@ -200,13 +351,14 @@ int main(int argc, char *argv[]) case 'B': case 'C': case 'F': + case 'G': continue; } if (opt == 1) { /* an undecorated option - must be a device name. */ if (devs_found > 0 && mode == '@' && !devmode) { - fprintf(stderr, Name ": Must give on of -a/-r/-f for subsequent devices at %s\n", optarg); + fprintf(stderr, Name ": Must give one of -a/-r/-f for subsequent devices at %s\n", optarg); exit(2); } dv = malloc(sizeof(*dv)); @@ -243,17 +395,22 @@ int main(int argc, char *argv[]) } continue; + case O(GROW,'z'): case O(CREATE,'z'): /* size */ - if (size) { + if (size >= 0) { fprintf(stderr, Name ": size may only be specified once. " "Second value is %s.\n", optarg); exit(2); } - size = strtol(optarg, &c, 10); - if (!optarg[0] || *c || size < 4) { - fprintf(stderr, Name ": invalid size: %s\n", - optarg); - exit(2); + if (strcmp(optarg, "max")==0) + size = 0; + else { + size = strtol(optarg, &c, 10); + if (!optarg[0] || *c || size < 4) { + fprintf(stderr, Name ": invalid size: %s\n", + optarg); + exit(2); + } } continue; @@ -270,7 +427,7 @@ int main(int argc, char *argv[]) optarg); exit(2); } - if (level != 0 && level != -1 && mode == BUILD) { + if (level != 0 && level != -1 && level != 1 && level != -4 && mode == BUILD) { fprintf(stderr, Name ": Raid level %s not permitted with --build.\n", optarg); exit(2); @@ -310,6 +467,12 @@ int main(int argc, char *argv[]) } continue; + case O(CREATE,3): + case O(BUILD,3): /* assume clean */ + assume_clean = 1; + continue; + + case O(GROW,'n'): case O(CREATE,'n'): case O(BUILD,'n'): /* number of raid disks */ if (raiddisks) { @@ -350,6 +513,43 @@ int main(int argc, char *argv[]) exit(2); } continue; + + case O(CREATE,'a'): + case O(BUILD,'a'): + case O(ASSEMBLE,'a'): /* auto-creation of device node */ + if (optarg == NULL) + autof = -1; + else if (strcasecmp(optarg,"no")==0) + autof = 0; + else if (strcasecmp(optarg,"yes")==0 || strcasecmp(optarg,"md")==0) + autof = -1; + else { + /* There might be digits, and maybe a hypen, at the end */ + char *e = optarg + strlen(optarg); + int num = 4; + int len; + while (e > optarg && isdigit(e[-1])) + e--; + if (*e) { + num = atoi(e); + if (num <= 0) num = 1; + } + if (e > optarg && e[-1] == '-') + e--; + len = e - optarg; + if ((len == 3 && strncasecmp(optarg,"mdp",3)==0) || + (len == 1 && strncasecmp(optarg,"p",1)==0) || + (len >= 4 && strncasecmp(optarg,"part",4)==0)) + autof = num; + else { + fprintf(stderr, Name ": --auto flag arg of \"%s\" unrecognised: use no,yes,md,mdp,part\n" + " optionally followed by a number.\n", + optarg); + exit(2); + } + } + continue; + case O(BUILD,'f'): /* force honouring '-n 1' */ case O(CREATE,'f'): /* force honouring of device list */ case O(ASSEMBLE,'f'): /* force assembly */ @@ -463,10 +663,7 @@ int main(int argc, char *argv[]) /* now the general management options. Some are applicable * to other modes. None have arguments. */ - case O(MANAGE,'a'): - case O(CREATE,'a'): - case O(BUILD,'a'): - case O(ASSEMBLE,'a'): /* add a drive */ + case O(MANAGE,'a'): /* add a drive */ devmode = 'a'; continue; case O(MANAGE,'r'): /* remove a drive */ @@ -559,12 +756,17 @@ int main(int argc, char *argv[]) * we check that here and open it. */ - if (mode==MANAGE || mode == BUILD || mode == CREATE || (mode == ASSEMBLE && ! scan)) { + if (mode==MANAGE || mode == BUILD || mode == CREATE || mode == GROW || + (mode == ASSEMBLE && ! scan)) { if (devs_found < 1) { fprintf(stderr, Name ": an md device must be given in this mode\n"); exit(2); } - mdfd = open_mddev(devlist->devname); + if ((int)ident.super_minor == -2 && autof) { + fprintf(stderr, Name ": --super-minor=dev is incompatible with --auto\n"); + exit(2); + } + mdfd = open_mddev(devlist->devname, autof); if (mdfd < 0) exit(1); if ((int)ident.super_minor == -2) { @@ -593,7 +795,7 @@ int main(int argc, char *argv[]) ident.super_minor == UnSet && !scan ) { /* Only a device has been given, so get details from config file */ mddev_ident_t array_ident = conf_get_ident(configfile, devlist->devname); - mdfd = open_mddev(devlist->devname); + mdfd = open_mddev(devlist->devname, array_ident->autof); if (mdfd < 0) rv |= 1; else { @@ -618,7 +820,7 @@ int main(int argc, char *argv[]) } for (dv = devlist ; dv ; dv=dv->next) { mddev_ident_t array_ident = conf_get_ident(configfile, dv->devname); - mdfd = open_mddev(dv->devname); + mdfd = open_mddev(dv->devname, array_ident->autof); if (mdfd < 0) { rv |= 1; continue; @@ -641,7 +843,7 @@ int main(int argc, char *argv[]) } else for (; array_list; array_list = array_list->next) { mdu_array_info_t array; - mdfd = open_mddev(array_list->devname); + mdfd = open_mddev(array_list->devname, array_list->autof); if (mdfd < 0) { rv |= 1; continue; @@ -657,10 +859,10 @@ int main(int argc, char *argv[]) } break; case BUILD: - rv = Build(devlist->devname, mdfd, chunk, level, raiddisks, devlist->next); + rv = Build(devlist->devname, mdfd, chunk, level, raiddisks, devlist->next, assume_clean); break; case CREATE: - rv = Create(devlist->devname, mdfd, chunk, level, layout, size, + rv = Create(devlist->devname, mdfd, chunk, level, layout, size<0 ? 0 : size, raiddisks, sparedisks, devs_found-1, devlist->next, runstop, verbose, force); break; @@ -682,7 +884,7 @@ int main(int argc, char *argv[]) if (devlist == NULL) { if ((devmode == 'S' ||devmode=='D') && scan) { /* apply to all devices in /proc/mdstat */ - struct mdstat_ent *ms = mdstat_read(); + struct mdstat_ent *ms = mdstat_read(0); struct mdstat_ent *e; for (e=ms ; e ; e=e->next) { char *name = get_md_name(e->devnum); @@ -695,7 +897,7 @@ int main(int argc, char *argv[]) if (devmode == 'D') rv |= Detail(name, !verbose, test); else if (devmode=='S') { - mdfd = open_mddev(name); + mdfd = open_mddev(name, 0); if (mdfd >= 0) rv |= Manage_runstop(name, mdfd, -1); } @@ -715,7 +917,7 @@ int main(int argc, char *argv[]) case 'Q': rv |= Query(dv->devname); continue; } - mdfd = open_mddev(dv->devname); + mdfd = open_mddev(dv->devname, 0); if (mdfd>=0) switch(dv->disposition) { case 'R': @@ -739,6 +941,20 @@ int main(int argc, char *argv[]) rv= Monitor(devlist, mailaddr, program, delay?delay:60, daemonise, scan, oneshot, configfile, test); break; + + case GROW: + if (devs_found > 1) { + fprintf(stderr, Name ": Only one device may be given for --grow\n"); + rv = 1; + break; + } + if (size >= 0 && raiddisks) { + fprintf(stderr, Name ": can only grow size OR raiddisks, not both\n"); + rv = 1; + break; + } + rv = Manage_resize(devlist->devname, mdfd, size, raiddisks); + break; } exit(rv); } diff --git a/mdadm.conf.5 b/mdadm.conf.5 index 7b455223..97390102 100644 --- a/mdadm.conf.5 +++ b/mdadm.conf.5 @@ -128,6 +128,22 @@ a group of arrays is that will, when monitoring the arrays, move a spare drive from one array in a group to another array in that group if the first array had a failed or missing drive but no spare. + +.TP +.B auto= +This option declares to +.B mdadm +that it should try to create the device file of the array if it +doesn't already exist, or exists but with the wrong device number. + +The value of this option can be "yes" or "md" to indicate that a +traditional, non-partitionable md array should be created, or "mdp", +"part" or "partition" to indicate that a partitionable md array (only +available in linux 2.6 and later) should be used. This later set can +also have a number appended to indicate how many partitions to create +device files for, e.g. +.BR auto=mdp5 . +The default is 4. .RE .TP @@ -191,6 +207,14 @@ ARRAY /dev/md4 uuid=b23f3c6d:aec43a9f:fd65db85:369432df ARRAY /dev/md5 uuid=19464854:03f71b1b:e0df2edd:246cc977 .br spare-group=group1 +.br +# /dev/md/home is created if need to be a partitionable md array +.br +# any spare device number is allocated. +.br +ARRAY /dev/md/home UUID=9187a482:5dde19d9:eea3cc4a:d646ab8b +.br + auto=part MAILADDR root@mydomain.tld .br diff --git a/mdadm.h b/mdadm.h index 6c6cc7fe..90d3a094 100644 --- a/mdadm.h +++ b/mdadm.h @@ -55,6 +55,7 @@ char *strncpy(char *dest, const char *src, size_t n) __THROW; #include #include #define MD_MAJOR 9 +#define MdpMinorShift 6 #ifndef BLKGETSIZE64 #define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */ @@ -73,12 +74,13 @@ enum mode { MANAGE, MISC, MONITOR, + GROW, }; extern char short_options[]; extern struct option long_options[]; extern char Version[], Usage[], Help[], OptionHelp[], - Help_create[], Help_build[], Help_assemble[], + Help_create[], Help_build[], Help_assemble[], Help_grow[], Help_manage[], Help_misc[], Help_monitor[], Help_config[]; /* structures read from config file */ @@ -93,20 +95,22 @@ extern char Version[], Usage[], Help[], OptionHelp[], */ #define UnSet (0xfffe) typedef struct mddev_ident_s { - char *devname; + char *devname; - int uuid_set; - __u32 uuid[4]; + int uuid_set; + __u32 uuid[4]; unsigned int super_minor; - char *devices; /* comma separated list of device + char *devices; /* comma separated list of device * names with wild cards */ - int level; + int level; unsigned int raid_disks; unsigned int spare_disks; - char *spare_group; + int autof; /* 1 for normal, 2 for partitioned */ + char *spare_group; + struct mddev_ident_s *next; } *mddev_ident_t; @@ -135,8 +139,9 @@ struct mdstat_ent { struct mdstat_ent *next; }; -extern struct mdstat_ent *mdstat_read(void); +extern struct mdstat_ent *mdstat_read(int); extern void free_mdstat(struct mdstat_ent *ms); +extern void mdstat_wait(int seconds); #ifndef Sendmail #define Sendmail "/usr/lib/sendmail -t" @@ -151,6 +156,7 @@ extern char *map_dev(int major, int minor); extern int Manage_ro(char *devname, int fd, int readonly); extern int Manage_runstop(char *devname, int fd, int runstop); +extern int Manage_resize(char *devname, int fd, long long size, int raid_disks); extern int Manage_subdevs(char *devname, int fd, mddev_dev_t devlist); @@ -165,7 +171,7 @@ extern int Assemble(char *mddev, int mdfd, extern int Build(char *mddev, int mdfd, int chunk, int level, int raiddisks, - mddev_dev_t devlist); + mddev_dev_t devlist, int assume_clean); extern int Create(char *mddev, int mdfd, @@ -190,6 +196,10 @@ extern int check_ext2(int fd, char *name); extern int check_reiser(int fd, char *name); extern int check_raid(int fd, char *name); +extern int get_mdp_major(void); +extern int is_standard(char *dev); + + extern mddev_ident_t conf_get_ident(char *conffile, char *dev); extern mddev_dev_t conf_get_devs(char *conffile); extern char *conf_get_mailaddr(char *conffile); diff --git a/mdadm.spec b/mdadm.spec index 815ccd94..7465b340 100644 --- a/mdadm.spec +++ b/mdadm.spec @@ -1,6 +1,6 @@ Summary: mdadm is used for controlling Linux md devices (aka RAID arrays) Name: mdadm -Version: 1.5.0 +Version: 1.6.0 Release: 1 Source: http://www.cse.unsw.edu.au/~neilb/source/mdadm/mdadm-%{version}.tgz URL: http://www.cse.unsw.edu.au/~neilb/source/mdadm/ diff --git a/mdassemble b/mdassemble new file mode 100755 index 0000000000000000000000000000000000000000..cd8f042256af7972b95e96bd20642204e60ad31f GIT binary patch literal 62213 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�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 b%gfRzEf!A&_lj~@u-=xp;*N} void free_mdstat(struct mdstat_ent *ms) { @@ -99,13 +100,18 @@ void free_mdstat(struct mdstat_ent *ms) } } -struct mdstat_ent *mdstat_read() +static int mdstat_fd = -1; +struct mdstat_ent *mdstat_read(int hold) { FILE *f; struct mdstat_ent *all, **end; char *line; - f = fopen("/proc/mdstat", "r"); + if (hold && mdstat_fd != -1) { + lseek(mdstat_fd, 0L, 0); + f = fdopen(dup(mdstat_fd), "r"); + } else + f = fopen("/proc/mdstat", "r"); if (f == NULL) return NULL; @@ -141,8 +147,7 @@ struct mdstat_ent *mdstat_read() if (!ent) { fprintf(stderr, Name ": malloc failed reading /proc/mdstat.\n"); free_line(line); - fclose(f); - return all; + break; } ent->dev = ent->level = ent->pattern= NULL; ent->next = NULL; @@ -184,6 +189,20 @@ struct mdstat_ent *mdstat_read() *end = ent; end = &ent->next; } + if (hold && mdstat_fd == -1) + mdstat_fd = dup(fileno(f)); fclose(f); return all; } + +void mdstat_wait(int seconds) +{ + fd_set fds; + struct timeval tm; + FD_ZERO(&fds); + if (mdstat_fd >= 0) + FD_SET(mdstat_fd, &fds); + tm.tv_sec = seconds; + tm.tv_usec = 0; + select(mdstat_fd >2 ? mdstat_fd+1:3, NULL, NULL, &fds, &tm); +} diff --git a/t b/t new file mode 100644 index 00000000..baf4ab55 --- /dev/null +++ b/t @@ -0,0 +1 @@ +ARRAY /dev/fred auto=parti /dev/fred diff --git a/util.c b/util.c index bdafe66c..2f4ad963 100644 --- a/util.c +++ b/util.c @@ -421,23 +421,57 @@ int add_dev(const char *name, const struct stat *stb, int flag) return 0; } +int is_standard(char *dev) +{ + /* tests if dev is a "standard" md dev name. + * i.e if the last component is "/dNN" or "/mdNN", + * where NN is a string of digits + */ + dev = strrchr(dev, '/'); + if (!dev) + return 0; + if (strncmp(dev, "/d",2)==0) + dev += 2; + else if (strncmp(dev, "/md", 3)==0) + dev += 3; + else + return 0; + if (!*dev) + return 0; + while (isdigit(*dev)) + dev++; + if (*dev) + return 0; + return 1; +} + + +/* + * Find a block device with the right major/minor number. + * Avoid /dev/mdNN and /dev/md/dNN is possible + */ char *map_dev(int major, int minor) { - struct devmap *p; - if (!devlist_ready) { + struct devmap *p; + char *std = NULL; + if (!devlist_ready) { #ifndef __dietlibc__ - nftw("/dev", add_dev, 10, FTW_PHYS); + nftw("/dev", add_dev, 10, FTW_PHYS); #else - ftw("/dev", add_dev, 10); + ftw("/dev", add_dev, 10); #endif - devlist_ready=1; - } + devlist_ready=1; + } - for (p=devlist; p; p=p->next) - if (p->major == major && - p->minor == minor) - return p->name; - return NULL; + for (p=devlist; p; p=p->next) + if (p->major == major && + p->minor == minor) { + if (is_standard(p->name)) + std = p->name; + else + return p->name; + } + return std; } #endif @@ -504,16 +538,20 @@ char *human_size_brief(long long bytes) return buf; } -static int mdp_major = -1; -void get_mdp_major(void) +int get_mdp_major(void) { - FILE *fl = fopen("/proc/devices", "r"); +static int mdp_major = -1; + FILE *fl; char *w; int have_block = 0; int have_devices = 0; int last_num = -1; + + if (mdp_major != -1) + return mdp_major; + fl = fopen("/proc/devices", "r"); if (!fl) - return; + return -1; while ((w = conf_word(fl, 1))) { if (have_block && strcmp(w, "devices:")==0) have_devices = 1; @@ -525,6 +563,7 @@ void get_mdp_major(void) free(w); } fclose(fl); + return mdp_major; } @@ -536,12 +575,12 @@ char *get_md_name(int dev) static char devname[50]; struct stat stb; dev_t rdev; + char *dn; if (dev < 0) { - - if (mdp_major < 0) get_mdp_major(); - if (mdp_major < 0) return NULL; - rdev = MKDEV(mdp_major, (-1-dev)<<6); + int mdp = get_mdp_major(); + if (mdp < 0) return NULL; + rdev = MKDEV(mdp, (-1-dev)<<6); sprintf(devname, "/dev/md/d%d", -1-dev); if (stat(devname, &stb) == 0 && (S_IFMT&stb.st_mode) == S_IFBLK @@ -561,9 +600,13 @@ char *get_md_name(int dev) && (stb.st_rdev == rdev)) return devname; } + dn = map_dev(MAJOR(rdev), MINOR(rdev)); + if (dn) + return dn; sprintf(devname, "/dev/.tmp.md%d", dev); if (mknod(devname, S_IFBLK | 0600, rdev) == -1) - return NULL; + if (errno != EEXIST) + return NULL; if (stat(devname, &stb) == 0 && (S_IFMT&stb.st_mode) == S_IFBLK -- 2.39.2