Loading...
1// SPDX-License-Identifier: GPL-2.0
2// Author: Kirill Smelkov (kirr@nexedi.com)
3//
4// Search for stream-like files that are using nonseekable_open and convert
5// them to stream_open. A stream-like file is a file that does not use ppos in
6// its read and write. Rationale for the conversion is to avoid deadlock in
7// between read and write.
8
9virtual report
10virtual patch
11virtual explain // explain decisions in the patch (SPFLAGS="-D explain")
12
13// stream-like reader & writer - ones that do not depend on f_pos.
14@ stream_reader @
15identifier readstream, ppos;
16identifier f, buf, len;
17type loff_t;
18@@
19 ssize_t readstream(struct file *f, char *buf, size_t len, loff_t *ppos)
20 {
21 ... when != ppos
22 }
23
24@ stream_writer @
25identifier writestream, ppos;
26identifier f, buf, len;
27type loff_t;
28@@
29 ssize_t writestream(struct file *f, const char *buf, size_t len, loff_t *ppos)
30 {
31 ... when != ppos
32 }
33
34
35// a function that blocks
36@ blocks @
37identifier block_f;
38identifier wait =~ "^wait_.*";
39@@
40 block_f(...) {
41 ... when exists
42 wait(...)
43 ... when exists
44 }
45
46// stream_reader that can block inside.
47//
48// XXX wait_* can be called not directly from current function (e.g. func -> f -> g -> wait())
49// XXX currently reader_blocks supports only direct and 1-level indirect cases.
50@ reader_blocks_direct @
51identifier stream_reader.readstream;
52identifier wait =~ "^wait_.*";
53@@
54 readstream(...)
55 {
56 ... when exists
57 wait(...)
58 ... when exists
59 }
60
61@ reader_blocks_1 @
62identifier stream_reader.readstream;
63identifier blocks.block_f;
64@@
65 readstream(...)
66 {
67 ... when exists
68 block_f(...)
69 ... when exists
70 }
71
72@ reader_blocks depends on reader_blocks_direct || reader_blocks_1 @
73identifier stream_reader.readstream;
74@@
75 readstream(...) {
76 ...
77 }
78
79
80// file_operations + whether they have _any_ .read, .write, .llseek ... at all.
81//
82// XXX add support for file_operations xxx[N] = ... (sound/core/pcm_native.c)
83@ fops0 @
84identifier fops;
85@@
86 struct file_operations fops = {
87 ...
88 };
89
90@ has_read @
91identifier fops0.fops;
92identifier read_f;
93@@
94 struct file_operations fops = {
95 .read = read_f,
96 };
97
98@ has_read_iter @
99identifier fops0.fops;
100identifier read_iter_f;
101@@
102 struct file_operations fops = {
103 .read_iter = read_iter_f,
104 };
105
106@ has_write @
107identifier fops0.fops;
108identifier write_f;
109@@
110 struct file_operations fops = {
111 .write = write_f,
112 };
113
114@ has_write_iter @
115identifier fops0.fops;
116identifier write_iter_f;
117@@
118 struct file_operations fops = {
119 .write_iter = write_iter_f,
120 };
121
122@ has_llseek @
123identifier fops0.fops;
124identifier llseek_f;
125@@
126 struct file_operations fops = {
127 .llseek = llseek_f,
128 };
129
130@ has_no_llseek @
131identifier fops0.fops;
132@@
133 struct file_operations fops = {
134 .llseek = no_llseek,
135 };
136
137@ has_noop_llseek @
138identifier fops0.fops;
139@@
140 struct file_operations fops = {
141 .llseek = noop_llseek,
142 };
143
144@ has_mmap @
145identifier fops0.fops;
146identifier mmap_f;
147@@
148 struct file_operations fops = {
149 .mmap = mmap_f,
150 };
151
152@ has_copy_file_range @
153identifier fops0.fops;
154identifier copy_file_range_f;
155@@
156 struct file_operations fops = {
157 .copy_file_range = copy_file_range_f,
158 };
159
160@ has_remap_file_range @
161identifier fops0.fops;
162identifier remap_file_range_f;
163@@
164 struct file_operations fops = {
165 .remap_file_range = remap_file_range_f,
166 };
167
168@ has_splice_read @
169identifier fops0.fops;
170identifier splice_read_f;
171@@
172 struct file_operations fops = {
173 .splice_read = splice_read_f,
174 };
175
176@ has_splice_write @
177identifier fops0.fops;
178identifier splice_write_f;
179@@
180 struct file_operations fops = {
181 .splice_write = splice_write_f,
182 };
183
184
185// file_operations that is candidate for stream_open conversion - it does not
186// use mmap and other methods that assume @offset access to file.
187//
188// XXX for simplicity require no .{read/write}_iter and no .splice_{read/write} for now.
189// XXX maybe_steam.fops cannot be used in other rules - it gives "bad rule maybe_stream or bad variable fops".
190@ maybe_stream depends on (!has_llseek || has_no_llseek || has_noop_llseek) && !has_mmap && !has_copy_file_range && !has_remap_file_range && !has_read_iter && !has_write_iter && !has_splice_read && !has_splice_write @
191identifier fops0.fops;
192@@
193 struct file_operations fops = {
194 };
195
196
197// ---- conversions ----
198
199// XXX .open = nonseekable_open -> .open = stream_open
200// XXX .open = func -> openfunc -> nonseekable_open
201
202// read & write
203//
204// if both are used in the same file_operations together with an opener -
205// under that conditions we can use stream_open instead of nonseekable_open.
206@ fops_rw depends on maybe_stream @
207identifier fops0.fops, openfunc;
208identifier stream_reader.readstream;
209identifier stream_writer.writestream;
210@@
211 struct file_operations fops = {
212 .open = openfunc,
213 .read = readstream,
214 .write = writestream,
215 };
216
217@ report_rw depends on report @
218identifier fops_rw.openfunc;
219position p1;
220@@
221 openfunc(...) {
222 <...
223 nonseekable_open@p1
224 ...>
225 }
226
227@ script:python depends on report && reader_blocks @
228fops << fops0.fops;
229p << report_rw.p1;
230@@
231coccilib.report.print_report(p[0],
232 "ERROR: %s: .read() can deadlock .write(); change nonseekable_open -> stream_open to fix." % (fops,))
233
234@ script:python depends on report && !reader_blocks @
235fops << fops0.fops;
236p << report_rw.p1;
237@@
238coccilib.report.print_report(p[0],
239 "WARNING: %s: .read() and .write() have stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
240
241
242@ explain_rw_deadlocked depends on explain && reader_blocks @
243identifier fops_rw.openfunc;
244@@
245 openfunc(...) {
246 <...
247- nonseekable_open
248+ nonseekable_open /* read & write (was deadlock) */
249 ...>
250 }
251
252
253@ explain_rw_nodeadlock depends on explain && !reader_blocks @
254identifier fops_rw.openfunc;
255@@
256 openfunc(...) {
257 <...
258- nonseekable_open
259+ nonseekable_open /* read & write (no direct deadlock) */
260 ...>
261 }
262
263@ patch_rw depends on patch @
264identifier fops_rw.openfunc;
265@@
266 openfunc(...) {
267 <...
268- nonseekable_open
269+ stream_open
270 ...>
271 }
272
273
274// read, but not write
275@ fops_r depends on maybe_stream && !has_write @
276identifier fops0.fops, openfunc;
277identifier stream_reader.readstream;
278@@
279 struct file_operations fops = {
280 .open = openfunc,
281 .read = readstream,
282 };
283
284@ report_r depends on report @
285identifier fops_r.openfunc;
286position p1;
287@@
288 openfunc(...) {
289 <...
290 nonseekable_open@p1
291 ...>
292 }
293
294@ script:python depends on report @
295fops << fops0.fops;
296p << report_r.p1;
297@@
298coccilib.report.print_report(p[0],
299 "WARNING: %s: .read() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
300
301@ explain_r depends on explain @
302identifier fops_r.openfunc;
303@@
304 openfunc(...) {
305 <...
306- nonseekable_open
307+ nonseekable_open /* read only */
308 ...>
309 }
310
311@ patch_r depends on patch @
312identifier fops_r.openfunc;
313@@
314 openfunc(...) {
315 <...
316- nonseekable_open
317+ stream_open
318 ...>
319 }
320
321
322// write, but not read
323@ fops_w depends on maybe_stream && !has_read @
324identifier fops0.fops, openfunc;
325identifier stream_writer.writestream;
326@@
327 struct file_operations fops = {
328 .open = openfunc,
329 .write = writestream,
330 };
331
332@ report_w depends on report @
333identifier fops_w.openfunc;
334position p1;
335@@
336 openfunc(...) {
337 <...
338 nonseekable_open@p1
339 ...>
340 }
341
342@ script:python depends on report @
343fops << fops0.fops;
344p << report_w.p1;
345@@
346coccilib.report.print_report(p[0],
347 "WARNING: %s: .write() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
348
349@ explain_w depends on explain @
350identifier fops_w.openfunc;
351@@
352 openfunc(...) {
353 <...
354- nonseekable_open
355+ nonseekable_open /* write only */
356 ...>
357 }
358
359@ patch_w depends on patch @
360identifier fops_w.openfunc;
361@@
362 openfunc(...) {
363 <...
364- nonseekable_open
365+ stream_open
366 ...>
367 }
368
369
370// no read, no write - don't change anything
1// SPDX-License-Identifier: GPL-2.0
2// Author: Kirill Smelkov (kirr@nexedi.com)
3//
4// Search for stream-like files that are using nonseekable_open and convert
5// them to stream_open. A stream-like file is a file that does not use ppos in
6// its read and write. Rationale for the conversion is to avoid deadlock in
7// between read and write.
8
9virtual report
10virtual patch
11virtual explain // explain decisions in the patch (SPFLAGS="-D explain")
12
13// stream-like reader & writer - ones that do not depend on f_pos.
14@ stream_reader @
15identifier readstream, ppos;
16identifier f, buf, len;
17type loff_t;
18@@
19 ssize_t readstream(struct file *f, char *buf, size_t len, loff_t *ppos)
20 {
21 ... when != ppos
22 }
23
24@ stream_writer @
25identifier writestream, ppos;
26identifier f, buf, len;
27type loff_t;
28@@
29 ssize_t writestream(struct file *f, const char *buf, size_t len, loff_t *ppos)
30 {
31 ... when != ppos
32 }
33
34
35// a function that blocks
36@ blocks @
37identifier block_f;
38identifier wait =~ "^wait_.*";
39@@
40 block_f(...) {
41 ... when exists
42 wait(...)
43 ... when exists
44 }
45
46// stream_reader that can block inside.
47//
48// XXX wait_* can be called not directly from current function (e.g. func -> f -> g -> wait())
49// XXX currently reader_blocks supports only direct and 1-level indirect cases.
50@ reader_blocks_direct @
51identifier stream_reader.readstream;
52identifier wait =~ "^wait_.*";
53@@
54 readstream(...)
55 {
56 ... when exists
57 wait(...)
58 ... when exists
59 }
60
61@ reader_blocks_1 @
62identifier stream_reader.readstream;
63identifier blocks.block_f;
64@@
65 readstream(...)
66 {
67 ... when exists
68 block_f(...)
69 ... when exists
70 }
71
72@ reader_blocks depends on reader_blocks_direct || reader_blocks_1 @
73identifier stream_reader.readstream;
74@@
75 readstream(...) {
76 ...
77 }
78
79
80// file_operations + whether they have _any_ .read, .write, .llseek ... at all.
81//
82// XXX add support for file_operations xxx[N] = ... (sound/core/pcm_native.c)
83@ fops0 @
84identifier fops;
85@@
86 struct file_operations fops = {
87 ...
88 };
89
90@ has_read @
91identifier fops0.fops;
92identifier read_f;
93@@
94 struct file_operations fops = {
95 .read = read_f,
96 };
97
98@ has_read_iter @
99identifier fops0.fops;
100identifier read_iter_f;
101@@
102 struct file_operations fops = {
103 .read_iter = read_iter_f,
104 };
105
106@ has_write @
107identifier fops0.fops;
108identifier write_f;
109@@
110 struct file_operations fops = {
111 .write = write_f,
112 };
113
114@ has_write_iter @
115identifier fops0.fops;
116identifier write_iter_f;
117@@
118 struct file_operations fops = {
119 .write_iter = write_iter_f,
120 };
121
122@ has_llseek @
123identifier fops0.fops;
124identifier llseek_f;
125@@
126 struct file_operations fops = {
127 .llseek = llseek_f,
128 };
129
130@ has_no_llseek @
131identifier fops0.fops;
132@@
133 struct file_operations fops = {
134 .llseek = no_llseek,
135 };
136
137@ has_noop_llseek @
138identifier fops0.fops;
139@@
140 struct file_operations fops = {
141 .llseek = noop_llseek,
142 };
143
144@ has_mmap @
145identifier fops0.fops;
146identifier mmap_f;
147@@
148 struct file_operations fops = {
149 .mmap = mmap_f,
150 };
151
152@ has_copy_file_range @
153identifier fops0.fops;
154identifier copy_file_range_f;
155@@
156 struct file_operations fops = {
157 .copy_file_range = copy_file_range_f,
158 };
159
160@ has_remap_file_range @
161identifier fops0.fops;
162identifier remap_file_range_f;
163@@
164 struct file_operations fops = {
165 .remap_file_range = remap_file_range_f,
166 };
167
168@ has_splice_read @
169identifier fops0.fops;
170identifier splice_read_f;
171@@
172 struct file_operations fops = {
173 .splice_read = splice_read_f,
174 };
175
176@ has_splice_write @
177identifier fops0.fops;
178identifier splice_write_f;
179@@
180 struct file_operations fops = {
181 .splice_write = splice_write_f,
182 };
183
184
185// file_operations that is candidate for stream_open conversion - it does not
186// use mmap and other methods that assume @offset access to file.
187//
188// XXX for simplicity require no .{read/write}_iter and no .splice_{read/write} for now.
189// XXX maybe_steam.fops cannot be used in other rules - it gives "bad rule maybe_stream or bad variable fops".
190@ maybe_stream depends on (!has_llseek || has_no_llseek || has_noop_llseek) && !has_mmap && !has_copy_file_range && !has_remap_file_range && !has_read_iter && !has_write_iter && !has_splice_read && !has_splice_write @
191identifier fops0.fops;
192@@
193 struct file_operations fops = {
194 };
195
196
197// ---- conversions ----
198
199// XXX .open = nonseekable_open -> .open = stream_open
200// XXX .open = func -> openfunc -> nonseekable_open
201
202// read & write
203//
204// if both are used in the same file_operations together with an opener -
205// under that conditions we can use stream_open instead of nonseekable_open.
206@ fops_rw depends on maybe_stream @
207identifier fops0.fops, openfunc;
208identifier stream_reader.readstream;
209identifier stream_writer.writestream;
210@@
211 struct file_operations fops = {
212 .open = openfunc,
213 .read = readstream,
214 .write = writestream,
215 };
216
217@ report_rw depends on report @
218identifier fops_rw.openfunc;
219position p1;
220@@
221 openfunc(...) {
222 <...
223 nonseekable_open@p1
224 ...>
225 }
226
227@ script:python depends on report && reader_blocks @
228fops << fops0.fops;
229p << report_rw.p1;
230@@
231coccilib.report.print_report(p[0],
232 "ERROR: %s: .read() can deadlock .write(); change nonseekable_open -> stream_open to fix." % (fops,))
233
234@ script:python depends on report && !reader_blocks @
235fops << fops0.fops;
236p << report_rw.p1;
237@@
238coccilib.report.print_report(p[0],
239 "WARNING: %s: .read() and .write() have stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
240
241
242@ explain_rw_deadlocked depends on explain && reader_blocks @
243identifier fops_rw.openfunc;
244@@
245 openfunc(...) {
246 <...
247- nonseekable_open
248+ nonseekable_open /* read & write (was deadlock) */
249 ...>
250 }
251
252
253@ explain_rw_nodeadlock depends on explain && !reader_blocks @
254identifier fops_rw.openfunc;
255@@
256 openfunc(...) {
257 <...
258- nonseekable_open
259+ nonseekable_open /* read & write (no direct deadlock) */
260 ...>
261 }
262
263@ patch_rw depends on patch @
264identifier fops_rw.openfunc;
265@@
266 openfunc(...) {
267 <...
268- nonseekable_open
269+ stream_open
270 ...>
271 }
272
273
274// read, but not write
275@ fops_r depends on maybe_stream && !has_write @
276identifier fops0.fops, openfunc;
277identifier stream_reader.readstream;
278@@
279 struct file_operations fops = {
280 .open = openfunc,
281 .read = readstream,
282 };
283
284@ report_r depends on report @
285identifier fops_r.openfunc;
286position p1;
287@@
288 openfunc(...) {
289 <...
290 nonseekable_open@p1
291 ...>
292 }
293
294@ script:python depends on report @
295fops << fops0.fops;
296p << report_r.p1;
297@@
298coccilib.report.print_report(p[0],
299 "WARNING: %s: .read() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
300
301@ explain_r depends on explain @
302identifier fops_r.openfunc;
303@@
304 openfunc(...) {
305 <...
306- nonseekable_open
307+ nonseekable_open /* read only */
308 ...>
309 }
310
311@ patch_r depends on patch @
312identifier fops_r.openfunc;
313@@
314 openfunc(...) {
315 <...
316- nonseekable_open
317+ stream_open
318 ...>
319 }
320
321
322// write, but not read
323@ fops_w depends on maybe_stream && !has_read @
324identifier fops0.fops, openfunc;
325identifier stream_writer.writestream;
326@@
327 struct file_operations fops = {
328 .open = openfunc,
329 .write = writestream,
330 };
331
332@ report_w depends on report @
333identifier fops_w.openfunc;
334position p1;
335@@
336 openfunc(...) {
337 <...
338 nonseekable_open@p1
339 ...>
340 }
341
342@ script:python depends on report @
343fops << fops0.fops;
344p << report_w.p1;
345@@
346coccilib.report.print_report(p[0],
347 "WARNING: %s: .write() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
348
349@ explain_w depends on explain @
350identifier fops_w.openfunc;
351@@
352 openfunc(...) {
353 <...
354- nonseekable_open
355+ nonseekable_open /* write only */
356 ...>
357 }
358
359@ patch_w depends on patch @
360identifier fops_w.openfunc;
361@@
362 openfunc(...) {
363 <...
364- nonseekable_open
365+ stream_open
366 ...>
367 }
368
369
370// no read, no write - don't change anything