]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/blob
78b7b7f3447b67686eedfd4c92c168e78ff5d899
[thirdparty/kernel/stable-queue.git] /
1 From 10dce8af34226d90fa56746a934f8da5dcdba3df Mon Sep 17 00:00:00 2001
2 From: Kirill Smelkov <kirr@nexedi.com>
3 Date: Tue, 26 Mar 2019 22:20:43 +0000
4 Subject: fs: stream_open - opener for stream-like files so that read and write can run simultaneously without deadlock
5
6 From: Kirill Smelkov <kirr@nexedi.com>
7
8 commit 10dce8af34226d90fa56746a934f8da5dcdba3df upstream.
9
10 Commit 9c225f2655e3 ("vfs: atomic f_pos accesses as per POSIX") added
11 locking for file.f_pos access and in particular made concurrent read and
12 write not possible - now both those functions take f_pos lock for the
13 whole run, and so if e.g. a read is blocked waiting for data, write will
14 deadlock waiting for that read to complete.
15
16 This caused regression for stream-like files where previously read and
17 write could run simultaneously, but after that patch could not do so
18 anymore. See e.g. commit 581d21a2d02a ("xenbus: fix deadlock on writes
19 to /proc/xen/xenbus") which fixes such regression for particular case of
20 /proc/xen/xenbus.
21
22 The patch that added f_pos lock in 2014 did so to guarantee POSIX thread
23 safety for read/write/lseek and added the locking to file descriptors of
24 all regular files. In 2014 that thread-safety problem was not new as it
25 was already discussed earlier in 2006.
26
27 However even though 2006'th version of Linus's patch was adding f_pos
28 locking "only for files that are marked seekable with FMODE_LSEEK (thus
29 avoiding the stream-like objects like pipes and sockets)", the 2014
30 version - the one that actually made it into the tree as 9c225f2655e3 -
31 is doing so irregardless of whether a file is seekable or not.
32
33 See
34
35 https://lore.kernel.org/lkml/53022DB1.4070805@gmail.com/
36 https://lwn.net/Articles/180387
37 https://lwn.net/Articles/180396
38
39 for historic context.
40
41 The reason that it did so is, probably, that there are many files that
42 are marked non-seekable, but e.g. their read implementation actually
43 depends on knowing current position to correctly handle the read. Some
44 examples:
45
46 kernel/power/user.c snapshot_read
47 fs/debugfs/file.c u32_array_read
48 fs/fuse/control.c fuse_conn_waiting_read + ...
49 drivers/hwmon/asus_atk0110.c atk_debugfs_ggrp_read
50 arch/s390/hypfs/inode.c hypfs_read_iter
51 ...
52
53 Despite that, many nonseekable_open users implement read and write with
54 pure stream semantics - they don't depend on passed ppos at all. And for
55 those cases where read could wait for something inside, it creates a
56 situation similar to xenbus - the write could be never made to go until
57 read is done, and read is waiting for some, potentially external, event,
58 for potentially unbounded time -> deadlock.
59
60 Besides xenbus, there are 14 such places in the kernel that I've found
61 with semantic patch (see below):
62
63 drivers/xen/evtchn.c:667:8-24: ERROR: evtchn_fops: .read() can deadlock .write()
64 drivers/isdn/capi/capi.c:963:8-24: ERROR: capi_fops: .read() can deadlock .write()
65 drivers/input/evdev.c:527:1-17: ERROR: evdev_fops: .read() can deadlock .write()
66 drivers/char/pcmcia/cm4000_cs.c:1685:7-23: ERROR: cm4000_fops: .read() can deadlock .write()
67 net/rfkill/core.c:1146:8-24: ERROR: rfkill_fops: .read() can deadlock .write()
68 drivers/s390/char/fs3270.c:488:1-17: ERROR: fs3270_fops: .read() can deadlock .write()
69 drivers/usb/misc/ldusb.c:310:1-17: ERROR: ld_usb_fops: .read() can deadlock .write()
70 drivers/hid/uhid.c:635:1-17: ERROR: uhid_fops: .read() can deadlock .write()
71 net/batman-adv/icmp_socket.c:80:1-17: ERROR: batadv_fops: .read() can deadlock .write()
72 drivers/media/rc/lirc_dev.c:198:1-17: ERROR: lirc_fops: .read() can deadlock .write()
73 drivers/leds/uleds.c:77:1-17: ERROR: uleds_fops: .read() can deadlock .write()
74 drivers/input/misc/uinput.c:400:1-17: ERROR: uinput_fops: .read() can deadlock .write()
75 drivers/infiniband/core/user_mad.c:985:7-23: ERROR: umad_fops: .read() can deadlock .write()
76 drivers/gnss/core.c:45:1-17: ERROR: gnss_fops: .read() can deadlock .write()
77
78 In addition to the cases above another regression caused by f_pos
79 locking is that now FUSE filesystems that implement open with
80 FOPEN_NONSEEKABLE flag, can no longer implement bidirectional
81 stream-like files - for the same reason as above e.g. read can deadlock
82 write locking on file.f_pos in the kernel.
83
84 FUSE's FOPEN_NONSEEKABLE was added in 2008 in a7c1b990f715 ("fuse:
85 implement nonseekable open") to support OSSPD. OSSPD implements /dev/dsp
86 in userspace with FOPEN_NONSEEKABLE flag, with corresponding read and
87 write routines not depending on current position at all, and with both
88 read and write being potentially blocking operations:
89
90 See
91
92 https://github.com/libfuse/osspd
93 https://lwn.net/Articles/308445
94
95 https://github.com/libfuse/osspd/blob/14a9cff0/osspd.c#L1406
96 https://github.com/libfuse/osspd/blob/14a9cff0/osspd.c#L1438-L1477
97 https://github.com/libfuse/osspd/blob/14a9cff0/osspd.c#L1479-L1510
98
99 Corresponding libfuse example/test also describes FOPEN_NONSEEKABLE as
100 "somewhat pipe-like files ..." with read handler not using offset.
101 However that test implements only read without write and cannot exercise
102 the deadlock scenario:
103
104 https://github.com/libfuse/libfuse/blob/fuse-3.4.2-3-ga1bff7d/example/poll.c#L124-L131
105 https://github.com/libfuse/libfuse/blob/fuse-3.4.2-3-ga1bff7d/example/poll.c#L146-L163
106 https://github.com/libfuse/libfuse/blob/fuse-3.4.2-3-ga1bff7d/example/poll.c#L209-L216
107
108 I've actually hit the read vs write deadlock for real while implementing
109 my FUSE filesystem where there is /head/watch file, for which open
110 creates separate bidirectional socket-like stream in between filesystem
111 and its user with both read and write being later performed
112 simultaneously. And there it is semantically not easy to split the
113 stream into two separate read-only and write-only channels:
114
115 https://lab.nexedi.com/kirr/wendelin.core/blob/f13aa600/wcfs/wcfs.go#L88-169
116
117 Let's fix this regression. The plan is:
118
119 1. We can't change nonseekable_open to include &~FMODE_ATOMIC_POS -
120 doing so would break many in-kernel nonseekable_open users which
121 actually use ppos in read/write handlers.
122
123 2. Add stream_open() to kernel to open stream-like non-seekable file
124 descriptors. Read and write on such file descriptors would never use
125 nor change ppos. And with that property on stream-like files read and
126 write will be running without taking f_pos lock - i.e. read and write
127 could be running simultaneously.
128
129 3. With semantic patch search and convert to stream_open all in-kernel
130 nonseekable_open users for which read and write actually do not
131 depend on ppos and where there is no other methods in file_operations
132 which assume @offset access.
133
134 4. Add FOPEN_STREAM to fs/fuse/ and open in-kernel file-descriptors via
135 steam_open if that bit is present in filesystem open reply.
136
137 It was tempting to change fs/fuse/ open handler to use stream_open
138 instead of nonseekable_open on just FOPEN_NONSEEKABLE flags, but
139 grepping through Debian codesearch shows users of FOPEN_NONSEEKABLE,
140 and in particular GVFS which actually uses offset in its read and
141 write handlers
142
143 https://codesearch.debian.net/search?q=-%3Enonseekable+%3D
144 https://gitlab.gnome.org/GNOME/gvfs/blob/1.40.0-6-gcbc54396/client/gvfsfusedaemon.c#L1080
145 https://gitlab.gnome.org/GNOME/gvfs/blob/1.40.0-6-gcbc54396/client/gvfsfusedaemon.c#L1247-1346
146 https://gitlab.gnome.org/GNOME/gvfs/blob/1.40.0-6-gcbc54396/client/gvfsfusedaemon.c#L1399-1481
147
148 so if we would do such a change it will break a real user.
149
150 5. Add stream_open and FOPEN_STREAM handling to stable kernels starting
151 from v3.14+ (the kernel where 9c225f2655 first appeared).
152
153 This will allow to patch OSSPD and other FUSE filesystems that
154 provide stream-like files to return FOPEN_STREAM | FOPEN_NONSEEKABLE
155 in their open handler and this way avoid the deadlock on all kernel
156 versions. This should work because fs/fuse/ ignores unknown open
157 flags returned from a filesystem and so passing FOPEN_STREAM to a
158 kernel that is not aware of this flag cannot hurt. In turn the kernel
159 that is not aware of FOPEN_STREAM will be < v3.14 where just
160 FOPEN_NONSEEKABLE is sufficient to implement streams without read vs
161 write deadlock.
162
163 This patch adds stream_open, converts /proc/xen/xenbus to it and adds
164 semantic patch to automatically locate in-kernel places that are either
165 required to be converted due to read vs write deadlock, or that are just
166 safe to be converted because read and write do not use ppos and there
167 are no other funky methods in file_operations.
168
169 Regarding semantic patch I've verified each generated change manually -
170 that it is correct to convert - and each other nonseekable_open instance
171 left - that it is either not correct to convert there, or that it is not
172 converted due to current stream_open.cocci limitations.
173
174 The script also does not convert files that should be valid to convert,
175 but that currently have .llseek = noop_llseek or generic_file_llseek for
176 unknown reason despite file being opened with nonseekable_open (e.g.
177 drivers/input/mousedev.c)
178
179 Cc: Michael Kerrisk <mtk.manpages@gmail.com>
180 Cc: Yongzhi Pan <panyongzhi@gmail.com>
181 Cc: Jonathan Corbet <corbet@lwn.net>
182 Cc: David Vrabel <david.vrabel@citrix.com>
183 Cc: Juergen Gross <jgross@suse.com>
184 Cc: Miklos Szeredi <miklos@szeredi.hu>
185 Cc: Tejun Heo <tj@kernel.org>
186 Cc: Kirill Tkhai <ktkhai@virtuozzo.com>
187 Cc: Arnd Bergmann <arnd@arndb.de>
188 Cc: Christoph Hellwig <hch@lst.de>
189 Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
190 Cc: Julia Lawall <Julia.Lawall@lip6.fr>
191 Cc: Nikolaus Rath <Nikolaus@rath.org>
192 Cc: Han-Wen Nienhuys <hanwen@google.com>
193 Signed-off-by: Kirill Smelkov <kirr@nexedi.com>
194 Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
195 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
196
197 ---
198 drivers/xen/xenbus/xenbus_dev_frontend.c | 4
199 fs/open.c | 18 +
200 fs/read_write.c | 5
201 include/linux/fs.h | 4
202 scripts/coccinelle/api/stream_open.cocci | 363 +++++++++++++++++++++++++++++++
203 5 files changed, 389 insertions(+), 5 deletions(-)
204
205 --- a/drivers/xen/xenbus/xenbus_dev_frontend.c
206 +++ b/drivers/xen/xenbus/xenbus_dev_frontend.c
207 @@ -614,9 +614,7 @@ static int xenbus_file_open(struct inode
208 if (xen_store_evtchn == 0)
209 return -ENOENT;
210
211 - nonseekable_open(inode, filp);
212 -
213 - filp->f_mode &= ~FMODE_ATOMIC_POS; /* cdev-style semantics */
214 + stream_open(inode, filp);
215
216 u = kzalloc(sizeof(*u), GFP_KERNEL);
217 if (u == NULL)
218 --- a/fs/open.c
219 +++ b/fs/open.c
220 @@ -1212,3 +1212,21 @@ int nonseekable_open(struct inode *inode
221 }
222
223 EXPORT_SYMBOL(nonseekable_open);
224 +
225 +/*
226 + * stream_open is used by subsystems that want stream-like file descriptors.
227 + * Such file descriptors are not seekable and don't have notion of position
228 + * (file.f_pos is always 0). Contrary to file descriptors of other regular
229 + * files, .read() and .write() can run simultaneously.
230 + *
231 + * stream_open never fails and is marked to return int so that it could be
232 + * directly used as file_operations.open .
233 + */
234 +int stream_open(struct inode *inode, struct file *filp)
235 +{
236 + filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE | FMODE_ATOMIC_POS);
237 + filp->f_mode |= FMODE_STREAM;
238 + return 0;
239 +}
240 +
241 +EXPORT_SYMBOL(stream_open);
242 --- a/fs/read_write.c
243 +++ b/fs/read_write.c
244 @@ -555,12 +555,13 @@ ssize_t vfs_write(struct file *file, con
245
246 static inline loff_t file_pos_read(struct file *file)
247 {
248 - return file->f_pos;
249 + return file->f_mode & FMODE_STREAM ? 0 : file->f_pos;
250 }
251
252 static inline void file_pos_write(struct file *file, loff_t pos)
253 {
254 - file->f_pos = pos;
255 + if ((file->f_mode & FMODE_STREAM) == 0)
256 + file->f_pos = pos;
257 }
258
259 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
260 --- a/include/linux/fs.h
261 +++ b/include/linux/fs.h
262 @@ -148,6 +148,9 @@ typedef int (dio_iodone_t)(struct kiocb
263 /* Has write method(s) */
264 #define FMODE_CAN_WRITE ((__force fmode_t)0x40000)
265
266 +/* File is stream-like */
267 +#define FMODE_STREAM ((__force fmode_t)0x200000)
268 +
269 /* File was opened by fanotify and shouldn't generate fanotify events */
270 #define FMODE_NONOTIFY ((__force fmode_t)0x4000000)
271
272 @@ -2945,6 +2948,7 @@ extern loff_t no_seek_end_llseek_size(st
273 extern loff_t no_seek_end_llseek(struct file *, loff_t, int);
274 extern int generic_file_open(struct inode * inode, struct file * filp);
275 extern int nonseekable_open(struct inode * inode, struct file * filp);
276 +extern int stream_open(struct inode * inode, struct file * filp);
277
278 #ifdef CONFIG_BLOCK
279 typedef void (dio_submit_t)(struct bio *bio, struct inode *inode,
280 --- /dev/null
281 +++ b/scripts/coccinelle/api/stream_open.cocci
282 @@ -0,0 +1,363 @@
283 +// SPDX-License-Identifier: GPL-2.0
284 +// Author: Kirill Smelkov (kirr@nexedi.com)
285 +//
286 +// Search for stream-like files that are using nonseekable_open and convert
287 +// them to stream_open. A stream-like file is a file that does not use ppos in
288 +// its read and write. Rationale for the conversion is to avoid deadlock in
289 +// between read and write.
290 +
291 +virtual report
292 +virtual patch
293 +virtual explain // explain decisions in the patch (SPFLAGS="-D explain")
294 +
295 +// stream-like reader & writer - ones that do not depend on f_pos.
296 +@ stream_reader @
297 +identifier readstream, ppos;
298 +identifier f, buf, len;
299 +type loff_t;
300 +@@
301 + ssize_t readstream(struct file *f, char *buf, size_t len, loff_t *ppos)
302 + {
303 + ... when != ppos
304 + }
305 +
306 +@ stream_writer @
307 +identifier writestream, ppos;
308 +identifier f, buf, len;
309 +type loff_t;
310 +@@
311 + ssize_t writestream(struct file *f, const char *buf, size_t len, loff_t *ppos)
312 + {
313 + ... when != ppos
314 + }
315 +
316 +
317 +// a function that blocks
318 +@ blocks @
319 +identifier block_f;
320 +identifier wait_event =~ "^wait_event_.*";
321 +@@
322 + block_f(...) {
323 + ... when exists
324 + wait_event(...)
325 + ... when exists
326 + }
327 +
328 +// stream_reader that can block inside.
329 +//
330 +// XXX wait_* can be called not directly from current function (e.g. func -> f -> g -> wait())
331 +// XXX currently reader_blocks supports only direct and 1-level indirect cases.
332 +@ reader_blocks_direct @
333 +identifier stream_reader.readstream;
334 +identifier wait_event =~ "^wait_event_.*";
335 +@@
336 + readstream(...)
337 + {
338 + ... when exists
339 + wait_event(...)
340 + ... when exists
341 + }
342 +
343 +@ reader_blocks_1 @
344 +identifier stream_reader.readstream;
345 +identifier blocks.block_f;
346 +@@
347 + readstream(...)
348 + {
349 + ... when exists
350 + block_f(...)
351 + ... when exists
352 + }
353 +
354 +@ reader_blocks depends on reader_blocks_direct || reader_blocks_1 @
355 +identifier stream_reader.readstream;
356 +@@
357 + readstream(...) {
358 + ...
359 + }
360 +
361 +
362 +// file_operations + whether they have _any_ .read, .write, .llseek ... at all.
363 +//
364 +// XXX add support for file_operations xxx[N] = ... (sound/core/pcm_native.c)
365 +@ fops0 @
366 +identifier fops;
367 +@@
368 + struct file_operations fops = {
369 + ...
370 + };
371 +
372 +@ has_read @
373 +identifier fops0.fops;
374 +identifier read_f;
375 +@@
376 + struct file_operations fops = {
377 + .read = read_f,
378 + };
379 +
380 +@ has_read_iter @
381 +identifier fops0.fops;
382 +identifier read_iter_f;
383 +@@
384 + struct file_operations fops = {
385 + .read_iter = read_iter_f,
386 + };
387 +
388 +@ has_write @
389 +identifier fops0.fops;
390 +identifier write_f;
391 +@@
392 + struct file_operations fops = {
393 + .write = write_f,
394 + };
395 +
396 +@ has_write_iter @
397 +identifier fops0.fops;
398 +identifier write_iter_f;
399 +@@
400 + struct file_operations fops = {
401 + .write_iter = write_iter_f,
402 + };
403 +
404 +@ has_llseek @
405 +identifier fops0.fops;
406 +identifier llseek_f;
407 +@@
408 + struct file_operations fops = {
409 + .llseek = llseek_f,
410 + };
411 +
412 +@ has_no_llseek @
413 +identifier fops0.fops;
414 +@@
415 + struct file_operations fops = {
416 + .llseek = no_llseek,
417 + };
418 +
419 +@ has_mmap @
420 +identifier fops0.fops;
421 +identifier mmap_f;
422 +@@
423 + struct file_operations fops = {
424 + .mmap = mmap_f,
425 + };
426 +
427 +@ has_copy_file_range @
428 +identifier fops0.fops;
429 +identifier copy_file_range_f;
430 +@@
431 + struct file_operations fops = {
432 + .copy_file_range = copy_file_range_f,
433 + };
434 +
435 +@ has_remap_file_range @
436 +identifier fops0.fops;
437 +identifier remap_file_range_f;
438 +@@
439 + struct file_operations fops = {
440 + .remap_file_range = remap_file_range_f,
441 + };
442 +
443 +@ has_splice_read @
444 +identifier fops0.fops;
445 +identifier splice_read_f;
446 +@@
447 + struct file_operations fops = {
448 + .splice_read = splice_read_f,
449 + };
450 +
451 +@ has_splice_write @
452 +identifier fops0.fops;
453 +identifier splice_write_f;
454 +@@
455 + struct file_operations fops = {
456 + .splice_write = splice_write_f,
457 + };
458 +
459 +
460 +// file_operations that is candidate for stream_open conversion - it does not
461 +// use mmap and other methods that assume @offset access to file.
462 +//
463 +// XXX for simplicity require no .{read/write}_iter and no .splice_{read/write} for now.
464 +// XXX maybe_steam.fops cannot be used in other rules - it gives "bad rule maybe_stream or bad variable fops".
465 +@ maybe_stream depends on (!has_llseek || has_no_llseek) && !has_mmap && !has_copy_file_range && !has_remap_file_range && !has_read_iter && !has_write_iter && !has_splice_read && !has_splice_write @
466 +identifier fops0.fops;
467 +@@
468 + struct file_operations fops = {
469 + };
470 +
471 +
472 +// ---- conversions ----
473 +
474 +// XXX .open = nonseekable_open -> .open = stream_open
475 +// XXX .open = func -> openfunc -> nonseekable_open
476 +
477 +// read & write
478 +//
479 +// if both are used in the same file_operations together with an opener -
480 +// under that conditions we can use stream_open instead of nonseekable_open.
481 +@ fops_rw depends on maybe_stream @
482 +identifier fops0.fops, openfunc;
483 +identifier stream_reader.readstream;
484 +identifier stream_writer.writestream;
485 +@@
486 + struct file_operations fops = {
487 + .open = openfunc,
488 + .read = readstream,
489 + .write = writestream,
490 + };
491 +
492 +@ report_rw depends on report @
493 +identifier fops_rw.openfunc;
494 +position p1;
495 +@@
496 + openfunc(...) {
497 + <...
498 + nonseekable_open@p1
499 + ...>
500 + }
501 +
502 +@ script:python depends on report && reader_blocks @
503 +fops << fops0.fops;
504 +p << report_rw.p1;
505 +@@
506 +coccilib.report.print_report(p[0],
507 + "ERROR: %s: .read() can deadlock .write(); change nonseekable_open -> stream_open to fix." % (fops,))
508 +
509 +@ script:python depends on report && !reader_blocks @
510 +fops << fops0.fops;
511 +p << report_rw.p1;
512 +@@
513 +coccilib.report.print_report(p[0],
514 + "WARNING: %s: .read() and .write() have stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
515 +
516 +
517 +@ explain_rw_deadlocked depends on explain && reader_blocks @
518 +identifier fops_rw.openfunc;
519 +@@
520 + openfunc(...) {
521 + <...
522 +- nonseekable_open
523 ++ nonseekable_open /* read & write (was deadlock) */
524 + ...>
525 + }
526 +
527 +
528 +@ explain_rw_nodeadlock depends on explain && !reader_blocks @
529 +identifier fops_rw.openfunc;
530 +@@
531 + openfunc(...) {
532 + <...
533 +- nonseekable_open
534 ++ nonseekable_open /* read & write (no direct deadlock) */
535 + ...>
536 + }
537 +
538 +@ patch_rw depends on patch @
539 +identifier fops_rw.openfunc;
540 +@@
541 + openfunc(...) {
542 + <...
543 +- nonseekable_open
544 ++ stream_open
545 + ...>
546 + }
547 +
548 +
549 +// read, but not write
550 +@ fops_r depends on maybe_stream && !has_write @
551 +identifier fops0.fops, openfunc;
552 +identifier stream_reader.readstream;
553 +@@
554 + struct file_operations fops = {
555 + .open = openfunc,
556 + .read = readstream,
557 + };
558 +
559 +@ report_r depends on report @
560 +identifier fops_r.openfunc;
561 +position p1;
562 +@@
563 + openfunc(...) {
564 + <...
565 + nonseekable_open@p1
566 + ...>
567 + }
568 +
569 +@ script:python depends on report @
570 +fops << fops0.fops;
571 +p << report_r.p1;
572 +@@
573 +coccilib.report.print_report(p[0],
574 + "WARNING: %s: .read() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
575 +
576 +@ explain_r depends on explain @
577 +identifier fops_r.openfunc;
578 +@@
579 + openfunc(...) {
580 + <...
581 +- nonseekable_open
582 ++ nonseekable_open /* read only */
583 + ...>
584 + }
585 +
586 +@ patch_r depends on patch @
587 +identifier fops_r.openfunc;
588 +@@
589 + openfunc(...) {
590 + <...
591 +- nonseekable_open
592 ++ stream_open
593 + ...>
594 + }
595 +
596 +
597 +// write, but not read
598 +@ fops_w depends on maybe_stream && !has_read @
599 +identifier fops0.fops, openfunc;
600 +identifier stream_writer.writestream;
601 +@@
602 + struct file_operations fops = {
603 + .open = openfunc,
604 + .write = writestream,
605 + };
606 +
607 +@ report_w depends on report @
608 +identifier fops_w.openfunc;
609 +position p1;
610 +@@
611 + openfunc(...) {
612 + <...
613 + nonseekable_open@p1
614 + ...>
615 + }
616 +
617 +@ script:python depends on report @
618 +fops << fops0.fops;
619 +p << report_w.p1;
620 +@@
621 +coccilib.report.print_report(p[0],
622 + "WARNING: %s: .write() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
623 +
624 +@ explain_w depends on explain @
625 +identifier fops_w.openfunc;
626 +@@
627 + openfunc(...) {
628 + <...
629 +- nonseekable_open
630 ++ nonseekable_open /* write only */
631 + ...>
632 + }
633 +
634 +@ patch_w depends on patch @
635 +identifier fops_w.openfunc;
636 +@@
637 + openfunc(...) {
638 + <...
639 +- nonseekable_open
640 ++ stream_open
641 + ...>
642 + }
643 +
644 +
645 +// no read, no write - don't change anything