From cffe56e6a818f2b2d50fc8564b6398f8308ae9e8 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 26 Sep 2026 12:38:36 +0800 Subject: vfs: add FILE_DEDUPE_RANGE_REPORT_PROGRESS flag to FIDEDUPERANGE Deduplication tools such as duperemove, bees and rmlint advance their file offsets by the bytes_deduped the kernel returns for each FIDEDUPERANGE call. vfs_dedupe_file_range() passes REMAP_FILE_CAN_SHORTEN, so generic_remap_checks() may round the length down to a block multiple, but the ioctl still reports the requested length in bytes_deduped. The caller cannot tell that the tail of its request was left alone: rmlint 2.10.3 on btrfs (4 KiB blocks) deduping a 100000-byte file against a 250000-byte file with the same prefix is told bytes_deduped=100000 with status SAME while only 98304 bytes were actually shared; its loop ends and it reports the pair fully deduplicated. duperemove and bees advance the same way, and jdupes advances by its own requested length without reading the field; all of them skip such a tail. Add FILE_DEDUPE_RANGE_REPORT_PROGRESS for file_dedupe_range.flags: with it, bytes_deduped is the length the filesystem actually deduplicated when status is FILE_DEDUPE_RANGE_SAME, and 0 on FILE_DEDUPE_RANGE_DIFFERS or error. Callers advance by it as today, but must treat 0 as "stop or subdivide", not retry unchanged. One cause of SAME with 0 is a sub-block request that does not end at both files' EOF, which the generic range preparation shortens to nothing. On DIFFERS there is no sound progress or mismatch offset to report, so 0 leaves subdividing to the caller, as rmlint already does. The default cannot change: commit 4a57a8400075 ("vf/remap: return the amount of bytes actually deduplicated") did exactly that and was reverted the next day; among deployed callers, duperemove re-queues a request while its status is 0 and would re-issue the same sub-block request forever. Without the flag nothing changes. Suggested-by: Darrick J. Wong Link: https://lore.kernel.org/linux-fsdevel/20260805071414.3414870-1-matthias.goergens@gmail.com/ Signed-off-by: Matthias Goergens Link: https://patch.msgid.link/20260926043836.3301898-3-matthias.goergens@gmail.com Reviewed-by: Amir Goldstein Reviewed-by: "Darrick J. Wong" Signed-off-by: Christian Brauner (Amutable) --- include/uapi/linux/fs.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index a46c33692aa2..a13c7b32a1bd 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -178,13 +178,22 @@ struct file_dedupe_range_info { __u32 reserved; /* must be zero */ }; +/* flags for struct file_dedupe_range */ +/* + * Without this flag, bytes_deduped is the requested length on success, + * even if the filesystem deduplicated fewer bytes (e.g. after shortening + * the request to a block boundary). With this flag, bytes_deduped is + * the number of bytes actually deduplicated. + */ +#define FILE_DEDUPE_RANGE_REPORT_PROGRESS (1U << 0) + /* from struct btrfs_ioctl_file_extent_same_args */ struct file_dedupe_range { __u64 src_offset; /* in - start of extent in source */ __u64 src_length; /* in - length of extent */ __u16 dest_count; /* in - total elements in info array */ __u16 reserved1; /* must be zero */ - __u32 reserved2; /* must be zero */ + __u32 flags; /* in - FILE_DEDUPE_RANGE_* flags */ struct file_dedupe_range_info info[]; }; -- cgit v1.2.3 From 6f635ec3f6eb5ff548d5359984bb1a4d06372360 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 29 Oct 2026 12:28:16 -0400 Subject: statx: add STATX_ATTR_MULTIGRAIN_TIMESTAMP Userland has no way to tell whether a file's timestamps are multigrain, i.e. whether successive changes after a query are guaranteed to yield distinct timestamps. Add STATX_ATTR_MULTIGRAIN_TIMESTAMP and set it in vfs_getattr_nosec() when is_mgtime() is true (IOP_MGTIME is set from FS_MGTIME at inode init). Always advertise it in attributes_mask. Suggested-by: Christoph Hellwig Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20261029-mgtime-statx-v1-1-175b5afdf2fb@kernel.org Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/stat.c | 6 +++++- include/uapi/linux/stat.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/fs/stat.c b/fs/stat.c index a2e63c6c45b7..67b9f1784b23 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -206,9 +206,13 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat, if (IS_VERITY(inode)) stat->attributes |= STATX_ATTR_VERITY; + if (is_mgtime(inode)) + stat->attributes |= STATX_ATTR_MULTIGRAIN_TIMESTAMP; + stat->attributes_mask |= (STATX_ATTR_AUTOMOUNT | STATX_ATTR_DAX | - STATX_ATTR_VERITY); + STATX_ATTR_VERITY | + STATX_ATTR_MULTIGRAIN_TIMESTAMP); idmap = mnt_idmap(path->mnt); if (inode->i_op->getattr) { diff --git a/include/uapi/linux/stat.h b/include/uapi/linux/stat.h index 1686861aae20..b978860590b1 100644 --- a/include/uapi/linux/stat.h +++ b/include/uapi/linux/stat.h @@ -255,6 +255,7 @@ struct statx { #define STATX_ATTR_VERITY 0x00100000 /* [I] Verity protected file */ #define STATX_ATTR_DAX 0x00200000 /* File is currently in DAX state */ #define STATX_ATTR_WRITE_ATOMIC 0x00400000 /* File supports atomic write operations */ +#define STATX_ATTR_MULTIGRAIN_TIMESTAMP 0x00800000 /* File uses multigrain timestamps */ #endif /* _UAPI_LINUX_STAT_H */ -- cgit v1.2.3 From a9deb044d54315498f3fbde1e14cd53eb7658722 Mon Sep 17 00:00:00 2001 From: Chen Linxuan Date: Fri, 6 Nov 2026 15:12:23 +0800 Subject: pidfd: Add task path ioctls Add PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT so callers holding a pidfd can obtain close-on-exec O_PATH file descriptors for the target's executable, working directory, and root without requiring procfs. Use the task acquisition helper shared with the namespace ioctls to check PID namespace visibility and ptrace access. Hold exec_update_lock across the access check and path acquisition to prevent a concurrent execve() from changing the target's credentials between them. Add task_path_open() to perform weak revalidation before opening the selected path. This gives the filesystem the opportunity to reject a path handed out directly by a task, as it can when following a procfs magic link. Pass flags zero because no pathname lookup is performed, propagate negative errors, and return ESTALE for a zero result. Release exec_update_lock before calling task_path_open() so filesystem revalidation runs outside the target's exec critical section. Suggested-by: Christian Brauner Link: https://lore.kernel.org/all/20261105-gefolge-rosten-neueinstellung-6223a18aae90@brauner/ Signed-off-by: Chen Linxuan Link: https://patch.msgid.link/20261106-pidfd-get-paths-v5-3-3147ce6571cb@black-desk.cn Signed-off-by: Christian Brauner (Amutable) --- fs/internal.h | 1 + fs/open.c | 30 ++++++++++++++++++++++++++++++ fs/pidfs.c | 37 +++++++++++++++++++++++++++++++++++++ include/uapi/linux/pidfd.h | 7 +++++++ 4 files changed, 75 insertions(+) (limited to 'include/uapi/linux') diff --git a/fs/internal.h b/fs/internal.h index 3f2fbac40f8e..e43ebcfd9846 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -205,6 +205,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, int chown_common(const struct path *path, uid_t user, gid_t group); extern int vfs_open(const struct path *, struct file *); int vfs_open_consume(struct path *, struct file *); +struct file *task_path_open(const struct path *path); /* * inode.c diff --git a/fs/open.c b/fs/open.c index e43f02ff64ac..55441ff0525c 100644 --- a/fs/open.c +++ b/fs/open.c @@ -1156,6 +1156,36 @@ struct file *dentry_open(const struct path *path, int flags, } EXPORT_SYMBOL(dentry_open); +/** + * task_path_open - open a path handed out by a task + * @path: the task's path + * + * Open a task's exe, cwd or root for the caller. The path wasn't found by + * a lookup, it's handed out directly, the same way a /proc//{exe,cwd,root} + * jump lands on it. So give the filesystem the chance to refuse it that + * complete_walk() gives it for a jumped dentry. + * + * Only O_PATH opens are supported. Anything else would need the permission + * checks a lookup performs before the open. + * + * Return: An O_PATH file on success, an error pointer on failure. + */ +struct file *task_path_open(const struct path *path) +{ + struct dentry *dentry = path->dentry; + + if (unlikely(dentry->d_flags & DCACHE_OP_WEAK_REVALIDATE)) { + int status = dentry->d_op->d_weak_revalidate(dentry, 0); + + if (status < 0) + return ERR_PTR(status); + if (!status) + return ERR_PTR(-ESTALE); + } + + return dentry_open(path, O_PATH, current_cred()); +} + struct file *dentry_open_nonotify(const struct path *path, int flags, const struct cred *cred) { diff --git a/fs/pidfs.c b/fs/pidfs.c index 354df6342f54..a2a0691cf131 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -544,6 +545,9 @@ static bool pidfs_ioctl_valid(unsigned int cmd) case PIDFD_GET_UTS_NAMESPACE: case PIDFD_GET_USER_NAMESPACE: case PIDFD_GET_PID_NAMESPACE: + case PIDFD_GET_EXE: + case PIDFD_GET_CWD: + case PIDFD_GET_ROOT: return true; } @@ -721,9 +725,31 @@ static struct ns_common *pidfd_get_namespace(struct pid *pid, return ns_common; } +static int pidfd_get_task_path(struct pid *pid, unsigned int cmd, + unsigned long arg, struct path *path) +{ + CLASS(pidfd_task_locked, task)(pid, arg); + + if (IS_ERR(task)) + return PTR_ERR(task); + + switch (cmd) { + case PIDFD_GET_EXE: + return get_task_exe_path(task, path); + case PIDFD_GET_CWD: + return get_task_pwd(task, path); + case PIDFD_GET_ROOT: + return get_task_root(task, path); + } + + return -EINVAL; +} + static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ns_common *ns_common = NULL; + struct path path __free(path_put) = {}; + int error; if (!pidfs_ioctl_valid(cmd)) return -ENOIOCTLCMD; @@ -741,6 +767,17 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO)) return pidfd_info(file, cmd, arg); + switch (cmd) { + case PIDFD_GET_EXE: + case PIDFD_GET_CWD: + case PIDFD_GET_ROOT: + error = pidfd_get_task_path(pidfd_pid(file), cmd, arg, &path); + if (error) + return error; + + return FD_ADD(O_CLOEXEC, task_path_open(&path)); + } + ns_common = pidfd_get_namespace(pidfd_pid(file), cmd, arg); if (IS_ERR(ns_common)) return PTR_ERR(ns_common); diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h index 0919246a1611..95ce1819f423 100644 --- a/include/uapi/linux/pidfd.h +++ b/include/uapi/linux/pidfd.h @@ -121,4 +121,11 @@ struct pidfd_info { #define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10) #define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info) +/* Return an O_PATH file descriptor for the target task's executable. */ +#define PIDFD_GET_EXE _IO(PIDFS_IOCTL_MAGIC, 12) +/* Return an O_PATH file descriptor for the target task's working directory. */ +#define PIDFD_GET_CWD _IO(PIDFS_IOCTL_MAGIC, 13) +/* Return an O_PATH file descriptor for the target task's root directory. */ +#define PIDFD_GET_ROOT _IO(PIDFS_IOCTL_MAGIC, 14) + #endif /* _UAPI_LINUX_PIDFD_H */ -- cgit v1.2.3