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

autofs: add per dentry expire timeout

Add ability to set per-dentry mount expire timeout to autofs.

There are two fairly well known automounter map formats, the autofs
format and the amd format (more or less System V and Berkley).

Some time ago Linux autofs added an amd map format parser that
implemented a fair amount of the amd functionality. This was done
within the autofs infrastructure and some functionality wasn't
implemented because it either didn't make sense or required extra
kernel changes. The idea was to restrict changes to be within the
existing autofs functionality as much as possible and leave changes
with a wider scope to be considered later.

One of these changes is implementing the amd options:
1) "unmount", expire this mount according to a timeout (same as the
current autofs default).
2) "nounmount", don't expire this mount (same as setting the autofs
timeout to 0 except only for this specific mount) .
3) "utimeout=<seconds>", expire this mount using the specified
timeout (again same as setting the autofs timeout but only for
this mount).

To implement these options per-dentry expire timeouts need to be
implemented for autofs indirect mounts. This is because all map keys
(mounts) for autofs indirect mounts use an expire timeout stored in
the autofs mount super block info. structure and all indirect mounts
use the same expire timeout.

Now I have a request to add the "nounmount" option so I need to add
the per-dentry expire handling to the kernel implementation to do this.

The implementation uses the trailing path component to identify the
mount (and is also used as the autofs map key) which is passed in the
autofs_dev_ioctl structure path field. The expire timeout is passed
in autofs_dev_ioctl timeout field (well, of the timeout union).

If the passed in timeout is equal to -1 the per-dentry timeout and
flag are cleared providing for the "unmount" option. If the timeout
is greater than or equal to 0 the timeout is set to the value and the
flag is also set. If the dentry timeout is 0 the dentry will not expire
by timeout which enables the implementation of the "nounmount" option
for the specific mount. When the dentry timeout is greater than zero it
allows for the implementation of the "utimeout=<seconds>" option.

Signed-off-by: Ian Kent <raven@themaw.net>
Link: https://lore.kernel.org/r/20240814090231.963520-1-raven@themaw.net
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

Ian Kent and committed by
Christian Brauner
433f9d76 122381a4

+104 -8
+4
fs/autofs/autofs_i.h
··· 62 62 struct list_head expiring; 63 63 64 64 struct autofs_sb_info *sbi; 65 + unsigned long exp_timeout; 65 66 unsigned long last_used; 66 67 int count; 67 68 ··· 82 81 */ 83 82 #define AUTOFS_INF_PENDING (1<<2) /* dentry pending mount */ 84 83 84 + #define AUTOFS_INF_EXPIRE_SET (1<<3) /* per-dentry expire timeout set for 85 + this mount point. 86 + */ 85 87 struct autofs_wait_queue { 86 88 wait_queue_head_t queue; 87 89 struct autofs_wait_queue *next;
+92 -5
fs/autofs/dev-ioctl.c
··· 128 128 goto out; 129 129 } 130 130 131 + /* Setting the per-dentry expire timeout requires a trailing 132 + * path component, ie. no '/', so invert the logic of the 133 + * check_name() return for AUTOFS_DEV_IOCTL_TIMEOUT_CMD. 134 + */ 131 135 err = check_name(param->path); 136 + if (cmd == AUTOFS_DEV_IOCTL_TIMEOUT_CMD) 137 + err = err ? 0 : -EINVAL; 132 138 if (err) { 133 139 pr_warn("invalid path supplied for cmd(0x%08x)\n", 134 140 cmd); ··· 402 396 return 0; 403 397 } 404 398 405 - /* Set the autofs mount timeout */ 399 + /* 400 + * Set the autofs mount expire timeout. 401 + * 402 + * There are two places an expire timeout can be set, in the autofs 403 + * super block info. (this is all that's needed for direct and offset 404 + * mounts because there's a distinct mount corresponding to each of 405 + * these) and per-dentry within within the dentry info. If a per-dentry 406 + * timeout is set it will override the expire timeout set in the parent 407 + * autofs super block info. 408 + * 409 + * If setting the autofs super block expire timeout the autofs_dev_ioctl 410 + * size field will be equal to the autofs_dev_ioctl structure size. If 411 + * setting the per-dentry expire timeout the mount point name is passed 412 + * in the autofs_dev_ioctl path field and the size field updated to 413 + * reflect this. 414 + * 415 + * Setting the autofs mount expire timeout sets the timeout in the super 416 + * block info. struct. Setting the per-dentry timeout does a little more. 417 + * If the timeout is equal to -1 the per-dentry timeout (and flag) is 418 + * cleared which reverts to using the super block timeout, otherwise if 419 + * timeout is 0 the timeout is set to this value and the flag is left 420 + * set which disables expiration for the mount point, lastly the flag 421 + * and the timeout are set enabling the dentry to use this timeout. 422 + */ 406 423 static int autofs_dev_ioctl_timeout(struct file *fp, 407 424 struct autofs_sb_info *sbi, 408 425 struct autofs_dev_ioctl *param) 409 426 { 410 - unsigned long timeout; 427 + unsigned long timeout = param->timeout.timeout; 411 428 412 - timeout = param->timeout.timeout; 413 - param->timeout.timeout = sbi->exp_timeout / HZ; 414 - sbi->exp_timeout = timeout * HZ; 429 + /* If setting the expire timeout for an individual indirect 430 + * mount point dentry the mount trailing component path is 431 + * placed in param->path and param->size adjusted to account 432 + * for it otherwise param->size it is set to the structure 433 + * size. 434 + */ 435 + if (param->size == AUTOFS_DEV_IOCTL_SIZE) { 436 + param->timeout.timeout = sbi->exp_timeout / HZ; 437 + sbi->exp_timeout = timeout * HZ; 438 + } else { 439 + struct dentry *base = fp->f_path.dentry; 440 + struct inode *inode = base->d_inode; 441 + int path_len = param->size - AUTOFS_DEV_IOCTL_SIZE - 1; 442 + struct dentry *dentry; 443 + struct autofs_info *ino; 444 + 445 + if (!autofs_type_indirect(sbi->type)) 446 + return -EINVAL; 447 + 448 + /* An expire timeout greater than the superblock timeout 449 + * could be a problem at shutdown but the super block 450 + * timeout itself can change so all we can really do is 451 + * warn the user. 452 + */ 453 + if (timeout >= sbi->exp_timeout) 454 + pr_warn("per-mount expire timeout is greater than " 455 + "the parent autofs mount timeout which could " 456 + "prevent shutdown\n"); 457 + 458 + inode_lock_shared(inode); 459 + dentry = try_lookup_one_len(param->path, base, path_len); 460 + inode_unlock_shared(inode); 461 + if (IS_ERR_OR_NULL(dentry)) 462 + return dentry ? PTR_ERR(dentry) : -ENOENT; 463 + ino = autofs_dentry_ino(dentry); 464 + if (!ino) { 465 + dput(dentry); 466 + return -ENOENT; 467 + } 468 + 469 + if (ino->exp_timeout && ino->flags & AUTOFS_INF_EXPIRE_SET) 470 + param->timeout.timeout = ino->exp_timeout / HZ; 471 + else 472 + param->timeout.timeout = sbi->exp_timeout / HZ; 473 + 474 + if (timeout == -1) { 475 + /* Revert to using the super block timeout */ 476 + ino->flags &= ~AUTOFS_INF_EXPIRE_SET; 477 + ino->exp_timeout = 0; 478 + } else { 479 + /* Set the dentry expire flag and timeout. 480 + * 481 + * If timeout is 0 it will prevent the expire 482 + * of this particular automount. 483 + */ 484 + ino->flags |= AUTOFS_INF_EXPIRE_SET; 485 + ino->exp_timeout = timeout * HZ; 486 + } 487 + dput(dentry); 488 + } 489 + 415 490 return 0; 416 491 } 417 492
+5 -2
fs/autofs/expire.c
··· 429 429 if (!root) 430 430 return NULL; 431 431 432 - timeout = sbi->exp_timeout; 433 - 434 432 dentry = NULL; 435 433 while ((dentry = get_next_positive_subdir(dentry, root))) { 436 434 spin_lock(&sbi->fs_lock); ··· 438 440 continue; 439 441 } 440 442 spin_unlock(&sbi->fs_lock); 443 + 444 + if (ino->flags & AUTOFS_INF_EXPIRE_SET) 445 + timeout = ino->exp_timeout; 446 + else 447 + timeout = sbi->exp_timeout; 441 448 442 449 expired = should_expire(dentry, mnt, timeout, how); 443 450 if (!expired)
+2
fs/autofs/inode.c
··· 19 19 INIT_LIST_HEAD(&ino->expiring); 20 20 ino->last_used = jiffies; 21 21 ino->sbi = sbi; 22 + ino->exp_timeout = -1; 22 23 ino->count = 1; 23 24 } 24 25 return ino; ··· 29 28 { 30 29 ino->uid = GLOBAL_ROOT_UID; 31 30 ino->gid = GLOBAL_ROOT_GID; 31 + ino->exp_timeout = -1; 32 32 ino->last_used = jiffies; 33 33 } 34 34
+1 -1
include/uapi/linux/auto_fs.h
··· 23 23 #define AUTOFS_MIN_PROTO_VERSION 3 24 24 #define AUTOFS_MAX_PROTO_VERSION 5 25 25 26 - #define AUTOFS_PROTO_SUBVERSION 5 26 + #define AUTOFS_PROTO_SUBVERSION 6 27 27 28 28 /* 29 29 * The wait_queue_token (autofs_wqt_t) is part of a structure which is passed