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

use std::os::fd::AsRawFd;

use libseccomp::ScmpNotifResp;
use nix::errno::Errno;

use crate::{
    confine::is_valid_ptr,
    fd::is_valid_fd,
    kernel::syscall_path_handler,
    lookup::FsFlags,
    req::{SysArg, UNotifyEventRequest},
};

pub(crate) fn sys_statfs(request: UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: If second argument is not a valid pointer, return EFAULT.
    let req = request.scmpreq;
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(request, "statfs", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        // SAFETY:
        // 1. SysArg has one element.
        // 2. `/` is not permitted -> EACCES.
        #[expect(clippy::disallowed_methods)]
        let fd = path_args
            .0
            .as_ref()
            .unwrap()
            .dir
            .as_ref()
            .ok_or(Errno::EACCES)?;

        const SIZ: usize = size_of::<libc::statfs>();
        let mut buf: Vec<u8> = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        let ptr: *mut libc::statfs = buf.as_mut_ptr().cast();

        // SAFETY: Record blocking call so it can get invalidated.
        request.cache.add_sys_block(req, false)?;

        let result =
                // SAFETY: Libc version may call fstatfs64 behind our back!
                Errno::result(unsafe { libc::syscall(crate::compat::SYS_fstatfs, fd.as_raw_fd(), ptr) });

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

        result?;
        request.write_mem(&buf, req.data.args[1])?;
        Ok(request.return_syscall(0))
    })
}

pub(crate) fn sys_statfs64(request: UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY:
    // If second argument is not a valid size, return EINVAL.
    // If third argument is not a valid pointer, return EFAULT.
    const SIZ: usize = size_of::<libc::statfs64>();
    let req = request.scmpreq;
    let siz = match usize::try_from(req.data.args[1]) {
        Ok(siz) if siz != SIZ => return request.fail_syscall(Errno::EINVAL),
        Ok(siz) => siz,
        Err(_) => return request.fail_syscall(Errno::EINVAL),
    };
    if !is_valid_ptr(req.data.args[2], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(request, "statfs64", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        // SAFETY:
        // 1. SysArg has one element.
        // 2. `/` is not permitted -> EACCES.
        #[expect(clippy::disallowed_methods)]
        let fd = path_args
            .0
            .as_ref()
            .unwrap()
            .dir
            .as_ref()
            .ok_or(Errno::EACCES)?;

        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        let ptr = buf.as_mut_ptr().cast();

        // SAFETY: Record blocking call so it can get invalidated.
        request.cache.add_sys_block(req, false)?;

        // SAFETY: In libc we trust.
        let result = Errno::result(unsafe { libc::fstatfs64(fd.as_raw_fd(), ptr) });

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

        result?;
        let n = buf.len().min(siz);
        request.write_mem(&buf[..n], req.data.args[2])?;
        Ok(request.return_syscall(0))
    })
}

pub(crate) fn sys_fstatfs(request: UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY:
    // FD-only call:
    //   1. Assert valid fd before other arguments.
    //   2. AT_FDCWD is an invalid fd argument.
    // If second argument is not a valid pointer, return EFAULT.
    let req = request.scmpreq;
    if !is_valid_fd(req.data.args[0]) {
        return request.fail_syscall(Errno::EBADF);
    } else if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        ..Default::default()
    }];

    syscall_path_handler(request, "fstatfs", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        // SAFETY:
        // 1. SysArg has one element.
        // 2. SysArg.path is None asserting dir is Some.
        #[expect(clippy::disallowed_methods)]
        let fd = path_args.0.as_ref().unwrap().dir.as_ref().unwrap();

        const SIZ: usize = size_of::<libc::statfs>();
        let mut buf: Vec<u8> = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        let ptr: *mut libc::statfs = buf.as_mut_ptr().cast();

        // SAFETY: Record blocking call so it can get invalidated.
        request.cache.add_sys_block(req, false)?;

        let result =
            // SAFETY: Libc version may call fstatfs64 behind our back!
            Errno::result(unsafe { libc::syscall(crate::compat::SYS_fstatfs, fd.as_raw_fd(), ptr) });

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

        result?;
        request.write_mem(&buf, req.data.args[1])?;
        Ok(request.return_syscall(0))
    })
}

pub(crate) fn sys_fstatfs64(request: UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY:
    // FD-only call:
    //   1. Assert valid fd before other arguments.
    //   2. AT_FDCWD is an invalid fd argument.
    // If second argument is not a valid size, return EINVAL.
    // If third argument is not a valid pointer, return EFAULT.
    const SIZ: usize = size_of::<libc::statfs64>();
    let req = request.scmpreq;
    let siz = match usize::try_from(req.data.args[1]) {
        Ok(siz) if siz != SIZ => return request.fail_syscall(Errno::EINVAL),
        Ok(siz) => siz,
        Err(_) => return request.fail_syscall(Errno::EINVAL),
    };
    if !is_valid_fd(req.data.args[0]) {
        return request.fail_syscall(Errno::EBADF);
    }
    if !is_valid_ptr(req.data.args[2], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        ..Default::default()
    }];

    syscall_path_handler(request, "fstatfs64", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        // SAFETY:
        // 1. SysArg has one element.
        // 2. SysArg.path is None asserting dir is Some.
        #[expect(clippy::disallowed_methods)]
        let fd = path_args.0.as_ref().unwrap().dir.as_ref().unwrap();

        let mut buf = Vec::new();
        buf.try_reserve(SIZ).or(Err(Errno::ENOMEM))?;
        buf.resize(SIZ, 0);
        let ptr = buf.as_mut_ptr().cast();

        // SAFETY: Record blocking call so it can get invalidated.
        request.cache.add_sys_block(req, false)?;

        // SAFETY: In libc we trust.
        let result = Errno::result(unsafe { libc::fstatfs64(fd.as_raw_fd(), ptr) });

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

        result?;
        let n = buf.len().min(siz);
        request.write_mem(&buf[..n], req.data.args[2])?;
        Ok(request.return_syscall(0))
    })
}
