Skip to content

Commit 1558e00

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 1558e00

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
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: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use anyhow::{bail, ensure, Context, Result};
1212
use once_cell::sync::OnceCell;
1313
use rustix::{
1414
fs::{
15-
fdatasync, flock, linkat, mkdirat, open, openat, readlinkat, symlinkat, AtFlags, Dir,
16-
FileType, FlockOperation, Mode, OFlags, CWD,
15+
fdatasync, flock, linkat, mkdirat, open, openat, readlinkat, AtFlags, Dir, FileType,
16+
FlockOperation, Mode, OFlags, CWD,
1717
},
1818
io::{Errno, Result as ErrnoResult},
1919
};
@@ -25,7 +25,7 @@ use crate::{
2525
},
2626
mount::mount_composefs_at,
2727
splitstream::{DigestMap, SplitStreamReader, SplitStreamWriter},
28-
util::{proc_self_fd, Sha256Digest},
28+
util::{proc_self_fd, replace_symlinkat, Sha256Digest},
2929
};
3030

3131
/// Call openat() on the named subdirectory of "dirfd", possibly creating it first.
@@ -432,7 +432,9 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
432432
relative.push(target_component);
433433
}
434434

435-
symlinkat(relative, &self.repository, name)
435+
// Atomically replace existing symlink
436+
437+
replace_symlinkat(&relative, &self.repository, &name)
436438
}
437439

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

crates/composefs/src/util.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
use rand::{distr::Alphanumeric, Rng};
12
use std::{
23
io::{Error, ErrorKind, Read, Result},
3-
os::fd::{AsFd, AsRawFd},
4+
os::fd::{AsFd, AsRawFd, OwnedFd},
5+
path::Path,
46
};
57

8+
use rustix::{
9+
fs::{renameat, symlinkat, unlinkat, AtFlags},
10+
io::{Errno, Result as ErrnoResult},
11+
};
612
use tokio::io::{AsyncRead, AsyncReadExt};
713

814
/// Formats a string like "/proc/self/fd/3" for the given fd. This can be used to work with kernel
@@ -97,6 +103,43 @@ pub fn parse_sha256(string: impl AsRef<str>) -> Result<Sha256Digest> {
97103
Ok(value)
98104
}
99105

106+
pub fn generate_tmpname(prefix: &str) -> String {
107+
let rand_string: String = rand::rng()
108+
.sample_iter(&Alphanumeric)
109+
.take(12)
110+
.map(char::from)
111+
.collect();
112+
format!("{}{}", prefix, rand_string)
113+
}
114+
115+
pub fn replace_symlinkat(
116+
target: impl AsRef<Path>,
117+
dirfd: &OwnedFd,
118+
name: impl AsRef<Path>,
119+
) -> ErrnoResult<()> {
120+
let name = name.as_ref();
121+
let target = target.as_ref();
122+
123+
for _ in 0..16 {
124+
let tmp_name = generate_tmpname(".symlink-");
125+
match symlinkat(target, dirfd, &tmp_name) {
126+
Ok(_) => match renameat(dirfd, &tmp_name, &dirfd, name) {
127+
Ok(_) => return Ok(()),
128+
Err(e) => {
129+
let _ = unlinkat(dirfd, tmp_name, AtFlags::empty());
130+
return Err(e);
131+
}
132+
},
133+
Err(Errno::EXIST) => {
134+
continue;
135+
}
136+
Err(other) => Err(other)?,
137+
}
138+
}
139+
140+
Err(Errno::EXIST)
141+
}
142+
100143
#[cfg(test)]
101144
mod test {
102145
use similar_asserts::assert_eq;

0 commit comments

Comments
 (0)