| 1 | |
| 2 | |
| 3 | * Copyright (c) 2026 Christoph Hellwig.
|
| 4 |
|
| 5 | #include <linux/debugfs.h>
|
| 6 | #include <linux/blkdev.h>
|
| 7 | #include <linux/parser.h>
|
| 8 | #include <linux/seq_file.h>
|
| 9 | #include "blk.h"
|
| 10 | #include "error-injection.h"
|
| 11 |
|
| 12 | struct blk_error_inject {
|
| 13 | struct list_head entry;
|
| 14 | sector_t start;
|
| 15 | sector_t end;
|
| 16 | enum req_op op;
|
| 17 | blk_status_t status;
|
| 18 |
|
| 19 |
|
| 20 | unsigned int chance;
|
| 21 | };
|
| 22 |
|
| 23 | DEFINE_STATIC_KEY_FALSE(blk_error_injection_enabled);
|
| 24 |
|
| 25 | bool __blk_error_inject(struct bio *bio)
|
| 26 | {
|
| 27 | struct gendisk *disk = bio->bi_bdev->bd_disk;
|
| 28 | struct blk_error_inject *inj;
|
| 29 |
|
| 30 | rcu_read_lock();
|
| 31 | list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) {
|
| 32 | if (bio_op(bio) != inj->op)
|
| 33 | continue;
|
| 34 | |
| 35 | * This never matches 0-sized bios like empty WRITEs with
|
| 36 | * REQ_PREFLUSH or ZONE_RESET_ALL. While adding a special case
|
| 37 | * for them would be trivial, that means any WRITE rule would
|
| 38 | * trigger for flushes. So before we can make this work
|
| 39 | * properly, we'll need to start using REQ_OP_FLUSH for pure
|
| 40 | * flushes at the bio level like we already do in blk-mq.
|
| 41 |
|
| 42 | if (bio->bi_iter.bi_sector > inj->end ||
|
| 43 | bio_end_sector(bio) <= inj->start)
|
| 44 | continue;
|
| 45 | if (inj->chance > 1 && (get_random_u32() % inj->chance) != 0)
|
| 46 | continue;
|
| 47 |
|
| 48 | pr_info_ratelimited("%pg: injecting %s error for %s at sector %llu:%u\n",
|
| 49 | disk->part0, blk_status_to_str(inj->status),
|
| 50 | blk_op_str(inj->op), bio->bi_iter.bi_sector,
|
| 51 | bio_sectors(bio));
|
| 52 | bio->bi_status = inj->status;
|
| 53 | rcu_read_unlock();
|
| 54 | bio_endio(bio);
|
| 55 | return true;
|
| 56 | }
|
| 57 | rcu_read_unlock();
|
| 58 | return false;
|
| 59 | }
|
| 60 |
|
| 61 | static int error_inject_add(struct gendisk *disk, enum req_op op,
|
| 62 | sector_t start, u64 nr_sectors, blk_status_t status,
|
| 63 | unsigned int chance)
|
| 64 | {
|
| 65 | struct blk_error_inject *inj;
|
| 66 | int error = -EINVAL;
|
| 67 |
|
| 68 | if (op == REQ_OP_LAST)
|
| 69 | return -EINVAL;
|
| 70 | if (status == BLK_STS_OK)
|
| 71 | return -EINVAL;
|
| 72 |
|
| 73 | inj = kzalloc_obj(*inj);
|
| 74 | if (!inj)
|
| 75 | return -ENOMEM;
|
| 76 |
|
| 77 | if (nr_sectors) {
|
| 78 | if (U64_MAX - nr_sectors < start)
|
| 79 | goto out_free_inj;
|
| 80 | inj->end = start + nr_sectors - 1;
|
| 81 | } else {
|
| 82 | inj->end = U64_MAX;
|
| 83 | }
|
| 84 |
|
| 85 | inj->op = op;
|
| 86 | inj->start = start;
|
| 87 | inj->status = status;
|
| 88 | inj->chance = chance;
|
| 89 |
|
| 90 | pr_debug_ratelimited("%pg: adding %s injection for %s at sector %llu:%llu\n",
|
| 91 | disk->part0, blk_status_to_str(status),
|
| 92 | blk_op_str(op),
|
| 93 | start, nr_sectors);
|
| 94 |
|
| 95 | |
| 96 | * Add to the front of the list so that newer entries can partially
|
| 97 | * override other entries. This also intentionally allows duplicate
|
| 98 | * entries as there is no real reason to reject them.
|
| 99 |
|
| 100 | mutex_lock(&disk->error_injection_lock);
|
| 101 | if (!disk_live(disk)) {
|
| 102 | mutex_unlock(&disk->error_injection_lock);
|
| 103 | error = -ENODEV;
|
| 104 | goto out_free_inj;
|
| 105 | }
|
| 106 | if (list_empty(&disk->error_injection_list))
|
| 107 | static_branch_inc(&blk_error_injection_enabled);
|
| 108 | list_add_rcu(&inj->entry, &disk->error_injection_list);
|
| 109 | set_bit(GD_ERROR_INJECT, &disk->state);
|
| 110 | mutex_unlock(&disk->error_injection_lock);
|
| 111 | return 0;
|
| 112 |
|
| 113 | out_free_inj:
|
| 114 | kfree(inj);
|
| 115 | return error;
|
| 116 | }
|
| 117 |
|
| 118 | static void error_inject_removeall(struct gendisk *disk)
|
| 119 | {
|
| 120 | struct blk_error_inject *inj;
|
| 121 |
|
| 122 | mutex_lock(&disk->error_injection_lock);
|
| 123 | if (test_and_clear_bit(GD_ERROR_INJECT, &disk->state))
|
| 124 | static_branch_dec(&blk_error_injection_enabled);
|
| 125 | while ((inj = list_first_entry_or_null(&disk->error_injection_list,
|
| 126 | struct blk_error_inject, entry))) {
|
| 127 | list_del_rcu(&inj->entry);
|
| 128 | kfree_rcu_mightsleep(inj);
|
| 129 | }
|
| 130 | mutex_unlock(&disk->error_injection_lock);
|
| 131 | }
|
| 132 |
|
| 133 | enum options {
|
| 134 | Opt_add = (1u << 0),
|
| 135 | Opt_removeall = (1u << 1),
|
| 136 |
|
| 137 | Opt_op = (1u << 16),
|
| 138 | Opt_start = (1u << 17),
|
| 139 | Opt_nr_sectors = (1u << 18),
|
| 140 | Opt_status = (1u << 19),
|
| 141 | Opt_chance = (1u << 20),
|
| 142 |
|
| 143 | Opt_invalid,
|
| 144 | };
|
| 145 |
|
| 146 | static const match_table_t opt_tokens = {
|
| 147 | { Opt_add, "add", },
|
| 148 | { Opt_removeall, "removeall", },
|
| 149 | { Opt_op, "op=%s", },
|
| 150 | { Opt_start, "start=%u" },
|
| 151 | { Opt_nr_sectors, "nr_sectors=%u" },
|
| 152 | { Opt_status, "status=%s" },
|
| 153 | { Opt_chance, "chance=%u" },
|
| 154 | { Opt_invalid, NULL, },
|
| 155 | };
|
| 156 |
|
| 157 | static int match_op(substring_t *args, enum req_op *op)
|
| 158 | {
|
| 159 | const char *tag;
|
| 160 |
|
| 161 | tag = match_strdup(args);
|
| 162 | if (!tag)
|
| 163 | return -ENOMEM;
|
| 164 | *op = str_to_blk_op(tag);
|
| 165 | if (*op == REQ_OP_LAST)
|
| 166 | pr_warn("invalid op '%s'\n", tag);
|
| 167 | kfree(tag);
|
| 168 | return 0;
|
| 169 | }
|
| 170 |
|
| 171 | static int match_status(substring_t *args, blk_status_t *status)
|
| 172 | {
|
| 173 | const char *tag;
|
| 174 |
|
| 175 | tag = match_strdup(args);
|
| 176 | if (!tag)
|
| 177 | return -ENOMEM;
|
| 178 | *status = tag_to_blk_status(tag);
|
| 179 | if (!*status)
|
| 180 | pr_warn("invalid status '%s'\n", tag);
|
| 181 | kfree(tag);
|
| 182 | return 0;
|
| 183 | }
|
| 184 |
|
| 185 | static ssize_t blk_error_injection_parse_options(struct gendisk *disk,
|
| 186 | char *options)
|
| 187 | {
|
| 188 | enum { Unset, Add, Removeall } action = Unset;
|
| 189 | unsigned int option_mask = 0, chance = 1;
|
| 190 | enum req_op op = REQ_OP_LAST;
|
| 191 | u64 start = 0, nr_sectors = 0;
|
| 192 | blk_status_t status = BLK_STS_OK;
|
| 193 | substring_t args[MAX_OPT_ARGS];
|
| 194 | char *p;
|
| 195 |
|
| 196 | while ((p = strsep(&options, ",\n")) != NULL) {
|
| 197 | int error = 0;
|
| 198 | ssize_t token;
|
| 199 |
|
| 200 | if (!*p)
|
| 201 | continue;
|
| 202 | token = match_token(p, opt_tokens, args);
|
| 203 | option_mask |= token;
|
| 204 | switch (token) {
|
| 205 | case Opt_add:
|
| 206 | if (action != Unset)
|
| 207 | return -EINVAL;
|
| 208 | action = Add;
|
| 209 | break;
|
| 210 | case Opt_removeall:
|
| 211 | if (action != Unset)
|
| 212 | return -EINVAL;
|
| 213 | action = Removeall;
|
| 214 | break;
|
| 215 | case Opt_op:
|
| 216 | error = match_op(args, &op);
|
| 217 | break;
|
| 218 | case Opt_start:
|
| 219 | error = match_u64(args, &start);
|
| 220 | break;
|
| 221 | case Opt_nr_sectors:
|
| 222 | error = match_u64(args, &nr_sectors);
|
| 223 | break;
|
| 224 | case Opt_status:
|
| 225 | error = match_status(args, &status);
|
| 226 | break;
|
| 227 | case Opt_chance:
|
| 228 | error = match_uint(args, &chance);
|
| 229 | if (!error && chance == 0)
|
| 230 | error = -EINVAL;
|
| 231 | break;
|
| 232 | default:
|
| 233 | pr_warn("unknown parameter or missing value '%s'\n", p);
|
| 234 | error = -EINVAL;
|
| 235 | }
|
| 236 | if (error)
|
| 237 | return error;
|
| 238 | }
|
| 239 |
|
| 240 | switch (action) {
|
| 241 | case Add:
|
| 242 | return error_inject_add(disk, op, start, nr_sectors, status,
|
| 243 | chance);
|
| 244 | case Removeall:
|
| 245 | if (option_mask & ~Opt_removeall)
|
| 246 | return -EINVAL;
|
| 247 | error_inject_removeall(disk);
|
| 248 | return 0;
|
| 249 | default:
|
| 250 | return -EINVAL;
|
| 251 | }
|
| 252 | }
|
| 253 |
|
| 254 | static ssize_t blk_error_injection_write(struct file *file,
|
| 255 | const char __user *ubuf, size_t count, loff_t *pos)
|
| 256 | {
|
| 257 | struct gendisk *disk = file_inode(file)->i_private;
|
| 258 | char *options;
|
| 259 | int error;
|
| 260 |
|
| 261 | options = memdup_user_nul(ubuf, count);
|
| 262 | if (IS_ERR(options))
|
| 263 | return PTR_ERR(options);
|
| 264 | error = blk_error_injection_parse_options(disk, options);
|
| 265 | kfree(options);
|
| 266 |
|
| 267 | if (error)
|
| 268 | return error;
|
| 269 | return count;
|
| 270 | }
|
| 271 |
|
| 272 | static int blk_error_injection_show(struct seq_file *s, void *private)
|
| 273 | {
|
| 274 | struct gendisk *disk = s->private;
|
| 275 | struct blk_error_inject *inj;
|
| 276 |
|
| 277 | rcu_read_lock();
|
| 278 | list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) {
|
| 279 | seq_printf(s, "%llu:%llu op=%s,status=%s,chance=%u",
|
| 280 | inj->start, inj->end,
|
| 281 | blk_op_str(inj->op),
|
| 282 | blk_status_to_tag(inj->status), inj->chance);
|
| 283 | seq_putc(s, '\n');
|
| 284 | }
|
| 285 | rcu_read_unlock();
|
| 286 | return 0;
|
| 287 | }
|
| 288 |
|
| 289 | static int blk_error_injection_open(struct inode *inode, struct file *file)
|
| 290 | {
|
| 291 | return single_open(file, blk_error_injection_show, inode->i_private);
|
| 292 | }
|
| 293 |
|
| 294 | static int blk_error_injection_release(struct inode *inode, struct file *file)
|
| 295 | {
|
| 296 | return single_release(inode, file);
|
| 297 | }
|
| 298 |
|
| 299 | static const struct file_operations blk_error_injection_fops = {
|
| 300 | .owner = THIS_MODULE,
|
| 301 | .write = blk_error_injection_write,
|
| 302 | .read = seq_read,
|
| 303 | .open = blk_error_injection_open,
|
| 304 | .release = blk_error_injection_release,
|
| 305 | };
|
| 306 |
|
| 307 | void blk_error_injection_init(struct gendisk *disk)
|
| 308 | {
|
| 309 | debugfs_create_file("error_injection", 0600, disk->queue->debugfs_dir,
|
| 310 | disk, &blk_error_injection_fops);
|
| 311 | }
|
| 312 |
|
| 313 | void blk_error_injection_exit(struct gendisk *disk)
|
| 314 | {
|
| 315 | error_inject_removeall(disk);
|
| 316 | }
|