Skip to content

Commit ff39d80

Browse files
committed
repository: Atomically replace refs/ symlinks
Naming a new version of an image the same as the old version is pretty standard, and this is currenlty giving EEXIST errors. We fix this by doing an atomic symlink + rename operation to replace the symlink. Signed-off-by: Alexander Larsson <[email protected]>
1 parent d8e285f commit ff39d80

File tree

2 files changed

+26
-1
lines changed

2 files changed

+26
-1
lines changed

crates/composefs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ tempfile = { version = "3.8.0", optional = true, default-features = false }
2929
xxhash-rust = { version = "0.8.2", default-features = false, features = ["xxh32"] }
3030
zerocopy = { version = "0.8.0", default-features = false, features = ["derive", "std"] }
3131
zstd = { version = "0.13.0", default-features = false }
32+
rand = { version = "0.9.1", default-features = true }
3233

3334
[dev-dependencies]
3435
insta = "1.42.2"

crates/composefs/src/repository.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010

1111
use anyhow::{bail, ensure, Context, Result};
1212
use once_cell::sync::OnceCell;
13+
use rand::{distr::Alphanumeric, Rng};
1314
use rustix::{
1415
fs::{
1516
fdatasync, flock, linkat, mkdirat, open, openat, readlinkat, symlinkat, AtFlags, Dir,
@@ -432,7 +433,30 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
432433
relative.push(target_component);
433434
}
434435

435-
symlinkat(relative, &self.repository, name)
436+
// Atomically replace existing symlink
437+
438+
let mut count = 0;
439+
let tmp_name = loop {
440+
let rand_string: String = rand::rng()
441+
.sample_iter(&Alphanumeric)
442+
.take(12)
443+
.map(char::from)
444+
.collect();
445+
let tmp_name = format!(".symlink{}", rand_string);
446+
match symlinkat(&relative, &self.repository, &tmp_name) {
447+
Ok(_) => break tmp_name,
448+
Err(Errno::EXIST) => {
449+
if count > 10 {
450+
Err(Errno::EXIST)?
451+
}
452+
count += 1;
453+
continue;
454+
}
455+
Err(other) => Err(other)?,
456+
}
457+
};
458+
459+
renameat(&self.repository, &tmp_name, &self.repository, name)
436460
}
437461

438462
pub fn ensure_symlink<P: AsRef<Path>>(&self, name: P, target: &str) -> ErrnoResult<()> {

0 commit comments

Comments
 (0)