mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-26 16:54:01 +00:00
Auto merge of #117156 - jmillikin:os-unix-socket-ext, r=Amanieu,dtolnay
Convert `Unix{Datagram,Stream}::{set_}passcred()` to per-OS traits These methods are the pre-stabilized API for obtaining peer credentials from an `AF_UNIX` socket, part of the `unix_socket_ancillary_data` feature. Their current behavior is to get/set one of the `SO_PASSCRED` (Linux), `LOCAL_CREDS_PERSISTENT` (FreeBSD), or `LOCAL_CREDS` (NetBSD) socket options. On other targets the `{set_}passcred()` methods do not exist. There are two problems with this approach: 1. Having public methods only exist for certain targets isn't permitted in a stable `std` API. 2. These options have generally similar purposes, but they are non-POSIX and their details can differ in subtle and surprising ways (such as whether they continue to be set after the next call to `recvmsg()`). Splitting into OS-specific extension traits is the preferred solution to both problems.
This commit is contained in:
commit
6639672554
@ -5,5 +5,8 @@
|
||||
#[stable(feature = "unix_socket_abstract", since = "1.70.0")]
|
||||
pub use crate::os::net::linux_ext::addr::SocketAddrExt;
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub use crate::os::net::linux_ext::socket::UnixSocketExt;
|
||||
|
||||
#[unstable(feature = "tcp_quickack", issue = "96256")]
|
||||
pub use crate::os::net::linux_ext::tcp::TcpStreamExt;
|
||||
|
@ -3,4 +3,5 @@
|
||||
#![stable(feature = "raw_ext", since = "1.1.0")]
|
||||
|
||||
pub mod fs;
|
||||
pub mod net;
|
||||
pub mod raw;
|
||||
|
65
library/std/src/os/freebsd/net.rs
Normal file
65
library/std/src/os/freebsd/net.rs
Normal file
@ -0,0 +1,65 @@
|
||||
//! FreeBSD-specific networking functionality.
|
||||
|
||||
#![unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
|
||||
use crate::io;
|
||||
use crate::os::unix::net;
|
||||
use crate::sealed::Sealed;
|
||||
use crate::sys_common::AsInner;
|
||||
|
||||
/// FreeBSD-specific functionality for `AF_UNIX` sockets [`UnixDatagram`]
|
||||
/// and [`UnixStream`].
|
||||
///
|
||||
/// [`UnixDatagram`]: net::UnixDatagram
|
||||
/// [`UnixStream`]: net::UnixStream
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub trait UnixSocketExt: Sealed {
|
||||
/// Query the current setting of socket option `LOCAL_CREDS_PERSISTENT`.
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
fn local_creds_persistent(&self) -> io::Result<bool>;
|
||||
|
||||
/// Enable or disable socket option `LOCAL_CREDS_PERSISTENT`.
|
||||
///
|
||||
/// This option enables the credentials of the sending process to be
|
||||
/// received as a control message in [`AncillaryData`].
|
||||
///
|
||||
/// [`AncillaryData`]: net::AncillaryData
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// #![feature(unix_socket_ancillary_data)]
|
||||
/// use std::os::freebsd::net::UnixSocketExt;
|
||||
/// use std::os::unix::net::UnixDatagram;
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let sock = UnixDatagram::unbound()?;
|
||||
/// sock.set_local_creds_persistent(true).expect("set_local_creds_persistent failed");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
fn set_local_creds_persistent(&self, local_creds_persistent: bool) -> io::Result<()>;
|
||||
}
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
impl UnixSocketExt for net::UnixDatagram {
|
||||
fn local_creds_persistent(&self) -> io::Result<bool> {
|
||||
self.as_inner().local_creds_persistent()
|
||||
}
|
||||
|
||||
fn set_local_creds_persistent(&self, local_creds_persistent: bool) -> io::Result<()> {
|
||||
self.as_inner().set_local_creds_persistent(local_creds_persistent)
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
impl UnixSocketExt for net::UnixStream {
|
||||
fn local_creds_persistent(&self) -> io::Result<bool> {
|
||||
self.as_inner().local_creds_persistent()
|
||||
}
|
||||
|
||||
fn set_local_creds_persistent(&self, local_creds_persistent: bool) -> io::Result<()> {
|
||||
self.as_inner().set_local_creds_persistent(local_creds_persistent)
|
||||
}
|
||||
}
|
@ -5,5 +5,8 @@
|
||||
#[stable(feature = "unix_socket_abstract", since = "1.70.0")]
|
||||
pub use crate::os::net::linux_ext::addr::SocketAddrExt;
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub use crate::os::net::linux_ext::socket::UnixSocketExt;
|
||||
|
||||
#[unstable(feature = "tcp_quickack", issue = "96256")]
|
||||
pub use crate::os::net::linux_ext::tcp::TcpStreamExt;
|
||||
|
@ -5,6 +5,9 @@
|
||||
#[stable(feature = "unix_socket_abstract", since = "1.70.0")]
|
||||
pub(crate) mod addr;
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub(crate) mod socket;
|
||||
|
||||
#[unstable(feature = "tcp_quickack", issue = "96256")]
|
||||
pub(crate) mod tcp;
|
||||
|
||||
|
63
library/std/src/os/net/linux_ext/socket.rs
Normal file
63
library/std/src/os/net/linux_ext/socket.rs
Normal file
@ -0,0 +1,63 @@
|
||||
//! Linux and Android-specific socket functionality.
|
||||
|
||||
use crate::io;
|
||||
use crate::os::unix::net;
|
||||
use crate::sealed::Sealed;
|
||||
use crate::sys_common::AsInner;
|
||||
|
||||
/// Linux-specific functionality for `AF_UNIX` sockets [`UnixDatagram`]
|
||||
/// and [`UnixStream`].
|
||||
///
|
||||
/// [`UnixDatagram`]: net::UnixDatagram
|
||||
/// [`UnixStream`]: net::UnixStream
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub trait UnixSocketExt: Sealed {
|
||||
/// Query the current setting of socket option `SO_PASSCRED`.
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
fn passcred(&self) -> io::Result<bool>;
|
||||
|
||||
/// Enable or disable socket option `SO_PASSCRED`.
|
||||
///
|
||||
/// This option enables the credentials of the sending process to be
|
||||
/// received as a control message in [`AncillaryData`].
|
||||
///
|
||||
/// [`AncillaryData`]: net::AncillaryData
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// #![feature(unix_socket_ancillary_data)]
|
||||
/// use std::os::linux::net::UnixSocketExt;
|
||||
/// use std::os::unix::net::UnixDatagram;
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let sock = UnixDatagram::unbound()?;
|
||||
/// sock.set_passcred(true).expect("set_passcred failed");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
fn set_passcred(&self, passcred: bool) -> io::Result<()>;
|
||||
}
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
impl UnixSocketExt for net::UnixDatagram {
|
||||
fn passcred(&self) -> io::Result<bool> {
|
||||
self.as_inner().passcred()
|
||||
}
|
||||
|
||||
fn set_passcred(&self, passcred: bool) -> io::Result<()> {
|
||||
self.as_inner().set_passcred(passcred)
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
impl UnixSocketExt for net::UnixStream {
|
||||
fn passcred(&self) -> io::Result<bool> {
|
||||
self.as_inner().passcred()
|
||||
}
|
||||
|
||||
fn set_passcred(&self, passcred: bool) -> io::Result<()> {
|
||||
self.as_inner().set_passcred(passcred)
|
||||
}
|
||||
}
|
@ -3,4 +3,5 @@
|
||||
#![stable(feature = "raw_ext", since = "1.1.0")]
|
||||
|
||||
pub mod fs;
|
||||
pub mod net;
|
||||
pub mod raw;
|
||||
|
65
library/std/src/os/netbsd/net.rs
Normal file
65
library/std/src/os/netbsd/net.rs
Normal file
@ -0,0 +1,65 @@
|
||||
//! NetBSD-specific networking functionality.
|
||||
|
||||
#![unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
|
||||
use crate::io;
|
||||
use crate::os::unix::net;
|
||||
use crate::sealed::Sealed;
|
||||
use crate::sys_common::AsInner;
|
||||
|
||||
/// NetBSD-specific functionality for `AF_UNIX` sockets [`UnixDatagram`]
|
||||
/// and [`UnixStream`].
|
||||
///
|
||||
/// [`UnixDatagram`]: net::UnixDatagram
|
||||
/// [`UnixStream`]: net::UnixStream
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub trait UnixSocketExt: Sealed {
|
||||
/// Query the current setting of socket option `LOCAL_CREDS`.
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
fn local_creds(&self) -> io::Result<bool>;
|
||||
|
||||
/// Enable or disable socket option `LOCAL_CREDS`.
|
||||
///
|
||||
/// This option enables the credentials of the sending process to be
|
||||
/// received as a control message in [`AncillaryData`].
|
||||
///
|
||||
/// [`AncillaryData`]: net::AncillaryData
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// #![feature(unix_socket_ancillary_data)]
|
||||
/// use std::os::netbsd::net::UnixSocketExt;
|
||||
/// use std::os::unix::net::UnixDatagram;
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let sock = UnixDatagram::unbound()?;
|
||||
/// sock.set_local_creds(true).expect("set_local_creds failed");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
fn set_local_creds(&self, local_creds: bool) -> io::Result<()>;
|
||||
}
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
impl UnixSocketExt for net::UnixDatagram {
|
||||
fn local_creds(&self) -> io::Result<bool> {
|
||||
self.as_inner().local_creds()
|
||||
}
|
||||
|
||||
fn set_local_creds(&self, local_creds: bool) -> io::Result<()> {
|
||||
self.as_inner().set_local_creds(local_creds)
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
impl UnixSocketExt for net::UnixStream {
|
||||
fn local_creds(&self) -> io::Result<bool> {
|
||||
self.as_inner().local_creds()
|
||||
}
|
||||
|
||||
fn set_local_creds(&self, local_creds: bool) -> io::Result<()> {
|
||||
self.as_inner().set_local_creds(local_creds)
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ use crate::io::{IoSlice, IoSliceMut};
|
||||
use crate::net::Shutdown;
|
||||
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
|
||||
use crate::path::Path;
|
||||
use crate::sealed::Sealed;
|
||||
use crate::sys::cvt;
|
||||
use crate::sys::net::Socket;
|
||||
use crate::sys_common::{AsInner, FromInner, IntoInner};
|
||||
@ -54,6 +55,10 @@ const MSG_NOSIGNAL: libc::c_int = 0x0;
|
||||
#[stable(feature = "unix_socket", since = "1.10.0")]
|
||||
pub struct UnixDatagram(Socket);
|
||||
|
||||
/// Allows extension traits within `std`.
|
||||
#[unstable(feature = "sealed", issue = "none")]
|
||||
impl Sealed for UnixDatagram {}
|
||||
|
||||
#[stable(feature = "unix_socket", since = "1.10.0")]
|
||||
impl fmt::Debug for UnixDatagram {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@ -802,69 +807,6 @@ impl UnixDatagram {
|
||||
self.0.set_nonblocking(nonblocking)
|
||||
}
|
||||
|
||||
/// Moves the socket to pass unix credentials as control message in [`SocketAncillary`].
|
||||
///
|
||||
/// Set the socket option `SO_PASSCRED`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(
|
||||
any(
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
),
|
||||
doc = "```no_run"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(any(
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd"
|
||||
)),
|
||||
doc = "```ignore"
|
||||
)]
|
||||
/// #![feature(unix_socket_ancillary_data)]
|
||||
/// use std::os::unix::net::UnixDatagram;
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let sock = UnixDatagram::unbound()?;
|
||||
/// sock.set_passcred(true).expect("set_passcred function failed");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(any(
|
||||
doc,
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd"
|
||||
))]
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
|
||||
self.0.set_passcred(passcred)
|
||||
}
|
||||
|
||||
/// Get the current value of the socket for passing unix credentials in [`SocketAncillary`].
|
||||
/// This value can be change by [`set_passcred`].
|
||||
///
|
||||
/// Get the socket option `SO_PASSCRED`.
|
||||
///
|
||||
/// [`set_passcred`]: UnixDatagram::set_passcred
|
||||
#[cfg(any(
|
||||
doc,
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd"
|
||||
))]
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub fn passcred(&self) -> io::Result<bool> {
|
||||
self.0.passcred()
|
||||
}
|
||||
|
||||
/// Set the id of the socket for network filtering purpose
|
||||
///
|
||||
#[cfg_attr(
|
||||
@ -1038,3 +980,10 @@ impl From<OwnedFd> for UnixDatagram {
|
||||
unsafe { Self::from_raw_fd(owned.into_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsInner<Socket> for UnixDatagram {
|
||||
#[inline]
|
||||
fn as_inner(&self) -> &Socket {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ use crate::io::{self, IoSlice, IoSliceMut};
|
||||
use crate::net::Shutdown;
|
||||
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
|
||||
use crate::path::Path;
|
||||
use crate::sealed::Sealed;
|
||||
use crate::sys::cvt;
|
||||
use crate::sys::net::Socket;
|
||||
use crate::sys_common::{AsInner, FromInner};
|
||||
@ -44,6 +45,10 @@ use crate::time::Duration;
|
||||
#[stable(feature = "unix_socket", since = "1.10.0")]
|
||||
pub struct UnixStream(pub(super) Socket);
|
||||
|
||||
/// Allows extension traits within `std`.
|
||||
#[unstable(feature = "sealed", issue = "none")]
|
||||
impl Sealed for UnixStream {}
|
||||
|
||||
#[stable(feature = "unix_socket", since = "1.10.0")]
|
||||
impl fmt::Debug for UnixStream {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@ -379,69 +384,6 @@ impl UnixStream {
|
||||
self.0.set_nonblocking(nonblocking)
|
||||
}
|
||||
|
||||
/// Moves the socket to pass unix credentials as control message in [`SocketAncillary`].
|
||||
///
|
||||
/// Set the socket option `SO_PASSCRED`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(
|
||||
any(
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd"
|
||||
),
|
||||
doc = "```no_run"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(any(
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd"
|
||||
)),
|
||||
doc = "```ignore"
|
||||
)]
|
||||
/// #![feature(unix_socket_ancillary_data)]
|
||||
/// use std::os::unix::net::UnixStream;
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// let socket = UnixStream::connect("/tmp/sock")?;
|
||||
/// socket.set_passcred(true).expect("Couldn't set passcred");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(any(
|
||||
doc,
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd"
|
||||
))]
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
|
||||
self.0.set_passcred(passcred)
|
||||
}
|
||||
|
||||
/// Get the current value of the socket for passing unix credentials in [`SocketAncillary`].
|
||||
/// This value can be change by [`set_passcred`].
|
||||
///
|
||||
/// Get the socket option `SO_PASSCRED`.
|
||||
///
|
||||
/// [`set_passcred`]: UnixStream::set_passcred
|
||||
#[cfg(any(
|
||||
doc,
|
||||
target_os = "android",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd"
|
||||
))]
|
||||
#[unstable(feature = "unix_socket_ancillary_data", issue = "76915")]
|
||||
pub fn passcred(&self) -> io::Result<bool> {
|
||||
self.0.passcred()
|
||||
}
|
||||
|
||||
/// Set the id of the socket for network filtering purpose
|
||||
///
|
||||
#[cfg_attr(
|
||||
@ -751,3 +693,10 @@ impl From<OwnedFd> for UnixStream {
|
||||
unsafe { Self::from_raw_fd(owned.into_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsInner<Socket> for UnixStream {
|
||||
#[inline]
|
||||
fn as_inner(&self) -> &Socket {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ use crate::thread;
|
||||
use crate::time::Duration;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use crate::os::android::net::SocketAddrExt;
|
||||
use crate::os::android::net::{SocketAddrExt, UnixSocketExt};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::os::linux::net::SocketAddrExt;
|
||||
use crate::os::linux::net::{SocketAddrExt, UnixSocketExt};
|
||||
|
||||
macro_rules! or_panic {
|
||||
($e:expr) => {
|
||||
|
@ -465,25 +465,31 @@ impl Socket {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
|
||||
setsockopt(self, 0 as libc::c_int, libc::LOCAL_CREDS, passcred as libc::c_int)
|
||||
pub fn set_local_creds(&self, local_creds: bool) -> io::Result<()> {
|
||||
setsockopt(self, 0 as libc::c_int, libc::LOCAL_CREDS, local_creds as libc::c_int)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
pub fn passcred(&self) -> io::Result<bool> {
|
||||
let passcred: libc::c_int = getsockopt(self, 0 as libc::c_int, libc::LOCAL_CREDS)?;
|
||||
Ok(passcred != 0)
|
||||
pub fn local_creds(&self) -> io::Result<bool> {
|
||||
let local_creds: libc::c_int = getsockopt(self, 0 as libc::c_int, libc::LOCAL_CREDS)?;
|
||||
Ok(local_creds != 0)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
|
||||
setsockopt(self, libc::AF_LOCAL, libc::LOCAL_CREDS_PERSISTENT, passcred as libc::c_int)
|
||||
pub fn set_local_creds_persistent(&self, local_creds_persistent: bool) -> io::Result<()> {
|
||||
setsockopt(
|
||||
self,
|
||||
libc::AF_LOCAL,
|
||||
libc::LOCAL_CREDS_PERSISTENT,
|
||||
local_creds_persistent as libc::c_int,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub fn passcred(&self) -> io::Result<bool> {
|
||||
let passcred: libc::c_int = getsockopt(self, libc::AF_LOCAL, libc::LOCAL_CREDS_PERSISTENT)?;
|
||||
Ok(passcred != 0)
|
||||
pub fn local_creds_persistent(&self) -> io::Result<bool> {
|
||||
let local_creds_persistent: libc::c_int =
|
||||
getsockopt(self, libc::AF_LOCAL, libc::LOCAL_CREDS_PERSISTENT)?;
|
||||
Ok(local_creds_persistent != 0)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "solaris", target_os = "illumos", target_os = "vita")))]
|
||||
|
Loading…
Reference in New Issue
Block a user