|  | // 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 =~ "^wait_.*"; | 
|  | @@ | 
|  | block_f(...) { | 
|  | ... when exists | 
|  | wait(...) | 
|  | ... 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 =~ "^wait_.*"; | 
|  | @@ | 
|  | readstream(...) | 
|  | { | 
|  | ... when exists | 
|  | wait(...) | 
|  | ... 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_noop_llseek @ | 
|  | identifier fops0.fops; | 
|  | @@ | 
|  | struct file_operations fops = { | 
|  | .llseek = noop_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_noop_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 |