Skip to main content

fxcp_core/consistency/
exchange.rs

1// SPDX-License-Identifier: GPL-2.0-or-later
2// Copyright (C) 2025 Joel Wirāmu Pauling <aenertia@aenertia.net>
3//
4//! Atomic exchange operations for safe file replacement.
5
6use std::path::Path;
7use std::fs::File;
8use std::os::unix::io::AsRawFd;
9use std::io;
10use libc;
11
12const XFS_IOC_EXCHANGE_RANGE: u64 = 0xC0385828;
13const XFS_EXCHANGE_RANGE_TO_EOF: u64 = 1 << 0;
14
15#[repr(C)]
16struct xfs_exchange_range {
17    file1_fd: i32,
18    pad: i32,
19    file1_offset: u64,
20    file2_offset: u64,
21    length: u64,
22    flags: u64,
23    pad2: [u64; 2],
24}
25
26/// Attempts to atomically exchange the contents of `temp` and `target` using XFS_IOC_EXCHANGE_RANGE.
27/// This preserves the inode number and hardlinks of `target`.
28pub fn atomic_exchange(temp: &Path, target: &Path) -> io::Result<()> {
29    let temp_file = File::open(temp)?;
30    let target_file = File::open(target)?;
31    
32    let args = xfs_exchange_range {
33        file1_fd: temp_file.as_raw_fd(),
34        pad: 0,
35        file1_offset: 0,
36        file2_offset: 0,
37        length: 0, // 0 with TO_EOF implies full file exchange
38        flags: XFS_EXCHANGE_RANGE_TO_EOF,
39        pad2: [0; 2],
40    };
41
42    // SAFETY: target_file is a valid open fd. args is a repr(C) struct
43    // matching the kernel's xfs_exchange_range layout, with file1_fd set
44    // to the temp file descriptor.
45    let ret = unsafe {
46        libc::ioctl(target_file.as_raw_fd(), XFS_IOC_EXCHANGE_RANGE, &args)
47    };
48
49    if ret == 0 {
50        Ok(())
51    } else {
52        Err(io::Error::last_os_error())
53    }
54}