mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-05-01 21:24:36 +00:00
watchers: don't dereference symlinks when copying files
The current implementation just copies the file, dereferencing any simlinks in the process. This results in symlinks no being preserved, and a change in layout relative to the mount that we are making watchable. What we want is something like "cp -d" This isn't available in a crate, so let's go ahead and introduce a copy function which will create a symlink with same relative path if the source file is a symlink. Regular files are handled with the standard fs::copy. Introduce a unit test to verify symlinks are now handled appropriately. Fixes: #2950 Signed-off-by: Eric Ernst <eric_ernst@apple.com>
This commit is contained in:
parent
c01189d4a6
commit
2b6dfe414a
@ -79,6 +79,16 @@ impl Drop for Storage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
|
||||||
|
// if source is a symlink, just create new symlink with same link source
|
||||||
|
if fs::symlink_metadata(&from).await?.file_type().is_symlink() {
|
||||||
|
fs::symlink(fs::read_link(&from).await?, to).await?;
|
||||||
|
} else {
|
||||||
|
fs::copy(from, to).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl Storage {
|
impl Storage {
|
||||||
async fn new(storage: protos::Storage) -> Result<Storage> {
|
async fn new(storage: protos::Storage) -> Result<Storage> {
|
||||||
let entry = Storage {
|
let entry = Storage {
|
||||||
@ -110,19 +120,13 @@ impl Storage {
|
|||||||
dest_file_path
|
dest_file_path
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(
|
copy(&source_file_path, &dest_file_path)
|
||||||
logger,
|
|
||||||
"Copy from {} to {}",
|
|
||||||
source_file_path.display(),
|
|
||||||
dest_file_path.display()
|
|
||||||
);
|
|
||||||
fs::copy(&source_file_path, &dest_file_path)
|
|
||||||
.await
|
.await
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Copy from {} to {} failed",
|
"Copy from {} to {} failed",
|
||||||
source_file_path.display(),
|
source_file_path.display(),
|
||||||
dest_file_path.display()
|
dest_file_path.display(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -843,6 +847,95 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_copy() {
|
||||||
|
// prepare tmp src/destination
|
||||||
|
let source_dir = tempfile::tempdir().unwrap();
|
||||||
|
let dest_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
// verify copy of a regular file
|
||||||
|
let src_file = source_dir.path().join("file.txt");
|
||||||
|
let dst_file = dest_dir.path().join("file.txt");
|
||||||
|
fs::write(&src_file, "foo").unwrap();
|
||||||
|
copy(&src_file, &dst_file).await.unwrap();
|
||||||
|
// verify destination:
|
||||||
|
assert!(!fs::symlink_metadata(dst_file)
|
||||||
|
.unwrap()
|
||||||
|
.file_type()
|
||||||
|
.is_symlink());
|
||||||
|
|
||||||
|
// verify copy of a symlink
|
||||||
|
let src_symlink_file = source_dir.path().join("symlink_file.txt");
|
||||||
|
let dst_symlink_file = dest_dir.path().join("symlink_file.txt");
|
||||||
|
tokio::fs::symlink(&src_file, &src_symlink_file)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
copy(src_symlink_file, &dst_symlink_file).await.unwrap();
|
||||||
|
// verify destination:
|
||||||
|
assert!(fs::symlink_metadata(&dst_symlink_file)
|
||||||
|
.unwrap()
|
||||||
|
.file_type()
|
||||||
|
.is_symlink());
|
||||||
|
assert_eq!(fs::read_link(&dst_symlink_file).unwrap(), src_file);
|
||||||
|
assert_eq!(fs::read_to_string(&dst_symlink_file).unwrap(), "foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn watch_directory_with_symlinks() {
|
||||||
|
// Prepare source directory:
|
||||||
|
// ./tmp/.data/file.txt
|
||||||
|
// ./tmp/1.txt -> ./tmp/.data/file.txt
|
||||||
|
let source_dir = tempfile::tempdir().unwrap();
|
||||||
|
fs::create_dir_all(source_dir.path().join(".data")).unwrap();
|
||||||
|
fs::write(source_dir.path().join(".data/file.txt"), "two").unwrap();
|
||||||
|
tokio::fs::symlink(
|
||||||
|
source_dir.path().join(".data/file.txt"),
|
||||||
|
source_dir.path().join("1.txt"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let dest_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
let mut entry = Storage::new(protos::Storage {
|
||||||
|
source: source_dir.path().display().to_string(),
|
||||||
|
mount_point: dest_dir.path().display().to_string(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let logger = slog::Logger::root(slog::Discard, o!());
|
||||||
|
|
||||||
|
assert_eq!(entry.scan(&logger).await.unwrap(), 2);
|
||||||
|
|
||||||
|
// Should copy no files since nothing is changed since last check
|
||||||
|
assert_eq!(entry.scan(&logger).await.unwrap(), 0);
|
||||||
|
|
||||||
|
// Should copy 1 file
|
||||||
|
thread::sleep(Duration::from_secs(1));
|
||||||
|
fs::write(source_dir.path().join(".data/file.txt"), "updated").unwrap();
|
||||||
|
assert_eq!(entry.scan(&logger).await.unwrap(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read_to_string(dest_dir.path().join(".data/file.txt")).unwrap(),
|
||||||
|
"updated"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read_to_string(dest_dir.path().join("1.txt")).unwrap(),
|
||||||
|
"updated"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify that resulting 1.txt is a symlink:
|
||||||
|
assert!(tokio::fs::symlink_metadata(dest_dir.path().join("1.txt"))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.file_type()
|
||||||
|
.is_symlink());
|
||||||
|
|
||||||
|
// Should copy no new files after copy happened
|
||||||
|
assert_eq!(entry.scan(&logger).await.unwrap(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn watch_directory() {
|
async fn watch_directory() {
|
||||||
// Prepare source directory:
|
// Prepare source directory:
|
||||||
|
Loading…
Reference in New Issue
Block a user