369 lines · 7.7 KB · Text
RawDownload
// 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 reportvirtual patchvirtual 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 = {  }; @ 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