GNU Linux-libre 5.10.217-gnu1
[releases.git] / scripts / coccinelle / api / stream_open.cocci
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
9 virtual report
10 virtual patch
11 virtual 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 @
15 identifier readstream, ppos;
16 identifier f, buf, len;
17 type 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 @
25 identifier writestream, ppos;
26 identifier f, buf, len;
27 type 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 @
37 identifier block_f;
38 identifier 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 @
51 identifier stream_reader.readstream;
52 identifier wait =~ "^wait_.*";
53 @@
54   readstream(...)
55   {
56     ... when exists
57     wait(...)
58     ... when exists
59   }
60
61 @ reader_blocks_1 @
62 identifier stream_reader.readstream;
63 identifier 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 @
73 identifier 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 @
84 identifier fops;
85 @@
86   struct file_operations fops = {
87     ...
88   };
89
90 @ has_read @
91 identifier fops0.fops;
92 identifier read_f;
93 @@
94   struct file_operations fops = {
95     .read = read_f,
96   };
97
98 @ has_read_iter @
99 identifier fops0.fops;
100 identifier read_iter_f;
101 @@
102   struct file_operations fops = {
103     .read_iter = read_iter_f,
104   };
105
106 @ has_write @
107 identifier fops0.fops;
108 identifier write_f;
109 @@
110   struct file_operations fops = {
111     .write = write_f,
112   };
113
114 @ has_write_iter @
115 identifier fops0.fops;
116 identifier write_iter_f;
117 @@
118   struct file_operations fops = {
119     .write_iter = write_iter_f,
120   };
121
122 @ has_llseek @
123 identifier fops0.fops;
124 identifier llseek_f;
125 @@
126   struct file_operations fops = {
127     .llseek = llseek_f,
128   };
129
130 @ has_no_llseek @
131 identifier fops0.fops;
132 @@
133   struct file_operations fops = {
134     .llseek = no_llseek,
135   };
136
137 @ has_noop_llseek @
138 identifier fops0.fops;
139 @@
140   struct file_operations fops = {
141     .llseek = noop_llseek,
142   };
143
144 @ has_mmap @
145 identifier fops0.fops;
146 identifier mmap_f;
147 @@
148   struct file_operations fops = {
149     .mmap = mmap_f,
150   };
151
152 @ has_copy_file_range @
153 identifier fops0.fops;
154 identifier 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 @
161 identifier fops0.fops;
162 identifier remap_file_range_f;
163 @@
164   struct file_operations fops = {
165     .remap_file_range = remap_file_range_f,
166   };
167
168 @ has_splice_read @
169 identifier fops0.fops;
170 identifier splice_read_f;
171 @@
172   struct file_operations fops = {
173     .splice_read = splice_read_f,
174   };
175
176 @ has_splice_write @
177 identifier fops0.fops;
178 identifier 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 @
191 identifier 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 @
207 identifier fops0.fops, openfunc;
208 identifier stream_reader.readstream;
209 identifier 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 @
218 identifier fops_rw.openfunc;
219 position p1;
220 @@
221   openfunc(...) {
222     <...
223      nonseekable_open@p1
224     ...>
225   }
226
227 @ script:python depends on report && reader_blocks @
228 fops << fops0.fops;
229 p << report_rw.p1;
230 @@
231 coccilib.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 @
235 fops << fops0.fops;
236 p << report_rw.p1;
237 @@
238 coccilib.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 @
243 identifier 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 @
254 identifier 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 @
264 identifier 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 @
276 identifier fops0.fops, openfunc;
277 identifier stream_reader.readstream;
278 @@
279   struct file_operations fops = {
280       .open  = openfunc,
281       .read  = readstream,
282   };
283
284 @ report_r depends on report @
285 identifier fops_r.openfunc;
286 position p1;
287 @@
288   openfunc(...) {
289     <...
290     nonseekable_open@p1
291     ...>
292   }
293
294 @ script:python depends on report @
295 fops << fops0.fops;
296 p << report_r.p1;
297 @@
298 coccilib.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 @
302 identifier fops_r.openfunc;
303 @@
304   openfunc(...) {
305     <...
306 -   nonseekable_open
307 +   nonseekable_open /* read only */
308     ...>
309   }
310
311 @ patch_r depends on patch @
312 identifier 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 @
324 identifier fops0.fops, openfunc;
325 identifier stream_writer.writestream;
326 @@
327   struct file_operations fops = {
328       .open  = openfunc,
329       .write = writestream,
330   };
331
332 @ report_w depends on report @
333 identifier fops_w.openfunc;
334 position p1;
335 @@
336   openfunc(...) {
337     <...
338     nonseekable_open@p1
339     ...>
340   }
341
342 @ script:python depends on report @
343 fops << fops0.fops;
344 p << report_w.p1;
345 @@
346 coccilib.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 @
350 identifier fops_w.openfunc;
351 @@
352   openfunc(...) {
353     <...
354 -   nonseekable_open
355 +   nonseekable_open /* write only */
356     ...>
357   }
358
359 @ patch_w depends on patch @
360 identifier 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