Skip to content

Commit 5430db6

Browse files
committed
Add sha256 checksums to the lockfile
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
1 parent 8214bb9 commit 5430db6

File tree

10 files changed

+564
-65
lines changed

10 files changed

+564
-65
lines changed

src/cargo/core/registry.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ use sources::config::SourceConfigMap;
1111
pub trait Registry {
1212
/// Attempt to find the packages that match a dependency request.
1313
fn query(&mut self, name: &Dependency) -> CargoResult<Vec<Summary>>;
14+
15+
/// Returns whether or not this registry will return summaries with
16+
/// checksums listed.
17+
///
18+
/// By default, registries do not support checksums.
19+
fn supports_checksums(&self) -> bool {
20+
false
21+
}
1422
}
1523

1624
impl Registry for Vec<Summary> {

src/cargo/core/resolver/encode.rs

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use std::collections::{HashMap, BTreeMap};
2+
use std::fmt;
3+
use std::str::FromStr;
24

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

68
use core::{Package, PackageId, SourceId, Workspace};
7-
use util::{CargoResult, Graph, Config};
9+
use util::{CargoResult, Graph, Config, internal, ChainError, CargoError};
810

911
use super::Resolve;
1012

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

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

@@ -90,13 +92,56 @@ impl EncodableResolve {
9092
try!(add_dependencies(id, pkg));
9193
}
9294
}
95+
let mut metadata = self.metadata.unwrap_or(BTreeMap::new());
96+
97+
// Parse out all package checksums. After we do this we can be in a few
98+
// situations:
99+
//
100+
// * We parsed no checksums. In this situation we're dealing with an old
101+
// lock file and we're gonna fill them all in.
102+
// * We parsed some checksums, but not one for all packages listed. It
103+
// could have been the case that some were listed, then an older Cargo
104+
// client added more dependencies, and now we're going to fill in the
105+
// missing ones.
106+
// * There are too many checksums listed, indicative of an older Cargo
107+
// client removing a package but not updating the checksums listed.
108+
//
109+
// In all of these situations they're part of normal usage, so we don't
110+
// really worry about it. We just try to slurp up as many checksums as
111+
// possible.
112+
let mut checksums = HashMap::new();
113+
let prefix = "checksum ";
114+
let mut to_remove = Vec::new();
115+
for (k, v) in metadata.iter().filter(|p| p.0.starts_with(prefix)) {
116+
to_remove.push(k.to_string());
117+
let k = &k[prefix.len()..];
118+
let id: EncodablePackageId = try!(k.parse().chain_error(|| {
119+
internal("invalid encoding of checksum in lockfile")
120+
}));
121+
let id = try!(to_package_id(&id.name,
122+
&id.version,
123+
id.source.as_ref(),
124+
default,
125+
&path_deps));
126+
let v = if v == "<none>" {
127+
None
128+
} else {
129+
Some(v.to_string())
130+
};
131+
checksums.insert(id, v);
132+
}
133+
134+
for k in to_remove {
135+
metadata.remove(&k);
136+
}
93137

94138
Ok(Resolve {
95139
graph: g,
96140
root: root,
97141
features: HashMap::new(),
98-
metadata: self.metadata.clone(),
99142
replacements: replacements,
143+
checksums: checksums,
144+
metadata: metadata,
100145
})
101146
}
102147
}
@@ -168,33 +213,30 @@ pub struct EncodablePackageId {
168213
source: Option<SourceId>
169214
}
170215

171-
impl Encodable for EncodablePackageId {
172-
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
173-
let mut out = format!("{} {}", self.name, self.version);
216+
impl fmt::Display for EncodablePackageId {
217+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218+
try!(write!(f, "{} {}", self.name, self.version));
174219
if let Some(ref s) = self.source {
175-
out.push_str(&format!(" ({})", s.to_url()));
220+
try!(write!(f, " ({})", s.to_url()));
176221
}
177-
out.encode(s)
222+
Ok(())
178223
}
179224
}
180225

181-
impl Decodable for EncodablePackageId {
182-
fn decode<D: Decoder>(d: &mut D) -> Result<EncodablePackageId, D::Error> {
183-
let string: String = try!(Decodable::decode(d));
226+
impl FromStr for EncodablePackageId {
227+
type Err = Box<CargoError>;
228+
229+
fn from_str(s: &str) -> CargoResult<EncodablePackageId> {
184230
let regex = Regex::new(r"^([^ ]+) ([^ ]+)(?: \(([^\)]+)\))?$").unwrap();
185-
let captures = try!(regex.captures(&string).ok_or_else(|| {
186-
d.error("invalid serialized PackageId")
231+
let captures = try!(regex.captures(s).ok_or_else(|| {
232+
internal("invalid serialized PackageId")
187233
}));
188234

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

192238
let source_id = match captures.at(3) {
193-
Some(s) => {
194-
Some(try!(SourceId::from_url(s).map_err(|e| {
195-
d.error(&e.to_string())
196-
})))
197-
}
239+
Some(s) => Some(try!(SourceId::from_url(s))),
198240
None => None,
199241
};
200242

@@ -206,6 +248,21 @@ impl Decodable for EncodablePackageId {
206248
}
207249
}
208250

251+
impl Encodable for EncodablePackageId {
252+
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
253+
self.to_string().encode(s)
254+
}
255+
}
256+
257+
impl Decodable for EncodablePackageId {
258+
fn decode<D: Decoder>(d: &mut D) -> Result<EncodablePackageId, D::Error> {
259+
String::decode(d).and_then(|string| {
260+
string.parse::<EncodablePackageId>()
261+
.map_err(|e| d.error(&e.to_string()))
262+
})
263+
}
264+
}
265+
209266
pub struct WorkspaceResolve<'a, 'cfg: 'a> {
210267
pub ws: &'a Workspace<'cfg>,
211268
pub resolve: &'a Resolve,
@@ -226,12 +283,25 @@ impl<'a, 'cfg> Encodable for WorkspaceResolve<'a, 'cfg> {
226283
}
227284

228285
Some(encodable_resolve_node(id, self.resolve))
229-
}).collect::<Vec<EncodableDependency>>();
286+
}).collect::<Vec<_>>();
287+
288+
let mut metadata = self.resolve.metadata.clone();
289+
290+
for id in ids.iter().filter(|id| !id.source_id().is_path()) {
291+
let checksum = match self.resolve.checksums[*id] {
292+
Some(ref s) => &s[..],
293+
None => "<none>",
294+
};
295+
let id = encodable_package_id(id);
296+
metadata.insert(format!("checksum {}", id.to_string()),
297+
checksum.to_string());
298+
}
230299

300+
let metadata = if metadata.len() == 0 {None} else {Some(metadata)};
231301
EncodableResolve {
232302
package: Some(encodable),
233303
root: encodable_resolve_node(&root, self.resolve),
234-
metadata: self.resolve.metadata.clone(),
304+
metadata: metadata,
235305
}.encode(s)
236306
}
237307
}

0 commit comments

Comments
 (0)