// Syd: rock-solid application kernel
// src/kernel/net/sendto.rs: sendto(2) handler
//
// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::os::fd::{AsRawFd, OwnedFd};

use libseccomp::ScmpNotifResp;
use nix::{
    errno::Errno,
    sys::socket::{send, sendto, SockaddrStorage},
};

use crate::{
    compat::MsgFlags,
    config::MAX_RW_COUNT,
    fd::{get_nonblock, has_recv_timeout},
    kernel::net::to_msgflags,
    req::UNotifyEventRequest,
};

pub(crate) fn handle_sendto(
    fd: OwnedFd,
    args: &[u64; 6],
    request: &UNotifyEventRequest,
    addr: Option<SockaddrStorage>,
    restrict_oob: bool,
) -> Result<ScmpNotifResp, Errno> {
    // SAFETY: Reject undefined/invalid flags.
    let flags = to_msgflags(args[3])?;

    // SAFETY: Reject MSG_OOB as necessary.
    if restrict_oob && flags.contains(MsgFlags::MSG_OOB) {
        // Signal no support to let the sandbox process
        // handle the error gracefully. This is consistent
        // with the Linux kernel.
        return Err(Errno::EOPNOTSUPP);
    }

    // SAFETY:
    // 1. The length argument to the sendto call
    //    must not be fully trusted, it can be overly large,
    //    and allocating a Vector of that capacity may overflow.
    // 2. It is valid for the length to be zero to send an empty message.
    // 3. Buffer read from sandbox process MUST be zeroized on drop.
    let len = usize::try_from(args[2])
        .or(Err(Errno::EINVAL))?
        .min(*MAX_RW_COUNT); // Cap count at MAX_RW_COUNT.

    // read_vec_zeroed returns an empty Vec with len==0,
    // without performing any memory reads.
    let buf = request.read_vec_zeroed(args[1], len)?;

    // SAFETY: Record blocking call so it can get invalidated.
    let req = request.scmpreq;
    let is_blocking = if !flags.contains(MsgFlags::MSG_DONTWAIT) && !get_nonblock(&fd)? {
        let ignore_restart = has_recv_timeout(&fd)?;

        // Record the blocking call.
        request.cache.add_sys_block(req, ignore_restart)?;

        true
    } else {
        false
    };

    let result = if let Some(addr) = addr {
        // Connection-less socket.
        sendto(fd.as_raw_fd(), &buf, &addr, flags.into())
    } else {
        // Connection mode socket, no address specified.
        send(fd.as_raw_fd(), &buf, flags.into())
    };

    // Remove invalidation record unless interrupted.
    if is_blocking {
        request
            .cache
            .del_sys_block(req.id, matches!(result, Err(Errno::EINTR)))?;
    }

    // Send SIGPIPE for EPIPE unless MSG_NOSIGNAL is set.
    #[expect(clippy::cast_possible_wrap)]
    Ok(match result {
        Ok(n) => request.return_syscall(n as i64),
        Err(Errno::EPIPE) if !flags.contains(MsgFlags::MSG_NOSIGNAL) => {
            request.pidfd_kill(libc::SIGPIPE)?;
            request.fail_syscall(Errno::EPIPE)
        }
        Err(errno) => request.fail_syscall(errno),
    })
}
