forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
7.7 KiB
370 lines
7.7 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
// Author: Kirill Smelkov ([email protected]) |
|
// |
|
// 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
|
|
|