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 9 virtual report 10 virtual patch 11 virtual 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 @ 15 identifier readstream, ppos; 16 identifier f, buf, len; 17 type 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 @ 25 identifier writestream, ppos; 26 identifier f, buf, len; 27 type 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 @ 37 identifier block_f; 38 identifier 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 @ 51 identifier stream_reader.readstream; 52 identifier wait =~ "^wait_.*"; 53 @@ 54 readstream(...) 55 { 56 ... when exists 57 wait(...) 58 ... when exists 59 } 60 61 @ reader_blocks_1 @ 62 identifier stream_reader.readstream; 63 identifier 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 @ 73 identifier 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 @ 84 identifier fops; 85 @@ 86 struct file_operations fops = { 87 ... 88 }; 89 90 @ has_read @ 91 identifier fops0.fops; 92 identifier read_f; 93 @@ 94 struct file_operations fops = { 95 .read = read_f, 96 }; 97 98 @ has_read_iter @ 99 identifier fops0.fops; 100 identifier read_iter_f; 101 @@ 102 struct file_operations fops = { 103 .read_iter = read_iter_f, 104 }; 105 106 @ has_write @ 107 identifier fops0.fops; 108 identifier write_f; 109 @@ 110 struct file_operations fops = { 111 .write = write_f, 112 }; 113 114 @ has_write_iter @ 115 identifier fops0.fops; 116 identifier write_iter_f; 117 @@ 118 struct file_operations fops = { 119 .write_iter = write_iter_f, 120 }; 121 122 @ has_llseek @ 123 identifier fops0.fops; 124 identifier llseek_f; 125 @@ 126 struct file_operations fops = { 127 .llseek = llseek_f, 128 }; 129 130 @ has_no_llseek @ 131 identifier fops0.fops; 132 @@ 133 struct file_operations fops = { 134 }; 135 136 @ has_noop_llseek @ 137 identifier fops0.fops; 138 @@ 139 struct file_operations fops = { 140 .llseek = noop_llseek, 141 }; 142 143 @ has_mmap @ 144 identifier fops0.fops; 145 identifier mmap_f; 146 @@ 147 struct file_operations fops = { 148 .mmap = mmap_f, 149 }; 150 151 @ has_copy_file_range @ 152 identifier fops0.fops; 153 identifier 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 @ 160 identifier fops0.fops; 161 identifier remap_file_range_f; 162 @@ 163 struct file_operations fops = { 164 .remap_file_range = remap_file_range_f, 165 }; 166 167 @ has_splice_read @ 168 identifier fops0.fops; 169 identifier splice_read_f; 170 @@ 171 struct file_operations fops = { 172 .splice_read = splice_read_f, 173 }; 174 175 @ has_splice_write @ 176 identifier fops0.fops; 177 identifier 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 @ 190 identifier 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 @ 206 identifier fops0.fops, openfunc; 207 identifier stream_reader.readstream; 208 identifier 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 @ 217 identifier fops_rw.openfunc; 218 position p1; 219 @@ 220 openfunc(...) { 221 <... 222 nonseekable_open@p1 223 ...> 224 } 225 226 @ script:python depends on report && reader_blocks @ 227 fops << fops0.fops; 228 p << report_rw.p1; 229 @@ 230 coccilib.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 @ 234 fops << fops0.fops; 235 p << report_rw.p1; 236 @@ 237 coccilib.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 @ 242 identifier 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 @ 253 identifier 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 @ 263 identifier 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 @ 275 identifier fops0.fops, openfunc; 276 identifier stream_reader.readstream; 277 @@ 278 struct file_operations fops = { 279 .open = openfunc, 280 .read = readstream, 281 }; 282 283 @ report_r depends on report @ 284 identifier fops_r.openfunc; 285 position p1; 286 @@ 287 openfunc(...) { 288 <... 289 nonseekable_open@p1 290 ...> 291 } 292 293 @ script:python depends on report @ 294 fops << fops0.fops; 295 p << report_r.p1; 296 @@ 297 coccilib.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 @ 301 identifier fops_r.openfunc; 302 @@ 303 openfunc(...) { 304 <... 305 - nonseekable_open 306 + nonseekable_open /* read only */ 307 ...> 308 } 309 310 @ patch_r depends on patch @ 311 identifier 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 @ 323 identifier fops0.fops, openfunc; 324 identifier stream_writer.writestream; 325 @@ 326 struct file_operations fops = { 327 .open = openfunc, 328 .write = writestream, 329 }; 330 331 @ report_w depends on report @ 332 identifier fops_w.openfunc; 333 position p1; 334 @@ 335 openfunc(...) { 336 <... 337 nonseekable_open@p1 338 ...> 339 } 340 341 @ script:python depends on report @ 342 fops << fops0.fops; 343 p << report_w.p1; 344 @@ 345 coccilib.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 @ 349 identifier fops_w.openfunc; 350 @@ 351 openfunc(...) { 352 <... 353 - nonseekable_open 354 + nonseekable_open /* write only */ 355 ...> 356 } 357 358 @ patch_w depends on patch @ 359 identifier 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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.