123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- // SPDX-License-Identifier: GPL-2.0
- // Author: Kirill Smelkov (kirr@nexedi.com)
- //
- // Search for stream-like files that are using nonseekable_open and convert
- // them to stream_open. A stream-like file is a file that does not use ppos in
- // its read and write. Rationale for the conversion is to avoid deadlock in
- // between read and write.
- virtual report
- virtual patch
- virtual explain // explain decisions in the patch (SPFLAGS="-D explain")
- // stream-like reader & writer - ones that do not depend on f_pos.
- @ stream_reader @
- identifier readstream, ppos;
- identifier f, buf, len;
- type loff_t;
- @@
- ssize_t readstream(struct file *f, char *buf, size_t len, loff_t *ppos)
- {
- ... when != ppos
- }
- @ stream_writer @
- identifier writestream, ppos;
- identifier f, buf, len;
- type loff_t;
- @@
- ssize_t writestream(struct file *f, const char *buf, size_t len, loff_t *ppos)
- {
- ... when != ppos
- }
- // a function that blocks
- @ blocks @
- identifier block_f;
- identifier wait_event =~ "^wait_event_.*";
- @@
- block_f(...) {
- ... when exists
- wait_event(...)
- ... when exists
- }
- // stream_reader that can block inside.
- //
- // XXX wait_* can be called not directly from current function (e.g. func -> f -> g -> wait())
- // XXX currently reader_blocks supports only direct and 1-level indirect cases.
- @ reader_blocks_direct @
- identifier stream_reader.readstream;
- identifier wait_event =~ "^wait_event_.*";
- @@
- readstream(...)
- {
- ... when exists
- wait_event(...)
- ... when exists
- }
- @ reader_blocks_1 @
- identifier stream_reader.readstream;
- identifier blocks.block_f;
- @@
- readstream(...)
- {
- ... when exists
- block_f(...)
- ... when exists
- }
- @ reader_blocks depends on reader_blocks_direct || reader_blocks_1 @
- identifier stream_reader.readstream;
- @@
- readstream(...) {
- ...
- }
- // file_operations + whether they have _any_ .read, .write, .llseek ... at all.
- //
- // XXX add support for file_operations xxx[N] = ... (sound/core/pcm_native.c)
- @ fops0 @
- identifier fops;
- @@
- struct file_operations fops = {
- ...
- };
- @ has_read @
- identifier fops0.fops;
- identifier read_f;
- @@
- struct file_operations fops = {
- .read = read_f,
- };
- @ has_read_iter @
- identifier fops0.fops;
- identifier read_iter_f;
- @@
- struct file_operations fops = {
- .read_iter = read_iter_f,
- };
- @ has_write @
- identifier fops0.fops;
- identifier write_f;
- @@
- struct file_operations fops = {
- .write = write_f,
- };
- @ has_write_iter @
- identifier fops0.fops;
- identifier write_iter_f;
- @@
- struct file_operations fops = {
- .write_iter = write_iter_f,
- };
- @ has_llseek @
- identifier fops0.fops;
- identifier llseek_f;
- @@
- struct file_operations fops = {
- .llseek = llseek_f,
- };
- @ has_no_llseek @
- identifier fops0.fops;
- @@
- struct file_operations fops = {
- .llseek = no_llseek,
- };
- @ has_mmap @
- identifier fops0.fops;
- identifier mmap_f;
- @@
- struct file_operations fops = {
- .mmap = mmap_f,
- };
- @ has_copy_file_range @
- identifier fops0.fops;
- identifier copy_file_range_f;
- @@
- struct file_operations fops = {
- .copy_file_range = copy_file_range_f,
- };
- @ has_remap_file_range @
- identifier fops0.fops;
- identifier remap_file_range_f;
- @@
- struct file_operations fops = {
- .remap_file_range = remap_file_range_f,
- };
- @ has_splice_read @
- identifier fops0.fops;
- identifier splice_read_f;
- @@
- struct file_operations fops = {
- .splice_read = splice_read_f,
- };
- @ has_splice_write @
- identifier fops0.fops;
- identifier splice_write_f;
- @@
- struct file_operations fops = {
- .splice_write = splice_write_f,
- };
- // file_operations that is candidate for stream_open conversion - it does not
- // use mmap and other methods that assume @offset access to file.
- //
- // XXX for simplicity require no .{read/write}_iter and no .splice_{read/write} for now.
- // XXX maybe_steam.fops cannot be used in other rules - it gives "bad rule maybe_stream or bad variable fops".
- @ 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 @
- identifier fops0.fops;
- @@
- struct file_operations fops = {
- };
- // ---- conversions ----
- // XXX .open = nonseekable_open -> .open = stream_open
- // XXX .open = func -> openfunc -> nonseekable_open
- // read & write
- //
- // if both are used in the same file_operations together with an opener -
- // under that conditions we can use stream_open instead of nonseekable_open.
- @ fops_rw depends on maybe_stream @
- identifier fops0.fops, openfunc;
- identifier stream_reader.readstream;
- identifier stream_writer.writestream;
- @@
- struct file_operations fops = {
- .open = openfunc,
- .read = readstream,
- .write = writestream,
- };
- @ report_rw depends on report @
- identifier fops_rw.openfunc;
- position p1;
- @@
- openfunc(...) {
- <...
- nonseekable_open@p1
- ...>
- }
- @ script:python depends on report && reader_blocks @
- fops << fops0.fops;
- p << report_rw.p1;
- @@
- coccilib.report.print_report(p[0],
- "ERROR: %s: .read() can deadlock .write(); change nonseekable_open -> stream_open to fix." % (fops,))
- @ script:python depends on report && !reader_blocks @
- fops << fops0.fops;
- p << report_rw.p1;
- @@
- coccilib.report.print_report(p[0],
- "WARNING: %s: .read() and .write() have stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
- @ explain_rw_deadlocked depends on explain && reader_blocks @
- identifier fops_rw.openfunc;
- @@
- openfunc(...) {
- <...
- - nonseekable_open
- + nonseekable_open /* read & write (was deadlock) */
- ...>
- }
- @ explain_rw_nodeadlock depends on explain && !reader_blocks @
- identifier fops_rw.openfunc;
- @@
- openfunc(...) {
- <...
- - nonseekable_open
- + nonseekable_open /* read & write (no direct deadlock) */
- ...>
- }
- @ patch_rw depends on patch @
- identifier fops_rw.openfunc;
- @@
- openfunc(...) {
- <...
- - nonseekable_open
- + stream_open
- ...>
- }
- // read, but not write
- @ fops_r depends on maybe_stream && !has_write @
- identifier fops0.fops, openfunc;
- identifier stream_reader.readstream;
- @@
- struct file_operations fops = {
- .open = openfunc,
- .read = readstream,
- };
- @ report_r depends on report @
- identifier fops_r.openfunc;
- position p1;
- @@
- openfunc(...) {
- <...
- nonseekable_open@p1
- ...>
- }
- @ script:python depends on report @
- fops << fops0.fops;
- p << report_r.p1;
- @@
- coccilib.report.print_report(p[0],
- "WARNING: %s: .read() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
- @ explain_r depends on explain @
- identifier fops_r.openfunc;
- @@
- openfunc(...) {
- <...
- - nonseekable_open
- + nonseekable_open /* read only */
- ...>
- }
- @ patch_r depends on patch @
- identifier fops_r.openfunc;
- @@
- openfunc(...) {
- <...
- - nonseekable_open
- + stream_open
- ...>
- }
- // write, but not read
- @ fops_w depends on maybe_stream && !has_read @
- identifier fops0.fops, openfunc;
- identifier stream_writer.writestream;
- @@
- struct file_operations fops = {
- .open = openfunc,
- .write = writestream,
- };
- @ report_w depends on report @
- identifier fops_w.openfunc;
- position p1;
- @@
- openfunc(...) {
- <...
- nonseekable_open@p1
- ...>
- }
- @ script:python depends on report @
- fops << fops0.fops;
- p << report_w.p1;
- @@
- coccilib.report.print_report(p[0],
- "WARNING: %s: .write() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,))
- @ explain_w depends on explain @
- identifier fops_w.openfunc;
- @@
- openfunc(...) {
- <...
- - nonseekable_open
- + nonseekable_open /* write only */
- ...>
- }
- @ patch_w depends on patch @
- identifier fops_w.openfunc;
- @@
- openfunc(...) {
- <...
- - nonseekable_open
- + stream_open
- ...>
- }
- // no read, no write - don't change anything
|