s4lib/readers/
fixedstructreader.rs

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