Skip to content

Commit

Permalink
Add sha256 checksums to the lockfile
Browse files Browse the repository at this point in the history
This commit changes how lock files are encoded by checksums for each package in
the lockfile to the `[metadata]` section. The previous commit implemented the
ability to redirect sources, but the core assumption there was that a package
coming from two different locations was always the same. An inevitable case,
however, is that a source gets corrupted or, worse, ships a modified version of
a crate to introduce instability between two "mirrors".

The purpose of adding checksums will be to resolve this discrepancy. Each crate
coming from crates.io will now record its sha256 checksum in the lock file. When
a lock file already exists, the new checksum for a crate will be checked against
it, and if they differ compilation will be aborted. Currently only registry
crates will have sha256 checksums listed, all other sources do not have
checksums at this time.

The astute may notice that if the lock file format is changing, then a lock file
generated by a newer Cargo might be mangled by an older Cargo. In anticipation
of this, however, all Cargo versions published support a `[metadata]` section of
the lock file which is transparently carried forward if encountered. This means
that older Cargos compiling with a newer lock file will not verify checksums in
the lock file, but they will carry forward the checksum information and prevent
it from being removed.

There are, however, a few situations where problems may still arise:

1. If an older Cargo takes a newer lockfile (with checksums) and updates it with
   a modified `Cargo.toml` (e.g. a package was added, removed, or updated), then
   the `[metadata]` section will not be updated appropriately. This modification
   would require a newer Cargo to come in and update the checksums for such a
   modification.

2. Today Cargo can only calculate checksums for registry sources, but we may
   eventually want to support other sources like git (or just straight-up path
   sources). If future Cargo implements support for this sort of checksum, then
   it's the same problem as above where older Cargos will not know how to keep
   the checksum in sync
  • Loading branch information
alexcrichton committed Aug 1, 2016
1 parent 8214bb9 commit 5430db6
Show file tree
Hide file tree
Showing 10 changed files with 564 additions and 65 deletions.
8 changes: 8 additions & 0 deletions src/cargo/core/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ use sources::config::SourceConfigMap;
pub trait Registry {
/// Attempt to find the packages that match a dependency request.
fn query(&mut self, name: &Dependency) -> CargoResult<Vec<Summary>>;

/// Returns whether or not this registry will return summaries with
/// checksums listed.
///
/// By default, registries do not support checksums.
fn supports_checksums(&self) -> bool {
false
}
}

impl Registry for Vec<Summary> {
Expand Down
110 changes: 90 additions & 20 deletions src/cargo/core/resolver/encode.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::collections::{HashMap, BTreeMap};
use std::fmt;
use std::str::FromStr;

use regex::Regex;
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};

use core::{Package, PackageId, SourceId, Workspace};
use util::{CargoResult, Graph, Config};
use util::{CargoResult, Graph, Config, internal, ChainError, CargoError};

use super::Resolve;

Expand All @@ -18,7 +20,7 @@ pub struct EncodableResolve {
pub type Metadata = BTreeMap<String, String>;

impl EncodableResolve {
pub fn to_resolve(&self, ws: &Workspace) -> CargoResult<Resolve> {
pub fn to_resolve(self, ws: &Workspace) -> CargoResult<Resolve> {
let path_deps = build_path_deps(ws);
let default = try!(ws.current()).package_id().source_id();

Expand Down Expand Up @@ -90,13 +92,56 @@ impl EncodableResolve {
try!(add_dependencies(id, pkg));
}
}
let mut metadata = self.metadata.unwrap_or(BTreeMap::new());

// Parse out all package checksums. After we do this we can be in a few
// situations:
//
// * We parsed no checksums. In this situation we're dealing with an old
// lock file and we're gonna fill them all in.
// * We parsed some checksums, but not one for all packages listed. It
// could have been the case that some were listed, then an older Cargo
// client added more dependencies, and now we're going to fill in the
// missing ones.
// * There are too many checksums listed, indicative of an older Cargo
// client removing a package but not updating the checksums listed.
//
// In all of these situations they're part of normal usage, so we don't
// really worry about it. We just try to slurp up as many checksums as
// possible.
let mut checksums = HashMap::new();
let prefix = "checksum ";
let mut to_remove = Vec::new();
for (k, v) in metadata.iter().filter(|p| p.0.starts_with(prefix)) {
to_remove.push(k.to_string());
let k = &k[prefix.len()..];
let id: EncodablePackageId = try!(k.parse().chain_error(|| {
internal("invalid encoding of checksum in lockfile")
}));
let id = try!(to_package_id(&id.name,
&id.version,
id.source.as_ref(),
default,
&path_deps));
let v = if v == "<none>" {
None
} else {
Some(v.to_string())
};
checksums.insert(id, v);
}

for k in to_remove {
metadata.remove(&k);
}

Ok(Resolve {
graph: g,
root: root,
features: HashMap::new(),
metadata: self.metadata.clone(),
replacements: replacements,
checksums: checksums,
metadata: metadata,
})
}
}
Expand Down Expand Up @@ -168,33 +213,30 @@ pub struct EncodablePackageId {
source: Option<SourceId>
}

impl Encodable for EncodablePackageId {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
let mut out = format!("{} {}", self.name, self.version);
impl fmt::Display for EncodablePackageId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "{} {}", self.name, self.version));
if let Some(ref s) = self.source {
out.push_str(&format!(" ({})", s.to_url()));
try!(write!(f, " ({})", s.to_url()));
}
out.encode(s)
Ok(())
}
}

impl Decodable for EncodablePackageId {
fn decode<D: Decoder>(d: &mut D) -> Result<EncodablePackageId, D::Error> {
let string: String = try!(Decodable::decode(d));
impl FromStr for EncodablePackageId {
type Err = Box<CargoError>;

fn from_str(s: &str) -> CargoResult<EncodablePackageId> {
let regex = Regex::new(r"^([^ ]+) ([^ ]+)(?: \(([^\)]+)\))?$").unwrap();
let captures = try!(regex.captures(&string).ok_or_else(|| {
d.error("invalid serialized PackageId")
let captures = try!(regex.captures(s).ok_or_else(|| {
internal("invalid serialized PackageId")
}));

let name = captures.at(1).unwrap();
let version = captures.at(2).unwrap();

let source_id = match captures.at(3) {
Some(s) => {
Some(try!(SourceId::from_url(s).map_err(|e| {
d.error(&e.to_string())
})))
}
Some(s) => Some(try!(SourceId::from_url(s))),
None => None,
};

Expand All @@ -206,6 +248,21 @@ impl Decodable for EncodablePackageId {
}
}

impl Encodable for EncodablePackageId {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
self.to_string().encode(s)
}
}

impl Decodable for EncodablePackageId {
fn decode<D: Decoder>(d: &mut D) -> Result<EncodablePackageId, D::Error> {
String::decode(d).and_then(|string| {
string.parse::<EncodablePackageId>()
.map_err(|e| d.error(&e.to_string()))
})
}
}

pub struct WorkspaceResolve<'a, 'cfg: 'a> {
pub ws: &'a Workspace<'cfg>,
pub resolve: &'a Resolve,
Expand All @@ -226,12 +283,25 @@ impl<'a, 'cfg> Encodable for WorkspaceResolve<'a, 'cfg> {
}

Some(encodable_resolve_node(id, self.resolve))
}).collect::<Vec<EncodableDependency>>();
}).collect::<Vec<_>>();

let mut metadata = self.resolve.metadata.clone();

for id in ids.iter().filter(|id| !id.source_id().is_path()) {
let checksum = match self.resolve.checksums[*id] {
Some(ref s) => &s[..],
None => "<none>",
};
let id = encodable_package_id(id);
metadata.insert(format!("checksum {}", id.to_string()),
checksum.to_string());
}

let metadata = if metadata.len() == 0 {None} else {Some(metadata)};
EncodableResolve {
package: Some(encodable),
root: encodable_resolve_node(&root, self.resolve),
metadata: self.resolve.metadata.clone(),
metadata: metadata,
}.encode(s)
}
}
Expand Down
Loading

0 comments on commit 5430db6

Please sign in to comment.