s4lib/readers/
fixedstructreader.rs

1// src/readers/fixedstructreader.rs
2
3//! Implements a [`FixedStructReader`],
4//! the driver of deriving [`FixedStruct`s] (acct/lastlog/utmp/etc.)
5//! from a fixed C-struct format file using a [`BlockReader`].
6//!
7//! Sibling of [`SyslogProcessor`]. But simpler in a number of ways due to
8//! predictable format of the fixedsturct files. Also, a `FixedStructReader`
9//! does not presume entries are in chronological order.
10//! Whereas `SyslogProcessor` presumes entries are in chronological order.
11//! This makes a big difference for implementations.
12//!
13//! This is an _s4lib_ structure used by the binary program _s4_.
14//!
15//! Implements [Issue #70].
16//!
17//! [`FixedStructReader`]: self::FixedStructReader
18//! [`FixedStruct`s]: crate::data::fixedstruct::FixedStruct
19//! [`BlockReader`]: crate::readers::blockreader::BlockReader
20//! [`SyslogProcessor`]: crate::readers::syslogprocessor::SyslogProcessor
21//! [Issue #70]: https://github.com/jtmoon79/super-speedy-syslog-searcher/issues/70
22
23// TODO: ask question on SO about difference in
24//       `e_termination` and `e_exit` in `struct exit_status`
25//       https://elixir.bootlin.com/glibc/glibc-2.37/source/bits/utmp.h#L48
26
27use std::collections::{
28    BTreeMap,
29    LinkedList,
30};
31use std::fmt;
32use std::io::{
33    Error,
34    ErrorKind,
35    Result,
36};
37
38use ::more_asserts::{
39    debug_assert_ge,
40    debug_assert_le,
41};
42#[allow(unused_imports)]
43use ::si_trace_print::{
44    de,
45    def1n,
46    def1o,
47    def1x,
48    def1ñ,
49    defn,
50    defo,
51    defx,
52    defñ,
53    den,
54    deo,
55    dex,
56    deñ,
57    pfn,
58    pfo,
59    pfx,
60};
61
62use crate::common::{
63    Count,
64    FPath,
65    FileOffset,
66    FileSz,
67    FileType,
68    FileTypeFixedStruct,
69    ResultFind,
70    debug_panic,
71    summary_stat,
72    summary_stats_enabled,
73};
74use crate::data::datetime::{
75    dt_after_or_before,
76    dt_pass_filters,
77    DateTimeL,
78    DateTimeLOpt,
79    FixedOffset,
80    Result_Filter_DateTime1,
81    Result_Filter_DateTime2,
82    SystemTime,
83};
84use crate::data::fixedstruct::{
85    buffer_to_fixedstructptr,
86    convert_datetime_tvpair,
87    filesz_to_types,
88    tv_pair_type,
89    FixedStruct,
90    FixedStructDynPtr,
91    FixedStructType,
92    FixedStructTypeSet,
93    Score,
94    ENTRY_SZ_MAX,
95    ENTRY_SZ_MIN,
96    TIMEVAL_SZ_MAX,
97};
98use crate::readers::blockreader::{
99    BlockIndex,
100    BlockOffset,
101    BlockReader,
102    BlockSz,
103    ResultReadDataToBuffer,
104};
105use crate::readers::summary::Summary;
106use crate::{
107    de_err,
108    de_wrn,
109    e_err,
110};
111
112// -----------------
113// FixedStructReader
114
115/// Map [`FileOffset`] To [`FixedStruct`].
116///
117/// Storage for `FixedStruct` found from the underlying `BlockReader`.
118/// FileOffset key is the first byte/offset that begins the `FixedStruct`.
119///
120/// [`FileOffset`]: crate::common::FileOffset
121/// [`FixedStruct`]: crate::data::fixedstruct::FixedStruct
122pub type FoToEntry = BTreeMap<FileOffset, FixedStruct>;
123
124/// Map [`FileOffset`] To `FileOffset`
125///
126/// [`FileOffset`]: crate::common::FileOffset
127pub type FoToFo = BTreeMap<FileOffset, FileOffset>;
128
129pub type FoList = LinkedList<FileOffset>;
130
131type MapTvPairToFo = BTreeMap<tv_pair_type, FileOffset>;
132
133/// [`FixedStructReader.find`*] functions results.
134///
135/// [`FixedStructReader.find`*]: self::FixedStructReader#method.find_entry
136pub type ResultFindFixedStruct = ResultFind<(FileOffset, FixedStruct), (Option<FileOffset>, Error)>;
137
138pub type ResultFindFixedStructProcZeroBlock = ResultFind<(FixedStructType, Score, ListFileOffsetFixedStructPtr), Error>;
139
140pub type ResultTvFo = Result<(usize, usize, usize, usize, MapTvPairToFo)>;
141
142#[cfg(test)]
143pub type DroppedBlocks = LinkedList<BlockOffset>;
144
145type ListFileOffsetFixedStructPtr = LinkedList<(FileOffset, FixedStructDynPtr)>;
146
147/// Enum return value for [`FixedStructReader::new`].
148#[derive(Debug)]
149pub enum ResultFixedStructReaderNew<E> {
150    /// `FixedStructReader::new` was successful and returns the
151    /// `FixedStructReader`
152    FileOk(FixedStructReader),
153    FileErrEmpty,
154    FileErrTooSmall(String),
155    /// No valid fixedstruct
156    FileErrNoValidFixedStruct,
157    /// No fixedstruct within the datetime filters
158    FileErrNoFixedStructWithinDtFilters,
159    /// Carries the `E` error data. This is how an [`Error`] is carried between
160    /// a processing thread and the main printing thread
161    FileErrIo(E),
162}
163
164pub type ResultFixedStructReaderNewError = ResultFixedStructReaderNew<Error>;
165
166#[derive(Debug)]
167pub enum ResultFixedStructReaderScoreFile<E> {
168    /// `score_file` was successful; return the `FixedStructType`, `Score`,
169    /// already processed `FixedStrcutDynPtr` entries (with associated offsets)
170    FileOk(FixedStructType, Score, ListFileOffsetFixedStructPtr),
171    FileErrEmpty,
172    /// No valid fixedstruct
173    FileErrNoValidFixedStruct,
174    /// No high score for the file
175    FileErrNoHighScore,
176    /// Carries the `E` error data. This is how an [`Error`] is carried between
177    /// a processing thread and the main printing thread
178    FileErrIo(E),
179}
180
181pub type ResultFixedStructReaderScoreFileError = ResultFixedStructReaderScoreFile<Error>;
182
183/// A specialized reader that uses [`BlockReader`] to read [`FixedStruct`]
184/// entries in a file.
185///
186/// The `FixedStructReader` converts `\[u8\]` to `FixedStruct` in
187/// [`buffer_to_fixedstructptr`].
188///
189/// ## Summary of operation
190///
191/// A `FixedStructReader` first deteremines the `FixedStructType` of the file
192/// in [`preprocess_fixedstructtype`].
193/// Then it scans all ***t***ime ***v***alues in each entry to determine the
194/// order to process the entries in [`preprocess_timevalues`]. This implies the
195/// `blockreader` must read the entire file into memory. So far, "in the wild"
196/// user accounting records are only a few kilobytes at most. So reading the
197/// entire file into memory should not put much strain on memory usage of a
198/// typical desktop or server.
199/// The processing of time values is done first and for the entire file
200/// because records may not be stored in chronological order.
201/// Then the caller makes repeated calls to [`process_entry_at`] which processes
202/// the `FixedStruct`s found in the file.
203///
204/// 0x00 byte and 0xFF byte fixedstructs are considered a null entry and
205/// ignored.
206///
207/// _XXX: not a rust "Reader"; does not implement trait [`Read`]._
208///
209/// [`buffer_to_fixedstructptr`]: crate::data::fixedstruct::buffer_to_fixedstructptr
210/// [`BlockReader`]: crate::readers::blockreader::BlockReader
211/// [`Read`]: std::io::Read
212/// [`preprocess_fixedstructtype`]: FixedStructReader::preprocess_fixedstructtype
213/// [`preprocess_timevalues`]: FixedStructReader::preprocess_timevalues
214/// [`process_entry_at`]: FixedStructReader::process_entry_at
215pub struct FixedStructReader {
216    pub(crate) blockreader: BlockReader,
217    fixedstruct_type: FixedStructType,
218    filetype_fixedstruct: FileTypeFixedStruct,
219    /// Size of a single [`FixedStruct`] entry.
220    fixedstruct_size: usize,
221    /// The highest score found during `preprocess_file`.
222    /// Used to determine the `FixedStructType` of the file.
223    high_score: Score,
224    /// Timezone to use for conversions using function
225    /// [`convert_tvpair_to_datetime`].
226    ///
227    /// [`convert_tvpair_to_datetime`]: crate::data::fixedstruct::convert_tvpair_to_datetime
228    tz_offset: FixedOffset,
229    /// A temporary hold for [`FixedStruct`] entries found
230    /// by [`preprocess_fixedstructtype`]. Use `insert_cache_entry` and
231    /// `remove_entry` to manage this cache.
232    ///
233    /// [`preprocess_fixedstructtype`]: FixedStructReader::preprocess_fixedstructtype
234    pub(crate) cache_entries: FoToEntry,
235    /// A mapping of all entries in the entire file (that pass the datetime
236    /// filters), mapped by [`tv_pair_type`] to [`FileOffset`]. Created by
237    /// [`preprocess_timevalues`].
238    ///
239    /// [`preprocess_timevalues`]: FixedStructReader::preprocess_timevalues
240    pub(crate) map_tvpair_fo: MapTvPairToFo,
241    pub(crate) block_use_count: BTreeMap<BlockOffset, usize>,
242    /// The first entry found in the file, by `FileOffset`
243    pub(crate) first_entry_fileoffset: FileOffset,
244    /// "high watermark" of `FixedStruct` stored in `self.cache_entries`
245    pub(crate) entries_stored_highest: usize,
246    pub(crate) entries_out_of_order: usize,
247    /// Internal stats - hits of `self.cache_entries` in `find_entry*` functions.
248    pub(super) entries_hits: Count,
249    /// Internal stats - misses of `self.cache_entries` in `find_entry*` functions.
250    pub(super) entries_miss: Count,
251    /// `Count` of `FixedStruct`s processed.
252    ///
253    /// Distinct from `self.cache_entries.len()` as that may have contents removed.
254    pub(super) entries_processed: Count,
255    /// Summary statistic.
256    /// First (soonest) processed [`DateTimeL`] (not necessarily printed,
257    /// not representative of the entire file).
258    pub(super) dt_first: DateTimeLOpt,
259    /// Summary statistic.
260    /// Last (latest) processed [`DateTimeL`] (not necessarily printed,
261    /// not representative of the entire file).
262    pub(super) dt_last: DateTimeLOpt,
263    /// Summary statistic.
264    /// `Count` of dropped `FixedStruct`.
265    pub(super) drop_entry_ok: Count,
266    /// Summary statistic.
267    /// `Count` of failed drop attempts of `FixedStruct`.
268    pub(super) drop_entry_errors: Count,
269    /// testing-only tracker of successfully dropped `FixedStruct`
270    #[cfg(test)]
271    pub(crate) dropped_blocks: DroppedBlocks,
272    pub(super) map_tvpair_fo_max_len: usize,
273    /// The last [`Error`], if any, as a `String`. Set by [`set_error`].
274    ///
275    /// Annoyingly, cannot [Clone or Copy `Error`].
276    ///
277    /// [`Error`]: std::io::Error
278    /// [Clone or Copy `Error`]: https://github.com/rust-lang/rust/issues/24135
279    /// [`set_error`]: self::FixedStructReader#method.set_error
280    // TRACKING: https://github.com/rust-lang/rust/issues/24135
281    error: Option<String>,
282}
283
284impl fmt::Debug for FixedStructReader {
285    fn fmt(
286        &self,
287        f: &mut fmt::Formatter,
288    ) -> fmt::Result {
289        f.debug_struct("FixedStructReader")
290            .field("Path", &self.path())
291            .field("Entries", &self.cache_entries.len())
292            .field("tz_offset", &self.tz_offset)
293            .field("Error?", &self.error)
294            .finish()
295    }
296}
297
298// TODO: [2023/04] remove redundant variable prefix name `fixedstructreader_`
299// TODO: [2023/05] instead of having 1:1 manual copying of `FixedStructReader`
300//       fields to `SummaryFixedStructReader` fields, just store a
301//       `SummaryFixedStructReader` in `FixedStructReader` and update directly.
302#[allow(non_snake_case)]
303#[derive(Clone, Default, Eq, PartialEq, Debug)]
304pub struct SummaryFixedStructReader {
305    pub fixedstructreader_fixedstructtype_opt: Option<FixedStructType>,
306    pub fixedstructreader_filetypefixedstruct_opt: Option<FileTypeFixedStruct>,
307    pub fixedstructreader_fixedstruct_size: usize,
308    pub fixedstructreader_high_score: Score,
309    pub fixedstructreader_utmp_entries: Count,
310    pub fixedstructreader_first_entry_fileoffset: FileOffset,
311    pub fixedstructreader_entries_out_of_order: usize,
312    pub fixedstructreader_utmp_entries_max: Count,
313    pub fixedstructreader_utmp_entries_hit: Count,
314    pub fixedstructreader_utmp_entries_miss: Count,
315    pub fixedstructreader_drop_entry_ok: Count,
316    pub fixedstructreader_drop_entry_errors: Count,
317    /// datetime soonest seen (not necessarily reflective of entire file)
318    pub fixedstructreader_datetime_first: DateTimeLOpt,
319    /// datetime latest seen (not necessarily reflective of entire file)
320    pub fixedstructreader_datetime_last: DateTimeLOpt,
321    pub fixedstructreader_map_tvpair_fo_max_len: usize,
322}
323
324/// Implement the FixedStructReader.
325impl FixedStructReader {
326    /// Create a new `FixedStructReader`.
327    ///
328    /// **NOTE:** this `new()` calls [`BlockerReader.read_block`],
329    /// dissimilar from other
330    /// `*Readers::new()` which try to avoid calls to `read_block`.
331    /// This means the reading may return `Done` (like if the file is empty) and
332    /// `Done` must be reflected in the return value of `new`. Hence this
333    /// function has a specialized return value.
334    ///
335    /// [`BlockerReader.read_block`]: crate::readers::blockreader::BlockReader#method.read_block
336    pub fn new(
337        path: FPath,
338        filetype: FileType,
339        blocksz: BlockSz,
340        tz_offset: FixedOffset,
341        dt_filter_after: DateTimeLOpt,
342        dt_filter_before: DateTimeLOpt,
343    ) -> ResultFixedStructReaderNewError {
344        def1n!(
345            "({:?}, filetype={:?}, blocksz={:?}, {:?}, {:?}, {:?})",
346            path, filetype, blocksz, tz_offset, dt_filter_after, dt_filter_before,
347        );
348        let mut blockreader = match BlockReader::new(path.clone(), filetype, blocksz) {
349            Ok(blockreader_) => blockreader_,
350            Err(err) => {
351                def1x!("return Err {}", err);
352                //return Some(Result::Err(err));
353                return ResultFixedStructReaderNew::FileErrIo(err);
354            }
355        };
356        let filetype_fixedstruct = match filetype {
357            FileType::FixedStruct { archival_type: _, fixedstruct_type: type_ } => type_,
358            _ => {
359                debug_panic!("Unexpected FileType: {:?}", filetype);
360                return ResultFixedStructReaderNew::FileErrIo(Error::new(
361                    ErrorKind::InvalidData,
362                    format!("Unexpected FileType {:?}", filetype),
363                ));
364            }
365        };
366
367        const ENTRY_SZ_MIN_FSZ: FileSz = ENTRY_SZ_MIN as FileSz;
368        if blockreader.filesz() == 0 {
369            def1x!("return FileErrEmpty");
370            return ResultFixedStructReaderNew::FileErrEmpty;
371        } else if blockreader.filesz() < ENTRY_SZ_MIN_FSZ {
372            def1x!(
373                "return FileErrTooSmall; {} < {} (ENTRY_SZ_MIN)",
374                blockreader.filesz(), ENTRY_SZ_MIN_FSZ
375            );
376            return ResultFixedStructReaderNew::FileErrTooSmall(
377                format!(
378                    "file size {} < {} (ENTRY_SZ_MIN), file {:?}",
379                    blockreader.filesz(), ENTRY_SZ_MIN_FSZ, path,
380                )
381            );
382        }
383
384        // preprocess the file, pass `oneblock=false` to process
385        // the entire file. This is because `lastlog` files are often nearly
386        // entirely null bytes until maybe one entry near the end, so this should
387        // search past the first block of data.
388        let (
389            fixedstruct_type,
390            high_score,
391            list_entries,
392        ) = match FixedStructReader::preprocess_fixedstructtype(
393            &mut blockreader, &filetype_fixedstruct, false,
394        ) {
395            ResultFixedStructReaderScoreFileError::FileOk(
396                fixedstruct_type_, high_score_, list_entries_,
397            ) => (fixedstruct_type_, high_score_, list_entries_),
398            ResultFixedStructReaderScoreFileError::FileErrEmpty => {
399                def1x!("return FileErrEmpty");
400                return ResultFixedStructReaderNew::FileErrEmpty;
401            }
402            ResultFixedStructReaderScoreFileError::FileErrNoHighScore => {
403                def1x!("return FileErrNoHighScore");
404                return ResultFixedStructReaderNew::FileErrNoValidFixedStruct;
405            }
406            ResultFixedStructReaderScoreFileError::FileErrNoValidFixedStruct => {
407                def1x!("return FileErrNoValidFixedStruct");
408                return ResultFixedStructReaderNew::FileErrNoValidFixedStruct;
409            }
410            ResultFixedStructReaderScoreFileError::FileErrIo(err) => {
411                de_err!("FixedStructReader::preprocess_fixedstructtype Error {}; file {:?}",
412                        err, blockreader.path());
413                def1x!("return Err {:?}", err);
414                return ResultFixedStructReaderNew::FileErrIo(err);
415            }
416        };
417
418        let (total, invalid, valid_no_filter, out_of_order, map_tvpair_fo) = 
419            match FixedStructReader::preprocess_timevalues(
420                &mut blockreader,
421                fixedstruct_type,
422                &dt_filter_after,
423                &dt_filter_before,
424            )
425        {
426            ResultTvFo::Err(err) => {
427                de_err!("FixedStructReader::preprocess_timevalues Error {}; file {:?}",
428                        err, blockreader.path());
429                def1x!("return Err {:?}", err);
430                return ResultFixedStructReaderNew::FileErrIo(err);
431            }
432            ResultTvFo::Ok(
433                (total_, invalid_, valid_no_filter_, out_of_order_, map_tvpair_fo_)
434            ) =>
435                (total_, invalid_, valid_no_filter_, out_of_order_, map_tvpair_fo_),
436        };
437        def1o!("total: {}, invalid: {}, valid_no_filter: {}, out_of_order: {}",
438               total, invalid, valid_no_filter, out_of_order);
439        #[cfg(debug_assertions)]
440        {
441            def1o!("map_tvpair_fo has {} entries", map_tvpair_fo.len());
442            for (_tv_pair, _fo) in map_tvpair_fo.iter() {
443                def1o!("map_tvpair_fo: [tv_pair: {:?}] = fo: {}", _tv_pair, _fo);
444            }
445        }
446        debug_assert_ge!(total, invalid);
447        debug_assert_ge!(total, valid_no_filter);
448
449        if map_tvpair_fo.is_empty() {
450            if valid_no_filter > 0 {
451                def1x!("return FileErrNoFixedStructWithinDtFilters");
452                return ResultFixedStructReaderNew::FileErrNoFixedStructWithinDtFilters;
453            }
454            def1x!("return FileErrNoValidFixedStruct");
455            return ResultFixedStructReaderNew::FileErrNoValidFixedStruct;
456        }
457
458        // set `first_entry_fileoffset` to the first entry by fileoffset
459        let mut first_entry_fileoffset: FileOffset = blockreader.filesz();
460        for (_tv_pair, fo) in map_tvpair_fo.iter() {
461            if &first_entry_fileoffset > fo {
462                first_entry_fileoffset = *fo;
463            }
464        }
465        debug_assert_ne!(first_entry_fileoffset, blockreader.filesz(), "failed to update first_entry_fileoffset");
466
467        // create a mapping of blocks to not-yet-processed entries
468        // later on, this will be used to proactively drop blocks
469        let mut block_use_count: BTreeMap<BlockOffset, usize> = BTreeMap::new();
470        def1o!("block_use_count create");
471        for (_tv_pair, fo) in map_tvpair_fo.iter() {
472            let bo_beg: BlockOffset = BlockReader::block_offset_at_file_offset(*fo, blocksz);
473            let fo_end: FileOffset = *fo + fixedstruct_type.size() as FileOffset;
474            let bo_end: BlockOffset = BlockReader::block_offset_at_file_offset(fo_end, blocksz);
475            def1o!("blocksz = {}", blocksz);
476            for bo in bo_beg..bo_end + 1 {
477                match block_use_count.get_mut(&bo) {
478                    Some(count) => {
479                        let count_ = *count + 1;
480                        def1o!(
481                            "block_use_count[{}] += 1 ({}); [{}‥{}]; total span [{}‥{})",
482                            bo,
483                            count_,
484                            *fo,
485                            fo_end,
486                            BlockReader::file_offset_at_block_offset(bo_beg, blocksz),
487                            BlockReader::file_offset_at_block_offset(bo_end + 1, blocksz),
488                        );
489                        *count = count_;
490                    }
491                    None => {
492                        def1o!(
493                            "block_use_count[{}] = 1; [{}‥{}]; total span [{}‥{})",
494                            bo,
495                            *fo,
496                            fo_end,
497                            BlockReader::file_offset_at_block_offset(bo_beg, blocksz),
498                            BlockReader::file_offset_at_block_offset(bo_end + 1, blocksz),
499                        );
500                        block_use_count.insert(bo, 1);
501                    }
502                }
503            }
504        }
505        #[cfg(debug_assertions)]
506        {
507            for (bo, count) in block_use_count.iter() {
508                def1o!(
509                    "block_use_count[{}] = {}; total span [{}‥{}]",
510                    bo,
511                    count,
512                    BlockReader::file_offset_at_block_offset(*bo, blocksz),
513                    BlockReader::file_offset_at_block_offset(*bo + 1, blocksz),
514                );
515            }
516        }
517
518        let map_max_len = map_tvpair_fo.len();
519        // now that the `fixedstruct_type` is known, create the FixedStructReader
520        let mut fixedstructreader = FixedStructReader {
521            blockreader,
522            fixedstruct_type,
523            filetype_fixedstruct,
524            fixedstruct_size: fixedstruct_type.size(),
525            high_score,
526            tz_offset,
527            cache_entries: FoToEntry::new(),
528            map_tvpair_fo,
529            block_use_count,
530            first_entry_fileoffset,
531            entries_stored_highest: 0,
532            entries_out_of_order: out_of_order,
533            entries_hits: 0,
534            entries_miss: 0,
535            entries_processed: 0,
536            dt_first: DateTimeLOpt::None,
537            dt_last: DateTimeLOpt::None,
538            drop_entry_ok: 0,
539            drop_entry_errors: 0,
540            #[cfg(test)]
541            dropped_blocks: DroppedBlocks::new(),
542            map_tvpair_fo_max_len: map_max_len,
543            error: None,
544        };
545
546        // store the entries found and processed during `preprocess_file` into
547        // `fixedstructreader.cache_entries`, to avoid duplicating work later on
548        for (fo, fixedstructptr) in list_entries.into_iter() {
549            // TODO: cost-savings: if `FixedStructTrait` had a `tv_pair` function then that time
550            //       value could be checked against `map_tvpair_fo` *before* creating a new
551            //       `FixedStruct`. However, the maximum number of `FixedStruct` entries
552            //       created and then discarded will be very few so this is a marginal
553            //       improvement.
554            match FixedStruct::from_fixedstructptr(fo, &tz_offset, fixedstructptr) {
555                Ok(fixedstruct) => {
556                    if fixedstructreader
557                        .map_tvpair_fo
558                        .iter()
559                        .any(|(_tv_pair, fo2)| &fo == fo2)
560                    {
561                        def1o!("insert entry at fo {}", fo);
562                        fixedstructreader.insert_cache_entry(fixedstruct);
563                    } else {
564                        def1o!("skip entry at fo {}; not in map_tvpair_fo", fo);
565                    }
566                }
567                Err(err) => {
568                    de_err!("FixedStruct::from_fixedstructptr Error {}; file {:?}", err, fixedstructreader.path());
569                    fixedstructreader.set_error(&err);
570                }
571            }
572        }
573
574        def1x!("return FileOk(FixedStructReader)");
575
576        ResultFixedStructReaderNew::FileOk(fixedstructreader)
577    }
578
579    /// See [`BlockReader::blocksz`].
580    ///
581    /// [`BlockReader::blocksz`]: crate::readers::blockreader::BlockReader#method.blocksz
582    #[inline(always)]
583    pub const fn blocksz(&self) -> BlockSz {
584        self.blockreader.blocksz()
585    }
586
587    /// See [`BlockReader::filesz`].
588    ///
589    /// [`BlockReader::filesz`]: crate::readers::blockreader::BlockReader#method.filesz
590    #[inline(always)]
591    pub const fn filesz(&self) -> FileSz {
592        self.blockreader.filesz()
593    }
594
595    /// See [`BlockReader::filetype`].
596    ///
597    /// [`BlockReader::filetype`]: crate::readers::blockreader::BlockReader#method.filetype
598    #[inline(always)]
599    pub const fn filetype(&self) -> FileType {
600        self.blockreader.filetype()
601    }
602
603    /// See [`BlockReader::path`].
604    ///
605    /// [`BlockReader::path`]: crate::readers::blockreader::BlockReader#method.path
606    #[inline(always)]
607    pub const fn path(&self) -> &FPath {
608        self.blockreader.path()
609    }
610
611    /// See [`BlockReader::mtime`].
612    ///
613    /// [`BlockReader::mtime`]: crate::readers::blockreader::BlockReader#method.mtime
614    pub fn mtime(&self) -> SystemTime {
615        self.blockreader.mtime()
616    }
617
618    /// `Count` of `FixedStruct`s processed by this `FixedStructReader`
619    /// (i.e. `self.entries_processed`).
620    #[inline(always)]
621    pub fn count_entries_processed(&self) -> Count {
622        self.entries_processed
623    }
624
625    /// "_High watermark_" of `FixedStruct` stored in `self.cache_entries`.
626    #[inline(always)]
627    pub fn entries_stored_highest(&self) -> usize {
628        self.entries_stored_highest
629    }
630
631    /// See [`BlockReader::block_offset_at_file_offset`].
632    ///
633    /// [`BlockReader::block_offset_at_file_offset`]: crate::readers::blockreader::BlockReader#method.block_offset_at_file_offset
634    #[inline(always)]
635    pub const fn block_offset_at_file_offset(
636        &self,
637        fileoffset: FileOffset,
638    ) -> BlockOffset {
639        BlockReader::block_offset_at_file_offset(fileoffset, self.blocksz())
640    }
641
642    /// See [`BlockReader::file_offset_at_block_offset`].
643    ///
644    /// [`BlockReader::file_offset_at_block_offset`]: crate::readers::blockreader::BlockReader#method.file_offset_at_block_offset
645    #[inline(always)]
646    pub const fn file_offset_at_block_offset(
647        &self,
648        blockoffset: BlockOffset,
649    ) -> FileOffset {
650        BlockReader::file_offset_at_block_offset(blockoffset, self.blocksz())
651    }
652
653    /// See [`BlockReader::file_offset_at_block_offset_index`].
654    ///
655    /// [`BlockReader::file_offset_at_block_offset_index`]: crate::readers::blockreader::BlockReader#method.file_offset_at_block_offset_index
656    #[inline(always)]
657    pub const fn file_offset_at_block_offset_index(
658        &self,
659        blockoffset: BlockOffset,
660        blockindex: BlockIndex,
661    ) -> FileOffset {
662        BlockReader::file_offset_at_block_offset_index(blockoffset, self.blocksz(), blockindex)
663    }
664
665    /// See [`BlockReader::block_index_at_file_offset`].
666    ///
667    /// [`BlockReader::block_index_at_file_offset`]: crate::readers::blockreader::BlockReader#method.block_index_at_file_offset
668    #[inline(always)]
669    pub const fn block_index_at_file_offset(
670        &self,
671        fileoffset: FileOffset,
672    ) -> BlockIndex {
673        BlockReader::block_index_at_file_offset(fileoffset, self.blocksz())
674    }
675
676    /// See [`BlockReader::count_blocks`].
677    ///
678    /// [`BlockReader::count_blocks`]: crate::readers::blockreader::BlockReader#method.count_blocks
679    #[inline(always)]
680    pub const fn count_blocks(&self) -> Count {
681        BlockReader::count_blocks(self.filesz(), self.blocksz()) as Count
682    }
683
684    /// See [`BlockReader::blockoffset_last`].
685    ///
686    /// [`BlockReader::blockoffset_last`]: crate::readers::blockreader::BlockReader#method.blockoffset_last
687    pub const fn blockoffset_last(&self) -> BlockOffset {
688        self.blockreader
689            .blockoffset_last()
690    }
691
692    /// See [`BlockReader::fileoffset_last`].
693    ///
694    /// [`BlockReader::fileoffset_last`]: crate::readers::blockreader::BlockReader#method.fileoffset_last
695    pub const fn fileoffset_last(&self) -> FileOffset {
696        self.blockreader
697            .fileoffset_last()
698    }
699
700    /// Is the passed `FileOffset` the last byte of the file?
701    pub const fn is_fileoffset_last(
702        &self,
703        fileoffset: FileOffset,
704    ) -> bool {
705        self.fileoffset_last() == fileoffset
706    }
707
708    /// Is the passed `FixedStruct` the last of the file?
709    #[inline(always)]
710    pub fn is_last(
711        &self,
712        fixedstruct: &FixedStruct,
713    ) -> bool {
714        self.is_fileoffset_last(fixedstruct.fileoffset_end() - 1)
715    }
716
717    /// Return the `FileOffset` that is adjusted to the beginning offset of
718    /// a `fixedstruct` entry.
719    #[inline(always)]
720    pub const fn fileoffset_to_fixedstructoffset(
721        &self,
722        fileoffset: FileOffset,
723    ) -> FileOffset {
724        (fileoffset / self.fixedstruct_size_fo()) * self.fixedstruct_size_fo()
725    }
726
727    /// Return the first file offset from `self.map_tvpair_fo` for the first
728    /// entry as sorted by `tv_pair_type` (datetime); i.e. the earliest entry.
729    /// Ties are broken by `FileOffset`.
730    pub fn fileoffset_first(&self) -> Option<FileOffset> {
731        match self
732            .map_tvpair_fo
733            .iter()
734            .min_by_key(|(tv_pair, fo)| (*tv_pair, *fo))
735        {
736            Some((_tv_pair, fo_)) => Some(*fo_),
737            None => None,
738        }
739    }
740
741    /// The size in bytes of the `FixedStruct` entries managed by this
742    /// `FixedStructReader`.
743    #[inline(always)]
744    pub const fn fixedstruct_size(&self) -> usize {
745        self.fixedstruct_size
746    }
747
748    /// [`fixedstruct_size`] as a `FileOffset`.
749    ///
750    /// [`fixedstruct_size`]: self::FixedStructReader#method.fixedstruct_size
751    #[inline(always)]
752    pub const fn fixedstruct_size_fo(&self) -> FileOffset {
753        self.fixedstruct_size() as FileOffset
754    }
755
756    /// The `FixedStructType` of the file.
757    #[inline(always)]
758    pub const fn fixedstruct_type(&self) -> FixedStructType {
759        self.fixedstruct_type
760    }
761
762    /// Return all currently stored `FileOffset` in `self.cache_entries`.
763    ///
764    /// Only for testing.
765    #[cfg(test)]
766    pub fn get_fileoffsets(&self) -> Vec<FileOffset> {
767        self.cache_entries
768            .keys()
769            .cloned()
770            .collect()
771    }
772
773    /// store an `Error` that occurred. For later printing during `--summary`.
774    // XXX: duplicates `SyslogProcessor.set_error`
775    fn set_error(
776        &mut self,
777        error: &Error,
778    ) {
779        def1ñ!("{:?}", error);
780        let mut error_string: String = error.kind().to_string();
781        error_string.push_str(": ");
782        error_string.push_str(error.kind().to_string().as_str());
783        // print the error but avoid printing the same error more than once
784        // XXX: This is somewhat a hack as it's possible the same error, with the
785        //      the same error message, could occur more than once.
786        //      Considered another way, this function `set_error` may get called
787        //      too often. The responsibility for calling `set_error` is haphazard.
788        match &self.error {
789            Some(err_s) => {
790                if err_s != &error_string {
791                    e_err!("{}", error);
792                }
793            }
794            None => {
795                e_err!("{}", error);
796            }
797        }
798        if let Some(ref _err) = self.error {
799            de_wrn!("skip overwrite of previous Error ({:?}) with Error ({:?})", _err, error);
800            return;
801        }
802        self.error = Some(error_string);
803    }
804
805    /// Store information about a single [`FixedStruct`] entry.
806    ///
807    /// Should only be called by `FixedStructReader::new`
808    ///
809    /// [`FixedStruct`]: crate::data::fixedstruct::FixedStruct
810    fn insert_cache_entry(
811        &mut self,
812        entry: FixedStruct,
813    ) {
814        defn!("@{}", entry.fileoffset_begin());
815        let fo_beg: FileOffset = entry.fileoffset_begin();
816        debug_assert!(
817            !self.cache_entries.contains_key(&fo_beg),
818            "self.cache_entries already contains key {}",
819            fo_beg
820        );
821
822        // update some stats and (most importantly) `self.cache_entries`
823        self.cache_entries
824            .insert(fo_beg, entry);
825        self.entries_stored_highest = std::cmp::max(self.entries_stored_highest, self.cache_entries.len());
826        self.entries_processed += 1;
827        defo!("entries_processed = {}", self.entries_processed);
828
829        defx!();
830    }
831
832    /// Update the statistic `DateTimeL` of
833    /// `self.dt_first` and `self.dt_last`
834    fn dt_first_last_update(
835        &mut self,
836        datetime: &DateTimeL,
837    ) {
838        if !summary_stats_enabled() {
839            return;
840        }
841        defñ!("({:?})", datetime);
842        match self.dt_first {
843            Some(dt_first_) => {
844                if &dt_first_ > datetime {
845                    self.dt_first = Some(*datetime);
846                }
847            }
848            None => {
849                self.dt_first = Some(*datetime);
850            }
851        }
852        match self.dt_last {
853            Some(dt_last_) => {
854                if &dt_last_ < datetime {
855                    self.dt_last = Some(*datetime);
856                }
857            }
858            None => {
859                self.dt_last = Some(*datetime);
860            }
861        }
862    }
863
864    /// Proactively `drop` the [`Block`s] associated with the
865    /// passed [`FixedStruct`]. Return count of dropped entries (0 or 1).
866    ///
867    /// _The caller must know what they are doing!_
868    ///
869    /// [`FixedStruct`]: crate::data::fixedstruct::FixedStruct
870    /// [`Block`s]: crate::readers::blockreader::Block
871    /// [`FileOffset`]: crate::common::FileOffset
872    fn drop_entry(
873        &mut self,
874        fixedstruct: &FixedStruct,
875    ) -> usize {
876        let bsz: BlockSz = self.blocksz();
877        defn!(
878            "(fixedstruct@{}); offsets [{}‥{}), blocks [{}‥{}]",
879            fixedstruct.fileoffset_begin(),
880            fixedstruct.fileoffset_begin(),
881            fixedstruct.fileoffset_end(),
882            fixedstruct.blockoffset_begin(bsz),
883            fixedstruct.blockoffset_end(bsz),
884        );
885        let mut dropped_ok: usize = 0;
886        let mut dropped_err: usize = 0;
887        let mut bo_at: BlockOffset = fixedstruct.blockoffset_begin(bsz);
888        let bo_end: BlockOffset = fixedstruct.blockoffset_end(bsz);
889        debug_assert_le!(bo_at, bo_end);
890        while bo_at <= bo_end {
891            defo!("block_use_count.get_mut({})", bo_at);
892            match self.block_use_count.get_mut(&bo_at) {
893                Some(count) => {
894                    if *count <= 1 {
895                        defo!(
896                            "block_use_count[{}] found; count=={}; total span [{}‥{})",
897                            bo_at, count,
898                            BlockReader::file_offset_at_block_offset(bo_at, bsz),
899                            BlockReader::file_offset_at_block_offset(bo_end + 1, bsz),
900                        );
901                        if self
902                            .blockreader
903                            .drop_block(bo_at)
904                        {
905                            defo!(
906                                "dropped block {}; total span [{}‥{})",
907                                bo_at,
908                                BlockReader::file_offset_at_block_offset(bo_at, bsz),
909                                BlockReader::file_offset_at_block_offset(bo_end + 1, bsz),
910                            );
911                            self.block_use_count.remove(&bo_at);
912                            #[cfg(test)]
913                            self.dropped_blocks.push_back(bo_at);
914                            dropped_ok += 1;
915                        } else {
916                            defo!("failed to drop block {}", bo_at);
917                            dropped_err += 1;
918                        }
919                    } else {
920                        *count -= 1;
921                        defo!("block_use_count[{}] found; count-=1=={}", bo_at, *count);
922                    }
923                }
924                None => {
925                    defo!("block_use_count[{}] not found", bo_at);
926                }
927            }
928            bo_at += 1;
929        }
930        summary_stat!(
931            if dropped_ok > 0 {
932                self.drop_entry_ok += 1;
933            }
934        );
935        summary_stat!(
936            if dropped_err > 0 {
937                self.drop_entry_errors += 1;
938            }
939        );
940        defx!("return {}", dropped_ok);
941
942        dropped_ok
943    }
944
945    /// Check the internal storage `self.cache_entries`.
946    /// Remove the entry and return it if found.
947    #[inline(always)]
948    fn remove_cache_entry(
949        &mut self,
950        fileoffset: FileOffset,
951    ) -> Option<FixedStruct> {
952        match self.cache_entries.remove(&fileoffset) {
953            Some(fixedstruct) => {
954                defñ!("({}): found in store", fileoffset);
955                self.entries_hits += 1;
956
957                Some(fixedstruct)
958            }
959            None => {
960                defñ!("({}): not found in store", fileoffset);
961                self.entries_miss += 1;
962
963                None
964            }
965        }
966    }
967
968    /// Process entries for the file managed by the `BlockReader`.
969    /// Find the entry with the highest "score" as judged by `score_fixedstruct`.
970    ///
971    /// Returns the highest scored `FixedStructType`, that highest score,
972    /// and any processed [`FixedStruct`] entries (referenced by a
973    /// [`FixedStructDynPtr`]) in a list.
974    ///
975    /// Each list entry is a tuple of the
976    /// the entries `FileOffset` and the `FixedStructDynPtr`.
977    /// The entries will presumably be stored in the
978    /// `FixedStructReader`'s `cache_entries`.
979    pub fn score_file(
980        blockreader: &mut BlockReader,
981        oneblock: bool,
982        types_to_bonus: FixedStructTypeSet,
983    ) -> ResultFixedStructReaderScoreFileError {
984        def1n!("(oneblock={}, types_to_bonus len {})", oneblock, types_to_bonus.len());
985        #[cfg(debug_assertions)]
986        {
987            for (fixedstructtype, bonus) in types_to_bonus.iter() {
988                def1o!("types_to_bonus: ({:<30?}, {:2}) size {}", fixedstructtype, bonus, fixedstructtype.size(),);
989            }
990        }
991        // allocate largest possible buffer needed on the stack
992        let mut buffer: [u8; ENTRY_SZ_MAX] = [0; ENTRY_SZ_MAX];
993        // only score this number of entries, i.e. don't walk the
994        // entire file scoring all entries
995        #[cfg(not(test))]
996        const COUNT_FOUND_ENTRIES_MAX: usize = 5;
997        #[cfg(test)]
998        const COUNT_FOUND_ENTRIES_MAX: usize = 2;
999        let mut _count_total: usize = 0;
1000        let mut highest_score: Score = 0;
1001        let mut highest_score_type: Option<FixedStructType> = None;
1002        let mut highest_score_entries = ListFileOffsetFixedStructPtr::new();
1003
1004        for (fixedstructtype, bonus) in types_to_bonus.into_iter() {
1005            let mut _count_loop: usize = 0;
1006            let mut count_found_entries: usize = 0;
1007            let mut high_score: Score = 0;
1008            let mut fo: FileOffset = 0;
1009            let mut found_entries = ListFileOffsetFixedStructPtr::new();
1010
1011            loop {
1012                if count_found_entries >= COUNT_FOUND_ENTRIES_MAX {
1013                    def1o!("count_found_entries {} >= COUNT_FOUND_ENTRIES_MAX {}", count_found_entries, COUNT_FOUND_ENTRIES_MAX);
1014                    break;
1015                }
1016                _count_total += 1;
1017                _count_loop += 1;
1018                let utmp_sz: usize = fixedstructtype.size();
1019                let fo_end = fo + utmp_sz as FileOffset;
1020                def1o!(
1021                    "loop try {} (total {}), fixedstructtype {:?}, zero the buffer (size {}), looking at fileoffset {}‥{} (0x{:08X}‥0x{:08X})",
1022                    _count_loop, _count_total, fixedstructtype, buffer.len(), fo, fo_end, fo, fo_end
1023                );
1024                // zero out the buffer
1025                // XXX: not strictly necessary to zero buffer but it helps humans
1026                //      manually reviewing debug logs
1027                // this compiles down to a single `memset` call
1028                // See https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:rust,selection:(endColumn:1,endLineNumber:5,positionColumn:1,positionLineNumber:5,selectionStartColumn:1,selectionStartLineNumber:5,startColumn:1,startLineNumber:5),source:'pub+fn+foo()+%7B%0A++++const+ENTRY_SZ_MAX:+usize+%3D+400%3B%0A++++let+mut+buffer:+%5Bu8%3B+ENTRY_SZ_MAX%5D+%3D+%5B0%3B+ENTRY_SZ_MAX%5D%3B%0A++++buffer.iter_mut().for_each(%7Cm%7C+*m+%3D+0)%3B%0A%0A++++std::hint::black_box(buffer)%3B%0A%7D'),l:'5',n:'0',o:'Rust+source+%231',t:'0')),k:41.18316477421653,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:r1750,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'1',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:rust,libs:!(),options:'-O',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+rustc+1.75.0+(Editor+%231)',t:'0')),k:42.788718063415104,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(editorid:1,fontScale:14,fontUsePx:'0',j:1,wrap:'1'),l:'5',n:'0',o:'Output+of+rustc+1.75.0+(Compiler+%231)',t:'0')),k:16.028117162368364,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4
1029                // See https://godbolt.org/z/KcxW9hWYb
1030                buffer.iter_mut().for_each(|m| *m = 0);
1031                // read data into buffer
1032                let buffer_read: usize = match blockreader.read_data_to_buffer(fo, fo_end, oneblock, &mut buffer) {
1033                    ResultReadDataToBuffer::Found(buffer_read) => buffer_read,
1034                    ResultReadDataToBuffer::Err(err) => {
1035                        def1x!("return Err");
1036                        return ResultFixedStructReaderScoreFileError::FileErrIo(err);
1037                    }
1038                    ResultReadDataToBuffer::Done => {
1039                        // reached end of block (if `oneblock` is `true`) or end of file
1040                        break;
1041                    }
1042                };
1043                if buffer_read < utmp_sz {
1044                    def1o!(
1045                        "read_data_to_buffer read bytes {} < {} requested fixedstruct size bytes; break",
1046                        buffer_read,
1047                        utmp_sz,
1048                    );
1049                    break;
1050                }
1051                // advance the file offset for the next loop
1052                let fo2 = fo;
1053                fo += utmp_sz as FileOffset;
1054                // grab the slice of interest
1055                let slice_ = &buffer[..buffer_read];
1056                // convert buffer to fixedstruct
1057                let fixedstructptr: FixedStructDynPtr = match buffer_to_fixedstructptr(slice_, fixedstructtype) {
1058                    Some(val) => val,
1059                    None => {
1060                        def1o!(
1061                            "buffer_to_fixedstructptr(buf len {}, {:?}) returned None; continue",
1062                            buffer.len(),
1063                            fixedstructtype,
1064                        );
1065                        continue;
1066                    }
1067                };
1068                count_found_entries += 1;
1069                // score the newly create fixedstruct
1070                let score: Score = FixedStruct::score_fixedstruct(&fixedstructptr, bonus);
1071                def1o!("score {} for entry type {:?} @[{}‥{}]", score, fixedstructptr.fixedstruct_type(), fo2, fo_end);
1072                // update the high score
1073                let _fs_type: FixedStructType = fixedstructptr.fixedstruct_type();
1074                found_entries.push_back((fo2, fixedstructptr));
1075                if score <= high_score {
1076                    def1o!("score {} ({:?}) not higher than high score {}", score, _fs_type, high_score,);
1077                    // score is not higher than high score so continue
1078                    continue;
1079                }
1080                // there is a new high score for this type
1081                def1o!("new high score {} for entry type {:?} @[{}‥{}]", score, _fs_type, fo2, fo_end);
1082                high_score = score;
1083            }
1084            // finished with that fixedstructtype
1085            // so check if it's high score beat any previous high score of a different fixedstructtype
1086            if high_score > highest_score {
1087                // a new high score was found for a different type so throw away
1088                // linked lists of entries for the previous type
1089                match highest_score_type {
1090                    None => {
1091                        def1o!(
1092                            "new highest score {} entry type {:?} with {} entries",
1093                            high_score, fixedstructtype, found_entries.len(),
1094                        );
1095                    }
1096                    Some(_highest_score_type) => {
1097                        def1o!(
1098                            "new highest score {} entry type {:?} with {} entries; replaces old high score {} entry type {:?} with {} entries (entries dropped)",
1099                            high_score, fixedstructtype, found_entries.len(),
1100                            highest_score, _highest_score_type, highest_score_entries.len(),
1101                        );
1102                    }
1103                }
1104                highest_score = high_score;
1105                highest_score_type = Some(fixedstructtype);
1106                highest_score_entries = found_entries;
1107            } else {
1108                def1o!(
1109                    "no new highest score: score {} entry type {:?} with {} entries. old high score remains: score {} entry type {:?} with {} entries",
1110                    high_score, fixedstructtype, found_entries.len(),
1111                    highest_score, highest_score_type, highest_score_entries.len(),
1112                );
1113            }
1114        }
1115
1116        match highest_score_type {
1117            None => {
1118                def1x!("return Err {:?}", ResultFixedStructReaderScoreFileError::FileErrNoHighScore);
1119                return ResultFixedStructReaderScoreFileError::FileErrNoHighScore;
1120            }
1121            Some(highest_score_type) => {
1122                def1x!("return Ok(({:?}, {}, found_entries))", highest_score_type, highest_score);
1123
1124                ResultFixedStructReaderScoreFileError::FileOk(highest_score_type, highest_score, highest_score_entries)
1125            }
1126        }
1127    }
1128
1129    /// Determine the `FixedStructType` based on the file size and data.
1130    ///
1131    /// 1. Makes best guess about file structure based on size by calling
1132    ///    `filesz_to_types`
1133    /// 2. Searches for a valid struct within the first block of the file (if
1134    ///    `oneblock` is `true`, else searches all available blocks).
1135    /// 3. Creates a [`FixedStruct`] from the found struct.
1136    ///
1137    /// Call this before calling `process_entry_at`.
1138    ///
1139    /// [OpenBSD file `w.c`] processes `/var/log/utmp`. Reviewing the code you
1140    /// get some idea of how the file is determined to be valid.
1141    ///
1142    /// [OpenBSD file `w.c`]: https://github.com/openbsd/src/blob/20248fc4cbb7c0efca41a8aafd40db7747023515/usr.bin/w/w.c
1143    pub(crate) fn preprocess_fixedstructtype(
1144        blockreader: &mut BlockReader,
1145        filetype_fixedstruct: &FileTypeFixedStruct,
1146        oneblock: bool,
1147    ) -> ResultFixedStructReaderScoreFileError {
1148        def1n!("({:?}, oneblock={})", filetype_fixedstruct, oneblock);
1149
1150        // short-circuit special case of empty file
1151        if blockreader.filesz() == 0 {
1152            def1x!("empty file; return FileErrEmpty");
1153            return ResultFixedStructReaderScoreFileError::FileErrEmpty;
1154        }
1155
1156        let types_to_bonus: FixedStructTypeSet = match filesz_to_types(
1157            blockreader.filesz(),
1158            filetype_fixedstruct,
1159        ) {
1160            Some(set) => set,
1161            None => {
1162                de_wrn!("FixedStructReader::filesz_to_types({}) failed; file {:?}",
1163                        blockreader.filesz(), blockreader.path());
1164                def1x!("filesz_to_types returned None; return FileErrNoValidFixedStruct");
1165                return ResultFixedStructReaderScoreFileError::FileErrNoValidFixedStruct;
1166            }
1167        };
1168        def1o!("filesz_to_types returned {} types: {:?}", types_to_bonus.len(), types_to_bonus);
1169
1170        let ret = FixedStructReader::score_file(blockreader, oneblock, types_to_bonus);
1171        def1x!("score_file returned {:?}", ret);
1172
1173        ret
1174    }
1175
1176    /// Jump to each entry offset, convert the raw bytes to a `tv_pair_type`.
1177    /// If the value is within the passed filters than save the value and the
1178    /// file offset of the entry.
1179    /// Return a count of out of order entries, and map of filtered tv_pair to
1180    /// fileoffsets. The fileoffsets in the map are the fixedstruct offsets
1181    /// (not timevalue offsets).
1182    pub(crate) fn preprocess_timevalues(
1183        blockreader: &mut BlockReader,
1184        fixedstruct_type: FixedStructType,
1185        dt_filter_after: &DateTimeLOpt,
1186        dt_filter_before: &DateTimeLOpt,
1187    ) -> ResultTvFo {
1188        defn!();
1189        // allocate largest possible buffer needed on the stack
1190        let mut buffer: [u8; TIMEVAL_SZ_MAX] = [0; TIMEVAL_SZ_MAX];
1191        // map of time values to file offsets
1192        let mut map_tv_pair_fo: MapTvPairToFo = MapTvPairToFo::new();
1193        // count of out of order entries
1194        let mut out_of_order: usize = 0;
1195        // valid fixedstruct but does not pass the time value filters
1196        let mut valid_no_pass_filter: usize = 0;
1197        // null fixedstruct or non-sense time values
1198        let mut invalid: usize = 0;
1199        // total number of entries processed
1200        let mut total_entries: usize = 0;
1201
1202        let tv_filter_after: Option<tv_pair_type> = match dt_filter_after {
1203            Some(dt) => Some(convert_datetime_tvpair(dt)),
1204            None => None,
1205        };
1206        defo!("tv_filter_after: {:?}", tv_filter_after);
1207        let tv_filter_before: Option<tv_pair_type> = match dt_filter_before {
1208            Some(dt) => Some(convert_datetime_tvpair(dt)),
1209            None => None,
1210        };
1211        defo!("tv_filter_before: {:?}", tv_filter_before);
1212
1213        // 1. get offsets
1214        let entry_sz: FileOffset = fixedstruct_type.size() as FileOffset;
1215        debug_assert_eq!(blockreader.filesz() % entry_sz, 0, "file not a multiple of entry size {}", entry_sz);
1216        let tv_sz: usize = fixedstruct_type.size_tv();
1217        let tv_offset: usize = fixedstruct_type.offset_tv();
1218        let slice_: &mut [u8] = &mut buffer[..tv_sz];
1219        let mut fo: FileOffset = 0;
1220        let mut tv_pair_prev: Option<tv_pair_type> = None;
1221        loop {
1222            // 2. jump to each offset, grab datetime bytes,
1223            let beg: FileOffset = fo + tv_offset as FileOffset;
1224            let end: FileOffset = beg + tv_sz as FileOffset;
1225            match blockreader.read_data_to_buffer(beg, end, false, slice_) {
1226                ResultReadDataToBuffer::Found(_readn) => {
1227                    defo!("read {} bytes at fileoffset {}", _readn, beg);
1228                    debug_assert_eq!(
1229                        _readn, tv_sz,
1230                        "read {} bytes, expected {} bytes (size of a time value)",
1231                        _readn, tv_sz,
1232                    );
1233                }
1234                ResultReadDataToBuffer::Err(err) => {
1235                    defx!("return Err");
1236                    return ResultTvFo::Err(err);
1237                }
1238                ResultReadDataToBuffer::Done => {
1239                    defo!("return Done");
1240                    break;
1241                }
1242            }
1243            // 3. convert bytes to tv_sec, tv_usec
1244            let tv_pair: tv_pair_type = match fixedstruct_type.tv_pair_from_buffer(slice_) {
1245                Some(pair) => pair,
1246                None => {
1247                    de_err!("invalid entry at fileoffset {}", fo);
1248                    defo!("invalid entry at fileoffset {}", fo);
1249                    fo += entry_sz;
1250                    invalid += 1;
1251                    continue;
1252                }
1253            };
1254            defo!("tv_pair: {:?}", tv_pair);
1255            if tv_pair == tv_pair_type(0, 0) {
1256                defo!("tv_pair is (0, 0); continue");
1257                fo += entry_sz;
1258                continue;
1259            }
1260            match tv_pair_prev {
1261                Some(tv_pair_prev) => {
1262                    if tv_pair < tv_pair_prev {
1263                        out_of_order += 1;
1264                        defo!(
1265                            "out_of_order = {}; tv_pair = {:?}, tv_pair_prev = {:?}",
1266                            out_of_order,
1267                            tv_pair,
1268                            tv_pair_prev,
1269                        );
1270                    }
1271                }
1272                None => {}
1273            }
1274            tv_pair_prev = Some(tv_pair);
1275            total_entries += 1;
1276            // 4. compare to time value filters
1277            if let Some(tv_filter) = tv_filter_after {
1278                if tv_pair < tv_filter {
1279                    defo!("tv_pair {:?} < {:?} tv_filter_after; continue", tv_pair, tv_filter);
1280                    fo += entry_sz;
1281                    valid_no_pass_filter += 1;
1282                    continue;
1283                }
1284            }
1285            if let Some(tv_filter) = tv_filter_before {
1286                if tv_pair > tv_filter {
1287                    defo!("tv_pair {:?} > {:?} tv_filter_before; continue", tv_pair, tv_filter);
1288                    fo += entry_sz;
1289                    valid_no_pass_filter += 1;
1290                    // continue to check _all_ entries as the entries may not be
1291                    // in chronological order
1292                    continue;
1293                }
1294            }
1295            // 5. save entries that pass the time value filters
1296            defo!("tv_pair {:?} @{} passes time value filters", tv_pair, fo);
1297            map_tv_pair_fo.insert(tv_pair, fo);
1298
1299            fo += entry_sz;
1300        }
1301        // 6. return list of valid entries
1302        defx!("map_tv_pair_fo len {}", map_tv_pair_fo.len());
1303
1304        ResultTvFo::Ok((total_entries, invalid, valid_no_pass_filter, out_of_order, map_tv_pair_fo))
1305    }
1306
1307    /// Process the data at FileOffset `fo`. Transform it into a `FixedStruct`
1308    /// using [`FixedStruct::new`].
1309    /// But before that, check private `self.cache_entries`
1310    /// in case the data at the fileoffset was already processed (transformed)
1311    /// during `FixedStructReader::new`.
1312    ///
1313    /// Let the caller pass a `buffer` to avoid this function having allocate.
1314    ///
1315    /// This function does the bulk of file processing after the
1316    /// `FixedStructReader` has been initialized during
1317    /// [`FixedStructReader::new`].
1318    pub fn process_entry_at(
1319        &mut self,
1320        fo: FileOffset,
1321        buffer: &mut [u8],
1322    ) -> ResultFindFixedStruct {
1323        defn!("({})", fo);
1324
1325        let sz: FileOffset = self.fixedstruct_size_fo();
1326        debug_assert_eq!(fo % sz, 0, "fileoffset {} not multiple of {}", fo, sz,);
1327        let fileoffset: FileOffset = fo - (fo % sz);
1328
1329        if fileoffset >= self.filesz() {
1330            defx!("return ResultFindFixedStruct::Done; fileoffset {} >= filesz {}", fileoffset, self.filesz());
1331            return ResultFindFixedStruct::Done;
1332        }
1333
1334        // The `map_tvpair_fo` is the oracle listing of entries ordered by
1335        // `tv_pair` (it was  fully created during `preprocess_timevalues`).
1336        // Search `map_tvpair_fo` for the passed `fo`, then return the
1337        // fileoffset of the entry *after that* which will be returned in `Found`.
1338        // If no next fileoffset is found it means `map_tvpair_fo` is empty.
1339        // In that case, the `fo_next` will be the value of `filesz()` and the
1340        // next call to `process_entry_at` will return `Done`.
1341        let fo_next: FileOffset = {
1342            let mut fo_next_: FileOffset = self.filesz();
1343            let mut next_pair: bool = false;
1344            let mut tv_pair_at_opt: Option<tv_pair_type> = None;
1345            // TODO: is there a rustic iterator way to
1346            //       "find something and return the next thing"?
1347            for (tv_pair_at, fo_at) in self.map_tvpair_fo.iter() {
1348                if next_pair {
1349                    defo!("set fo_next = {}", fo_at);
1350                    fo_next_ = *fo_at;
1351                    break;
1352                }
1353                if &fileoffset == fo_at {
1354                    defo!("found fileoffset {} with key {:?} in map_tvpair_fo", fileoffset, tv_pair_at,);
1355                    tv_pair_at_opt = Some(*tv_pair_at);
1356                    next_pair = true;
1357                }
1358            }
1359            // remove the `tv_pair` from `map_tvpair_fo`
1360            match tv_pair_at_opt {
1361                Some(tv_pair_at) => {
1362                    self.map_tvpair_fo
1363                        .remove(&tv_pair_at);
1364                    defo!("remove tv_pair {:?}; map_tvpair_fo size {}", tv_pair_at, self.map_tvpair_fo.len());
1365                }
1366                None => {
1367                    defo!("no map_tvpair_fo found!");
1368                }
1369            }
1370
1371            fo_next_
1372        };
1373        defo!("fo_next = {}", fo_next);
1374
1375        // check if the entry is already stored
1376        if let Some(fixedstruct) = self.remove_cache_entry(fileoffset) {
1377            summary_stat!(self.dt_first_last_update(fixedstruct.dt()));
1378            // try to drop blocks associated with the entry
1379            self.drop_entry(&fixedstruct);
1380            defx!("remove_cache_entry found fixedstruct at fileoffset {}; return Found({}, …)", fileoffset, fo_next,);
1381            return ResultFindFixedStruct::Found((fo_next, fixedstruct));
1382        }
1383
1384        // the entry was not in the cache so read the raw bytes from the file
1385        // and transform them into a `FixedStruct`
1386
1387        // check the buffer size
1388        if buffer.len() < sz as usize {
1389            defx!("return ResultFindFixedStruct::Err");
1390            return ResultFindFixedStruct::Err((
1391                None,
1392                Error::new(
1393                    ErrorKind::InvalidData,
1394                    format!(
1395                        "buffer size {} less than fixedstruct size {} at fileoffset {}, file {:?}",
1396                        buffer.len(), sz, fileoffset, self.path(),
1397                    ),
1398                ),
1399            ));
1400        }
1401
1402        // zero out the slice
1403        defo!("zero buffer[‥{}]", sz);
1404        let slice_: &mut [u8] = &mut buffer[..sz as usize];
1405        slice_.iter_mut().for_each(|m| *m = 0);
1406
1407        // read raw bytes into the slice
1408        let _readn = match self
1409            .blockreader
1410            .read_data_to_buffer(fileoffset, fileoffset + sz, false, slice_)
1411        {
1412            ResultReadDataToBuffer::Found(val) => val,
1413            ResultReadDataToBuffer::Done => {
1414                defx!("return ResultFindFixedStruct::Done; read_data_to_buffer returned Done");
1415                return ResultFindFixedStruct::Done;
1416            }
1417            ResultReadDataToBuffer::Err(err) => {
1418                self.set_error(&err);
1419                defx!("return ResultFindFixedStruct::Err({:?})", err);
1420                // an error from `blockreader.read_data_to_buffer` is unlikely to improve
1421                // with a retry so return `None` signifying no more processing of the file
1422                return ResultFindFixedStruct::Err((None, err));
1423            }
1424        };
1425        debug_assert_eq!(_readn, sz as usize, "read {} bytes, expected {} bytes", _readn, sz);
1426
1427        // create a FixedStruct from the slice
1428        let fs: FixedStruct = match FixedStruct::new(
1429            fileoffset,
1430            &self.tz_offset,
1431            slice_,
1432            self.fixedstruct_type(),
1433        ) {
1434            Ok(val) => val,
1435            Err(err) => {
1436                defx!("return ResultFindFixedStruct::Done; FixedStruct::new returned Err({:?})", err);
1437                return ResultFindFixedStruct::Err((Some(fo_next), err));
1438            }
1439        };
1440        // update various statistics/counters
1441        self.entries_processed += 1;
1442        defo!("entries_processed = {}", self.entries_processed);
1443        summary_stat!(self.dt_first_last_update(fs.dt()));
1444        // try to drop blocks associated with the entry
1445        self.drop_entry(&fs);
1446
1447        defx!("return ResultFindFixedStruct::Found((fo_next={}, …))", fo_next);
1448
1449        ResultFindFixedStruct::Found((fo_next, fs))
1450    }
1451
1452    /// Wrapper function for call to [`datetime::dt_after_or_before`] using the
1453    /// [`FixedStruct::dt`] of the `entry`.
1454    ///
1455    /// [`datetime::dt_after_or_before`]: crate::data::datetime::dt_after_or_before
1456    /// [`FixedStruct::dt`]: crate::data::fixedstruct::FixedStruct::dt
1457    pub fn entry_dt_after_or_before(
1458        entry: &FixedStruct,
1459        dt_filter: &DateTimeLOpt,
1460    ) -> Result_Filter_DateTime1 {
1461        defñ!("({:?})", dt_filter);
1462
1463        dt_after_or_before(entry.dt(), dt_filter)
1464    }
1465
1466    /// Wrapper function for call to [`datetime::dt_pass_filters`] using the
1467    /// [`FixedStruct::dt`] of the `entry`.
1468    ///
1469    /// [`datetime::dt_pass_filters`]: crate::data::datetime::dt_pass_filters
1470    /// [`FixedStruct::dt`]: crate::data::fixedstruct::FixedStruct::dt
1471    #[inline(always)]
1472    pub fn entry_pass_filters(
1473        entry: &FixedStruct,
1474        dt_filter_after: &DateTimeLOpt,
1475        dt_filter_before: &DateTimeLOpt,
1476    ) -> Result_Filter_DateTime2 {
1477        defn!("({:?}, {:?})", dt_filter_after, dt_filter_before);
1478
1479        let result: Result_Filter_DateTime2 = dt_pass_filters(
1480            entry.dt(),
1481            dt_filter_after,
1482            dt_filter_before
1483        );
1484        defx!("(…) return {:?};", result);
1485
1486        result
1487    }
1488
1489    /// Return an up-to-date [`SummaryFixedStructReader`] instance for this
1490    /// `FixedStructReader`.
1491    ///
1492    /// [`SummaryFixedStructReader`]: SummaryFixedStructReader
1493    #[allow(non_snake_case)]
1494    pub fn summary(&self) -> SummaryFixedStructReader {
1495        let fixedstructreader_fixedstructtype_opt = Some(self.fixedstruct_type());
1496        let fixedstructreader_filetypefixedstruct_opt = Some(self.filetype_fixedstruct);
1497        let fixedstructreader_high_score: Score = self.high_score;
1498        let fixedstructreader_utmp_entries: Count = self.entries_processed;
1499        let fixedstructreader_first_entry_fileoffset: FileOffset = self.first_entry_fileoffset;
1500        let fixedstructreader_entries_out_of_order: usize = self.entries_out_of_order;
1501        let fixedstructreader_utmp_entries_max: Count = self.entries_stored_highest as Count;
1502        let fixedstructreader_utmp_entries_hit: Count = self.entries_hits as Count;
1503        let fixedstructreader_utmp_entries_miss: Count = self.entries_miss as Count;
1504        let fixedstructreader_drop_entry_ok: Count = self.drop_entry_ok;
1505        let fixedstructreader_drop_entry_errors: Count = self.drop_entry_errors;
1506        let fixedstructreader_datetime_first = self.dt_first;
1507        let fixedstructreader_datetime_last = self.dt_last;
1508        let fixedstructreader_map_tvpair_fo_max_len: usize = self.map_tvpair_fo_max_len;
1509
1510        SummaryFixedStructReader {
1511            fixedstructreader_fixedstructtype_opt,
1512            fixedstructreader_filetypefixedstruct_opt,
1513            fixedstructreader_fixedstruct_size: self.fixedstruct_size(),
1514            fixedstructreader_high_score,
1515            fixedstructreader_utmp_entries,
1516            fixedstructreader_first_entry_fileoffset,
1517            fixedstructreader_entries_out_of_order,
1518            fixedstructreader_utmp_entries_max,
1519            fixedstructreader_utmp_entries_hit,
1520            fixedstructreader_utmp_entries_miss,
1521            fixedstructreader_drop_entry_ok,
1522            fixedstructreader_drop_entry_errors,
1523            fixedstructreader_datetime_first,
1524            fixedstructreader_datetime_last,
1525            fixedstructreader_map_tvpair_fo_max_len,
1526        }
1527    }
1528
1529    /// Return an up-to-date [`Summary`] instance for this `FixedStructReader`.
1530    ///
1531    /// [`Summary`]: crate::readers::summary::Summary
1532    pub fn summary_complete(&self) -> Summary {
1533        let path = self.path().clone();
1534        let path_ntf = None;
1535        let filetype = self.filetype();
1536        let logmessagetype = filetype.to_logmessagetype();
1537        let summaryblockreader = self.blockreader.summary();
1538        let summaryfixedstructreader = self.summary();
1539        let error: Option<String> = self.error.clone();
1540
1541        Summary::new(
1542            path,
1543            path_ntf,
1544            filetype,
1545            logmessagetype,
1546            Some(summaryblockreader),
1547            None,
1548            None,
1549            None,
1550            Some(summaryfixedstructreader),
1551            None,
1552            None,
1553            None,
1554            error,
1555        )
1556    }
1557}