syd 3.52.0

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

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

use crate::{
    compat::RenameFlags,
    cookie::safe_renameat2,
    kernel::{syscall_path_handler, to_renameflags},
    lookup::FsFlags,
    req::{PathArgs, SysArg, UNotifyEventRequest},
};

pub(crate) fn sys_rename(request: UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[
        SysArg {
            path: Some(0),
            fsflags: FsFlags::MUST_PATH
                | FsFlags::NO_FOLLOW_LAST
                | FsFlags::WANT_BASE
                | FsFlags::DOTLAST_EBUSY,
            ..Default::default()
        },
        SysArg {
            path: Some(1),
            fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE | FsFlags::DOTLAST_EBUSY,
            ..Default::default()
        },
    ];

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

        syscall_rename_handler(request, path_args, RenameFlags::empty())
    })
}

pub(crate) fn sys_renameat(request: UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[
        SysArg {
            dirfd: Some(0),
            path: Some(1),
            fsflags: FsFlags::MUST_PATH
                | FsFlags::NO_FOLLOW_LAST
                | FsFlags::WANT_BASE
                | FsFlags::DOTLAST_EBUSY,
            ..Default::default()
        },
        SysArg {
            dirfd: Some(2),
            path: Some(3),
            fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE | FsFlags::DOTLAST_EBUSY,
            ..Default::default()
        },
    ];

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

        syscall_rename_handler(request, path_args, RenameFlags::empty())
    })
}

pub(crate) fn sys_renameat2(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // Reject undefined/invalid flags.
    let flags = match to_renameflags(req.data.args[4]) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // EXCHANGE and NOREPLACE flags are mutually exclusive.
    let mut fsflags_new = FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE | FsFlags::DOTLAST_EBUSY;
    if flags.contains(RenameFlags::RENAME_EXCHANGE) {
        fsflags_new.insert(FsFlags::MUST_PATH);
    } else if flags.contains(RenameFlags::RENAME_NOREPLACE) {
        fsflags_new.insert(FsFlags::MISS_LAST);
    }

    let argv = &[
        SysArg {
            dirfd: Some(0),
            path: Some(1),
            fsflags: FsFlags::MUST_PATH
                | FsFlags::NO_FOLLOW_LAST
                | FsFlags::WANT_BASE
                | FsFlags::DOTLAST_EBUSY,
            ..Default::default()
        },
        SysArg {
            dirfd: Some(2),
            path: Some(3),
            fsflags: fsflags_new,
            ..Default::default()
        },
    ];

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

        syscall_rename_handler(request, path_args, flags)
    })
}

// A helper function to handle rename and renameat syscalls.
fn syscall_rename_handler(
    request: &UNotifyEventRequest,
    args: PathArgs,
    flags: RenameFlags,
) -> Result<ScmpNotifResp, Errno> {
    // SysArg has two elements.
    #[expect(clippy::disallowed_methods)]
    let old_path = &args.0.as_ref().unwrap().path;
    #[expect(clippy::disallowed_methods)]
    let new_path = &args.1.as_ref().unwrap().path;

    // Linux rejects rootfs with EBUSY.
    if old_path.base().is_empty() || new_path.base().is_empty() {
        return Err(Errno::EBUSY);
    }

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

    // All done, call the underlying system call.
    let result = safe_renameat2(
        old_path.dir(),
        old_path.base(),
        new_path.dir(),
        new_path.base(),
        flags,
    );

    // Remove invalidation record.
    request.cache.del_sys_block(req.id)?;

    result.map(|_| request.return_syscall(0))
}