Skip to main content

nix_index/
frcode.rs

1//! A compact encoding for file tree entries based on sharing prefixes.
2//!
3//! This module contains a rust implementation of a variant of the `frcode` tool
4//! used by GNU findutils' locate. It has been extended to allow meta information
5//! to be attached to each entry so it is no longer compatible with the original
6//! frcode format.
7//! (See http://www.delorie.com/gnu/docs/findutils/locatedb.5.html for a description of the frcode format.)
8//!
9//! The basic building block of the encoding is a line. Each line has the following format:
10//! (the spaces are for readability only, they are not present in the encoding)
11//!
12//! ```text
13//! <metadata> <\x00 byte> <shared prefix differential> <additional path bytes> <newline character>
14//! ```
15//!
16//! Each entry holds two parts of data: metadata, which is just some arbitrary blob of NUL-terminated bytes
17//! and a path. Because we are storing file trees, the path will likely share a long prefix with the previous
18//! entry's path (we traverse directory entries in sorted order to maximize this chance), so we first store
19//! the length of the shared prefix.
20//!
21//! Since this length will likely be similar to the previous one (if there are many entries in `/foo/bar`, then they will
22//! all share a prefix of at least the length of `/foo/bar`) we only store the signed *difference* to the previous shared prefix length
23//! (This is why it's called a differential). For differences smaller than +/-127 we store them directly as a single byte. If the
24//! difference is greater than that, the first byte will by `0x80` (-128) indicating that the following two bytes represent the
25//! difference (with the high byte first [big endian]).
26//!
27//! As an example, consider the following non-encoded plaintext, where `:` separates the metadata from the path:
28//!
29//! ```text
30//! d:/
31//! d:/foo
32//! d:/foo/bar
33//! f:/foo/bar/test.txt
34//! f:/foo/bar/text.txt
35//! d:/foo/baz
36//! ```
37//!
38//! This text would be encoded as (using `[v]` to indicate a byte with the value of v)
39//!
40//! ```text
41//! d[0][0]/
42//! d[0][1]foo
43//! d[0][3]/bar
44//! f[0][4]/test.txt
45//! f[0][3]xt.txt
46//! d[0][-4]z
47//! ```
48//!
49//! At the beginning, there is no previous entry, so the shared prefix length must always be `0` (and so must the shared prefix differential).
50//! The second entry shares `1` byte with the first path so the difference is `1`. The third entry shares `4` bytes with the second one, which
51//! is `3` more than the shared length of the second one, so we encode a `3` followed by the non-shared bytes, and so on for the remaining entries.
52//! The last entry shares four bytes less than the second to last one did with its predecessor, so here the differential is negative.
53//!
54//! Through this encoding, the size of the index is typically reduces by a factor of 3 to 5.
55use std::cmp;
56use std::io::{self, BufRead, Write};
57use std::ops::{Deref, DerefMut};
58
59use memchr;
60use thiserror::Error;
61
62#[derive(Error, Debug)]
63pub enum Error {
64    #[error("I/O error: {0}")]
65    Io(#[from] io::Error),
66    #[error("length of shared prefix must be >= 0 and <= {previous_len} (length of previous item), but found: {shared_len}")]
67    SharedOutOfRange {
68        previous_len: usize,
69        shared_len: isize,
70    },
71    #[error("length of shared prefix too big: cannot add {shared_len} to {diff} without overflow")]
72    SharedOverflow { shared_len: isize, diff: isize },
73    #[error("missing terminating NUL byte for entry")]
74    MissingNul,
75    #[error("missing newline separator for entry")]
76    MissingNewline,
77    #[error("missing the shared prefix length differential for entry")]
78    MissingPrefixDifferential,
79}
80
81type Result<T> = std::result::Result<T, Error>;
82
83/// A buffer that may be resizable or not. This is used for decoding,
84/// where we want to make the buffer resizable as long as we haven't decoded
85/// a full entry yet but want to lock it as soon as we got a full entry.
86///
87/// This is necessary because we always need to be able to decode at least
88/// one entry to make progress, as we never return partial entries during decoding.
89struct ResizableBuf {
90    allow_resize: bool,
91    data: Vec<u8>,
92}
93
94impl ResizableBuf {
95    /// Allocates a new resizable buffer with the given initial size.
96    ///
97    /// The new buffer will allow resizing initially.
98    fn new(capacity: usize) -> ResizableBuf {
99        ResizableBuf {
100            data: vec![0; capacity],
101            allow_resize: true,
102        }
103    }
104
105    /// Resizes the buffer to hold at least `new_size` elements. Returns `true`
106    /// if resizing was successful (so that buffer can now hold at least `new_size` elements)
107    /// or `false` if not (meaning `new_size` is greater than the current size and resizing
108    /// was not allowed).
109    fn resize(&mut self, new_size: usize) -> bool {
110        if new_size <= self.data.len() {
111            return true;
112        }
113
114        if !self.allow_resize {
115            return false;
116        }
117
118        self.data.resize(new_size, b'\x00');
119        true
120    }
121}
122
123impl Deref for ResizableBuf {
124    type Target = [u8];
125
126    fn deref(&self) -> &[u8] {
127        &self.data
128    }
129}
130
131impl DerefMut for ResizableBuf {
132    fn deref_mut(&mut self) -> &mut [u8] {
133        &mut self.data
134    }
135}
136
137/// A decoder for the frcode format. It reads data from some input source
138/// and returns blocks of decoded entries.
139///
140/// It will not split the metadata/path parts of individual entries since
141/// the primary use case for this is searching, where it is enough to decode
142/// the entries that match.
143pub struct Decoder<R> {
144    /// The input source from which we decode
145    reader: R,
146    /// Position of the first byte of the path part of the last entry.
147    /// We need this to copy the shared prefix.
148    last_path: usize,
149    /// Position of the start of the entry that didn't fully fit in the buffer in the
150    /// last decode iteration. Since this entry was partial, it hasn't been returned to
151    /// the user yet and we need to continue decoding this entry in this iteration.
152    partial_entry_start: usize,
153    /// The length of the shared prefix for the current entry. This is necessary because
154    /// the shared length is stored as a difference, so we need the previous value to update it.
155    shared_len: isize,
156    /// The buffer into which we store the decoded bytes.
157    buf: ResizableBuf,
158    /// Current write position in buf. The next decoded byte should be written to buf[pos].
159    pos: usize,
160}
161
162impl<R: BufRead> Decoder<R> {
163    /// Construct a new decoder for the given source.
164    pub fn new(reader: R) -> Decoder<R> {
165        let capacity = 1_000_000;
166        Decoder {
167            reader,
168            buf: ResizableBuf::new(capacity),
169            pos: 0,
170            last_path: 0,
171            shared_len: 0,
172            partial_entry_start: 0,
173        }
174    }
175
176    /// Copies `self.shared_len` bytes from the previous entry's path into the output buffer.
177    ///
178    /// Returns false if the buffer was too small and could not be resized. In this case, no
179    /// bytes will be copied.
180    fn copy_shared(&mut self) -> Result<bool> {
181        let shared_len = self.shared_len as usize;
182        let new_pos = self.pos + shared_len;
183        let new_last_path = self.pos;
184        if !self.buf.resize(new_pos) {
185            return Ok(false);
186        }
187
188        if self.shared_len < 0 || self.last_path + shared_len > self.pos {
189            return Err(Error::SharedOutOfRange {
190                previous_len: self.pos - self.last_path,
191                shared_len: self.shared_len,
192            });
193        }
194
195        let (_, last) = self.buf.split_at_mut(self.last_path);
196        let (last, new) = last.split_at_mut(self.pos - self.last_path);
197        new[..shared_len].copy_from_slice(&last[..shared_len]);
198
199        self.pos += shared_len;
200        self.last_path = new_last_path;
201        Ok(true)
202    }
203
204    /// Copies bytes from the input reader to the output buffer until a `\x00` byte is read.
205    /// The NUL byte is included in the output buffer.
206    ///
207    /// Returns false if the output buffer was exhausted before a NUL byte could be found and
208    /// could not be resized. All bytes that were read before this situation was detected will
209    /// have already been copied to the output buffer in this case.
210    ///
211    /// It will also return false if the end of the input was reached.
212    fn read_to_nul(&mut self) -> Result<bool> {
213        loop {
214            let (done, len) = {
215                let &mut Decoder {
216                    ref mut reader,
217                    ref mut buf,
218                    ref mut pos,
219                    ..
220                } = self;
221                let input = match reader.fill_buf() {
222                    Ok(data) => data,
223                    Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
224                    Err(e) => return Err(Error::from(e)),
225                };
226
227                if input.is_empty() {
228                    return Ok(false);
229                }
230
231                let (done, len) = match memchr::memchr(b'\x00', input) {
232                    Some(i) => (true, i + 1),
233                    None => (false, input.len()),
234                };
235
236                let new_pos = *pos + len;
237                if buf.resize(new_pos) {
238                    buf[*pos..new_pos].copy_from_slice(&input[..len]);
239                    *pos = new_pos;
240                    (done, len)
241                } else {
242                    return Ok(false);
243                }
244            };
245            self.reader.consume(len);
246            if done {
247                return Ok(true);
248            }
249        }
250    }
251
252    /// Read the differential from the input reader. This function will return an error
253    /// if the end of input has been reached.
254    fn decode_prefix_diff(&mut self) -> Result<i16> {
255        let mut buf = [0; 1];
256        self.reader
257            .read_exact(&mut buf)
258            .map_err(|_| Error::MissingPrefixDifferential)?;
259
260        if buf[0] != 0x80 {
261            Ok((buf[0] as i8) as i16)
262        } else {
263            let mut buf = [0; 2];
264            self.reader
265                .read_exact(&mut buf)
266                .map_err(|_| Error::MissingPrefixDifferential)?;
267            let high = buf[0] as i16;
268            let low = buf[1] as i16;
269            Ok(high << 8 | low)
270        }
271    }
272
273    /// Decodes some entries to fill the buffer and returns a block of decoded entries.
274    ///
275    /// It will decode as many entries as fit into the internal buffer, but at least one.
276    /// In the returned block of bytes, an entry's metadata and path will be separated by a NUL byte
277    /// and entries will be terminated with a newline character. This allows for fast searching with
278    /// a line based searcher.
279    ///
280    /// The function does not return partially decoded entries. Because of this, the size of returned
281    /// slice will vary from call to call. The last entry which did not fully fit into the buffer yet
282    /// will be returned as the first entry at the next call.
283    pub fn decode(&mut self) -> Result<&mut [u8]> {
284        // Save end pointer from previous iteration and reset write position
285        let end = self.pos;
286        self.pos = 0;
287
288        // We need to preserve some data from the previous iteration, namely:
289        //
290        // * all data after the `self.last_path` position, for copying the shared prefix
291        // * everything from the start of the partial entry, since this entry wasn't fully decoded
292        //   in the last iteration and we want to continue decoding it now
293        //
294        // If we stopped decoding the partial entry after already copying the shared prefix, then
295        // `last_path` will already point to the partial entry so it will be greater than `partial_entry_start`.
296        //
297        // If we stopped decoding during copying the metadata though, which comes before we copy the shared
298        // prefix, then `last_path` will point to the previous entry's path, so it will be smaller than
299        // `partial_entry_start`.
300        //
301        // To support both these cases, we take the minimum here.
302        let mut copy_pos = cmp::min(self.partial_entry_start, self.last_path);
303
304        // Since we sometimes copy more than just the partial entry, we need to know where the partial entry
305        // starts as that is the first position that we want to return (everything before that was already
306        // part of an entry returned in the last iteration).
307        let item_start = self.partial_entry_start - copy_pos;
308
309        // Shift the last path, because we copy it from copy_pos.. to 0..
310        self.last_path -= copy_pos;
311
312        // Now we can do the actual copying. We cannot use copy_from_slice here since source and target
313        // may overlap.
314        while copy_pos < end {
315            self.buf[self.pos] = self.buf[copy_pos];
316            self.pos += 1;
317            copy_pos += 1;
318        }
319
320        // Allow resizing the buffer, since we haven't decoded a full entry yet
321        self.buf.allow_resize = true;
322
323        // If the the last decoded byte in the buffer is a NUL byte, that means that
324        // we are now at the start of the path part of the entry. This means that
325        // we need to copy the shared prefix now.
326        let mut found_nul = self.pos > 0 && self.buf[self.pos - 1] == b'\x00';
327        if found_nul {
328            self.copy_shared()?;
329        }
330
331        // At this point, we are guaranteed to be in either the metadata part or the non-shared part
332        // of an entry. In both cases, the action that we need to take is the same: copy data till
333        // the next NUL byte. After the NUL byte, we know that we are at the end of the metadata part,
334        // so we read a differential and copy the shared prefix, and repeat.
335        //
336        // Note that this loop doesn't care about where entries end. Only the path part of each entry requires
337        // special processing, so we can jump from NUL byte to NUL byte, decode the path and then just copy
338        // the data from the source when jumping to the next NUL byte.
339        loop {
340            // Read data up to the next nul byte.
341            if !self.read_to_nul()? {
342                break;
343            }
344
345            // If we have already found a NUL byte before this, so we've now got two NUL bytes, so
346            // we've got at least one full entry in between.
347            self.buf.allow_resize = !found_nul;
348
349            // We found a NUL byte. Note that we need to set this *after* updating allow_resize,
350            // since allow_resize should be set to false only after we've found two NUL bytes.
351            found_nul = true;
352
353            // Parse the next prefix length difference
354            let diff = self.decode_prefix_diff()? as isize;
355
356            // Update the shared len
357            self.shared_len = self
358                .shared_len
359                .checked_add(diff)
360                .ok_or(Error::SharedOverflow {
361                    shared_len: self.shared_len,
362                    diff,
363                })?;
364
365            // Copy the shared prefix
366            if !self.copy_shared()? {
367                break;
368            }
369        }
370
371        // Since we don't want to return partially decoded items, we need to find the end of the last entry.
372        self.partial_entry_start =
373            memchr::memrchr(b'\n', &self.buf[..self.pos]).ok_or(Error::MissingNewline)? + 1;
374        Ok(&mut self.buf[item_start..self.partial_entry_start])
375    }
376}
377
378/// This struct implements an encoder for the frcode format. The encoder
379/// writes directly to the underlying `Write` instance.
380///
381/// To encode an entry you should first call `write_meta` a number of times
382/// to fill the meta data portion. Then, call `write_path` once to finialize the entry.
383///
384/// One important property of this encoder is that it is safe to open and close
385/// it multiple times on the same stream, like this:
386///
387/// ```text
388/// {
389///   let encoder1 = Encoder::new(&mut stream);
390/// } // encoder1 gets dropped here
391/// {
392///   let encoder2 = Encoder::new(&mut stream);
393/// }
394/// ```
395///
396/// To support this, the encoder has a "footer" item that will get written when it is dropped.
397/// This is necessary because we need to write at least one more entry to reset the shared prefix
398/// length to zero, since the next encoder will expect that as initial state.
399pub struct Encoder<W: Write> {
400    writer: W,
401    last: Vec<u8>,
402    shared_len: i16,
403    footer_meta: Vec<u8>,
404    footer_path: Vec<u8>,
405    footer_written: bool,
406}
407
408impl<W: Write> Drop for Encoder<W> {
409    fn drop(&mut self) {
410        self.write_footer().expect("failed to write footer")
411    }
412}
413
414impl<W: Write> Encoder<W> {
415    /// Constructs a new encoder for the specific writer.
416    ///
417    /// The encoder will write the given `footer_meta` and `footer_path` as the last entry.
418    ///
419    /// # Panics
420    ///
421    /// If either `footer_meta` or `footer_path` contain NUL or newline bytes.
422    pub fn new(writer: W, footer_meta: Vec<u8>, footer_path: Vec<u8>) -> Encoder<W> {
423        assert!(
424            !footer_meta.contains(&b'\x00'),
425            "footer meta must not contain null bytes"
426        );
427        assert!(
428            !footer_path.contains(&b'\x00'),
429            "footer path must not contain null bytes"
430        );
431        assert!(
432            !footer_meta.contains(&b'\n'),
433            "footer meta must not contain newlines"
434        );
435        assert!(
436            !footer_path.contains(&b'\n'),
437            "footer path must not contain newlines"
438        );
439        Encoder {
440            writer,
441            last: Vec::new(),
442            shared_len: 0,
443            footer_meta,
444            footer_path,
445            footer_written: false,
446        }
447    }
448
449    /// Writes the specific shared prefix differential to the output stream.
450    ///
451    /// This function takes care of the variable-length encoding using for prefix differentials
452    /// in the frcode format.
453    fn encode_diff(&mut self, diff: i16) -> io::Result<()> {
454        let low = (diff & 0xFF) as u8;
455        if diff.abs() < i8::MAX as i16 {
456            self.writer.write_all(&[low])?;
457        } else {
458            let high = ((diff >> 8) & 0xFF) as u8;
459            self.writer.write_all(&[0x80, high, low])?;
460        }
461        Ok(())
462    }
463
464    /// Writes the meta data of an entry to the output stream.
465    ///
466    /// This function can be called multiple times to extend the current meta data part.
467    /// Since the meta data is written as-is to the output stream, calling the function
468    /// multiple times will concatenate the meta data of all calls.
469    ///
470    /// # Panics
471    ///
472    /// If the meta data contains NUL bytes or newlines.
473    pub fn write_meta(&mut self, meta: &[u8]) -> io::Result<()> {
474        assert!(
475            !meta.contains(&b'\x00'),
476            "entry must not contain null bytes"
477        );
478        assert!(!meta.contains(&b'\n'), "entry must not contain newlines");
479
480        self.writer.write_all(meta)?;
481        Ok(())
482    }
483
484    /// Finalizes an entry by encoding its path to the output stream.
485    ///
486    /// This function should be called after you've finished writing the meta data for
487    /// the current entry. It will terminate the meta data part by writing the NUL byte
488    /// and then encode the path into the output stream.
489    ///
490    /// The entry will be terminated with a newline.
491    ///
492    /// # Panics
493    ///
494    /// If the path contains NUL bytes or newlines.
495    pub fn write_path(&mut self, path: Vec<u8>) -> io::Result<()> {
496        assert!(
497            !path.contains(&b'\x00'),
498            "entry must not contain null bytes"
499        );
500        assert!(!path.contains(&b'\x00'), "entry must not contain newlines");
501        self.writer.write_all(b"\x00")?;
502
503        let mut shared: isize = 0;
504        let max_shared = i16::MAX as isize;
505        for (a, b) in self.last.iter().zip(path.iter()) {
506            if a != b || shared > max_shared {
507                break;
508            }
509            shared += 1;
510        }
511        let shared = shared as i16;
512
513        let diff = shared - self.shared_len;
514        self.encode_diff(diff)?;
515
516        self.last = path;
517        self.shared_len = shared;
518
519        let pos = shared as usize;
520        self.writer.write_all(&self.last[pos..])?;
521        self.writer.write_all(b"\n")?;
522
523        Ok(())
524    }
525
526    /// Writes the footer entry.
527    ///
528    /// The footer entry will not share any prefix with the preceding entry,
529    /// so after this function, the shared prefix length is zero. This guarantees
530    /// that we can start another Encoder after this item, since the Encoder expects
531    /// the initial shared prefix length to be zero.
532    fn write_footer(&mut self) -> io::Result<()> {
533        if self.footer_written {
534            return Ok(());
535        }
536
537        let diff = -self.shared_len;
538        self.writer.write_all(&self.footer_meta)?;
539        self.writer.write_all(b"\x00")?;
540        self.encode_diff(diff)?;
541        self.writer.write_all(&self.footer_path)?;
542        self.writer.write_all(b"\n")?;
543        self.footer_written = true;
544        Ok(())
545    }
546
547    /// Finishes the encoder by writing the footer entry.
548    ///
549    /// This function is called by drop, but calling it explictly is recommended as
550    /// drop has no way to report IO errors that may occur during writing the footer.
551    pub fn finish(mut self) -> io::Result<()> {
552        self.write_footer()?;
553
554        Ok(())
555    }
556}