This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

tools.lazy-store: read a .ls directory without entries as empty

16 of the 17 paths without a listing in CI run 39052697442 (neoprism)
failed with "missing field `entries`": cache.lassul.us writes empty
directories as {"type":"directory"}, e.g. 3k9vl6iwf58qy65wcjqccqykgzypm7va.ls
is {"version":1,"root":{"type":"directory"}}. harmonia-file-core's
Directory requires "entries", so since 207a89b5 that was an error; the
hand-walked parser before read a missing "entries" as empty. Fill it in
before deserializing. "size" of regular files is always written (also
for empty files).

The 17th, 9pczdaxss3xdmidr8cvgpw8ccn0agnhq.ls, is not a client bug: B2
itself (also queried directly, over HTTP/1.1) now and then answers 200
with Content-Length 78 and closes without sending a body byte. nginx
turns that into an HTTP/2 RST_STREAM INTERNAL_ERROR, which reqwest
reports as "error decoding response body". The same request succeeds
moments later; the existing retries cover it unless all six tries hit
a truncated response, as in that CI run.

+26 -6
+26 -6
tools/lazy-store/src/nar.rs
··· 29 29 /// The metadata of a store path, as in the caches' `.ls` listings. 30 30 pub type Tree = FileTree<Size>; 31 31 32 - /// Parse a `.ls` listing (`{"version": 1, "root": {...}}`). 32 + /// Parse a `.ls` listing (`{"version": 1, "root": {...}}`). Empty 33 + /// directories may come without `entries`. 33 34 pub fn from_listing(body: &[u8]) -> io::Result<Tree> { 34 - #[derive(Deserialize)] 35 - struct Listing { 36 - root: Tree, 35 + fn fill(v: &mut serde_json::Value) { 36 + if v["type"] == "directory" { 37 + match v.get_mut("entries").and_then(|e| e.as_object_mut()) { 38 + Some(e) => e.values_mut().for_each(fill), 39 + None => v["entries"] = serde_json::json!({}), 40 + } 41 + } 37 42 } 38 - let l: Listing = serde_json::from_slice(body).map_err(io::Error::other)?; 39 - Ok(l.root) 43 + let mut v: serde_json::Value = serde_json::from_slice(body).map_err(io::Error::other)?; 44 + let mut root = v["root"].take(); 45 + fill(&mut root); 46 + serde_json::from_value(root).map_err(io::Error::other) 40 47 } 41 48 42 49 /// Listing of an unpacked store path (for paths without `.ls`). ··· 169 176 "bin":{"type":"regular","size":10,"executable":true,"narOffset":96}, 170 177 "link":{"type":"symlink","target":"bin"}}}}"#; 171 178 assert_eq!(from_listing(ls).unwrap(), t); 179 + remove(&dir).unwrap(); 180 + 181 + // as cache.lassul.us serves empty directories 182 + let ls = br#"{"version":1,"root":{"type":"directory","entries":{ 183 + "empty":{"type":"directory"}}}}"#; 184 + fs::create_dir_all(dir.join("empty")).unwrap(); 185 + assert_eq!(from_listing(ls).unwrap(), from_path(&dir).unwrap()); 186 + assert_eq!( 187 + from_listing(br#"{"version":1,"root":{"type":"directory"}}"#).unwrap(), 188 + FileTree(FileSystemObject::Directory(Directory { 189 + entries: BTreeMap::new() 190 + })) 191 + ); 172 192 remove(&dir).unwrap(); 173 193 174 194 // the caller's NarSize check rejects it