Add extended attribute (xattr) syscalls: setxattr, getxattr, listxattr and removexattr

This commit is contained in:
Shaowei Song 2025-03-28 05:58:37 +00:00 committed by Tate, Hongliang Tian
parent 8b5a6f0d58
commit a47eda413c
11 changed files with 649 additions and 15 deletions

View File

@ -15,7 +15,7 @@ support the loading of Linux kernel modules.
## System Calls ## System Calls
At the time of writing, 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. provided by Linux on x86-64 architecture.
| Numbers | Names | Is Implemented | | Numbers | Names | Is Implemented |
@ -208,18 +208,18 @@ provided by Linux on x86-64 architecture.
| 185 | security | ❌ | | 185 | security | ❌ |
| 186 | gettid | ✅ | | 186 | gettid | ✅ |
| 187 | readahead | ❌ | | 187 | readahead | ❌ |
| 188 | setxattr | | | 188 | setxattr | |
| 189 | lsetxattr | | | 189 | lsetxattr | |
| 190 | fsetxattr | | | 190 | fsetxattr | |
| 191 | getxattr | | | 191 | getxattr | |
| 192 | lgetxattr | | | 192 | lgetxattr | |
| 193 | fgetxattr | | | 193 | fgetxattr | |
| 194 | listxattr | | | 194 | listxattr | |
| 195 | llistxattr | | | 195 | llistxattr | |
| 196 | flistxattr | | | 196 | flistxattr | |
| 197 | removexattr | | | 197 | removexattr | |
| 198 | lremovexattr | | | 198 | lremovexattr | |
| 199 | fremovexattr | | | 199 | fremovexattr | |
| 200 | tkill | ❌ | | 200 | tkill | ❌ |
| 201 | time | ✅ | | 201 | time | ✅ |
| 202 | futex | ✅ | | 202 | futex | ✅ |

View File

@ -17,7 +17,8 @@ use crate::{
fs::{ fs::{
path::mount::MountNode, path::mount::MountNode,
utils::{ utils::{
FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, Permission, NAME_MAX, FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, Permission, XattrName,
XattrNamespace, XattrSetFlags, NAME_MAX,
}, },
}, },
prelude::*, prelude::*,
@ -356,6 +357,19 @@ impl Dentry_ {
pub fn ctime(&self) -> Duration; pub fn ctime(&self) -> Duration;
pub fn set_ctime(&self, time: Duration); pub fn set_ctime(&self, time: Duration);
pub fn is_dentry_cacheable(&self) -> bool; 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<usize>;
pub fn list_xattr(
&self,
namespace: XattrNamespace,
list_writer: &mut VmWriter,
) -> Result<usize>;
pub fn remove_xattr(&self, name: XattrName) -> Result<()>;
} }
impl Debug for Dentry_ { impl Debug for Dentry_ {
@ -769,4 +783,17 @@ impl Dentry {
pub fn inode(&self) -> &Arc<dyn Inode>; pub fn inode(&self) -> &Arc<dyn Inode>;
pub fn is_root_of_mount(&self) -> bool; pub fn is_root_of_mount(&self) -> bool;
pub fn is_mountpoint(&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<usize>;
pub fn list_xattr(
&self,
namespace: XattrNamespace,
list_writer: &mut VmWriter,
) -> Result<usize>;
pub fn remove_xattr(&self, name: XattrName) -> Result<()>;
} }

View File

@ -8,7 +8,10 @@ use aster_rights::Full;
use core2::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult, Write}; use core2::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult, Write};
use ostd::task::Task; use ostd::task::Task;
use super::{AccessMode, DirentVisitor, FallocMode, FileSystem, IoctlCmd}; use super::{
AccessMode, DirentVisitor, FallocMode, FileSystem, IoctlCmd, XattrName, XattrNamespace,
XattrSetFlags,
};
use crate::{ use crate::{
events::IoEvents, events::IoEvents,
fs::device::{Device, DeviceType}, fs::device::{Device, DeviceType},
@ -497,6 +500,27 @@ pub trait Inode: Any + Sync + Send {
None 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<usize> {
Err(Error::new(Errno::EOPNOTSUPP))
}
fn list_xattr(&self, namespace: XattrNamespace, list_writer: &mut VmWriter) -> Result<usize> {
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. /// Used to check for read/write/execute permissions on a file.
/// ///
/// Similar to Linux, using "fsuid" here allows setting filesystem permissions /// Similar to Linux, using "fsuid" here allows setting filesystem permissions

View File

@ -19,6 +19,10 @@ pub use range_lock::{
FileRange, RangeLockItem, RangeLockItemBuilder, RangeLockList, RangeLockType, OFFSET_MAX, FileRange, RangeLockItem, RangeLockItemBuilder, RangeLockList, RangeLockType, OFFSET_MAX,
}; };
pub use status_flags::StatusFlags; 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 access_mode;
mod channel; mod channel;
@ -35,6 +39,7 @@ mod page_cache;
mod random_test; mod random_test;
mod range_lock; mod range_lock;
mod status_flags; mod status_flags;
mod xattr;
use core::{ use core::{
borrow::Borrow, borrow::Borrow,

View File

@ -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<XattrNamespace> {
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<Self> {
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;
}
}

View File

@ -52,11 +52,13 @@ use crate::syscall::{
gettid::sys_gettid, gettid::sys_gettid,
gettimeofday::sys_gettimeofday, gettimeofday::sys_gettimeofday,
getuid::sys_getuid, getuid::sys_getuid,
getxattr::{sys_fgetxattr, sys_getxattr, sys_lgetxattr},
impl_syscall_nums_and_dispatch_fn, impl_syscall_nums_and_dispatch_fn,
ioctl::sys_ioctl, ioctl::sys_ioctl,
kill::sys_kill, kill::sys_kill,
link::{sys_link, sys_linkat}, link::{sys_link, sys_linkat},
listen::sys_listen, listen::sys_listen,
listxattr::{sys_flistxattr, sys_listxattr, sys_llistxattr},
lseek::sys_lseek, lseek::sys_lseek,
madvise::sys_madvise, madvise::sys_madvise,
mkdir::{sys_mkdir, sys_mkdirat}, mkdir::{sys_mkdir, sys_mkdirat},
@ -82,6 +84,7 @@ use crate::syscall::{
readlink::{sys_readlink, sys_readlinkat}, readlink::{sys_readlink, sys_readlinkat},
recvfrom::sys_recvfrom, recvfrom::sys_recvfrom,
recvmsg::sys_recvmsg, recvmsg::sys_recvmsg,
removexattr::{sys_fremovexattr, sys_lremovexattr, sys_removexattr},
rename::{sys_rename, sys_renameat}, rename::{sys_rename, sys_renameat},
rmdir::sys_rmdir, rmdir::sys_rmdir,
rt_sigaction::sys_rt_sigaction, rt_sigaction::sys_rt_sigaction,
@ -122,6 +125,7 @@ use crate::syscall::{
setsid::sys_setsid, setsid::sys_setsid,
setsockopt::sys_setsockopt, setsockopt::sys_setsockopt,
setuid::sys_setuid, setuid::sys_setuid,
setxattr::{sys_fsetxattr, sys_lsetxattr, sys_setxattr},
shutdown::sys_shutdown, shutdown::sys_shutdown,
sigaltstack::sys_sigaltstack, sigaltstack::sys_sigaltstack,
signalfd::{sys_signalfd, sys_signalfd4}, signalfd::{sys_signalfd, sys_signalfd4},
@ -284,6 +288,18 @@ impl_syscall_nums_and_dispatch_fn! {
SYS_MOUNT = 165 => sys_mount(args[..5]); SYS_MOUNT = 165 => sys_mount(args[..5]);
SYS_UMOUNT2 = 166 => sys_umount(args[..2]); SYS_UMOUNT2 = 166 => sys_umount(args[..2]);
SYS_GETTID = 186 => sys_gettid(args[..0]); 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_TIME = 201 => sys_time(args[..1]);
SYS_FUTEX = 202 => sys_futex(args[..6]); SYS_FUTEX = 202 => sys_futex(args[..6]);
SYS_SCHED_SETAFFINITY = 203 => sys_sched_setaffinity(args[..3]); SYS_SCHED_SETAFFINITY = 203 => sys_sched_setaffinity(args[..3]);

View File

@ -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<SyscallReturn> {
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<SyscallReturn> {
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<SyscallReturn> {
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<usize> {
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)
}

View File

@ -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<SyscallReturn> {
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<SyscallReturn> {
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<SyscallReturn> {
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<usize> {
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
}
}

View File

@ -60,10 +60,12 @@ mod getsockopt;
mod gettid; mod gettid;
mod gettimeofday; mod gettimeofday;
mod getuid; mod getuid;
mod getxattr;
mod ioctl; mod ioctl;
mod kill; mod kill;
mod link; mod link;
mod listen; mod listen;
mod listxattr;
mod lseek; mod lseek;
mod madvise; mod madvise;
mod mkdir; mod mkdir;
@ -89,6 +91,7 @@ mod read;
mod readlink; mod readlink;
mod recvfrom; mod recvfrom;
mod recvmsg; mod recvmsg;
mod removexattr;
mod rename; mod rename;
mod rmdir; mod rmdir;
mod rt_sigaction; mod rt_sigaction;
@ -129,6 +132,7 @@ mod setreuid;
mod setsid; mod setsid;
mod setsockopt; mod setsockopt;
mod setuid; mod setuid;
mod setxattr;
mod shutdown; mod shutdown;
mod sigaltstack; mod sigaltstack;
mod signalfd; mod signalfd;

View File

@ -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<SyscallReturn> {
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<SyscallReturn> {
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<SyscallReturn> {
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)
}

View File

@ -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<SyscallReturn> {
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<SyscallReturn> {
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<SyscallReturn> {
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<dyn FileLike>>),
}
pub(super) fn lookup_dentry_for_xattr<'a>(
file_ctx: &'a XattrFileCtx<'a>,
ctx: &'a Context,
) -> Result<Cow<'a, Dentry>> {
let lookup_dentry_from_fs =
|path: &CString, ctx: &Context, symlink_no_follow: bool| -> Result<Cow<'_, Dentry>> {
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<CString> {
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<XattrName> {
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(())
}