//
// Syd: rock-solid application kernel
// src/kernel/link.rs: symlink(2) and symlinkat(2) handlers
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::os::fd::AsFd;

use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, NixPath};

use crate::{
    cookie::safe_symlinkat,
    kernel::sandbox_path,
    lookup::FsFlags,
    req::{RemoteProcess, SysArg, UNotifyEventRequest},
    sandbox::Capability,
};

pub(crate) fn sys_symlink(request: UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |request: UNotifyEventRequest| {
        // SAFETY: No checking of the target is done.
        // This is consistent with the system call.
        let arg = SysArg {
            path: Some(1),
            dotlast: Some(Errno::EINVAL),
            fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::MISS_LAST,
            ..Default::default()
        };
        syscall_symlink_handler(request, arg)
    })
}

pub(crate) fn sys_symlinkat(request: UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |request: UNotifyEventRequest| {
        // SAFETY: No checking of the target is done.
        // This is consistent with the system call.
        let arg = SysArg {
            dirfd: Some(1),
            path: Some(2),
            dotlast: Some(Errno::EINVAL),
            fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::MISS_LAST,
            ..Default::default()
        };
        syscall_symlink_handler(request, arg)
    })
}

/// A helper function to handle symlink{,at} syscalls.
fn syscall_symlink_handler(
    request: UNotifyEventRequest,
    arg: SysArg,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.scmpreq;

    let process = RemoteProcess::new(request.scmpreq.pid());

    // Read remote path, request will be validated by remote_path.
    let target = process.remote_path(req.data.arch, req.data.args[0], Some(&request))?;

    // symlink() returns ENOENT if target is an empty string.
    if target.is_empty() {
        return Err(Errno::ENOENT);
    }

    // Read remote path.
    let sandbox = request.get_sandbox();
    let (path, _, _) = request.read_path(&sandbox, arg)?;

    // Check for access.
    let hide = sandbox.enabled(Capability::CAP_STAT);
    let name = if arg.dirfd.is_some() {
        "symlinkat"
    } else {
        "symlink"
    };
    sandbox_path(
        Some(&request),
        &sandbox,
        request.scmpreq.pid(), // Unused when request.is_some()
        path.abs(),
        Capability::CAP_SYMLINK,
        hide,
        name,
    )?;
    drop(sandbox); // release the read-lock.

    // SAFETY: Path hiding is done:
    // Now it is safe to return EEXIST if linkpath exists.
    if path.base().is_empty() {
        return Err(Errno::EEXIST);
    }

    // All done, call underlying system call.
    safe_symlinkat(
        &target,
        path.dir.as_ref().map(|fd| fd.as_fd()).ok_or(Errno::EBADF)?,
        path.base(),
    )
    .map(|_| request.return_syscall(0))
}
