xref: /linux/scripts/coccinelle/api/stream_open.cocci (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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  };
135
136@ has_noop_llseek @
137identifier fops0.fops;
138@@
139  struct file_operations fops = {
140    .llseek = noop_llseek,
141  };
142
143@ has_mmap @
144identifier fops0.fops;
145identifier mmap_f;
146@@
147  struct file_operations fops = {
148    .mmap = mmap_f,
149  };
150
151@ has_copy_file_range @
152identifier fops0.fops;
153identifier copy_file_range_f;
154@@
155  struct file_operations fops = {
156    .copy_file_range = copy_file_range_f,
157  };
158
159@ has_remap_file_range @
160identifier fops0.fops;
161identifier remap_file_range_f;
162@@
163  struct file_operations fops = {
164    .remap_file_range = remap_file_range_f,
165  };
166
167@ has_splice_read @
168identifier fops0.fops;
169identifier splice_read_f;
170@@
171  struct file_operations fops = {
172    .splice_read = splice_read_f,
173  };
174
175@ has_splice_write @
176identifier fops0.fops;
177identifier splice_write_f;
178@@
179  struct file_operations fops = {
180    .splice_write = splice_write_f,
181  };
182
183
184// file_operations that is candidate for stream_open conversion - it does not
185// use mmap and other methods that assume @offset access to file.
186//
187// XXX for simplicity require no .{read/write}_iter and no .splice_{read/write} for now.
188// XXX maybe_steam.fops cannot be used in other rules - it gives "bad rule maybe_stream or bad variable fops".
189@ 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 @
190identifier fops0.fops;
191@@
192  struct file_operations fops = {
193  };
194
195
196// ---- conversions ----
197
198// XXX .open = nonseekable_open -> .open = stream_open
199// XXX .open = func -> openfunc -> nonseekable_open
200
201// read & write
202//
203// if both are used in the same file_operations together with an opener -
204// under that conditions we can use stream_open instead of nonseekable_open.
205@ fops_rw depends on maybe_stream @
206identifier fops0.fops, openfunc;
207identifier stream_reader.readstream;
208identifier stream_writer.writestream;
209@@
210  struct file_operations fops = {
211      .open  = openfunc,
212      .read  = readstream,
213      .write = writestream,
214  };
215
216@ report_rw depends on report @
217identifier fops_rw.openfunc;
218position p1;
219@@
220  openfunc(...) {
221    <...
222     nonseekable_open@p1
223    ...>
224  }
225
226@ script:python depends on report && reader_blocks @
227fops << fops0.fops;
228p << report_rw.p1;
229@@
230coccilib.report.print_report(p[0],
231  "ERROR: %s: .read() can deadlock .write(); change nonseekable_open -> stream_open to fix." % (fops,))
232
233@ script:python depends on report && !reader_blocks @
234fops << fops0.fops;
235p << report_rw.p1;
236@@
237coccilib.report.print_report(p[0],
238  "WARNING: %s: .read() and .write() have stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
239
240
241@ explain_rw_deadlocked depends on explain && reader_blocks @
242identifier fops_rw.openfunc;
243@@
244  openfunc(...) {
245    <...
246-    nonseekable_open
247+    nonseekable_open /* read & write (was deadlock) */
248    ...>
249  }
250
251
252@ explain_rw_nodeadlock depends on explain && !reader_blocks @
253identifier fops_rw.openfunc;
254@@
255  openfunc(...) {
256    <...
257-    nonseekable_open
258+    nonseekable_open /* read & write (no direct deadlock) */
259    ...>
260  }
261
262@ patch_rw depends on patch @
263identifier fops_rw.openfunc;
264@@
265  openfunc(...) {
266    <...
267-   nonseekable_open
268+   stream_open
269    ...>
270  }
271
272
273// read, but not write
274@ fops_r depends on maybe_stream && !has_write @
275identifier fops0.fops, openfunc;
276identifier stream_reader.readstream;
277@@
278  struct file_operations fops = {
279      .open  = openfunc,
280      .read  = readstream,
281  };
282
283@ report_r depends on report @
284identifier fops_r.openfunc;
285position p1;
286@@
287  openfunc(...) {
288    <...
289    nonseekable_open@p1
290    ...>
291  }
292
293@ script:python depends on report @
294fops << fops0.fops;
295p << report_r.p1;
296@@
297coccilib.report.print_report(p[0],
298  "WARNING: %s: .read() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
299
300@ explain_r depends on explain @
301identifier fops_r.openfunc;
302@@
303  openfunc(...) {
304    <...
305-   nonseekable_open
306+   nonseekable_open /* read only */
307    ...>
308  }
309
310@ patch_r depends on patch @
311identifier fops_r.openfunc;
312@@
313  openfunc(...) {
314    <...
315-   nonseekable_open
316+   stream_open
317    ...>
318  }
319
320
321// write, but not read
322@ fops_w depends on maybe_stream && !has_read @
323identifier fops0.fops, openfunc;
324identifier stream_writer.writestream;
325@@
326  struct file_operations fops = {
327      .open  = openfunc,
328      .write = writestream,
329  };
330
331@ report_w depends on report @
332identifier fops_w.openfunc;
333position p1;
334@@
335  openfunc(...) {
336    <...
337    nonseekable_open@p1
338    ...>
339  }
340
341@ script:python depends on report @
342fops << fops0.fops;
343p << report_w.p1;
344@@
345coccilib.report.print_report(p[0],
346  "WARNING: %s: .write() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
347
348@ explain_w depends on explain @
349identifier fops_w.openfunc;
350@@
351  openfunc(...) {
352    <...
353-   nonseekable_open
354+   nonseekable_open /* write only */
355    ...>
356  }
357
358@ patch_w depends on patch @
359identifier fops_w.openfunc;
360@@
361  openfunc(...) {
362    <...
363-   nonseekable_open
364+   stream_open
365    ...>
366  }
367
368
369// no read, no write - don't change anything
370