Skip to content

Commit cac182e

Browse files
committed
fix: properly handle the case where a file is already external.
We don't want to move, but to copy in that case. We also update the db, but limit the max number of external paths to MAX_EXTERNAL_PATHS This fixes a regression from blobs 0.35 regarding ExportMode::TryReference
1 parent 30b8ac5 commit cac182e

File tree

1 file changed

+27
-15
lines changed

1 file changed

+27
-15
lines changed

src/store/fs.rs

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ use crate::{
152152
HashAndFormat,
153153
};
154154

155+
/// Maximum number of external paths we track per blob.
156+
const MAX_EXTERNAL_PATHS: usize = 8;
157+
155158
/// Create a 16 byte unique ID.
156159
fn new_uuid() -> [u8; 16] {
157160
use rand::RngCore;
@@ -1239,20 +1242,20 @@ async fn export_path_impl(
12391242
}
12401243
};
12411244
trace!("exporting {} to {}", cmd.hash.to_hex(), target.display());
1242-
let (data, external) = match data_location {
1243-
DataLocation::Inline(data) => (MemOrFile::Mem(data), false),
1245+
let (data, mut external) = match data_location {
1246+
DataLocation::Inline(data) => (MemOrFile::Mem(data), vec![]),
12441247
DataLocation::Owned(size) => (
12451248
MemOrFile::File((ctx.options().path.data_path(&cmd.hash), size)),
1246-
false,
1249+
vec![],
12471250
),
12481251
DataLocation::External(paths, size) => (
12491252
MemOrFile::File((
1250-
paths.into_iter().next().ok_or_else(|| {
1253+
paths.iter().cloned().next().ok_or_else(|| {
12511254
io::Error::new(io::ErrorKind::NotFound, "no external data path")
12521255
})?,
12531256
size,
12541257
)),
1255-
true,
1258+
paths,
12561259
),
12571260
};
12581261
let size = match &data {
@@ -1277,34 +1280,43 @@ async fn export_path_impl(
12771280
);
12781281
}
12791282
ExportMode::TryReference => {
1280-
if external {
1283+
if !external.is_empty() {
1284+
// the file already exists externally, so we need to copy it.
1285+
// if the OS supports reflink, we might as well use that.
12811286
let res =
12821287
reflink_or_copy_with_progress(&source_path, &target, size, tx).await?;
12831288
trace!(
12841289
"exported {} also to {}, {res:?}",
12851290
source_path.display(),
12861291
target.display()
12871292
);
1293+
external.push(target);
1294+
external.sort();
1295+
external.dedup();
1296+
external.truncate(MAX_EXTERNAL_PATHS);
12881297
} else {
1298+
// the file was previously owned, so we can just move it.
1299+
// if that fails with ERR_CROSS, we fall back to copy.
12891300
match std::fs::rename(&source_path, &target) {
12901301
Ok(()) => {}
12911302
Err(cause) => {
12921303
const ERR_CROSS: i32 = 18;
12931304
if cause.raw_os_error() == Some(ERR_CROSS) {
1294-
let source = fs::File::open(&source_path)?;
1295-
let mut target = fs::File::create(&target)?;
1296-
copy_with_progress(&source, size, &mut target, tx).await?;
1305+
reflink_or_copy_with_progress(&source_path, &target, size, tx)
1306+
.await?;
1307+
// todo: delete file at source_path
12971308
} else {
12981309
return Err(cause.into());
12991310
}
13001311
}
13011312
}
1302-
ctx.set(EntryState::Complete {
1303-
data_location: DataLocation::External(vec![target], size),
1304-
outboard_location,
1305-
})
1306-
.await?;
1307-
}
1313+
external.push(target);
1314+
};
1315+
ctx.set(EntryState::Complete {
1316+
data_location: DataLocation::External(external, size),
1317+
outboard_location,
1318+
})
1319+
.await?;
13081320
}
13091321
},
13101322
}

0 commit comments

Comments
 (0)