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 0000000..4461fe3 --- /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 0179807..3e182f8 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 7c8fe05..a505a74 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 a536a75..fb9857b 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 419c45c..5028ae2 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 0d8ad8d..624c775 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 2d3693b..021a967 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 7cd8240..6ba33ba 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 4cfa1eb..a282744 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 067beb6..1671d26 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 38bcb73..6e20b7e 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 d17e0b1..827f334 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 7b45522..9739010 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 6c6cc7f..90d3a09 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 815ccd9..7465b34 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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 literal 0 HcmV?d00001 diff --git a/mdstat.c b/mdstat.c index 9711e54..3204d2e 100644 --- a/mdstat.c +++ b/mdstat.c @@ -85,6 +85,7 @@ #include "mdadm.h" #include "dlink.h" +#include 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 0000000..baf4ab5 --- /dev/null +++ b/t @@ -0,0 +1 @@ +ARRAY /dev/fred auto=parti /dev/fred diff --git a/util.c b/util.c index bdafe66..2f4ad96 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