Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

debugfs: add small file operations for most files

As struct file_operations is really big, but (most) debugfs
files only use simple_open, read, write and perhaps seek, and
don't need anything else, this wastes a lot of space for NULL
pointers.

Add a struct debugfs_short_fops and some bookkeeping code in
debugfs so that users can use that with debugfs_create_file()
using _Generic to figure out which function to use.

Converting mac80211 to use it where possible saves quite a
bit of space:

1010127 205064 1220 1216411 128f9b net/mac80211/mac80211.ko (before)
981199 205064 1220 1187483 121e9b net/mac80211/mac80211.ko (after)
-------
-28928 = ~28KiB

With a marginal space cost in debugfs:

8701 550 16 9267 2433 fs/debugfs/inode.o (before)
25233 325 32 25590 63f6 fs/debugfs/file.o (before)
8914 558 16 9488 2510 fs/debugfs/inode.o (after)
25380 325 32 25737 6489 fs/debugfs/file.o (after)
---------------
+360 +8

(All on x86-64)

A simple spatch suggests there are more than 300 instances,
not even counting the ones hidden in macros like in mac80211,
that could be trivially converted, for additional savings of
about 240 bytes for each.

Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Link: https://patch.msgid.link/20241022151838.26f9925fb959.Ia80b55e934bbfc45ce0df42a3233d34b35508046@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

+160 -71
+70 -30
fs/debugfs/file.c
··· 100 100 if (!fsd) 101 101 return -ENOMEM; 102 102 103 - fsd->real_fops = (void *)((unsigned long)d_fsd & 104 - ~DEBUGFS_FSDATA_IS_REAL_FOPS_BIT); 103 + if ((unsigned long)d_fsd & DEBUGFS_FSDATA_IS_SHORT_FOPS_BIT) { 104 + fsd->real_fops = NULL; 105 + fsd->short_fops = (void *)((unsigned long)d_fsd & 106 + ~(DEBUGFS_FSDATA_IS_REAL_FOPS_BIT | 107 + DEBUGFS_FSDATA_IS_SHORT_FOPS_BIT)); 108 + } else { 109 + fsd->real_fops = (void *)((unsigned long)d_fsd & 110 + ~DEBUGFS_FSDATA_IS_REAL_FOPS_BIT); 111 + fsd->short_fops = NULL; 112 + } 105 113 refcount_set(&fsd->active_users, 1); 106 114 init_completion(&fsd->active_users_drained); 107 115 INIT_LIST_HEAD(&fsd->cancellations); ··· 249 241 { 250 242 if ((inode->i_mode & 07777 & ~0444) == 0 && 251 243 !(filp->f_mode & FMODE_WRITE) && 252 - !real_fops->unlocked_ioctl && 253 - !real_fops->compat_ioctl && 254 - !real_fops->mmap) 244 + (!real_fops || 245 + (!real_fops->unlocked_ioctl && 246 + !real_fops->compat_ioctl && 247 + !real_fops->mmap))) 255 248 return 0; 256 249 257 250 if (security_locked_down(LOCKDOWN_DEBUGFS)) ··· 325 316 return r; \ 326 317 } 327 318 328 - FULL_PROXY_FUNC(llseek, loff_t, filp, 329 - PROTO(struct file *filp, loff_t offset, int whence), 330 - ARGS(filp, offset, whence)); 319 + #define FULL_PROXY_FUNC_BOTH(name, ret_type, filp, proto, args) \ 320 + static ret_type full_proxy_ ## name(proto) \ 321 + { \ 322 + struct dentry *dentry = F_DENTRY(filp); \ 323 + struct debugfs_fsdata *fsd; \ 324 + ret_type r; \ 325 + \ 326 + r = debugfs_file_get(dentry); \ 327 + if (unlikely(r)) \ 328 + return r; \ 329 + fsd = dentry->d_fsdata; \ 330 + if (fsd->real_fops) \ 331 + r = fsd->real_fops->name(args); \ 332 + else \ 333 + r = fsd->short_fops->name(args); \ 334 + debugfs_file_put(dentry); \ 335 + return r; \ 336 + } 331 337 332 - FULL_PROXY_FUNC(read, ssize_t, filp, 333 - PROTO(struct file *filp, char __user *buf, size_t size, 334 - loff_t *ppos), 335 - ARGS(filp, buf, size, ppos)); 338 + FULL_PROXY_FUNC_BOTH(llseek, loff_t, filp, 339 + PROTO(struct file *filp, loff_t offset, int whence), 340 + ARGS(filp, offset, whence)); 336 341 337 - FULL_PROXY_FUNC(write, ssize_t, filp, 338 - PROTO(struct file *filp, const char __user *buf, size_t size, 339 - loff_t *ppos), 340 - ARGS(filp, buf, size, ppos)); 342 + FULL_PROXY_FUNC_BOTH(read, ssize_t, filp, 343 + PROTO(struct file *filp, char __user *buf, size_t size, 344 + loff_t *ppos), 345 + ARGS(filp, buf, size, ppos)); 346 + 347 + FULL_PROXY_FUNC_BOTH(write, ssize_t, filp, 348 + PROTO(struct file *filp, const char __user *buf, 349 + size_t size, loff_t *ppos), 350 + ARGS(filp, buf, size, ppos)); 341 351 342 352 FULL_PROXY_FUNC(unlocked_ioctl, long, filp, 343 353 PROTO(struct file *filp, unsigned int cmd, unsigned long arg), ··· 391 363 * not to leak any resources. Releasers must not assume that 392 364 * ->i_private is still being meaningful here. 393 365 */ 394 - if (real_fops->release) 366 + if (real_fops && real_fops->release) 395 367 r = real_fops->release(inode, filp); 396 368 397 369 replace_fops(filp, d_inode(dentry)->i_fop); ··· 401 373 } 402 374 403 375 static void __full_proxy_fops_init(struct file_operations *proxy_fops, 404 - const struct file_operations *real_fops) 376 + struct debugfs_fsdata *fsd) 405 377 { 406 378 proxy_fops->release = full_proxy_release; 407 - if (real_fops->llseek) 379 + 380 + if ((fsd->real_fops && fsd->real_fops->llseek) || 381 + (fsd->short_fops && fsd->short_fops->llseek)) 408 382 proxy_fops->llseek = full_proxy_llseek; 409 - if (real_fops->read) 383 + 384 + if ((fsd->real_fops && fsd->real_fops->read) || 385 + (fsd->short_fops && fsd->short_fops->read)) 410 386 proxy_fops->read = full_proxy_read; 411 - if (real_fops->write) 387 + 388 + if ((fsd->real_fops && fsd->real_fops->write) || 389 + (fsd->short_fops && fsd->short_fops->write)) 412 390 proxy_fops->write = full_proxy_write; 413 - if (real_fops->poll) 391 + 392 + if (fsd->real_fops && fsd->real_fops->poll) 414 393 proxy_fops->poll = full_proxy_poll; 415 - if (real_fops->unlocked_ioctl) 394 + 395 + if (fsd->real_fops && fsd->real_fops->unlocked_ioctl) 416 396 proxy_fops->unlocked_ioctl = full_proxy_unlocked_ioctl; 417 397 } 418 398 419 399 static int full_proxy_open(struct inode *inode, struct file *filp) 420 400 { 421 401 struct dentry *dentry = F_DENTRY(filp); 422 - const struct file_operations *real_fops = NULL; 402 + const struct file_operations *real_fops; 423 403 struct file_operations *proxy_fops = NULL; 404 + struct debugfs_fsdata *fsd; 424 405 int r; 425 406 426 407 r = debugfs_file_get(dentry); 427 408 if (r) 428 409 return r == -EIO ? -ENOENT : r; 429 410 430 - real_fops = debugfs_real_fops(filp); 431 - 411 + fsd = dentry->d_fsdata; 412 + real_fops = fsd->real_fops; 432 413 r = debugfs_locked_down(inode, filp, real_fops); 433 414 if (r) 434 415 goto out; 435 416 436 - if (!fops_get(real_fops)) { 417 + if (real_fops && !fops_get(real_fops)) { 437 418 #ifdef CONFIG_MODULES 438 419 if (real_fops->owner && 439 420 real_fops->owner->state == MODULE_STATE_GOING) { ··· 463 426 r = -ENOMEM; 464 427 goto free_proxy; 465 428 } 466 - __full_proxy_fops_init(proxy_fops, real_fops); 429 + __full_proxy_fops_init(proxy_fops, fsd); 467 430 replace_fops(filp, proxy_fops); 468 431 469 - if (real_fops->open) { 470 - r = real_fops->open(inode, filp); 432 + if (!real_fops || real_fops->open) { 433 + if (real_fops) 434 + r = real_fops->open(inode, filp); 435 + else 436 + r = simple_open(inode, filp); 471 437 if (r) { 472 438 replace_fops(filp, d_inode(dentry)->i_fop); 473 439 goto free_proxy;
+26 -37
fs/debugfs/inode.c
··· 412 412 static struct dentry *__debugfs_create_file(const char *name, umode_t mode, 413 413 struct dentry *parent, void *data, 414 414 const struct file_operations *proxy_fops, 415 - const struct file_operations *real_fops) 415 + const void *real_fops) 416 416 { 417 417 struct dentry *dentry; 418 418 struct inode *inode; ··· 450 450 return end_creating(dentry); 451 451 } 452 452 453 - /** 454 - * debugfs_create_file - create a file in the debugfs filesystem 455 - * @name: a pointer to a string containing the name of the file to create. 456 - * @mode: the permission that the file should have. 457 - * @parent: a pointer to the parent dentry for this file. This should be a 458 - * directory dentry if set. If this parameter is NULL, then the 459 - * file will be created in the root of the debugfs filesystem. 460 - * @data: a pointer to something that the caller will want to get to later 461 - * on. The inode.i_private pointer will point to this value on 462 - * the open() call. 463 - * @fops: a pointer to a struct file_operations that should be used for 464 - * this file. 465 - * 466 - * This is the basic "create a file" function for debugfs. It allows for a 467 - * wide range of flexibility in creating a file, or a directory (if you want 468 - * to create a directory, the debugfs_create_dir() function is 469 - * recommended to be used instead.) 470 - * 471 - * This function will return a pointer to a dentry if it succeeds. This 472 - * pointer must be passed to the debugfs_remove() function when the file is 473 - * to be removed (no automatic cleanup happens if your module is unloaded, 474 - * you are responsible here.) If an error occurs, ERR_PTR(-ERROR) will be 475 - * returned. 476 - * 477 - * If debugfs is not enabled in the kernel, the value -%ENODEV will be 478 - * returned. 479 - * 480 - * NOTE: it's expected that most callers should _ignore_ the errors returned 481 - * by this function. Other debugfs functions handle the fact that the "dentry" 482 - * passed to them could be an error and they don't crash in that case. 483 - * Drivers should generally work fine even if debugfs fails to init anyway. 484 - */ 485 - struct dentry *debugfs_create_file(const char *name, umode_t mode, 486 - struct dentry *parent, void *data, 487 - const struct file_operations *fops) 453 + struct dentry *debugfs_create_file_full(const char *name, umode_t mode, 454 + struct dentry *parent, void *data, 455 + const struct file_operations *fops) 488 456 { 457 + if (WARN_ON((unsigned long)fops & 458 + (DEBUGFS_FSDATA_IS_SHORT_FOPS_BIT | 459 + DEBUGFS_FSDATA_IS_REAL_FOPS_BIT))) 460 + return ERR_PTR(-EINVAL); 489 461 490 462 return __debugfs_create_file(name, mode, parent, data, 491 463 fops ? &debugfs_full_proxy_file_operations : 492 464 &debugfs_noop_file_operations, 493 465 fops); 494 466 } 495 - EXPORT_SYMBOL_GPL(debugfs_create_file); 467 + EXPORT_SYMBOL_GPL(debugfs_create_file_full); 468 + 469 + struct dentry *debugfs_create_file_short(const char *name, umode_t mode, 470 + struct dentry *parent, void *data, 471 + const struct debugfs_short_fops *fops) 472 + { 473 + if (WARN_ON((unsigned long)fops & 474 + (DEBUGFS_FSDATA_IS_SHORT_FOPS_BIT | 475 + DEBUGFS_FSDATA_IS_REAL_FOPS_BIT))) 476 + return ERR_PTR(-EINVAL); 477 + 478 + return __debugfs_create_file(name, mode, parent, data, 479 + fops ? &debugfs_full_proxy_file_operations : 480 + &debugfs_noop_file_operations, 481 + (const void *)((unsigned long)fops | 482 + DEBUGFS_FSDATA_IS_SHORT_FOPS_BIT)); 483 + } 484 + EXPORT_SYMBOL_GPL(debugfs_create_file_short); 496 485 497 486 /** 498 487 * debugfs_create_file_unsafe - create a file in the debugfs filesystem
+6
fs/debugfs/internal.h
··· 18 18 19 19 struct debugfs_fsdata { 20 20 const struct file_operations *real_fops; 21 + const struct debugfs_short_fops *short_fops; 21 22 union { 22 23 /* automount_fn is used when real_fops is NULL */ 23 24 debugfs_automount_t automount; ··· 40 39 * pointer gets its lowest bit set. 41 40 */ 42 41 #define DEBUGFS_FSDATA_IS_REAL_FOPS_BIT BIT(0) 42 + /* 43 + * A dentry's ->d_fsdata, when pointing to real fops, is with 44 + * short fops instead of full fops. 45 + */ 46 + #define DEBUGFS_FSDATA_IS_SHORT_FOPS_BIT BIT(1) 43 47 44 48 /* Access BITS */ 45 49 #define DEBUGFS_ALLOW_API BIT(0)
+58 -4
include/linux/debugfs.h
··· 71 71 72 72 struct dentry *debugfs_lookup(const char *name, struct dentry *parent); 73 73 74 - struct dentry *debugfs_create_file(const char *name, umode_t mode, 75 - struct dentry *parent, void *data, 76 - const struct file_operations *fops); 74 + struct debugfs_short_fops { 75 + ssize_t (*read)(struct file *, char __user *, size_t, loff_t *); 76 + ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *); 77 + loff_t (*llseek) (struct file *, loff_t, int); 78 + }; 79 + 80 + struct dentry *debugfs_create_file_full(const char *name, umode_t mode, 81 + struct dentry *parent, void *data, 82 + const struct file_operations *fops); 83 + struct dentry *debugfs_create_file_short(const char *name, umode_t mode, 84 + struct dentry *parent, void *data, 85 + const struct debugfs_short_fops *fops); 86 + 87 + /** 88 + * debugfs_create_file - create a file in the debugfs filesystem 89 + * @name: a pointer to a string containing the name of the file to create. 90 + * @mode: the permission that the file should have. 91 + * @parent: a pointer to the parent dentry for this file. This should be a 92 + * directory dentry if set. If this parameter is NULL, then the 93 + * file will be created in the root of the debugfs filesystem. 94 + * @data: a pointer to something that the caller will want to get to later 95 + * on. The inode.i_private pointer will point to this value on 96 + * the open() call. 97 + * @fops: a pointer to a struct file_operations or struct debugfs_short_fops that 98 + * should be used for this file. 99 + * 100 + * This is the basic "create a file" function for debugfs. It allows for a 101 + * wide range of flexibility in creating a file, or a directory (if you want 102 + * to create a directory, the debugfs_create_dir() function is 103 + * recommended to be used instead.) 104 + * 105 + * This function will return a pointer to a dentry if it succeeds. This 106 + * pointer must be passed to the debugfs_remove() function when the file is 107 + * to be removed (no automatic cleanup happens if your module is unloaded, 108 + * you are responsible here.) If an error occurs, ERR_PTR(-ERROR) will be 109 + * returned. 110 + * 111 + * If debugfs is not enabled in the kernel, the value -%ENODEV will be 112 + * returned. 113 + * 114 + * If fops points to a struct debugfs_short_fops, then simple_open() will be 115 + * used for the open, and only read/write/llseek are supported and are proxied, 116 + * so no module reference or release are needed. 117 + * 118 + * NOTE: it's expected that most callers should _ignore_ the errors returned 119 + * by this function. Other debugfs functions handle the fact that the "dentry" 120 + * passed to them could be an error and they don't crash in that case. 121 + * Drivers should generally work fine even if debugfs fails to init anyway. 122 + */ 123 + #define debugfs_create_file(name, mode, parent, data, fops) \ 124 + _Generic(fops, \ 125 + const struct file_operations *: debugfs_create_file_full, \ 126 + const struct debugfs_short_fops *: debugfs_create_file_short, \ 127 + struct file_operations *: debugfs_create_file_full, \ 128 + struct debugfs_short_fops *: debugfs_create_file_short) \ 129 + (name, mode, parent, data, fops) 130 + 77 131 struct dentry *debugfs_create_file_unsafe(const char *name, umode_t mode, 78 132 struct dentry *parent, void *data, 79 133 const struct file_operations *fops); ··· 261 207 262 208 static inline struct dentry *debugfs_create_file(const char *name, umode_t mode, 263 209 struct dentry *parent, void *data, 264 - const struct file_operations *fops) 210 + const void *fops) 265 211 { 266 212 return ERR_PTR(-ENODEV); 267 213 }