From 11ecf80a250c01061cfc69b313b45812c392d9d4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Nov 2020 16:32:21 +0100 Subject: [PATCH] feature(sync): add --data & --file-system (#1639) --- src/uu/sync/src/sync.rs | 102 ++++++++++++++++++++++++++++++++++--- tests/by-util/test_sync.rs | 30 ++++++++++- 2 files changed, 123 insertions(+), 9 deletions(-) diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index c1b6cb8ad..c94308cdb 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -13,20 +13,46 @@ extern crate libc; #[macro_use] extern crate uucore; -use clap::App; +use clap::{App, Arg}; +use std::path::Path; + +static EXIT_ERR: i32 = 1; + static ABOUT: &str = "Synchronize cached writes to persistent storage"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static OPT_FILE_SYSTEM: &str = "file-system"; +static OPT_DATA: &str = "data"; + +static ARG_FILES: &str = "files"; #[cfg(unix)] mod platform { use super::libc; - - extern "C" { - fn sync() -> libc::c_void; - } + use std::fs::File; + use std::os::unix::io::AsRawFd; pub unsafe fn do_sync() -> isize { - sync(); + libc::sync(); + 0 + } + + #[cfg(target_os = "linux")] + pub unsafe fn do_syncfs(files: Vec) -> isize { + for path in files { + let f = File::open(&path).unwrap(); + let fd = f.as_raw_fd(); + libc::syscall(libc::SYS_syncfs, fd); + } + 0 + } + + #[cfg(target_os = "linux")] + pub unsafe fn do_fdatasync(files: Vec) -> isize { + for path in files { + let f = File::open(&path).unwrap(); + let fd = f.as_raw_fd(); + libc::syscall(libc::SYS_fdatasync, fd); + } 0 } } @@ -42,6 +68,7 @@ mod platform { use std::fs::OpenOptions; use std::mem; use std::os::windows::prelude::*; + use std::path::Path; use uucore::wide::{FromWide, ToWide}; unsafe fn flush_volume(name: &str) { @@ -113,6 +140,21 @@ mod platform { } 0 } + + pub unsafe fn do_syncfs(files: Vec) -> isize { + for path in files { + flush_volume( + Path::new(&path) + .components() + .next() + .unwrap() + .as_os_str() + .to_str() + .unwrap(), + ); + } + 0 + } } fn get_usage() -> String { @@ -122,16 +164,60 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let _matches = App::new(executable!()) + let matches = App::new(executable!()) .version(VERSION) .about(ABOUT) .usage(&usage[..]) + .arg( + Arg::with_name(OPT_FILE_SYSTEM) + .short("f") + .long(OPT_FILE_SYSTEM) + .conflicts_with(OPT_DATA) + .help("sync the file systems that contain the files (Linux and Windows only)"), + ) + .arg( + Arg::with_name(OPT_DATA) + .short("d") + .long(OPT_DATA) + .conflicts_with(OPT_FILE_SYSTEM) + .help("sync only file data, no unneeded metadata (Linux only)"), + ) + .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) .get_matches_from(args); - sync(); + let files: Vec = matches + .values_of(ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + for f in &files { + if !Path::new(&f).exists() { + crash!(EXIT_ERR, "cannot stat '{}': No such file or directory", f); + } + } + + if matches.is_present(OPT_FILE_SYSTEM) { + #[cfg(any(target_os = "linux", target_os = "windows"))] + syncfs(files); + } else if matches.is_present(OPT_DATA) { + #[cfg(target_os = "linux")] + fdatasync(files); + } else { + sync(); + } 0 } fn sync() -> isize { unsafe { platform::do_sync() } } + +#[cfg(any(target_os = "linux", target_os = "windows"))] +fn syncfs(files: Vec) -> isize { + unsafe { platform::do_syncfs(files) } +} + +#[cfg(target_os = "linux")] +fn fdatasync(files: Vec) -> isize { + unsafe { platform::do_fdatasync(files) } +} diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs index e4eb72eac..138992ee4 100644 --- a/tests/by-util/test_sync.rs +++ b/tests/by-util/test_sync.rs @@ -1,11 +1,39 @@ use crate::common::util::*; +use std::fs; +extern crate tempfile; +use self::tempfile::tempdir; + #[test] fn test_sync_default() { - new_ucmd!().run(); + let result = new_ucmd!().run(); + assert!(result.success); } #[test] fn test_sync_incorrect_arg() { new_ucmd!().arg("--foo").fails(); } + +#[test] +fn test_sync_fs() { + let temporary_directory = tempdir().unwrap(); + let temporary_path = fs::canonicalize(temporary_directory.path()).unwrap(); + let result = new_ucmd!().arg("--file-system").arg(&temporary_path).run(); + assert!(result.success); +} + +#[test] +fn test_sync_data() { + // Todo add a second arg + let temporary_directory = tempdir().unwrap(); + let temporary_path = fs::canonicalize(temporary_directory.path()).unwrap(); + let result = new_ucmd!().arg("--data").arg(&temporary_path).run(); + assert!(result.success); +} + +#[test] +fn test_sync_no_existing_files() { + let result = new_ucmd!().arg("--data").arg("do-no-exist").fails(); + assert!(result.stderr.contains("error: cannot stat")); +}