Now that "struct proc_ops" exist we can start putting there stuff which could not fly with VFS "struct file_operations"... Most of fs/proc/inode.c file is dedicated to make open/read/.../close reliable in the event of disappearing /proc entries which usually happens if module is getting removed. Files like /proc/cpuinfo which never disappear simply do not need such protection. Save 2 atomic ops, 1 allocation, 1 free per open/read/close sequence for such "permanent" files. Enable "permanent" flag for /proc/cpuinfo /proc/kmsg /proc/modules /proc/slabinfo /proc/stat /proc/sysvipc/* /proc/swaps More will come once I figure out foolproof way to prevent out module authors from marking their stuff "permanent" for performance reasons when it is not. This should help with scalability: benchmark is "read /proc/cpuinfo R times by N threads scattered over the system". N R t, s (before) t, s (after) ----------------------------------------------------- 64 4096 1.582458 1.530502 -3.2% 256 4096 6.371926 6.125168 -3.9% 1024 4096 25.64888 24.47528 -4.6% Benchmark source: #include <chrono> #include <iostream> #include <thread> #include <vector> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> const int NR_CPUS = sysconf(_SC_NPROCESSORS_ONLN); int N; const char *filename; int R; int xxx = 0; int glue(int n) { cpu_set_t m; CPU_ZERO(&m); CPU_SET(n, &m); return sched_setaffinity(0, sizeof(cpu_set_t), &m); } void f(int n) { glue(n % NR_CPUS); while (*(volatile int *)&xxx == 0) { } for (int i = 0; i < R; i++) { int fd = open(filename, O_RDONLY); char buf[4096]; ssize_t rv = read(fd, buf, sizeof(buf)); asm volatile ("" :: "g" (rv)); close(fd); } } int main(int argc, char *argv[]) { if (argc < 4) { std::cerr << "usage: " << argv[0] << ' ' << "N /proc/filename R "; return 1; } N = atoi(argv[1]); filename = argv[2]; R = atoi(argv[3]); for (int i = 0; i < NR_CPUS; i++) { if (glue(i) == 0) break; } std::vector<std::thread> T; T.reserve(N); for (int i = 0; i < N; i++) { T.emplace_back(f, i); } auto t0 = std::chrono::system_clock::now(); { *(volatile int *)&xxx = 1; for (auto& t: T) { t.join(); } } auto t1 = std::chrono::system_clock::now(); std::chrono::duration<double> dt = t1 - t0; std::cout << dt.count() << ' '; return 0; } P.S.: Explicit randomization marker is added because adding non-function pointer will silently disable structure layout randomization. [akpm@linux-foundation.org: coding style fixes] Reported-by: kbuild test robot <lkp@intel.com> Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Joe Perches <joe@perches.com> Link: http://lkml.kernel.org/r/20200222201539.GA22576@avx2 Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Unified · Split
| @@ -17,6 +17,7 @@ static int cpuinfo_open(struct inode *inode, struct file *file) · 16 unmodified lines | |||
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | static const struct proc_ops cpuinfo_proc_ops = { | 19 | static const struct proc_ops cpuinfo_proc_ops = { |
| 20 | .proc_flags = PROC_ENTRY_PERMANENT, | ||
| 20 | .proc_open = cpuinfo_open, | 21 | .proc_open = cpuinfo_open, |
| 21 | .proc_read = seq_read, | 22 | .proc_read = seq_read, |
| 22 | .proc_lseek = seq_lseek, | 23 | .proc_lseek = seq_lseek, |
| @@ -531,6 +531,12 @@ struct proc_dir_entry *proc_create_reg(const char *name, umode_t mode, · 530 unmodified lines | |||
| 531 | return p; | 531 | return p; |
| 532 | } | 532 | } |
| 533 | 533 | ||
| 534 | static inline void pde_set_flags(struct proc_dir_entry *pde) | ||
| 535 | { | ||
| 536 | if (pde->proc_ops->proc_flags & PROC_ENTRY_PERMANENT) | ||
| 537 | pde->flags |= PROC_ENTRY_PERMANENT; | ||
| 538 | } | ||
| 539 | |||
| 534 | struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, | 540 | struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, |
| 535 | struct proc_dir_entry *parent, | 541 | struct proc_dir_entry *parent, |
| 536 | const struct proc_ops *proc_ops, void *data) | 542 | const struct proc_ops *proc_ops, void *data) |
| @@ -541,6 +547,7 @@ struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, · 4 unmodified lines | |||
| 541 | if (!p) | 547 | if (!p) |
| 542 | return NULL; | 548 | return NULL; |
| 543 | p->proc_ops = proc_ops; | 549 | p->proc_ops = proc_ops; |
| 550 | pde_set_flags(p); | ||
| 544 | return proc_register(parent, p); | 551 | return proc_register(parent, p); |
| 545 | } | 552 | } |
| 546 | EXPORT_SYMBOL(proc_create_data); | 553 | EXPORT_SYMBOL(proc_create_data); |
| @@ -572,6 +579,7 @@ static int proc_seq_release(struct inode *inode, struct file *file) · 25 unmodified lines | |||
| 572 | } | 579 | } |
| 573 | 580 | ||
| 574 | static const struct proc_ops proc_seq_ops = { | 581 | static const struct proc_ops proc_seq_ops = { |
| 582 | /* not permanent -- can call into arbitrary seq_operations */ | ||
| 575 | .proc_open = proc_seq_open, | 583 | .proc_open = proc_seq_open, |
| 576 | .proc_read = seq_read, | 584 | .proc_read = seq_read, |
| 577 | .proc_lseek = seq_lseek, | 585 | .proc_lseek = seq_lseek, |
| @@ -602,6 +610,7 @@ static int proc_single_open(struct inode *inode, struct file *file) · 24 unmodified lines | |||
| 602 | } | 610 | } |
| 603 | 611 | ||
| 604 | static const struct proc_ops proc_single_ops = { | 612 | static const struct proc_ops proc_single_ops = { |
| 613 | /* not permanent -- can call into arbitrary ->single_show */ | ||
| 605 | .proc_open = proc_single_open, | 614 | .proc_open = proc_single_open, |
| 606 | .proc_read = seq_read, | 615 | .proc_read = seq_read, |
| 607 | .proc_lseek = seq_lseek, | 616 | .proc_lseek = seq_lseek, |
| @@ -662,9 +671,13 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent) · 54 unmodified lines | |||
| 662 | 671 | ||
| 663 | de = pde_subdir_find(parent, fn, len); | 672 | de = pde_subdir_find(parent, fn, len); |
| 664 | if (de) { | 673 | if (de) { |
| 665 | rb_erase(&de->subdir_node, &parent->subdir); | 674 | if (unlikely(pde_is_permanent(de))) { |
| 666 | if (S_ISDIR(de->mode)) { | 675 | WARN(1, "removing permanent /proc entry '%s'", de->name); |
| 667 | parent->nlink--; | 676 | de = NULL; |
| 677 | } else { | ||
| 678 | rb_erase(&de->subdir_node, &parent->subdir); | ||
| 679 | if (S_ISDIR(de->mode)) | ||
| 680 | parent->nlink--; | ||
| 668 | } | 681 | } |
| 669 | } | 682 | } |
| 670 | write_unlock(&proc_subdir_lock); | 683 | write_unlock(&proc_subdir_lock); |
| @@ -700,12 +713,24 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent) · 29 unmodified lines | |||
| 700 | write_unlock(&proc_subdir_lock); | 713 | write_unlock(&proc_subdir_lock); |
| 701 | return -ENOENT; | 714 | return -ENOENT; |
| 702 | } | 715 | } |
| 716 | if (unlikely(pde_is_permanent(root))) { | ||
| 717 | write_unlock(&proc_subdir_lock); | ||
| 718 | WARN(1, "removing permanent /proc entry '%s/%s'", | ||
| 719 | root->parent->name, root->name); | ||
| 720 | return -EINVAL; | ||
| 721 | } | ||
| 703 | rb_erase(&root->subdir_node, &parent->subdir); | 722 | rb_erase(&root->subdir_node, &parent->subdir); |
| 704 | 723 | ||
| 705 | de = root; | 724 | de = root; |
| 706 | while (1) { | 725 | while (1) { |
| 707 | next = pde_subdir_first(de); | 726 | next = pde_subdir_first(de); |
| 708 | if (next) { | 727 | if (next) { |
| 728 | if (unlikely(pde_is_permanent(root))) { | ||
| 729 | write_unlock(&proc_subdir_lock); | ||
| 730 | WARN(1, "removing permanent /proc entry '%s/%s'", | ||
| 731 | next->parent->name, next->name); | ||
| 732 | return -EINVAL; | ||
| 733 | } | ||
| 709 | rb_erase(&next->subdir_node, &de->subdir); | 734 | rb_erase(&next->subdir_node, &de->subdir); |
| 710 | de = next; | 735 | de = next; |
| 711 | continue; | 736 | continue; |
| @@ -259,135 +259,204 @@ void proc_entry_rundown(struct proc_dir_entry *de) · 258 unmodified lines | |||
| 259 | spin_unlock(&de->pde_unload_lock); | 259 | spin_unlock(&de->pde_unload_lock); |
| 260 | } | 260 | } |
| 261 | 261 | ||
| 262 | static loff_t pde_lseek(struct proc_dir_entry *pde, struct file *file, loff_t offset, int whence) | ||
| 263 | { | ||
| 264 | typeof_member(struct proc_ops, proc_lseek) lseek; | ||
| 265 | |||
| 266 | lseek = pde->proc_ops->proc_lseek; | ||
| 267 | if (!lseek) | ||
| 268 | lseek = default_llseek; | ||
| 269 | return lseek(file, offset, whence); | ||
| 270 | } | ||
| 271 | |||
| 262 | static loff_t proc_reg_llseek(struct file *file, loff_t offset, int whence) | 272 | static loff_t proc_reg_llseek(struct file *file, loff_t offset, int whence) |
| 263 | { | 273 | { |
| 264 | struct proc_dir_entry *pde = PDE(file_inode(file)); | 274 | struct proc_dir_entry *pde = PDE(file_inode(file)); |
| 265 | loff_t rv = -EINVAL; | 275 | loff_t rv = -EINVAL; |
| 266 | if (use_pde(pde)) { | ||
| 267 | typeof_member(struct proc_ops, proc_lseek) lseek; | ||
| 268 | 276 | ||
| 269 | lseek = pde->proc_ops->proc_lseek; | 277 | if (pde_is_permanent(pde)) { |
| 270 | if (!lseek) | 278 | return pde_lseek(pde, file, offset, whence); |
| 271 | lseek = default_llseek; | 279 | } else if (use_pde(pde)) { |
| 272 | rv = lseek(file, offset, whence); | 280 | rv = pde_lseek(pde, file, offset, whence); |
| 273 | unuse_pde(pde); | 281 | unuse_pde(pde); |
| 274 | } | 282 | } |
| 275 | return rv; | 283 | return rv; |
| 276 | } | 284 | } |
| 277 | 285 | ||
| 286 | static ssize_t pde_read(struct proc_dir_entry *pde, struct file *file, char __user *buf, size_t count, loff_t *ppos) | ||
| 287 | { | ||
| 288 | typeof_member(struct proc_ops, proc_read) read; | ||
| 289 | |||
| 290 | read = pde->proc_ops->proc_read; | ||
| 291 | if (read) | ||
| 292 | return read(file, buf, count, ppos); | ||
| 293 | return -EIO; | ||
| 294 | } | ||
| 295 | |||
| 278 | static ssize_t proc_reg_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) | 296 | static ssize_t proc_reg_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) |
| 279 | { | 297 | { |
| 280 | struct proc_dir_entry *pde = PDE(file_inode(file)); | 298 | struct proc_dir_entry *pde = PDE(file_inode(file)); |
| 281 | ssize_t rv = -EIO; | 299 | ssize_t rv = -EIO; |
| 282 | if (use_pde(pde)) { | ||
| 283 | typeof_member(struct proc_ops, proc_read) read; | ||
| 284 | 300 | ||
| 285 | read = pde->proc_ops->proc_read; | 301 | if (pde_is_permanent(pde)) { |
| 286 | if (read) | 302 | return pde_read(pde, file, buf, count, ppos); |
| 287 | rv = read(file, buf, count, ppos); | 303 | } else if (use_pde(pde)) { |
| 304 | rv = pde_read(pde, file, buf, count, ppos); | ||
| 288 | unuse_pde(pde); | 305 | unuse_pde(pde); |
| 289 | } | 306 | } |
| 290 | return rv; | 307 | return rv; |
| 291 | } | 308 | } |
| 292 | 309 | ||
| 310 | static ssize_t pde_write(struct proc_dir_entry *pde, struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
| 311 | { | ||
| 312 | typeof_member(struct proc_ops, proc_write) write; | ||
| 313 | |||
| 314 | write = pde->proc_ops->proc_write; | ||
| 315 | if (write) | ||
| 316 | return write(file, buf, count, ppos); | ||
| 317 | return -EIO; | ||
| 318 | } | ||
| 319 | |||
| 293 | static ssize_t proc_reg_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | 320 | static ssize_t proc_reg_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) |
| 294 | { | 321 | { |
| 295 | struct proc_dir_entry *pde = PDE(file_inode(file)); | 322 | struct proc_dir_entry *pde = PDE(file_inode(file)); |
| 296 | ssize_t rv = -EIO; | 323 | ssize_t rv = -EIO; |
| 297 | if (use_pde(pde)) { | ||
| 298 | typeof_member(struct proc_ops, proc_write) write; | ||
| 299 | 324 | ||
| 300 | write = pde->proc_ops->proc_write; | 325 | if (pde_is_permanent(pde)) { |
| 301 | if (write) | 326 | return pde_write(pde, file, buf, count, ppos); |
| 302 | rv = write(file, buf, count, ppos); | 327 | } else if (use_pde(pde)) { |
| 328 | rv = pde_write(pde, file, buf, count, ppos); | ||
| 303 | unuse_pde(pde); | 329 | unuse_pde(pde); |
| 304 | } | 330 | } |
| 305 | return rv; | 331 | return rv; |
| 306 | } | 332 | } |
| 307 | 333 | ||
| 334 | static __poll_t pde_poll(struct proc_dir_entry *pde, struct file *file, struct poll_table_struct *pts) | ||
| 335 | { | ||
| 336 | typeof_member(struct proc_ops, proc_poll) poll; | ||
| 337 | |||
| 338 | poll = pde->proc_ops->proc_poll; | ||
| 339 | if (poll) | ||
| 340 | return poll(file, pts); | ||
| 341 | return DEFAULT_POLLMASK; | ||
| 342 | } | ||
| 343 | |||
| 308 | static __poll_t proc_reg_poll(struct file *file, struct poll_table_struct *pts) | 344 | static __poll_t proc_reg_poll(struct file *file, struct poll_table_struct *pts) |
| 309 | { | 345 | { |
| 310 | struct proc_dir_entry *pde = PDE(file_inode(file)); | 346 | struct proc_dir_entry *pde = PDE(file_inode(file)); |
| 311 | __poll_t rv = DEFAULT_POLLMASK; | 347 | __poll_t rv = DEFAULT_POLLMASK; |
| 312 | if (use_pde(pde)) { | ||
| 313 | typeof_member(struct proc_ops, proc_poll) poll; | ||
| 314 | 348 | ||
| 315 | poll = pde->proc_ops->proc_poll; | 349 | if (pde_is_permanent(pde)) { |
| 316 | if (poll) | 350 | return pde_poll(pde, file, pts); |
| 317 | rv = poll(file, pts); | 351 | } else if (use_pde(pde)) { |
| 352 | rv = pde_poll(pde, file, pts); | ||
| 318 | unuse_pde(pde); | 353 | unuse_pde(pde); |
| 319 | } | 354 | } |
| 320 | return rv; | 355 | return rv; |
| 321 | } | 356 | } |
| 322 | 357 | ||
| 358 | static long pde_ioctl(struct proc_dir_entry *pde, struct file *file, unsigned int cmd, unsigned long arg) | ||
| 359 | { | ||
| 360 | typeof_member(struct proc_ops, proc_ioctl) ioctl; | ||
| 361 | |||
| 362 | ioctl = pde->proc_ops->proc_ioctl; | ||
| 363 | if (ioctl) | ||
| 364 | return ioctl(file, cmd, arg); | ||
| 365 | return -ENOTTY; | ||
| 366 | } | ||
| 367 | |||
| 323 | static long proc_reg_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | 368 | static long proc_reg_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| 324 | { | 369 | { |
| 325 | struct proc_dir_entry *pde = PDE(file_inode(file)); | 370 | struct proc_dir_entry *pde = PDE(file_inode(file)); |
| 326 | long rv = -ENOTTY; | 371 | long rv = -ENOTTY; |
| 327 | if (use_pde(pde)) { | ||
| 328 | typeof_member(struct proc_ops, proc_ioctl) ioctl; | ||
| 329 | 372 | ||
| 330 | ioctl = pde->proc_ops->proc_ioctl; | 373 | if (pde_is_permanent(pde)) { |
| 331 | if (ioctl) | 374 | return pde_ioctl(pde, file, cmd, arg); |
| 332 | rv = ioctl(file, cmd, arg); | 375 | } else if (use_pde(pde)) { |
| 376 | rv = pde_ioctl(pde, file, cmd, arg); | ||
| 333 | unuse_pde(pde); | 377 | unuse_pde(pde); |
| 334 | } | 378 | } |
| 335 | return rv; | 379 | return rv; |
| 336 | } | 380 | } |
| 337 | 381 | ||
| 338 | #ifdef CONFIG_COMPAT | 382 | #ifdef CONFIG_COMPAT |
| 383 | static long pde_compat_ioctl(struct proc_dir_entry *pde, struct file *file, unsigned int cmd, unsigned long arg) | ||
| 384 | { | ||
| 385 | typeof_member(struct proc_ops, proc_compat_ioctl) compat_ioctl; | ||
| 386 | |||
| 387 | compat_ioctl = pde->proc_ops->proc_compat_ioctl; | ||
| 388 | if (compat_ioctl) | ||
| 389 | return compat_ioctl(file, cmd, arg); | ||
| 390 | return -ENOTTY; | ||
| 391 | } | ||
| 392 | |||
| 339 | static long proc_reg_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | 393 | static long proc_reg_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| 340 | { | 394 | { |
| 341 | struct proc_dir_entry *pde = PDE(file_inode(file)); | 395 | struct proc_dir_entry *pde = PDE(file_inode(file)); |
| 342 | long rv = -ENOTTY; | 396 | long rv = -ENOTTY; |
| 343 | if (use_pde(pde)) { | 397 | if (pde_is_permanent(pde)) { |
| 344 | typeof_member(struct proc_ops, proc_compat_ioctl) compat_ioctl; | 398 | return pde_compat_ioctl(pde, file, cmd, arg); |
| 345 | 399 | } else if (use_pde(pde)) { | |
| 346 | compat_ioctl = pde->proc_ops->proc_compat_ioctl; | 400 | rv = pde_compat_ioctl(pde, file, cmd, arg); |
| 347 | if (compat_ioctl) | ||
| 348 | rv = compat_ioctl(file, cmd, arg); | ||
| 349 | unuse_pde(pde); | 401 | unuse_pde(pde); |
| 350 | } | 402 | } |
| 351 | return rv; | 403 | return rv; |
| 352 | } | 404 | } |
| 353 | #endif | 405 | #endif |
| 354 | 406 | ||
| 407 | static int pde_mmap(struct proc_dir_entry *pde, struct file *file, struct vm_area_struct *vma) | ||
| 408 | { | ||
| 409 | typeof_member(struct proc_ops, proc_mmap) mmap; | ||
| 410 | |||
| 411 | mmap = pde->proc_ops->proc_mmap; | ||
| 412 | if (mmap) | ||
| 413 | return mmap(file, vma); | ||
| 414 | return -EIO; | ||
| 415 | } | ||
| 416 | |||
| 355 | static int proc_reg_mmap(struct file *file, struct vm_area_struct *vma) | 417 | static int proc_reg_mmap(struct file *file, struct vm_area_struct *vma) |
| 356 | { | 418 | { |
| 357 | struct proc_dir_entry *pde = PDE(file_inode(file)); | 419 | struct proc_dir_entry *pde = PDE(file_inode(file)); |
| 358 | int rv = -EIO; | 420 | int rv = -EIO; |
| 359 | if (use_pde(pde)) { | ||
| 360 | typeof_member(struct proc_ops, proc_mmap) mmap; | ||
| 361 | 421 | ||
| 362 | mmap = pde->proc_ops->proc_mmap; | 422 | if (pde_is_permanent(pde)) { |
| 363 | if (mmap) | 423 | return pde_mmap(pde, file, vma); |
| 364 | rv = mmap(file, vma); | 424 | } else if (use_pde(pde)) { |
| 425 | rv = pde_mmap(pde, file, vma); | ||
| 365 | unuse_pde(pde); | 426 | unuse_pde(pde); |
| 366 | } | 427 | } |
| 367 | return rv; | 428 | return rv; |
| 368 | } | 429 | } |
| 369 | 430 | ||
| 370 | static unsigned long | 431 | static unsigned long |
| 371 | proc_reg_get_unmapped_area(struct file *file, unsigned long orig_addr, | 432 | pde_get_unmapped_area(struct proc_dir_entry *pde, struct file *file, unsigned long orig_addr, |
| 372 | unsigned long len, unsigned long pgoff, | 433 | unsigned long len, unsigned long pgoff, |
| 373 | unsigned long flags) | 434 | unsigned long flags) |
| 374 | { | 435 | { |
| 375 | struct proc_dir_entry *pde = PDE(file_inode(file)); | 436 | typeof_member(struct proc_ops, proc_get_unmapped_area) get_area; |
| 376 | unsigned long rv = -EIO; | ||
| 377 | |||
| 378 | if (use_pde(pde)) { | ||
| 379 | typeof_member(struct proc_ops, proc_get_unmapped_area) get_area; | ||
| 380 | 437 | ||
| 381 | get_area = pde->proc_ops->proc_get_unmapped_area; | 438 | get_area = pde->proc_ops->proc_get_unmapped_area; |
| 382 | #ifdef CONFIG_MMU | 439 | #ifdef CONFIG_MMU |
| 383 | if (!get_area) | 440 | if (!get_area) |
| 384 | get_area = current->mm->get_unmapped_area; | 441 | get_area = current->mm->get_unmapped_area; |
| 385 | #endif | 442 | #endif |
| 443 | if (get_area) | ||
| 444 | return get_area(file, orig_addr, len, pgoff, flags); | ||
| 445 | return orig_addr; | ||
| 446 | } | ||
| 447 | |||
| 448 | static unsigned long | ||
| 449 | proc_reg_get_unmapped_area(struct file *file, unsigned long orig_addr, | ||
| 450 | unsigned long len, unsigned long pgoff, | ||
| 451 | unsigned long flags) | ||
| 452 | { | ||
| 453 | struct proc_dir_entry *pde = PDE(file_inode(file)); | ||
| 454 | unsigned long rv = -EIO; | ||
| 386 | 455 | ||
| 387 | if (get_area) | 456 | if (pde_is_permanent(pde)) { |
| 388 | rv = get_area(file, orig_addr, len, pgoff, flags); | 457 | return pde_get_unmapped_area(pde, file, orig_addr, len, pgoff, flags); |
| 389 | else | 458 | } else if (use_pde(pde)) { |
| 390 | rv = orig_addr; | 459 | rv = pde_get_unmapped_area(pde, file, orig_addr, len, pgoff, flags); |
| 391 | unuse_pde(pde); | 460 | unuse_pde(pde); |
| 392 | } | 461 | } |
| 393 | return rv; | 462 | return rv; |
| @@ -401,6 +470,13 @@ static int proc_reg_open(struct inode *inode, struct file *file) · 7 unmodified lines | |||
| 401 | typeof_member(struct proc_ops, proc_release) release; | 470 | typeof_member(struct proc_ops, proc_release) release; |
| 402 | struct pde_opener *pdeo; | 471 | struct pde_opener *pdeo; |
| 403 | 472 | ||
| 473 | if (pde_is_permanent(pde)) { | ||
| 474 | open = pde->proc_ops->proc_open; | ||
| 475 | if (open) | ||
| 476 | rv = open(inode, file); | ||
| 477 | return rv; | ||
| 478 | } | ||
| 479 | |||
| 404 | /* | 480 | /* |
| 405 | * Ensure that | 481 | * Ensure that |
| 406 | * 1) PDE's ->release hook will be called no matter what | 482 | * 1) PDE's ->release hook will be called no matter what |
| @@ -450,6 +526,17 @@ static int proc_reg_release(struct inode *inode, struct file *file) · 43 unmodified lines | |||
| 450 | { | 526 | { |
| 451 | struct proc_dir_entry *pde = PDE(inode); | 527 | struct proc_dir_entry *pde = PDE(inode); |
| 452 | struct pde_opener *pdeo; | 528 | struct pde_opener *pdeo; |
| 529 | |||
| 530 | if (pde_is_permanent(pde)) { | ||
| 531 | typeof_member(struct proc_ops, proc_release) release; | ||
| 532 | |||
| 533 | release = pde->proc_ops->proc_release; | ||
| 534 | if (release) { | ||
| 535 | return release(inode, file); | ||
| 536 | } | ||
| 537 | return 0; | ||
| 538 | } | ||
| 539 | |||
| 453 | spin_lock(&pde->pde_unload_lock); | 540 | spin_lock(&pde->pde_unload_lock); |
| 454 | list_for_each_entry(pdeo, &pde->pde_openers, lh) { | 541 | list_for_each_entry(pdeo, &pde->pde_openers, lh) { |
| 455 | if (pdeo->file == file) { | 542 | if (pdeo->file == file) { |
| @@ -61,6 +61,7 @@ struct proc_dir_entry { · 60 unmodified lines | |||
| 61 | struct rb_node subdir_node; | 61 | struct rb_node subdir_node; |
| 62 | char *name; | 62 | char *name; |
| 63 | umode_t mode; | 63 | umode_t mode; |
| 64 | u8 flags; | ||
| 64 | u8 namelen; | 65 | u8 namelen; |
| 65 | char inline_name[]; | 66 | char inline_name[]; |
| 66 | } __randomize_layout; | 67 | } __randomize_layout; |
| @@ -73,6 +74,11 @@ struct proc_dir_entry { · 6 unmodified lines | |||
| 73 | 0) | 74 | 0) |
| 74 | #define SIZEOF_PDE_INLINE_NAME (SIZEOF_PDE - sizeof(struct proc_dir_entry)) | 75 | #define SIZEOF_PDE_INLINE_NAME (SIZEOF_PDE - sizeof(struct proc_dir_entry)) |
| 75 | 76 | ||
| 77 | static inline bool pde_is_permanent(const struct proc_dir_entry *pde) | ||
| 78 | { | ||
| 79 | return pde->flags & PROC_ENTRY_PERMANENT; | ||
| 80 | } | ||
| 81 | |||
| 76 | extern struct kmem_cache *proc_dir_entry_cache; | 82 | extern struct kmem_cache *proc_dir_entry_cache; |
| 77 | void pde_free(struct proc_dir_entry *pde); | 83 | void pde_free(struct proc_dir_entry *pde); |
| 78 | 84 | ||
| @@ -50,6 +50,7 @@ static __poll_t kmsg_poll(struct file *file, poll_table *wait) · 49 unmodified lines | |||
| 50 | 50 | ||
| 51 | 51 | ||
| 52 | static const struct proc_ops kmsg_proc_ops = { | 52 | static const struct proc_ops kmsg_proc_ops = { |
| 53 | .proc_flags = PROC_ENTRY_PERMANENT, | ||
| 53 | .proc_read = kmsg_read, | 54 | .proc_read = kmsg_read, |
| 54 | .proc_poll = kmsg_poll, | 55 | .proc_poll = kmsg_poll, |
| 55 | .proc_open = kmsg_open, | 56 | .proc_open = kmsg_open, |
| @@ -224,6 +224,7 @@ static int stat_open(struct inode *inode, struct file *file) · 223 unmodified lines | |||
| 224 | } | 224 | } |
| 225 | 225 | ||
| 226 | static const struct proc_ops stat_proc_ops = { | 226 | static const struct proc_ops stat_proc_ops = { |
| 227 | .proc_flags = PROC_ENTRY_PERMANENT, | ||
| 227 | .proc_open = stat_open, | 228 | .proc_open = stat_open, |
| 228 | .proc_read = seq_read, | 229 | .proc_read = seq_read, |
| 229 | .proc_lseek = seq_lseek, | 230 | .proc_lseek = seq_lseek, |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | #ifndef _LINUX_PROC_FS_H | 5 | #ifndef _LINUX_PROC_FS_H |
| 6 | #define _LINUX_PROC_FS_H | 6 | #define _LINUX_PROC_FS_H |
| 7 | 7 | ||
| 8 | #include <linux/compiler.h> | ||
| 8 | #include <linux/types.h> | 9 | #include <linux/types.h> |
| 9 | #include <linux/fs.h> | 10 | #include <linux/fs.h> |
| 10 | 11 | ||
| @@ -12,7 +13,21 @@ struct proc_dir_entry; · 1 unmodified line | |||
| 12 | struct seq_file; | 13 | struct seq_file; |
| 13 | struct seq_operations; | 14 | struct seq_operations; |
| 14 | 15 | ||
| 16 | enum { | ||
| 17 | /* | ||
| 18 | * All /proc entries using this ->proc_ops instance are never removed. | ||
| 19 | * | ||
| 20 | * If in doubt, ignore this flag. | ||
| 21 | */ | ||
| 22 | #ifdef MODULE | ||
| 23 | PROC_ENTRY_PERMANENT = 0U, | ||
| 24 | #else | ||
| 25 | PROC_ENTRY_PERMANENT = 1U << 0, | ||
| 26 | #endif | ||
| 27 | }; | ||
| 28 | |||
| 15 | struct proc_ops { | 29 | struct proc_ops { |
| 30 | unsigned int proc_flags; | ||
| 16 | int (*proc_open)(struct inode *, struct file *); | 31 | int (*proc_open)(struct inode *, struct file *); |
| 17 | ssize_t (*proc_read)(struct file *, char __user *, size_t, loff_t *); | 32 | ssize_t (*proc_read)(struct file *, char __user *, size_t, loff_t *); |
| 18 | ssize_t (*proc_write)(struct file *, const char __user *, size_t, loff_t *); | 33 | ssize_t (*proc_write)(struct file *, const char __user *, size_t, loff_t *); |
| @@ -25,7 +40,7 @@ struct proc_ops { · 6 unmodified lines | |||
| 25 | #endif | 40 | #endif |
| 26 | int (*proc_mmap)(struct file *, struct vm_area_struct *); | 41 | int (*proc_mmap)(struct file *, struct vm_area_struct *); |
| 27 | unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); | 42 | unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); |
| 28 | }; | 43 | } __randomize_layout; |
| 29 | 44 | ||
| 30 | #ifdef CONFIG_PROC_FS | 45 | #ifdef CONFIG_PROC_FS |
| 31 | 46 | ||
| @@ -885,6 +885,7 @@ static int sysvipc_proc_release(struct inode *inode, struct file *file) · 884 unmodified lines | |||
| 885 | } | 885 | } |
| 886 | 886 | ||
| 887 | static const struct proc_ops sysvipc_proc_ops = { | 887 | static const struct proc_ops sysvipc_proc_ops = { |
| 888 | .proc_flags = PROC_ENTRY_PERMANENT, | ||
| 888 | .proc_open = sysvipc_proc_open, | 889 | .proc_open = sysvipc_proc_open, |
| 889 | .proc_read = seq_read, | 890 | .proc_read = seq_read, |
| 890 | .proc_lseek = seq_lseek, | 891 | .proc_lseek = seq_lseek, |
| @@ -4355,6 +4355,7 @@ static int modules_open(struct inode *inode, struct file *file) · 4354 unmodified lines | |||
| 4355 | } | 4355 | } |
| 4356 | 4356 | ||
| 4357 | static const struct proc_ops modules_proc_ops = { | 4357 | static const struct proc_ops modules_proc_ops = { |
| 4358 | .proc_flags = PROC_ENTRY_PERMANENT, | ||
| 4358 | .proc_open = modules_open, | 4359 | .proc_open = modules_open, |
| 4359 | .proc_read = seq_read, | 4360 | .proc_read = seq_read, |
| 4360 | .proc_lseek = seq_lseek, | 4361 | .proc_lseek = seq_lseek, |
| @@ -1581,6 +1581,7 @@ static int slabinfo_open(struct inode *inode, struct file *file) · 1580 unmodified lines | |||
| 1581 | } | 1581 | } |
| 1582 | 1582 | ||
| 1583 | static const struct proc_ops slabinfo_proc_ops = { | 1583 | static const struct proc_ops slabinfo_proc_ops = { |
| 1584 | .proc_flags = PROC_ENTRY_PERMANENT, | ||
| 1584 | .proc_open = slabinfo_open, | 1585 | .proc_open = slabinfo_open, |
| 1585 | .proc_read = seq_read, | 1586 | .proc_read = seq_read, |
| 1586 | .proc_write = slabinfo_write, | 1587 | .proc_write = slabinfo_write, |
| @@ -2797,6 +2797,7 @@ static int swaps_open(struct inode *inode, struct file *file) · 2796 unmodified lines | |||
| 2797 | } | 2797 | } |
| 2798 | 2798 | ||
| 2799 | static const struct proc_ops swaps_proc_ops = { | 2799 | static const struct proc_ops swaps_proc_ops = { |
| 2800 | .proc_flags = PROC_ENTRY_PERMANENT, | ||
| 2800 | .proc_open = swaps_open, | 2801 | .proc_open = swaps_open, |
| 2801 | .proc_read = seq_read, | 2802 | .proc_read = seq_read, |
| 2802 | .proc_lseek = seq_lseek, | 2803 | .proc_lseek = seq_lseek, |