Skip to content

Commit 3606db5

Browse files
authored
Turbopack: correctly overwrite existing symlinks (#86808)
1 parent c112f8a commit 3606db5

File tree

1 file changed

+128
-1
lines changed
  • turbopack/crates/turbo-tasks-fs/src

1 file changed

+128
-1
lines changed

turbopack/crates/turbo-tasks-fs/src/lib.rs

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,26 @@ impl FileSystem for DiskFileSystem {
11271127
PathBuf::from(unix_to_sys(target).as_ref())
11281128
};
11291129
let full_path = full_path.into_owned();
1130+
1131+
if old_content.is_some() {
1132+
// Remove existing symlink before creating a new one. At least on Unix,
1133+
// symlink(2) fails with EEXIST if the link already exists instead of
1134+
// overwriting it
1135+
retry_blocking(full_path.clone(), |path| std::fs::remove_file(path))
1136+
.concurrency_limited(&inner.write_semaphore)
1137+
.await
1138+
.or_else(|err| {
1139+
if err.kind() == ErrorKind::NotFound {
1140+
Ok(())
1141+
} else {
1142+
Err(err)
1143+
}
1144+
})
1145+
.with_context(|| {
1146+
anyhow!("removing existing symlink {} failed", full_path.display())
1147+
})?;
1148+
}
1149+
11301150
retry_blocking(target_path, move |target_path| {
11311151
let _span = tracing::info_span!(
11321152
"write symlink",
@@ -2915,7 +2935,114 @@ mod tests {
29152935
.unwrap();
29162936
}
29172937

2918-
// Test helpers for denied_path tests
2938+
#[cfg(test)]
2939+
mod symlink_tests {
2940+
use std::{
2941+
fs::{File, create_dir_all, read_to_string},
2942+
io::Write,
2943+
};
2944+
2945+
use turbo_rcstr::{RcStr, rcstr};
2946+
use turbo_tasks::{ResolvedVc, apply_effects};
2947+
use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
2948+
2949+
use crate::{DiskFileSystem, FileSystem, FileSystemPath, LinkContent, LinkType};
2950+
2951+
#[turbo_tasks::function(operation)]
2952+
async fn test_write_link_effect(
2953+
fs: ResolvedVc<DiskFileSystem>,
2954+
path: FileSystemPath,
2955+
target: RcStr,
2956+
) -> anyhow::Result<()> {
2957+
let write_file = |f| {
2958+
fs.write_link(
2959+
f,
2960+
LinkContent::Link {
2961+
target: format!("{target}/data.txt").into(),
2962+
link_type: LinkType::empty(),
2963+
}
2964+
.cell(),
2965+
)
2966+
};
2967+
// Write it twice (same content)
2968+
write_file(path.join("symlink-file")?).await?;
2969+
write_file(path.join("symlink-file")?).await?;
2970+
2971+
let write_dir = |f| {
2972+
fs.write_link(
2973+
f,
2974+
LinkContent::Link {
2975+
target: target.clone(),
2976+
link_type: LinkType::DIRECTORY,
2977+
}
2978+
.cell(),
2979+
)
2980+
};
2981+
// Write it twice (same content)
2982+
write_dir(path.join("symlink-dir")?).await?;
2983+
write_dir(path.join("symlink-dir")?).await?;
2984+
2985+
Ok(())
2986+
}
2987+
2988+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2989+
async fn test_write_link() {
2990+
let scratch = tempfile::tempdir().unwrap();
2991+
let path = scratch.path().to_owned();
2992+
2993+
create_dir_all(path.join("subdir-a")).unwrap();
2994+
File::create_new(path.join("subdir-a/data.txt"))
2995+
.unwrap()
2996+
.write_all(b"foo")
2997+
.unwrap();
2998+
create_dir_all(path.join("subdir-b")).unwrap();
2999+
File::create_new(path.join("subdir-b/data.txt"))
3000+
.unwrap()
3001+
.write_all(b"bar")
3002+
.unwrap();
3003+
let root = path.to_str().unwrap().into();
3004+
3005+
let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3006+
BackendOptions::default(),
3007+
noop_backing_storage(),
3008+
));
3009+
3010+
tt.run_once(async move {
3011+
let fs = DiskFileSystem::new(rcstr!("test"), root)
3012+
.to_resolved()
3013+
.await?;
3014+
let root_path = fs.root().owned().await?;
3015+
3016+
let write_result =
3017+
test_write_link_effect(fs, root_path.clone(), rcstr!("subdir-a"));
3018+
write_result.read_strongly_consistent().await?;
3019+
apply_effects(write_result).await?;
3020+
3021+
assert_eq!(read_to_string(path.join("symlink-file")).unwrap(), "foo");
3022+
assert_eq!(
3023+
read_to_string(path.join("symlink-dir/data.txt")).unwrap(),
3024+
"foo"
3025+
);
3026+
3027+
// Write the same links again but with different targets
3028+
let write_result = test_write_link_effect(fs, root_path, rcstr!("subdir-b"));
3029+
write_result.read_strongly_consistent().await?;
3030+
apply_effects(write_result).await?;
3031+
3032+
assert_eq!(read_to_string(path.join("symlink-file")).unwrap(), "bar");
3033+
assert_eq!(
3034+
read_to_string(path.join("symlink-dir/data.txt")).unwrap(),
3035+
"bar"
3036+
);
3037+
3038+
anyhow::Ok(())
3039+
})
3040+
.await
3041+
.unwrap();
3042+
}
3043+
}
3044+
3045+
// Tests helpers for denied_path tests
29193046
#[cfg(test)]
29203047
mod denied_path_tests {
29213048
use std::{

0 commit comments

Comments
 (0)