xref: /linux/block/error-injection.c (revision 056e065a6b6e01ab54bb9770c0d5a15350e571e2)
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 	clear_bit(GD_ERROR_INJECT, &disk->state);
124 	while ((inj = list_first_entry_or_null(&disk->error_injection_list,
125 			struct blk_error_inject, entry))) {
126 		list_del_rcu(&inj->entry);
127 		kfree_rcu_mightsleep(inj);
128 	}
129 	static_branch_dec(&blk_error_injection_enabled);
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