radicle: temporary repository helper

It's possible that a temporary directory can be created on a different
mount, and when moved during a clone, creates an I/O error.

To prevent this, add a method to `Storage` which creates a temporary
directory and `Repository` with the path in the form
`<rid>.lock`.

This `.lock` path name can easily be skipped when listing
repositories. Further tooling can be added for detecting leftover
`.lock` directories if, for some reason, they were not cleaned up by
the `tempfile` crate.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2023-11-16 09:13:00 +00:00
parent 25ed6b8b28
commit e71f07e592
No known key found for this signature in database
GPG Key ID: 582959CEFC92CB4E
4 changed files with 32 additions and 26 deletions

View File

@ -136,13 +136,7 @@ impl Runtime {
let network = config.network;
let rng = fastrand::Rng::new();
let clock = LocalTime::now();
let storage = Storage::open(
home.storage(),
git::UserInfo {
alias: alias.clone(),
key: id,
},
)?;
let storage = Storage::open(home.storage(), git::UserInfo { alias, key: id })?;
let address_db = node_dir.join(ADDRESS_DB_FILE);
let routing_db = node_dir.join(ROUTING_DB_FILE);
let tracking_db = node_dir.join(TRACKING_DB_FILE);
@ -254,7 +248,6 @@ impl Runtime {
scope,
tracking_db,
limit: FetchLimit::default(),
info: git::UserInfo { alias, key: nid },
local: nid,
expiry: worker::garbage::Expiry::default(),
};

View File

@ -14,7 +14,7 @@ use radicle::identity::Id;
use radicle::prelude::NodeId;
use radicle::storage::refs::RefsAt;
use radicle::storage::{ReadRepository, ReadStorage};
use radicle::{crypto, git, Storage};
use radicle::{crypto, Storage};
use radicle_fetch::FetchLimit;
use crate::runtime::{thread, Handle};
@ -159,8 +159,6 @@ pub struct FetchConfig {
pub tracking_db: PathBuf,
/// Data limits when fetching from a remote.
pub limit: FetchLimit,
/// Information of the local peer.
pub info: git::UserInfo,
/// Public key of the local peer.
pub local: crypto::PublicKey,
/// Configuration for `git gc` garbage collection. Defaults to `1
@ -281,7 +279,6 @@ impl Worker {
scope,
tracking_db,
limit,
info,
local,
expiry,
} = &self.fetch_config;
@ -292,15 +289,7 @@ impl Worker {
let tracked = radicle_fetch::Tracked::from_config(rid, &tracking)?;
let blocked = radicle_fetch::BlockList::from_config(&tracking)?;
let handle = fetch::Handle::new(
rid,
*local,
info.clone(),
&self.storage,
tracked,
blocked,
channels,
)?;
let handle = fetch::Handle::new(rid, *local, &self.storage, tracked, blocked, channels)?;
let result = handle.fetch(rid, &self.storage, *limit, remote, refs_at)?;
if let Err(e) = garbage::collect(&self.storage, rid, *expiry) {

View File

@ -3,9 +3,7 @@ pub mod error;
use std::collections::HashSet;
use radicle::crypto::PublicKey;
use radicle::git::UserInfo;
use radicle::prelude::Id;
use radicle::storage::git::Repository;
use radicle::storage::refs::RefsAt;
use radicle::storage::{ReadStorage as _, RefUpdate, WriteRepository as _};
use radicle::Storage;
@ -35,7 +33,6 @@ impl Handle {
pub fn new(
rid: Id,
local: PublicKey,
info: UserInfo,
storage: &Storage,
tracked: Tracked,
blocked: BlockList,
@ -47,8 +44,7 @@ impl Handle {
let handle = radicle_fetch::Handle::new(local, repo, tracked, blocked, channels)?;
Ok(Handle::Pull { handle })
} else {
let tmp = tempfile::tempdir()?;
let repo = Repository::create(tmp.path(), rid, &info)?;
let (repo, tmp) = storage.lock_repository(rid)?;
let handle = radicle_fetch::Handle::new(local, repo, tracked, blocked, channels)?;
Ok(Handle::Clone { handle, tmp })
}

View File

@ -9,6 +9,7 @@ use std::{fs, io};
use crypto::{Signer, Verified};
use once_cell::sync::Lazy;
use tempfile::TempDir;
use crate::crypto::Unverified;
use crate::git;
@ -152,6 +153,27 @@ impl Storage {
Ok(Self { path, info })
}
/// Create a [`Repository`] in a temporary directory.
///
/// N.b. it is important to keep the [`TempDir`] in scope while
/// using the [`Repository`]. If it is dropped, any action on the
/// `Repository` will fail.
pub fn lock_repository(&self, rid: Id) -> Result<(Repository, TempDir), RepositoryError> {
if self.contains(&rid)? {
return Err(Error::Io(io::Error::new(
io::ErrorKind::AlreadyExists,
format!("refusing to create '{}.lock'", rid),
))
.into());
}
let tmp = tempfile::Builder::new()
.prefix(&rid.canonical())
.suffix(".lock")
.tempdir_in(self.path())
.map_err(Error::from)?;
Ok((Repository::create(tmp.path(), rid, &self.info)?, tmp))
}
pub fn path(&self) -> &Path {
self.path.as_path()
}
@ -170,6 +192,12 @@ impl Storage {
if path.file_name().to_string_lossy().starts_with('.') {
continue;
}
// Skip lock files.
if let Some(ext) = path.path().extension() {
if ext == "lock" {
continue;
}
}
let rid =
Id::try_from(path.file_name()).map_err(|_| Error::InvalidId(path.file_name()))?;