• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jtmoon79 / super-speedy-syslog-searcher / 16740593858

05 Aug 2025 04:30AM UTC coverage: 58.456% (-0.02%) from 58.48%
16740593858

push

github

jtmoon79
(LIB) NFC note TODO naming DTFSSet

12063 of 20636 relevant lines covered (58.46%)

22044.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.46
/src/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

27
use crate::{e_err, de_err, de_wrn};
28
use crate::common::{
29
    debug_panic,
30
    Count,
31
    FileOffset,
32
    FileSz,
33
    FileType,
34
    FileTypeFixedStruct,
35
    FPath,
36
    ResultS3,
37
};
38
use 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
};
48
use 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
};
62
use crate::readers::blockreader::{
63
    BlockIndex,
64
    BlockOffset,
65
    BlockReader,
66
    BlockSz,
67
    ResultReadDataToBuffer,
68
};
69
use crate::readers::summary::Summary;
70

71
use std::collections::{BTreeMap, LinkedList};
72
use std::fmt;
73
use std::io::{Error, ErrorKind, Result};
74

75
use ::more_asserts::{debug_assert_ge, debug_assert_le};
76
#[allow(unused_imports)]
77
use ::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
107
pub type FoToEntry = BTreeMap<FileOffset, FixedStruct>;
108

109
/// Map [`FileOffset`] To `FileOffset`
110
///
111
/// [`FileOffset`]: crate::common::FileOffset
112
pub type FoToFo = BTreeMap<FileOffset, FileOffset>;
113

114
pub type FoList = LinkedList<FileOffset>;
115

116
type MapTvPairToFo = BTreeMap<tv_pair_type, FileOffset>;
117

118
/// [`FixedStructReader.find`*] functions results.
119
///
120
/// [`FixedStructReader.find`*]: self::FixedStructReader#method.find_entry
121
pub type ResultS3FixedStructFind = ResultS3<(FileOffset, FixedStruct), (Option<FileOffset>, Error)>;
122

123
pub type ResultS3FixedStructProcZeroBlock = ResultS3<(FixedStructType, Score, ListFileOffsetFixedStructPtr), Error>;
124

125
pub type ResultTvFo = Result<(usize, usize, usize, usize, MapTvPairToFo)>;
126

127
#[cfg(test)]
128
pub type DroppedBlocks = LinkedList<BlockOffset>;
129

130
type ListFileOffsetFixedStructPtr = LinkedList<(FileOffset, FixedStructDynPtr)>;
131

132
/// Enum return value for [`FixedStructReader::new`].
133
#[derive(Debug)]
134
pub 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

149
pub type ResultFixedStructReaderNewError = ResultFixedStructReaderNew<Error>;
150

151
#[derive(Debug)]
152
pub 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

166
pub 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
200
pub 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

276
impl 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)]
299
pub 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.
320
impl 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(
131✔
333
        path: FPath,
131✔
334
        filetype: FileType,
131✔
335
        blocksz: BlockSz,
131✔
336
        tz_offset: FixedOffset,
131✔
337
        dt_filter_after: DateTimeLOpt,
131✔
338
        dt_filter_before: DateTimeLOpt,
131✔
339
    ) -> ResultFixedStructReaderNewError {
131✔
340
        def1n!(
131✔
341
            "({:?}, filetype={:?}, blocksz={:?}, {:?}, {:?}, {:?})",
131✔
342
            path, filetype, blocksz, tz_offset, dt_filter_after, dt_filter_before,
343
        );
344
        let mut blockreader = match BlockReader::new(
131✔
345
            path.clone(), filetype, blocksz
131✔
346
        ) {
131✔
347
            Ok(blockreader_) => blockreader_,
130✔
348
            Err(err) => {
1✔
349
                def1x!("return Err {}", err);
1✔
350
                //return Some(Result::Err(err));
351
                return ResultFixedStructReaderNew::FileErrIo(err);
1✔
352
            }
353
        };
354
        let filetype_fixedstruct = match filetype {
130✔
355
            FileType::FixedStruct { archival_type: _, fixedstruct_type: type_ } => type_,
130✔
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 {
130✔
369
            def1x!("return FileErrEmpty");
1✔
370
            return ResultFixedStructReaderNew::FileErrEmpty;
1✔
371
        } else if blockreader.filesz() < ENTRY_SZ_MIN_FSZ {
129✔
372
            def1x!(
1✔
373
                "return FileErrTooSmall; {} < {} (ENTRY_SZ_MIN)",
1✔
374
                blockreader.filesz(), ENTRY_SZ_MIN_FSZ
1✔
375
            );
376
            return ResultFixedStructReaderNew::FileErrTooSmall(
1✔
377
                format!(
1✔
378
                    "file size {} < {} (ENTRY_SZ_MIN), file {:?}",
1✔
379
                    blockreader.filesz(), ENTRY_SZ_MIN_FSZ, path,
1✔
380
                )
1✔
381
            );
1✔
382
        }
128✔
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,
120✔
390
            high_score,
120✔
391
            list_entries,
120✔
392
        ) = match FixedStructReader::preprocess_fixedstructtype(
128✔
393
            &mut blockreader, &filetype_fixedstruct, false,
128✔
394
        ) {
128✔
395
            ResultFixedStructReaderScoreFileError::FileOk(
120✔
396
                fixedstruct_type_, high_score_, list_entries_,
120✔
397
            ) => (fixedstruct_type_, high_score_, list_entries_),
120✔
398
            ResultFixedStructReaderScoreFileError::FileErrEmpty => {
×
399
                def1x!("return FileErrEmpty");
×
400
                return ResultFixedStructReaderNew::FileErrEmpty;
×
401
            }
402
            ResultFixedStructReaderScoreFileError::FileErrNoHighScore => {
8✔
403
                def1x!("return FileErrNoHighScore");
8✔
404
                return ResultFixedStructReaderNew::FileErrNoValidFixedStruct;
8✔
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) = 
120✔
419
            match FixedStructReader::preprocess_timevalues(
120✔
420
                &mut blockreader,
120✔
421
                fixedstruct_type,
120✔
422
                &dt_filter_after,
120✔
423
                &dt_filter_before,
120✔
424
            )
120✔
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(
120✔
433
                (total_, invalid_, valid_no_filter_, out_of_order_, map_tvpair_fo_)
120✔
434
            ) =>
120✔
435
                (total_, invalid_, valid_no_filter_, out_of_order_, map_tvpair_fo_),
120✔
436
        };
437
        def1o!("total: {}, invalid: {}, valid_no_filter: {}, out_of_order: {}",
120✔
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());
120✔
442
            for (_tv_pair, _fo) in map_tvpair_fo.iter() {
194✔
443
                def1o!("map_tvpair_fo: [tv_pair: {:?}] = fo: {}", _tv_pair, _fo);
194✔
444
            }
445
        }
446
        debug_assert_ge!(total, invalid);
120✔
447
        debug_assert_ge!(total, valid_no_filter);
120✔
448

449
        if map_tvpair_fo.is_empty() {
120✔
450
            if valid_no_filter > 0 {
19✔
451
                def1x!("return FileErrNoFixedStructWithinDtFilters");
19✔
452
                return ResultFixedStructReaderNew::FileErrNoFixedStructWithinDtFilters;
19✔
453
            }
×
454
            def1x!("return FileErrNoValidFixedStruct");
×
455
            return ResultFixedStructReaderNew::FileErrNoValidFixedStruct;
×
456
        }
101✔
457

458
        // set `first_entry_fileoffset` to the first entry by fileoffset
459
        let mut first_entry_fileoffset: FileOffset = blockreader.filesz();
101✔
460
        for (_tv_pair, fo) in map_tvpair_fo.iter() {
194✔
461
            if &first_entry_fileoffset > fo {
194✔
462
                first_entry_fileoffset = *fo;
131✔
463
            }
131✔
464
        }
465
        debug_assert_ne!(
101✔
466
            first_entry_fileoffset, blockreader.filesz(),
101✔
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();
101✔
473
        def1o!("block_use_count create");
101✔
474
        for (_tv_pair, fo) in map_tvpair_fo.iter() {
194✔
475
            let bo_beg: BlockOffset = BlockReader::block_offset_at_file_offset(*fo, blocksz);
194✔
476
            let fo_end: FileOffset = *fo + fixedstruct_type.size() as FileOffset;
194✔
477
            let bo_end: BlockOffset = BlockReader::block_offset_at_file_offset(fo_end, blocksz);
194✔
478
            def1o!("blocksz = {}", blocksz);
194✔
479
            for bo in bo_beg..bo_end+1 {
3,437✔
480
                match block_use_count.get_mut(&bo) {
3,437✔
481
                    Some(count) => {
93✔
482
                        let count_ = *count + 1;
93✔
483
                        def1o!(
93✔
484
                            "block_use_count[{}] += 1 ({}); [{}‥{}]; total span [{}‥{})",
93✔
485
                            bo, count_, *fo, fo_end,
486
                            BlockReader::file_offset_at_block_offset(bo_beg, blocksz),
93✔
487
                            BlockReader::file_offset_at_block_offset(bo_end + 1, blocksz),
93✔
488
                        );
489
                        *count = count_;
93✔
490
                    }
491
                    None => {
492
                        def1o!(
3,344✔
493
                            "block_use_count[{}] = 1; [{}‥{}]; total span [{}‥{})",
3,344✔
494
                            bo, *fo, fo_end,
495
                            BlockReader::file_offset_at_block_offset(bo_beg, blocksz),
3,344✔
496
                            BlockReader::file_offset_at_block_offset(bo_end + 1, blocksz),
3,344✔
497
                        );
498
                        block_use_count.insert(bo, 1);
3,344✔
499
                    }
500
                }
501
            }
502
        }
503
        #[cfg(debug_assertions)]
504
        {
505
            for (bo, count) in block_use_count.iter() {
3,344✔
506
                def1o!(
3,344✔
507
                    "block_use_count[{}] = {}; total span [{}‥{}]",
3,344✔
508
                    bo, count,
509
                    BlockReader::file_offset_at_block_offset(*bo, blocksz),
3,344✔
510
                    BlockReader::file_offset_at_block_offset(*bo + 1, blocksz),
3,344✔
511
                );
512
            }
513
        }
514

515
        let map_max_len = map_tvpair_fo.len();
101✔
516
        // now that the `fixedstruct_type` is known, create the FixedStructReader
517
        let mut fixedstructreader = FixedStructReader
101✔
518
        {
101✔
519
            blockreader,
101✔
520
            fixedstruct_type,
101✔
521
            filetype_fixedstruct,
101✔
522
            fixedstruct_size: fixedstruct_type.size(),
101✔
523
            high_score,
101✔
524
            tz_offset,
101✔
525
            cache_entries: FoToEntry::new(),
101✔
526
            map_tvpair_fo,
101✔
527
            block_use_count,
101✔
528
            first_entry_fileoffset,
101✔
529
            entries_stored_highest: 0,
101✔
530
            entries_out_of_order: out_of_order,
101✔
531
            entries_hits: 0,
101✔
532
            entries_miss: 0,
101✔
533
            entries_processed: 0,
101✔
534
            dt_first: DateTimeLOpt::None,
101✔
535
            dt_last: DateTimeLOpt::None,
101✔
536
            drop_entry_ok: 0,
101✔
537
            drop_entry_errors: 0,
101✔
538
            blockoffset_drop_last: 0,
101✔
539
            #[cfg(test)]
101✔
540
            dropped_blocks: DroppedBlocks::new(),
101✔
541
            map_tvpair_fo_max_len: map_max_len,
101✔
542
            error: None,
101✔
543
        };
101✔
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() {
186✔
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(
186✔
554
                fo, &tz_offset, fixedstructptr
186✔
555
            ) {
186✔
556
                Ok(fixedstruct) => {
186✔
557
                    if fixedstructreader.map_tvpair_fo.iter().find(|(_tv_pair, fo2)| &fo == *fo2).is_some() {
281✔
558
                        def1o!("insert entry at fo {}", fo);
158✔
559
                        fixedstructreader.insert_cache_entry(fixedstruct);
158✔
560
                    } else {
561
                        def1o!("skip entry at fo {}; not in map_tvpair_fo", fo);
28✔
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)");
101✔
573

574
        ResultFixedStructReaderNew::FileOk(fixedstructreader)
101✔
575
    }
131✔
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 {
139✔
582
        self.blockreader.blocksz()
139✔
583
    }
139✔
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 {
356✔
590
        self.blockreader.filesz()
356✔
591
    }
356✔
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 {
23✔
598
        self.blockreader.filetype()
23✔
599
    }
23✔
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 {
23✔
606
        self.blockreader.path()
23✔
607
    }
23✔
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 {
1✔
620
        self.entries_processed
1✔
621
    }
1✔
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(
1✔
634
        &self,
1✔
635
        fileoffset: FileOffset,
1✔
636
    ) -> BlockOffset {
1✔
637
        BlockReader::block_offset_at_file_offset(fileoffset, self.blocksz())
1✔
638
    }
1✔
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(
1✔
645
        &self,
1✔
646
        blockoffset: BlockOffset,
1✔
647
    ) -> FileOffset {
1✔
648
        BlockReader::file_offset_at_block_offset(blockoffset, self.blocksz())
1✔
649
    }
1✔
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(
1✔
656
        &self,
1✔
657
        blockoffset: BlockOffset,
1✔
658
        blockindex: BlockIndex,
1✔
659
    ) -> FileOffset {
1✔
660
        BlockReader::file_offset_at_block_offset_index(blockoffset, self.blocksz(), blockindex)
1✔
661
    }
1✔
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(
1✔
668
        &self,
1✔
669
        fileoffset: FileOffset,
1✔
670
    ) -> BlockIndex {
1✔
671
        BlockReader::block_index_at_file_offset(fileoffset, self.blocksz())
1✔
672
    }
1✔
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 {
1✔
679
        BlockReader::count_blocks(self.filesz(), self.blocksz()) as Count
1✔
680
    }
1✔
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 {
1✔
686
        self.blockreader
1✔
687
            .blockoffset_last()
1✔
688
    }
1✔
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 {
10✔
694
        self.blockreader
10✔
695
            .fileoffset_last()
10✔
696
    }
10✔
697

698
    /// Is the passed `FileOffset` the last byte of the file?
699
    pub const fn is_fileoffset_last(
8✔
700
        &self,
8✔
701
        fileoffset: FileOffset,
8✔
702
    ) -> bool {
8✔
703
        self.fileoffset_last() == fileoffset
8✔
704
    }
8✔
705

706
    /// Is the passed `FixedStruct` the last of the file?
707
    #[inline(always)]
708
    pub fn is_last(
2✔
709
        &self,
2✔
710
        fixedstruct: &FixedStruct,
2✔
711
    ) -> bool {
2✔
712
        self.is_fileoffset_last(fixedstruct.fileoffset_end() - 1)
2✔
713
    }
2✔
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 (
1✔
719
        &self,
1✔
720
        fileoffset: FileOffset,
1✔
721
    ) -> FileOffset {
1✔
722
        (fileoffset / self.fixedstruct_size_fo()) * self.fixedstruct_size_fo()
1✔
723
    }
1✔
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> {
32✔
729
        match self.map_tvpair_fo.iter().min_by_key(|(tv_pair, fo)| (*tv_pair, *fo)) {
71✔
730
            Some((_tv_pair, fo_)) => Some(*fo_),
32✔
731
            None => None,
×
732
        }
733
    }
32✔
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 {
205✔
739
        self.fixedstruct_size
205✔
740
    }
205✔
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 {
180✔
747
        self.fixedstruct_size() as FileOffset
180✔
748
    }
180✔
749

750
    /// The `FixedStructType` of the file.
751
    #[inline(always)]
752
    pub const fn fixedstruct_type(&self) -> FixedStructType {
53✔
753
        self.fixedstruct_type
53✔
754
    }
53✔
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> {
1✔
761
        self.cache_entries
1✔
762
            .keys()
1✔
763
            .cloned()
1✔
764
            .collect()
1✔
765
    }
1✔
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(
158✔
805
        &mut self,
158✔
806
        entry: FixedStruct,
158✔
807
    ) {
158✔
808
        defn!("@{}", entry.fileoffset_begin());
158✔
809
        let fo_beg: FileOffset = entry.fileoffset_begin();
158✔
810
        debug_assert!(
158✔
811
            !self.cache_entries.contains_key(&fo_beg),
158✔
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
158✔
818
            .insert(fo_beg, entry);
158✔
819
        self.entries_stored_highest = std::cmp::max(
158✔
820
            self.entries_stored_highest, self.cache_entries.len()
158✔
821
        );
158✔
822
        self.entries_processed += 1;
158✔
823
        defo!("entries_processed = {}", self.entries_processed);
158✔
824

825
        defx!();
158✔
826
    }
158✔
827

828
    /// Update the statistic `DateTimeL` of
829
    /// `self.dt_first` and `self.dt_last`
830
    fn dt_first_last_update(
133✔
831
        &mut self,
133✔
832
        datetime: &DateTimeL,
133✔
833
    ) {
133✔
834
        defñ!("({:?})", datetime);
133✔
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 {
133✔
839
            Some(dt_first_) => {
42✔
840
                if &dt_first_ > datetime {
42✔
841
                    self.dt_first = Some(*datetime);
×
842
                }
42✔
843
            }
844
            None => {
91✔
845
                self.dt_first = Some(*datetime);
91✔
846
            }
91✔
847
        }
848
        match self.dt_last {
133✔
849
            Some(dt_last_) => {
42✔
850
                if &dt_last_ < datetime {
42✔
851
                    self.dt_last = Some(*datetime);
41✔
852
                }
41✔
853
            }
854
            None => {
91✔
855
                self.dt_last = Some(*datetime);
91✔
856
            }
91✔
857
        }
858
    }
133✔
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(
133✔
869
        &mut self,
133✔
870
        fixedstruct: &FixedStruct,
133✔
871
    ) -> usize {
133✔
872
        let bsz: BlockSz = self.blocksz();
133✔
873
        defn!(
133✔
874
            "(fixedstruct@{}); offsets [{}‥{}), blocks [{}‥{}]",
133✔
875
            fixedstruct.fileoffset_begin(),
133✔
876
            fixedstruct.fileoffset_begin(),
133✔
877
            fixedstruct.fileoffset_end(),
133✔
878
            fixedstruct.blockoffset_begin(bsz),
133✔
879
            fixedstruct.blockoffset_end(bsz),
133✔
880
        );
881
        let mut dropped_ok: usize = 0;
133✔
882
        let mut dropped_err: usize = 0;
133✔
883
        let mut bo_at: BlockOffset = fixedstruct.blockoffset_begin(bsz);
133✔
884
        let bo_end: BlockOffset = fixedstruct.blockoffset_end(bsz);
133✔
885
        debug_assert_le!(bo_at, bo_end);
133✔
886
        while bo_at <= bo_end {
3,019✔
887
            defo!("block_use_count.get_mut({})", bo_at);
2,886✔
888
            match self.block_use_count.get_mut(&bo_at) {
2,886✔
889
                Some(count) => {
2,882✔
890
                    if *count <= 1 {
2,882✔
891
                        defo!(
2,806✔
892
                            "block_use_count[{}] found; count=={}; total span [{}‥{})",
2,806✔
893
                            bo_at, count,
894
                            BlockReader::file_offset_at_block_offset(bo_at, bsz),
2,806✔
895
                            BlockReader::file_offset_at_block_offset(bo_end + 1, bsz),
2,806✔
896
                        );
897
                        if self
2,806✔
898
                            .blockreader
2,806✔
899
                            .drop_block(bo_at)
2,806✔
900
                        {
901
                            defo!(
2,803✔
902
                                "dropped block {}; total span [{}‥{})",
2,803✔
903
                                bo_at,
904
                                BlockReader::file_offset_at_block_offset(bo_at, bsz),
2,803✔
905
                                BlockReader::file_offset_at_block_offset(bo_end + 1, bsz),
2,803✔
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);
2,803✔
910
                            self.block_use_count.remove(&bo_at);
2,803✔
911
                            #[cfg(test)]
912
                            self.dropped_blocks.push_back(bo_at);
2,803✔
913
                            dropped_ok += 1;
2,803✔
914
                        } else {
915
                            defo!("failed to drop block {}", bo_at);
3✔
916
                            dropped_err += 1;
3✔
917
                        }
918
                    } else {
919
                        *count -= 1;
76✔
920
                        defo!("block_use_count[{}] found; count-=1=={}", bo_at, *count);
76✔
921
                    }
922
                }
923
                None => {
924
                    defo!("block_use_count[{}] not found", bo_at);
4✔
925
                }
926
            }
927
            bo_at += 1;
2,886✔
928
        }
929
        if dropped_ok > 0 {
133✔
930
            self.drop_entry_ok += 1;
101✔
931
        }
101✔
932
        if dropped_err > 0 {
133✔
933
            self.drop_entry_errors += 1;
2✔
934
        }
131✔
935
        defx!("return {}", dropped_ok);
133✔
936

937
        dropped_ok
133✔
938
    }
133✔
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(
133✔
944
        &mut self,
133✔
945
        fileoffset: FileOffset,
133✔
946
    ) -> Option<FixedStruct> {
133✔
947
        match self.cache_entries.remove(&fileoffset) {
133✔
948
            Some(fixedstruct) => {
104✔
949
                defñ!("({}): found in store", fileoffset);
104✔
950
                self.entries_hits += 1;
104✔
951

952
                Some(fixedstruct)
104✔
953
            }
954
            None => {
955
                defñ!("({}): not found in store", fileoffset);
29✔
956
                self.entries_miss += 1;
29✔
957

958
                None
29✔
959
            }
960
        }
961
    }
133✔
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(
128✔
975
        blockreader: &mut BlockReader,
128✔
976
        oneblock: bool,
128✔
977
        types_to_bonus: FixedStructTypeSet,
128✔
978
    ) -> ResultFixedStructReaderScoreFileError {
128✔
979
        def1n!("(oneblock={}, types_to_bonus len {})", oneblock, types_to_bonus.len());
128✔
980
        #[cfg(debug_assertions)]
981
        {
982
            for (fixedstructtype, bonus) in types_to_bonus.iter() {
491✔
983
                def1o!(
491✔
984
                    "types_to_bonus: ({:<30?}, {:2}) size {}",
491✔
985
                    fixedstructtype, bonus, fixedstructtype.size(),
491✔
986
                );
987
            }
988
        }
989
        // allocate largest possible buffer needed on the stack
990
        let mut buffer: [u8; ENTRY_SZ_MAX] = [0; ENTRY_SZ_MAX];
128✔
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;
128✔
998
        let mut highest_score: Score = 0;
128✔
999
        let mut highest_score_type: Option<FixedStructType> = None;
128✔
1000
        let mut highest_score_entries = ListFileOffsetFixedStructPtr::new();
128✔
1001

1002
        for (fixedstructtype, bonus) in types_to_bonus.into_iter() {
491✔
1003
            let mut _count_loop: usize = 0;
491✔
1004
            let mut count_found_entries: usize = 0;
491✔
1005
            let mut high_score: Score = 0;
491✔
1006
            let mut fo: FileOffset = 0;
491✔
1007
            let mut found_entries = ListFileOffsetFixedStructPtr::new();
491✔
1008

1009
            loop {
1010
                if count_found_entries >= COUNT_FOUND_ENTRIES_MAX {
1,552✔
1011
                    def1o!("count_found_entries {} >= COUNT_FOUND_ENTRIES_MAX {}", count_found_entries, COUNT_FOUND_ENTRIES_MAX);
444✔
1012
                    break;
444✔
1013
                }
1,108✔
1014
                _count_total += 1;
1,108✔
1015
                _count_loop += 1;
1,108✔
1016
                let utmp_sz: usize = fixedstructtype.size();
1,108✔
1017
                let fo_end = fo + utmp_sz as FileOffset;
1,108✔
1018
                def1o!(
1,108✔
1019
                    "loop try {} (total {}), fixedstructtype {:?}, zero the buffer (size {}), looking at fileoffset {}‥{} (0x{:08X}‥0x{:08X})",
1,108✔
1020
                    _count_loop, _count_total, fixedstructtype, buffer.len(), fo, fo_end, fo, fo_end
1,108✔
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);
1,108✔
1029
                // read data into buffer
1030
                let buffer_read: usize = match blockreader.read_data_to_buffer(
1,108✔
1031
                    fo,
1,108✔
1032
                    fo_end,
1,108✔
1033
                    oneblock,
1,108✔
1034
                    &mut buffer,
1,108✔
1035
                ) {
1,108✔
1036
                    ResultReadDataToBuffer::Found(buffer_read) => buffer_read,
1,061✔
1037
                    ResultReadDataToBuffer::Err(err) => {
×
1038
                        def1x!("return Err");
×
1039
                        return ResultFixedStructReaderScoreFileError::FileErrIo(err);
×
1040
                    }
1041
                    ResultReadDataToBuffer::Done => {
47✔
1042
                        // reached end of block (if `oneblock` is `true`) or end of file
1043
                        break;
47✔
1044
                    }
1045
                };
1046
                if buffer_read < utmp_sz {
1,061✔
1047
                    def1o!(
×
1048
                        "read_data_to_buffer read bytes {} < {} requested fixedstruct size bytes; break",
×
1049
                        buffer_read, utmp_sz,
1050
                    );
1051
                    break;
×
1052
                }
1,061✔
1053
                // advance the file offset for the next loop
1054
                let fo2 = fo;
1,061✔
1055
                fo += utmp_sz as FileOffset;
1,061✔
1056
                // grab the slice of interest
1057
                let slice_ = &buffer[..buffer_read];
1,061✔
1058
                // convert buffer to fixedstruct
1059
                let fixedstructptr: FixedStructDynPtr = match buffer_to_fixedstructptr(slice_, fixedstructtype) {
1,061✔
1060
                    Some(val) => val,
911✔
1061
                    None => {
1062
                        def1o!(
150✔
1063
                            "buffer_to_fixedstructptr(buf len {}, {:?}) returned None; continue",
150✔
1064
                            buffer.len(), fixedstructtype,
150✔
1065
                        );
1066
                        continue;
150✔
1067
                    }
1068
                };
1069
                count_found_entries += 1;
911✔
1070
                // score the newly create fixedstruct
1071
                let score: Score = FixedStruct::score_fixedstruct(&fixedstructptr, bonus);
911✔
1072
                def1o!("score {} for entry type {:?} @[{}‥{}]",
911✔
1073
                       score, fixedstructptr.fixedstruct_type(), fo2, fo_end);
911✔
1074
                // update the high score
1075
                let _fs_type: FixedStructType = fixedstructptr.fixedstruct_type();
911✔
1076
                found_entries.push_back((fo2, fixedstructptr));
911✔
1077
                if score <= high_score {
911✔
1078
                    def1o!(
783✔
1079
                        "score {} ({:?}) not higher than high score {}",
783✔
1080
                        score,
1081
                        _fs_type,
1082
                        high_score,
1083
                    );
1084
                    // score is not higher than high score so continue
1085
                    continue;
783✔
1086
                }
128✔
1087
                // there is a new high score for this type
1088
                def1o!("new high score {} for entry type {:?} @[{}‥{}]",
128✔
1089
                       score, _fs_type, fo2, fo_end);
1090
                high_score = score;
128✔
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 {
491✔
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 {
120✔
1098
                    None => {
1099
                        def1o!(
120✔
1100
                            "new highest score {} entry type {:?} with {} entries",
120✔
1101
                            high_score, fixedstructtype, found_entries.len(),
120✔
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;
120✔
1113
                highest_score_type = Some(fixedstructtype);
120✔
1114
                highest_score_entries = found_entries;
120✔
1115
            } else {
1116
                def1o!(
371✔
1117
                    "no new highest score: score {} entry type {:?} with {} entries. old high score remains: score {} entry type {:?} with {} entries",
371✔
1118
                    high_score, fixedstructtype, found_entries.len(),
371✔
1119
                    highest_score, highest_score_type, highest_score_entries.len(),
371✔
1120
                );
1121
            }
1122
        }
1123

1124
        match highest_score_type {
128✔
1125
            None => {
1126
                def1x!("return Err {:?}", ResultFixedStructReaderScoreFileError::FileErrNoHighScore);
8✔
1127
                return ResultFixedStructReaderScoreFileError::FileErrNoHighScore;
8✔
1128
            }
1129
            Some(highest_score_type) => {
120✔
1130
                def1x!("return Ok(({:?}, {}, found_entries))", highest_score_type, highest_score);
120✔
1131

1132
                ResultFixedStructReaderScoreFileError::FileOk(highest_score_type, highest_score, highest_score_entries)
120✔
1133
            }
1134
        }
1135
    }
128✔
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(
128✔
1152
        blockreader: &mut BlockReader,
128✔
1153
        filetype_fixedstruct: &FileTypeFixedStruct,
128✔
1154
        oneblock: bool,
128✔
1155
    ) -> ResultFixedStructReaderScoreFileError
128✔
1156
    {
1157
        def1n!("({:?}, oneblock={})", filetype_fixedstruct, oneblock);
128✔
1158

1159
        // short-circuit special case of empty file
1160
        if blockreader.filesz() == 0 {
128✔
1161
            def1x!("empty file; return FileErrEmpty");
×
1162
            return ResultFixedStructReaderScoreFileError::FileErrEmpty;
×
1163
        }
128✔
1164

1165
        let types_to_bonus: FixedStructTypeSet = match filesz_to_types(
128✔
1166
            blockreader.filesz(),
128✔
1167
            filetype_fixedstruct,
128✔
1168
        ) {
128✔
1169
            Some(set) => set,
128✔
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);
128✔
1178

1179
        match FixedStructReader::score_file(blockreader, oneblock, types_to_bonus) {
128✔
1180
            ret => {
128✔
1181
                def1x!("score_file returned {:?}", ret);
128✔
1182

1183
                ret
128✔
1184
            }
1185
        }
1186
    }
128✔
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(
120✔
1195
        blockreader: &mut BlockReader,
120✔
1196
        fixedstruct_type: FixedStructType,
120✔
1197
        dt_filter_after: &DateTimeLOpt,
120✔
1198
        dt_filter_before: &DateTimeLOpt,
120✔
1199
    ) -> ResultTvFo
120✔
1200
    {
1201
        defn!();
120✔
1202
        // allocate largest possible buffer needed on the stack
1203
        let mut buffer: [u8; TIMEVAL_SZ_MAX] = [0; TIMEVAL_SZ_MAX];
120✔
1204
        // map of time values to file offsets
1205
        let mut map_tv_pair_fo: MapTvPairToFo = MapTvPairToFo::new();
120✔
1206
        // count of out of order entries
1207
        let mut out_of_order: usize = 0;
120✔
1208
        // valid fixedstruct but does not pass the time value filters
1209
        let mut valid_no_pass_filter: usize = 0;
120✔
1210
        // null fixedstruct or non-sense time values
1211
        let mut invalid: usize = 0;
120✔
1212
        // total number of entries processed
1213
        let mut total_entries: usize = 0;
120✔
1214

1215
        let tv_filter_after: Option<tv_pair_type> = match dt_filter_after {
120✔
1216
            Some(dt) => Some(convert_datetime_tvpair(dt)),
60✔
1217
            None => None,
60✔
1218
        };
1219
        defo!("tv_filter_after: {:?}", tv_filter_after);
120✔
1220
        let tv_filter_before: Option<tv_pair_type> = match dt_filter_before {
120✔
1221
            Some(dt) => Some(convert_datetime_tvpair(dt)),
34✔
1222
            None => None,
86✔
1223
        };
1224
        defo!("tv_filter_before: {:?}", tv_filter_before);
120✔
1225

1226
        // 1. get offsets
1227
        let entry_sz: FileOffset = fixedstruct_type.size() as FileOffset;
120✔
1228
        debug_assert_eq!(blockreader.filesz() % entry_sz, 0, "file not a multiple of entry size {}", entry_sz);
120✔
1229
        let tv_sz: usize = fixedstruct_type.size_tv();
120✔
1230
        let tv_offset: usize = fixedstruct_type.offset_tv();
120✔
1231
        let slice_: &mut [u8] = &mut buffer[..tv_sz];
120✔
1232
        let mut fo: FileOffset = 0;
120✔
1233
        let mut tv_pair_prev: Option<tv_pair_type> = None;
120✔
1234
        loop {
1235
            // 2. jump to each offset, grab datetime bytes,
1236
            let beg: FileOffset = fo + tv_offset as FileOffset;
397✔
1237
            let end: FileOffset = beg + tv_sz as FileOffset;
397✔
1238
            match blockreader.read_data_to_buffer(
397✔
1239
                beg,
397✔
1240
                end,
397✔
1241
                false,
397✔
1242
                slice_,
397✔
1243
            ) {
397✔
1244
                ResultReadDataToBuffer::Found(_readn) => {
277✔
1245
                    defo!("read {} bytes at fileoffset {}", _readn, beg);
277✔
1246
                    debug_assert_eq!(
277✔
1247
                        _readn, tv_sz as usize,
277✔
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 => {
120✔
1257
                    defo!("return Done");
120✔
1258
                    break;
120✔
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(
277✔
1263
                slice_,
277✔
1264
            ) {
277✔
1265
                Some(pair) => pair,
277✔
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);
277✔
1275
            if tv_pair == tv_pair_type(0, 0) {
277✔
1276
                defo!("tv_pair is (0, 0); continue");
×
1277
                fo += entry_sz;
×
1278
                continue;
×
1279
            }
277✔
1280
            match tv_pair_prev {
277✔
1281
                Some(tv_pair_prev) => {
157✔
1282
                    if tv_pair < tv_pair_prev {
157✔
1283
                        out_of_order += 1;
40✔
1284
                        defo!(
40✔
1285
                            "out_of_order = {}; tv_pair = {:?}, tv_pair_prev = {:?}",
40✔
1286
                            out_of_order, tv_pair, tv_pair_prev,
1287
                        );
1288
                    }
117✔
1289
                }
1290
                None => {}
120✔
1291
            }
1292
            tv_pair_prev = Some(tv_pair);
277✔
1293
            total_entries += 1;
277✔
1294
            // 4. compare to time value filters
1295
            if let Some(tv_filter) = tv_filter_after {
277✔
1296
                if tv_pair < tv_filter {
150✔
1297
                    defo!("tv_pair {:?} < {:?} tv_filter_after; continue", tv_pair, tv_filter);
61✔
1298
                    fo += entry_sz;
61✔
1299
                    valid_no_pass_filter += 1;
61✔
1300
                    continue;
61✔
1301
                }
89✔
1302
            }
127✔
1303
            if let Some(tv_filter) = tv_filter_before {
216✔
1304
                if tv_pair > tv_filter {
49✔
1305
                    defo!("tv_pair {:?} > {:?} tv_filter_before; continue", tv_pair, tv_filter);
22✔
1306
                    fo += entry_sz;
22✔
1307
                    valid_no_pass_filter += 1;
22✔
1308
                    // continue to check _all_ entries as the entries may not be
1309
                    // in chronological order
1310
                    continue;
22✔
1311
                }
27✔
1312
            }
167✔
1313
            // 5. save entries that pass the time value filters
1314
            defo!("tv_pair {:?} @{} passes time value filters", tv_pair, fo);
194✔
1315
            map_tv_pair_fo.insert(tv_pair, fo);
194✔
1316

1317
            fo += entry_sz;
194✔
1318
        }
1319
        // 6. return list of valid entries
1320
        defx!("map_tv_pair_fo len {}", map_tv_pair_fo.len());
120✔
1321

1322
        ResultTvFo::Ok(
120✔
1323
            (total_entries, invalid, valid_no_pass_filter, out_of_order, map_tv_pair_fo)
120✔
1324
        )
120✔
1325
    }
120✔
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])
177✔
1339
        -> ResultS3FixedStructFind
177✔
1340
    {
1341
        defn!("({})", fo);
177✔
1342

1343
        let sz: FileOffset = self.fixedstruct_size_fo();
177✔
1344
        debug_assert_eq!(
177✔
1345
            fo % sz, 0,
177✔
1346
            "fileoffset {} not multiple of {}", fo, sz,
×
1347
        );
1348
        let fileoffset: FileOffset = fo - (fo % sz);
177✔
1349

1350
        if fileoffset >= self.filesz() {
177✔
1351
            defx!(
44✔
1352
                "return ResultS3FixedStructFind::Done; fileoffset {} >= filesz {}",
44✔
1353
                fileoffset, self.filesz()
44✔
1354
            );
1355
            return ResultS3FixedStructFind::Done;
44✔
1356
        }
133✔
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 = {
133✔
1366
            let mut fo_next_: FileOffset = self.filesz();
133✔
1367
            let mut next_pair: bool = false;
133✔
1368
            let mut tv_pair_at_opt: Option<tv_pair_type> = None;
133✔
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() {
214✔
1372
                if next_pair {
214✔
1373
                    defo!("set fo_next = {}", fo_at);
57✔
1374
                    fo_next_ = *fo_at;
57✔
1375
                    break;
57✔
1376
                }
157✔
1377
                if &fileoffset == fo_at {
157✔
1378
                    defo!(
125✔
1379
                        "found fileoffset {} with key {:?} in map_tvpair_fo",
125✔
1380
                        fileoffset, tv_pair_at,
1381
                    );
1382
                    tv_pair_at_opt = Some(*tv_pair_at);
125✔
1383
                    next_pair = true;
125✔
1384
                }
32✔
1385
            }
1386
            // remove the `tv_pair` from `map_tvpair_fo`
1387
            match tv_pair_at_opt {
133✔
1388
                Some(tv_pair_at) => {
125✔
1389
                    self.map_tvpair_fo.remove(&tv_pair_at);
125✔
1390
                    defo!(
125✔
1391
                        "remove tv_pair {:?}; map_tvpair_fo size {}",
125✔
1392
                        tv_pair_at, self.map_tvpair_fo.len()
125✔
1393
                    );
1394
                }
1395
                None => {
1396
                    defo!("no map_tvpair_fo found!");
8✔
1397
                }
1398
            }
1399

1400
            fo_next_
133✔
1401
        };
1402
        defo!("fo_next = {}", fo_next);
133✔
1403

1404
        // check if the entry is already stored
1405
        if let Some(fixedstruct) = self.remove_cache_entry(fileoffset) {
133✔
1406
            self.dt_first_last_update(fixedstruct.dt());
104✔
1407
            // try to drop blocks associated with the entry
1408
            self.drop_entry(&fixedstruct);
104✔
1409
            defx!(
104✔
1410
                "remove_cache_entry found fixedstruct at fileoffset {}; return Found({}, …)",
104✔
1411
                fileoffset, fo_next,
1412
            );
1413
            return ResultS3FixedStructFind::Found((fo_next, fixedstruct));
104✔
1414
        }
29✔
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 {
29✔
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
        }
29✔
1433

1434
        // zero out the slice
1435
        defo!("zero buffer[‥{}]", sz);
29✔
1436
        let slice_: &mut [u8] = &mut buffer[..sz as usize];
29✔
1437
        slice_.iter_mut().for_each(|m| *m = 0);
29✔
1438

1439
        // read raw bytes into the slice
1440
        let _readn = match self.blockreader.read_data_to_buffer(
29✔
1441
            fileoffset,
29✔
1442
            fileoffset + sz,
29✔
1443
            false,
29✔
1444
            slice_,
29✔
1445
        ) {
29✔
1446
            ResultReadDataToBuffer::Found(val) => val,
29✔
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);
29✔
1460

1461
        // create a FixedStruct from the slice
1462
        let fs: FixedStruct = match FixedStruct::new(
29✔
1463
            fileoffset,
29✔
1464
            &self.tz_offset,
29✔
1465
            &slice_,
29✔
1466
            self.fixedstruct_type(),
29✔
1467
        ) {
29✔
1468
            Ok(val) => val,
29✔
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;
29✔
1476
        defo!("entries_processed = {}", self.entries_processed);
29✔
1477
        self.dt_first_last_update(fs.dt());
29✔
1478
        // try to drop blocks associated with the entry
1479
        self.drop_entry(&fs);
29✔
1480

1481
        defx!("return ResultS3FixedStructFind::Found((fo_next={}, …))", fo_next);
29✔
1482

1483
        ResultS3FixedStructFind::Found((fo_next, fs))
29✔
1484
    }
177✔
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 {
24✔
1529
        let fixedstructreader_fixedstructtype_opt = Some(self.fixedstruct_type());
24✔
1530
        let fixedstructreader_filetypefixedstruct_opt = Some(self.filetype_fixedstruct);
24✔
1531
        let fixedstructreader_high_score: Score = self.high_score;
24✔
1532
        let fixedstructreader_utmp_entries: Count = self.entries_processed;
24✔
1533
        let fixedstructreader_first_entry_fileoffset: FileOffset = self.first_entry_fileoffset;
24✔
1534
        let fixedstructreader_entries_out_of_order: usize = self.entries_out_of_order;
24✔
1535
        let fixedstructreader_utmp_entries_max: Count = self.entries_stored_highest as Count;
24✔
1536
        let fixedstructreader_utmp_entries_hit: Count = self.entries_hits as Count;
24✔
1537
        let fixedstructreader_utmp_entries_miss: Count = self.entries_miss as Count;
24✔
1538
        let fixedstructreader_drop_entry_ok: Count = self.drop_entry_ok;
24✔
1539
        let fixedstructreader_drop_entry_errors: Count = self.drop_entry_errors;
24✔
1540
        let fixedstructreader_datetime_first = self.dt_first;
24✔
1541
        let fixedstructreader_datetime_last = self.dt_last;
24✔
1542
        let fixedstructreader_map_tvpair_fo_max_len: usize = self.map_tvpair_fo_max_len;
24✔
1543

1544
        SummaryFixedStructReader {
24✔
1545
            fixedstructreader_fixedstructtype_opt,
24✔
1546
            fixedstructreader_filetypefixedstruct_opt,
24✔
1547
            fixedstructreader_fixedstruct_size: self.fixedstruct_size(),
24✔
1548
            fixedstructreader_high_score,
24✔
1549
            fixedstructreader_utmp_entries,
24✔
1550
            fixedstructreader_first_entry_fileoffset,
24✔
1551
            fixedstructreader_entries_out_of_order,
24✔
1552
            fixedstructreader_utmp_entries_max,
24✔
1553
            fixedstructreader_utmp_entries_hit,
24✔
1554
            fixedstructreader_utmp_entries_miss,
24✔
1555
            fixedstructreader_drop_entry_ok,
24✔
1556
            fixedstructreader_drop_entry_errors,
24✔
1557
            fixedstructreader_datetime_first,
24✔
1558
            fixedstructreader_datetime_last,
24✔
1559
            fixedstructreader_map_tvpair_fo_max_len,
24✔
1560
        }
24✔
1561
    }
24✔
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 {
23✔
1567
        let path = self.path().clone();
23✔
1568
        let path_ntf = None;
23✔
1569
        let filetype = self.filetype();
23✔
1570
        let logmessagetype = filetype.to_logmessagetype();
23✔
1571
        let summaryblockreader = self.blockreader.summary();
23✔
1572
        let summaryutmpreader = self.summary();
23✔
1573
        let error: Option<String> = self.error.clone();
23✔
1574

1575
        Summary::new(
23✔
1576
            path,
23✔
1577
            path_ntf,
23✔
1578
            filetype,
23✔
1579
            logmessagetype,
23✔
1580
            Some(summaryblockreader),
23✔
1581
            None,
23✔
1582
            None,
23✔
1583
            None,
23✔
1584
            Some(summaryutmpreader),
23✔
1585
            None,
23✔
1586
            None,
23✔
1587
            error,
23✔
1588
        )
1589
    }
23✔
1590
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc