mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-26 15:32:30 +00:00
libs/sys-util: implement reflink_copy()
Implement reflink_copy() to copy file by reflink, and fallback to normal file copy. Signed-off-by: Liu Jiang <gerry@linux.alibaba.com> Signed-off-by: Eryu Guan <eguan@linux.alibaba.com>
This commit is contained in:
parent
1d5c898d7f
commit
5300ea23ad
@ -5,11 +5,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::fs;
|
use std::fs::{self, File};
|
||||||
use std::io::Result;
|
use std::io::{Error, Result};
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use crate::eother;
|
use crate::{eother, sl};
|
||||||
|
|
||||||
// from linux.git/fs/fuse/inode.c: #define FUSE_SUPER_MAGIC 0x65735546
|
// from linux.git/fs/fuse/inode.c: #define FUSE_SUPER_MAGIC 0x65735546
|
||||||
const FUSE_SUPER_MAGIC: u32 = 0x65735546;
|
const FUSE_SUPER_MAGIC: u32 = 0x65735546;
|
||||||
@ -58,12 +60,114 @@ pub fn is_symlink<P: AsRef<Path>>(path: P) -> std::io::Result<bool> {
|
|||||||
Ok(meta.file_type().is_symlink())
|
Ok(meta.file_type().is_symlink())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reflink copy src to dst, and falls back to regular copy if reflink copy fails.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The `reflink_copy()` doesn't preserve permission/security context for the copied file,
|
||||||
|
/// so caller needs to take care of it.
|
||||||
|
pub fn reflink_copy<S: AsRef<Path>, D: AsRef<Path>>(src: S, dst: D) -> Result<()> {
|
||||||
|
let src_path = src.as_ref();
|
||||||
|
let dst_path = dst.as_ref();
|
||||||
|
let src = src_path.to_string_lossy();
|
||||||
|
let dst = dst_path.to_string_lossy();
|
||||||
|
|
||||||
|
if !src_path.is_file() {
|
||||||
|
return Err(eother!("reflink_copy src {} is not a regular file", src));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure dst's parent exist. If dst is a regular file, then unlink it for later copy.
|
||||||
|
if dst_path.exists() {
|
||||||
|
if !dst_path.is_file() {
|
||||||
|
return Err(eother!("reflink_copy dst {} is not a regular file", dst));
|
||||||
|
} else {
|
||||||
|
fs::remove_file(dst_path)?;
|
||||||
|
}
|
||||||
|
} else if let Some(dst_parent) = dst_path.parent() {
|
||||||
|
if !dst_parent.exists() {
|
||||||
|
if let Err(e) = fs::create_dir_all(dst_parent) {
|
||||||
|
return Err(eother!(
|
||||||
|
"reflink_copy: create_dir_all {} failed: {:?}",
|
||||||
|
dst_parent.to_str().unwrap(),
|
||||||
|
e
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if !dst_parent.is_dir() {
|
||||||
|
return Err(eother!("reflink_copy parent of {} is not a directory", dst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reflink copy, and fallback to regular copy if reflink fails.
|
||||||
|
let src_file = fs::File::open(src_path)?;
|
||||||
|
let dst_file = fs::File::create(dst_path)?;
|
||||||
|
if let Err(e) = do_reflink_copy(src_file, dst_file) {
|
||||||
|
match e.raw_os_error() {
|
||||||
|
// Cross dev copy or filesystem doesn't support reflink, do regular copy
|
||||||
|
Some(os_err)
|
||||||
|
if os_err == nix::Error::EXDEV as i32
|
||||||
|
|| os_err == nix::Error::EOPNOTSUPP as i32 =>
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
sl!(),
|
||||||
|
"reflink_copy: reflink is not supported ({:?}), do regular copy instead", e,
|
||||||
|
);
|
||||||
|
if let Err(e) = do_regular_copy(src.as_ref(), dst.as_ref()) {
|
||||||
|
return Err(eother!(
|
||||||
|
"reflink_copy: regular copy {} to {} failed: {:?}",
|
||||||
|
src,
|
||||||
|
dst,
|
||||||
|
e
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reflink copy failed
|
||||||
|
_ => {
|
||||||
|
return Err(eother!(
|
||||||
|
"reflink_copy: copy {} to {} failed: {:?}",
|
||||||
|
src,
|
||||||
|
dst,
|
||||||
|
e,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy file using cp command, which handles sparse file copy.
|
||||||
|
fn do_regular_copy(src: &str, dst: &str) -> Result<()> {
|
||||||
|
let mut cmd = Command::new("/bin/cp");
|
||||||
|
cmd.args(&["--sparse=auto", src, dst]);
|
||||||
|
|
||||||
|
match cmd.output() {
|
||||||
|
Ok(output) => match output.status.success() {
|
||||||
|
true => Ok(()),
|
||||||
|
false => Err(eother!("`{:?}` failed: {:?}", cmd, output)),
|
||||||
|
},
|
||||||
|
Err(e) => Err(eother!("`{:?}` failed: {:?}", cmd, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy file by reflink
|
||||||
|
fn do_reflink_copy(src: File, dst: File) -> Result<()> {
|
||||||
|
use nix::ioctl_write_int;
|
||||||
|
// FICLONE ioctl number definition, from include/linux/fs.h
|
||||||
|
const FS_IOC_MAGIC: u8 = 0x94;
|
||||||
|
const FS_IOC_FICLONE: u8 = 9;
|
||||||
|
// Define FICLONE ioctl using nix::ioctl_write_int! macro.
|
||||||
|
// The generated function has the following signature:
|
||||||
|
// pub unsafe fn ficlone(fd: libc::c_int, data: libc::c_ulang) -> Result<libc::c_int>
|
||||||
|
ioctl_write_int!(ficlone, FS_IOC_MAGIC, FS_IOC_FICLONE);
|
||||||
|
|
||||||
|
// Safe because the `src` and `dst` are valid file objects and we have checked the result.
|
||||||
|
unsafe { ficlone(dst.as_raw_fd(), src.as_raw_fd() as u64) }
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|e| Error::from_raw_os_error(e as i32))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mount::umount_all;
|
|
||||||
use std::process::Command;
|
|
||||||
use thiserror::private::PathAsDisplay;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_base_name() {
|
fn test_get_base_name() {
|
||||||
@ -84,28 +188,17 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_overlayfs() {
|
fn test_reflink_copy() {
|
||||||
let tmpdir1 = tempfile::tempdir().unwrap();
|
let tmpdir = tempfile::tempdir().unwrap();
|
||||||
let tmpdir2 = tempfile::tempdir().unwrap();
|
let path = tmpdir.path().join("mounts");
|
||||||
let tmpdir3 = tempfile::tempdir().unwrap();
|
reflink_copy("/proc/mounts", &path).unwrap();
|
||||||
let tmpdir4 = tempfile::tempdir().unwrap();
|
let content = fs::read_to_string(&path).unwrap();
|
||||||
|
assert!(!content.is_empty());
|
||||||
|
reflink_copy("/proc/mounts", &path).unwrap();
|
||||||
|
let content = fs::read_to_string(&path).unwrap();
|
||||||
|
assert!(!content.is_empty());
|
||||||
|
|
||||||
let option = format!(
|
reflink_copy("/proc/mounts", tmpdir.path()).unwrap_err();
|
||||||
"-o lowerdir={},upperdir={},workdir={}",
|
reflink_copy("/proc/mounts_not_exist", &path).unwrap_err();
|
||||||
tmpdir1.path().as_display(),
|
|
||||||
tmpdir2.path().display(),
|
|
||||||
tmpdir3.path().display()
|
|
||||||
);
|
|
||||||
let target = format!("{}", tmpdir4.path().display());
|
|
||||||
|
|
||||||
Command::new("/bin/mount")
|
|
||||||
.arg("-t overlay")
|
|
||||||
.arg(option)
|
|
||||||
.arg("overlay")
|
|
||||||
.arg(target)
|
|
||||||
.output()
|
|
||||||
.unwrap();
|
|
||||||
assert!(is_overlay_fs(tmpdir4.path()));
|
|
||||||
umount_all(tmpdir4.path(), false).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user