1use std::fs::File;
2use std::io::{self, BufReader, BufWriter, Read, Seek, Write};
7use std::path::Path;
8
9use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
10use grep;
11use grep::matcher::{LineMatchKind, Match, Matcher, NoError};
12use memchr::{memchr, memrchr};
13use regex::bytes::Regex;
14use regex_syntax::ast::{AssertionKind, Ast, Literal};
15use serde_json;
16use thiserror::Error;
17use zstd;
18
19use crate::files::{FileTree, FileTreeEntry};
20use crate::frcode;
21use crate::package::StorePath;
22
23const FORMAT_VERSION: u64 = 1;
27
28const FILE_MAGIC: &[u8] = b"NIXI";
31
32pub struct Writer {
34 writer: Option<BufWriter<zstd::Encoder<'static, File>>>,
37}
38
39impl Drop for Writer {
42 fn drop(&mut self) {
43 if self.writer.is_some() {
44 self.finish_encoder().expect("failed to flush database");
45 }
46 }
47}
48
49impl Writer {
50 pub fn create<P: AsRef<Path>>(path: P, level: i32) -> io::Result<Writer> {
53 let mut file = File::create(path)?;
54 file.write_all(FILE_MAGIC)?;
55 file.write_u64::<LittleEndian>(FORMAT_VERSION)?;
56 let mut encoder = zstd::Encoder::new(file, level)?;
57 encoder.multithread(num_cpus::get() as u32)?;
58
59 Ok(Writer {
60 writer: Some(BufWriter::new(encoder)),
61 })
62 }
63
64 pub fn add(
67 &mut self,
68 path: StorePath,
69 files: FileTree,
70 filter_prefix: &[u8],
71 ) -> io::Result<()> {
72 let entries = files.to_list(filter_prefix);
73
74 if entries.is_empty() {
76 return Ok(());
77 }
78 let writer = self.writer.as_mut().expect("not dropped yet");
79 let mut encoder = frcode::Encoder::new(
80 writer,
81 b"p".to_vec(),
82 serde_json::to_vec(&path).expect("failed to serialize path"),
83 );
84 for entry in entries {
85 entry.encode(&mut encoder)?;
86 }
87 Ok(())
88 }
89
90 fn finish_encoder(&mut self) -> io::Result<File> {
95 let writer = self.writer.take().expect("not dropped yet");
96 let encoder = writer.into_inner()?;
97 encoder.finish()
98 }
99
100 pub fn finish(mut self) -> io::Result<u64> {
102 let mut file = self.finish_encoder()?;
103 file.stream_position()
104 }
105}
106
107#[derive(Error, Debug)]
108pub enum Error {
109 #[error("expected file to start with nix-index file magic 'NIXI', but found '{found:?}' (is this a valid nix-index database file?)")]
110 UnsupportedFileType { found: Vec<u8> },
111 #[error("this executable only supports the nix-index database version {}, but found a database with version {found}", FORMAT_VERSION)]
112 UnsupportedVersion { found: u64 },
113 #[error("database corrupt, found a file entry without a matching package entry")]
114 MissingPackageEntry,
115 #[error("database corrupt, frcode error: {0}")]
116 Frcode(#[from] frcode::Error),
117 #[error("database corrupt, could not parse entry: {entry:?}")]
118 EntryParse { entry: Vec<u8> },
119 #[error("database corrupt, could not parse store path: {path:?}")]
120 StorePathParse { path: Vec<u8> },
121 #[error("I/O error: {0}")]
122 Io(#[from] io::Error),
123 #[error("grep error: {0}")]
124 Grep(#[from] grep::regex::Error),
125}
126
127type Result<T> = std::result::Result<T, Error>;
128
129pub struct Reader {
131 decoder: frcode::Decoder<BufReader<zstd::Decoder<'static, BufReader<File>>>>,
132}
133
134impl Reader {
135 pub fn open<P: AsRef<Path>>(path: P) -> Result<Reader> {
139 let mut file = File::open(path)?;
140 let mut magic = [0u8; 4];
141 file.read_exact(&mut magic)?;
142
143 if magic != FILE_MAGIC {
144 return Err(Error::UnsupportedFileType {
145 found: magic.to_vec(),
146 });
147 }
148
149 let version = file.read_u64::<LittleEndian>()?;
150 if version != FORMAT_VERSION {
151 return Err(Error::UnsupportedVersion { found: version });
152 }
153
154 let decoder = zstd::Decoder::new(file)?;
155 Ok(Reader {
156 decoder: frcode::Decoder::new(BufReader::new(decoder)),
157 })
158 }
159
160 pub fn query(self, exact_regex: &Regex) -> Query<'_, '_> {
164 Query {
165 reader: self,
166 exact_regex,
167 hash: None,
168 package_pattern: None,
169 }
170 }
171
172 #[allow(clippy::print_stdout)]
174 pub fn dump(&mut self) -> Result<()> {
175 loop {
176 let block = self.decoder.decode()?;
177 if block.is_empty() {
178 break;
179 }
180 for line in block.split(|c| *c == b'\n') {
181 println!("{:?}", String::from_utf8_lossy(line));
182 }
183 println!("-- block boundary");
184 }
185 Ok(())
186 }
187}
188
189pub struct Query<'a, 'b> {
191 reader: Reader,
193
194 exact_regex: &'a Regex,
196
197 hash: Option<String>,
199
200 package_pattern: Option<&'b Regex>,
202}
203
204impl<'a, 'b> Query<'a, 'b> {
205 pub fn hash(self, hash: Option<String>) -> Query<'a, 'b> {
207 Query { hash, ..self }
208 }
209
210 pub fn package_pattern(self, package_pattern: Option<&'b Regex>) -> Query<'a, 'b> {
212 Query {
213 package_pattern,
214 ..self
215 }
216 }
217
218 pub fn run(self) -> Result<ReaderIter<'a, 'b>> {
222 let mut expr = regex_syntax::ast::parse::Parser::new()
223 .parse(self.exact_regex.as_str())
224 .expect("regex cannot be invalid");
225 {
228 let mut stack = vec![&mut expr];
229 while let Some(e) = stack.pop() {
230 match e {
231 Ast::Assertion(a) if a.kind == AssertionKind::StartLine => {
232 *e = Ast::Literal(Box::new(Literal {
233 span: a.span,
234 c: '\0',
235 kind: regex_syntax::ast::LiteralKind::Verbatim,
236 }))
237 }
238 Ast::Group(g) => stack.push(&mut g.ast),
239 Ast::Repetition(r) => stack.push(&mut r.ast),
240 Ast::Concat(c) => stack.extend(c.asts.iter_mut()),
241 Ast::Alternation(a) => stack.extend(a.asts.iter_mut()),
242 _ => {}
243 }
244 }
245 }
246 let mut regex_builder = grep::regex::RegexMatcherBuilder::new();
247 regex_builder.line_terminator(Some(b'\n')).multi_line(true);
248
249 let grep = regex_builder.build(&format!("{}", expr))?;
250 Ok(ReaderIter {
251 reader: self.reader,
252 found: Vec::new(),
253 found_without_package: Vec::new(),
254 pattern: grep,
255 exact_pattern: self.exact_regex,
256 package_entry_pattern: regex_builder.build("^p\0").expect("valid regex"),
257 package_name_pattern: self.package_pattern,
258 package_hash: self.hash,
259 })
260 }
261}
262
263pub struct ReaderIter<'a, 'b> {
265 reader: Reader,
267 found: Vec<(StorePath, FileTreeEntry)>,
269 found_without_package: Vec<FileTreeEntry>,
275 pattern: grep::regex::RegexMatcher,
283 exact_pattern: &'a Regex,
286 package_entry_pattern: grep::regex::RegexMatcher,
288 package_name_pattern: Option<&'b Regex>,
290 package_hash: Option<String>,
292}
293
294fn consume_no_error<T>(e: NoError) -> T {
295 panic!("impossible: {}", e)
296}
297
298fn next_matching_line<M: Matcher<Error = NoError>>(
299 matcher: M,
300 buf: &[u8],
301 mut start: usize,
302) -> Option<Match> {
303 while let Some(candidate) = matcher
304 .find_candidate_line(&buf[start..])
305 .unwrap_or_else(consume_no_error)
306 {
307 if start == buf.len() {
311 return None;
312 };
313
314 let (pos, confirmed) = match candidate {
315 LineMatchKind::Confirmed(pos) => (start + pos, true),
316 LineMatchKind::Candidate(pos) => (start + pos, false),
317 };
318
319 let line_start = memrchr(b'\n', &buf[..pos]).map_or(0, |x| x + 1);
320 let line_end = memchr(b'\n', &buf[pos..]).map_or(buf.len(), |x| x + pos + 1);
321
322 if !confirmed
323 && !matcher
324 .is_match(&buf[line_start..line_end])
325 .unwrap_or_else(consume_no_error)
326 {
327 start = line_end;
328 continue;
329 }
330
331 return Some(Match::new(line_start, line_end));
332 }
333 None
334}
335
336impl<'a, 'b> ReaderIter<'a, 'b> {
337 fn fill_buf(&mut self) -> Result<()> {
339 while self.found.is_empty() {
341 let &mut ReaderIter {
342 ref mut reader,
343 ref package_entry_pattern,
344 ref package_name_pattern,
345 ref package_hash,
346 ..
347 } = self;
348 let block = reader.decoder.decode()?;
349
350 if block.is_empty() {
352 return Ok(());
353 }
354
355 let mut cached_package: Option<(StorePath, usize)> = None;
363 let mut no_more_package = false;
364 let mut find_package = |item_end| -> Result<_> {
365 if let Some((ref pkg, end)) = cached_package {
366 if item_end < end {
367 return Ok(Some((pkg.clone(), end)));
368 }
369 }
370
371 if no_more_package {
372 return Ok(None);
373 }
374
375 let mat = match next_matching_line(package_entry_pattern, block, item_end) {
376 Some(v) => v,
377 None => {
378 no_more_package = true;
379 return Ok(None);
380 }
381 };
382
383 let json = &block[mat.start() + 2..mat.end() - 1];
384 let pkg: StorePath =
385 serde_json::from_slice(json).map_err(|_| Error::StorePathParse {
386 path: json.to_vec(),
387 })?;
388 cached_package = Some((pkg.clone(), mat.end()));
389 Ok(Some((pkg, mat.end())))
390 };
391
392 let should_search_package = |pkg: &StorePath| -> bool {
394 package_name_pattern.is_none_or(|r| r.is_match(pkg.name().as_bytes()))
395 && package_hash.as_ref().is_none_or(|h| h == &pkg.hash())
396 };
397
398 let mut pos = 0;
399 if !self.found_without_package.is_empty() {
402 if let Some((pkg, end)) = find_package(0)? {
403 if !should_search_package(&pkg) {
404 pos = end;
406 self.found_without_package.truncate(0);
407 } else {
408 for entry in self.found_without_package.split_off(0) {
409 self.found.push((pkg.clone(), entry));
410 }
411 }
412 }
413 }
414
415 while let Some(mat) = next_matching_line(&self.pattern, block, pos) {
417 pos = mat.end();
418 let entry = &block[mat.start()..mat.end() - 1];
419 if self
421 .package_entry_pattern
422 .is_match(entry)
423 .unwrap_or_else(consume_no_error)
424 {
425 continue;
426 }
427
428 if let Some((pkg, end)) = find_package(mat.end())? {
431 if !should_search_package(&pkg) {
432 pos = end;
434 continue;
435 }
436 }
437
438 let entry = FileTreeEntry::decode(entry).ok_or_else(|| Error::EntryParse {
439 entry: entry.to_vec(),
440 })?;
441
442 if !self.exact_pattern.is_match(&entry.path) {
444 continue;
445 }
446
447 match find_package(mat.end())? {
448 None => self.found_without_package.push(entry),
449 Some((pkg, _)) => self.found.push((pkg, entry)),
450 }
451 }
452 }
453 Ok(())
454 }
455
456 fn next_match(&mut self) -> Result<Option<(StorePath, FileTreeEntry)>> {
458 self.fill_buf()?;
459 Ok(self.found.pop())
460 }
461}
462
463impl<'a, 'b> Iterator for ReaderIter<'a, 'b> {
464 type Item = Result<(StorePath, FileTreeEntry)>;
465
466 fn next(&mut self) -> Option<Self::Item> {
467 match self.next_match() {
468 Err(e) => Some(Err(e)),
469 Ok(v) => v.map(Ok),
470 }
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 #[test]
479 fn test_next_matching_line_package() {
480 let matcher = grep::regex::RegexMatcherBuilder::new()
481 .line_terminator(Some(b'\n'))
482 .multi_line(true)
483 .build("^p")
484 .expect("valid regex");
485 let buffer = br#"
486SOME LINE
487pDATA
488ANOTHER LINE
489 "#;
490
491 let mat = next_matching_line(matcher, buffer, 0);
492 assert_eq!(mat, Some(Match::new(11, 17)));
493 }
494}