forked from mirrors/linux
		
	- the first one converts stream_open.cocci to treat all functions that start with wait_.* as blocking. Previously it was only wait_event_.* functions that were considered as blocking, but this was falsely reporting several deadlock cases as only warning. The patch was picked by linux-kbuild and entered mainline as0c4ab18fc3. It is thus omitted from hereby pull-request. - the second one teaches stream_open.cocci to consider files as being stream-like even if they use noop_llseek. I posted this patch for review 3 weeks ago[1], but got neither feedback nor complaints. [1] https://lore.kernel.org/lkml/20190623072838.31234-2-kirr@nexedi.com/ -----BEGIN PGP SIGNATURE----- iQJEBAABCgAuFiEECVWwJCUO7/z+QjZbZsp4hBP2dUkFAl0rMVkQHGtpcnJAbmV4 ZWRpLmNvbQAKCRBmyniEE/Z1SU1MD/wKv/EEGzHPXU1vFFBpOAMW4ko06vf/cqxW egRFKxKYIw7rVan3T/ONo0Bs2gnw4zGR5bzOmnmFOLqgmpN0OCVjirVVwGj1anxg cM0NF9KL/qW06dN2G5/T4eFw8BltdsCuinmmE0uJvSp/2qZ/oxUvb4ZK7oxs5BF8 kzRF+K/rFiqAklHvkPV7WFQitTzmcxEiCACg9ZZOFRjj1Ryt7kUUh/2NA7OCF7kd JKmHIIv1iyN5vDmtXh0gSZiM+hBNwcEyQFOoA9TqMT2nOl5caf56PlefNGAmUTsZ TY8dw/g7raU6SZodqZrmMM7HSWp2bnA8hfDx8gIgBbSd01UF6fbg4xBoyM7VXUy1 4QClRBAI2vlPqjpy7qEeJM7VP605UxQj/6gUd+b/luaWLBUBf0N/pUiNgXjAcCxl JkEUDAdwQlUh0RErJIKAfWS7APwptiM5PUF7meKKRWQXMRoFHFU2ayUVHmvleJFR /qrfuNTota261XRbN/e4rghkh7M/1vIYuCNYGZVr3Sdd32ehzNKjwFlDo8/co/BS s9hC155gxqUZUGp0K86i9WLiuO9bUhD54k0HXZXYXDY65yF0Sp8+6CEMsEoTc6r3 OckTw3tYxOi895PqLXXB2g4h76Y3cx43K0S4CjCi97ITpEK9fT6sNwq59EX2LsYR 6NeXFAxYcQ== =CWqT -----END PGP SIGNATURE----- Merge tag 'stream_open-5.3' of https://lab.nexedi.com/kirr/linux Pull stream_open() updates from Kirill Smelkov: "This time on stream_open front it is only two small changes: - the first one converts stream_open.cocci to treat all functions that start with wait_.* as blocking. Previously it was only wait_event_.* functions that were considered as blocking, but this was falsely reporting several deadlock cases as only warning. This was picked by linux-kbuild and entered mainline as commit0c4ab18fc3("coccinelle: api/stream_open: treat all wait_.*() calls as blocking"), and already merged earlier. - the second one teaches stream_open.cocci to consider files as being stream-like even if they use noop_llseek. It results in two more drivers being converted to stream_open() (mousedev.c and hid-sensor-custom.c)" * tag 'stream_open-5.3' of https://lab.nexedi.com/kirr/linux: *: convert stream-like files -> stream_open, even if they use noop_llseek
		
			
				
	
	
		
			370 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
// 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
 |