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 }; 135 136@ has_noop_llseek @ 137identifier fops0.fops; 138@@ 139 struct file_operations fops = { 140 .llseek = noop_llseek, 141 }; 142 143@ has_mmap @ 144identifier fops0.fops; 145identifier mmap_f; 146@@ 147 struct file_operations fops = { 148 .mmap = mmap_f, 149 }; 150 151@ has_copy_file_range @ 152identifier fops0.fops; 153identifier copy_file_range_f; 154@@ 155 struct file_operations fops = { 156 .copy_file_range = copy_file_range_f, 157 }; 158 159@ has_remap_file_range @ 160identifier fops0.fops; 161identifier remap_file_range_f; 162@@ 163 struct file_operations fops = { 164 .remap_file_range = remap_file_range_f, 165 }; 166 167@ has_splice_read @ 168identifier fops0.fops; 169identifier splice_read_f; 170@@ 171 struct file_operations fops = { 172 .splice_read = splice_read_f, 173 }; 174 175@ has_splice_write @ 176identifier fops0.fops; 177identifier splice_write_f; 178@@ 179 struct file_operations fops = { 180 .splice_write = splice_write_f, 181 }; 182 183 184// file_operations that is candidate for stream_open conversion - it does not 185// use mmap and other methods that assume @offset access to file. 186// 187// XXX for simplicity require no .{read/write}_iter and no .splice_{read/write} for now. 188// XXX maybe_steam.fops cannot be used in other rules - it gives "bad rule maybe_stream or bad variable fops". 189@ 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 @ 190identifier fops0.fops; 191@@ 192 struct file_operations fops = { 193 }; 194 195 196// ---- conversions ---- 197 198// XXX .open = nonseekable_open -> .open = stream_open 199// XXX .open = func -> openfunc -> nonseekable_open 200 201// read & write 202// 203// if both are used in the same file_operations together with an opener - 204// under that conditions we can use stream_open instead of nonseekable_open. 205@ fops_rw depends on maybe_stream @ 206identifier fops0.fops, openfunc; 207identifier stream_reader.readstream; 208identifier stream_writer.writestream; 209@@ 210 struct file_operations fops = { 211 .open = openfunc, 212 .read = readstream, 213 .write = writestream, 214 }; 215 216@ report_rw depends on report @ 217identifier fops_rw.openfunc; 218position p1; 219@@ 220 openfunc(...) { 221 <... 222 nonseekable_open@p1 223 ...> 224 } 225 226@ script:python depends on report && reader_blocks @ 227fops << fops0.fops; 228p << report_rw.p1; 229@@ 230coccilib.report.print_report(p[0], 231 "ERROR: %s: .read() can deadlock .write(); change nonseekable_open -> stream_open to fix." % (fops,)) 232 233@ script:python depends on report && !reader_blocks @ 234fops << fops0.fops; 235p << report_rw.p1; 236@@ 237coccilib.report.print_report(p[0], 238 "WARNING: %s: .read() and .write() have stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) 239 240 241@ explain_rw_deadlocked depends on explain && reader_blocks @ 242identifier fops_rw.openfunc; 243@@ 244 openfunc(...) { 245 <... 246- nonseekable_open 247+ nonseekable_open /* read & write (was deadlock) */ 248 ...> 249 } 250 251 252@ explain_rw_nodeadlock depends on explain && !reader_blocks @ 253identifier fops_rw.openfunc; 254@@ 255 openfunc(...) { 256 <... 257- nonseekable_open 258+ nonseekable_open /* read & write (no direct deadlock) */ 259 ...> 260 } 261 262@ patch_rw depends on patch @ 263identifier fops_rw.openfunc; 264@@ 265 openfunc(...) { 266 <... 267- nonseekable_open 268+ stream_open 269 ...> 270 } 271 272 273// read, but not write 274@ fops_r depends on maybe_stream && !has_write @ 275identifier fops0.fops, openfunc; 276identifier stream_reader.readstream; 277@@ 278 struct file_operations fops = { 279 .open = openfunc, 280 .read = readstream, 281 }; 282 283@ report_r depends on report @ 284identifier fops_r.openfunc; 285position p1; 286@@ 287 openfunc(...) { 288 <... 289 nonseekable_open@p1 290 ...> 291 } 292 293@ script:python depends on report @ 294fops << fops0.fops; 295p << report_r.p1; 296@@ 297coccilib.report.print_report(p[0], 298 "WARNING: %s: .read() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) 299 300@ explain_r depends on explain @ 301identifier fops_r.openfunc; 302@@ 303 openfunc(...) { 304 <... 305- nonseekable_open 306+ nonseekable_open /* read only */ 307 ...> 308 } 309 310@ patch_r depends on patch @ 311identifier fops_r.openfunc; 312@@ 313 openfunc(...) { 314 <... 315- nonseekable_open 316+ stream_open 317 ...> 318 } 319 320 321// write, but not read 322@ fops_w depends on maybe_stream && !has_read @ 323identifier fops0.fops, openfunc; 324identifier stream_writer.writestream; 325@@ 326 struct file_operations fops = { 327 .open = openfunc, 328 .write = writestream, 329 }; 330 331@ report_w depends on report @ 332identifier fops_w.openfunc; 333position p1; 334@@ 335 openfunc(...) { 336 <... 337 nonseekable_open@p1 338 ...> 339 } 340 341@ script:python depends on report @ 342fops << fops0.fops; 343p << report_w.p1; 344@@ 345coccilib.report.print_report(p[0], 346 "WARNING: %s: .write() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) 347 348@ explain_w depends on explain @ 349identifier fops_w.openfunc; 350@@ 351 openfunc(...) { 352 <... 353- nonseekable_open 354+ nonseekable_open /* write only */ 355 ...> 356 } 357 358@ patch_w depends on patch @ 359identifier fops_w.openfunc; 360@@ 361 openfunc(...) { 362 <... 363- nonseekable_open 364+ stream_open 365 ...> 366 } 367 368 369// no read, no write - don't change anything 370