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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

79.9
/src/logfile.cc
1
/**
2
 * Copyright (c) 2007-2012, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * @file logfile.cc
30
 */
31

32
#include <filesystem>
33
#include <utility>
34

35
#include "logfile.hh"
36

37
#include <errno.h>
38
#include <fcntl.h>
39
#include <string.h>
40
#include <sys/param.h>
41
#include <sys/resource.h>
42
#include <sys/stat.h>
43
#include <time.h>
44

45
#include "base/ansi_scrubber.hh"
46
#include "base/attr_line.builder.hh"
47
#include "base/date_time_scanner.cfg.hh"
48
#include "base/fs_util.hh"
49
#include "base/injector.hh"
50
#include "base/snippet_highlighters.hh"
51
#include "base/string_util.hh"
52
#include "base/time_util.hh"
53
#include "config.h"
54
#include "file_options.hh"
55
#include "hasher.hh"
56
#include "lnav_util.hh"
57
#include "log.watch.hh"
58
#include "log_format.hh"
59
#include "logfile.cfg.hh"
60
#include "piper.header.hh"
61
#include "yajlpp/yajlpp_def.hh"
62

63
using namespace lnav::roles::literals;
64

65
static auto intern_lifetime = intern_string::get_table_lifetime();
66

67
static constexpr size_t INDEX_RESERVE_INCREMENT = 1024;
68

69
static const typed_json_path_container<lnav::gzip::header>&
70
get_file_header_handlers()
4✔
71
{
72
    static const typed_json_path_container<lnav::gzip::header> retval = {
73
        yajlpp::property_handler("name").for_field(&lnav::gzip::header::h_name),
8✔
74
        yajlpp::property_handler("mtime").for_field(
8✔
75
            &lnav::gzip::header::h_mtime),
76
        yajlpp::property_handler("comment").for_field(
8✔
77
            &lnav::gzip::header::h_comment),
78
    };
24✔
79

80
    return retval;
4✔
81
}
16✔
82

83
Result<std::shared_ptr<logfile>, std::string>
84
logfile::open(std::filesystem::path filename,
660✔
85
              const logfile_open_options& loo,
86
              auto_fd fd)
87
{
88
    require(!filename.empty());
660✔
89

90
    auto lf = std::shared_ptr<logfile>(new logfile(std::move(filename), loo));
660✔
91

92
    memset(&lf->lf_stat, 0, sizeof(lf->lf_stat));
660✔
93
    std::filesystem::path resolved_path;
660✔
94

95
    if (!fd.has_value()) {
660✔
96
        auto rp_res = lnav::filesystem::realpath(lf->lf_filename);
651✔
97
        if (rp_res.isErr()) {
651✔
UNCOV
98
            return Err(fmt::format(FMT_STRING("realpath({}) failed with: {}"),
×
99
                                   lf->lf_filename,
×
100
                                   rp_res.unwrapErr()));
×
101
        }
102

103
        resolved_path = rp_res.unwrap();
651✔
104
        if (lnav::filesystem::statp(resolved_path, &lf->lf_stat) == -1) {
651✔
UNCOV
105
            return Err(fmt::format(FMT_STRING("stat({}) failed with: {}"),
×
UNCOV
106
                                   lf->lf_filename,
×
107
                                   strerror(errno)));
×
108
        }
109

110
        if (!S_ISREG(lf->lf_stat.st_mode)) {
651✔
UNCOV
111
            return Err(fmt::format(FMT_STRING("{} is not a regular file"),
×
UNCOV
112
                                   lf->lf_filename));
×
113
        }
114
    }
651✔
115

116
    auto_fd lf_fd;
660✔
117
    if (fd.has_value()) {
660✔
118
        lf_fd = std::move(fd);
9✔
119
    } else if ((lf_fd
651✔
120
                = lnav::filesystem::openp(resolved_path, O_RDONLY | O_CLOEXEC))
651✔
121
               == -1)
651✔
122
    {
123
        return Err(fmt::format(FMT_STRING("open({}) failed with: {}"),
×
124
                               lf->lf_filename,
×
125
                               strerror(errno)));
×
126
    } else {
127
        lf->lf_actual_path = lf->lf_filename;
651✔
128
        lf->lf_valid_filename = true;
651✔
129
    }
130

131
    lf_fd.close_on_exec();
660✔
132

133
    log_info("Creating logfile: fd=%d; size=%" PRId64 "; mtime=%" PRId64
660✔
134
             "; filename=%s",
135
             (int) lf_fd,
136
             (long long) lf->lf_stat.st_size,
137
             (long long) lf->lf_stat.st_mtime,
138
             lf->lf_filename_as_string.c_str());
139
    if (lf->lf_actual_path) {
660✔
140
        log_info("  actual_path=%s", lf->lf_actual_path->c_str());
651✔
141
    }
142

143
    if (!lf->lf_options.loo_filename.empty()) {
660✔
144
        lf->set_filename(lf->lf_options.loo_filename);
70✔
145
        lf->lf_valid_filename = false;
70✔
146
    }
147

148
    lf->lf_line_buffer.set_fd(lf_fd);
660✔
149
    lf->lf_index.reserve(INDEX_RESERVE_INCREMENT);
660✔
150

151
    lf->lf_indexing = lf->lf_options.loo_is_visible;
660✔
152
    lf->lf_text_format
660✔
153
        = lf->lf_options.loo_text_format.value_or(text_format_t::TF_UNKNOWN);
660✔
154
    lf->lf_format_match_messages = loo.loo_match_details;
660✔
155

156
    const auto& hdr = lf->lf_line_buffer.get_header_data();
660✔
157
    if (hdr.valid()) {
660✔
158
        log_info("%s: has header %d",
62✔
159
                 lf->lf_filename_as_string.c_str(),
160
                 hdr.valid());
161
        hdr.match(
62✔
UNCOV
162
            [&lf](const lnav::gzip::header& gzhdr) {
×
163
                if (!gzhdr.empty()) {
4✔
164
                    lf->lf_embedded_metadata["net.zlib.gzip.header"] = {
12✔
165
                        text_format_t::TF_JSON,
166
                        get_file_header_handlers()
8✔
167
                            .formatter_for(gzhdr)
8✔
168
                            .with_config(yajl_gen_beautify, 1)
8✔
169
                            .to_string(),
170
                    };
12✔
171
                }
172
            },
12✔
173
            [&lf](const lnav::piper::header& phdr) {
62✔
174
                static auto& safe_options_hier
175
                    = injector::get<lnav::safe_file_options_hier&>();
58✔
176

177
                lf->lf_embedded_metadata["org.lnav.piper.header"] = {
174✔
178
                    text_format_t::TF_JSON,
179
                    lnav::piper::header_handlers.formatter_for(phdr)
58✔
180
                        .with_config(yajl_gen_beautify, 1)
116✔
181
                        .to_string(),
182
                };
116✔
183
                log_info("setting file name from piper header: %s",
58✔
184
                         phdr.h_name.c_str());
185
                lf->set_filename(phdr.h_name);
58✔
186
                lf->lf_valid_filename = false;
58✔
187
                if (phdr.h_demux_output == lnav::piper::demux_output_t::signal)
58✔
188
                {
189
                    lf->lf_text_format = text_format_t::TF_LOG;
12✔
190
                }
191

192
                lnav::file_options fo;
58✔
193
                if (!phdr.h_timezone.empty()) {
58✔
194
                    log_info("setting default time zone from piper header: %s",
23✔
195
                             phdr.h_timezone.c_str());
196
                    try {
197
                        fo.fo_default_zone.pp_value
198
                            = date::locate_zone(phdr.h_timezone);
23✔
UNCOV
199
                    } catch (const std::runtime_error& e) {
×
200
                        log_error("unable to get tz from piper header %s -- %s",
×
201
                                  phdr.h_timezone.c_str(),
202
                                  e.what());
UNCOV
203
                    }
×
204
                }
205
                if (!fo.empty()) {
58✔
206
                    safe::WriteAccess<lnav::safe_file_options_hier>
207
                        options_hier(safe_options_hier);
23✔
208

209
                    options_hier->foh_generation += 1;
23✔
210
                    auto& coll = options_hier->foh_path_to_collection["/"];
23✔
211
                    coll.foc_pattern_to_options[lf->get_filename()] = fo;
23✔
212
                }
23✔
213
            });
174✔
214
    }
215

216
    lf->file_options_have_changed();
660✔
217
    lf->lf_content_id = hasher().update(lf->lf_filename_as_string).to_string();
660✔
218

219
    lf->lf_line_buffer.set_do_preloading(true);
660✔
220
    lf->lf_line_buffer.send_initial_load();
660✔
221

222
    ensure(lf->invariant());
660✔
223

224
    return Ok(lf);
660✔
225
}
660✔
226

227
logfile::logfile(std::filesystem::path filename,
660✔
228
                 const logfile_open_options& loo)
660✔
229
    : lf_filename(std::move(filename)),
660✔
230
      lf_filename_as_string(lf_filename.string()), lf_options(loo)
1,320✔
231
{
232
    this->lf_opids.writeAccess()->los_opid_ranges.reserve(64);
660✔
233
}
660✔
234

235
logfile::~logfile()
1,282✔
236
{
237
    log_info("destructing logfile: %s", this->lf_filename_as_string.c_str());
641✔
238
}
1,282✔
239

240
bool
241
logfile::file_options_have_changed()
4,580✔
242
{
243
    static auto& safe_options_hier
244
        = injector::get<lnav::safe_file_options_hier&>();
4,580✔
245

246
    bool tz_changed = false;
4,580✔
247

248
    {
249
        safe::ReadAccess<lnav::safe_file_options_hier> options_hier(
250
            safe_options_hier);
4,580✔
251

252
        if (this->lf_file_options_generation == options_hier->foh_generation) {
4,580✔
253
            return false;
3,989✔
254
        }
255
        log_info("checking new generation of file options: %d -> %d",
591✔
256
                 this->lf_file_options_generation,
257
                 options_hier->foh_generation);
258
        auto new_options = options_hier->match(this->get_filename());
591✔
259
        if (this->lf_file_options == new_options) {
591✔
260
            this->lf_file_options_generation = options_hier->foh_generation;
561✔
261
            return false;
561✔
262
        }
263

264
        this->lf_file_options = new_options;
30✔
265
        log_info("%s: file options have changed",
30✔
266
                 this->lf_filename_as_string.c_str());
267
        if (this->lf_file_options) {
30✔
268
            log_info(
30✔
269
                "  tz=%s",
270
                this->lf_file_options->second.fo_default_zone.pp_value->name()
271
                    .c_str());
272
            if (this->lf_file_options->second.fo_default_zone.pp_value
30✔
273
                    != nullptr
274
                && this->lf_format != nullptr
30✔
275
                && !(this->lf_format->lf_timestamp_flags & ETF_ZONE_SET))
60✔
276
            {
277
                log_info("  tz change affects this file");
3✔
278
                tz_changed = true;
3✔
279
            }
280
        } else if (this->lf_format != nullptr
×
281
                   && !(this->lf_format->lf_timestamp_flags & ETF_ZONE_SET)
×
282
                   && this->lf_format->lf_date_time.dts_default_zone != nullptr)
×
283
        {
284
            tz_changed = true;
×
285
        }
286
        this->lf_file_options_generation = options_hier->foh_generation;
30✔
287
    }
5,141✔
288

289
    return tz_changed;
30✔
290
}
291

292
bool
293
logfile::exists() const
4,275✔
294
{
295
    if (!this->lf_actual_path) {
4,275✔
296
        return true;
62✔
297
    }
298

299
    if (this->lf_options.loo_source == logfile_name_source::ARCHIVE) {
4,213✔
300
        return true;
×
301
    }
302

303
    auto stat_res = lnav::filesystem::stat_file(this->lf_actual_path.value());
4,213✔
304
    if (stat_res.isErr()) {
4,213✔
305
        log_error("%s: stat failed -- %s",
×
306
                  this->lf_actual_path.value().c_str(),
307
                  stat_res.unwrapErr().c_str());
308
        return false;
×
309
    }
310

311
    auto st = stat_res.unwrap();
4,213✔
312
    return this->lf_stat.st_dev == st.st_dev
4,213✔
313
        && this->lf_stat.st_ino == st.st_ino;
4,213✔
314
}
4,213✔
315

316
auto
317
logfile::reset_state() -> void
8✔
318
{
319
    this->clear_time_offset();
8✔
320
    this->lf_indexing = this->lf_options.loo_is_visible;
8✔
321
}
8✔
322

323
void
324
logfile::set_format_base_time(log_format* lf, const line_info& li)
733,081✔
325
{
326
    time_t file_time = li.li_timestamp.tv_sec != 0
733,081✔
327
        ? li.li_timestamp.tv_sec
733,081✔
328
        : this->lf_line_buffer.get_file_time();
716,171✔
329

330
    if (file_time == 0) {
733,081✔
331
        file_time = this->lf_stat.st_mtime;
714,393✔
332
    }
333

334
    if (!this->lf_cached_base_time
733,081✔
335
        || this->lf_cached_base_time.value() != file_time)
733,081✔
336
    {
337
        tm new_base_tm;
338
        this->lf_cached_base_time = file_time;
632✔
339
        localtime_r(&file_time, &new_base_tm);
632✔
340
        this->lf_cached_base_tm = new_base_tm;
632✔
341
    }
342
    lf->lf_date_time.set_base_time(this->lf_cached_base_time.value(),
733,081✔
343
                                   this->lf_cached_base_tm.value());
733,081✔
344
}
733,081✔
345

346
bool
347
logfile::process_prefix(shared_buffer_ref& sbr,
16,276✔
348
                        const line_info& li,
349
                        scan_batch_context& sbc)
350
{
351
    static auto max_unrecognized_lines
352
        = injector::get<const lnav::logfile::config&>()
1,100✔
353
              .lc_max_unrecognized_lines;
16,276✔
354

355
    log_format::scan_result_t found = log_format::scan_no_match{};
16,276✔
356
    size_t prescan_size = this->lf_index.size();
16,276✔
357
    auto prescan_time = std::chrono::microseconds{0};
16,276✔
358
    bool retval = false;
16,276✔
359

360
    if (this->lf_options.loo_detect_format
32,552✔
361
        && (this->lf_format == nullptr || this->lf_index.size() < 250))
16,276✔
362
    {
363
        const auto& root_formats = log_format::get_root_formats();
10,378✔
364
        std::optional<std::pair<log_format*, log_format::scan_match>>
365
            best_match;
10,378✔
366
        size_t scan_count = 0;
10,378✔
367

368
        if (!this->lf_index.empty()) {
10,378✔
369
            prescan_time = this->lf_index[prescan_size - 1]
9,337✔
370
                               .get_time<std::chrono::microseconds>();
9,337✔
371
        }
372
        if (this->lf_format != nullptr) {
10,378✔
UNCOV
373
            best_match = std::make_pair(
×
374
                this->lf_format.get(),
8,446✔
375
                log_format::scan_match{this->lf_format_quality});
16,892✔
376
        }
377

378
        /*
379
         * Try each scanner until we get a match.  Fortunately, the formats
380
         * tend to be sufficiently different that there are few ambiguities...
381
         */
382
        log_trace("logfile[%s]: scanning line %d (offset: %d; size: %d)",
10,378✔
383
                  this->lf_filename_as_string.c_str(),
384
                  this->lf_index.size(),
385
                  li.li_file_range.fr_offset,
386
                  li.li_file_range.fr_size);
387
        auto starting_index_size = this->lf_index.size();
10,378✔
388
        size_t prev_index_size = this->lf_index.size();
10,378✔
389
        for (const auto& curr : root_formats) {
760,097✔
390
            if (this->lf_index.size()
749,719✔
391
                >= curr->lf_max_unrecognized_lines.value_or(
749,719✔
392
                    max_unrecognized_lines))
393
            {
394
                continue;
17,166✔
395
            }
396

397
            if (this->lf_mismatched_formats.count(curr->get_name()) > 0) {
749,719✔
398
                continue;
16,078✔
399
            }
400

401
            auto match_res = curr->match_name(this->lf_filename_as_string);
733,641✔
402
            if (match_res.is<log_format::name_mismatched>()) {
733,641✔
403
                auto nm = match_res.get<log_format::name_mismatched>();
1,088✔
404
                if (li.li_file_range.fr_offset == 0) {
1,088✔
405
                    log_debug("(%s) does not match file name: %s",
1,024✔
406
                              curr->get_name().get(),
407
                              this->lf_filename_as_string.c_str());
408
                }
409
                auto regex_al = attr_line_t(nm.nm_pattern);
1,088✔
410
                lnav::snippets::regex_highlighter(
1,088✔
411
                    regex_al, -1, line_range{0, (int) regex_al.length()});
1,088✔
412
                auto note = attr_line_t("pattern: ")
1,088✔
413
                                .append(regex_al)
1,088✔
414
                                .append("\n  ")
1,088✔
415
                                .append(lnav::roles::quoted_code(
2,176✔
416
                                    fmt::to_string(this->get_filename())))
2,176✔
417
                                .append("\n")
1,088✔
418
                                .append(nm.nm_partial + 2, ' ')
1,088✔
419
                                .append("^ matched up to here"_snippet_border);
1,088✔
420
                auto match_um = lnav::console::user_message::info(
1,088✔
421
                                    attr_line_t()
1,088✔
422
                                        .append(lnav::roles::identifier(
2,176✔
423
                                            curr->get_name().to_string()))
2,176✔
424
                                        .append(" file name pattern required "
1,088✔
425
                                                "by format does not match"))
426
                                    .with_note(note)
1,088✔
427
                                    .move();
1,088✔
428
                this->lf_format_match_messages.emplace_back(match_um);
1,088✔
429
                this->lf_mismatched_formats.insert(curr->get_name());
1,088✔
430
                continue;
1,088✔
431
            }
1,088✔
432
            if (this->lf_options.loo_format_name
732,553✔
433
                && !(curr->get_name()
732,553✔
434
                     == this->lf_options.loo_format_name.value()))
1,465,106✔
435
            {
UNCOV
436
                if (li.li_file_range.fr_offset == 0) {
×
437
                    log_debug("(%s) does not match file format: %s",
×
438
                              curr->get_name().get(),
439
                              fmt::to_string(this->lf_options.loo_file_format)
440
                                  .c_str());
441
                }
UNCOV
442
                continue;
×
443
            }
444

445
            scan_count += 1;
732,553✔
446
            curr->clear();
732,553✔
447
            this->set_format_base_time(curr.get(), li);
732,553✔
448
            log_format::scan_result_t scan_res{mapbox::util::no_init{}};
732,553✔
449
            scan_batch_context sbc_tmp{this->lf_allocator};
732,553✔
450
            if (this->lf_format != nullptr
732,553✔
451
                && this->lf_format->lf_root_format == curr.get())
732,553✔
452
            {
453
                scan_res = this->lf_format->scan(
16,892✔
454
                    *this, this->lf_index, li, sbr, sbc);
8,446✔
455
            } else {
456
                scan_res = curr->scan(*this, this->lf_index, li, sbr, sbc_tmp);
724,107✔
457
            }
458

459
            scan_res.match(
732,553✔
460
                [this,
732,553✔
461
                 &sbc,
462
                 &sbc_tmp,
463
                 &found,
464
                 &curr,
465
                 &best_match,
466
                 &prev_index_size,
467
                 starting_index_size](const log_format::scan_match& sm) {
468
                    if (best_match && this->lf_format != nullptr
19,496✔
469
                        && this->lf_format->lf_root_format == curr.get()
9,316✔
470
                        && best_match->first == this->lf_format.get())
19,496✔
471
                    {
472
                        prev_index_size = this->lf_index.size();
7,585✔
473
                        found = best_match->second;
7,585✔
474
                    } else if (!best_match
2,427✔
475
                               || (sm.sm_quality > best_match->second.sm_quality
4,296✔
476
                                   || (sm.sm_quality
3,738✔
477
                                           == best_match->second.sm_quality
1,869✔
478
                                       && sm.sm_strikes
248✔
479
                                           < best_match->second.sm_strikes)))
124✔
480
                    {
481
                        log_info(
558✔
482
                            "  scan with format (%s) matched with quality of "
483
                            "%d and %d strikes",
484
                            curr->get_name().c_str(),
485
                            sm.sm_quality,
486
                            sm.sm_strikes);
487

488
                        sbc.sbc_opids = sbc_tmp.sbc_opids;
558✔
489
                        auto match_um
UNCOV
490
                            = lnav::console::user_message::info(
×
491
                                  attr_line_t()
558✔
492
                                      .append(lnav::roles::identifier(
558✔
493
                                          curr->get_name().to_string()))
1,116✔
494
                                      .append(" matched line ")
558✔
495
                                      .append(lnav::roles::number(
1,116✔
496
                                          fmt::to_string(starting_index_size))))
1,116✔
497
                                  .with_note(
1,116✔
498
                                      attr_line_t("match quality is ")
1,116✔
499
                                          .append(lnav::roles::number(
558✔
500
                                              fmt::to_string(sm.sm_quality)))
1,116✔
501
                                          .append(" with ")
558✔
502
                                          .append(lnav::roles::number(
1,116✔
503
                                              fmt::to_string(sm.sm_strikes)))
1,116✔
504
                                          .append(" strikes"))
558✔
505
                                  .move();
558✔
506
                        this->lf_format_match_messages.emplace_back(match_um);
558✔
507
                        if (best_match) {
558✔
508
                            auto starting_iter = std::next(
60✔
509
                                this->lf_index.begin(), starting_index_size);
30✔
510
                            auto last_iter = std::next(this->lf_index.begin(),
60✔
511
                                                       prev_index_size);
30✔
512
                            this->lf_index.erase(starting_iter, last_iter);
30✔
513
                        }
514
                        best_match = std::make_pair(curr.get(), sm);
558✔
515
                        prev_index_size = this->lf_index.size();
558✔
516
                    } else {
558✔
517
                        log_trace(
1,869✔
518
                            "  scan with format (%s) matched, but "
519
                            "is lower quality (%d < %d) or more strikes (%d "
520
                            "vs. %d)",
521
                            curr->get_name().c_str(),
522
                            sm.sm_quality,
523
                            best_match->second.sm_quality,
524
                            sm.sm_strikes,
525
                            best_match->second.sm_strikes);
526
                        while (this->lf_index.size() > prev_index_size) {
4,219✔
527
                            this->lf_index.pop_back();
2,350✔
528
                        }
529
                    }
530
                },
10,012✔
531
                [curr](const log_format::scan_incomplete& si) {
1,465,106✔
532
                    log_trace(
17✔
533
                        "  scan with format (%s) is incomplete, "
534
                        "more data required",
535
                        curr->get_name().c_str());
536
                },
17✔
537
                [this, curr](const log_format::scan_no_match& snm) {
1,465,106✔
538
                    if (this->lf_format == nullptr) {
722,524✔
539
                        log_trace(
135,194✔
540
                            "  scan with format (%s) does not match -- %s",
541
                            curr->get_name().c_str(),
542
                            snm.snm_reason);
543
                    }
544
                });
722,524✔
545
        }
733,641✔
546

547
        if (!scan_count) {
10,378✔
UNCOV
548
            log_info("%s: no formats available to scan, no longer detecting",
×
549
                     this->lf_filename_as_string.c_str());
UNCOV
550
            this->lf_options.loo_detect_format = false;
×
551
        }
552

553
        if (best_match
10,378✔
554
            && (this->lf_format == nullptr
18,824✔
555
                || ((this->lf_format->lf_root_format != best_match->first)
8,446✔
556
                    && best_match->second.sm_quality
8,446✔
557
                        > this->lf_format_quality)))
18,824✔
558
        {
559
            auto winner = best_match.value();
528✔
560
            auto* curr = winner.first;
528✔
561
            log_info("%s:%d:log format found -- %s",
528✔
562
                     this->lf_filename_as_string.c_str(),
563
                     this->lf_index.size(),
564
                     curr->get_name().get());
565

566
            auto match_um = lnav::console::user_message::ok(
567
                attr_line_t()
528✔
568
                    .append(lnav::roles::identifier(
528✔
569
                        winner.first->get_name().to_string()))
1,056✔
570
                    .append(" is the best match for line ")
528✔
571
                    .append(lnav::roles::number(
1,056✔
572
                        fmt::to_string(starting_index_size))));
1,584✔
573
            this->lf_format_match_messages.emplace_back(match_um);
528✔
574
            this->lf_text_format = text_format_t::TF_LOG;
528✔
575
            this->lf_format = curr->specialized();
528✔
576
            this->lf_format_quality = winner.second.sm_quality;
528✔
577
            this->set_format_base_time(this->lf_format.get(), li);
528✔
578
            if (this->lf_format->lf_date_time.dts_fmt_lock != -1) {
528✔
579
                this->lf_content_id
580
                    = hasher().update(sbr.get_data(), sbr.length()).to_string();
513✔
581
            }
582

583
            this->lf_applicable_taggers.clear();
528✔
584
            for (auto& td_pair : this->lf_format->lf_tag_defs) {
539✔
585
                bool matches = td_pair.second->ftd_paths.empty();
11✔
586
                for (const auto& pr : td_pair.second->ftd_paths) {
15✔
587
                    if (pr.matches(this->lf_filename_as_string.c_str())) {
4✔
UNCOV
588
                        matches = true;
×
UNCOV
589
                        break;
×
590
                    }
591
                }
592
                if (!matches) {
11✔
593
                    continue;
4✔
594
                }
595

596
                log_info("%s: found applicable tag definition /%s/tags/%s",
7✔
597
                         this->lf_filename_as_string.c_str(),
598
                         this->lf_format->get_name().get(),
599
                         td_pair.second->ftd_name.c_str());
600
                this->lf_applicable_taggers.emplace_back(td_pair.second);
7✔
601
            }
602

603
            this->lf_applicable_partitioners.clear();
528✔
604
            for (auto& pd_pair : this->lf_format->lf_partition_defs) {
540✔
605
                bool matches = pd_pair.second->fpd_paths.empty();
12✔
606
                for (const auto& pr : pd_pair.second->fpd_paths) {
16✔
607
                    if (pr.matches(this->lf_filename_as_string.c_str())) {
4✔
UNCOV
608
                        matches = true;
×
UNCOV
609
                        break;
×
610
                    }
611
                }
612
                if (!matches) {
12✔
613
                    continue;
4✔
614
                }
615

616
                log_info(
8✔
617
                    "%s: found applicable partition definition "
618
                    "/%s/partitions/%s",
619
                    this->lf_filename_as_string.c_str(),
620
                    this->lf_format->get_name().get(),
621
                    pd_pair.second->fpd_name.c_str());
622
                this->lf_applicable_partitioners.emplace_back(pd_pair.second);
8✔
623
            }
624

625
            /*
626
             * We'll go ahead and assume that any previous lines were
627
             * written out at the same time as the last one, so we need to
628
             * go back and update everything.
629
             */
630
            const auto& last_line = this->lf_index.back();
528✔
631

632
            require_lt(starting_index_size, this->lf_index.size());
528✔
633
            for (size_t lpc = 0; lpc < starting_index_size; lpc++) {
751✔
634
                if (this->lf_format->lf_multiline) {
223✔
635
                    this->lf_index[lpc].set_time(
223✔
636
                        last_line.get_time<std::chrono::microseconds>());
637
                    if (this->lf_format->lf_structured) {
223✔
638
                        this->lf_index[lpc].set_ignore(true);
216✔
639
                    }
640
                } else {
UNCOV
641
                    this->lf_index[lpc].set_time(
×
642
                        last_line.get_time<std::chrono::microseconds>());
UNCOV
643
                    this->lf_index[lpc].set_level(LEVEL_INVALID);
×
644
                }
645
                retval = true;
223✔
646
            }
647

648
            found = best_match->second;
528✔
649
        }
528✔
650
    } else if (this->lf_format.get() != nullptr) {
5,898✔
651
        if (!this->lf_index.empty()) {
3,569✔
652
            prescan_time = this->lf_index[prescan_size - 1]
3,569✔
653
                               .get_time<std::chrono::microseconds>();
3,569✔
654
        }
655
        /* We've locked onto a format, just use that scanner. */
656
        found = this->lf_format->scan(*this, this->lf_index, li, sbr, sbc);
3,569✔
657
    }
658

659
    if (found.is<log_format::scan_match>()) {
16,276✔
660
        if (!this->lf_index.empty()) {
10,584✔
661
            auto& last_line = this->lf_index.back();
10,584✔
662

663
            last_line.set_valid_utf(last_line.is_valid_utf()
21,168✔
664
                                    && li.li_utf8_scan_result.is_valid());
10,584✔
665
            last_line.set_has_ansi(last_line.has_ansi()
21,168✔
666
                                   || li.li_utf8_scan_result.usr_has_ansi);
10,584✔
667
            if (last_line.get_msg_level() == LEVEL_INVALID) {
10,584✔
668
                if (this->lf_invalid_lines.ili_lines.size()
9✔
669
                    < invalid_line_info::MAX_INVALID_LINES)
9✔
670
                {
671
                    this->lf_invalid_lines.ili_lines.push_back(
18✔
672
                        this->lf_index.size() - 1);
9✔
673
                }
674
                this->lf_invalid_lines.ili_total += 1;
9✔
675
            }
676
        }
677
        if (prescan_size > 0 && this->lf_index.size() >= prescan_size
9,642✔
678
            && prescan_time
20,226✔
679
                != this->lf_index[prescan_size - 1]
19,284✔
680
                       .get_time<std::chrono::microseconds>())
20,226✔
681
        {
682
            retval = true;
51✔
683
        }
684
        if (prescan_size > 0 && prescan_size < this->lf_index.size()) {
10,584✔
685
            auto& second_to_last = this->lf_index[prescan_size - 1];
9,620✔
686
            auto& latest = this->lf_index[prescan_size];
9,620✔
687

688
            if (!second_to_last.is_ignored() && latest < second_to_last) {
9,620✔
689
                if (this->lf_format->lf_time_ordered) {
1,194✔
690
                    this->lf_out_of_time_order_count += 1;
16✔
691
                    for (size_t lpc = prescan_size; lpc < this->lf_index.size();
32✔
692
                         lpc++)
693
                    {
694
                        auto& line_to_update = this->lf_index[lpc];
16✔
695

696
                        line_to_update.set_time_skew(true);
16✔
697
                        line_to_update.set_time(
16✔
698
                            second_to_last
699
                                .get_time<std::chrono::microseconds>());
700
                    }
701
                } else {
702
                    retval = true;
1,178✔
703
                }
704
            }
705
        }
706
    } else if (found.is<log_format::scan_no_match>()) {
5,692✔
707
        log_level_t last_level = LEVEL_UNKNOWN;
5,692✔
708
        auto last_time = this->lf_index_time;
5,692✔
709
        uint8_t last_mod = 0, last_opid = 0;
5,692✔
710

711
        if (this->lf_format == nullptr && li.li_timestamp.tv_sec != 0) {
5,692✔
712
            last_time = std::chrono::duration_cast<std::chrono::microseconds>(
36✔
713
                            std::chrono::seconds{li.li_timestamp.tv_sec})
36✔
714
                + std::chrono::microseconds(li.li_timestamp.tv_usec);
72✔
715
            last_level = li.li_level;
36✔
716
        } else if (!this->lf_index.empty()) {
5,656✔
717
            const auto& ll = this->lf_index.back();
5,567✔
718

719
            /*
720
             * Assume this line is part of the previous one(s) and copy the
721
             * metadata over.
722
             */
723
            last_time = ll.get_time<std::chrono::microseconds>();
5,567✔
724
            if (this->lf_format.get() != nullptr) {
5,567✔
725
                last_level = (log_level_t) (ll.get_level_and_flags()
1,959✔
726
                                            | LEVEL_CONTINUED);
1,959✔
727
            }
728
            last_mod = ll.get_module_id();
5,567✔
729
            last_opid = ll.get_opid();
5,567✔
730
        }
731
        this->lf_index.emplace_back(li.li_file_range.fr_offset,
5,692✔
732
                                    last_time,
733
                                    last_level,
734
                                    last_mod,
735
                                    last_opid);
736
        this->lf_index.back().set_valid_utf(li.li_utf8_scan_result.is_valid());
5,692✔
737
        this->lf_index.back().set_has_ansi(li.li_utf8_scan_result.usr_has_ansi);
5,692✔
738
    }
739

740
    return retval;
16,276✔
741
}
16,276✔
742

743
logfile::rebuild_result_t
744
logfile::rebuild_index(std::optional<ui_clock::time_point> deadline)
3,934✔
745
{
746
    static const auto& dts_cfg
747
        = injector::get<const date_time_scanner_ns::config&>();
3,934✔
748

749
    if (!this->lf_invalidated_opids.empty()) {
3,934✔
UNCOV
750
        auto writeOpids = this->lf_opids.writeAccess();
×
751

752
        for (auto bm_pair : this->lf_bookmark_metadata) {
×
753
            if (bm_pair.second.bm_opid.empty()) {
×
UNCOV
754
                continue;
×
755
            }
756

UNCOV
757
            if (!this->lf_invalidated_opids.contains(bm_pair.second.bm_opid)) {
×
UNCOV
758
                continue;
×
759
            }
760

761
            auto opid_iter
UNCOV
762
                = writeOpids->los_opid_ranges.find(bm_pair.second.bm_opid);
×
UNCOV
763
            if (opid_iter == writeOpids->los_opid_ranges.end()) {
×
UNCOV
764
                log_warning("opid not in ranges: %s",
×
765
                            bm_pair.second.bm_opid.c_str());
UNCOV
766
                continue;
×
767
            }
768

UNCOV
769
            if (bm_pair.first >= this->lf_index.size()) {
×
UNCOV
770
                log_warning("stale bookmark: %d", bm_pair.first);
×
UNCOV
771
                continue;
×
772
            }
773

UNCOV
774
            auto& ll = this->lf_index[bm_pair.first];
×
UNCOV
775
            opid_iter->second.otr_range.extend_to(ll.get_timeval());
×
UNCOV
776
            opid_iter->second.otr_level_stats.update_msg_count(
×
777
                ll.get_msg_level());
778
        }
UNCOV
779
        this->lf_invalidated_opids.clear();
×
780
    }
781

782
    if (!this->lf_indexing) {
3,934✔
783
        if (this->lf_sort_needed) {
14✔
UNCOV
784
            this->lf_sort_needed = false;
×
UNCOV
785
            return rebuild_result_t::NEW_ORDER;
×
786
        }
787
        return rebuild_result_t::NO_NEW_LINES;
14✔
788
    }
789

790
    if (this->file_options_have_changed()
3,920✔
791
        || (this->lf_format != nullptr
6,773✔
792
            && (this->lf_zoned_to_local_state != dts_cfg.c_zoned_to_local
2,853✔
793
                || this->lf_format->format_changed())))
2,853✔
794
    {
795
        log_info("%s: format has changed, rebuilding",
4✔
796
                 this->lf_filename_as_string.c_str());
797
        this->lf_index.clear();
4✔
798
        this->lf_index_size = 0;
4✔
799
        this->lf_partial_line = false;
4✔
800
        this->lf_longest_line = 0;
4✔
801
        this->lf_sort_needed = true;
4✔
802
        {
803
            safe::WriteAccess<logfile::safe_opid_state> writable_opid_map(
804
                this->lf_opids);
4✔
805

806
            writable_opid_map->los_opid_ranges.clear();
4✔
807
            writable_opid_map->los_sub_in_use.clear();
4✔
808
        }
4✔
809
        this->lf_allocator.reset();
4✔
810
    }
811
    this->lf_zoned_to_local_state = dts_cfg.c_zoned_to_local;
3,920✔
812

813
    auto retval = rebuild_result_t::NO_NEW_LINES;
3,920✔
814
    struct stat st;
815

816
    this->lf_activity.la_polls += 1;
3,920✔
817

818
    if (fstat(this->lf_line_buffer.get_fd(), &st) == -1) {
3,920✔
UNCOV
819
        if (errno == EINTR) {
×
UNCOV
820
            return rebuild_result_t::NO_NEW_LINES;
×
821
        }
UNCOV
822
        return rebuild_result_t::INVALID;
×
823
    }
824

825
    const auto is_truncated = st.st_size < this->lf_stat.st_size;
3,920✔
826
    const auto is_user_provided_and_rewritten = (
3,920✔
827
        // files from other sources can have their mtimes monkeyed with
828
        this->lf_options.loo_source == logfile_name_source::USER
3,920✔
829
        && this->lf_stat.st_size == st.st_size
3,920✔
830
        && this->lf_stat.st_mtime != st.st_mtime);
7,840✔
831

832
    // Check the previous stat against the last to see if things are wonky.
833
    if (this->lf_named_file && (is_truncated || is_user_provided_and_rewritten))
3,920✔
834
    {
835
        auto is_overwritten = true;
1✔
836
        if (this->lf_format != nullptr) {
1✔
837
            const auto first_line = this->lf_index.begin();
1✔
838
            const auto first_line_range
839
                = this->get_file_range(first_line, false);
1✔
840
            auto read_res = this->read_range(first_line_range);
1✔
841
            if (read_res.isOk()) {
1✔
842
                auto sbr = read_res.unwrap();
1✔
843
                if (first_line->has_ansi()) {
1✔
UNCOV
844
                    sbr.erase_ansi();
×
845
                }
846
                auto curr_content_id
847
                    = hasher().update(sbr.get_data(), sbr.length()).to_string();
1✔
848

849
                log_info(
1✔
850
                    "%s: overwrite content_id double check: old:%s; now:%s",
851
                    this->lf_filename_as_string.c_str(),
852
                    this->lf_content_id.c_str(),
853
                    curr_content_id.c_str());
854
                if (this->lf_content_id == curr_content_id) {
1✔
855
                    is_overwritten = false;
1✔
856
                }
857
            } else {
1✔
UNCOV
858
                auto errmsg = read_res.unwrapErr();
×
UNCOV
859
                log_error("unable to read first line for overwrite check: %s",
×
860
                          errmsg.c_str());
861
            }
862
        }
1✔
863

864
        if (is_truncated || is_overwritten) {
1✔
865
            log_info("overwritten file detected, closing -- %s  new: %" PRId64
1✔
866
                     "/%" PRId64 "  old: %" PRId64 "/%" PRId64,
867
                     this->lf_filename_as_string.c_str(),
868
                     st.st_size,
869
                     st.st_mtime,
870
                     this->lf_stat.st_size,
871
                     this->lf_stat.st_mtime);
872
            this->close();
1✔
873
            return rebuild_result_t::NO_NEW_LINES;
1✔
874
        }
875
    }
876

877
    if (this->lf_text_format == text_format_t::TF_BINARY) {
3,919✔
878
        this->lf_index_size = st.st_size;
20✔
879
        this->lf_stat = st;
20✔
880
    } else if (this->lf_line_buffer.is_data_available(this->lf_index_size,
3,899✔
881
                                                      st.st_size))
882
    {
883
        this->lf_activity.la_reads += 1;
1,092✔
884

885
        // We haven't reached the end of the file.  Note that we use the
886
        // line buffer's notion of the file size since it may be compressed.
887
        bool has_format = this->lf_format.get() != nullptr;
1,092✔
888
        struct rusage begin_rusage;
889
        file_off_t off;
890
        size_t begin_size = this->lf_index.size();
1,092✔
891
        bool record_rusage = this->lf_index.size() == 1;
1,092✔
892
        off_t begin_index_size = this->lf_index_size;
1,092✔
893
        size_t rollback_size = 0, rollback_index_start = 0;
1,092✔
894

895
        if (record_rusage) {
1,092✔
896
            getrusage(RUSAGE_SELF, &begin_rusage);
425✔
897
        }
898

899
        if (begin_size == 0 && !has_format) {
1,092✔
900
            log_debug("scanning file... %s",
607✔
901
                      this->lf_filename_as_string.c_str());
902
        }
903

904
        if (!this->lf_index.empty()) {
1,092✔
905
            off = this->lf_index.back().get_offset();
481✔
906

907
            /*
908
             * Drop the last line we read since it might have been a partial
909
             * read.
910
             */
911
            while (this->lf_index.back().get_sub_offset() != 0) {
585✔
912
                this->lf_index.pop_back();
104✔
913
                rollback_size += 1;
104✔
914
            }
915
            this->lf_index.pop_back();
481✔
916
            rollback_index_start = this->lf_index.size();
481✔
917
            rollback_size += 1;
481✔
918

919
            if (!this->lf_index.empty()) {
481✔
920
                auto last_line = std::prev(this->lf_index.end());
36✔
921
                if (last_line != this->lf_index.begin()) {
36✔
922
                    auto prev_line = std::prev(last_line);
35✔
923
                    this->lf_line_buffer.flush_at(prev_line->get_offset());
35✔
924
                    auto prev_len_res
925
                        = this->message_byte_length(prev_line, false);
35✔
926

927
                    auto read_result = this->lf_line_buffer.read_range({
928
                        prev_line->get_offset(),
35✔
929
                        prev_len_res.mlr_length + 1,
35✔
930
                    });
70✔
931
                    if (read_result.isErr()) {
35✔
UNCOV
932
                        log_info(
×
933
                            "overwritten file detected, closing -- %s (%s)",
934
                            this->lf_filename_as_string.c_str(),
935
                            read_result.unwrapErr().c_str());
936
                        this->close();
×
UNCOV
937
                        return rebuild_result_t::INVALID;
×
938
                    }
939

940
                    auto sbr = read_result.unwrap();
35✔
941
                    if (!sbr.to_string_fragment().endswith("\n")) {
35✔
942
                        log_info("overwritten file detected, closing -- %s",
×
943
                                 this->lf_filename_as_string.c_str());
944
                        this->close();
×
945
                        return rebuild_result_t::INVALID;
×
946
                    }
947
                } else {
35✔
948
                    this->lf_line_buffer.flush_at(last_line->get_offset());
1✔
949
                }
950
                auto last_length_res
951
                    = this->message_byte_length(last_line, false);
36✔
952

953
                auto read_result = this->lf_line_buffer.read_range({
954
                    last_line->get_offset(),
36✔
955
                    last_length_res.mlr_length,
36✔
956
                });
72✔
957

958
                if (read_result.isErr()) {
36✔
959
                    log_info("overwritten file detected, closing -- %s (%s)",
×
960
                             this->lf_filename_as_string.c_str(),
961
                             read_result.unwrapErr().c_str());
UNCOV
962
                    this->close();
×
UNCOV
963
                    return rebuild_result_t::INVALID;
×
964
                }
965
            } else {
36✔
966
                this->lf_line_buffer.flush_at(0);
445✔
967
            }
968
        } else {
969
            this->lf_line_buffer.flush_at(0);
611✔
970
            off = 0;
611✔
971
        }
972
        if (this->lf_logline_observer != nullptr) {
1,092✔
973
            this->lf_logline_observer->logline_restart(*this, rollback_size);
1,003✔
974
        }
975

976
        bool sort_needed = std::exchange(this->lf_sort_needed, false);
1,092✔
977
        size_t limit = SIZE_MAX;
1,092✔
978

979
        if (deadline) {
1,092✔
UNCOV
980
            if (ui_clock::now() > deadline.value()) {
×
UNCOV
981
                if (has_format) {
×
UNCOV
982
                    log_warning("with format ran past deadline! -- %s",
×
983
                                this->lf_filename_as_string.c_str());
UNCOV
984
                    limit = 1000;
×
985
                } else {
UNCOV
986
                    limit = 100;
×
987
                }
UNCOV
988
            } else if (!has_format) {
×
UNCOV
989
                limit = 1000;
×
990
            } else {
UNCOV
991
                limit = 1000 * 1000;
×
992
            }
993
        }
994
        if (!has_format) {
1,092✔
995
            log_debug("loading file... %s:%d",
607✔
996
                      this->lf_filename_as_string.c_str(),
997
                      begin_size);
998
        }
999
        scan_batch_context sbc{this->lf_allocator};
1,092✔
1000
        sbc.sbc_opids.los_opid_ranges.reserve(32);
1,092✔
1001
        auto prev_range = file_range{off};
1,092✔
1002
        while (limit > 0) {
16,821✔
1003
            auto load_result = this->lf_line_buffer.load_next_line(prev_range);
16,821✔
1004

1005
            if (load_result.isErr()) {
16,821✔
UNCOV
1006
                log_error("%s: load next line failure -- %s",
×
1007
                          this->lf_filename_as_string.c_str(),
1008
                          load_result.unwrapErr().c_str());
UNCOV
1009
                this->close();
×
UNCOV
1010
                return rebuild_result_t::INVALID;
×
1011
            }
1012

1013
            auto li = load_result.unwrap();
16,821✔
1014

1015
            if (li.li_file_range.empty()) {
16,821✔
1016
                break;
545✔
1017
            }
1018
            prev_range = li.li_file_range;
16,276✔
1019

1020
            if (this->lf_format == nullptr
16,276✔
1021
                && !this->lf_options.loo_non_utf_is_visible
4,261✔
1022
                && !li.li_utf8_scan_result.is_valid())
20,537✔
1023
            {
UNCOV
1024
                log_info("file is not utf, hiding: %s",
×
1025
                         this->lf_filename_as_string.c_str());
UNCOV
1026
                this->lf_indexing = false;
×
UNCOV
1027
                this->lf_options.loo_is_visible = false;
×
1028
                auto utf8_error_um
UNCOV
1029
                    = lnav::console::user_message::error("invalid UTF-8")
×
UNCOV
1030
                          .with_reason(
×
UNCOV
1031
                              attr_line_t(li.li_utf8_scan_result.usr_message)
×
UNCOV
1032
                                  .append(" at line ")
×
UNCOV
1033
                                  .append(lnav::roles::number(fmt::to_string(
×
UNCOV
1034
                                      this->lf_index.size() + 1)))
×
UNCOV
1035
                                  .append(" column ")
×
UNCOV
1036
                                  .append(lnav::roles::number(fmt::to_string(
×
1037
                                      li.li_utf8_scan_result.usr_valid_frag
1038
                                          .sf_end))))
UNCOV
1039
                          .move();
×
UNCOV
1040
                auto note_um = lnav::console::user_message::warning(
×
1041
                                   "skipping indexing for file")
1042
                                   .with_reason(utf8_error_um)
×
UNCOV
1043
                                   .move();
×
UNCOV
1044
                this->lf_notes.writeAccess()->emplace(note_type::not_utf,
×
1045
                                                      note_um);
UNCOV
1046
                if (this->lf_logfile_observer != nullptr) {
×
UNCOV
1047
                    this->lf_logfile_observer->logfile_indexing(this, 0, 0);
×
1048
                }
UNCOV
1049
                break;
×
1050
            }
1051
            size_t old_size = this->lf_index.size();
16,276✔
1052

1053
            if (old_size == 0
16,276✔
1054
                && this->lf_text_format == text_format_t::TF_UNKNOWN)
1,056✔
1055
            {
1056
                auto fr = this->lf_line_buffer.get_available();
594✔
1057
                auto avail_data = this->lf_line_buffer.read_range(fr);
594✔
1058

1059
                this->lf_text_format
1060
                    = avail_data
594✔
1061
                          .map([path = this->get_path(),
1,188✔
1062
                                this](const shared_buffer_ref& avail_sbr)
1063
                                   -> text_format_t {
1064
                              constexpr auto DETECT_LIMIT = 16 * 1024;
594✔
1065
                              auto sbr_str = to_string(avail_sbr);
594✔
1066
                              if (sbr_str.size() > DETECT_LIMIT) {
594✔
1067
                                  sbr_str.resize(DETECT_LIMIT);
28✔
1068
                              }
1069

1070
                              if (this->lf_line_buffer.is_piper()) {
594✔
1071
                                  auto lines
1072
                                      = string_fragment::from_str(sbr_str)
45✔
1073
                                            .split_lines();
45✔
1074
                                  for (auto line_iter = lines.rbegin();
45✔
1075
                                       // XXX rejigger read_range() for
1076
                                       // multi-line reads
1077
                                       std::next(line_iter) != lines.rend();
264✔
1078
                                       ++line_iter)
87✔
1079
                                  {
1080
                                      sbr_str.erase(line_iter->sf_begin, 22);
87✔
1081
                                  }
1082
                              }
45✔
1083
                              const auto& sbr_meta = avail_sbr.get_metadata();
594✔
1084
                              if (!sbr_meta.m_valid_utf) {
594✔
UNCOV
1085
                                  return text_format_t::TF_BINARY;
×
1086
                              }
1087
                              if (sbr_meta.m_has_ansi) {
594✔
UNCOV
1088
                                  auto new_size = erase_ansi_escapes(sbr_str);
×
UNCOV
1089
                                  sbr_str.resize(new_size);
×
1090
                              }
1091
                              return detect_text_format(sbr_str, path);
594✔
1092
                          })
594✔
1093
                          .unwrapOr(text_format_t::TF_UNKNOWN);
594✔
1094
                log_debug("setting text format to %s",
594✔
1095
                          fmt::to_string(this->lf_text_format).c_str());
1096
                switch (this->lf_text_format) {
594✔
1097
                    case text_format_t::TF_DIFF:
15✔
1098
                    case text_format_t::TF_MAN:
1099
                    case text_format_t::TF_MARKDOWN:
1100
                        log_debug(
15✔
1101
                            "  file is text, disabling log format detection");
1102
                        this->lf_options.loo_detect_format = false;
15✔
1103
                        break;
15✔
1104
                    default:
579✔
1105
                        break;
579✔
1106
                }
1107
            }
594✔
1108
            if (!li.li_utf8_scan_result.is_valid()
16,276✔
1109
                && this->lf_text_format != text_format_t::TF_MARKDOWN
53✔
1110
                && this->lf_text_format != text_format_t::TF_LOG)
16,329✔
1111
            {
1112
                this->lf_text_format = text_format_t::TF_BINARY;
53✔
1113
            }
1114

1115
            auto read_result
1116
                = this->lf_line_buffer.read_range(li.li_file_range);
16,276✔
1117
            if (read_result.isErr()) {
16,276✔
UNCOV
1118
                log_error("%s:read failure -- %s",
×
1119
                          this->lf_filename_as_string.c_str(),
1120
                          read_result.unwrapErr().c_str());
UNCOV
1121
                this->close();
×
UNCOV
1122
                return rebuild_result_t::INVALID;
×
1123
            }
1124

1125
            auto sbr = read_result.unwrap();
16,276✔
1126

1127
            if (!li.li_utf8_scan_result.is_valid()) {
16,276✔
1128
                log_warning(
53✔
1129
                    "%s: invalid UTF-8 detected at L%d:C%d/%d (O:%lld) -- %s",
1130
                    this->lf_filename_as_string.c_str(),
1131
                    this->lf_index.size() + 1,
1132
                    li.li_utf8_scan_result.usr_valid_frag.sf_end,
1133
                    li.li_file_range.fr_size,
1134
                    li.li_file_range.fr_offset,
1135
                    li.li_utf8_scan_result.usr_message);
1136
                if (lnav_log_level <= lnav_log_level_t::TRACE) {
53✔
UNCOV
1137
                    attr_line_t al;
×
UNCOV
1138
                    attr_line_builder alb(al);
×
UNCOV
1139
                    alb.append_as_hexdump(
×
UNCOV
1140
                        sbr.to_string_fragment().sub_range(0, 256));
×
UNCOV
1141
                    log_warning("  dump: %s", al.al_string.c_str());
×
1142
                }
1143
            }
1144

1145
            sbr.rtrim(is_line_ending);
16,276✔
1146

1147
            if (li.li_utf8_scan_result.is_valid()
16,276✔
1148
                && li.li_utf8_scan_result.usr_has_ansi)
16,276✔
1149
            {
1150
                sbr.erase_ansi();
105✔
1151
            }
1152

1153
            this->lf_longest_line
1154
                = std::max(this->lf_longest_line,
16,276✔
1155
                           li.li_utf8_scan_result.usr_column_width_guess);
1156
            this->lf_partial_line = li.li_partial;
16,276✔
1157
            sort_needed = this->process_prefix(sbr, li, sbc) || sort_needed;
16,276✔
1158

1159
            if (old_size > this->lf_index.size()) {
16,276✔
UNCOV
1160
                old_size = 0;
×
1161
            }
1162

1163
            // Update this early so that line_length() works
1164
            this->lf_index_size = li.li_file_range.next_offset();
16,276✔
1165

1166
            if (this->lf_logline_observer != nullptr) {
16,276✔
1167
                auto nl_rc = this->lf_logline_observer->logline_new_lines(
31,270✔
1168
                    *this, this->begin() + old_size, this->end(), sbr);
31,270✔
1169
                if (rollback_size > 0 && old_size == rollback_index_start
15,635✔
1170
                    && nl_rc)
437✔
1171
                {
1172
                    log_debug(
3✔
1173
                        "%s: rollbacked line %zu matched filter, forcing "
1174
                        "full sort",
1175
                        this->lf_filename_as_string.c_str(),
1176
                        rollback_index_start);
1177
                    sort_needed = true;
3✔
1178
                }
1179
            }
1180

1181
            if (this->lf_logfile_observer != nullptr) {
16,276✔
1182
                auto indexing_res = this->lf_logfile_observer->logfile_indexing(
15,635✔
1183
                    this,
1184
                    this->lf_line_buffer.get_read_offset(
1185
                        li.li_file_range.next_offset()),
1186
                    st.st_size);
1187

1188
                if (indexing_res == lnav::progress_result_t::interrupt) {
15,635✔
1189
                    break;
×
1190
                }
1191
            }
1192

1193
            if (!has_format && this->lf_format != nullptr) {
16,276✔
1194
                break;
528✔
1195
            }
1196
            if (begin_size == 0 && !has_format
15,748✔
1197
                && li.li_file_range.fr_offset > 16 * 1024)
3,733✔
1198
            {
1199
                break;
1✔
1200
            }
1201
#if 0
1202
            if (this->lf_line_buffer.is_likely_to_flush(prev_range)
1203
                && this->lf_index.size() - begin_size > 1)
1204
            {
1205
                log_debug("likely to flush, breaking");
1206
                break;
1207
            }
1208
#endif
1209
            if (this->lf_format) {
15,747✔
1210
                auto sf = sbr.to_string_fragment();
12,015✔
1211

1212
                for (const auto& td : this->lf_applicable_taggers) {
12,123✔
1213
                    auto curr_ll = this->end() - 1;
108✔
1214

1215
                    if (td->ftd_level != LEVEL_UNKNOWN
108✔
1216
                        && td->ftd_level != curr_ll->get_msg_level())
108✔
1217
                    {
UNCOV
1218
                        continue;
×
1219
                    }
1220

1221
                    if (td->ftd_pattern.pp_value
108✔
1222
                            ->find_in(sf, PCRE2_NO_UTF_CHECK)
216✔
1223
                            .ignore_error()
216✔
1224
                            .has_value())
108✔
1225
                    {
1226
                        while (curr_ll->is_continued()) {
3✔
UNCOV
1227
                            --curr_ll;
×
1228
                        }
1229
                        curr_ll->set_meta_mark(true);
3✔
1230
                        auto line_number = static_cast<uint32_t>(
1231
                            std::distance(this->begin(), curr_ll));
3✔
1232

1233
                        this->lf_bookmark_metadata[line_number].add_tag(
3✔
1234
                            td->ftd_name);
3✔
1235
                    }
1236
                }
1237

1238
                for (const auto& pd : this->lf_applicable_partitioners) {
12,134✔
1239
                    thread_local auto part_md
1240
                        = lnav::pcre2pp::match_data::unitialized();
119✔
1241

1242
                    auto curr_ll = this->end() - 1;
119✔
1243

1244
                    if (pd->fpd_level != LEVEL_UNKNOWN
119✔
1245
                        && pd->fpd_level != curr_ll->get_msg_level())
119✔
1246
                    {
UNCOV
1247
                        continue;
×
1248
                    }
1249

1250
                    auto match_res = pd->fpd_pattern.pp_value->capture_from(sf)
119✔
1251
                                         .into(part_md)
119✔
1252
                                         .matches(PCRE2_NO_UTF_CHECK)
238✔
1253
                                         .ignore_error();
119✔
1254
                    if (match_res) {
119✔
1255
                        while (curr_ll->is_continued()) {
8✔
UNCOV
1256
                            --curr_ll;
×
1257
                        }
1258
                        curr_ll->set_meta_mark(true);
8✔
1259
                        auto line_number = static_cast<uint32_t>(
1260
                            std::distance(this->begin(), curr_ll));
8✔
1261

1262
                        this->lf_bookmark_metadata[line_number].bm_name
8✔
1263
                            = part_md.to_string();
16✔
1264
                    }
1265
                }
1266

1267
                if (!this->back().is_continued()) {
12,015✔
1268
                    lnav::log::watch::eval_with(*this, this->end() - 1);
9,835✔
1269
                }
1270
            }
1271

1272
            if (li.li_partial) {
15,747✔
1273
                // The last read was at the end of the file, so break.  We'll
1274
                // need to cycle back around to pop off this partial line in
1275
                // order to continue reading correctly.
1276
                break;
18✔
1277
            }
1278

1279
            limit -= 1;
15,729✔
1280
        }
17,915✔
1281

1282
        if (this->lf_format == nullptr
1,092✔
1283
            && this->lf_options.loo_visible_size_limit > 0
79✔
UNCOV
1284
            && prev_range.fr_offset > 256 * 1024
×
1285
            && st.st_size >= this->lf_options.loo_visible_size_limit)
1,171✔
1286
        {
UNCOV
1287
            log_info("file has unknown format and is too large: %s",
×
1288
                     this->lf_filename_as_string.c_str());
UNCOV
1289
            this->lf_indexing = false;
×
1290
            auto note_um
UNCOV
1291
                = lnav::console::user_message::warning(
×
1292
                      "skipping indexing for file")
UNCOV
1293
                      .with_reason(
×
1294
                          "file is large and has no discernible log format")
UNCOV
1295
                      .move();
×
UNCOV
1296
            this->lf_notes.writeAccess()->emplace(note_type::indexing_disabled,
×
1297
                                                  note_um);
UNCOV
1298
            if (this->lf_logfile_observer != nullptr) {
×
UNCOV
1299
                this->lf_logfile_observer->logfile_indexing(this, 0, 0);
×
1300
            }
1301
        }
1302

1303
        if (this->lf_logline_observer != nullptr) {
1,092✔
1304
            this->lf_logline_observer->logline_eof(*this);
1,003✔
1305
        }
1306

1307
        if (record_rusage
1,092✔
1308
            && (prev_range.fr_offset - begin_index_size) > (500 * 1024))
425✔
1309
        {
1310
            struct rusage end_rusage;
1311

UNCOV
1312
            getrusage(RUSAGE_SELF, &end_rusage);
×
UNCOV
1313
            rusagesub(end_rusage,
×
1314
                      begin_rusage,
UNCOV
1315
                      this->lf_activity.la_initial_index_rusage);
×
UNCOV
1316
            log_info("Resource usage for initial indexing of file: %s:%d-%d",
×
1317
                     this->lf_filename_as_string.c_str(),
1318
                     begin_size,
1319
                     this->lf_index.size());
UNCOV
1320
            log_rusage(lnav_log_level_t::INFO,
×
1321
                       this->lf_activity.la_initial_index_rusage);
1322
        }
1323

1324
        /*
1325
         * The file can still grow between the above fstat and when we're
1326
         * doing the scanning, so use the line buffer's notion of the file
1327
         * size.
1328
         */
1329
        this->lf_index_size = prev_range.next_offset();
1,092✔
1330
        this->lf_stat = st;
1,092✔
1331

1332
        {
1333
            safe::WriteAccess<logfile::safe_opid_state> writable_opid_map(
1334
                this->lf_opids);
1,092✔
1335

1336
            for (const auto& opid_pair : sbc.sbc_opids.los_opid_ranges) {
4,233✔
1337
                auto opid_iter
1338
                    = writable_opid_map->los_opid_ranges.find(opid_pair.first);
3,141✔
1339

1340
                if (opid_iter == writable_opid_map->los_opid_ranges.end()) {
3,141✔
1341
                    writable_opid_map->los_opid_ranges.emplace(opid_pair);
2,788✔
1342
                } else {
1343
                    opid_iter->second |= opid_pair.second;
353✔
1344
                }
1345
            }
1346
            log_debug(
1,092✔
1347
                "%s: opid_map size: count=%zu; sizeof(otr)=%zu; alloc=%zu",
1348
                this->lf_filename_as_string.c_str(),
1349
                writable_opid_map->los_opid_ranges.size(),
1350
                sizeof(opid_time_range),
1351
                this->lf_allocator.getNumBytesAllocated());
1352
        }
1,092✔
1353

1354
        if (begin_size > this->lf_index.size()) {
1,092✔
UNCOV
1355
            log_info("overwritten file detected, closing -- %s",
×
1356
                     this->lf_filename_as_string.c_str());
UNCOV
1357
            this->close();
×
UNCOV
1358
            return rebuild_result_t::INVALID;
×
1359
        }
1360

1361
        if (sort_needed || begin_size > this->lf_index.size()) {
1,092✔
1362
            retval = rebuild_result_t::NEW_ORDER;
82✔
1363
        } else {
1364
            retval = rebuild_result_t::NEW_LINES;
1,010✔
1365
        }
1366

1367
        {
1368
            auto est_rem = this->estimated_remaining_lines();
1,092✔
1369
            if (est_rem > 0) {
1,092✔
1370
                this->lf_index.reserve(this->lf_index.size() + est_rem);
441✔
1371
            }
1372
        }
1373
    } else {
1,092✔
1374
        this->lf_stat = st;
2,807✔
1375
        if (this->lf_sort_needed) {
2,807✔
1376
            retval = rebuild_result_t::NEW_ORDER;
13✔
1377
            this->lf_sort_needed = false;
13✔
1378
        }
1379
    }
1380

1381
    this->lf_index_time
1382
        = std::chrono::seconds{this->lf_line_buffer.get_file_time()};
3,919✔
1383
    if (this->lf_index_time.count() == 0) {
3,919✔
1384
        this->lf_index_time = std::chrono::seconds{st.st_mtime};
3,898✔
1385
    }
1386

1387
    if (this->lf_out_of_time_order_count) {
3,919✔
1388
        log_info("Detected %d out-of-time-order lines in file: %s",
8✔
1389
                 this->lf_out_of_time_order_count,
1390
                 this->lf_filename_as_string.c_str());
1391
        this->lf_out_of_time_order_count = 0;
8✔
1392
    }
1393

1394
    return retval;
3,919✔
1395
}
1396

1397
Result<shared_buffer_ref, std::string>
1398
logfile::read_line(iterator ll, subline_options opts)
24,602✔
1399
{
1400
    try {
1401
        auto get_range_res = this->get_file_range(ll, false);
24,602✔
1402
        return this->lf_line_buffer.read_range(get_range_res)
49,204✔
1403
            .map([&ll, &get_range_res, &opts, this](auto sbr) {
24,602✔
1404
                sbr.rtrim(is_line_ending);
24,602✔
1405
                if (!get_range_res.fr_metadata.m_valid_utf) {
24,602✔
1406
                    scrub_to_utf8(sbr.get_writable_data(), sbr.length());
6✔
1407
                    sbr.get_metadata().m_valid_utf = true;
6✔
1408
                }
1409

1410
                if (this->lf_format != nullptr) {
24,602✔
1411
                    this->lf_format->get_subline(*ll, sbr, opts);
20,657✔
1412
                }
1413

1414
                return sbr;
24,602✔
1415
            });
24,602✔
UNCOV
1416
    } catch (const line_buffer::error& e) {
×
UNCOV
1417
        return Err(std::string(strerror(e.e_err)));
×
UNCOV
1418
    }
×
1419
}
1420

1421
Result<logfile::read_file_result, std::string>
1422
logfile::read_file(read_format_t format)
102✔
1423
{
1424
    if (this->lf_stat.st_size > line_buffer::MAX_LINE_BUFFER_SIZE) {
102✔
UNCOV
1425
        return Err(std::string("file is too large to read"));
×
1426
    }
1427

1428
    auto retval = read_file_result{};
102✔
1429
    retval.rfr_content.reserve(this->lf_stat.st_size);
102✔
1430

1431
    if (format == read_format_t::with_framing) {
102✔
1432
        retval.rfr_content.append(this->lf_line_buffer.get_piper_header_size(),
87✔
1433
                                  '\x16');
1434
    }
1435
    for (auto iter = this->begin(); iter != this->end(); ++iter) {
5,498✔
1436
        const auto fr = this->get_file_range(iter);
5,396✔
1437
        retval.rfr_range.fr_metadata |= fr.fr_metadata;
5,396✔
1438
        retval.rfr_range.fr_size = fr.next_offset();
5,396✔
1439
        auto sbr = TRY(this->lf_line_buffer.read_range(fr));
5,396✔
1440

1441
        if (format == read_format_t::with_framing
5,396✔
1442
            && this->lf_line_buffer.is_piper())
5,396✔
1443
        {
1444
            retval.rfr_content.append(22, '\x16');
34✔
1445
        }
1446
        retval.rfr_content.append(sbr.get_data(), sbr.length());
5,396✔
1447
        if ((file_ssize_t) retval.rfr_content.size() < this->lf_stat.st_size) {
5,396✔
1448
            retval.rfr_content.push_back('\n');
5,391✔
1449
        }
1450
    }
5,396✔
1451

1452
    return Ok(std::move(retval));
102✔
1453
}
102✔
1454

1455
Result<shared_buffer_ref, std::string>
1456
logfile::read_range(const file_range& fr)
1,592✔
1457
{
1458
    return this->lf_line_buffer.read_range(fr);
1,592✔
1459
}
1460

1461
void
1462
logfile::read_full_message(const_iterator ll,
34,415✔
1463
                           shared_buffer_ref& msg_out,
1464
                           line_buffer::scan_direction dir,
1465
                           read_format_t format)
1466
{
1467
    require(ll->get_sub_offset() == 0);
34,415✔
1468

1469
#if 0
1470
    log_debug(
1471
        "%s: reading msg at %d", this->lf_filename_as_string.c_str(), ll->get_offset());
1472
#endif
1473

1474
    msg_out.disown();
34,415✔
1475
    auto mlr = this->message_byte_length(ll);
34,415✔
1476
    auto range_for_line
1477
        = file_range{ll->get_offset(), mlr.mlr_length, mlr.mlr_metadata};
34,415✔
1478
    try {
1479
        if (range_for_line.fr_size > line_buffer::MAX_LINE_BUFFER_SIZE) {
34,415✔
1480
            range_for_line.fr_size = line_buffer::MAX_LINE_BUFFER_SIZE;
×
1481
        }
1482
        if (format == read_format_t::plain && mlr.mlr_line_count > 1
34,415✔
1483
            && this->lf_line_buffer.is_piper())
68,830✔
1484
        {
1485
            this->lf_plain_msg_shared.invalidate_refs();
5✔
1486
            this->lf_plain_msg_buffer.expand_to(mlr.mlr_length);
5✔
1487
            this->lf_plain_msg_buffer.clear();
5✔
1488
            auto curr_ll = ll;
5✔
1489
            do {
1490
                const auto curr_range = this->get_file_range(curr_ll, false);
15✔
1491
                auto read_result
1492
                    = this->lf_line_buffer.read_range(curr_range, dir);
15✔
1493

1494
                if (curr_ll != ll) {
15✔
1495
                    this->lf_plain_msg_buffer.push_back('\n');
10✔
1496
                }
1497
                if (read_result.isErr()) {
15✔
UNCOV
1498
                    auto errmsg = read_result.unwrapErr();
×
UNCOV
1499
                    log_error("%s:%d:unable to read range %d:%d -- %s",
×
1500
                              this->get_unique_path().c_str(),
1501
                              std::distance(this->cbegin(), ll),
1502
                              range_for_line.fr_offset,
1503
                              range_for_line.fr_size,
1504
                              errmsg.c_str());
UNCOV
1505
                    return;
×
1506
                }
1507

1508
                auto curr_buf = read_result.unwrap();
15✔
1509
                this->lf_plain_msg_buffer.append(curr_buf.to_string_view());
15✔
1510

1511
                ++curr_ll;
15✔
1512
            } while (curr_ll != this->end() && curr_ll->is_continued());
30✔
1513
            msg_out.share(this->lf_plain_msg_shared,
10✔
1514
                          this->lf_plain_msg_buffer.data(),
5✔
1515
                          this->lf_plain_msg_buffer.size());
1516
        } else {
1517
            auto read_result
1518
                = this->lf_line_buffer.read_range(range_for_line, dir);
34,410✔
1519

1520
            if (read_result.isErr()) {
34,410✔
UNCOV
1521
                auto errmsg = read_result.unwrapErr();
×
UNCOV
1522
                log_error("%s:%d:unable to read range %d:%d -- %s",
×
1523
                          this->get_unique_path().c_str(),
1524
                          std::distance(this->cbegin(), ll),
1525
                          range_for_line.fr_offset,
1526
                          range_for_line.fr_size,
1527
                          errmsg.c_str());
UNCOV
1528
                return;
×
1529
            }
1530
            msg_out = read_result.unwrap();
34,410✔
1531
            msg_out.get_metadata() = range_for_line.fr_metadata;
34,410✔
1532
        }
34,410✔
1533
        if (this->lf_format.get() != nullptr) {
34,415✔
1534
            this->lf_format->get_subline(*ll, msg_out, {true});
34,415✔
1535
        }
UNCOV
1536
    } catch (const line_buffer::error& e) {
×
UNCOV
1537
        log_error("failed to read line");
×
UNCOV
1538
    }
×
1539
}
1540

1541
void
1542
logfile::set_logline_observer(logline_observer* llo)
1,632✔
1543
{
1544
    this->lf_logline_observer = llo;
1,632✔
1545
    if (llo != nullptr) {
1,632✔
1546
        this->reobserve_from(this->begin());
1,057✔
1547
    }
1548
}
1,632✔
1549

1550
void
1551
logfile::reobserve_from(iterator iter)
1,166✔
1552
{
1553
    for (; iter != this->end(); ++iter) {
2,156✔
1554
        off_t offset = std::distance(this->begin(), iter);
990✔
1555

1556
        if (iter->get_sub_offset() > 0) {
990✔
1557
            continue;
125✔
1558
        }
1559

1560
        if (this->lf_logfile_observer != nullptr) {
865✔
1561
            auto indexing_res = this->lf_logfile_observer->logfile_indexing(
865✔
1562
                this, offset, this->size());
865✔
1563
            if (indexing_res == lnav::progress_result_t::interrupt) {
865✔
UNCOV
1564
                break;
×
1565
            }
1566
        }
1567

1568
        this->read_line(iter).then([this, iter](auto sbr) {
865✔
1569
            auto iter_end = iter + 1;
865✔
1570

1571
            while (iter_end != this->end() && iter_end->get_sub_offset() != 0) {
990✔
1572
                ++iter_end;
125✔
1573
            }
1574
            this->lf_logline_observer->logline_new_lines(
1,730✔
1575
                *this, iter, iter_end, sbr);
865✔
1576
        });
865✔
1577
    }
1578
    if (this->lf_logfile_observer != nullptr) {
1,166✔
1579
        this->lf_logfile_observer->logfile_indexing(
1,166✔
1580
            this, this->size(), this->size());
1,166✔
1581
        this->lf_logline_observer->logline_eof(*this);
1,166✔
1582
    }
1583
}
1,166✔
1584

1585
std::filesystem::path
1586
logfile::get_path() const
1,813✔
1587
{
1588
    return this->lf_filename;
1,813✔
1589
}
1590

1591
logfile::message_length_result
1592
logfile::message_byte_length(logfile::const_iterator ll, bool include_continues)
64,768✔
1593
{
1594
    auto next_line = ll;
64,768✔
1595
    file_range::metadata meta;
64,768✔
1596
    file_ssize_t retval;
1597
    size_t line_count = 0;
64,768✔
1598

1599
    if (!include_continues && this->lf_next_line_cache) {
64,768✔
1600
        if (ll->get_offset() == (*this->lf_next_line_cache).first) {
22,930✔
1601
            return {
1602
                (file_ssize_t) this->lf_next_line_cache->second,
2,991✔
1603
                1,
1604
                {ll->is_valid_utf(), ll->has_ansi()},
5,982✔
1605
            };
8,973✔
1606
        }
1607
    }
1608

1609
    do {
1610
        line_count += 1;
64,697✔
1611
        meta.m_has_ansi = meta.m_has_ansi || next_line->has_ansi();
64,697✔
1612
        meta.m_valid_utf = meta.m_valid_utf && next_line->is_valid_utf();
64,697✔
1613
        ++next_line;
64,697✔
1614
    } while ((next_line != this->end())
64,697✔
1615
             && ((ll->get_offset() == next_line->get_offset())
123,396✔
1616
                 || (include_continues && next_line->is_continued())));
58,699✔
1617

1618
    if (next_line == this->end()) {
61,777✔
1619
        retval = this->lf_index_size - ll->get_offset();
4,561✔
1620
        if (retval > line_buffer::MAX_LINE_BUFFER_SIZE) {
4,561✔
UNCOV
1621
            retval = line_buffer::MAX_LINE_BUFFER_SIZE;
×
1622
        }
1623
        if (retval > 0 && !this->lf_partial_line) {
4,561✔
1624
            retval -= 1;
4,243✔
1625
        }
1626
    } else {
1627
        retval = next_line->get_offset() - ll->get_offset() - 1;
57,216✔
1628
        if (!include_continues) {
57,216✔
1629
            this->lf_next_line_cache
1630
                = std::make_optional(std::make_pair(ll->get_offset(), retval));
17,843✔
1631
        }
1632
    }
1633

1634
    return {retval, line_count, meta};
61,777✔
1635
}
1636

1637
Result<shared_buffer_ref, std::string>
1638
logfile::read_raw_message(logfile::const_iterator ll)
65✔
1639
{
1640
    require(ll->get_sub_offset() == 0);
65✔
1641

1642
    return this->lf_line_buffer.read_range(this->get_file_range(ll));
65✔
1643
}
1644

1645
intern_string_t
1646
logfile::get_format_name() const
4,088✔
1647
{
1648
    if (this->lf_format) {
4,088✔
1649
        return this->lf_format->get_name();
4,088✔
1650
    }
1651

1652
    return {};
×
1653
}
1654

1655
std::optional<logfile::const_iterator>
1656
logfile::find_from_time(const timeval& tv) const
×
1657
{
1658
    auto retval
UNCOV
1659
        = std::lower_bound(this->lf_index.begin(), this->lf_index.end(), tv);
×
1660
    if (retval == this->lf_index.end()) {
×
1661
        return std::nullopt;
×
1662
    }
1663

UNCOV
1664
    return retval;
×
1665
}
1666

1667
bool
1668
logfile::mark_as_duplicate(const std::string& name)
×
1669
{
1670
    safe::WriteAccess<safe_notes> notes(this->lf_notes);
×
1671

1672
    const auto iter = notes->find(note_type::duplicate);
×
UNCOV
1673
    if (iter != notes->end()) {
×
UNCOV
1674
        return false;
×
1675
    }
1676

UNCOV
1677
    this->lf_indexing = false;
×
1678
    this->lf_options.loo_is_visible = false;
×
1679
    auto note_um
1680
        = lnav::console::user_message::warning("hiding duplicate file")
×
UNCOV
1681
              .with_reason(
×
UNCOV
1682
                  attr_line_t("this file appears to have the same content as ")
×
1683
                      .append(lnav::roles::file(name)))
×
1684
              .move();
×
UNCOV
1685
    notes->emplace(note_type::duplicate, note_um);
×
UNCOV
1686
    return true;
×
1687
}
1688

1689
void
1690
logfile::adjust_content_time(int line, const timeval& tv, bool abs_offset)
21✔
1691
{
1692
    if (this->lf_time_offset == tv) {
21✔
1693
        return;
8✔
1694
    }
1695

1696
    auto old_time = this->lf_time_offset;
13✔
1697

1698
    this->lf_time_offset_line = line;
13✔
1699
    if (abs_offset) {
13✔
1700
        this->lf_time_offset = tv;
9✔
1701
    } else {
1702
        timeradd(&old_time, &tv, &this->lf_time_offset);
4✔
1703
    }
1704
    for (auto& iter : *this) {
85✔
1705
        timeval curr, diff, new_time;
1706

1707
        curr = iter.get_timeval();
72✔
1708
        timersub(&curr, &old_time, &diff);
72✔
1709
        timeradd(&diff, &this->lf_time_offset, &new_time);
72✔
1710
        iter.set_time(new_time);
72✔
1711
    }
1712
    this->lf_sort_needed = true;
13✔
1713
    this->lf_index_generation += 1;
13✔
1714
}
1715

1716
void
1717
logfile::set_filename(const std::string& filename)
132✔
1718
{
1719
    if (this->lf_filename != filename) {
132✔
1720
        this->lf_filename = filename;
100✔
1721
        this->lf_filename_as_string = this->lf_filename.string();
100✔
1722
        std::filesystem::path p(filename);
100✔
1723
        this->lf_basename = p.filename();
100✔
1724
    }
100✔
1725
}
132✔
1726

1727
struct timeval
1728
logfile::original_line_time(logfile::iterator ll)
152✔
1729
{
1730
    if (this->is_time_adjusted()) {
152✔
1731
        struct timeval line_time = ll->get_timeval();
11✔
1732
        struct timeval retval;
1733

1734
        timersub(&line_time, &this->lf_time_offset, &retval);
11✔
1735
        return retval;
11✔
1736
    }
1737

1738
    return ll->get_timeval();
141✔
1739
}
1740

1741
std::optional<logfile::const_iterator>
1742
logfile::line_for_offset(file_off_t off) const
4✔
1743
{
1744
    struct cmper {
1745
        bool operator()(const file_off_t& lhs, const logline& rhs) const
1746
        {
1747
            return lhs < rhs.get_offset();
1748
        }
1749

1750
        bool operator()(const logline& lhs, const file_off_t& rhs) const
16✔
1751
        {
1752
            return lhs.get_offset() < rhs;
16✔
1753
        }
1754
    };
1755

1756
    if (this->lf_index.empty()) {
4✔
UNCOV
1757
        return std::nullopt;
×
1758
    }
1759

1760
    auto iter = std::lower_bound(
4✔
1761
        this->lf_index.begin(), this->lf_index.end(), off, cmper{});
1762
    if (iter == this->lf_index.end()) {
4✔
UNCOV
1763
        if (this->lf_index.back().get_offset() <= off
×
UNCOV
1764
            && off < this->lf_index_size)
×
1765
        {
UNCOV
1766
            return std::make_optional(iter);
×
1767
        }
UNCOV
1768
        return std::nullopt;
×
1769
    }
1770

1771
    if (off < iter->get_offset() && iter != this->lf_index.begin()) {
4✔
1772
        --iter;
4✔
1773
    }
1774

1775
    return std::make_optional(iter);
4✔
1776
}
1777

1778
void
1779
logfile::dump_stats()
862✔
1780
{
1781
    const auto buf_stats = this->lf_line_buffer.consume_stats();
862✔
1782

1783
    if (buf_stats.empty()) {
862✔
1784
        return;
600✔
1785
    }
1786
    log_info("line buffer stats for file: %s",
262✔
1787
             this->lf_filename_as_string.c_str());
1788
    log_info("  file_size=%lld", this->lf_line_buffer.get_file_size());
262✔
1789
    log_info("  buffer_size=%ld", this->lf_line_buffer.get_buffer_size());
262✔
1790
    log_info("  read_hist=[%4lu %4lu %4lu %4lu %4lu %4lu %4lu %4lu %4lu %4lu]",
262✔
1791
             buf_stats.s_hist[0],
1792
             buf_stats.s_hist[1],
1793
             buf_stats.s_hist[2],
1794
             buf_stats.s_hist[3],
1795
             buf_stats.s_hist[4],
1796
             buf_stats.s_hist[5],
1797
             buf_stats.s_hist[6],
1798
             buf_stats.s_hist[7],
1799
             buf_stats.s_hist[8],
1800
             buf_stats.s_hist[9]);
1801
    log_info("  decompressions=%lu", buf_stats.s_decompressions);
262✔
1802
    log_info("  preads=%lu", buf_stats.s_preads);
262✔
1803
    log_info("  requested_preloads=%lu", buf_stats.s_requested_preloads);
262✔
1804
    log_info("  used_preloads=%lu", buf_stats.s_used_preloads);
262✔
1805
}
1806

1807
void
1808
logfile::set_logline_opid(uint32_t line_number, string_fragment opid)
9✔
1809
{
1810
    if (line_number >= this->lf_index.size()) {
9✔
UNCOV
1811
        log_error("invalid line number: %s", line_number);
×
UNCOV
1812
        return;
×
1813
    }
1814

1815
    auto bm_iter = this->lf_bookmark_metadata.find(line_number);
9✔
1816
    if (bm_iter != this->lf_bookmark_metadata.end()) {
9✔
UNCOV
1817
        if (bm_iter->second.bm_opid == opid) {
×
UNCOV
1818
            return;
×
1819
        }
1820
    }
1821

1822
    auto write_opids = this->lf_opids.writeAccess();
9✔
1823

1824
    if (bm_iter != this->lf_bookmark_metadata.end()
9✔
1825
        && !bm_iter->second.bm_opid.empty())
9✔
1826
    {
UNCOV
1827
        auto old_opid_iter = write_opids->los_opid_ranges.find(opid);
×
UNCOV
1828
        if (old_opid_iter != write_opids->los_opid_ranges.end()) {
×
UNCOV
1829
            this->lf_invalidated_opids.insert(old_opid_iter->first);
×
1830
        }
1831
    }
1832

1833
    auto& ll = this->lf_index[line_number];
9✔
1834
    auto log_tv = ll.get_timeval();
9✔
1835
    auto opid_iter = write_opids->insert_op(this->lf_allocator, opid, log_tv);
9✔
1836
    auto& otr = opid_iter->second;
9✔
1837

1838
    otr.otr_level_stats.update_msg_count(ll.get_msg_level());
9✔
1839
    ll.set_opid(opid.hash());
9✔
1840
    this->lf_bookmark_metadata[line_number].bm_opid = opid.to_string();
9✔
1841
}
9✔
1842

1843
void
UNCOV
1844
logfile::clear_logline_opid(uint32_t line_number)
×
1845
{
UNCOV
1846
    if (line_number >= this->lf_index.size()) {
×
UNCOV
1847
        return;
×
1848
    }
1849

UNCOV
1850
    auto iter = this->lf_bookmark_metadata.find(line_number);
×
UNCOV
1851
    if (iter == this->lf_bookmark_metadata.end()) {
×
UNCOV
1852
        return;
×
1853
    }
1854

UNCOV
1855
    if (iter->second.bm_opid.empty()) {
×
UNCOV
1856
        return;
×
1857
    }
1858

UNCOV
1859
    auto& ll = this->lf_index[line_number];
×
UNCOV
1860
    ll.set_opid(0);
×
UNCOV
1861
    auto opid = std::move(iter->second.bm_opid);
×
UNCOV
1862
    auto opid_sf = string_fragment::from_str(opid);
×
1863

UNCOV
1864
    if (iter->second.empty(bookmark_metadata::categories::any)) {
×
UNCOV
1865
        this->lf_bookmark_metadata.erase(iter);
×
1866

UNCOV
1867
        auto writeOpids = this->lf_opids.writeAccess();
×
1868

UNCOV
1869
        auto otr_iter = writeOpids->los_opid_ranges.find(opid_sf);
×
UNCOV
1870
        if (otr_iter == writeOpids->los_opid_ranges.end()) {
×
UNCOV
1871
            return;
×
1872
        }
1873

UNCOV
1874
        if (otr_iter->second.otr_range.tr_begin != ll.get_timeval()
×
UNCOV
1875
            && otr_iter->second.otr_range.tr_end != ll.get_timeval())
×
1876
        {
UNCOV
1877
            otr_iter->second.otr_level_stats.update_msg_count(
×
1878
                ll.get_msg_level(), -1);
UNCOV
1879
            return;
×
1880
        }
1881

UNCOV
1882
        otr_iter->second.clear();
×
UNCOV
1883
        this->lf_invalidated_opids.insert(opid_sf);
×
1884
    }
1885
}
1886

1887
size_t
1888
logfile::estimated_remaining_lines() const
6,039✔
1889
{
1890
    if (this->lf_index.empty() || this->is_compressed()) {
6,039✔
1891
        return 10;
602✔
1892
    }
1893

1894
    const auto bytes_per_line = this->lf_index_size / this->lf_index.size();
5,437✔
1895
    if (this->lf_index_size > this->lf_stat.st_size) {
5,437✔
1896
        return 0;
13✔
1897
    }
1898
    const auto remaining_bytes = this->lf_stat.st_size - this->lf_index_size;
5,424✔
1899

1900
    return remaining_bytes / bytes_per_line;
5,424✔
1901
}
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