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

fs: introduce file_getattr and file_setattr syscalls

Introduce file_getattr() and file_setattr() syscalls to manipulate inode
extended attributes. The syscalls takes pair of file descriptor and
pathname. Then it operates on inode opened accroding to openat()
semantics. The struct file_attr is passed to obtain/change extended
attributes.

This is an alternative to FS_IOC_FSSETXATTR ioctl with a difference
that file don't need to be open as we can reference it with a path
instead of fd. By having this we can manipulated inode extended
attributes not only on regular files but also on special ones. This
is not possible with FS_IOC_FSSETXATTR ioctl as with special files
we can not call ioctl() directly on the filesystem inode using fd.

This patch adds two new syscalls which allows userspace to get/set
extended inode attributes on special files by using parent directory
and a path - *at() like syscall.

CC: linux-api@vger.kernel.org
CC: linux-fsdevel@vger.kernel.org
CC: linux-xfs@vger.kernel.org
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Link: https://lore.kernel.org/20250630-xattrat-syscall-v6-6-c4e3bc35227b@kernel.org
Acked-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

Andrey Albershteyn and committed by
Christian Brauner
be7efb2d 276e136b

+218 -1
+2
arch/alpha/kernel/syscalls/syscall.tbl
··· 507 507 575 common listxattrat sys_listxattrat 508 508 576 common removexattrat sys_removexattrat 509 509 577 common open_tree_attr sys_open_tree_attr 510 + 578 common file_getattr sys_file_getattr 511 + 579 common file_setattr sys_file_setattr
+2
arch/arm/tools/syscall.tbl
··· 482 482 465 common listxattrat sys_listxattrat 483 483 466 common removexattrat sys_removexattrat 484 484 467 common open_tree_attr sys_open_tree_attr 485 + 468 common file_getattr sys_file_getattr 486 + 469 common file_setattr sys_file_setattr
+2
arch/arm64/tools/syscall_32.tbl
··· 479 479 465 common listxattrat sys_listxattrat 480 480 466 common removexattrat sys_removexattrat 481 481 467 common open_tree_attr sys_open_tree_attr 482 + 468 common file_getattr sys_file_getattr 483 + 469 common file_setattr sys_file_setattr
+2
arch/m68k/kernel/syscalls/syscall.tbl
··· 467 467 465 common listxattrat sys_listxattrat 468 468 466 common removexattrat sys_removexattrat 469 469 467 common open_tree_attr sys_open_tree_attr 470 + 468 common file_getattr sys_file_getattr 471 + 469 common file_setattr sys_file_setattr
+2
arch/microblaze/kernel/syscalls/syscall.tbl
··· 473 473 465 common listxattrat sys_listxattrat 474 474 466 common removexattrat sys_removexattrat 475 475 467 common open_tree_attr sys_open_tree_attr 476 + 468 common file_getattr sys_file_getattr 477 + 469 common file_setattr sys_file_setattr
+2
arch/mips/kernel/syscalls/syscall_n32.tbl
··· 406 406 465 n32 listxattrat sys_listxattrat 407 407 466 n32 removexattrat sys_removexattrat 408 408 467 n32 open_tree_attr sys_open_tree_attr 409 + 468 n32 file_getattr sys_file_getattr 410 + 469 n32 file_setattr sys_file_setattr
+2
arch/mips/kernel/syscalls/syscall_n64.tbl
··· 382 382 465 n64 listxattrat sys_listxattrat 383 383 466 n64 removexattrat sys_removexattrat 384 384 467 n64 open_tree_attr sys_open_tree_attr 385 + 468 n64 file_getattr sys_file_getattr 386 + 469 n64 file_setattr sys_file_setattr
+2
arch/mips/kernel/syscalls/syscall_o32.tbl
··· 455 455 465 o32 listxattrat sys_listxattrat 456 456 466 o32 removexattrat sys_removexattrat 457 457 467 o32 open_tree_attr sys_open_tree_attr 458 + 468 o32 file_getattr sys_file_getattr 459 + 469 o32 file_setattr sys_file_setattr
+2
arch/parisc/kernel/syscalls/syscall.tbl
··· 466 466 465 common listxattrat sys_listxattrat 467 467 466 common removexattrat sys_removexattrat 468 468 467 common open_tree_attr sys_open_tree_attr 469 + 468 common file_getattr sys_file_getattr 470 + 469 common file_setattr sys_file_setattr
+2
arch/powerpc/kernel/syscalls/syscall.tbl
··· 558 558 465 common listxattrat sys_listxattrat 559 559 466 common removexattrat sys_removexattrat 560 560 467 common open_tree_attr sys_open_tree_attr 561 + 468 common file_getattr sys_file_getattr 562 + 469 common file_setattr sys_file_setattr
+2
arch/s390/kernel/syscalls/syscall.tbl
··· 470 470 465 common listxattrat sys_listxattrat sys_listxattrat 471 471 466 common removexattrat sys_removexattrat sys_removexattrat 472 472 467 common open_tree_attr sys_open_tree_attr sys_open_tree_attr 473 + 468 common file_getattr sys_file_getattr sys_file_getattr 474 + 469 common file_setattr sys_file_setattr sys_file_setattr
+2
arch/sh/kernel/syscalls/syscall.tbl
··· 471 471 465 common listxattrat sys_listxattrat 472 472 466 common removexattrat sys_removexattrat 473 473 467 common open_tree_attr sys_open_tree_attr 474 + 468 common file_getattr sys_file_getattr 475 + 469 common file_setattr sys_file_setattr
+2
arch/sparc/kernel/syscalls/syscall.tbl
··· 513 513 465 common listxattrat sys_listxattrat 514 514 466 common removexattrat sys_removexattrat 515 515 467 common open_tree_attr sys_open_tree_attr 516 + 468 common file_getattr sys_file_getattr 517 + 469 common file_setattr sys_file_setattr
+2
arch/x86/entry/syscalls/syscall_32.tbl
··· 473 473 465 i386 listxattrat sys_listxattrat 474 474 466 i386 removexattrat sys_removexattrat 475 475 467 i386 open_tree_attr sys_open_tree_attr 476 + 468 i386 file_getattr sys_file_getattr 477 + 469 i386 file_setattr sys_file_setattr
+2
arch/x86/entry/syscalls/syscall_64.tbl
··· 391 391 465 common listxattrat sys_listxattrat 392 392 466 common removexattrat sys_removexattrat 393 393 467 common open_tree_attr sys_open_tree_attr 394 + 468 common file_getattr sys_file_getattr 395 + 469 common file_setattr sys_file_setattr 394 396 395 397 # 396 398 # Due to a historical design error, certain syscalls are numbered differently
+2
arch/xtensa/kernel/syscalls/syscall.tbl
··· 438 438 465 common listxattrat sys_listxattrat 439 439 466 common removexattrat sys_removexattrat 440 440 467 common open_tree_attr sys_open_tree_attr 441 + 468 common file_getattr sys_file_getattr 442 + 469 common file_setattr sys_file_setattr
+152
fs/file_attr.c
··· 4 4 #include <linux/fscrypt.h> 5 5 #include <linux/fileattr.h> 6 6 #include <linux/export.h> 7 + #include <linux/syscalls.h> 8 + #include <linux/namei.h> 9 + 10 + #include "internal.h" 7 11 8 12 /** 9 13 * fileattr_fill_xflags - initialize fileattr with xflags ··· 94 90 } 95 91 EXPORT_SYMBOL(vfs_fileattr_get); 96 92 93 + static void fileattr_to_file_attr(const struct fileattr *fa, 94 + struct file_attr *fattr) 95 + { 96 + __u32 mask = FS_XFLAGS_MASK; 97 + 98 + memset(fattr, 0, sizeof(struct file_attr)); 99 + fattr->fa_xflags = fa->fsx_xflags & mask; 100 + fattr->fa_extsize = fa->fsx_extsize; 101 + fattr->fa_nextents = fa->fsx_nextents; 102 + fattr->fa_projid = fa->fsx_projid; 103 + fattr->fa_cowextsize = fa->fsx_cowextsize; 104 + } 105 + 97 106 /** 98 107 * copy_fsxattr_to_user - copy fsxattr to userspace. 99 108 * @fa: fileattr pointer ··· 132 115 return 0; 133 116 } 134 117 EXPORT_SYMBOL(copy_fsxattr_to_user); 118 + 119 + static int file_attr_to_fileattr(const struct file_attr *fattr, 120 + struct fileattr *fa) 121 + { 122 + __u32 mask = FS_XFLAGS_MASK; 123 + 124 + if (fattr->fa_xflags & ~mask) 125 + return -EINVAL; 126 + 127 + fileattr_fill_xflags(fa, fattr->fa_xflags); 128 + fa->fsx_xflags &= ~FS_XFLAG_RDONLY_MASK; 129 + fa->fsx_extsize = fattr->fa_extsize; 130 + fa->fsx_projid = fattr->fa_projid; 131 + fa->fsx_cowextsize = fattr->fa_cowextsize; 132 + 133 + return 0; 134 + } 135 135 136 136 static int copy_fsxattr_from_user(struct fileattr *fa, 137 137 struct fsxattr __user *ufa) ··· 378 344 return err; 379 345 } 380 346 EXPORT_SYMBOL(ioctl_fssetxattr); 347 + 348 + SYSCALL_DEFINE5(file_getattr, int, dfd, const char __user *, filename, 349 + struct file_attr __user *, ufattr, size_t, usize, 350 + unsigned int, at_flags) 351 + { 352 + struct path filepath __free(path_put) = {}; 353 + struct filename *name __free(putname) = NULL; 354 + unsigned int lookup_flags = 0; 355 + struct file_attr fattr; 356 + struct fileattr fa; 357 + int error; 358 + 359 + BUILD_BUG_ON(sizeof(struct file_attr) < FILE_ATTR_SIZE_VER0); 360 + BUILD_BUG_ON(sizeof(struct file_attr) != FILE_ATTR_SIZE_LATEST); 361 + 362 + if ((at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0) 363 + return -EINVAL; 364 + 365 + if (!(at_flags & AT_SYMLINK_NOFOLLOW)) 366 + lookup_flags |= LOOKUP_FOLLOW; 367 + 368 + if (usize > PAGE_SIZE) 369 + return -E2BIG; 370 + 371 + if (usize < FILE_ATTR_SIZE_VER0) 372 + return -EINVAL; 373 + 374 + name = getname_maybe_null(filename, at_flags); 375 + if (IS_ERR(name)) 376 + return PTR_ERR(name); 377 + 378 + if (!name && dfd >= 0) { 379 + CLASS(fd, f)(dfd); 380 + if (fd_empty(f)) 381 + return -EBADF; 382 + 383 + filepath = fd_file(f)->f_path; 384 + path_get(&filepath); 385 + } else { 386 + error = filename_lookup(dfd, name, lookup_flags, &filepath, 387 + NULL); 388 + if (error) 389 + return error; 390 + } 391 + 392 + error = vfs_fileattr_get(filepath.dentry, &fa); 393 + if (error) 394 + return error; 395 + 396 + fileattr_to_file_attr(&fa, &fattr); 397 + error = copy_struct_to_user(ufattr, usize, &fattr, 398 + sizeof(struct file_attr), NULL); 399 + 400 + return error; 401 + } 402 + 403 + SYSCALL_DEFINE5(file_setattr, int, dfd, const char __user *, filename, 404 + struct file_attr __user *, ufattr, size_t, usize, 405 + unsigned int, at_flags) 406 + { 407 + struct path filepath __free(path_put) = {}; 408 + struct filename *name __free(putname) = NULL; 409 + unsigned int lookup_flags = 0; 410 + struct file_attr fattr; 411 + struct fileattr fa; 412 + int error; 413 + 414 + BUILD_BUG_ON(sizeof(struct file_attr) < FILE_ATTR_SIZE_VER0); 415 + BUILD_BUG_ON(sizeof(struct file_attr) != FILE_ATTR_SIZE_LATEST); 416 + 417 + if ((at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0) 418 + return -EINVAL; 419 + 420 + if (!(at_flags & AT_SYMLINK_NOFOLLOW)) 421 + lookup_flags |= LOOKUP_FOLLOW; 422 + 423 + if (usize > PAGE_SIZE) 424 + return -E2BIG; 425 + 426 + if (usize < FILE_ATTR_SIZE_VER0) 427 + return -EINVAL; 428 + 429 + error = copy_struct_from_user(&fattr, sizeof(struct file_attr), ufattr, 430 + usize); 431 + if (error) 432 + return error; 433 + 434 + error = file_attr_to_fileattr(&fattr, &fa); 435 + if (error) 436 + return error; 437 + 438 + name = getname_maybe_null(filename, at_flags); 439 + if (IS_ERR(name)) 440 + return PTR_ERR(name); 441 + 442 + if (!name && dfd >= 0) { 443 + CLASS(fd, f)(dfd); 444 + if (fd_empty(f)) 445 + return -EBADF; 446 + 447 + filepath = fd_file(f)->f_path; 448 + path_get(&filepath); 449 + } else { 450 + error = filename_lookup(dfd, name, lookup_flags, &filepath, 451 + NULL); 452 + if (error) 453 + return error; 454 + } 455 + 456 + error = mnt_want_write(filepath.mnt); 457 + if (!error) { 458 + error = vfs_fileattr_set(mnt_idmap(filepath.mnt), 459 + filepath.dentry, &fa); 460 + mnt_drop_write(filepath.mnt); 461 + } 462 + 463 + return error; 464 + }
+7
include/linux/syscalls.h
··· 78 78 struct statmount; 79 79 struct mnt_id_req; 80 80 struct xattr_args; 81 + struct file_attr; 81 82 82 83 #include <linux/types.h> 83 84 #include <linux/aio_abi.h> ··· 372 371 asmlinkage long sys_lremovexattr(const char __user *path, 373 372 const char __user *name); 374 373 asmlinkage long sys_fremovexattr(int fd, const char __user *name); 374 + asmlinkage long sys_file_getattr(int dfd, const char __user *filename, 375 + struct file_attr __user *attr, size_t usize, 376 + unsigned int at_flags); 377 + asmlinkage long sys_file_setattr(int dfd, const char __user *filename, 378 + struct file_attr __user *attr, size_t usize, 379 + unsigned int at_flags); 375 380 asmlinkage long sys_getcwd(char __user *buf, unsigned long size); 376 381 asmlinkage long sys_eventfd2(unsigned int count, int flags); 377 382 asmlinkage long sys_epoll_create1(int flags);
+7 -1
include/uapi/asm-generic/unistd.h
··· 852 852 #define __NR_open_tree_attr 467 853 853 __SYSCALL(__NR_open_tree_attr, sys_open_tree_attr) 854 854 855 + /* fs/inode.c */ 856 + #define __NR_file_getattr 468 857 + __SYSCALL(__NR_file_getattr, sys_file_getattr) 858 + #define __NR_file_setattr 469 859 + __SYSCALL(__NR_file_setattr, sys_file_setattr) 860 + 855 861 #undef __NR_syscalls 856 - #define __NR_syscalls 468 862 + #define __NR_syscalls 470 857 863 858 864 /* 859 865 * 32 bit systems traditionally used different
+18
include/uapi/linux/fs.h
··· 149 149 }; 150 150 151 151 /* 152 + * Variable size structure for file_[sg]et_attr(). 153 + * 154 + * Note. This is alternative to the structure 'struct fileattr'/'struct fsxattr'. 155 + * As this structure is passed to/from userspace with its size, this can 156 + * be versioned based on the size. 157 + */ 158 + struct file_attr { 159 + __u64 fa_xflags; /* xflags field value (get/set) */ 160 + __u32 fa_extsize; /* extsize field value (get/set)*/ 161 + __u32 fa_nextents; /* nextents field value (get) */ 162 + __u32 fa_projid; /* project identifier (get/set) */ 163 + __u32 fa_cowextsize; /* CoW extsize field value (get/set) */ 164 + }; 165 + 166 + #define FILE_ATTR_SIZE_VER0 24 167 + #define FILE_ATTR_SIZE_LATEST FILE_ATTR_SIZE_VER0 168 + 169 + /* 152 170 * Flags for the fsx_xflags field 153 171 */ 154 172 #define FS_XFLAG_REALTIME 0x00000001 /* data in realtime volume */
+2
scripts/syscall.tbl
··· 408 408 465 common listxattrat sys_listxattrat 409 409 466 common removexattrat sys_removexattrat 410 410 467 common open_tree_attr sys_open_tree_attr 411 + 468 common file_getattr sys_file_getattr 412 + 469 common file_setattr sys_file_setattr