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
__blk_error_inject(struct bio * bio)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
error_inject_add(struct gendisk * disk,enum req_op op,sector_t start,u64 nr_sectors,blk_status_t status,unsigned int chance)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
error_inject_removeall(struct gendisk * disk)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
match_op(substring_t * args,enum req_op * op)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
match_status(substring_t * args,blk_status_t * status)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
blk_error_injection_parse_options(struct gendisk * disk,char * options)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
blk_error_injection_write(struct file * file,const char __user * ubuf,size_t count,loff_t * pos)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
blk_error_injection_show(struct seq_file * s,void * private)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
blk_error_injection_open(struct inode * inode,struct file * file)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
blk_error_injection_release(struct inode * inode,struct file * file)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
blk_error_injection_init(struct gendisk * disk)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
blk_error_injection_exit(struct gendisk * disk)313 void blk_error_injection_exit(struct gendisk *disk)
314 {
315 error_inject_removeall(disk);
316 }
317