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

jtmoon79 / super-speedy-syslog-searcher / 28690460673

04 Jul 2026 01:15AM UTC coverage: 68.982% (+0.4%) from 68.549%
28690460673

push

github

jtmoon79
(BIN) allow -a -b accept timezone

19 of 32 new or added lines in 1 file covered. (59.38%)

2275 existing lines in 13 files now uncovered.

16951 of 24573 relevant lines covered (68.98%)

178397.05 hits per line

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

83.69
/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 std::collections::{
28
    BTreeMap,
29
    LinkedList,
30
};
31
use std::fmt;
32
use std::io::{
33
    Error,
34
    ErrorKind,
35
    Result,
36
};
37

38
use ::more_asserts::{
39
    debug_assert_ge,
40
    debug_assert_le,
41
};
42
#[allow(unused_imports)]
43
use ::si_trace_print::{
44
    de,
45
    def1n,
46
    def1o,
47
    def1x,
48
    def1ñ,
49
    defn,
50
    defo,
51
    defx,
52
    defñ,
53
    den,
54
    deo,
55
    dex,
56
    deñ,
57
    pfn,
58
    pfo,
59
    pfx,
60
};
61

62
use crate::common::{
63
    Count,
64
    FPath,
65
    FileOffset,
66
    FileSz,
67
    FileType,
68
    FileTypeFixedStruct,
69
    PathId,
70
    ResultFind,
71
    debug_panic,
72
    summary_stat,
73
    summary_stats_enabled,
74
};
75
use crate::data::datetime::{
76
    dt_after_or_before,
77
    dt_pass_filters,
78
    DateTimeL,
79
    DateTimeLOpt,
80
    FixedOffset,
81
    Result_Filter_DateTime1,
82
    Result_Filter_DateTime2,
83
    SystemTime,
84
};
85
use crate::data::fixedstruct::{
86
    buffer_to_fixedstructptr,
87
    convert_datetime_tvpair,
88
    filesz_to_types,
89
    tv_pair_type,
90
    FixedStruct,
91
    FixedStructDynPtr,
92
    FixedStructType,
93
    FixedStructTypeSet,
94
    Score,
95
    ENTRY_SZ_MAX,
96
    ENTRY_SZ_MIN,
97
    TIMEVAL_SZ_MAX,
98
};
99
use crate::readers::blockreader::{
100
    BlockIndex,
101
    BlockOffset,
102
    BlockReader,
103
    BlockSz,
104
    ResultReadDataToBuffer,
105
};
106
use crate::readers::summary::Summary;
107
use crate::{
108
    de_err,
109
    de_wrn,
110
    e_err,
111
};
112

113
// -----------------
114
// FixedStructReader
115

116
/// Map [`FileOffset`] To [`FixedStruct`].
117
///
118
/// Storage for `FixedStruct` found from the underlying `BlockReader`.
119
/// FileOffset key is the first byte/offset that begins the `FixedStruct`.
120
///
121
/// [`FileOffset`]: crate::common::FileOffset
122
/// [`FixedStruct`]: crate::data::fixedstruct::FixedStruct
123
pub type FoToEntry = BTreeMap<FileOffset, FixedStruct>;
124

125
/// Map [`FileOffset`] To `FileOffset`
126
///
127
/// [`FileOffset`]: crate::common::FileOffset
128
pub type FoToFo = BTreeMap<FileOffset, FileOffset>;
129

130
pub type FoList = LinkedList<FileOffset>;
131

132
type MapTvPairToFo = BTreeMap<tv_pair_type, FileOffset>;
133

134
/// [`FixedStructReader.find`*] functions results.
135
///
136
/// [`FixedStructReader.find`*]: self::FixedStructReader#method.find_entry
137
pub type ResultFindFixedStruct = ResultFind<(FileOffset, FixedStruct), (Option<FileOffset>, Error)>;
138

139
pub type ResultFindFixedStructProcZeroBlock = ResultFind<(FixedStructType, Score, ListFileOffsetFixedStructPtr), Error>;
140

141
pub type ResultTvFo = Result<(usize, usize, usize, Count, MapTvPairToFo)>;
142

143
#[cfg(test)]
144
pub type DroppedBlocks = LinkedList<BlockOffset>;
145

146
type ListFileOffsetFixedStructPtr = LinkedList<(FileOffset, FixedStructDynPtr)>;
147

148
/// Enum return value for [`FixedStructReader::new`].
149
#[derive(Debug)]
150
pub enum ResultFixedStructReaderNew<E> {
151
    /// `FixedStructReader::new` was successful and returns the
152
    /// `FixedStructReader`
153
    FileOk(FixedStructReader),
154
    FileErrEmpty,
155
    FileErrTooSmall(String),
156
    /// No valid fixedstruct
157
    FileErrNoValidFixedStruct,
158
    /// No fixedstruct within the datetime filters
159
    FileErrNoFixedStructWithinDtFilters,
160
    /// Carries the `E` error data. This is how an [`Error`] is carried between
161
    /// a processing thread and the main printing thread
162
    FileErrIo(E),
163
}
164

165
pub type ResultFixedStructReaderNewError = ResultFixedStructReaderNew<Error>;
166

167
#[derive(Debug)]
168
pub enum ResultFixedStructReaderScoreFile<E> {
169
    /// `score_file` was successful; return the `FixedStructType`, `Score`,
170
    /// already processed `FixedStrcutDynPtr` entries (with associated offsets)
171
    FileOk(FixedStructType, Score, ListFileOffsetFixedStructPtr),
172
    FileErrEmpty,
173
    /// No valid fixedstruct
174
    FileErrNoValidFixedStruct,
175
    /// No high score for the file
176
    FileErrNoHighScore,
177
    /// Carries the `E` error data. This is how an [`Error`] is carried between
178
    /// a processing thread and the main printing thread
179
    FileErrIo(E),
180
}
181

182
pub type ResultFixedStructReaderScoreFileError = ResultFixedStructReaderScoreFile<Error>;
183

184
/// A specialized reader that uses [`BlockReader`] to read [`FixedStruct`]
185
/// entries in a file.
186
///
187
/// The `FixedStructReader` converts `\[u8\]` to `FixedStruct` in
188
/// [`buffer_to_fixedstructptr`].
189
///
190
/// ## Summary of operation
191
///
192
/// A `FixedStructReader` first deteremines the `FixedStructType` of the file
193
/// in [`preprocess_fixedstructtype`].
194
/// Then it scans all ***t***ime ***v***alues in each entry to determine the
195
/// order to process the entries in [`preprocess_timevalues`]. This implies the
196
/// `blockreader` must read the entire file into memory. So far, "in the wild"
197
/// user accounting records are only a few kilobytes at most. So reading the
198
/// entire file into memory should not put much strain on memory usage of a
199
/// typical desktop or server.
200
/// The processing of time values is done first and for the entire file
201
/// because records may not be stored in chronological order.
202
/// Then the caller makes repeated calls to [`process_entry_at`] which processes
203
/// the `FixedStruct`s found in the file.
204
///
205
/// 0x00 byte and 0xFF byte fixedstructs are considered a null entry and
206
/// ignored.
207
///
208
/// _XXX: not a rust "Reader"; does not implement trait [`Read`]._
209
///
210
/// [`buffer_to_fixedstructptr`]: crate::data::fixedstruct::buffer_to_fixedstructptr
211
/// [`BlockReader`]: crate::readers::blockreader::BlockReader
212
/// [`Read`]: std::io::Read
213
/// [`preprocess_fixedstructtype`]: FixedStructReader::preprocess_fixedstructtype
214
/// [`preprocess_timevalues`]: FixedStructReader::preprocess_timevalues
215
/// [`process_entry_at`]: FixedStructReader::process_entry_at
216
pub struct FixedStructReader {
217
    pub(crate) blockreader: BlockReader,
218
    fixedstruct_type: FixedStructType,
219
    filetype_fixedstruct: FileTypeFixedStruct,
220
    /// Size of a single [`FixedStruct`] entry.
221
    fixedstruct_size: usize,
222
    /// The highest score found during `preprocess_file`.
223
    /// Used to determine the `FixedStructType` of the file.
224
    high_score: Score,
225
    /// Timezone to use for conversions using function
226
    /// [`convert_tvpair_to_datetime`].
227
    ///
228
    /// [`convert_tvpair_to_datetime`]: crate::data::fixedstruct::convert_tvpair_to_datetime
229
    tz_offset: FixedOffset,
230
    /// A temporary hold for [`FixedStruct`] entries found
231
    /// by [`preprocess_fixedstructtype`]. Use `insert_cache_entry` and
232
    /// `remove_entry` to manage this cache.
233
    ///
234
    /// [`preprocess_fixedstructtype`]: FixedStructReader::preprocess_fixedstructtype
235
    pub(crate) cache_entries: FoToEntry,
236
    /// A mapping of all entries in the entire file (that pass the datetime
237
    /// filters), mapped by [`tv_pair_type`] to [`FileOffset`]. Created by
238
    /// [`preprocess_timevalues`].
239
    ///
240
    /// [`preprocess_timevalues`]: FixedStructReader::preprocess_timevalues
241
    pub(crate) map_tvpair_fo: MapTvPairToFo,
242
    pub(crate) block_use_count: BTreeMap<BlockOffset, usize>,
243
    /// The first entry found in the file, by `FileOffset`
244
    pub(crate) first_entry_fileoffset: FileOffset,
245
    /// "high watermark" of `FixedStruct` stored in `self.cache_entries`
246
    pub(crate) entries_stored_highest: usize,
247
    pub(crate) entries_out_of_order: Count,
248
    /// Internal stats - hits of `self.cache_entries` in `find_entry*` functions.
249
    pub(super) entries_hits: Count,
250
    /// Internal stats - misses of `self.cache_entries` in `find_entry*` functions.
251
    pub(super) entries_miss: Count,
252
    /// `Count` of `FixedStruct`s processed.
253
    ///
254
    /// Distinct from `self.cache_entries.len()` as that may have contents removed.
255
    pub(super) entries_processed: Count,
256
    /// Summary statistic.
257
    /// First (soonest) processed [`DateTimeL`] (not necessarily printed,
258
    /// not representative of the entire file).
259
    pub(super) dt_first: DateTimeLOpt,
260
    /// Summary statistic.
261
    /// Last (latest) processed [`DateTimeL`] (not necessarily printed,
262
    /// not representative of the entire file).
263
    pub(super) dt_last: DateTimeLOpt,
264
    /// Summary statistic.
265
    /// `Count` of dropped `FixedStruct`.
266
    pub(super) drop_entry_ok: Count,
267
    /// Summary statistic.
268
    /// `Count` of failed drop attempts of `FixedStruct`.
269
    pub(super) drop_entry_errors: Count,
270
    /// testing-only tracker of successfully dropped `FixedStruct`
271
    #[cfg(test)]
272
    pub(crate) dropped_blocks: DroppedBlocks,
273
    pub(super) map_tvpair_fo_max_len: usize,
274
    /// The last [`Error`], if any, as a `String`. Set by [`set_error`].
275
    ///
276
    /// Annoyingly, cannot [Clone or Copy `Error`].
277
    ///
278
    /// [`Error`]: std::io::Error
279
    /// [Clone or Copy `Error`]: https://github.com/rust-lang/rust/issues/24135
280
    /// [`set_error`]: self::FixedStructReader#method.set_error
281
    // TRACKING: https://github.com/rust-lang/rust/issues/24135
282
    error: Option<String>,
283
}
284

285
impl fmt::Debug for FixedStructReader {
286
    fn fmt(
70✔
287
        &self,
70✔
288
        f: &mut fmt::Formatter,
70✔
289
    ) -> fmt::Result {
70✔
290
        f.debug_struct("FixedStructReader")
70✔
291
            .field("Path", &self.path())
70✔
292
            .field("Entries", &self.cache_entries.len())
70✔
293
            .field("tz_offset", &self.tz_offset)
70✔
294
            .field("Error?", &self.error)
70✔
295
            .finish()
70✔
296
    }
70✔
297
}
298

299
// TODO: [2023/04] remove redundant variable prefix name `fixedstructreader_`
300
// TODO: [2023/05] instead of having 1:1 manual copying of `FixedStructReader`
301
//       fields to `SummaryFixedStructReader` fields, just store a
302
//       `SummaryFixedStructReader` in `FixedStructReader` and update directly.
303
#[allow(non_snake_case)]
304
#[derive(Clone, Default, Eq, PartialEq, Debug)]
305
pub struct SummaryFixedStructReader {
306
    pub fixedstructreader_fixedstructtype_opt: Option<FixedStructType>,
307
    pub fixedstructreader_filetypefixedstruct_opt: Option<FileTypeFixedStruct>,
308
    pub fixedstructreader_fixedstruct_size: usize,
309
    pub fixedstructreader_high_score: Score,
310
    pub fixedstructreader_utmp_entries: Count,
311
    pub fixedstructreader_first_entry_fileoffset: FileOffset,
312
    pub fixedstructreader_entries_out_of_order: Count,
313
    pub fixedstructreader_utmp_entries_max: Count,
314
    pub fixedstructreader_utmp_entries_hit: Count,
315
    pub fixedstructreader_utmp_entries_miss: Count,
316
    pub fixedstructreader_drop_entry_ok: Count,
317
    pub fixedstructreader_drop_entry_errors: Count,
318
    /// datetime soonest seen (not necessarily reflective of entire file)
319
    pub fixedstructreader_datetime_first: DateTimeLOpt,
320
    /// datetime latest seen (not necessarily reflective of entire file)
321
    pub fixedstructreader_datetime_last: DateTimeLOpt,
322
    pub fixedstructreader_map_tvpair_fo_max_len: usize,
323
}
324

325
/// Implement the FixedStructReader.
326
impl FixedStructReader {
327
    /// Create a new `FixedStructReader`.
328
    ///
329
    /// **NOTE:** this `new()` calls [`BlockerReader.read_block`],
330
    /// dissimilar from other
331
    /// `*Readers::new()` which try to avoid calls to `read_block`.
332
    /// This means the reading may return `Done` (like if the file is empty) and
333
    /// `Done` must be reflected in the return value of `new`. Hence this
334
    /// function has a specialized return value.
335
    ///
336
    /// [`BlockerReader.read_block`]: crate::readers::blockreader::BlockReader#method.read_block
337
    pub fn new(
201✔
338
        path_id: PathId,
201✔
339
        path: FPath,
201✔
340
        filetype: FileType,
201✔
341
        blocksz: BlockSz,
201✔
342
        tz_offset: FixedOffset,
201✔
343
        dt_filter_after: DateTimeLOpt,
201✔
344
        dt_filter_before: DateTimeLOpt,
201✔
345
    ) -> ResultFixedStructReaderNewError {
201✔
346
        def1n!(
201✔
347
            "({}, {:?}, filetype={:?}, blocksz={:?}, {:?}, {:?}, {:?})",
348
            path_id, path, filetype, blocksz, tz_offset, dt_filter_after, dt_filter_before,
349
        );
350
        let mut blockreader = match BlockReader::new(path_id, path.clone(), filetype, blocksz) {
201✔
351
            Ok(blockreader_) => blockreader_,
200✔
352
            Err(err) => {
1✔
353
                def1x!("return Err {}", err);
1✔
354
                //return Some(Result::Err(err));
355
                return ResultFixedStructReaderNew::FileErrIo(err);
1✔
356
            }
357
        };
358
        let filetype_fixedstruct = match filetype {
200✔
359
            FileType::FixedStruct { archival_type: _, fixedstruct_type: type_ } => type_,
200✔
360
            _ => {
361
                debug_panic!("Unexpected FileType: {:?}", filetype);
×
362
                return ResultFixedStructReaderNew::FileErrIo(Error::new(
×
363
                    ErrorKind::InvalidData,
×
UNCOV
364
                    format!("Unexpected FileType {:?}", filetype),
×
UNCOV
365
                ));
×
366
            }
367
        };
368

369
        const ENTRY_SZ_MIN_FSZ: FileSz = ENTRY_SZ_MIN as FileSz;
370
        if blockreader.filesz() == 0 {
200✔
371
            def1x!("return FileErrEmpty");
1✔
372
            return ResultFixedStructReaderNew::FileErrEmpty;
1✔
373
        } else if blockreader.filesz() < ENTRY_SZ_MIN_FSZ {
199✔
374
            def1x!(
1✔
375
                "return FileErrTooSmall; {} < {} (ENTRY_SZ_MIN)",
376
                blockreader.filesz(), ENTRY_SZ_MIN_FSZ
1✔
377
            );
378
            return ResultFixedStructReaderNew::FileErrTooSmall(
1✔
379
                format!(
1✔
380
                    "file size {} < {} (ENTRY_SZ_MIN), file {:?}",
1✔
381
                    blockreader.filesz(), ENTRY_SZ_MIN_FSZ, path,
1✔
382
                )
1✔
383
            );
1✔
384
        }
198✔
385

386
        // preprocess the file, pass `oneblock=false` to process
387
        // the entire file. This is because `lastlog` files are often nearly
388
        // entirely null bytes until maybe one entry near the end, so this should
389
        // search past the first block of data.
390
        let (
391
            fixedstruct_type,
190✔
392
            high_score,
190✔
393
            list_entries,
190✔
394
        ) = match FixedStructReader::preprocess_fixedstructtype(
198✔
395
            &mut blockreader, &filetype_fixedstruct, false,
198✔
396
        ) {
198✔
397
            ResultFixedStructReaderScoreFileError::FileOk(
190✔
398
                fixedstruct_type_, high_score_, list_entries_,
190✔
399
            ) => (fixedstruct_type_, high_score_, list_entries_),
190✔
400
            ResultFixedStructReaderScoreFileError::FileErrEmpty => {
×
UNCOV
401
                def1x!("return FileErrEmpty");
×
UNCOV
402
                return ResultFixedStructReaderNew::FileErrEmpty;
×
403
            }
404
            ResultFixedStructReaderScoreFileError::FileErrNoHighScore => {
8✔
405
                def1x!("return FileErrNoHighScore");
8✔
406
                return ResultFixedStructReaderNew::FileErrNoValidFixedStruct;
8✔
407
            }
408
            ResultFixedStructReaderScoreFileError::FileErrNoValidFixedStruct => {
×
UNCOV
409
                def1x!("return FileErrNoValidFixedStruct");
×
410
                return ResultFixedStructReaderNew::FileErrNoValidFixedStruct;
×
411
            }
412
            ResultFixedStructReaderScoreFileError::FileErrIo(err) => {
×
413
                de_err!("FixedStructReader::preprocess_fixedstructtype Error {}; file {:?}",
×
414
                        err, blockreader.path());
×
UNCOV
415
                def1x!("return Err {:?}", err);
×
UNCOV
416
                return ResultFixedStructReaderNew::FileErrIo(err);
×
417
            }
418
        };
419

420
        let (total, invalid, valid_no_filter, out_of_order, map_tvpair_fo) = 
190✔
421
            match FixedStructReader::preprocess_timevalues(
190✔
422
                &mut blockreader,
190✔
423
                fixedstruct_type,
190✔
424
                &dt_filter_after,
190✔
425
                &dt_filter_before,
190✔
426
            )
190✔
427
        {
428
            ResultTvFo::Err(err) => {
×
429
                de_err!("FixedStructReader::preprocess_timevalues Error {}; file {:?}",
×
430
                        err, blockreader.path());
×
UNCOV
431
                def1x!("return Err {:?}", err);
×
UNCOV
432
                return ResultFixedStructReaderNew::FileErrIo(err);
×
433
            }
434
            ResultTvFo::Ok(
190✔
435
                (total_, invalid_, valid_no_filter_, out_of_order_, map_tvpair_fo_)
190✔
436
            ) =>
190✔
437
                (total_, invalid_, valid_no_filter_, out_of_order_, map_tvpair_fo_),
190✔
438
        };
439
        def1o!("total: {}, invalid: {}, valid_no_filter: {}, out_of_order: {}",
190✔
440
               total, invalid, valid_no_filter, out_of_order);
441
        #[cfg(debug_assertions)]
442
        {
443
            def1o!("map_tvpair_fo has {} entries", map_tvpair_fo.len());
190✔
444
            for (_tv_pair, _fo) in map_tvpair_fo.iter() {
1,094✔
445
                def1o!("map_tvpair_fo: [tv_pair: {:?}] = fo: {}", _tv_pair, _fo);
1,094✔
446
            }
447
        }
448
        debug_assert_ge!(total, invalid);
190✔
449
        debug_assert_ge!(total, valid_no_filter);
190✔
450

451
        if map_tvpair_fo.is_empty() {
190✔
452
            if valid_no_filter > 0 {
19✔
453
                def1x!("return FileErrNoFixedStructWithinDtFilters");
19✔
454
                return ResultFixedStructReaderNew::FileErrNoFixedStructWithinDtFilters;
19✔
455
            }
×
UNCOV
456
            def1x!("return FileErrNoValidFixedStruct");
×
UNCOV
457
            return ResultFixedStructReaderNew::FileErrNoValidFixedStruct;
×
458
        }
171✔
459

460
        // set `first_entry_fileoffset` to the first entry by fileoffset
461
        let mut first_entry_fileoffset: FileOffset = blockreader.filesz();
171✔
462
        for (_tv_pair, fo) in map_tvpair_fo.iter() {
1,094✔
463
            if &first_entry_fileoffset > fo {
1,094✔
464
                first_entry_fileoffset = *fo;
201✔
465
            }
961✔
466
        }
467
        debug_assert_ne!(first_entry_fileoffset, blockreader.filesz(), "failed to update first_entry_fileoffset");
171✔
468

469
        // create a mapping of blocks to not-yet-processed entries
470
        // later on, this will be used to proactively drop blocks
471
        let mut block_use_count: BTreeMap<BlockOffset, usize> = BTreeMap::new();
171✔
472
        def1o!("block_use_count create");
171✔
473
        for (_tv_pair, fo) in map_tvpair_fo.iter() {
1,094✔
474
            let bo_beg: BlockOffset = BlockReader::block_offset_at_file_offset(*fo, blocksz);
1,094✔
475
            let fo_end: FileOffset = *fo + fixedstruct_type.size() as FileOffset;
1,094✔
476
            let bo_end: BlockOffset = BlockReader::block_offset_at_file_offset(fo_end, blocksz);
1,094✔
477
            def1o!("blocksz = {}", blocksz);
1,094✔
478
            for bo in bo_beg..bo_end + 1 {
2,847✔
479
                match block_use_count.get_mut(&bo) {
2,847✔
480
                    Some(count) => {
923✔
481
                        let count_ = *count + 1;
923✔
482
                        def1o!(
923✔
483
                            "block_use_count[{}] += 1 ({}); [{}‥{}]; total span [{}‥{})",
484
                            bo,
485
                            count_,
486
                            *fo,
487
                            fo_end,
488
                            BlockReader::file_offset_at_block_offset(bo_beg, blocksz),
923✔
489
                            BlockReader::file_offset_at_block_offset(bo_end + 1, blocksz),
923✔
490
                        );
491
                        *count = count_;
923✔
492
                    }
493
                    None => {
494
                        def1o!(
1,924✔
495
                            "block_use_count[{}] = 1; [{}‥{}]; total span [{}‥{})",
496
                            bo,
497
                            *fo,
498
                            fo_end,
499
                            BlockReader::file_offset_at_block_offset(bo_beg, blocksz),
1,924✔
500
                            BlockReader::file_offset_at_block_offset(bo_end + 1, blocksz),
1,924✔
501
                        );
502
                        block_use_count.insert(bo, 1);
1,924✔
503
                    }
504
                }
505
            }
506
        }
507
        #[cfg(debug_assertions)]
508
        {
509
            for (bo, count) in block_use_count.iter() {
1,924✔
510
                def1o!(
1,924✔
511
                    "block_use_count[{}] = {}; total span [{}‥{}]",
512
                    bo,
513
                    count,
514
                    BlockReader::file_offset_at_block_offset(*bo, blocksz),
1,924✔
515
                    BlockReader::file_offset_at_block_offset(*bo + 1, blocksz),
1,924✔
516
                );
517
            }
518
        }
519

520
        let map_max_len = map_tvpair_fo.len();
171✔
521
        // now that the `fixedstruct_type` is known, create the FixedStructReader
522
        let mut fixedstructreader = FixedStructReader {
171✔
523
            blockreader,
171✔
524
            fixedstruct_type,
171✔
525
            filetype_fixedstruct,
171✔
526
            fixedstruct_size: fixedstruct_type.size(),
171✔
527
            high_score,
171✔
528
            tz_offset,
171✔
529
            cache_entries: FoToEntry::new(),
171✔
530
            map_tvpair_fo,
171✔
531
            block_use_count,
171✔
532
            first_entry_fileoffset,
171✔
533
            entries_stored_highest: 0,
171✔
534
            entries_out_of_order: out_of_order,
171✔
535
            entries_hits: 0,
171✔
536
            entries_miss: 0,
171✔
537
            entries_processed: 0,
171✔
538
            dt_first: DateTimeLOpt::None,
171✔
539
            dt_last: DateTimeLOpt::None,
171✔
540
            drop_entry_ok: 0,
171✔
541
            drop_entry_errors: 0,
171✔
542
            #[cfg(test)]
171✔
543
            dropped_blocks: DroppedBlocks::new(),
171✔
544
            map_tvpair_fo_max_len: map_max_len,
171✔
545
            error: None,
171✔
546
        };
171✔
547

548
        // store the entries found and processed during `preprocess_file` into
549
        // `fixedstructreader.cache_entries`, to avoid duplicating work later on
550
        for (fo, fixedstructptr) in list_entries.into_iter() {
536✔
551
            // TODO: cost-savings: if `FixedStructTrait` had a `tv_pair` function then that time
552
            //       value could be checked against `map_tvpair_fo` *before* creating a new
553
            //       `FixedStruct`. However, the maximum number of `FixedStruct` entries
554
            //       created and then discarded will be very few so this is a marginal
555
            //       improvement.
556
            match FixedStruct::from_fixedstructptr(fo, &tz_offset, fixedstructptr) {
536✔
557
                Ok(fixedstruct) => {
536✔
558
                    if fixedstructreader
536✔
559
                        .map_tvpair_fo
536✔
560
                        .iter()
536✔
561
                        .any(|(_tv_pair, fo2)| &fo == fo2)
1,331✔
562
                    {
563
                        def1o!("insert entry at fo {}", fo);
508✔
564
                        fixedstructreader.insert_cache_entry(fixedstruct);
508✔
565
                    } else {
566
                        def1o!("skip entry at fo {}; not in map_tvpair_fo", fo);
28✔
567
                    }
568
                }
569
                Err(err) => {
×
570
                    de_err!("FixedStruct::from_fixedstructptr Error {}; file {:?}", err, fixedstructreader.path());
×
UNCOV
571
                    fixedstructreader.set_error(&err);
×
UNCOV
572
                }
×
573
            }
574
        }
575

576
        def1x!("return FileOk(FixedStructReader)");
171✔
577

578
        ResultFixedStructReaderNew::FileOk(fixedstructreader)
171✔
579
    }
201✔
580

581
    /// See [`BlockReader::blocksz`].
582
    ///
583
    /// [`BlockReader::blocksz`]: crate::readers::blockreader::BlockReader#method.blocksz
584
    #[inline(always)]
585
    pub const fn blocksz(&self) -> BlockSz {
1,039✔
586
        self.blockreader.blocksz()
1,039✔
587
    }
1,039✔
588

589
    /// See [`BlockReader::path_id`].
590
    ///
591
    /// [`BlockReader::path_id`]: crate::readers::blockreader::BlockReader#method.path_id
592
    #[inline(always)]
UNCOV
593
    pub const fn path_id(&self) -> PathId {
×
UNCOV
594
        self.blockreader.path_id()
×
UNCOV
595
    }
×
596

597
    /// See [`BlockReader::filesz`].
598
    ///
599
    /// [`BlockReader::filesz`]: crate::readers::blockreader::BlockReader#method.filesz
600
    #[inline(always)]
601
    pub const fn filesz(&self) -> FileSz {
2,296✔
602
        self.blockreader.filesz()
2,296✔
603
    }
2,296✔
604

605
    /// See [`BlockReader::filetype`].
606
    ///
607
    /// [`BlockReader::filetype`]: crate::readers::blockreader::BlockReader#method.filetype
608
    #[inline(always)]
609
    pub const fn filetype(&self) -> FileType {
93✔
610
        self.blockreader.filetype()
93✔
611
    }
93✔
612

613
    /// See [`BlockReader::path`].
614
    ///
615
    /// [`BlockReader::path`]: crate::readers::blockreader::BlockReader#method.path
616
    #[inline(always)]
617
    pub const fn path(&self) -> &FPath {
163✔
618
        self.blockreader.path()
163✔
619
    }
163✔
620

621
    /// See [`BlockReader::mtime`].
622
    ///
623
    /// [`BlockReader::mtime`]: crate::readers::blockreader::BlockReader#method.mtime
624
    pub fn mtime(&self) -> SystemTime {
70✔
625
        self.blockreader.mtime()
70✔
626
    }
70✔
627

628
    /// `Count` of `FixedStruct`s processed by this `FixedStructReader`
629
    /// (i.e. `self.entries_processed`).
630
    #[inline(always)]
631
    pub fn count_entries_processed(&self) -> Count {
1✔
632
        self.entries_processed
1✔
633
    }
1✔
634

635
    /// "_High watermark_" of `FixedStruct` stored in `self.cache_entries`.
636
    #[inline(always)]
UNCOV
637
    pub fn entries_stored_highest(&self) -> usize {
×
UNCOV
638
        self.entries_stored_highest
×
UNCOV
639
    }
×
640

641
    /// See [`BlockReader::block_offset_at_file_offset`].
642
    ///
643
    /// [`BlockReader::block_offset_at_file_offset`]: crate::readers::blockreader::BlockReader#method.block_offset_at_file_offset
644
    #[inline(always)]
645
    pub const fn block_offset_at_file_offset(
1✔
646
        &self,
1✔
647
        fileoffset: FileOffset,
1✔
648
    ) -> BlockOffset {
1✔
649
        BlockReader::block_offset_at_file_offset(fileoffset, self.blocksz())
1✔
650
    }
1✔
651

652
    /// See [`BlockReader::file_offset_at_block_offset`].
653
    ///
654
    /// [`BlockReader::file_offset_at_block_offset`]: crate::readers::blockreader::BlockReader#method.file_offset_at_block_offset
655
    #[inline(always)]
656
    pub const fn file_offset_at_block_offset(
1✔
657
        &self,
1✔
658
        blockoffset: BlockOffset,
1✔
659
    ) -> FileOffset {
1✔
660
        BlockReader::file_offset_at_block_offset(blockoffset, self.blocksz())
1✔
661
    }
1✔
662

663
    /// See [`BlockReader::file_offset_at_block_offset_index`].
664
    ///
665
    /// [`BlockReader::file_offset_at_block_offset_index`]: crate::readers::blockreader::BlockReader#method.file_offset_at_block_offset_index
666
    #[inline(always)]
667
    pub const fn file_offset_at_block_offset_index(
1✔
668
        &self,
1✔
669
        blockoffset: BlockOffset,
1✔
670
        blockindex: BlockIndex,
1✔
671
    ) -> FileOffset {
1✔
672
        BlockReader::file_offset_at_block_offset_index(blockoffset, self.blocksz(), blockindex)
1✔
673
    }
1✔
674

675
    /// See [`BlockReader::block_index_at_file_offset`].
676
    ///
677
    /// [`BlockReader::block_index_at_file_offset`]: crate::readers::blockreader::BlockReader#method.block_index_at_file_offset
678
    #[inline(always)]
679
    pub const fn block_index_at_file_offset(
1✔
680
        &self,
1✔
681
        fileoffset: FileOffset,
1✔
682
    ) -> BlockIndex {
1✔
683
        BlockReader::block_index_at_file_offset(fileoffset, self.blocksz())
1✔
684
    }
1✔
685

686
    /// See [`BlockReader::count_blocks`].
687
    ///
688
    /// [`BlockReader::count_blocks`]: crate::readers::blockreader::BlockReader#method.count_blocks
689
    #[inline(always)]
690
    pub const fn count_blocks(&self) -> Count {
1✔
691
        BlockReader::count_blocks(self.filesz(), self.blocksz()) as Count
1✔
692
    }
1✔
693

694
    /// See [`BlockReader::blockoffset_last`].
695
    ///
696
    /// [`BlockReader::blockoffset_last`]: crate::readers::blockreader::BlockReader#method.blockoffset_last
697
    pub const fn blockoffset_last(&self) -> BlockOffset {
1✔
698
        self.blockreader
1✔
699
            .blockoffset_last()
1✔
700
    }
1✔
701

702
    /// See [`BlockReader::fileoffset_last`].
703
    ///
704
    /// [`BlockReader::fileoffset_last`]: crate::readers::blockreader::BlockReader#method.fileoffset_last
705
    pub const fn fileoffset_last(&self) -> FileOffset {
910✔
706
        self.blockreader
910✔
707
            .fileoffset_last()
910✔
708
    }
910✔
709

710
    /// Is the passed `FileOffset` the last byte of the file?
711
    pub const fn is_fileoffset_last(
908✔
712
        &self,
908✔
713
        fileoffset: FileOffset,
908✔
714
    ) -> bool {
908✔
715
        self.fileoffset_last() == fileoffset
908✔
716
    }
908✔
717

718
    /// Is the passed `FixedStruct` the last of the file?
719
    #[inline(always)]
720
    pub fn is_last(
902✔
721
        &self,
902✔
722
        fixedstruct: &FixedStruct,
902✔
723
    ) -> bool {
902✔
724
        self.is_fileoffset_last(fixedstruct.fileoffset_end() - 1)
902✔
725
    }
902✔
726

727
    /// Return the `FileOffset` that is adjusted to the beginning offset of
728
    /// a `fixedstruct` entry.
729
    #[inline(always)]
730
    pub const fn fileoffset_to_fixedstructoffset(
1✔
731
        &self,
1✔
732
        fileoffset: FileOffset,
1✔
733
    ) -> FileOffset {
1✔
734
        (fileoffset / self.fixedstruct_size_fo()) * self.fixedstruct_size_fo()
1✔
735
    }
1✔
736

737
    /// Return the first file offset from `self.map_tvpair_fo` for the first
738
    /// entry as sorted by `tv_pair_type` (datetime); i.e. the earliest entry.
739
    /// Ties are broken by `FileOffset`.
740
    pub fn fileoffset_first(&self) -> Option<FileOffset> {
102✔
741
        match self
102✔
742
            .map_tvpair_fo
102✔
743
            .iter()
102✔
744
            .min_by_key(|(tv_pair, fo)| (*tv_pair, *fo))
971✔
745
        {
746
            Some((_tv_pair, fo_)) => Some(*fo_),
102✔
UNCOV
747
            None => None,
×
748
        }
749
    }
102✔
750

751
    /// The size in bytes of the `FixedStruct` entries managed by this
752
    /// `FixedStructReader`.
753
    #[inline(always)]
754
    pub const fn fixedstruct_size(&self) -> usize {
1,245✔
755
        self.fixedstruct_size
1,245✔
756
    }
1,245✔
757

758
    /// [`fixedstruct_size`] as a `FileOffset`.
759
    ///
760
    /// [`fixedstruct_size`]: self::FixedStructReader#method.fixedstruct_size
761
    #[inline(always)]
762
    pub const fn fixedstruct_size_fo(&self) -> FileOffset {
1,150✔
763
        self.fixedstruct_size() as FileOffset
1,150✔
764
    }
1,150✔
765

766
    /// The `FixedStructType` of the file.
767
    #[inline(always)]
768
    pub const fn fixedstruct_type(&self) -> FixedStructType {
673✔
769
        self.fixedstruct_type
673✔
770
    }
673✔
771

772
    /// Return all currently stored `FileOffset` in `self.cache_entries`.
773
    ///
774
    /// Only for testing.
775
    #[cfg(test)]
776
    pub fn get_fileoffsets(&self) -> Vec<FileOffset> {
1✔
777
        self.cache_entries
1✔
778
            .keys()
1✔
779
            .cloned()
1✔
780
            .collect()
1✔
781
    }
1✔
782

783
    /// store an `Error` that occurred. For later printing during `--summary`.
784
    // XXX: duplicates `SyslogProcessor.set_error`
UNCOV
785
    fn set_error(
×
UNCOV
786
        &mut self,
×
UNCOV
787
        error: &Error,
×
788
    ) {
×
789
        def1ñ!("{:?}", error);
×
790
        let mut error_string: String = error.kind().to_string();
×
791
        error_string.push_str(": ");
×
792
        error_string.push_str(error.kind().to_string().as_str());
×
793
        // print the error but avoid printing the same error more than once
794
        // XXX: This is somewhat a hack as it's possible the same error, with the
795
        //      the same error message, could occur more than once.
796
        //      Considered another way, this function `set_error` may get called
797
        //      too often. The responsibility for calling `set_error` is haphazard.
798
        match &self.error {
×
799
            Some(err_s) => {
×
800
                if err_s != &error_string {
×
801
                    e_err!("{}", error);
×
802
                }
×
803
            }
UNCOV
804
            None => {
×
UNCOV
805
                e_err!("{}", error);
×
UNCOV
806
            }
×
807
        }
UNCOV
808
        if let Some(ref _err) = self.error {
×
UNCOV
809
            de_wrn!("skip overwrite of previous Error ({:?}) with Error ({:?})", _err, error);
×
UNCOV
810
            return;
×
UNCOV
811
        }
×
UNCOV
812
        self.error = Some(error_string);
×
UNCOV
813
    }
×
814

815
    /// Store information about a single [`FixedStruct`] entry.
816
    ///
817
    /// Should only be called by `FixedStructReader::new`
818
    ///
819
    /// [`FixedStruct`]: crate::data::fixedstruct::FixedStruct
820
    fn insert_cache_entry(
508✔
821
        &mut self,
508✔
822
        entry: FixedStruct,
508✔
823
    ) {
508✔
824
        defn!("@{}", entry.fileoffset_begin());
508✔
825
        let fo_beg: FileOffset = entry.fileoffset_begin();
508✔
826
        debug_assert!(
508✔
827
            !self.cache_entries.contains_key(&fo_beg),
508✔
828
            "self.cache_entries already contains key {}",
829
            fo_beg
830
        );
831

832
        // update some stats and (most importantly) `self.cache_entries`
833
        self.cache_entries
508✔
834
            .insert(fo_beg, entry);
508✔
835
        self.entries_stored_highest = std::cmp::max(self.entries_stored_highest, self.cache_entries.len());
508✔
836
        self.entries_processed += 1;
508✔
837
        defo!("entries_processed = {}", self.entries_processed);
508✔
838

839
        defx!();
508✔
840
    }
508✔
841

842
    /// Update the statistic `DateTimeL` of
843
    /// `self.dt_first` and `self.dt_last`
844
    fn dt_first_last_update(
943✔
845
        &mut self,
943✔
846
        datetime: &DateTimeL,
943✔
847
    ) {
943✔
848
        if !summary_stats_enabled() {
943✔
UNCOV
849
            return;
×
850
        }
943✔
851
        defñ!("({:?})", datetime);
943✔
852
        match self.dt_first {
943✔
853
            Some(dt_first_) => {
789✔
854
                if &dt_first_ > datetime {
789✔
UNCOV
855
                    self.dt_first = Some(*datetime);
×
856
                }
789✔
857
            }
858
            None => {
154✔
859
                self.dt_first = Some(*datetime);
154✔
860
            }
154✔
861
        }
862
        match self.dt_last {
943✔
863
            Some(dt_last_) => {
789✔
864
                if &dt_last_ < datetime {
789✔
865
                    self.dt_last = Some(*datetime);
788✔
866
                }
788✔
867
            }
868
            None => {
154✔
869
                self.dt_last = Some(*datetime);
154✔
870
            }
154✔
871
        }
872
    }
943✔
873

874
    /// Proactively `drop` the [`Block`s] associated with the
875
    /// passed [`FixedStruct`]. Return count of dropped entries (0 or 1).
876
    ///
877
    /// _The caller must know what they are doing!_
878
    ///
879
    /// [`FixedStruct`]: crate::data::fixedstruct::FixedStruct
880
    /// [`Block`s]: crate::readers::blockreader::Block
881
    /// [`FileOffset`]: crate::common::FileOffset
882
    fn drop_entry(
1,033✔
883
        &mut self,
1,033✔
884
        fixedstruct: &FixedStruct,
1,033✔
885
    ) -> usize {
1,033✔
886
        let bsz: BlockSz = self.blocksz();
1,033✔
887
        defn!(
1,033✔
888
            "(fixedstruct@{}); offsets [{}‥{}), blocks [{}‥{}]",
889
            fixedstruct.fileoffset_begin(),
1,033✔
890
            fixedstruct.fileoffset_begin(),
1,033✔
891
            fixedstruct.fileoffset_end(),
1,033✔
892
            fixedstruct.blockoffset_begin(bsz),
1,033✔
893
            fixedstruct.blockoffset_end(bsz),
1,033✔
894
        );
895
        let mut dropped_ok: usize = 0;
1,033✔
896
        let mut dropped_err: usize = 0;
1,033✔
897
        let mut bo_at: BlockOffset = fixedstruct.blockoffset_begin(bsz);
1,033✔
898
        let bo_end: BlockOffset = fixedstruct.blockoffset_end(bsz);
1,033✔
899
        debug_assert_le!(bo_at, bo_end);
1,033✔
900
        while bo_at <= bo_end {
3,525✔
901
            defo!("block_use_count.get_mut({})", bo_at);
2,492✔
902
            match self.block_use_count.get_mut(&bo_at) {
2,492✔
903
                Some(count) => {
2,481✔
904
                    if *count <= 1 {
2,481✔
905
                        defo!(
1,575✔
906
                            "block_use_count[{}] found; count=={}; total span [{}‥{})",
907
                            bo_at, count,
908
                            BlockReader::file_offset_at_block_offset(bo_at, bsz),
1,575✔
909
                            BlockReader::file_offset_at_block_offset(bo_end + 1, bsz),
1,575✔
910
                        );
911
                        if self
1,575✔
912
                            .blockreader
1,575✔
913
                            .drop_block(bo_at)
1,575✔
914
                        {
915
                            defo!(
1,572✔
916
                                "dropped block {}; total span [{}‥{})",
917
                                bo_at,
918
                                BlockReader::file_offset_at_block_offset(bo_at, bsz),
1,572✔
919
                                BlockReader::file_offset_at_block_offset(bo_end + 1, bsz),
1,572✔
920
                            );
921
                            self.block_use_count.remove(&bo_at);
1,572✔
922
                            #[cfg(test)]
923
                            self.dropped_blocks.push_back(bo_at);
1,502✔
924
                            dropped_ok += 1;
1,572✔
925
                        } else {
926
                            defo!("failed to drop block {}", bo_at);
3✔
927
                            dropped_err += 1;
3✔
928
                        }
929
                    } else {
930
                        *count -= 1;
906✔
931
                        defo!("block_use_count[{}] found; count-=1=={}", bo_at, *count);
906✔
932
                    }
933
                }
934
                None => {
935
                    defo!("block_use_count[{}] not found", bo_at);
11✔
936
                }
937
            }
938
            bo_at += 1;
2,492✔
939
        }
940
        summary_stat!(
1,033✔
941
            if dropped_ok > 0 {
943✔
942
                self.drop_entry_ok += 1;
162✔
943
            }
846✔
944
        );
945
        summary_stat!(
1,033✔
946
            if dropped_err > 0 {
943✔
947
                self.drop_entry_errors += 1;
2✔
948
            }
941✔
949
        );
950
        defx!("return {}", dropped_ok);
1,033✔
951

952
        dropped_ok
1,033✔
953
    }
1,033✔
954

955
    /// Check the internal storage `self.cache_entries`.
956
    /// Remove the entry and return it if found.
957
    #[inline(always)]
958
    fn remove_cache_entry(
1,033✔
959
        &mut self,
1,033✔
960
        fileoffset: FileOffset,
1,033✔
961
    ) -> Option<FixedStruct> {
1,033✔
962
        match self.cache_entries.remove(&fileoffset) {
1,033✔
963
            Some(fixedstruct) => {
454✔
964
                defñ!("({}): found in store", fileoffset);
454✔
965
                self.entries_hits += 1;
454✔
966

967
                Some(fixedstruct)
454✔
968
            }
969
            None => {
970
                defñ!("({}): not found in store", fileoffset);
579✔
971
                self.entries_miss += 1;
579✔
972

973
                None
579✔
974
            }
975
        }
976
    }
1,033✔
977

978
    /// Process entries for the file managed by the `BlockReader`.
979
    /// Find the entry with the highest "score" as judged by `score_fixedstruct`.
980
    ///
981
    /// Returns the highest scored `FixedStructType`, that highest score,
982
    /// and any processed [`FixedStruct`] entries (referenced by a
983
    /// [`FixedStructDynPtr`]) in a list.
984
    ///
985
    /// Each list entry is a tuple of the
986
    /// the entries `FileOffset` and the `FixedStructDynPtr`.
987
    /// The entries will presumably be stored in the
988
    /// `FixedStructReader`'s `cache_entries`.
989
    pub fn score_file(
198✔
990
        blockreader: &mut BlockReader,
198✔
991
        oneblock: bool,
198✔
992
        types_to_bonus: FixedStructTypeSet,
198✔
993
    ) -> ResultFixedStructReaderScoreFileError {
198✔
994
        def1n!("(oneblock={}, types_to_bonus len {})", oneblock, types_to_bonus.len());
198✔
995
        #[cfg(debug_assertions)]
996
        {
997
            for (fixedstructtype, bonus) in types_to_bonus.iter() {
771✔
998
                def1o!("types_to_bonus: ({:<30?}, {:2}) size {}", fixedstructtype, bonus, fixedstructtype.size(),);
771✔
999
            }
1000
        }
1001
        // allocate largest possible buffer needed on the stack
1002
        let mut buffer: [u8; ENTRY_SZ_MAX] = [0; ENTRY_SZ_MAX];
198✔
1003
        // only score this number of entries, i.e. don't walk the
1004
        // entire file scoring all entries
1005
        #[cfg(not(test))]
1006
        const COUNT_FOUND_ENTRIES_MAX: usize = 5;
1007
        #[cfg(test)]
1008
        const COUNT_FOUND_ENTRIES_MAX: usize = 2;
1009
        let mut _count_total: usize = 0;
198✔
1010
        let mut highest_score: Score = 0;
198✔
1011
        let mut highest_score_type: Option<FixedStructType> = None;
198✔
1012
        let mut highest_score_entries = ListFileOffsetFixedStructPtr::new();
198✔
1013

1014
        for (fixedstructtype, bonus) in types_to_bonus.into_iter() {
771✔
1015
            let mut _count_loop: usize = 0;
771✔
1016
            let mut count_found_entries: usize = 0;
771✔
1017
            let mut high_score: Score = 0;
771✔
1018
            let mut fo: FileOffset = 0;
771✔
1019
            let mut found_entries = ListFileOffsetFixedStructPtr::new();
771✔
1020

1021
            loop {
1022
                if count_found_entries >= COUNT_FOUND_ENTRIES_MAX {
4,192✔
1023
                    def1o!("count_found_entries {} >= COUNT_FOUND_ENTRIES_MAX {}", count_found_entries, COUNT_FOUND_ENTRIES_MAX);
724✔
1024
                    break;
724✔
1025
                }
3,468✔
1026
                _count_total += 1;
3,468✔
1027
                _count_loop += 1;
3,468✔
1028
                let utmp_sz: usize = fixedstructtype.size();
3,468✔
1029
                let fo_end = fo + utmp_sz as FileOffset;
3,468✔
1030
                def1o!(
3,468✔
1031
                    "loop try {} (total {}), fixedstructtype {:?}, zero the buffer (size {}), looking at fileoffset {}‥{} (0x{:08X}‥0x{:08X})",
1032
                    _count_loop, _count_total, fixedstructtype, buffer.len(), fo, fo_end, fo, fo_end
3,468✔
1033
                );
1034
                // zero out the buffer
1035
                // XXX: not strictly necessary to zero buffer but it helps humans
1036
                //      manually reviewing debug logs
1037
                // this compiles down to a single `memset` call
1038
                // 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
1039
                // See https://godbolt.org/z/KcxW9hWYb
1040
                buffer.iter_mut().for_each(|m| *m = 0);
3,468✔
1041
                // read data into buffer
1042
                let buffer_read: usize = match blockreader.read_data_to_buffer(fo, fo_end, oneblock, &mut buffer) {
3,468✔
1043
                    ResultReadDataToBuffer::Found(buffer_read) => buffer_read,
3,421✔
1044
                    ResultReadDataToBuffer::Err(err) => {
×
UNCOV
1045
                        def1x!("return Err");
×
UNCOV
1046
                        return ResultFixedStructReaderScoreFileError::FileErrIo(err);
×
1047
                    }
1048
                    ResultReadDataToBuffer::Done => {
47✔
1049
                        // reached end of block (if `oneblock` is `true`) or end of file
1050
                        break;
47✔
1051
                    }
1052
                };
1053
                if buffer_read < utmp_sz {
3,421✔
UNCOV
1054
                    def1o!(
×
1055
                        "read_data_to_buffer read bytes {} < {} requested fixedstruct size bytes; break",
1056
                        buffer_read,
1057
                        utmp_sz,
1058
                    );
UNCOV
1059
                    break;
×
1060
                }
3,421✔
1061
                // advance the file offset for the next loop
1062
                let fo2 = fo;
3,421✔
1063
                fo += utmp_sz as FileOffset;
3,421✔
1064
                // grab the slice of interest
1065
                let slice_ = &buffer[..buffer_read];
3,421✔
1066
                // convert buffer to fixedstruct
1067
                let fixedstructptr: FixedStructDynPtr = match buffer_to_fixedstructptr(slice_, fixedstructtype) {
3,421✔
1068
                    Some(val) => val,
2,311✔
1069
                    None => {
1070
                        def1o!(
1,110✔
1071
                            "buffer_to_fixedstructptr(buf len {}, {:?}) returned None; continue",
1072
                            buffer.len(),
1,110✔
1073
                            fixedstructtype,
1074
                        );
1075
                        continue;
1,110✔
1076
                    }
1077
                };
1078
                count_found_entries += 1;
2,311✔
1079
                // score the newly create fixedstruct
1080
                let score: Score = FixedStruct::score_fixedstruct(&fixedstructptr, bonus);
2,311✔
1081
                def1o!("score {} for entry type {:?} @[{}‥{}]", score, fixedstructptr.fixedstruct_type(), fo2, fo_end);
2,311✔
1082
                // update the high score
1083
                let _fs_type: FixedStructType = fixedstructptr.fixedstruct_type();
2,311✔
1084
                found_entries.push_back((fo2, fixedstructptr));
2,311✔
1085
                if score <= high_score {
2,311✔
1086
                    def1o!("score {} ({:?}) not higher than high score {}", score, _fs_type, high_score,);
2,093✔
1087
                    // score is not higher than high score so continue
1088
                    continue;
2,093✔
1089
                }
218✔
1090
                // there is a new high score for this type
1091
                def1o!("new high score {} for entry type {:?} @[{}‥{}]", score, _fs_type, fo2, fo_end);
218✔
1092
                high_score = score;
218✔
1093
            }
1094
            // finished with that fixedstructtype
1095
            // so check if it's high score beat any previous high score of a different fixedstructtype
1096
            if high_score > highest_score {
771✔
1097
                // a new high score was found for a different type so throw away
1098
                // linked lists of entries for the previous type
1099
                match highest_score_type {
196✔
1100
                    None => {
1101
                        def1o!(
190✔
1102
                            "new highest score {} entry type {:?} with {} entries",
1103
                            high_score, fixedstructtype, found_entries.len(),
190✔
1104
                        );
1105
                    }
1106
                    Some(_highest_score_type) => {
6✔
1107
                        def1o!(
6✔
1108
                            "new highest score {} entry type {:?} with {} entries; replaces old high score {} entry type {:?} with {} entries (entries dropped)",
1109
                            high_score, fixedstructtype, found_entries.len(),
6✔
1110
                            highest_score, _highest_score_type, highest_score_entries.len(),
6✔
1111
                        );
1112
                    }
1113
                }
1114
                highest_score = high_score;
196✔
1115
                highest_score_type = Some(fixedstructtype);
196✔
1116
                highest_score_entries = found_entries;
196✔
1117
            } else {
1118
                def1o!(
575✔
1119
                    "no new highest score: score {} entry type {:?} with {} entries. old high score remains: score {} entry type {:?} with {} entries",
1120
                    high_score, fixedstructtype, found_entries.len(),
575✔
1121
                    highest_score, highest_score_type, highest_score_entries.len(),
575✔
1122
                );
1123
            }
1124
        }
1125

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

1134
                ResultFixedStructReaderScoreFileError::FileOk(highest_score_type, highest_score, highest_score_entries)
190✔
1135
            }
1136
        }
1137
    }
198✔
1138

1139
    /// Determine the `FixedStructType` based on the file size and data.
1140
    ///
1141
    /// 1. Makes best guess about file structure based on size by calling
1142
    ///    `filesz_to_types`
1143
    /// 2. Searches for a valid struct within the first block of the file (if
1144
    ///    `oneblock` is `true`, else searches all available blocks).
1145
    /// 3. Creates a [`FixedStruct`] from the found struct.
1146
    ///
1147
    /// Call this before calling `process_entry_at`.
1148
    ///
1149
    /// [OpenBSD file `w.c`] processes `/var/log/utmp`. Reviewing the code you
1150
    /// get some idea of how the file is determined to be valid.
1151
    ///
1152
    /// [OpenBSD file `w.c`]: https://github.com/openbsd/src/blob/20248fc4cbb7c0efca41a8aafd40db7747023515/usr.bin/w/w.c
1153
    pub(crate) fn preprocess_fixedstructtype(
198✔
1154
        blockreader: &mut BlockReader,
198✔
1155
        filetype_fixedstruct: &FileTypeFixedStruct,
198✔
1156
        oneblock: bool,
198✔
1157
    ) -> ResultFixedStructReaderScoreFileError {
198✔
1158
        def1n!("({:?}, oneblock={})", filetype_fixedstruct, oneblock);
198✔
1159

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

1166
        let types_to_bonus: FixedStructTypeSet = match filesz_to_types(
198✔
1167
            blockreader.filesz(),
198✔
1168
            filetype_fixedstruct,
198✔
1169
        ) {
198✔
1170
            Some(set) => set,
198✔
1171
            None => {
UNCOV
1172
                de_wrn!("FixedStructReader::filesz_to_types({}) failed; file {:?}",
×
UNCOV
1173
                        blockreader.filesz(), blockreader.path());
×
UNCOV
1174
                def1x!("filesz_to_types returned None; return FileErrNoValidFixedStruct");
×
UNCOV
1175
                return ResultFixedStructReaderScoreFileError::FileErrNoValidFixedStruct;
×
1176
            }
1177
        };
1178
        def1o!("filesz_to_types returned {} types: {:?}", types_to_bonus.len(), types_to_bonus);
198✔
1179

1180
        let ret = FixedStructReader::score_file(blockreader, oneblock, types_to_bonus);
198✔
1181
        def1x!("score_file returned {:?}", ret);
198✔
1182

1183
        ret
198✔
1184
    }
198✔
1185

1186
    /// Jump to each entry offset, convert the raw bytes to a `tv_pair_type`.
1187
    /// If the value is within the passed filters than save the value and the
1188
    /// file offset of the entry.
1189
    /// Return a count of out of order entries, and map of filtered tv_pair to
1190
    /// fileoffsets. The fileoffsets in the map are the fixedstruct offsets
1191
    /// (not timevalue offsets).
1192
    pub(crate) fn preprocess_timevalues(
190✔
1193
        blockreader: &mut BlockReader,
190✔
1194
        fixedstruct_type: FixedStructType,
190✔
1195
        dt_filter_after: &DateTimeLOpt,
190✔
1196
        dt_filter_before: &DateTimeLOpt,
190✔
1197
    ) -> ResultTvFo {
190✔
1198
        defn!();
190✔
1199
        // allocate largest possible buffer needed on the stack
1200
        let mut buffer: [u8; TIMEVAL_SZ_MAX] = [0; TIMEVAL_SZ_MAX];
190✔
1201
        // map of time values to file offsets
1202
        let mut map_tv_pair_fo: MapTvPairToFo = MapTvPairToFo::new();
190✔
1203
        // count of out of order entries
1204
        let mut out_of_order: Count = 0;
190✔
1205
        // valid fixedstruct but does not pass the time value filters
1206
        let mut valid_no_pass_filter: usize = 0;
190✔
1207
        // null fixedstruct or non-sense time values
1208
        let mut invalid: usize = 0;
190✔
1209
        // total number of entries processed
1210
        let mut total_entries: usize = 0;
190✔
1211

1212
        let tv_filter_after: Option<tv_pair_type> = match dt_filter_after {
190✔
1213
            Some(dt) => Some(convert_datetime_tvpair(dt)),
60✔
1214
            None => None,
130✔
1215
        };
1216
        defo!("tv_filter_after: {:?}", tv_filter_after);
190✔
1217
        let tv_filter_before: Option<tv_pair_type> = match dt_filter_before {
190✔
1218
            Some(dt) => Some(convert_datetime_tvpair(dt)),
34✔
1219
            None => None,
156✔
1220
        };
1221
        defo!("tv_filter_before: {:?}", tv_filter_before);
190✔
1222

1223
        // 1. get offsets
1224
        let entry_sz: FileOffset = fixedstruct_type.size() as FileOffset;
190✔
1225
        debug_assert_eq!(blockreader.filesz() % entry_sz, 0, "file not a multiple of entry size {}", entry_sz);
190✔
1226
        let tv_sz: usize = fixedstruct_type.size_tv();
190✔
1227
        let tv_offset: usize = fixedstruct_type.offset_tv();
190✔
1228
        let slice_: &mut [u8] = &mut buffer[..tv_sz];
190✔
1229
        let mut fo: FileOffset = 0;
190✔
1230
        let mut tv_pair_prev: Option<tv_pair_type> = None;
190✔
1231
        loop {
1232
            // 2. jump to each offset, grab datetime bytes,
1233
            let beg: FileOffset = fo + tv_offset as FileOffset;
1,367✔
1234
            let end: FileOffset = beg + tv_sz as FileOffset;
1,367✔
1235
            match blockreader.read_data_to_buffer(beg, end, false, slice_) {
1,367✔
1236
                ResultReadDataToBuffer::Found(_readn) => {
1,177✔
1237
                    defo!("read {} bytes at fileoffset {}", _readn, beg);
1,177✔
1238
                    debug_assert_eq!(
1,177✔
1239
                        _readn, tv_sz,
1240
                        "read {} bytes, expected {} bytes (size of a time value)",
1241
                        _readn, tv_sz,
1242
                    );
1243
                }
UNCOV
1244
                ResultReadDataToBuffer::Err(err) => {
×
UNCOV
1245
                    defx!("return Err");
×
UNCOV
1246
                    return ResultTvFo::Err(err);
×
1247
                }
1248
                ResultReadDataToBuffer::Done => {
190✔
1249
                    defo!("return Done");
190✔
1250
                    break;
190✔
1251
                }
1252
            }
1253
            // 3. convert bytes to tv_sec, tv_usec
1254
            let tv_pair: tv_pair_type = match fixedstruct_type.tv_pair_from_buffer(slice_) {
1,177✔
1255
                Some(pair) => pair,
1,177✔
1256
                None => {
1257
                    de_err!("invalid entry at fileoffset {}", fo);
×
1258
                    defo!("invalid entry at fileoffset {}", fo);
×
UNCOV
1259
                    fo += entry_sz;
×
UNCOV
1260
                    invalid += 1;
×
UNCOV
1261
                    continue;
×
1262
                }
1263
            };
1264
            defo!("tv_pair: {:?}", tv_pair);
1,177✔
1265
            if tv_pair == tv_pair_type(0, 0) {
1,177✔
UNCOV
1266
                defo!("tv_pair is (0, 0); continue");
×
UNCOV
1267
                fo += entry_sz;
×
UNCOV
1268
                continue;
×
1269
            }
1,177✔
1270
            if let Some(tv_pair_prev) = tv_pair_prev {
1,177✔
1271
                if tv_pair < tv_pair_prev {
987✔
1272
                    out_of_order += 1;
70✔
1273
                    defo!(
70✔
1274
                        "out_of_order = {}; tv_pair = {:?}, tv_pair_prev = {:?}",
1275
                        out_of_order,
1276
                        tv_pair,
1277
                        tv_pair_prev,
1278
                    );
1279
                }
917✔
1280
            }
190✔
1281
            tv_pair_prev = Some(tv_pair);
1,177✔
1282
            total_entries += 1;
1,177✔
1283
            // 4. compare to time value filters
1284
            if let Some(tv_filter) = tv_filter_after {
1,177✔
1285
                if tv_pair < tv_filter {
150✔
1286
                    defo!("tv_pair {:?} < {:?} tv_filter_after; continue", tv_pair, tv_filter);
61✔
1287
                    fo += entry_sz;
61✔
1288
                    valid_no_pass_filter += 1;
61✔
1289
                    continue;
61✔
1290
                }
89✔
1291
            }
1,027✔
1292
            if let Some(tv_filter) = tv_filter_before {
1,116✔
1293
                if tv_pair > tv_filter {
49✔
1294
                    defo!("tv_pair {:?} > {:?} tv_filter_before; continue", tv_pair, tv_filter);
22✔
1295
                    fo += entry_sz;
22✔
1296
                    valid_no_pass_filter += 1;
22✔
1297
                    // continue to check _all_ entries as the entries may not be
1298
                    // in chronological order
1299
                    continue;
22✔
1300
                }
27✔
1301
            }
1,067✔
1302
            // 5. save entries that pass the time value filters
1303
            defo!("tv_pair {:?} @{} passes time value filters", tv_pair, fo);
1,094✔
1304
            map_tv_pair_fo.insert(tv_pair, fo);
1,094✔
1305

1306
            fo += entry_sz;
1,094✔
1307
        }
1308
        // 6. return list of valid entries
1309
        defx!("map_tv_pair_fo len {}", map_tv_pair_fo.len());
190✔
1310

1311
        ResultTvFo::Ok((total_entries, invalid, valid_no_pass_filter, out_of_order, map_tv_pair_fo))
190✔
1312
    }
190✔
1313

1314
    /// Process the data at FileOffset `fo`. Transform it into a `FixedStruct`
1315
    /// using [`FixedStruct::new`].
1316
    /// But before that, check private `self.cache_entries`
1317
    /// in case the data at the fileoffset was already processed (transformed)
1318
    /// during `FixedStructReader::new`.
1319
    ///
1320
    /// Let the caller pass a `buffer` to avoid this function having allocate.
1321
    ///
1322
    /// This function does the bulk of file processing after the
1323
    /// `FixedStructReader` has been initialized during
1324
    /// [`FixedStructReader::new`].
1325
    pub fn process_entry_at(
1,147✔
1326
        &mut self,
1,147✔
1327
        fo: FileOffset,
1,147✔
1328
        buffer: &mut [u8],
1,147✔
1329
    ) -> ResultFindFixedStruct {
1,147✔
1330
        defn!("({})", fo);
1,147✔
1331

1332
        let sz: FileOffset = self.fixedstruct_size_fo();
1,147✔
1333
        debug_assert_eq!(fo % sz, 0, "fileoffset {} not multiple of {}", fo, sz,);
1,147✔
1334
        let fileoffset: FileOffset = fo - (fo % sz);
1,147✔
1335

1336
        if fileoffset >= self.filesz() {
1,147✔
1337
            defx!("return ResultFindFixedStruct::Done; fileoffset {} >= filesz {}", fileoffset, self.filesz());
114✔
1338
            return ResultFindFixedStruct::Done;
114✔
1339
        }
1,033✔
1340

1341
        // The `map_tvpair_fo` is the oracle listing of entries ordered by
1342
        // `tv_pair` (it was  fully created during `preprocess_timevalues`).
1343
        // Search `map_tvpair_fo` for the passed `fo`, then return the
1344
        // fileoffset of the entry *after that* which will be returned in `Found`.
1345
        // If no next fileoffset is found it means `map_tvpair_fo` is empty.
1346
        // In that case, the `fo_next` will be the value of `filesz()` and the
1347
        // next call to `process_entry_at` will return `Done`.
1348
        let fo_next: FileOffset = {
1,033✔
1349
            let mut fo_next_: FileOffset = self.filesz();
1,033✔
1350
            let mut next_pair: bool = false;
1,033✔
1351
            let mut tv_pair_at_opt: Option<tv_pair_type> = None;
1,033✔
1352
            // TODO: is there a rustic iterator way to
1353
            //       "find something and return the next thing"?
1354
            for (tv_pair_at, fo_at) in self.map_tvpair_fo.iter() {
1,944✔
1355
                if next_pair {
1,944✔
1356
                    defo!("set fo_next = {}", fo_at);
887✔
1357
                    fo_next_ = *fo_at;
887✔
1358
                    break;
887✔
1359
                }
1,057✔
1360
                if &fileoffset == fo_at {
1,057✔
1361
                    defo!("found fileoffset {} with key {:?} in map_tvpair_fo", fileoffset, tv_pair_at,);
1,025✔
1362
                    tv_pair_at_opt = Some(*tv_pair_at);
1,025✔
1363
                    next_pair = true;
1,025✔
1364
                }
32✔
1365
            }
1366
            // remove the `tv_pair` from `map_tvpair_fo`
1367
            match tv_pair_at_opt {
1,033✔
1368
                Some(tv_pair_at) => {
1,025✔
1369
                    self.map_tvpair_fo
1,025✔
1370
                        .remove(&tv_pair_at);
1,025✔
1371
                    defo!("remove tv_pair {:?}; map_tvpair_fo size {}", tv_pair_at, self.map_tvpair_fo.len());
1,025✔
1372
                }
1373
                None => {
1374
                    defo!("no map_tvpair_fo found!");
8✔
1375
                }
1376
            }
1377

1378
            fo_next_
1,033✔
1379
        };
1380
        defo!("fo_next = {}", fo_next);
1,033✔
1381

1382
        // check if the entry is already stored
1383
        if let Some(fixedstruct) = self.remove_cache_entry(fileoffset) {
1,033✔
1384
            summary_stat!(self.dt_first_last_update(fixedstruct.dt()));
454✔
1385
            // try to drop blocks associated with the entry
1386
            self.drop_entry(&fixedstruct);
454✔
1387
            defx!("remove_cache_entry found fixedstruct at fileoffset {}; return Found({}, …)", fileoffset, fo_next,);
454✔
1388
            return ResultFindFixedStruct::Found((fo_next, fixedstruct));
454✔
1389
        }
579✔
1390

1391
        // the entry was not in the cache so read the raw bytes from the file
1392
        // and transform them into a `FixedStruct`
1393

1394
        // check the buffer size
1395
        if buffer.len() < sz as usize {
579✔
1396
            defx!("return ResultFindFixedStruct::Err");
×
UNCOV
1397
            return ResultFindFixedStruct::Err((
×
UNCOV
1398
                None,
×
UNCOV
1399
                Error::new(
×
UNCOV
1400
                    ErrorKind::InvalidData,
×
UNCOV
1401
                    format!(
×
UNCOV
1402
                        "buffer size {} less than fixedstruct size {} at fileoffset {}, file {:?}",
×
UNCOV
1403
                        buffer.len(), sz, fileoffset, self.path(),
×
UNCOV
1404
                    ),
×
UNCOV
1405
                ),
×
UNCOV
1406
            ));
×
1407
        }
579✔
1408

1409
        // zero out the slice
1410
        defo!("zero buffer[‥{}]", sz);
579✔
1411
        let slice_: &mut [u8] = &mut buffer[..sz as usize];
579✔
1412
        slice_.iter_mut().for_each(|m| *m = 0);
579✔
1413

1414
        // read raw bytes into the slice
1415
        let _readn = match self
579✔
1416
            .blockreader
579✔
1417
            .read_data_to_buffer(fileoffset, fileoffset + sz, false, slice_)
579✔
1418
        {
1419
            ResultReadDataToBuffer::Found(val) => val,
579✔
UNCOV
1420
            ResultReadDataToBuffer::Done => {
×
UNCOV
1421
                defx!("return ResultFindFixedStruct::Done; read_data_to_buffer returned Done");
×
UNCOV
1422
                return ResultFindFixedStruct::Done;
×
1423
            }
UNCOV
1424
            ResultReadDataToBuffer::Err(err) => {
×
UNCOV
1425
                self.set_error(&err);
×
UNCOV
1426
                defx!("return ResultFindFixedStruct::Err({:?})", err);
×
1427
                // an error from `blockreader.read_data_to_buffer` is unlikely to improve
1428
                // with a retry so return `None` signifying no more processing of the file
UNCOV
1429
                return ResultFindFixedStruct::Err((None, err));
×
1430
            }
1431
        };
1432
        debug_assert_eq!(_readn, sz as usize, "read {} bytes, expected {} bytes", _readn, sz);
579✔
1433

1434
        // create a FixedStruct from the slice
1435
        let fs: FixedStruct = match FixedStruct::new(
579✔
1436
            fileoffset,
579✔
1437
            &self.tz_offset,
579✔
1438
            slice_,
579✔
1439
            self.fixedstruct_type(),
579✔
1440
        ) {
579✔
1441
            Ok(val) => val,
579✔
UNCOV
1442
            Err(err) => {
×
UNCOV
1443
                defx!("return ResultFindFixedStruct::Done; FixedStruct::new returned Err({:?})", err);
×
UNCOV
1444
                return ResultFindFixedStruct::Err((Some(fo_next), err));
×
1445
            }
1446
        };
1447
        // update various statistics/counters
1448
        self.entries_processed += 1;
579✔
1449
        defo!("entries_processed = {}", self.entries_processed);
579✔
1450
        summary_stat!(self.dt_first_last_update(fs.dt()));
579✔
1451
        // try to drop blocks associated with the entry
1452
        self.drop_entry(&fs);
579✔
1453

1454
        defx!("return ResultFindFixedStruct::Found((fo_next={}, …))", fo_next);
579✔
1455

1456
        ResultFindFixedStruct::Found((fo_next, fs))
579✔
1457
    }
1,147✔
1458

1459
    /// Wrapper function for call to [`datetime::dt_after_or_before`] using the
1460
    /// [`FixedStruct::dt`] of the `entry`.
1461
    ///
1462
    /// [`datetime::dt_after_or_before`]: crate::data::datetime::dt_after_or_before
1463
    /// [`FixedStruct::dt`]: crate::data::fixedstruct::FixedStruct::dt
UNCOV
1464
    pub fn entry_dt_after_or_before(
×
UNCOV
1465
        entry: &FixedStruct,
×
UNCOV
1466
        dt_filter: &DateTimeLOpt,
×
UNCOV
1467
    ) -> Result_Filter_DateTime1 {
×
UNCOV
1468
        defñ!("({:?})", dt_filter);
×
1469

1470
        dt_after_or_before(entry.dt(), dt_filter)
×
1471
    }
×
1472

1473
    /// Wrapper function for call to [`datetime::dt_pass_filters`] using the
1474
    /// [`FixedStruct::dt`] of the `entry`.
1475
    ///
1476
    /// [`datetime::dt_pass_filters`]: crate::data::datetime::dt_pass_filters
1477
    /// [`FixedStruct::dt`]: crate::data::fixedstruct::FixedStruct::dt
1478
    #[inline(always)]
1479
    pub fn entry_pass_filters(
×
UNCOV
1480
        entry: &FixedStruct,
×
1481
        dt_filter_after: &DateTimeLOpt,
×
UNCOV
1482
        dt_filter_before: &DateTimeLOpt,
×
1483
    ) -> Result_Filter_DateTime2 {
×
1484
        defn!("({:?}, {:?})", dt_filter_after, dt_filter_before);
×
1485

UNCOV
1486
        let result: Result_Filter_DateTime2 = dt_pass_filters(
×
UNCOV
1487
            entry.dt(),
×
UNCOV
1488
            dt_filter_after,
×
UNCOV
1489
            dt_filter_before
×
1490
        );
UNCOV
1491
        defx!("(…) return {:?};", result);
×
1492

UNCOV
1493
        result
×
UNCOV
1494
    }
×
1495

1496
    /// Return an up-to-date [`SummaryFixedStructReader`] instance for this
1497
    /// `FixedStructReader`.
1498
    ///
1499
    /// [`SummaryFixedStructReader`]: SummaryFixedStructReader
1500
    #[allow(non_snake_case)]
1501
    pub fn summary(&self) -> SummaryFixedStructReader {
94✔
1502
        let fixedstructreader_fixedstructtype_opt = Some(self.fixedstruct_type());
94✔
1503
        let fixedstructreader_filetypefixedstruct_opt = Some(self.filetype_fixedstruct);
94✔
1504
        let fixedstructreader_high_score: Score = self.high_score;
94✔
1505
        let fixedstructreader_utmp_entries: Count = self.entries_processed;
94✔
1506
        let fixedstructreader_first_entry_fileoffset: FileOffset = self.first_entry_fileoffset;
94✔
1507
        let fixedstructreader_entries_out_of_order: Count = self.entries_out_of_order;
94✔
1508
        let fixedstructreader_utmp_entries_max: Count = self.entries_stored_highest as Count;
94✔
1509
        let fixedstructreader_utmp_entries_hit: Count = self.entries_hits as Count;
94✔
1510
        let fixedstructreader_utmp_entries_miss: Count = self.entries_miss as Count;
94✔
1511
        let fixedstructreader_drop_entry_ok: Count = self.drop_entry_ok;
94✔
1512
        let fixedstructreader_drop_entry_errors: Count = self.drop_entry_errors;
94✔
1513
        let fixedstructreader_datetime_first = self.dt_first;
94✔
1514
        let fixedstructreader_datetime_last = self.dt_last;
94✔
1515
        let fixedstructreader_map_tvpair_fo_max_len: usize = self.map_tvpair_fo_max_len;
94✔
1516

1517
        SummaryFixedStructReader {
94✔
1518
            fixedstructreader_fixedstructtype_opt,
94✔
1519
            fixedstructreader_filetypefixedstruct_opt,
94✔
1520
            fixedstructreader_fixedstruct_size: self.fixedstruct_size(),
94✔
1521
            fixedstructreader_high_score,
94✔
1522
            fixedstructreader_utmp_entries,
94✔
1523
            fixedstructreader_first_entry_fileoffset,
94✔
1524
            fixedstructreader_entries_out_of_order,
94✔
1525
            fixedstructreader_utmp_entries_max,
94✔
1526
            fixedstructreader_utmp_entries_hit,
94✔
1527
            fixedstructreader_utmp_entries_miss,
94✔
1528
            fixedstructreader_drop_entry_ok,
94✔
1529
            fixedstructreader_drop_entry_errors,
94✔
1530
            fixedstructreader_datetime_first,
94✔
1531
            fixedstructreader_datetime_last,
94✔
1532
            fixedstructreader_map_tvpair_fo_max_len,
94✔
1533
        }
94✔
1534
    }
94✔
1535

1536
    /// Return an up-to-date [`Summary`] instance for this `FixedStructReader`.
1537
    ///
1538
    /// [`Summary`]: crate::readers::summary::Summary
1539
    pub fn summary_complete(&self) -> Summary {
93✔
1540
        let path = self.path().clone();
93✔
1541
        let path_ntf = None;
93✔
1542
        let filetype = self.filetype();
93✔
1543
        let logmessagetype = filetype.to_logmessagetype();
93✔
1544
        let summaryblockreader = self.blockreader.summary();
93✔
1545
        let summaryfixedstructreader = self.summary();
93✔
1546
        let error: Option<String> = self.error.clone();
93✔
1547

1548
        Summary::new(
93✔
1549
            path,
93✔
1550
            path_ntf,
93✔
1551
            filetype,
93✔
1552
            logmessagetype,
93✔
1553
            Some(summaryblockreader),
93✔
1554
            None,
93✔
1555
            None,
93✔
1556
            None,
93✔
1557
            Some(summaryfixedstructreader),
93✔
1558
            None,
93✔
1559
            None,
93✔
1560
            None,
93✔
1561
            error,
93✔
1562
        )
1563
    }
93✔
1564
}
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