stream_open.cocci (7880B)
1// SPDX-License-Identifier: GPL-2.0 2// Author: Kirill Smelkov (kirr@nexedi.com) 3// 4// Search for stream-like files that are using nonseekable_open and convert 5// them to stream_open. A stream-like file is a file that does not use ppos in 6// its read and write. Rationale for the conversion is to avoid deadlock in 7// between read and write. 8 9virtual report 10virtual patch 11virtual explain // explain decisions in the patch (SPFLAGS="-D explain") 12 13// stream-like reader & writer - ones that do not depend on f_pos. 14@ stream_reader @ 15identifier readstream, ppos; 16identifier f, buf, len; 17type loff_t; 18@@ 19 ssize_t readstream(struct file *f, char *buf, size_t len, loff_t *ppos) 20 { 21 ... when != ppos 22 } 23 24@ stream_writer @ 25identifier writestream, ppos; 26identifier f, buf, len; 27type loff_t; 28@@ 29 ssize_t writestream(struct file *f, const char *buf, size_t len, loff_t *ppos) 30 { 31 ... when != ppos 32 } 33 34 35// a function that blocks 36@ blocks @ 37identifier block_f; 38identifier wait =~ "^wait_.*"; 39@@ 40 block_f(...) { 41 ... when exists 42 wait(...) 43 ... when exists 44 } 45 46// stream_reader that can block inside. 47// 48// XXX wait_* can be called not directly from current function (e.g. func -> f -> g -> wait()) 49// XXX currently reader_blocks supports only direct and 1-level indirect cases. 50@ reader_blocks_direct @ 51identifier stream_reader.readstream; 52identifier wait =~ "^wait_.*"; 53@@ 54 readstream(...) 55 { 56 ... when exists 57 wait(...) 58 ... when exists 59 } 60 61@ reader_blocks_1 @ 62identifier stream_reader.readstream; 63identifier blocks.block_f; 64@@ 65 readstream(...) 66 { 67 ... when exists 68 block_f(...) 69 ... when exists 70 } 71 72@ reader_blocks depends on reader_blocks_direct || reader_blocks_1 @ 73identifier stream_reader.readstream; 74@@ 75 readstream(...) { 76 ... 77 } 78 79 80// file_operations + whether they have _any_ .read, .write, .llseek ... at all. 81// 82// XXX add support for file_operations xxx[N] = ... (sound/core/pcm_native.c) 83@ fops0 @ 84identifier fops; 85@@ 86 struct file_operations fops = { 87 ... 88 }; 89 90@ has_read @ 91identifier fops0.fops; 92identifier read_f; 93@@ 94 struct file_operations fops = { 95 .read = read_f, 96 }; 97 98@ has_read_iter @ 99identifier fops0.fops; 100identifier read_iter_f; 101@@ 102 struct file_operations fops = { 103 .read_iter = read_iter_f, 104 }; 105 106@ has_write @ 107identifier fops0.fops; 108identifier write_f; 109@@ 110 struct file_operations fops = { 111 .write = write_f, 112 }; 113 114@ has_write_iter @ 115identifier fops0.fops; 116identifier write_iter_f; 117@@ 118 struct file_operations fops = { 119 .write_iter = write_iter_f, 120 }; 121 122@ has_llseek @ 123identifier fops0.fops; 124identifier llseek_f; 125@@ 126 struct file_operations fops = { 127 .llseek = llseek_f, 128 }; 129 130@ has_no_llseek @ 131identifier fops0.fops; 132@@ 133 struct file_operations fops = { 134 .llseek = no_llseek, 135 }; 136 137@ has_noop_llseek @ 138identifier fops0.fops; 139@@ 140 struct file_operations fops = { 141 .llseek = noop_llseek, 142 }; 143 144@ has_mmap @ 145identifier fops0.fops; 146identifier mmap_f; 147@@ 148 struct file_operations fops = { 149 .mmap = mmap_f, 150 }; 151 152@ has_copy_file_range @ 153identifier fops0.fops; 154identifier copy_file_range_f; 155@@ 156 struct file_operations fops = { 157 .copy_file_range = copy_file_range_f, 158 }; 159 160@ has_remap_file_range @ 161identifier fops0.fops; 162identifier remap_file_range_f; 163@@ 164 struct file_operations fops = { 165 .remap_file_range = remap_file_range_f, 166 }; 167 168@ has_splice_read @ 169identifier fops0.fops; 170identifier splice_read_f; 171@@ 172 struct file_operations fops = { 173 .splice_read = splice_read_f, 174 }; 175 176@ has_splice_write @ 177identifier fops0.fops; 178identifier splice_write_f; 179@@ 180 struct file_operations fops = { 181 .splice_write = splice_write_f, 182 }; 183 184 185// file_operations that is candidate for stream_open conversion - it does not 186// use mmap and other methods that assume @offset access to file. 187// 188// XXX for simplicity require no .{read/write}_iter and no .splice_{read/write} for now. 189// XXX maybe_steam.fops cannot be used in other rules - it gives "bad rule maybe_stream or bad variable fops". 190@ 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 @ 191identifier fops0.fops; 192@@ 193 struct file_operations fops = { 194 }; 195 196 197// ---- conversions ---- 198 199// XXX .open = nonseekable_open -> .open = stream_open 200// XXX .open = func -> openfunc -> nonseekable_open 201 202// read & write 203// 204// if both are used in the same file_operations together with an opener - 205// under that conditions we can use stream_open instead of nonseekable_open. 206@ fops_rw depends on maybe_stream @ 207identifier fops0.fops, openfunc; 208identifier stream_reader.readstream; 209identifier stream_writer.writestream; 210@@ 211 struct file_operations fops = { 212 .open = openfunc, 213 .read = readstream, 214 .write = writestream, 215 }; 216 217@ report_rw depends on report @ 218identifier fops_rw.openfunc; 219position p1; 220@@ 221 openfunc(...) { 222 <... 223 nonseekable_open@p1 224 ...> 225 } 226 227@ script:python depends on report && reader_blocks @ 228fops << fops0.fops; 229p << report_rw.p1; 230@@ 231coccilib.report.print_report(p[0], 232 "ERROR: %s: .read() can deadlock .write(); change nonseekable_open -> stream_open to fix." % (fops,)) 233 234@ script:python depends on report && !reader_blocks @ 235fops << fops0.fops; 236p << report_rw.p1; 237@@ 238coccilib.report.print_report(p[0], 239 "WARNING: %s: .read() and .write() have stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) 240 241 242@ explain_rw_deadlocked depends on explain && reader_blocks @ 243identifier fops_rw.openfunc; 244@@ 245 openfunc(...) { 246 <... 247- nonseekable_open 248+ nonseekable_open /* read & write (was deadlock) */ 249 ...> 250 } 251 252 253@ explain_rw_nodeadlock depends on explain && !reader_blocks @ 254identifier fops_rw.openfunc; 255@@ 256 openfunc(...) { 257 <... 258- nonseekable_open 259+ nonseekable_open /* read & write (no direct deadlock) */ 260 ...> 261 } 262 263@ patch_rw depends on patch @ 264identifier fops_rw.openfunc; 265@@ 266 openfunc(...) { 267 <... 268- nonseekable_open 269+ stream_open 270 ...> 271 } 272 273 274// read, but not write 275@ fops_r depends on maybe_stream && !has_write @ 276identifier fops0.fops, openfunc; 277identifier stream_reader.readstream; 278@@ 279 struct file_operations fops = { 280 .open = openfunc, 281 .read = readstream, 282 }; 283 284@ report_r depends on report @ 285identifier fops_r.openfunc; 286position p1; 287@@ 288 openfunc(...) { 289 <... 290 nonseekable_open@p1 291 ...> 292 } 293 294@ script:python depends on report @ 295fops << fops0.fops; 296p << report_r.p1; 297@@ 298coccilib.report.print_report(p[0], 299 "WARNING: %s: .read() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) 300 301@ explain_r depends on explain @ 302identifier fops_r.openfunc; 303@@ 304 openfunc(...) { 305 <... 306- nonseekable_open 307+ nonseekable_open /* read only */ 308 ...> 309 } 310 311@ patch_r depends on patch @ 312identifier fops_r.openfunc; 313@@ 314 openfunc(...) { 315 <... 316- nonseekable_open 317+ stream_open 318 ...> 319 } 320 321 322// write, but not read 323@ fops_w depends on maybe_stream && !has_read @ 324identifier fops0.fops, openfunc; 325identifier stream_writer.writestream; 326@@ 327 struct file_operations fops = { 328 .open = openfunc, 329 .write = writestream, 330 }; 331 332@ report_w depends on report @ 333identifier fops_w.openfunc; 334position p1; 335@@ 336 openfunc(...) { 337 <... 338 nonseekable_open@p1 339 ...> 340 } 341 342@ script:python depends on report @ 343fops << fops0.fops; 344p << report_w.p1; 345@@ 346coccilib.report.print_report(p[0], 347 "WARNING: %s: .write() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) 348 349@ explain_w depends on explain @ 350identifier fops_w.openfunc; 351@@ 352 openfunc(...) { 353 <... 354- nonseekable_open 355+ nonseekable_open /* write only */ 356 ...> 357 } 358 359@ patch_w depends on patch @ 360identifier fops_w.openfunc; 361@@ 362 openfunc(...) { 363 <... 364- nonseekable_open 365+ stream_open 366 ...> 367 } 368 369 370// no read, no write - don't change anything