* [RFC PATCH v5 1/4] md: add uapi definitions for the md control device
2026-10-29 20:22 [RFC PATCH v5 0/4] md: add a control device for array management Abd-Alrhman Masalkhi
@ 2026-10-29 20:22 ` Abd-Alrhman Masalkhi
2026-10-29 20:22 ` [RFC PATCH v5 2/4] md: pass struct mdu_disk_info64 to md_add_new_disk() Abd-Alrhman Masalkhi
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Abd-Alrhman Masalkhi @ 2026-10-29 20:22 UTC (permalink / raw)
To: song, yukuai, mtkaczyk, magiclinan, xiao
Cc: linux-raid, linux-kernel, Abd-Alrhman Masalkhi
Add the ioctl commands and structures for /dev/md-control, a misc device
that manages md arrays without opening the md block device. The device
itself is added in the next patch.
Every command except MD_CTL_RAID_VERSION takes a struct mdu_ioctl, which
names the array by name, UUID (it is only available for arrays with
native metadata) or device number, carries a flags field, and points to
the command's payload .
The commands mirror the existing block device ioctls. They use the
MD_MAJOR ioctl type, with numbers starting at 0x40, grouped like the
old ones: status at 0x40, configuration at 0x50 and usage at 0x60.
STOP_ARRAY_RO has no separate command: MD_CTL_STOP_ARRAY with
MD_CTL_RO_FLAG set switches the array to read-only mode instead of
stopping it.
MD_CTL_LOOKUP_ARRAY has no equivalent among the legacy ioctls. Given
the kernel name, device number, or UUID of an array, it returns all
three identifiers. It also indicates whether the array exists, even
if it has not yet been configured. It returns ENODEV only if no such
MD device exists. For arrays using external metadata or having no
superblock, the UUID is returned as all zeros.
The payload structures are new versions of the old ones with fixed-size
types and explicit padding, so they have the same layout on 32-bit and
64-bit systems. Fields that overflow in the old structures are 64-bit
now: size, ctime and utime in struct mdu_array_info64.
struct mdu_array_info64 also reports the number of journal disks.
Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com>
---
include/uapi/linux/raid/md_u.h | 119 +++++++++++++++++++++++++++++++++
1 file changed, 119 insertions(+)
diff --git a/include/uapi/linux/raid/md_u.h b/include/uapi/linux/raid/md_u.h
index a893010735fb..3616127eacea 100644
--- a/include/uapi/linux/raid/md_u.h
+++ b/include/uapi/linux/raid/md_u.h
@@ -12,6 +12,8 @@
#ifndef _UAPI_MD_U_H
#define _UAPI_MD_U_H
+#include <linux/types.h>
+
/*
* Different major versions are not compatible.
* Different minor versions are only downward compatible.
@@ -30,6 +32,10 @@
*/
#define MD_PATCHLEVEL_VERSION 3
+#define MD_NAME_LEN 32
+#define MD_UUID_LEN 16
+#define MD_CTL_NODE "md-control"
+
/* ioctls */
/* status */
@@ -61,6 +67,28 @@
#define RESTART_ARRAY_RW _IO (MD_MAJOR, 0x34)
#define CLUSTERED_DISK_NACK _IO (MD_MAJOR, 0x35)
+/* ioctl commands for the MD misc control driver */
+/* status */
+#define MD_CTL_RAID_VERSION _IOR(MD_MAJOR, 0x40, struct mdu_version64)
+#define MD_CTL_LOOKUP_ARRAY _IOWR(MD_MAJOR, 0x41, struct mdu_ioctl)
+#define MD_CTL_GET_ARRAY_INFO _IOWR(MD_MAJOR, 0x42, struct mdu_ioctl)
+#define MD_CTL_GET_DISK_INFO _IOWR(MD_MAJOR, 0x43, struct mdu_ioctl)
+#define MD_CTL_GET_BITMAP_FILE _IOWR(MD_MAJOR, 0x44, struct mdu_ioctl)
+
+/* configuration */
+#define MD_CTL_SET_ARRAY_INFO _IOWR(MD_MAJOR, 0x50, struct mdu_ioctl)
+#define MD_CTL_ADD_NEW_DISK _IOWR(MD_MAJOR, 0x51, struct mdu_ioctl)
+#define MD_CTL_HOT_ADD_DISK _IOWR(MD_MAJOR, 0x52, struct mdu_ioctl)
+#define MD_CTL_HOT_REMOVE_DISK _IOWR(MD_MAJOR, 0x53, struct mdu_ioctl)
+#define MD_CTL_SET_DISK_FAULTY _IOWR(MD_MAJOR, 0x54, struct mdu_ioctl)
+#define MD_CTL_SET_BITMAP_FILE _IOWR(MD_MAJOR, 0x55, struct mdu_ioctl)
+
+/* usage */
+#define MD_CTL_RUN_ARRAY _IOWR(MD_MAJOR, 0x60, struct mdu_ioctl)
+#define MD_CTL_STOP_ARRAY _IOWR(MD_MAJOR, 0x61, struct mdu_ioctl)
+#define MD_CTL_RESTART_ARRAY_RW _IOWR(MD_MAJOR, 0x62, struct mdu_ioctl)
+#define MD_CTL_CLUSTERED_DISK_NACK _IOWR(MD_MAJOR, 0x63, struct mdu_ioctl)
+
/* 63 partitions with the alternate major number (mdp) */
#define MdpMinorShift 6
@@ -146,4 +174,95 @@ typedef struct mdu_param_s
int max_fault; /* unused for now */
} mdu_param_t;
+/* structures for the MD misc control driver */
+struct mdu_version64 {
+ __u32 major;
+ __u32 minor;
+ __u32 patchlevel;
+ __u32 padding;
+};
+
+struct mdu_array_info64 {
+ /*
+ * Generic constant information
+ */
+ __u32 major_version;
+ __u32 minor_version;
+ __u32 patch_version;
+ __s32 level;
+
+ __u64 ctime;
+ __s64 size;
+
+ __u32 nr_disks;
+ __u32 raid_disks;
+ __u32 md_minor;
+ __u32 not_persistent;
+
+ /*
+ * Generic state information
+ */
+ __u64 utime; /* 0 Superblock update time */
+
+ __u32 state; /* 1 State bits (clean, ...) */
+ __u32 active_disks; /* 2 Number of currently active disks */
+ __u32 working_disks; /* 3 Number of working disks */
+ __u32 failed_disks; /* 4 Number of failed disks */
+ __u32 spare_disks; /* 5 Number of spare disks */
+ __u32 journal_disks; /* 6 Number of disks used for journaling */
+
+ /*
+ * Personality information
+ */
+ __s32 layout; /* 0 the array's physical layout */
+ __u32 chunk_size; /* 1 chunk size in bytes */
+
+};
+
+struct mdu_disk_info64 {
+ /*
+ * configuration/status of one particular disk
+ */
+ __u32 state;
+ __u32 major;
+ __u32 minor;
+ __u32 padding;
+
+ __s32 number;
+ __s32 raid_disk;
+};
+
+struct mdu_param64 {
+ __s32 personality; /* 1,2,3,4 */
+ __u32 max_fault; /* unused for now */
+ __u32 chunk_size; /* in bytes */
+ __u32 padding;
+};
+
+struct mdu_ioctl {
+ char md_name[MD_NAME_LEN];
+ char md_uuid[MD_UUID_LEN];
+ __u64 md_dev; /* md array device number */
+ __u32 flags; /* Flags for the ioctl operation */
+ __u32 padding;
+ union {
+ __u64 array; /* Pointer to struct mdu_array_info64 */
+ __u64 disk; /* Pointer to struct mdu_disk_info64 */
+ __u64 param; /* Pointer to struct mdu_param64 */
+
+ /* GET_BITMAP_FILE: pointer to struct mdu_bitmap_file_s
+ * SET_BITMAP_FILE: file descriptor.
+ */
+ __u64 bitmap_file;
+ __u64 disk_dev; /* Disk device number */
+ __u64 arg; /* Generic argument */
+ };
+};
+
+/*
+ * If set, MD_STOP_ARRAY will switch the array to read-only mode instead
+ * of fully stopping it.
+ */
+#define MD_CTL_RO_FLAG (1u << 0)
+
#endif /* _UAPI_MD_U_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [RFC PATCH v5 2/4] md: pass struct mdu_disk_info64 to md_add_new_disk()
2026-10-29 20:22 [RFC PATCH v5 0/4] md: add a control device for array management Abd-Alrhman Masalkhi
2026-10-29 20:22 ` [RFC PATCH v5 1/4] md: add uapi definitions for the md control device Abd-Alrhman Masalkhi
@ 2026-10-29 20:22 ` Abd-Alrhman Masalkhi
2026-10-29 20:22 ` [RFC PATCH v5 3/4] md: use struct mdu_array_info64 for SET_ARRAY_INFO Abd-Alrhman Masalkhi
2026-10-29 20:22 ` [RFC PATCH v5 4/4] md: add a control device for array management Abd-Alrhman Masalkhi
3 siblings, 0 replies; 6+ messages in thread
From: Abd-Alrhman Masalkhi @ 2026-10-29 20:22 UTC (permalink / raw)
To: song, yukuai, mtkaczyk, magiclinan, xiao
Cc: linux-raid, linux-kernel, Abd-Alrhman Masalkhi
The md control device passes disk information as struct mdu_disk_info64.
Make md_add_new_disk() take this structure, so that the control device
and the old ADD_NEW_DISK ioctl can share it.
The ADD_NEW_DISK ioctl converts its mdu_disk_info_t with the new helper
disk_info_to64(), and md_setup_drive() uses a struct mdu_disk_info64
directly.
No functional change.
Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com>
---
drivers/md/md-autodetect.c | 2 +-
drivers/md/md.c | 25 ++++++++++++++++++++-----
drivers/md/md.h | 4 ++--
3 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/drivers/md/md-autodetect.c b/drivers/md/md-autodetect.c
index 4b80165afd23..9ba061f1a628 100644
--- a/drivers/md/md-autodetect.c
+++ b/drivers/md/md-autodetect.c
@@ -201,7 +201,7 @@ static void __init md_setup_drive(struct md_setup_args *args)
err = md_set_array_info(mddev, &ainfo);
for (i = 0; i <= MD_SB_DISKS && devices[i]; i++) {
- struct mdu_disk_info_s dinfo = {
+ struct mdu_disk_info64 dinfo = {
.major = MAJOR(devices[i]),
.minor = MINOR(devices[i]),
};
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 154ee5a65cb7..ae893c6296ba 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -7486,7 +7486,7 @@ static int get_disk_info(struct mddev *mddev, void __user * arg)
return 0;
}
-int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info)
+int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info64 *info)
{
struct md_rdev *rdev;
dev_t dev = MKDEV(info->major,info->minor);
@@ -8305,6 +8305,15 @@ static bool md_ioctl_need_suspend(unsigned int cmd)
}
}
+static void disk_info_to64(struct mdu_disk_info64 *info64, mdu_disk_info_t *info)
+{
+ info64->number = info->number;
+ info64->major = info->major;
+ info64->minor = info->minor;
+ info64->raid_disk = info->raid_disk;
+ info64->state = info->state;
+}
+
static int __md_set_array_info(struct mddev *mddev, void __user *argp)
{
mdu_array_info_t info;
@@ -8452,13 +8461,16 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
*/
if (mddev->pers) {
mdu_disk_info_t info;
+ struct mdu_disk_info64 info64 = {0};
if (copy_from_user(&info, argp, sizeof(info)))
err = -EFAULT;
else if (!(info.state & (1<<MD_DISK_SYNC)))
/* Need to clear read-only for this */
break;
- else
- err = md_add_new_disk(mddev, &info);
+ else {
+ disk_info_to64(&info64, &info);
+ err = md_add_new_disk(mddev, &info64);
+ }
goto unlock;
}
break;
@@ -8493,10 +8505,13 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
case ADD_NEW_DISK:
{
mdu_disk_info_t info;
+ struct mdu_disk_info64 info64 = {0};
if (copy_from_user(&info, argp, sizeof(info)))
err = -EFAULT;
- else
- err = md_add_new_disk(mddev, &info);
+ else {
+ disk_info_to64(&info64, &info);
+ err = md_add_new_disk(mddev, &info64);
+ }
goto unlock;
}
diff --git a/drivers/md/md.h b/drivers/md/md.h
index 6440da292105..a84c8b97fa2c 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -1043,12 +1043,12 @@ static inline void mddev_unlock_and_resume(struct mddev *mddev)
}
struct mdu_array_info_s;
-struct mdu_disk_info_s;
+struct mdu_disk_info64;
extern int mdp_major;
void md_autostart_arrays(int part);
int md_set_array_info(struct mddev *mddev, struct mdu_array_info_s *info);
-int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info_s *info);
+int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info64 *info);
int do_md_run(struct mddev *mddev);
#define MDDEV_STACK_INTEGRITY (1u << 0)
int mddev_stack_rdev_limits(struct mddev *mddev, struct queue_limits *lim,
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [RFC PATCH v5 3/4] md: use struct mdu_array_info64 for SET_ARRAY_INFO
2026-10-29 20:22 [RFC PATCH v5 0/4] md: add a control device for array management Abd-Alrhman Masalkhi
2026-10-29 20:22 ` [RFC PATCH v5 1/4] md: add uapi definitions for the md control device Abd-Alrhman Masalkhi
2026-10-29 20:22 ` [RFC PATCH v5 2/4] md: pass struct mdu_disk_info64 to md_add_new_disk() Abd-Alrhman Masalkhi
@ 2026-10-29 20:22 ` Abd-Alrhman Masalkhi
2026-10-29 20:22 ` [RFC PATCH v5 4/4] md: add a control device for array management Abd-Alrhman Masalkhi
3 siblings, 0 replies; 6+ messages in thread
From: Abd-Alrhman Masalkhi @ 2026-10-29 20:22 UTC (permalink / raw)
To: song, yukuai, mtkaczyk, magiclinan, xiao
Cc: linux-raid, linux-kernel, Abd-Alrhman Masalkhi
The md control device passes array information as struct
mdu_array_info64. Make md_set_array_info() and update_array_info()
take the new struct mdu_array_info64, so that the md control device
and the old SET_ARRAY_INFO ioctl can share them.
md_set_array_info() no longer checks for a negative major_version:
the field is unsigned now, and the existing check against the size
of super_types still rejects such values.
md_setup_drive() uses a struct mdu_array_info64 directly.
Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com>
---
drivers/md/md-autodetect.c | 2 +-
drivers/md/md.c | 37 +++++++++++++++++++++++++++++++------
drivers/md/md.h | 4 ++--
3 files changed, 34 insertions(+), 9 deletions(-)
diff --git a/drivers/md/md-autodetect.c b/drivers/md/md-autodetect.c
index 9ba061f1a628..227cd4a688a5 100644
--- a/drivers/md/md-autodetect.c
+++ b/drivers/md/md-autodetect.c
@@ -124,7 +124,7 @@ static void __init md_setup_drive(struct md_setup_args *args)
{
char *devname = args->device_names;
dev_t devices[MD_SB_DISKS + 1], mdev;
- struct mdu_array_info_s ainfo = { };
+ struct mdu_array_info64 ainfo = { };
struct mddev *mddev;
int err = 0, i;
char name[16];
diff --git a/drivers/md/md.c b/drivers/md/md.c
index ae893c6296ba..3b4497b615cf 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -7904,12 +7904,11 @@ static int set_bitmap_file(struct mddev *mddev, int fd)
* The minor and patch _version numbers are also kept incase the
* super_block handler wishes to interpret them.
*/
-int md_set_array_info(struct mddev *mddev, struct mdu_array_info_s *info)
+int md_set_array_info(struct mddev *mddev, struct mdu_array_info64 *info)
{
if (info->raid_disks == 0) {
/* just setting version number for superblock loading */
- if (info->major_version < 0 ||
- info->major_version >= ARRAY_SIZE(super_types) ||
+ if (info->major_version >= ARRAY_SIZE(super_types) ||
super_types[info->major_version].name == NULL) {
/* maybe try to auto-load a module? */
pr_warn("md: superblock version %d not known\n",
@@ -8101,7 +8100,7 @@ static void put_cluster_ops(struct mddev *mddev)
* Any differences that cannot be handled will cause an error.
* Normally, only one change can be managed at a time.
*/
-static int update_array_info(struct mddev *mddev, mdu_array_info_t *info)
+static int update_array_info(struct mddev *mddev, struct mdu_array_info64 *info)
{
int rv = 0;
int cnt = 0;
@@ -8314,9 +8313,33 @@ static void disk_info_to64(struct mdu_disk_info64 *info64, mdu_disk_info_t *info
info64->state = info->state;
}
+static void array_info_to64(struct mdu_array_info64 *info64,
+ mdu_array_info_t *info)
+{
+ info64->major_version = info->major_version;
+ info64->minor_version = info->minor_version;
+ info64->patch_version = info->patch_version;
+ info64->level = info->level;
+ info64->ctime = info->ctime;
+ info64->size = info->size;
+ info64->nr_disks = info->nr_disks;
+ info64->raid_disks = info->raid_disks;
+ info64->md_minor = info->md_minor;
+ info64->not_persistent = info->not_persistent;
+ info64->utime = info->utime;
+ info64->state = info->state;
+ info64->active_disks = info->active_disks;
+ info64->working_disks = info->working_disks;
+ info64->failed_disks = info->failed_disks;
+ info64->spare_disks = info->spare_disks;
+ info64->layout = info->layout;
+ info64->chunk_size = info->chunk_size;
+}
+
static int __md_set_array_info(struct mddev *mddev, void __user *argp)
{
mdu_array_info_t info;
+ struct mdu_array_info64 info64 = {0};
int err;
if (!argp)
@@ -8324,8 +8347,10 @@ static int __md_set_array_info(struct mddev *mddev, void __user *argp)
else if (copy_from_user(&info, argp, sizeof(info)))
return -EFAULT;
+ array_info_to64(&info64, &info);
+
if (mddev->pers) {
- err = update_array_info(mddev, &info);
+ err = update_array_info(mddev, &info64);
if (err)
pr_warn("md: couldn't update array info. %d\n", err);
return err;
@@ -8341,7 +8366,7 @@ static int __md_set_array_info(struct mddev *mddev, void __user *argp)
return -EBUSY;
}
- err = md_set_array_info(mddev, &info);
+ err = md_set_array_info(mddev, &info64);
if (err)
pr_warn("md: couldn't set array info. %d\n", err);
diff --git a/drivers/md/md.h b/drivers/md/md.h
index a84c8b97fa2c..27de34a8bdf9 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -1042,12 +1042,12 @@ static inline void mddev_unlock_and_resume(struct mddev *mddev)
mddev_resume(mddev);
}
-struct mdu_array_info_s;
+struct mdu_array_info64;
struct mdu_disk_info64;
extern int mdp_major;
void md_autostart_arrays(int part);
-int md_set_array_info(struct mddev *mddev, struct mdu_array_info_s *info);
+int md_set_array_info(struct mddev *mddev, struct mdu_array_info64 *info);
int md_add_new_disk(struct mddev *mddev, struct mdu_disk_info64 *info);
int do_md_run(struct mddev *mddev);
#define MDDEV_STACK_INTEGRITY (1u << 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [RFC PATCH v5 4/4] md: add a control device for array management
2026-10-29 20:22 [RFC PATCH v5 0/4] md: add a control device for array management Abd-Alrhman Masalkhi
` (2 preceding siblings ...)
2026-10-29 20:22 ` [RFC PATCH v5 3/4] md: use struct mdu_array_info64 for SET_ARRAY_INFO Abd-Alrhman Masalkhi
@ 2026-10-29 20:22 ` Abd-Alrhman Masalkhi
2026-10-29 20:48 ` sashiko-bot
3 siblings, 1 reply; 6+ messages in thread
From: Abd-Alrhman Masalkhi @ 2026-10-29 20:22 UTC (permalink / raw)
To: song, yukuai, mtkaczyk, magiclinan, xiao
Cc: linux-raid, linux-kernel, Abd-Alrhman Masalkhi, Zhihao Cheng
All md management ioctls (SET_ARRAY_INFO, ADD_NEW_DISK, RUN_ARRAY,
STOP_ARRAY, ...) are issued on the md block device itself, so the
caller must hold the array open while it configures or stops it.
This is a problem for STOP_ARRAY and STOP_ARRAY_RO. Before the array is
stopped, the page cache must be flushed, and no other task may have the
device open or be writing to it. The issue arises when several tasks
share the same file descriptor table, as they count as a single opener.
Consequently, one task may still be writing while another task flushes
the page cache and stops the array. A write within this window can race
with the stop operation.
Add a misc character device, /dev/md-control. The control device is
not tied to any md device, each request specifies the array in the
payload, either by name, by UUID (it is only available for arrays with
native metadata) or by device number, without the need to open the md
block device. Every request structure has a flags field. Unknown flags
are rejected with -ENOTTY.
md_alloc() adds a new mddev to all_mddevs before its gendisk exists,
and frees the mddev directly if creation fails. Move disks_mutex out
of md_alloc() and take it around the control device lookups, so that
they never take a reference to an mddev that md_alloc() has not
finished.
The existing ioctl on the md block device is unchanged, but a warning
message will be printed to recommend using the control device and
indicate that the per-array MD ioctls will be removed in the future.
Suggested-by: Yu Kuai <yukuai@fygo.io>
Suggested-by: Zhihao Cheng <chengzhihao1@huawei.com>
Signed-off-by: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com>
---
drivers/md/md.c | 632 +++++++++++++++++++++++++++++++++++++++++++++++-
drivers/md/md.h | 9 +
2 files changed, 638 insertions(+), 3 deletions(-)
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 3b4497b615cf..a1425b5e6b3e 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -64,6 +64,7 @@
#include <linux/slab.h>
#include <linux/percpu-refcount.h>
#include <linux/part_stat.h>
+#include <linux/miscdevice.h>
#include "md.h"
#include "md-bitmap.h"
@@ -367,6 +368,8 @@ EXPORT_SYMBOL_GPL(md_new_event);
static LIST_HEAD(all_mddevs);
static DEFINE_SPINLOCK(all_mddevs_lock);
+static DEFINE_MUTEX(disks_mutex);
+
static bool is_md_suspended(struct mddev *mddev)
{
return percpu_ref_is_dying(&mddev->active_io);
@@ -6319,7 +6322,6 @@ struct mddev *md_alloc(dev_t dev, char *name)
* If "name" is not NULL, the device is being created by
* writing to /sys/module/md_mod/parameters/new_array.
*/
- static DEFINE_MUTEX(disks_mutex);
struct mddev *mddev;
struct gendisk *disk;
int partitioned;
@@ -6386,8 +6388,10 @@ struct mddev *md_alloc(dev_t dev, char *name)
disk->events |= DISK_EVENT_MEDIA_CHANGE;
mddev->gendisk = disk;
error = add_disk(disk);
- if (error)
+ if (error) {
+ mddev->gendisk = NULL;
goto out_put_disk;
+ }
kobject_init(&mddev->kobj, &md_ktype);
error = kobject_add(&mddev->kobj, &disk_to_dev(disk)->kobj, "%s", "md");
@@ -8374,7 +8378,7 @@ static int __md_set_array_info(struct mddev *mddev, void __user *argp)
}
static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
- unsigned int cmd, unsigned long arg)
+ unsigned int cmd, unsigned long arg)
{
int err = 0;
unsigned int noio_flags = 0;
@@ -8386,6 +8390,8 @@ static int md_ioctl(struct block_device *bdev, blk_mode_t mode,
if (err)
return err;
+ pr_warn_once("md: %s uses the deprecated per-array md ioctls, which will be removed in future; use /dev/%s instead\n",
+ current->comm, MD_CTL_NODE);
/*
* Commands dealing with the RAID driver but not any
* particular array:
@@ -8601,6 +8607,618 @@ static int md_compat_ioctl(struct block_device *bdev, blk_mode_t mode,
}
#endif /* CONFIG_COMPAT */
+static struct mddev *mdctl_get_mddev_dev(dev_t dev)
+{
+ struct mddev *mddev, *ret = ERR_PTR(-EINVAL);
+
+ if (MINOR(dev) >= (1 << MINORBITS))
+ return ret;
+
+ if (MAJOR(dev) != MD_MAJOR)
+ dev &= ~((1 << MdpMinorShift) - 1);
+
+ ret = ERR_PTR(-ENODEV);
+ spin_lock(&all_mddevs_lock);
+ list_for_each_entry(mddev, &all_mddevs, all_mddevs) {
+ if (mddev->unit == dev) {
+ ret = mddev_get(mddev);
+ if (!ret)
+ ret = ERR_PTR(-ENODEV);
+ break;
+ }
+ }
+ spin_unlock(&all_mddevs_lock);
+
+ return ret;
+}
+
+static struct mddev *mdctl_get_mddev_name(const char *name)
+{
+ unsigned long minor;
+ struct mddev *mddev, *ret = ERR_PTR(-EINVAL);
+
+ if (strnlen(name, MD_NAME_LEN) == MD_NAME_LEN || strncmp(name, "md", 2))
+ return ret;
+
+ if (name[2] != '_' && (!isdigit(name[2]) ||
+ kstrtoul(&name[2], 10, &minor) ||
+ minor > MINORMASK))
+ return ret;
+
+ ret = ERR_PTR(-ENODEV);
+ spin_lock(&all_mddevs_lock);
+ list_for_each_entry(mddev, &all_mddevs, all_mddevs) {
+ if (mddev->gendisk &&
+ strcmp(mddev->gendisk->disk_name, name) == 0) {
+ ret = mddev_get(mddev);
+ if (!ret)
+ ret = ERR_PTR(-ENODEV);
+ break;
+ }
+ }
+ spin_unlock(&all_mddevs_lock);
+
+ return ret;
+}
+
+static struct mddev *mdctl_get_mddev_uuid(const char *uuid)
+{
+ struct mddev *mddev, *ret = ERR_PTR(-ENODEV);
+
+ spin_lock(&all_mddevs_lock);
+ list_for_each_entry(mddev, &all_mddevs, all_mddevs) {
+ if (memcmp(mddev->uuid, uuid, sizeof(mddev->uuid)) == 0) {
+ ret = mddev_get(mddev);
+ if (!ret)
+ ret = ERR_PTR(-ENODEV);
+ break;
+ }
+ }
+ spin_unlock(&all_mddevs_lock);
+
+ return ret;
+}
+
+static struct mddev *mdctl_find_mddev(struct mdu_ioctl *md_ctl)
+{
+ struct mddev *ret = ERR_PTR(-EINVAL);
+
+ /*
+ * md_alloc() adds a new mddev to all_mddevs before it creates the
+ * gendisk. If creation fails. it frees the mddev directly, without
+ * looking at ->active. All of this happens under disks_mutex.
+ * Holding disks_mutex for the lookup means we only see mddevs that
+ * md_alloc() has finished, so taking a reference is safe and every
+ * mddev we find has a gendisk.
+ */
+ mutex_lock(&disks_mutex);
+
+ if (memchr_inv(md_ctl->md_uuid, 0, MD_UUID_LEN)) {
+ if (*md_ctl->md_name || md_ctl->md_dev) {
+ pr_err_ratelimited("md: only supply one of name, uuid or dev number\n");
+ goto out;
+ }
+ ret = mdctl_get_mddev_uuid(md_ctl->md_uuid);
+ } else if (*md_ctl->md_name) {
+ if (md_ctl->md_dev) {
+ pr_err_ratelimited("md: only supply one of name, uuid or dev number\n");
+ goto out;
+ }
+ ret = mdctl_get_mddev_name(md_ctl->md_name);
+ } else if (md_ctl->md_dev) {
+ ret = mdctl_get_mddev_dev(new_decode_dev(md_ctl->md_dev));
+ }
+
+out:
+ mutex_unlock(&disks_mutex);
+ return ret;
+}
+
+static int mdctl_lookup_array(struct mddev *mddev, struct mdu_ioctl __user *u)
+{
+ struct mdu_ioctl md_ctl;
+
+ memset(&md_ctl, 0, sizeof(md_ctl));
+ memcpy(md_ctl.md_uuid, mddev->uuid, MD_UUID_LEN);
+ strscpy(md_ctl.md_name, mddev->gendisk->disk_name, MD_NAME_LEN);
+ md_ctl.md_dev = new_encode_dev(mddev->unit);
+
+ if (copy_to_user(u, &md_ctl, sizeof(md_ctl)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int mdctl_get_raid_version(struct mdu_version64 __user *user)
+{
+ struct mdu_version64 v;
+
+ memset(&v, 0, sizeof(v));
+ v.major = MD_MAJOR_VERSION;
+ v.minor = MD_MINOR_VERSION;
+ v.patchlevel = MD_PATCHLEVEL_VERSION;
+
+ if (copy_to_user(user, &v, sizeof(v)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int mdctl_get_array_info(struct mddev *mddev, void __user *arg)
+{
+ struct mdu_array_info64 info;
+ int nr, working, insync, failed, spare, journal;
+ struct md_rdev *rdev;
+
+ nr = working = insync = failed = spare = journal = 0;
+ rcu_read_lock();
+ rdev_for_each_rcu(rdev, mddev) {
+ nr++;
+ if (test_bit(Faulty, &rdev->flags)) {
+ failed++;
+ } else {
+ working++;
+ if (test_bit(In_sync, &rdev->flags))
+ insync++;
+ else if (test_bit(Journal, &rdev->flags))
+ journal++;
+ else
+ spare++;
+ }
+ }
+ rcu_read_unlock();
+
+ memset(&info, 0, sizeof(info));
+ info.major_version = mddev->major_version;
+ info.minor_version = mddev->minor_version;
+ info.patch_version = MD_PATCHLEVEL_VERSION;
+ info.ctime = mddev->ctime;
+ info.level = mddev->level;
+ info.size = mddev->dev_sectors / 2;
+ info.nr_disks = nr;
+ info.raid_disks = mddev->raid_disks;
+ info.md_minor = mddev->md_minor;
+ info.not_persistent = !mddev->persistent;
+
+ info.utime = mddev->utime;
+ info.state = 0;
+ if (mddev->in_sync)
+ info.state = (1 << MD_SB_CLEAN);
+ if (mddev->bitmap && mddev->bitmap_info.offset)
+ info.state |= (1 << MD_SB_BITMAP_PRESENT);
+ if (mddev_is_clustered(mddev))
+ info.state |= (1 << MD_SB_CLUSTERED);
+ info.active_disks = insync;
+ info.working_disks = working;
+ info.failed_disks = failed;
+ info.spare_disks = spare;
+ info.journal_disks = journal;
+
+ info.layout = mddev->layout;
+ info.chunk_size = mddev->chunk_sectors << 9;
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int mdctl_get_disk_info(struct mddev *mddev, void __user *arg)
+{
+ struct mdu_disk_info64 info;
+ struct md_rdev *rdev;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ return -EFAULT;
+
+ rcu_read_lock();
+ rdev = md_find_rdev_nr_rcu(mddev, info.number);
+ if (rdev) {
+ info.major = MAJOR(rdev->bdev->bd_dev);
+ info.minor = MINOR(rdev->bdev->bd_dev);
+ info.raid_disk = rdev->raid_disk;
+ info.state = 0;
+ if (test_bit(Faulty, &rdev->flags))
+ info.state |= (1 << MD_DISK_FAULTY);
+ else if (test_bit(In_sync, &rdev->flags)) {
+ info.state |= (1 << MD_DISK_ACTIVE);
+ info.state |= (1 << MD_DISK_SYNC);
+ }
+ if (test_bit(Journal, &rdev->flags))
+ info.state |= (1 << MD_DISK_JOURNAL);
+ if (test_bit(WriteMostly, &rdev->flags))
+ info.state |= (1 << MD_DISK_WRITEMOSTLY);
+ if (test_bit(FailFast, &rdev->flags))
+ info.state |= (1 << MD_DISK_FAILFAST);
+ } else {
+ info.major = info.minor = 0;
+ info.raid_disk = -1;
+ info.state = (1 << MD_DISK_REMOVED);
+ }
+ rcu_read_unlock();
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int mdctl_set_array_info(struct mddev *mddev,
+ struct mdu_array_info64 __user *user)
+{
+ struct mdu_array_info64 info;
+ int err;
+
+ if (!user)
+ memset(&info, 0, sizeof(info));
+ else if (copy_from_user(&info, user, sizeof(info)))
+ return -EFAULT;
+
+ if (mddev->pers) {
+ err = update_array_info(mddev, &info);
+ if (err)
+ pr_warn("md: couldn't update array info. %d\n", err);
+ return err;
+ }
+
+ if (!list_empty(&mddev->disks)) {
+ pr_warn("md: array %s already has disks!\n", mdname(mddev));
+ return -EBUSY;
+ }
+
+ if (mddev->raid_disks) {
+ pr_warn("md: array %s already initialised!\n", mdname(mddev));
+ return -EBUSY;
+ }
+
+ err = md_set_array_info(mddev, &info);
+ if (err)
+ pr_warn("md: couldn't set array info. %d\n", err);
+
+ return err;
+}
+
+/*
+ * Return true if @cmd needs a configured array, false otherwise.
+ */
+static bool mdctl_ioctl_need_config(unsigned int cmd, u32 flags)
+{
+ switch (cmd) {
+ case MD_CTL_HOT_ADD_DISK:
+ case MD_CTL_HOT_REMOVE_DISK:
+ case MD_CTL_RESTART_ARRAY_RW:
+ case MD_CTL_CLUSTERED_DISK_NACK:
+ return true;
+
+ case MD_CTL_STOP_ARRAY:
+ /* switching to read-only needs an array, a full stop does not */
+ return !!(flags & MD_CTL_RO_FLAG);
+
+ default:
+ return false;
+ }
+}
+
+static bool mdctl_ioctl_need_suspend(unsigned int cmd)
+{
+ switch (cmd) {
+ case MD_CTL_ADD_NEW_DISK:
+ case MD_CTL_HOT_ADD_DISK:
+ case MD_CTL_HOT_REMOVE_DISK:
+ case MD_CTL_SET_BITMAP_FILE:
+ case MD_CTL_SET_ARRAY_INFO:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int mdctl_ioctl_valid(unsigned int cmd)
+{
+ switch (cmd) {
+ case MD_CTL_RAID_VERSION:
+ case MD_CTL_LOOKUP_ARRAY:
+ case MD_CTL_GET_ARRAY_INFO:
+ case MD_CTL_GET_DISK_INFO:
+ return 0;
+
+ case MD_CTL_SET_DISK_FAULTY:
+ case MD_CTL_GET_BITMAP_FILE:
+ case MD_CTL_ADD_NEW_DISK:
+ case MD_CTL_HOT_ADD_DISK:
+ case MD_CTL_HOT_REMOVE_DISK:
+ case MD_CTL_RESTART_ARRAY_RW:
+ case MD_CTL_RUN_ARRAY:
+ case MD_CTL_SET_ARRAY_INFO:
+ case MD_CTL_SET_BITMAP_FILE:
+ case MD_CTL_STOP_ARRAY:
+ case MD_CTL_CLUSTERED_DISK_NACK:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int mdctl_flags_valid(unsigned int cmd, u32 flags)
+{
+ /*
+ * for now, Only MD_CTL_STOP_ARRAY takes flags; other commands need
+ * flags == 0.
+ */
+ switch (cmd) {
+ case MD_CTL_RAID_VERSION:
+ case MD_CTL_LOOKUP_ARRAY:
+ case MD_CTL_GET_ARRAY_INFO:
+ case MD_CTL_GET_DISK_INFO:
+ case MD_CTL_GET_BITMAP_FILE:
+ case MD_CTL_ADD_NEW_DISK:
+ case MD_CTL_HOT_ADD_DISK:
+ case MD_CTL_HOT_REMOVE_DISK:
+ case MD_CTL_RESTART_ARRAY_RW:
+ case MD_CTL_RUN_ARRAY:
+ case MD_CTL_SET_ARRAY_INFO:
+ case MD_CTL_SET_BITMAP_FILE:
+ case MD_CTL_SET_DISK_FAULTY:
+ case MD_CTL_CLUSTERED_DISK_NACK:
+ if (flags)
+ return -ENOTTY;
+ return 0;
+ case MD_CTL_STOP_ARRAY:
+ if (flags && flags != MD_CTL_RO_FLAG)
+ return -ENOTTY;
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static long mdctl_ioctl(struct file *filp, uint cmd, ulong u)
+{
+ int err;
+ unsigned int noio_flags = 0;
+ struct mddev *mddev;
+ void __user *arg;
+ struct mdu_ioctl md_ctl;
+ bool closing = false;
+ bool suspend;
+ u32 flags;
+
+ err = mdctl_ioctl_valid(cmd);
+ if (err)
+ return err;
+
+ /*
+ * Commands dealing with the RAID driver but not any
+ * particular array:
+ */
+ if (cmd == MD_CTL_RAID_VERSION)
+ return mdctl_get_raid_version((struct mdu_version64 __user *)u);
+
+ if (copy_from_user(&md_ctl, (void __user *)u, sizeof(md_ctl)))
+ return -EFAULT;
+
+ flags = md_ctl.flags;
+
+ err = mdctl_flags_valid(cmd, flags);
+ if (err)
+ return err;
+
+ mddev = mdctl_find_mddev(&md_ctl);
+ if (IS_ERR(mddev))
+ return PTR_ERR(mddev);
+
+ if (cmd == MD_CTL_LOOKUP_ARRAY) {
+ err = mdctl_lookup_array(mddev, (struct mdu_ioctl __user *)u);
+ goto out;
+ }
+
+ arg = u64_to_user_ptr(md_ctl.arg);
+
+ /* These commands do not need reconfig_mutex. */
+ err = -ENODEV;
+ switch (cmd) {
+ case MD_CTL_GET_ARRAY_INFO:
+ if (mddev_configured(mddev))
+ err = mdctl_get_array_info(mddev, arg);
+ goto out;
+
+ case MD_CTL_GET_DISK_INFO:
+ if (mddev_configured(mddev))
+ err = mdctl_get_disk_info(mddev, arg);
+ goto out;
+
+ case MD_CTL_GET_BITMAP_FILE:
+ err = get_bitmap_file(mddev, arg);
+ goto out;
+
+ case MD_CTL_SET_DISK_FAULTY:
+ err = set_disk_faulty(mddev, new_decode_dev(md_ctl.arg));
+ goto out;
+ }
+
+ if (cmd == MD_CTL_STOP_ARRAY) {
+ /* Need to flush page cache, and ensure no openers */
+ err = mddev_set_closing_and_sync_blockdev(mddev, 0);
+ if (err)
+ goto out;
+
+ closing = true;
+ }
+
+ if (!md_is_rdwr(mddev))
+ flush_work(&mddev->sync_work);
+
+ suspend = mdctl_ioctl_need_suspend(cmd);
+ err = suspend ? mddev_suspend_and_lock(mddev) : mddev_lock(mddev);
+ if (err) {
+ pr_debug("md: ioctl lock interrupted, reason %d, cmd %d\n",
+ err, cmd);
+ goto out;
+ }
+ if (suspend)
+ noio_flags = memalloc_noio_save();
+
+ /*
+ * Commands querying/configuring an existing array.
+ *
+ * If the array is not initialised yet, check of the command requires
+ * the array to be initialised.
+ */
+ if (!mddev_configured(mddev) && mdctl_ioctl_need_config(cmd, flags)) {
+ err = -ENODEV;
+ goto unlock;
+ }
+
+ /*
+ * Commands even a read-only array can execute:
+ */
+ switch (cmd) {
+ case MD_CTL_SET_ARRAY_INFO:
+ err = mdctl_set_array_info(mddev, arg);
+ goto unlock;
+
+ case MD_CTL_RESTART_ARRAY_RW:
+ err = restart_array(mddev);
+ goto unlock;
+
+ case MD_CTL_STOP_ARRAY:
+ err = -EINVAL;
+ if (!flags)
+ err = do_md_stop(mddev, 0);
+ else if (flags == MD_CTL_RO_FLAG && mddev->pers)
+ err = md_set_readonly(mddev);
+ goto unlock;
+
+ case MD_CTL_HOT_REMOVE_DISK:
+ err = hot_remove_disk(mddev, new_decode_dev(md_ctl.arg));
+ goto unlock;
+
+ case MD_CTL_ADD_NEW_DISK:
+ /* We can support ADD_NEW_DISK on read-only arrays
+ * only if we are re-adding a preexisting device.
+ * So require mddev->pers and MD_DISK_SYNC.
+ */
+ if (mddev->pers) {
+ struct mdu_disk_info64 info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ err = -EFAULT;
+ else if (!(info.state & (1 << MD_DISK_SYNC)))
+ /* Need to clear read-only for this */
+ break;
+ else
+ err = md_add_new_disk(mddev, &info);
+ goto unlock;
+ }
+ break;
+ }
+
+ /*
+ * The remaining ioctls are changing the state of the
+ * superblock, so we do not allow them on read-only arrays.
+ */
+ if (!md_is_rdwr(mddev) && mddev->pers) {
+ if (mddev->ro != MD_AUTO_READ) {
+ err = -EROFS;
+ goto unlock;
+ }
+ mddev->ro = MD_RDWR;
+ sysfs_notify_dirent_safe(mddev->sysfs_state);
+ set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);
+ /* mddev_unlock will wake thread */
+ /* If a device failed while we were read-only, we
+ * need to make sure the metadata is updated now.
+ */
+ if (test_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags)) {
+ mddev_unlock(mddev);
+ wait_event(mddev->sb_wait,
+ !test_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags) &&
+ !test_bit(MD_SB_CHANGE_PENDING, &mddev->sb_flags));
+ mddev_lock_nointr(mddev);
+ }
+ }
+
+ switch (cmd) {
+ case MD_CTL_ADD_NEW_DISK:
+ {
+ struct mdu_disk_info64 info;
+
+ if (copy_from_user(&info, arg, sizeof(info)))
+ err = -EFAULT;
+ else
+ err = md_add_new_disk(mddev, &info);
+
+ goto unlock;
+ }
+
+ case MD_CTL_CLUSTERED_DISK_NACK:
+ if (mddev_is_clustered(mddev))
+ mddev->cluster_ops->new_disk_ack(mddev, false);
+ else
+ err = -EINVAL;
+ goto unlock;
+
+ case MD_CTL_HOT_ADD_DISK:
+ err = hot_add_disk(mddev, new_decode_dev(md_ctl.arg));
+ goto unlock;
+
+ case MD_CTL_RUN_ARRAY:
+ err = do_md_run(mddev);
+ goto unlock;
+
+ case MD_CTL_SET_BITMAP_FILE:
+ err = set_bitmap_file(mddev, md_ctl.arg);
+ goto unlock;
+
+ default:
+ err = -EINVAL;
+ goto unlock;
+ }
+
+unlock:
+ if (!err && mddev->hold_active == UNTIL_IOCTL)
+ mddev->hold_active = 0;
+
+ if (suspend) {
+ memalloc_noio_restore(noio_flags);
+ mddev_unlock_and_resume(mddev);
+ } else {
+ mddev_unlock(mddev);
+ }
+out:
+ if (closing && (err || flags == MD_CTL_RO_FLAG))
+ clear_bit(MD_CLOSING, &mddev->flags);
+
+ mddev_put(mddev);
+ return err;
+}
+
+static const struct file_operations mdctl_file_ops = {
+ .open = nonseekable_open,
+ .unlocked_ioctl = mdctl_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .owner = THIS_MODULE,
+ .llseek = noop_llseek,
+};
+
+static struct miscdevice mdctl_misc = {
+ .name = MD_CTL_NODE,
+ .minor = MISC_DYNAMIC_MINOR,
+ .fops = &mdctl_file_ops,
+};
+
+static int __init mdctl_init(void)
+{
+ return misc_register(&mdctl_misc);
+}
+
+static void __exit mdctl_exit(void)
+{
+ misc_deregister(&mdctl_misc);
+}
+
static int md_set_read_only(struct block_device *bdev, bool ro)
{
struct mddev *mddev = bdev->bd_disk->private_data;
@@ -10814,12 +11432,19 @@ static int __init md_init(void)
goto err_mdp;
mdp_major = ret;
+ ret = mdctl_init();
+ if (ret < 0)
+ goto err_ctl;
+
register_reboot_notifier(&md_notifier);
raid_table_header = register_sysctl("dev/raid", raid_table);
md_geninit();
return 0;
+err_ctl:
+ unregister_blkdev(mdp_major, "mdp");
+ mdp_major = 0;
err_mdp:
unregister_blkdev(MD_MAJOR, "md");
err_md:
@@ -11097,6 +11722,7 @@ static __exit void md_exit(void)
struct mddev *mddev;
int delay = 1;
+ mdctl_exit();
unregister_blkdev(MD_MAJOR,"md");
unregister_blkdev(mdp_major, "mdp");
unregister_reboot_notifier(&md_notifier);
diff --git a/drivers/md/md.h b/drivers/md/md.h
index 27de34a8bdf9..0dfae7b86830 100644
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -996,6 +996,15 @@ static inline void rdev_record_write_error(struct md_rdev *rdev)
set_bit(MD_RECOVERY_NEEDED, &rdev->mddev->recovery);
}
+/*
+ * Return true if array is configured or its metadata is managed by user space,
+ * false otherwise.
+ */
+static inline bool mddev_configured(struct mddev *mddev)
+{
+ return mddev->raid_disks || mddev->external;
+}
+
static inline int mddev_is_clustered(struct mddev *mddev)
{
return mddev->cluster_info && mddev->bitmap_info.nodes > 1;
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread