blob: 66fe5d8a44a40e91b331c3e21f159e8717d1b13c [file] [log] [blame]
use std::cell::Cell;
use std::net::{TcpListener, TcpStream};
use rustc_const_eval::interpret::{InterpResult, interp_ok};
use rustc_middle::throw_unsup_format;
use rustc_target::spec::Os;
use crate::shims::files::{FdId, FileDescription};
use crate::{OpTy, Scalar, *};
#[derive(Debug, PartialEq)]
enum SocketFamily {
// IPv4 internet protocols
IPv4,
// IPv6 internet protocols
IPv6,
}
#[derive(Debug)]
enum SocketType {
/// Reliable full-duplex communication, based on connections.
Stream,
}
#[allow(unused)]
#[derive(Debug)]
enum SocketKind {
TcpListener(TcpListener),
TcpStream(TcpStream),
}
#[allow(unused)]
#[derive(Debug)]
struct Socket {
/// Family of the socket, used to ensure socket only binds/connects to address of
/// same family.
family: SocketFamily,
/// Type of the socket, either datagram or stream.
/// Only stream is supported at the moment!
socket_type: SocketType,
/// Whether this fd is non-blocking or not.
is_non_block: Cell<bool>,
}
impl FileDescription for Socket {
fn name(&self) -> &'static str {
"socket"
}
fn destroy<'tcx>(
self,
_self_id: FdId,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, std::io::Result<()>>
where
Self: Sized,
{
interp_ok(Ok(()))
}
fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
let mut flags = ecx.eval_libc_i32("O_RDWR");
if self.is_non_block.get() {
flags |= ecx.eval_libc_i32("O_NONBLOCK");
}
interp_ok(Scalar::from_i32(flags))
}
fn set_flags<'tcx>(
&self,
mut _flag: i32,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, Scalar> {
throw_unsup_format!("fcntl: socket flags aren't supported")
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// For more information on the arguments see the socket manpage:
/// <https://linux.die.net/man/2/socket>
fn socket(
&mut self,
domain: &OpTy<'tcx>,
type_: &OpTy<'tcx>,
protocol: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let domain = this.read_scalar(domain)?.to_i32()?;
let mut flags = this.read_scalar(type_)?.to_i32()?;
let protocol = this.read_scalar(protocol)?.to_i32()?;
// Reject if isolation is enabled
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`socket`", reject_with)?;
this.set_last_error(LibcError("EACCES"))?;
return interp_ok(Scalar::from_i32(-1));
}
let mut is_sock_nonblock = false;
// Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
// if there is anything left at the end, that's an unsupported flag.
if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd) {
// SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android and FreeBSD.
let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");
if flags & sock_nonblock == sock_nonblock {
is_sock_nonblock = true;
flags &= !sock_nonblock;
}
if flags & sock_cloexec == sock_cloexec {
// We don't support `exec` so we can ignore this.
flags &= !sock_cloexec;
}
}
let family = if domain == this.eval_libc_i32("AF_INET") {
SocketFamily::IPv4
} else if domain == this.eval_libc_i32("AF_INET6") {
SocketFamily::IPv6
} else {
throw_unsup_format!(
"socket: domain {:#x} is unsupported, only AF_INET and \
AF_INET6 are allowed.",
domain
);
};
if flags != this.eval_libc_i32("SOCK_STREAM") {
throw_unsup_format!(
"socket: type {:#x} is unsupported, only SOCK_STREAM, \
SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
flags
);
}
if protocol != 0 {
throw_unsup_format!(
"socket: socket protocol {protocol} is unsupported, \
only 0 is allowed"
);
}
let fds = &mut this.machine.fds;
let fd = fds.new_ref(Socket {
family,
is_non_block: Cell::new(is_sock_nonblock),
socket_type: SocketType::Stream,
});
interp_ok(Scalar::from_i32(fds.insert(fd)))
}
}