From a47eda413c659ab3f90c00e661025b3d63e7969b Mon Sep 17 00:00:00 2001 From: Shaowei Song Date: Fri, 28 Mar 2025 05:58:37 +0000 Subject: [PATCH] Add extended attribute (xattr) syscalls: setxattr, getxattr, listxattr and removexattr --- docs/src/kernel/linux-compatibility.md | 26 ++-- kernel/src/fs/path/dentry.rs | 29 +++- kernel/src/fs/utils/inode.rs | 26 +++- kernel/src/fs/utils/mod.rs | 5 + kernel/src/fs/utils/xattr.rs | 93 ++++++++++++ kernel/src/syscall/arch/x86.rs | 16 ++ kernel/src/syscall/getxattr.rs | 103 +++++++++++++ kernel/src/syscall/listxattr.rs | 107 +++++++++++++ kernel/src/syscall/mod.rs | 4 + kernel/src/syscall/removexattr.rs | 57 +++++++ kernel/src/syscall/setxattr.rs | 198 +++++++++++++++++++++++++ 11 files changed, 649 insertions(+), 15 deletions(-) create mode 100644 kernel/src/fs/utils/xattr.rs create mode 100644 kernel/src/syscall/getxattr.rs create mode 100644 kernel/src/syscall/listxattr.rs create mode 100644 kernel/src/syscall/removexattr.rs create mode 100644 kernel/src/syscall/setxattr.rs diff --git a/docs/src/kernel/linux-compatibility.md b/docs/src/kernel/linux-compatibility.md index cb2cccfd..7c355560 100644 --- a/docs/src/kernel/linux-compatibility.md +++ b/docs/src/kernel/linux-compatibility.md @@ -15,7 +15,7 @@ support the loading of Linux kernel modules. ## System Calls At the time of writing, -Asterinas implements 190 out of the 336 system calls +Asterinas implements 204 out of the 336 system calls provided by Linux on x86-64 architecture. | Numbers | Names | Is Implemented | @@ -208,18 +208,18 @@ provided by Linux on x86-64 architecture. | 185 | security | ❌ | | 186 | gettid | ✅ | | 187 | readahead | ❌ | -| 188 | setxattr | ❌ | -| 189 | lsetxattr | ❌ | -| 190 | fsetxattr | ❌ | -| 191 | getxattr | ❌ | -| 192 | lgetxattr | ❌ | -| 193 | fgetxattr | ❌ | -| 194 | listxattr | ❌ | -| 195 | llistxattr | ❌ | -| 196 | flistxattr | ❌ | -| 197 | removexattr | ❌ | -| 198 | lremovexattr | ❌ | -| 199 | fremovexattr | ❌ | +| 188 | setxattr | ✅ | +| 189 | lsetxattr | ✅ | +| 190 | fsetxattr | ✅ | +| 191 | getxattr | ✅ | +| 192 | lgetxattr | ✅ | +| 193 | fgetxattr | ✅ | +| 194 | listxattr | ✅ | +| 195 | llistxattr | ✅ | +| 196 | flistxattr | ✅ | +| 197 | removexattr | ✅ | +| 198 | lremovexattr | ✅ | +| 199 | fremovexattr | ✅ | | 200 | tkill | ❌ | | 201 | time | ✅ | | 202 | futex | ✅ | diff --git a/kernel/src/fs/path/dentry.rs b/kernel/src/fs/path/dentry.rs index 17193aa2..a41889a8 100644 --- a/kernel/src/fs/path/dentry.rs +++ b/kernel/src/fs/path/dentry.rs @@ -17,7 +17,8 @@ use crate::{ fs::{ path::mount::MountNode, utils::{ - FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, Permission, NAME_MAX, + FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, Permission, XattrName, + XattrNamespace, XattrSetFlags, NAME_MAX, }, }, prelude::*, @@ -356,6 +357,19 @@ impl Dentry_ { pub fn ctime(&self) -> Duration; pub fn set_ctime(&self, time: Duration); pub fn is_dentry_cacheable(&self) -> bool; + pub fn set_xattr( + &self, + name: XattrName, + value_reader: &mut VmReader, + flags: XattrSetFlags, + ) -> Result<()>; + pub fn get_xattr(&self, name: XattrName, value_writer: &mut VmWriter) -> Result; + pub fn list_xattr( + &self, + namespace: XattrNamespace, + list_writer: &mut VmWriter, + ) -> Result; + pub fn remove_xattr(&self, name: XattrName) -> Result<()>; } impl Debug for Dentry_ { @@ -769,4 +783,17 @@ impl Dentry { pub fn inode(&self) -> &Arc; pub fn is_root_of_mount(&self) -> bool; pub fn is_mountpoint(&self) -> bool; + pub fn set_xattr( + &self, + name: XattrName, + value_reader: &mut VmReader, + flags: XattrSetFlags, + ) -> Result<()>; + pub fn get_xattr(&self, name: XattrName, value_writer: &mut VmWriter) -> Result; + pub fn list_xattr( + &self, + namespace: XattrNamespace, + list_writer: &mut VmWriter, + ) -> Result; + pub fn remove_xattr(&self, name: XattrName) -> Result<()>; } diff --git a/kernel/src/fs/utils/inode.rs b/kernel/src/fs/utils/inode.rs index f68d84f0..f631346f 100644 --- a/kernel/src/fs/utils/inode.rs +++ b/kernel/src/fs/utils/inode.rs @@ -8,7 +8,10 @@ use aster_rights::Full; use core2::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult, Write}; use ostd::task::Task; -use super::{AccessMode, DirentVisitor, FallocMode, FileSystem, IoctlCmd}; +use super::{ + AccessMode, DirentVisitor, FallocMode, FileSystem, IoctlCmd, XattrName, XattrNamespace, + XattrSetFlags, +}; use crate::{ events::IoEvents, fs::device::{Device, DeviceType}, @@ -497,6 +500,27 @@ pub trait Inode: Any + Sync + Send { None } + fn set_xattr( + &self, + name: XattrName, + value_reader: &mut VmReader, + flags: XattrSetFlags, + ) -> Result<()> { + Err(Error::new(Errno::EOPNOTSUPP)) + } + + fn get_xattr(&self, name: XattrName, value_writer: &mut VmWriter) -> Result { + Err(Error::new(Errno::EOPNOTSUPP)) + } + + fn list_xattr(&self, namespace: XattrNamespace, list_writer: &mut VmWriter) -> Result { + Err(Error::new(Errno::EOPNOTSUPP)) + } + + fn remove_xattr(&self, name: XattrName) -> Result<()> { + Err(Error::new(Errno::EOPNOTSUPP)) + } + /// Used to check for read/write/execute permissions on a file. /// /// Similar to Linux, using "fsuid" here allows setting filesystem permissions diff --git a/kernel/src/fs/utils/mod.rs b/kernel/src/fs/utils/mod.rs index 05863963..da3d916f 100644 --- a/kernel/src/fs/utils/mod.rs +++ b/kernel/src/fs/utils/mod.rs @@ -19,6 +19,10 @@ pub use range_lock::{ FileRange, RangeLockItem, RangeLockItemBuilder, RangeLockList, RangeLockType, OFFSET_MAX, }; pub use status_flags::StatusFlags; +pub use xattr::{ + XattrName, XattrNamespace, XattrSetFlags, XATTR_LIST_MAX_LEN, XATTR_NAME_MAX_LEN, + XATTR_VALUE_MAX_LEN, +}; mod access_mode; mod channel; @@ -35,6 +39,7 @@ mod page_cache; mod random_test; mod range_lock; mod status_flags; +mod xattr; use core::{ borrow::Borrow, diff --git a/kernel/src/fs/utils/xattr.rs b/kernel/src/fs/utils/xattr.rs new file mode 100644 index 00000000..d0ef5d5d --- /dev/null +++ b/kernel/src/fs/utils/xattr.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MPL-2.0 + +use crate::prelude::*; + +pub const XATTR_NAME_MAX_LEN: usize = 255; +pub const XATTR_VALUE_MAX_LEN: usize = 65536; +pub const XATTR_LIST_MAX_LEN: usize = 65536; + +/// Represents different namespaces with different capabilities +/// for extended attributes (xattrs). +#[derive(Debug, PartialEq, Eq, Clone, Copy, TryFromInt, Hash)] +#[repr(u8)] +pub enum XattrNamespace { + User = 1, + Trusted = 2, + System = 3, + Security = 4, + // More namespaces can be added here. +} + +/// Represents the name of an xattr. It includes both a valid namespace +/// and a full name string slice, which contains the namespace prefix. +/// +/// For example, "user.foo" is a valid xattr name, and its namespace +/// is `XattrNamespace::User`. +#[derive(Debug, Hash)] +pub struct XattrName<'a> { + namespace: XattrNamespace, + full_name: &'a str, +} + +impl XattrNamespace { + pub fn try_from_full_name(full_name: &str) -> Option { + const USER_PREFIX: &str = "user."; + const TRUSTED_PREFIX: &str = "trusted."; + const SYSTEM_PREFIX: &str = "system."; + const SECURITY_PREFIX: &str = "security."; + + if full_name.starts_with(USER_PREFIX) { + Some(XattrNamespace::User) + } else if full_name.starts_with(TRUSTED_PREFIX) { + Some(XattrNamespace::Trusted) + } else if full_name.starts_with(SYSTEM_PREFIX) { + Some(XattrNamespace::System) + } else if full_name.starts_with(SECURITY_PREFIX) { + Some(XattrNamespace::Security) + } else { + None + } + } + + pub fn is_user(&self) -> bool { + matches!(self, XattrNamespace::User) + } + + pub fn is_admin(&self) -> bool { + matches!(self, XattrNamespace::Trusted) + } +} + +impl<'a> XattrName<'a> { + pub fn try_from_full_name(full_name: &'a str) -> Option { + let namespace = XattrNamespace::try_from_full_name(full_name)?; + Some(Self { + namespace, + full_name, + }) + } + + pub fn namespace(&self) -> XattrNamespace { + self.namespace + } + + pub const fn full_name(&self) -> &'a str { + self.full_name + } + + pub const fn full_name_len(&self) -> usize { + self.full_name.len() + } +} + +bitflags::bitflags! { + /// Flags for setting an xattr value. + pub struct XattrSetFlags: u8 { + /// Creates a new xattr if it doesn't exist, or replaces the value if it does. + const CREATE_OR_REPLACE = 0; + /// Creates a new xattr, fails if it already exists. + const CREATE_ONLY = 1; + /// Replaces the value of an existing xattr, fails if it doesn't exist. + const REPLACE_ONLY = 2; + } +} diff --git a/kernel/src/syscall/arch/x86.rs b/kernel/src/syscall/arch/x86.rs index 8d598e31..d14d2445 100644 --- a/kernel/src/syscall/arch/x86.rs +++ b/kernel/src/syscall/arch/x86.rs @@ -52,11 +52,13 @@ use crate::syscall::{ gettid::sys_gettid, gettimeofday::sys_gettimeofday, getuid::sys_getuid, + getxattr::{sys_fgetxattr, sys_getxattr, sys_lgetxattr}, impl_syscall_nums_and_dispatch_fn, ioctl::sys_ioctl, kill::sys_kill, link::{sys_link, sys_linkat}, listen::sys_listen, + listxattr::{sys_flistxattr, sys_listxattr, sys_llistxattr}, lseek::sys_lseek, madvise::sys_madvise, mkdir::{sys_mkdir, sys_mkdirat}, @@ -82,6 +84,7 @@ use crate::syscall::{ readlink::{sys_readlink, sys_readlinkat}, recvfrom::sys_recvfrom, recvmsg::sys_recvmsg, + removexattr::{sys_fremovexattr, sys_lremovexattr, sys_removexattr}, rename::{sys_rename, sys_renameat}, rmdir::sys_rmdir, rt_sigaction::sys_rt_sigaction, @@ -122,6 +125,7 @@ use crate::syscall::{ setsid::sys_setsid, setsockopt::sys_setsockopt, setuid::sys_setuid, + setxattr::{sys_fsetxattr, sys_lsetxattr, sys_setxattr}, shutdown::sys_shutdown, sigaltstack::sys_sigaltstack, signalfd::{sys_signalfd, sys_signalfd4}, @@ -284,6 +288,18 @@ impl_syscall_nums_and_dispatch_fn! { SYS_MOUNT = 165 => sys_mount(args[..5]); SYS_UMOUNT2 = 166 => sys_umount(args[..2]); SYS_GETTID = 186 => sys_gettid(args[..0]); + SYS_SETXATTR = 188 => sys_setxattr(args[..5]); + SYS_LSETXATTR = 189 => sys_lsetxattr(args[..5]); + SYS_FSETXATTR = 190 => sys_fsetxattr(args[..5]); + SYS_GETXATTR = 191 => sys_getxattr(args[..4]); + SYS_LGETXATTR = 192 => sys_lgetxattr(args[..4]); + SYS_FGETXATTR = 193 => sys_fgetxattr(args[..4]); + SYS_LISTXATTR = 194 => sys_listxattr(args[..3]); + SYS_LLISTXATTR = 195 => sys_llistxattr(args[..3]); + SYS_FLISTXATTR = 196 => sys_flistxattr(args[..3]); + SYS_REMOVEXATTR = 197 => sys_removexattr(args[..2]); + SYS_LREMOVEXATTR = 198 => sys_lremovexattr(args[..2]); + SYS_FREMOVEXATTR = 199 => sys_fremovexattr(args[..2]); SYS_TIME = 201 => sys_time(args[..1]); SYS_FUTEX = 202 => sys_futex(args[..6]); SYS_SCHED_SETAFFINITY = 203 => sys_sched_setaffinity(args[..3]); diff --git a/kernel/src/syscall/getxattr.rs b/kernel/src/syscall/getxattr.rs new file mode 100644 index 00000000..9a8d4ec2 --- /dev/null +++ b/kernel/src/syscall/getxattr.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{ + setxattr::{ + check_xattr_namespace, lookup_dentry_for_xattr, parse_xattr_name, + read_xattr_name_cstr_from_user, XattrFileCtx, + }, + SyscallReturn, +}; +use crate::{ + fs::{ + file_table::{get_file_fast, FileDesc}, + utils::XATTR_VALUE_MAX_LEN, + }, + prelude::*, + syscall::constants::MAX_FILENAME_LEN, +}; + +pub fn sys_getxattr( + path_ptr: Vaddr, + name_ptr: Vaddr, + value_ptr: Vaddr, + value_len: usize, + ctx: &Context, +) -> Result { + let user_space = ctx.user_space(); + let path = user_space.read_cstring(path_ptr, MAX_FILENAME_LEN)?; + + let len = getxattr( + XattrFileCtx::Path(path), + name_ptr, + value_ptr, + value_len, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(len as _)) +} + +pub fn sys_lgetxattr( + path_ptr: Vaddr, + name_ptr: Vaddr, + value_ptr: Vaddr, + value_len: usize, + ctx: &Context, +) -> Result { + let user_space = ctx.user_space(); + let path = user_space.read_cstring(path_ptr, MAX_FILENAME_LEN)?; + + let len = getxattr( + XattrFileCtx::PathNoFollow(path), + name_ptr, + value_ptr, + value_len, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(len as _)) +} + +pub fn sys_fgetxattr( + fd: FileDesc, + name_ptr: Vaddr, + value_ptr: Vaddr, + value_len: usize, + ctx: &Context, +) -> Result { + let mut file_table = ctx.thread_local.file_table().borrow_mut(); + let file = get_file_fast!(&mut file_table, fd); + + let user_space = ctx.user_space(); + let len = getxattr( + XattrFileCtx::FileHandle(file), + name_ptr, + value_ptr, + value_len, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(len as _)) +} + +fn getxattr( + file_ctx: XattrFileCtx, + name_ptr: Vaddr, + value_ptr: Vaddr, + value_len: usize, + user_space: &CurrentUserSpace, + ctx: &Context, +) -> Result { + let name_cstr = read_xattr_name_cstr_from_user(name_ptr, user_space)?; + let name_str = name_cstr.to_string_lossy(); + let xattr_name = parse_xattr_name(name_str.as_ref())?; + check_xattr_namespace(xattr_name.namespace(), ctx).map_err(|_| Error::new(Errno::ENODATA))?; + + let mut value_writer = user_space.writer(value_ptr, value_len.min(XATTR_VALUE_MAX_LEN))?; + + let dentry = lookup_dentry_for_xattr(&file_ctx, ctx)?; + dentry.get_xattr(xattr_name, &mut value_writer) +} diff --git a/kernel/src/syscall/listxattr.rs b/kernel/src/syscall/listxattr.rs new file mode 100644 index 00000000..071c99c5 --- /dev/null +++ b/kernel/src/syscall/listxattr.rs @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{ + setxattr::{lookup_dentry_for_xattr, XattrFileCtx}, + SyscallReturn, +}; +use crate::{ + fs::{ + file_table::{get_file_fast, FileDesc}, + utils::{XattrNamespace, XATTR_LIST_MAX_LEN}, + }, + prelude::*, + process::credentials::capabilities::CapSet, + syscall::constants::MAX_FILENAME_LEN, +}; + +pub fn sys_listxattr( + path_ptr: Vaddr, + list_ptr: Vaddr, // The given list is used to place xattr (null-terminated) names. + list_len: usize, + ctx: &Context, +) -> Result { + let user_space = ctx.user_space(); + let path = user_space.read_cstring(path_ptr, MAX_FILENAME_LEN)?; + + let len = listxattr( + XattrFileCtx::Path(path), + list_ptr, + list_len, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(len as _)) +} + +pub fn sys_llistxattr( + path_ptr: Vaddr, + list_ptr: Vaddr, // The given list is used to place xattr (null-terminated) names. + list_len: usize, + ctx: &Context, +) -> Result { + let user_space = ctx.user_space(); + let path = user_space.read_cstring(path_ptr, MAX_FILENAME_LEN)?; + + let len = listxattr( + XattrFileCtx::PathNoFollow(path), + list_ptr, + list_len, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(len as _)) +} + +pub fn sys_flistxattr( + fd: FileDesc, + list_ptr: Vaddr, // The given list is used to place xattr (null-terminated) names. + list_len: usize, + ctx: &Context, +) -> Result { + let mut file_table = ctx.thread_local.file_table().borrow_mut(); + let file = get_file_fast!(&mut file_table, fd); + + let user_space = ctx.user_space(); + let len = listxattr( + XattrFileCtx::FileHandle(file), + list_ptr, + list_len, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(len as _)) +} + +fn listxattr( + file_ctx: XattrFileCtx, + list_ptr: Vaddr, + list_len: usize, + user_space: &CurrentUserSpace, + ctx: &Context, +) -> Result { + if list_len > XATTR_LIST_MAX_LEN { + return_errno_with_message!(Errno::E2BIG, "xattr list too long"); + } + + let namespace = get_current_xattr_namespace(ctx); + let mut list_writer = user_space.writer(list_ptr, list_len)?; + + let dentry = lookup_dentry_for_xattr(&file_ctx, ctx)?; + dentry.list_xattr(namespace, &mut list_writer) +} + +fn get_current_xattr_namespace(ctx: &Context) -> XattrNamespace { + let credentials = ctx.posix_thread.credentials(); + let permitted_capset = credentials.permitted_capset(); + let effective_capset = credentials.effective_capset(); + + if permitted_capset.contains(CapSet::SYS_ADMIN) && effective_capset.contains(CapSet::SYS_ADMIN) + { + XattrNamespace::Trusted + } else { + XattrNamespace::User + } +} diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index 28d89ca5..ff45f72c 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -60,10 +60,12 @@ mod getsockopt; mod gettid; mod gettimeofday; mod getuid; +mod getxattr; mod ioctl; mod kill; mod link; mod listen; +mod listxattr; mod lseek; mod madvise; mod mkdir; @@ -89,6 +91,7 @@ mod read; mod readlink; mod recvfrom; mod recvmsg; +mod removexattr; mod rename; mod rmdir; mod rt_sigaction; @@ -129,6 +132,7 @@ mod setreuid; mod setsid; mod setsockopt; mod setuid; +mod setxattr; mod shutdown; mod sigaltstack; mod signalfd; diff --git a/kernel/src/syscall/removexattr.rs b/kernel/src/syscall/removexattr.rs new file mode 100644 index 00000000..0585dfe3 --- /dev/null +++ b/kernel/src/syscall/removexattr.rs @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::{ + setxattr::{ + check_xattr_namespace, lookup_dentry_for_xattr, parse_xattr_name, + read_xattr_name_cstr_from_user, XattrFileCtx, + }, + SyscallReturn, +}; +use crate::{ + fs::file_table::{get_file_fast, FileDesc}, + prelude::*, + syscall::constants::MAX_FILENAME_LEN, +}; + +pub fn sys_removexattr(path_ptr: Vaddr, name_ptr: Vaddr, ctx: &Context) -> Result { + let user_space = ctx.user_space(); + let path = user_space.read_cstring(path_ptr, MAX_FILENAME_LEN)?; + + removexattr(XattrFileCtx::Path(path), name_ptr, &user_space, ctx)?; + + Ok(SyscallReturn::Return(0)) +} + +pub fn sys_lremovexattr(path_ptr: Vaddr, name_ptr: Vaddr, ctx: &Context) -> Result { + let user_space = ctx.user_space(); + let path = user_space.read_cstring(path_ptr, MAX_FILENAME_LEN)?; + + removexattr(XattrFileCtx::PathNoFollow(path), name_ptr, &user_space, ctx)?; + + Ok(SyscallReturn::Return(0)) +} + +pub fn sys_fremovexattr(fd: FileDesc, name_ptr: Vaddr, ctx: &Context) -> Result { + let mut file_table = ctx.thread_local.file_table().borrow_mut(); + let file = get_file_fast!(&mut file_table, fd); + + let user_space = ctx.user_space(); + removexattr(XattrFileCtx::FileHandle(file), name_ptr, &user_space, ctx)?; + + Ok(SyscallReturn::Return(0)) +} + +fn removexattr( + file_ctx: XattrFileCtx, + name_ptr: Vaddr, + user_space: &CurrentUserSpace, + ctx: &Context, +) -> Result<()> { + let name_cstr = read_xattr_name_cstr_from_user(name_ptr, user_space)?; + let name_str = name_cstr.to_string_lossy(); + let xattr_name = parse_xattr_name(name_str.as_ref())?; + check_xattr_namespace(xattr_name.namespace(), ctx)?; + + let dentry = lookup_dentry_for_xattr(&file_ctx, ctx)?; + dentry.remove_xattr(xattr_name) +} diff --git a/kernel/src/syscall/setxattr.rs b/kernel/src/syscall/setxattr.rs new file mode 100644 index 00000000..e226f631 --- /dev/null +++ b/kernel/src/syscall/setxattr.rs @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::borrow::Cow; + +use super::SyscallReturn; +use crate::{ + fs::{ + file_handle::FileLike, + file_table::{get_file_fast, FileDesc}, + fs_resolver::{FsPath, AT_FDCWD}, + path::Dentry, + utils::{ + XattrName, XattrNamespace, XattrSetFlags, XATTR_NAME_MAX_LEN, XATTR_VALUE_MAX_LEN, + }, + }, + prelude::*, + process::credentials::capabilities::CapSet, + syscall::constants::MAX_FILENAME_LEN, +}; + +pub fn sys_setxattr( + path_ptr: Vaddr, + name_ptr: Vaddr, + value_ptr: Vaddr, + value_len: usize, + flags: i32, + ctx: &Context, +) -> Result { + let user_space = ctx.user_space(); + let path = user_space.read_cstring(path_ptr, MAX_FILENAME_LEN)?; + + setxattr( + XattrFileCtx::Path(path), + name_ptr, + value_ptr, + value_len, + flags, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(0)) +} + +pub fn sys_lsetxattr( + path_ptr: Vaddr, + name_ptr: Vaddr, + value_ptr: Vaddr, + value_len: usize, + flags: i32, + ctx: &Context, +) -> Result { + let user_space = ctx.user_space(); + let path = user_space.read_cstring(path_ptr, MAX_FILENAME_LEN)?; + + setxattr( + XattrFileCtx::PathNoFollow(path), + name_ptr, + value_ptr, + value_len, + flags, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(0)) +} + +pub fn sys_fsetxattr( + fd: FileDesc, + name_ptr: Vaddr, + value_ptr: Vaddr, + value_len: usize, + flags: i32, + ctx: &Context, +) -> Result { + let mut file_table = ctx.thread_local.file_table().borrow_mut(); + let file = get_file_fast!(&mut file_table, fd); + + let user_space = ctx.user_space(); + setxattr( + XattrFileCtx::FileHandle(file), + name_ptr, + value_ptr, + value_len, + flags, + &user_space, + ctx, + )?; + + Ok(SyscallReturn::Return(0)) +} + +fn setxattr( + file_ctx: XattrFileCtx, + name_ptr: Vaddr, + value_ptr: Vaddr, + value_len: usize, + flags: i32, + user_space: &CurrentUserSpace, + ctx: &Context, +) -> Result<()> { + let flags = XattrSetFlags::from_bits(flags as _) + .ok_or(Error::with_message(Errno::EINVAL, "invalid xattr flags"))?; + + let name_cstr = read_xattr_name_cstr_from_user(name_ptr, user_space)?; + let name_str = name_cstr.to_string_lossy(); + let xattr_name = parse_xattr_name(name_str.as_ref())?; + check_xattr_namespace(xattr_name.namespace(), ctx)?; + + if value_len > XATTR_VALUE_MAX_LEN { + return_errno_with_message!(Errno::E2BIG, "xattr value too long"); + } + let mut value_reader = user_space.reader(value_ptr, value_len)?; + + let dentry = lookup_dentry_for_xattr(&file_ctx, ctx)?; + dentry.set_xattr(xattr_name, &mut value_reader, flags) +} + +/// The context to describe the target file for xattr operations. +pub(super) enum XattrFileCtx<'a> { + Path(CString), + PathNoFollow(CString), + FileHandle(Cow<'a, Arc>), +} + +pub(super) fn lookup_dentry_for_xattr<'a>( + file_ctx: &'a XattrFileCtx<'a>, + ctx: &'a Context, +) -> Result> { + let lookup_dentry_from_fs = + |path: &CString, ctx: &Context, symlink_no_follow: bool| -> Result> { + let path = path.to_string_lossy(); + let fs_path = FsPath::new(AT_FDCWD, path.as_ref())?; + let fs = ctx.posix_thread.fs().resolver().read(); + let dentry = if symlink_no_follow { + fs.lookup_no_follow(&fs_path)? + } else { + fs.lookup(&fs_path)? + }; + Ok(Cow::Owned(dentry)) + }; + + match file_ctx { + XattrFileCtx::Path(path) => lookup_dentry_from_fs(path, ctx, false), + XattrFileCtx::PathNoFollow(path) => lookup_dentry_from_fs(path, ctx, true), + XattrFileCtx::FileHandle(file) => { + let dentry = file.as_inode_or_err()?.dentry(); + Ok(Cow::Borrowed(dentry)) + } + } +} + +pub(super) fn read_xattr_name_cstr_from_user<'a>( + name_ptr: Vaddr, + user_space: &CurrentUserSpace, +) -> Result { + let mut reader = user_space.reader(name_ptr, XATTR_NAME_MAX_LEN + 1)?; + reader.read_cstring().map_err(|e| { + if reader.remain() == 0 { + Error::with_message(Errno::ERANGE, "xattr name too long") + } else { + e + } + }) +} + +pub(super) fn parse_xattr_name(name_str: &str) -> Result { + if name_str.is_empty() || name_str.len() > XATTR_NAME_MAX_LEN { + return_errno_with_message!(Errno::ERANGE, "xattr name empty or too long"); + } + + let xattr_name = XattrName::try_from_full_name(name_str.as_ref()).ok_or( + Error::with_message(Errno::EOPNOTSUPP, "invalid xattr namespace"), + )?; + Ok(xattr_name) +} + +pub(super) fn check_xattr_namespace(namespace: XattrNamespace, ctx: &Context) -> Result<()> { + let credentials = ctx.posix_thread.credentials(); + let permitted_capset = credentials.permitted_capset(); + let effective_capset = credentials.effective_capset(); + + match namespace { + XattrNamespace::Trusted => { + if !permitted_capset.contains(CapSet::SYS_ADMIN) + || !effective_capset.contains(CapSet::SYS_ADMIN) + { + return_errno_with_message!( + Errno::EPERM, + "try to access trusted xattr without CAP_SYS_ADMIN" + ); + } + } + _ => {} + } + Ok(()) +}