1 // SPDX-License-Identifier: GPL-2.0 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 /* only inject every 1 / chance times */ 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 status=%s,chance=%u", 280 inj->start, inj->end, 281 blk_status_to_tag(inj->status), inj->chance); 282 seq_putc(s, '\n'); 283 } 284 rcu_read_unlock(); 285 return 0; 286 } 287 288 static int blk_error_injection_open(struct inode *inode, struct file *file) 289 { 290 return single_open(file, blk_error_injection_show, inode->i_private); 291 } 292 293 static int blk_error_injection_release(struct inode *inode, struct file *file) 294 { 295 return single_release(inode, file); 296 } 297 298 static const struct file_operations blk_error_injection_fops = { 299 .owner = THIS_MODULE, 300 .write = blk_error_injection_write, 301 .read = seq_read, 302 .open = blk_error_injection_open, 303 .release = blk_error_injection_release, 304 }; 305 306 void blk_error_injection_init(struct gendisk *disk) 307 { 308 debugfs_create_file("error_injection", 0600, disk->queue->debugfs_dir, 309 disk, &blk_error_injection_fops); 310 } 311 312 void blk_error_injection_exit(struct gendisk *disk) 313 { 314 error_inject_removeall(disk); 315 } 316