fxcp_core/consistency/
journal.rs1use std::path::{Path};
9use std::fs::File;
10use std::os::unix::io::AsRawFd;
11use std::io;
12use tracing::{debug, warn};
13use libc;
14use std::ffi::CString;
15use std::os::unix::ffi::OsStrExt;
16use std::process::Command;
17
18const XFS_IOC_EXCHANGE_RANGE: u64 = 0xC0385828;
19const XFS_EXCHANGE_RANGE_TO_EOF: u64 = 1 << 0;
20#[repr(C)]
21struct xfs_exchange_range {
22 file1_fd: i32,
23 pad: i32,
24 file1_offset: u64,
25 file2_offset: u64,
26 length: u64,
27 flags: u64,
28 pad2: [u64; 2],
29}
30pub fn atomic_commit(temp_path: &Path, target_path: &Path) -> io::Result<()> {
31 if let (Ok(temp_file), Ok(target_file)) = (File::open(temp_path), File::open(target_path)) {
32 let temp_fd = temp_file.as_raw_fd();
33 let target_fd = target_file.as_raw_fd();
34 let args = xfs_exchange_range {
35 file1_fd: temp_fd,
36 pad: 0,
37 file1_offset: 0,
38 file2_offset: 0,
39 length: 0,
40 flags: XFS_EXCHANGE_RANGE_TO_EOF,
41 pad2: [0; 2],
42 };
43 let ret = unsafe {
46 libc::ioctl(target_fd, XFS_IOC_EXCHANGE_RANGE, &args)
47 };
48 if ret == 0 {
49 debug!("Atomic Commit: XFS Exchange Range successful for {:?}", target_path);
50 let _ = std::fs::remove_file(temp_path);
51 return Ok(());
52 } else {
53 let err = io::Error::last_os_error();
54 match err.raw_os_error() {
55 Some(libc::EOPNOTSUPP) | Some(libc::ENOTTY) | Some(libc::EINVAL) => {
56 debug!("Atomic Commit: IOCTL failed ({}), falling back to rename.", err);
57 },
58 _ => {
59 warn!("Atomic Commit: Unexpected IOCTL error: {}. Falling back to rename.", err);
60 }
61 }
62 }
63 }
64 atomic_rename(temp_path, target_path, None)
65}
66pub fn atomic_rename(src: &Path, dst: &Path, flags: Option<u32>) -> io::Result<()> {
67 if let Some(parent) = dst.parent()
68 && !parent.exists() {
69 let _ = std::fs::create_dir_all(parent);
70 }
71 let flags = flags.unwrap_or(0);
72
73 if flags & (libc::RENAME_EXCHANGE | libc::RENAME_NOREPLACE) != 0 {
75 let src_c = CString::new(src.as_os_str().as_bytes())?;
76 let dst_c = CString::new(dst.as_os_str().as_bytes())?;
77 let ret = unsafe {
81 libc::renameat2(
82 libc::AT_FDCWD,
83 src_c.as_ptr(),
84 libc::AT_FDCWD,
85 dst_c.as_ptr(),
86 flags
87 )
88 };
89
90 if ret == 0 {
91 return Ok(());
92 } else {
93 let err = io::Error::last_os_error();
94 let errno = err.raw_os_error().unwrap_or(0);
95
96 if (flags & libc::RENAME_EXCHANGE) != 0 {
98 debug!("Atomic Commit: RENAME_EXCHANGE failed ({}), attempting userspace fallback.", err);
100
101 let temp_swap_path = dst.with_extension("exchange_bak");
103
104 let reflink_status = Command::new("cp")
106 .arg("--reflink=always")
107 .arg(dst)
108 .arg(&temp_swap_path)
109 .status();
110
111 let reflink_success = reflink_status.map(|s| s.success()).unwrap_or(false);
112
113 if reflink_success {
114 if let Err(e) = std::fs::rename(src, dst) {
117 let _ = std::fs::remove_file(&temp_swap_path);
119 return Err(e);
120 }
121 if let Err(e) = std::fs::rename(&temp_swap_path, src) {
123 warn!("Atomic Swap (Reflink): Failed to move temp back to src: {}. State inconsistent.", e);
124 return Err(e);
125 }
126 return Ok(());
127 } else {
128 warn!("Atomic Swap: Reflink fallback failed, attempting standard rename dance.");
130 if let Err(e) = std::fs::rename(dst, &temp_swap_path) {
132 if e.kind() == io::ErrorKind::NotFound {
133 } else {
135 return Err(e);
136 }
137 } else {
138 if let Err(e) = std::fs::rename(src, dst) {
140 if let Err(rollback_err) = std::fs::rename(&temp_swap_path, dst) {
142 tracing::error!(
143 path = %dst.display(),
144 temp = %temp_swap_path.display(),
145 err = %rollback_err,
146 "CRITICAL: journal atomic swap failed AND rollback failed — data may be lost"
147 );
148 }
149 return Err(e);
150 }
151 if let Err(e) = std::fs::rename(&temp_swap_path, src) {
153 warn!("Atomic Swap (Dance): Failed to move temp to src: {}.", e);
154 return Err(e);
155 }
156 return Ok(());
157 }
158 }
159 } else {
160 if (flags & libc::RENAME_NOREPLACE) != 0 {
162 if errno == libc::EEXIST {
164 return Err(err);
165 }
166 }
167
168 if errno == libc::EINVAL || errno == libc::EOPNOTSUPP || errno == libc::ENOSYS {
170 debug!("atomic_rename: Advanced flags ({}) not supported ({}). Falling back to standard rename.", flags, errno);
171 } else {
173 return Err(err);
175 }
176 }
177 }
178 }
179
180 std::fs::rename(src, dst)
182}