mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-21 13:08:42 +00:00
Merge pull request #227 from NobodyXu/fix/flock
Fix updating metafiles: Use file lock to fix race condition
This commit is contained in:
commit
45ba1de441
6 changed files with 158 additions and 23 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -109,6 +109,7 @@ dependencies = [
|
||||||
"dirs",
|
"dirs",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"fs4",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"guess_host_triple",
|
"guess_host_triple",
|
||||||
"home",
|
"home",
|
||||||
|
@ -369,6 +370,17 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs4"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9813c3dc174931eff4bd78609debba56465b7c1da888576d21636b601a46790"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rustix",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
|
@ -637,6 +649,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-lifetimes"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -704,6 +722,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.0.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.17"
|
version = "0.4.17"
|
||||||
|
@ -1025,6 +1049,20 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.35.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"io-lifetimes",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.20.6"
|
version = "0.20.6"
|
||||||
|
|
|
@ -27,6 +27,7 @@ clap = { version = "3.2.12", features = ["derive"] }
|
||||||
crates_io_api = { version = "0.8.0", default-features = false, features = ["rustls"] }
|
crates_io_api = { version = "0.8.0", default-features = false, features = ["rustls"] }
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
flate2 = { version = "1.0.24", features = ["zlib-ng"], default-features = false }
|
flate2 = { version = "1.0.24", features = ["zlib-ng"], default-features = false }
|
||||||
|
fs4 = "0.6.2"
|
||||||
futures-util = { version = "0.3.21", default-features = false }
|
futures-util = { version = "0.3.21", default-features = false }
|
||||||
home = "0.5.3"
|
home = "0.5.3"
|
||||||
jobserver = "0.1.24"
|
jobserver = "0.1.24"
|
||||||
|
|
|
@ -42,6 +42,9 @@ pub use tls_version::TLSVersion;
|
||||||
mod crate_name;
|
mod crate_name;
|
||||||
pub use crate_name::CrateName;
|
pub use crate_name::CrateName;
|
||||||
|
|
||||||
|
mod flock;
|
||||||
|
pub use flock::FileLock;
|
||||||
|
|
||||||
pub fn cargo_home() -> Result<&'static Path, io::Error> {
|
pub fn cargo_home() -> Result<&'static Path, io::Error> {
|
||||||
static CARGO_HOME: OnceCell<PathBuf> = OnceCell::new();
|
static CARGO_HOME: OnceCell<PathBuf> = OnceCell::new();
|
||||||
|
|
||||||
|
@ -50,6 +53,20 @@ pub fn cargo_home() -> Result<&'static Path, io::Error> {
|
||||||
.map(ops::Deref::deref)
|
.map(ops::Deref::deref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returned file is readable and writable.
|
||||||
|
pub fn create_if_not_exist(path: impl AsRef<Path>) -> io::Result<fs::File> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
let mut options = fs::File::options();
|
||||||
|
options.read(true).write(true);
|
||||||
|
|
||||||
|
options
|
||||||
|
.clone()
|
||||||
|
.create_new(true)
|
||||||
|
.open(path)
|
||||||
|
.or_else(|_| options.open(path))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn await_task<T>(task: tokio::task::JoinHandle<miette::Result<T>>) -> miette::Result<T> {
|
pub async fn await_task<T>(task: tokio::task::JoinHandle<miette::Result<T>>) -> miette::Result<T> {
|
||||||
match task.await {
|
match task.await {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
|
|
45
src/helpers/flock.rs
Normal file
45
src/helpers/flock.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::ops;
|
||||||
|
|
||||||
|
use fs4::FileExt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FileLock(File);
|
||||||
|
|
||||||
|
impl FileLock {
|
||||||
|
/// NOTE that this function blocks, so it cannot
|
||||||
|
/// be called in async context.
|
||||||
|
pub fn new_exclusive(file: File) -> io::Result<Self> {
|
||||||
|
file.lock_exclusive()?;
|
||||||
|
|
||||||
|
Ok(Self(file))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE that this function blocks, so it cannot
|
||||||
|
/// be called in async context.
|
||||||
|
pub fn new_shared(file: File) -> io::Result<Self> {
|
||||||
|
file.lock_shared()?;
|
||||||
|
|
||||||
|
Ok(Self(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FileLock {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for FileLock {
|
||||||
|
type Target = File;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ops::DerefMut for FileLock {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
fs, io,
|
fs,
|
||||||
|
io::{self, Seek},
|
||||||
iter::IntoIterator,
|
iter::IntoIterator,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
@ -11,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::CrateVersionSource;
|
use super::CrateVersionSource;
|
||||||
use crate::cargo_home;
|
use crate::{cargo_home, create_if_not_exist, FileLock};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
pub struct CratesToml {
|
pub struct CratesToml {
|
||||||
|
@ -27,6 +28,12 @@ impl CratesToml {
|
||||||
Self::load_from_path(Self::default_path()?)
|
Self::load_from_path(Self::default_path()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_from_reader<R: io::Read>(mut reader: R) -> Result<Self, CratesTomlParseError> {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
reader.read_to_end(&mut vec)?;
|
||||||
|
Ok(toml::from_slice(&vec)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, CratesTomlParseError> {
|
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, CratesTomlParseError> {
|
||||||
let file = fs::read_to_string(path)?;
|
let file = fs::read_to_string(path)?;
|
||||||
Self::from_str(&file)
|
Self::from_str(&file)
|
||||||
|
@ -40,6 +47,20 @@ impl CratesToml {
|
||||||
self.write_to_path(Self::default_path()?)
|
self.write_to_path(Self::default_path()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_to_writer<W: io::Write>(&self, mut writer: W) -> Result<(), CratesTomlParseError> {
|
||||||
|
let data = toml::to_vec(&self)?;
|
||||||
|
writer.write_all(&data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_to_file(&self, file: &mut fs::File) -> Result<(), CratesTomlParseError> {
|
||||||
|
self.write_to_writer(&mut *file)?;
|
||||||
|
let pos = file.stream_position()?;
|
||||||
|
file.set_len(pos)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_to_path(&self, path: impl AsRef<Path>) -> Result<(), CratesTomlParseError> {
|
pub fn write_to_path(&self, path: impl AsRef<Path>) -> Result<(), CratesTomlParseError> {
|
||||||
fs::write(path, &toml::to_vec(&self)?)?;
|
fs::write(path, &toml::to_vec(&self)?)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -52,17 +73,15 @@ impl CratesToml {
|
||||||
where
|
where
|
||||||
Iter: IntoIterator<Item = (&'a CrateVersionSource, BTreeSet<String>)>,
|
Iter: IntoIterator<Item = (&'a CrateVersionSource, BTreeSet<String>)>,
|
||||||
{
|
{
|
||||||
let mut c1 = match Self::load_from_path(path.as_ref()) {
|
let mut file = FileLock::new_exclusive(create_if_not_exist(path.as_ref())?)?;
|
||||||
Ok(c1) => c1,
|
let mut c1 = Self::load_from_reader(&mut *file)?;
|
||||||
Err(CratesTomlParseError::Io(io_err)) if io_err.kind() == io::ErrorKind::NotFound => {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
};
|
|
||||||
for (cvs, bins) in iter {
|
for (cvs, bins) in iter {
|
||||||
c1.insert(cvs, bins);
|
c1.insert(cvs, bins);
|
||||||
}
|
}
|
||||||
c1.write_to_path(path.as_ref())?;
|
|
||||||
|
file.rewind()?;
|
||||||
|
c1.write_to_file(&mut *file)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
fs, io,
|
fs,
|
||||||
|
io::{self, Seek},
|
||||||
iter::IntoIterator,
|
iter::IntoIterator,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
@ -10,7 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::CrateVersionSource;
|
use super::CrateVersionSource;
|
||||||
use crate::cargo_home;
|
use crate::{cargo_home, create_if_not_exist, FileLock};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
pub struct Crates2Json {
|
pub struct Crates2Json {
|
||||||
|
@ -48,9 +49,13 @@ impl Crates2Json {
|
||||||
Self::load_from_path(Self::default_path()?)
|
Self::load_from_path(Self::default_path()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_from_reader<R: io::Read>(reader: R) -> Result<Self, Crates2JsonParseError> {
|
||||||
|
Ok(serde_json::from_reader(reader)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, Crates2JsonParseError> {
|
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, Crates2JsonParseError> {
|
||||||
let file = fs::File::open(path.as_ref())?;
|
let file = fs::File::open(path.as_ref())?;
|
||||||
Ok(serde_json::from_reader(file)?)
|
Self::load_from_reader(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, cvs: &CrateVersionSource, info: CrateInfo) {
|
pub fn insert(&mut self, cvs: &CrateVersionSource, info: CrateInfo) {
|
||||||
|
@ -61,10 +66,22 @@ impl Crates2Json {
|
||||||
self.write_to_path(Self::default_path()?)
|
self.write_to_path(Self::default_path()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_to_writer<W: io::Write>(&self, writer: W) -> Result<(), Crates2JsonParseError> {
|
||||||
|
serde_json::to_writer(writer, &self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_to_file(&self, file: &mut fs::File) -> Result<(), Crates2JsonParseError> {
|
||||||
|
self.write_to_writer(&mut *file)?;
|
||||||
|
let pos = file.stream_position()?;
|
||||||
|
file.set_len(pos)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_to_path(&self, path: impl AsRef<Path>) -> Result<(), Crates2JsonParseError> {
|
pub fn write_to_path(&self, path: impl AsRef<Path>) -> Result<(), Crates2JsonParseError> {
|
||||||
let file = fs::File::create(path.as_ref())?;
|
let file = fs::File::create(path.as_ref())?;
|
||||||
serde_json::to_writer(file, &self)?;
|
self.write_to_writer(file)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append_to_path<Iter>(
|
pub fn append_to_path<Iter>(
|
||||||
|
@ -74,17 +91,15 @@ impl Crates2Json {
|
||||||
where
|
where
|
||||||
Iter: IntoIterator<Item = (CrateVersionSource, CrateInfo)>,
|
Iter: IntoIterator<Item = (CrateVersionSource, CrateInfo)>,
|
||||||
{
|
{
|
||||||
let mut c2 = match Self::load_from_path(path.as_ref()) {
|
let mut file = FileLock::new_exclusive(create_if_not_exist(path.as_ref())?)?;
|
||||||
Ok(c2) => c2,
|
let mut c2 = Self::load_from_reader(&mut *file)?;
|
||||||
Err(Crates2JsonParseError::Io(io_err)) if io_err.kind() == io::ErrorKind::NotFound => {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
};
|
|
||||||
for (cvs, info) in iter {
|
for (cvs, info) in iter {
|
||||||
c2.insert(&cvs, info);
|
c2.insert(&cvs, info);
|
||||||
}
|
}
|
||||||
c2.write_to_path(path.as_ref())?;
|
|
||||||
|
file.rewind()?;
|
||||||
|
c2.write_to_file(&mut *file)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue