1use clap::{Arg, ArgAction, Command};
9#[cfg(any(target_os = "linux", target_os = "android"))]
10use nix::errno::Errno;
11#[cfg(any(target_os = "linux", target_os = "android"))]
12use nix::fcntl::{OFlag, open};
13#[cfg(any(target_os = "linux", target_os = "android"))]
14use nix::sys::stat::Mode;
15use std::path::Path;
16use uucore::display::Quotable;
17use uucore::error::{UResult, USimpleError, get_exit_code, set_exit_code};
18use uucore::format_usage;
19use uucore::show_error;
20use uucore::translate;
21
22pub mod options {
23 pub static FILE_SYSTEM: &str = "file-system";
24 pub static DATA: &str = "data";
25}
26
27static ARG_FILES: &str = "files";
28
29#[cfg(unix)]
30mod platform {
31 #[cfg(any(target_os = "linux", target_os = "android"))]
32 use nix::fcntl::{FcntlArg, OFlag, fcntl};
33 use nix::unistd::sync;
34 #[cfg(any(target_os = "linux", target_os = "android"))]
35 use nix::unistd::{fdatasync, syncfs};
36 #[cfg(any(target_os = "linux", target_os = "android"))]
37 use std::fs::{File, OpenOptions};
38 #[cfg(any(target_os = "linux", target_os = "android"))]
39 use std::os::unix::fs::OpenOptionsExt;
40 #[cfg(any(target_os = "linux", target_os = "android"))]
41 use uucore::display::Quotable;
42 #[cfg(any(target_os = "linux", target_os = "android"))]
43 use uucore::error::FromIo;
44 #[cfg(any(target_os = "linux", target_os = "android"))]
45 use uucore::translate;
46
47 use uucore::error::UResult;
48
49 #[expect(
50 clippy::unnecessary_wraps,
51 reason = "fn sig must match on all platforms"
52 )]
53 pub fn do_sync() -> UResult<()> {
54 sync();
55 Ok(())
56 }
57
58 #[cfg(any(target_os = "linux", target_os = "android"))]
62 fn open_and_reset_nonblock(path: &str) -> UResult<File> {
63 let f = OpenOptions::new()
64 .read(true)
65 .custom_flags(OFlag::O_NONBLOCK.bits())
66 .open(path)
67 .map_err_context(|| path.to_string())?;
68 if let Err(e) = fcntl(&f, FcntlArg::F_SETFL(OFlag::empty())) {
71 eprintln!(
72 "sync: {}",
73 translate!("sync-warning-fcntl-failed", "file" => path, "error" => e.to_string())
74 );
75 }
76 Ok(f)
77 }
78
79 #[cfg(any(target_os = "linux", target_os = "android"))]
80 pub fn do_syncfs(files: Vec<String>) -> UResult<()> {
81 for path in files {
82 let f = open_and_reset_nonblock(&path)?;
83 syncfs(f).map_err_context(
84 || translate!("sync-error-syncing-file", "file" => path.quote()),
85 )?;
86 }
87 Ok(())
88 }
89
90 #[cfg(any(target_os = "linux", target_os = "android"))]
91 pub fn do_fdatasync(files: Vec<String>) -> UResult<()> {
92 for path in files {
93 let f = open_and_reset_nonblock(&path)?;
94 fdatasync(f).map_err_context(
95 || translate!("sync-error-syncing-file", "file" => path.quote()),
96 )?;
97 }
98 Ok(())
99 }
100}
101
102#[cfg(windows)]
103mod platform {
104 use std::fs::OpenOptions;
105 use std::os::windows::prelude::*;
106 use std::path::Path;
107 use uucore::error::{UResult, USimpleError};
108 use uucore::translate;
109 use uucore::wide::{FromWide, ToWide};
110 use windows_sys::Win32::Foundation::{
111 ERROR_NO_MORE_FILES, GetLastError, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH,
112 };
113 use windows_sys::Win32::Storage::FileSystem::{
114 FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, FlushFileBuffers, GetDriveTypeW,
115 };
116 use windows_sys::Win32::System::WindowsProgramming::DRIVE_FIXED;
117
118 fn get_last_error() -> u32 {
119 unsafe { GetLastError() as u32 }
121 }
122
123 fn flush_volume(name: &str) -> UResult<()> {
124 let name_wide = name.to_wide_null();
125 if unsafe { GetDriveTypeW(name_wide.as_ptr()) } == DRIVE_FIXED {
127 let sliced_name = &name[..name.len() - 1]; match OpenOptions::new().write(true).open(sliced_name) {
129 Ok(file) => {
130 if unsafe { FlushFileBuffers(file.as_raw_handle() as HANDLE) } == 0 {
132 Err(USimpleError::new(
133 get_last_error() as i32,
134 translate!("sync-error-flush-file-buffer"),
135 ))
136 } else {
137 Ok(())
138 }
139 }
140 Err(e) => Err(USimpleError::new(
141 e.raw_os_error().unwrap_or(1),
142 translate!("sync-error-create-volume-handle"),
143 )),
144 }
145 } else {
146 Ok(())
147 }
148 }
149
150 fn find_first_volume() -> UResult<(String, HANDLE)> {
151 let mut name: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
152 let handle = unsafe { FindFirstVolumeW(name.as_mut_ptr(), name.len() as u32) };
154 if handle == INVALID_HANDLE_VALUE {
155 return Err(USimpleError::new(
156 get_last_error() as i32,
157 translate!("sync-error-find-first-volume"),
158 ));
159 }
160 Ok((String::from_wide_null(&name), handle))
161 }
162
163 fn find_all_volumes() -> UResult<Vec<String>> {
164 let (first_volume, next_volume_handle) = find_first_volume()?;
165 let mut volumes = vec![first_volume];
166 loop {
167 let mut name: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
168 if unsafe { FindNextVolumeW(next_volume_handle, name.as_mut_ptr(), name.len() as u32) }
171 == 0
172 {
173 return match get_last_error() {
174 ERROR_NO_MORE_FILES => {
175 unsafe { FindVolumeClose(next_volume_handle) };
177 Ok(volumes)
178 }
179 err => Err(USimpleError::new(
180 err as i32,
181 translate!("sync-error-find-next-volume"),
182 )),
183 };
184 }
185 volumes.push(String::from_wide_null(&name));
186 }
187 }
188
189 pub fn do_sync() -> UResult<()> {
190 let volumes = find_all_volumes()?;
191 for vol in &volumes {
192 flush_volume(vol)?;
193 }
194 Ok(())
195 }
196
197 pub fn do_syncfs(files: Vec<String>) -> UResult<()> {
198 for path in files {
199 let maybe_first = Path::new(&path).components().next();
200 let vol_name = match maybe_first {
201 Some(c) => c.as_os_str().to_string_lossy().into_owned(),
202 None => {
203 return Err(USimpleError::new(
204 1,
205 translate!("sync-error-no-such-file", "file" => path),
206 ));
207 }
208 };
209 flush_volume(&vol_name)?;
210 }
211 Ok(())
212 }
213}
214
215#[uucore::main]
216pub fn uumain(args: impl uucore::Args) -> UResult<()> {
217 let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
218 let files: Vec<String> = matches
219 .get_many::<String>(ARG_FILES)
220 .map(|v| v.map(ToString::to_string).collect())
221 .unwrap_or_default();
222
223 if matches.get_flag(options::DATA) && files.is_empty() {
224 return Err(USimpleError::new(
225 1,
226 translate!("sync-error-data-needs-argument"),
227 ));
228 }
229
230 for f in &files {
231 #[cfg(any(target_os = "linux", target_os = "android"))]
233 {
234 let path = Path::new(&f);
235 if let Err(e) = open(path, OFlag::O_NONBLOCK, Mode::empty()) {
236 if e != Errno::EACCES || (e == Errno::EACCES && path.is_dir()) {
237 show_error!(
238 "{}",
239 translate!("sync-error-opening-file", "file" => f.quote(), "err" => e.desc())
240 );
241 set_exit_code(1);
242 }
243 }
244 }
245 #[cfg(not(any(target_os = "linux", target_os = "android")))]
246 {
247 if !Path::new(&f).exists() {
248 show_error!(
249 "{}",
250 translate!("sync-error-no-such-file", "file" => f.quote())
251 );
252 set_exit_code(1);
253 }
254 }
255 }
256
257 if get_exit_code() != 0 {
258 return Err(USimpleError::new(1, ""));
259 }
260
261 #[allow(clippy::if_same_then_else)]
262 if matches.get_flag(options::FILE_SYSTEM) {
263 #[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
264 syncfs(files)?;
265 } else if matches.get_flag(options::DATA) {
266 #[cfg(any(target_os = "linux", target_os = "android"))]
267 fdatasync(files)?;
268 } else {
269 sync()?;
270 }
271 Ok(())
272}
273
274pub fn uu_app() -> Command {
275 Command::new(uucore::util_name())
276 .version(uucore::crate_version!())
277 .help_template(uucore::localized_help_template(uucore::util_name()))
278 .about(translate!("sync-about"))
279 .override_usage(format_usage(&translate!("sync-usage")))
280 .infer_long_args(true)
281 .arg(
282 Arg::new(options::FILE_SYSTEM)
283 .short('f')
284 .long(options::FILE_SYSTEM)
285 .conflicts_with(options::DATA)
286 .help(translate!("sync-help-file-system"))
287 .action(ArgAction::SetTrue),
288 )
289 .arg(
290 Arg::new(options::DATA)
291 .short('d')
292 .long(options::DATA)
293 .conflicts_with(options::FILE_SYSTEM)
294 .help(translate!("sync-help-data"))
295 .action(ArgAction::SetTrue),
296 )
297 .arg(
298 Arg::new(ARG_FILES)
299 .action(ArgAction::Append)
300 .value_hint(clap::ValueHint::AnyPath),
301 )
302}
303
304fn sync() -> UResult<()> {
305 platform::do_sync()
306}
307
308#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
309fn syncfs(files: Vec<String>) -> UResult<()> {
310 platform::do_syncfs(files)
311}
312
313#[cfg(any(target_os = "linux", target_os = "android"))]
314fn fdatasync(files: Vec<String>) -> UResult<()> {
315 platform::do_fdatasync(files)
316}