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

tstack / lnav / 19674547642-2710

25 Nov 2025 03:17PM UTC coverage: 68.841% (+0.001%) from 68.84%
19674547642-2710

push

github

tstack
[spectro] per-row thresholds

32 of 37 new or added lines in 3 files covered. (86.49%)

378 existing lines in 10 files now uncovered.

51184 of 74351 relevant lines covered (68.84%)

431944.62 hits per line

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

71.35
/src/line_buffer.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 line_buffer.cc
30
 */
31

32
#include <errno.h>
33
#include <fcntl.h>
34
#include <stdio.h>
35
#include <string.h>
36
#include <unistd.h>
37

38
#include "config.h"
39

40
#ifdef HAVE_BZLIB_H
41
#    include <bzlib.h>
42
#endif
43

44
#include <algorithm>
45
#include <set>
46

47
#include "base/auto_mem.hh"
48
#include "base/auto_pid.hh"
49
#include "base/fs_util.hh"
50
#include "base/injector.bind.hh"
51
#include "base/injector.hh"
52
#include "base/is_utf8.hh"
53
#include "base/isc.hh"
54
#include "base/math_util.hh"
55
#include "base/paths.hh"
56
#include "fmtlib/fmt/format.h"
57
#include "hasher.hh"
58
#include "line_buffer.hh"
59
#include "piper.header.hh"
60
#include "scn/scan.h"
61
#include "yajlpp/yajlpp_def.hh"
62

63
using namespace std::chrono_literals;
64

65
static const ssize_t INITIAL_REQUEST_SIZE = 16 * 1024;
66
static const ssize_t DEFAULT_INCREMENT = 128 * 1024;
67
static const ssize_t INITIAL_COMPRESSED_BUFFER_SIZE = 5 * 1024 * 1024;
68
static const ssize_t MAX_COMPRESSED_BUFFER_SIZE = 32 * 1024 * 1024;
69

70
const ssize_t line_buffer::DEFAULT_LINE_BUFFER_SIZE = 256 * 1024;
71
const ssize_t line_buffer::MAX_LINE_BUFFER_SIZE
72
    = 4 * 4 * line_buffer::DEFAULT_LINE_BUFFER_SIZE;
73

74
class io_looper : public isc::service<io_looper> {};
75

76
struct io_looper_tag {};
77

78
static auto bound_io = injector::bind_multiple<isc::service_base>()
79
                           .add_singleton<io_looper, io_looper_tag>();
80

81
namespace injector {
82
template<>
83
void
84
force_linking(io_looper_tag anno)
644✔
85
{
86
}
644✔
87
}  // namespace injector
88

89
/*
90
 * XXX REMOVE ME
91
 *
92
 * The stock bzipped file code does not use pread, so we need to use a lock to
93
 * get exclusive access to the file.  In the future, we should just rewrite
94
 * the bzipped file code to use pread.
95
 */
96
class lock_hack {
97
public:
98
    class guard {
99
    public:
100
        guard() : g_lock(singleton()) { this->g_lock.lock(); }
2✔
101

102
        ~guard() { this->g_lock.unlock(); }
2✔
103

104
    private:
105
        lock_hack& g_lock;
106
    };
107

108
    static lock_hack& singleton()
2✔
109
    {
110
        static lock_hack retval;
2✔
111

112
        return retval;
2✔
113
    }
114

115
    void lock() { lockf(this->lh_fd, F_LOCK, 0); }
2✔
116

117
    void unlock() { lockf(this->lh_fd, F_ULOCK, 0); }
2✔
118

119
private:
120
    lock_hack()
1✔
121
    {
1✔
122
        char lockname[64];
123

124
        snprintf(lockname, sizeof(lockname), "/tmp/lnav.%d.lck", getpid());
1✔
125
        this->lh_fd = open(lockname, O_CREAT | O_RDWR, 0600);
1✔
126
        log_perror(fcntl(this->lh_fd, F_SETFD, FD_CLOEXEC));
1✔
127
        unlink(lockname);
1✔
128
    }
1✔
129

130
    auto_fd lh_fd;
131
};
132
/* XXX END */
133

134
#define Z_BUFSIZE      65536U
135
#define SYNCPOINT_SIZE (1024 * 1024)
136
line_buffer::gz_indexed::gz_indexed()
1,435✔
137
{
138
    if ((this->inbuf = auto_mem<Bytef>::malloc(Z_BUFSIZE)) == nullptr) {
1,435✔
139
        throw std::bad_alloc();
×
140
    }
141
}
1,435✔
142

143
void
144
line_buffer::gz_indexed::close()
1,449✔
145
{
146
    // Release old stream, if we were open
147
    if (*this) {
1,449✔
148
        inflateEnd(&this->strm);
7✔
149
        ::close(this->gz_fd);
7✔
150
        this->syncpoints.clear();
7✔
151
        this->gz_fd = -1;
7✔
152
    }
153
}
1,449✔
154

155
void
156
line_buffer::gz_indexed::init_stream()
25✔
157
{
158
    if (*this) {
25✔
159
        inflateEnd(&this->strm);
18✔
160
    }
161

162
    // initialize inflate struct
163
    int rc = inflateInit2(&this->strm, GZ_HEADER_MODE);
25✔
164
    this->strm.avail_in = 0;
25✔
165
    if (rc != Z_OK) {
25✔
166
        throw(rc);  // FIXME: exception wrapper
×
167
    }
168
}
25✔
169

170
void
171
line_buffer::gz_indexed::continue_stream()
8✔
172
{
173
    // Save our position and output buffer
174
    auto total_in = this->strm.total_in;
8✔
175
    auto total_out = this->strm.total_out;
8✔
176
    auto avail_out = this->strm.avail_out;
8✔
177
    auto next_out = this->strm.next_out;
8✔
178

179
    init_stream();
8✔
180

181
    // Restore position and output buffer
182
    this->strm.total_in = total_in;
8✔
183
    this->strm.total_out = total_out;
8✔
184
    this->strm.avail_out = avail_out;
8✔
185
    this->strm.next_out = next_out;
8✔
186
}
8✔
187

188
void
189
line_buffer::gz_indexed::open(int fd, lnav::gzip::header& hd)
7✔
190
{
191
    this->close();
7✔
192
    this->init_stream();
7✔
193
    this->gz_fd = fd;
7✔
194

195
    unsigned char name[1024];
196
    unsigned char comment[4096];
197

198
    name[0] = '\0';
7✔
199
    comment[0] = '\0';
7✔
200

201
    gz_header gz_hd;
202
    memset(&gz_hd, 0, sizeof(gz_hd));
7✔
203
    gz_hd.name = name;
7✔
204
    gz_hd.name_max = sizeof(name);
7✔
205
    gz_hd.comment = comment;
7✔
206
    gz_hd.comm_max = sizeof(comment);
7✔
207

208
    Bytef inbuf[8192];
209
    Bytef outbuf[8192];
210
    this->strm.next_out = outbuf;
7✔
211
    this->strm.total_out = 0;
7✔
212
    this->strm.avail_out = sizeof(outbuf);
7✔
213
    this->strm.next_in = inbuf;
7✔
214
    this->strm.total_in = 0;
7✔
215

216
    if (inflateGetHeader(&this->strm, &gz_hd) == Z_OK) {
7✔
217
        auto rc = pread(fd, inbuf, sizeof(inbuf), 0);
7✔
218
        if (rc >= 0) {
7✔
219
            this->strm.avail_in = rc;
7✔
220

221
            inflate(&this->strm, Z_BLOCK);
7✔
222
            inflateEnd(&this->strm);
7✔
223
            this->strm.next_out = Z_NULL;
7✔
224
            this->strm.next_in = Z_NULL;
7✔
225
            this->strm.next_in = Z_NULL;
7✔
226
            this->strm.total_in = 0;
7✔
227
            this->strm.avail_in = 0;
7✔
228
            this->init_stream();
7✔
229

230
            switch (gz_hd.done) {
7✔
231
                case 0:
×
232
                    log_debug("%d: no gzip header data", fd);
×
233
                    break;
×
234
                case 1:
7✔
235
                    hd.h_mtime.tv_sec = gz_hd.time;
7✔
236
                    name[sizeof(name) - 1] = '\0';
7✔
237
                    comment[sizeof(comment) - 1] = '\0';
7✔
238
                    hd.h_name = std::string((char*) name);
14✔
239
                    hd.h_comment = std::string((char*) comment);
7✔
240
                    log_info(
7✔
241
                        "%d: read gzip header (mtime=%ld; name='%s'; "
242
                        "comment='%s'; crc=%x)",
243
                        fd,
244
                        hd.h_mtime.tv_sec,
245
                        hd.h_name.c_str(),
246
                        hd.h_comment.c_str(),
247
                        gz_hd.hcrc);
248
                    break;
7✔
249
                default:
×
250
                    log_error("%d: failed to read gzip header data", fd);
×
251
                    break;
×
252
            }
253
        } else {
254
            log_error("%d: failed to read gzip header from file: %s",
×
255
                      fd,
256
                      strerror(errno));
257
        }
258
    } else {
259
        log_error("%d: unable to get gzip header", fd);
×
260
    }
261
}
7✔
262

263
int
264
line_buffer::gz_indexed::stream_data(void* buf, size_t size)
13✔
265
{
266
    this->strm.avail_out = size;
13✔
267
    this->strm.next_out = (unsigned char*) buf;
13✔
268

269
    size_t last = this->syncpoints.empty() ? 0 : this->syncpoints.back().in;
13✔
270
    while (this->strm.avail_out) {
39✔
271
        if (!this->strm.avail_in) {
39✔
272
            int rc = ::pread(
21✔
273
                this->gz_fd, &this->inbuf[0], Z_BUFSIZE, this->strm.total_in);
21✔
274
            if (rc < 0) {
21✔
275
                return rc;
×
276
            }
277
            this->strm.next_in = this->inbuf;
21✔
278
            this->strm.avail_in = rc;
21✔
279
        }
280
        if (this->strm.avail_in) {
39✔
281
            int flush = last > this->strm.total_in ? Z_SYNC_FLUSH : Z_BLOCK;
28✔
282
            auto err = inflate(&this->strm, flush);
28✔
283
            if (err == Z_STREAM_END) {
28✔
284
                // Reached end of stream; re-init for a possible subsequent
285
                // stream
286
                continue_stream();
8✔
287
            } else if (err != Z_OK) {
20✔
288
                log_error(" inflate-error at offset %lu: %d  %s",
2✔
289
                          this->strm.total_in,
290
                          (int) err,
291
                          this->strm.msg ? this->strm.msg : "");
292
                this->parent->lb_decompress_error = fmt::format(
2✔
293
                    FMT_STRING("inflate-error at offset {}: {}  {}"),
4✔
294
                    this->strm.total_in,
2✔
295
                    err,
296
                    this->strm.msg ? this->strm.msg : "");
4✔
297
                break;
2✔
298
            }
299

300
            if (this->strm.total_in >= last + SYNCPOINT_SIZE
26✔
301
                && size > this->strm.avail_out + GZ_WINSIZE
×
302
                && (this->strm.data_type & GZ_END_OF_BLOCK_MASK)
×
303
                && !(this->strm.data_type & GZ_END_OF_FILE_MASK))
×
304
            {
305
                this->syncpoints.emplace_back(this->strm, size);
×
306
                last = this->strm.total_out;
×
307
            }
308
        } else if (this->strm.avail_out) {
11✔
309
            // Processed all the gz file data but didn't fill
310
            // the output buffer.  We're done, even though we
311
            // produced fewer bytes than requested.
312
            break;
11✔
313
        }
314
    }
315
    return size - this->strm.avail_out;
13✔
316
}
317

318
void
319
line_buffer::gz_indexed::seek(off_t offset)
3✔
320
{
321
    if ((size_t) offset == this->strm.total_out) {
3✔
322
        return;
×
323
    }
324

325
    indexDict* dict = nullptr;
3✔
326
    // Find highest syncpoint not past offset
327
    // FIXME: Make this a binary-tree search
328
    for (auto& d : this->syncpoints) {
3✔
329
        if (d.out <= offset) {
×
330
            dict = &d;
×
331
        } else {
332
            break;
×
333
        }
334
    }
335

336
    // Choose highest available syncpoint, or keep current offset if it's ok
337
    if ((size_t) offset < this->strm.total_out
3✔
338
        || (dict && this->strm.total_out < (size_t) dict->out))
×
339
    {
340
        // Release the old z_stream
341
        inflateEnd(&this->strm);
3✔
342
        if (dict) {
3✔
343
            dict->apply(&this->strm);
×
344
        } else {
345
            init_stream();
3✔
346
        }
347
    }
348

349
    // Stream from compressed file until we reach our offset
350
    unsigned char dummy[Z_BUFSIZE];
351
    while ((size_t) offset > this->strm.total_out) {
3✔
352
        size_t to_copy
353
            = std::min(static_cast<size_t>(Z_BUFSIZE),
×
354
                       static_cast<size_t>(offset - this->strm.total_out));
355
        auto bytes = stream_data(dummy, to_copy);
×
356
        if (bytes <= 0) {
×
357
            break;
×
358
        }
359
    }
360
}
361

362
int
363
line_buffer::gz_indexed::read(void* buf, size_t offset, size_t size)
13✔
364
{
365
    if (offset != this->strm.total_out) {
13✔
366
        // log_debug("doing seek!  %zu %lu", offset, this->strm.total_out);
367
        this->seek(offset);
3✔
368
    }
369

370
    int bytes = stream_data(buf, size);
13✔
371

372
    return bytes;
13✔
373
}
374

375
line_buffer::line_buffer()
1,435✔
376
{
377
    this->lb_gz_file.writeAccess()->parent = this;
1,435✔
378

379
    ensure(this->invariant());
1,435✔
380
}
1,435✔
381

382
line_buffer::~line_buffer()
1,435✔
383
{
384
    if (this->lb_loader_future.valid()) {
1,435✔
385
        this->lb_loader_future.wait();
54✔
386
    }
387

388
    auto empty_fd = auto_fd();
1,435✔
389

390
    // Make sure any shared refs take ownership of the data.
391
    this->lb_share_manager.invalidate_refs();
1,435✔
392
    this->set_fd(empty_fd);
1,435✔
393
}
1,435✔
394

395
void
396
line_buffer::set_fd(auto_fd& fd)
2,847✔
397
{
398
    file_off_t newoff = 0;
2,847✔
399

400
    {
401
        safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
2,847✔
402

403
        if (*gi) {
2,847✔
404
            gi->close();
7✔
405
        }
406
    }
2,847✔
407

408
    if (this->lb_bz_file) {
2,847✔
409
        this->lb_bz_file = false;
1✔
410
    }
411

412
    if (fd != -1) {
2,847✔
413
        /* Sync the fd's offset with the object. */
414
        newoff = lseek(fd, 0, SEEK_CUR);
1,412✔
415
        if (newoff == -1) {
1,412✔
416
            if (errno != ESPIPE) {
69✔
417
                throw error(errno);
×
418
            }
419

420
            /* It's a pipe, start with a zero offset. */
421
            newoff = 0;
69✔
422
            this->lb_seekable = false;
69✔
423
        } else {
424
            char gz_id[2 + 1 + 1 + 4];
425

426
            if (pread(fd, gz_id, sizeof(gz_id), 0) == sizeof(gz_id)) {
1,343✔
427
                auto piper_hdr_opt = lnav::piper::read_header(fd, gz_id);
1,310✔
428

429
                if (piper_hdr_opt) {
1,310✔
430
                    static const intern_string_t SRC
431
                        = intern_string::lookup("piper");
210✔
432

433
                    auto meta_buf = std::move(piper_hdr_opt.value());
128✔
434

435
                    auto meta_sf = string_fragment::from_bytes(meta_buf.in(),
128✔
436
                                                               meta_buf.size());
437
                    auto meta_parse_res
438
                        = lnav::piper::header_handlers.parser_for(SRC).of(
256✔
439
                            meta_sf);
128✔
440
                    if (meta_parse_res.isErr()) {
128✔
441
                        log_error("failed to parse piper header: %s",
×
442
                                  meta_parse_res.unwrapErr()[0]
443
                                      .to_attr_line()
444
                                      .get_string()
445
                                      .c_str());
446
                        throw error(EINVAL);
×
447
                    }
448

449
                    this->lb_line_metadata = true;
128✔
450
                    this->lb_file_offset
451
                        = lnav::piper::HEADER_SIZE + meta_buf.size();
128✔
452
                    this->lb_piper_header_size = this->lb_file_offset;
128✔
453
                    this->lb_header = meta_parse_res.unwrap();
128✔
454
                } else if (gz_id[0] == '\037' && gz_id[1] == '\213') {
1,310✔
455
                    int gzfd = dup(fd);
7✔
456

457
                    log_perror(fcntl(gzfd, F_SETFD, FD_CLOEXEC));
7✔
458
                    if (lseek(fd, 0, SEEK_SET) < 0) {
7✔
459
                        close(gzfd);
×
460
                        throw error(errno);
×
461
                    }
462
                    lnav::gzip::header hdr;
7✔
463

464
                    this->lb_gz_file.writeAccess()->open(gzfd, hdr);
7✔
465
                    this->lb_compressed = true;
7✔
466
                    this->lb_file_time = hdr.h_mtime.tv_sec;
7✔
467
                    if (this->lb_file_time < 0) {
7✔
468
                        this->lb_file_time = 0;
×
469
                    }
470
                    this->lb_compressed_offset = 0;
7✔
471
                    if (!hdr.empty()) {
7✔
472
                        this->lb_header = std::move(hdr);
7✔
473
                    }
474
                    if (this->lb_decompress_extra) {
7✔
475
                        this->resize_buffer(INITIAL_COMPRESSED_BUFFER_SIZE);
4✔
476
                    }
477
                }
7✔
478
#ifdef HAVE_BZLIB_H
479
                else if (gz_id[0] == 'B' && gz_id[1] == 'Z')
1,175✔
480
                {
481
                    if (lseek(fd, 0, SEEK_SET) < 0) {
1✔
482
                        throw error(errno);
×
483
                    }
484
                    this->lb_bz_file = true;
1✔
485
                    this->lb_compressed = true;
1✔
486

487
                    /*
488
                     * Loading data from a bzip2 file is pretty slow, so we try
489
                     * to keep as much in memory as possible.
490
                     */
491
                    if (this->lb_decompress_extra) {
1✔
492
                        this->resize_buffer(INITIAL_COMPRESSED_BUFFER_SIZE);
1✔
493
                    }
494

495
                    this->lb_compressed_offset = 0;
1✔
496
                }
497
#endif
498
            }
1,310✔
499
            this->lb_seekable = true;
1,343✔
500
        }
501
    }
502
    this->lb_file_offset = newoff;
2,847✔
503
    this->lb_buffer.clear();
2,847✔
504
    this->lb_fd = std::move(fd);
2,847✔
505

506
    ensure(this->invariant());
2,847✔
507
}
2,847✔
508

509
void
510
line_buffer::resize_buffer(size_t new_max)
9✔
511
{
512
    if (((this->lb_compressed && new_max <= MAX_COMPRESSED_BUFFER_SIZE)
9✔
513
         || (!this->lb_compressed && new_max <= MAX_LINE_BUFFER_SIZE))
×
514
        && !this->lb_buffer.has_capacity_for(new_max))
18✔
515
    {
516
        /* Still need more space, try a realloc. */
517
        this->lb_share_manager.invalidate_refs();
9✔
518
        this->lb_buffer.expand_to(new_max);
9✔
519
    }
520
}
9✔
521

522
void
523
line_buffer::ensure_available(file_off_t start,
3,065✔
524
                              ssize_t max_length,
525
                              scan_direction dir)
526
{
527
    ssize_t prefill;
528

529
    require(this->lb_compressed || max_length <= MAX_LINE_BUFFER_SIZE);
3,065✔
530

531
    // log_debug("ensure avail %d %d", start, max_length);
532

533
    if (this->lb_file_size != -1) {
3,065✔
534
        if (start + (file_off_t) max_length > this->lb_file_size) {
89✔
535
            max_length = (this->lb_file_size - start);
78✔
536
        }
537
    }
538

539
    /*
540
     * Check to see if the start is inside the cached range or immediately
541
     * after.
542
     */
543
    if (start < this->lb_file_offset
6,130✔
544
        || start > (file_off_t) (this->lb_file_offset + this->lb_buffer.size()))
3,065✔
545
    {
546
        /*
547
         * The request is outside the cached range, need to reload the
548
         * whole thing.
549
         */
550
        this->lb_share_manager.invalidate_refs();
1,131✔
551
        prefill = 0;
1,131✔
552
        this->lb_buffer.clear();
1,131✔
553

554
        switch (dir) {
1,131✔
555
            case scan_direction::forward:
1,128✔
556
                break;
1,128✔
557
            case scan_direction::backward: {
3✔
558
                auto padded_max_length = max_length * 4;
3✔
559
                if (this->lb_buffer.has_capacity_for(padded_max_length)) {
3✔
560
                    start = std::max(
6✔
561
                        file_off_t{0},
6✔
562
                        static_cast<file_off_t>(start
3✔
563
                                                - (this->lb_buffer.capacity()
6✔
564
                                                   - padded_max_length)));
3✔
565
                }
566
                break;
3✔
567
            }
568
        }
569

570
        if (this->lb_file_size == (ssize_t) -1) {
1,131✔
571
            this->lb_file_offset = start;
1,131✔
572
        } else {
573
            require(start <= this->lb_file_size);
×
574
            /*
575
             * If the start is near the end of the file, move the offset back a
576
             * bit so we can get more of the file in the cache.
577
             */
578
            if (start + (ssize_t) this->lb_buffer.capacity()
×
579
                > this->lb_file_size)
×
580
            {
581
                this->lb_file_offset = this->lb_file_size
×
582
                    - std::min(this->lb_file_size,
×
583
                               (file_ssize_t) this->lb_buffer.capacity());
×
584
            } else {
585
                this->lb_file_offset = start;
×
586
            }
587
        }
588
    } else {
589
        /* The request is in the cached range.  Record how much extra data is in
590
         * the buffer before the requested range.
591
         */
592
        prefill = start - this->lb_file_offset;
1,934✔
593
    }
594
    require(this->lb_file_offset <= start);
3,065✔
595
    require(prefill <= (ssize_t) this->lb_buffer.size());
3,065✔
596

597
    ssize_t available
598
        = this->lb_buffer.capacity() - (start - this->lb_file_offset);
3,065✔
599
    if (max_length > available) {
3,065✔
600
        // log_debug("need more space!");
601
        /*
602
         * Need more space, move any existing data to the front of the
603
         * buffer.
604
         */
605
        this->lb_share_manager.invalidate_refs();
632✔
606

607
        this->lb_buffer.resize_by(-prefill);
632✔
608
        this->lb_file_offset += prefill;
632✔
609
        // log_debug("adjust file offset for prefill %d", this->lb_file_offset);
610
        memmove(this->lb_buffer.at(0),
1,264✔
611
                this->lb_buffer.at(prefill),
632✔
612
                this->lb_buffer.size());
613

614
        available = this->lb_buffer.capacity() - (start - this->lb_file_offset);
632✔
615
        if (max_length > available) {
632✔
616
            this->resize_buffer(roundup_size(max_length, DEFAULT_INCREMENT));
×
617
        }
618
    }
619
    this->lb_line_starts.clear();
3,065✔
620
    this->lb_line_is_utf.clear();
3,065✔
621
}
3,065✔
622

623
bool
624
line_buffer::load_next_buffer()
715✔
625
{
626
    static auto op = lnav_operation{"load_next_buffer"};
715✔
627
    auto op_guard = lnav_opid_guard::internal(op);
715✔
628

629
    // log_debug("loader here!");
630
    auto retval = false;
715✔
631
    const auto start = this->lb_loader_file_offset.value();
715✔
632
    ssize_t rc = 0;
715✔
633
    safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
715✔
634

635
    log_debug("BEGIN fd(%d) preload read of %zu at %lld",
715✔
636
              this->lb_fd.get(),
637
              this->lb_alt_buffer.value().available(),
638
              start + this->lb_alt_buffer->size());
639
    /* ... read in the new data. */
640
    if (!this->lb_cached_fd && *gi) {
715✔
641
        if (this->lb_file_size != (ssize_t) -1 && this->in_range(start)
×
642
            && this->in_range(this->lb_file_size - 1))
4✔
643
        {
644
            rc = 0;
×
645
        } else {
646
            // log_debug("async decomp start");
647
            rc = gi->read(this->lb_alt_buffer.value().end(),
8✔
648
                          start + this->lb_alt_buffer.value().size(),
4✔
649
                          this->lb_alt_buffer.value().available());
4✔
650
            this->lb_compressed_offset = gi->get_source_offset();
4✔
651
            ensure(this->lb_compressed_offset >= 0);
4✔
652
            if (rc != -1
4✔
653
                && rc < (ssize_t) this->lb_alt_buffer.value().available()
4✔
654
                && (start + (ssize_t) this->lb_alt_buffer.value().size() + rc
12✔
655
                    > this->lb_file_size))
4✔
656
            {
657
                this->lb_file_size
658
                    = (start + this->lb_alt_buffer.value().size() + rc);
4✔
659
                log_info("fd(%d): set file size to %llu",
4✔
660
                         this->lb_fd.get(),
661
                         this->lb_file_size);
662
            }
663
#if 0
664
            log_debug("async decomp end  %d+%d:%d",
665
                      this->lb_alt_buffer->size(),
666
                      rc,
667
                      this->lb_alt_buffer->capacity());
668
#endif
669
        }
670
    }
671
#ifdef HAVE_BZLIB_H
672
    else if (!this->lb_cached_fd && this->lb_bz_file)
711✔
673
    {
674
        if (this->lb_file_size != (ssize_t) -1
2✔
675
            && (((ssize_t) start >= this->lb_file_size)
1✔
676
                || (this->in_range(start)
×
677
                    && this->in_range(this->lb_file_size - 1))))
×
678
        {
679
            rc = 0;
×
680
        } else {
681
            lock_hack::guard guard;
1✔
682
            char scratch[32 * 1024];
683
            BZFILE* bz_file;
684
            file_off_t seek_to;
685
            int bzfd;
686

687
            /*
688
             * Unfortunately, there is no bzseek, so we need to reopen the
689
             * file every time we want to do a read.
690
             */
691
            bzfd = dup(this->lb_fd);
1✔
692
            if (lseek(this->lb_fd, 0, SEEK_SET) < 0) {
1✔
693
                close(bzfd);
×
694
                throw error(errno);
×
695
            }
696
            if ((bz_file = BZ2_bzdopen(bzfd, "r")) == nullptr) {
1✔
697
                close(bzfd);
×
698
                if (errno == 0) {
×
699
                    throw std::bad_alloc();
×
700
                } else {
701
                    throw error(errno);
×
702
                }
703
            }
704

705
            seek_to = start + this->lb_alt_buffer.value().size();
1✔
706
            while (seek_to > 0) {
1✔
707
                int count;
708

709
                count = BZ2_bzread(bz_file,
×
710
                                   scratch,
711
                                   std::min((size_t) seek_to, sizeof(scratch)));
×
712
                if (count <= 0) {
×
713
                    break;
×
714
                }
715
                seek_to -= count;
×
716
            }
717
            rc = BZ2_bzread(bz_file,
1✔
718
                            this->lb_alt_buffer->end(),
1✔
719
                            this->lb_alt_buffer->available());
1✔
720
            this->lb_compressed_offset = 0;
1✔
721
            BZ2_bzclose(bz_file);
1✔
722

723
            if (rc != -1
1✔
724
                && (rc < (ssize_t) (this->lb_alt_buffer.value().available()))
1✔
725
                && (start + (ssize_t) this->lb_alt_buffer.value().size() + rc
3✔
726
                    > this->lb_file_size))
1✔
727
            {
728
                this->lb_file_size
729
                    = (start + this->lb_alt_buffer.value().size() + rc);
1✔
730
                log_info("fd(%d): set file size to %llu",
1✔
731
                         this->lb_fd.get(),
732
                         this->lb_file_size);
733
            }
734
        }
1✔
735
    }
736
#endif
737
    else
738
    {
739
        rc = pread(this->lb_cached_fd ? this->lb_cached_fd.value().get()
1,420✔
740
                                      : this->lb_fd.get(),
710✔
741
                   this->lb_alt_buffer.value().end(),
710✔
742
                   this->lb_alt_buffer.value().available(),
710✔
743
                   start + this->lb_alt_buffer.value().size());
710✔
744
    }
745
    // XXX For some reason, cygwin is giving us a bogus return value when
746
    // up to the end of the file.
747
    if (rc > (ssize_t) this->lb_alt_buffer.value().available()) {
715✔
748
        rc = -1;
×
749
#ifdef ENODATA
750
        errno = ENODATA;
×
751
#else
752
        errno = EAGAIN;
753
#endif
754
    }
755
    switch (rc) {
715✔
756
        case 0:
14✔
757
            if (start < (file_off_t) this->lb_file_size) {
14✔
758
                retval = true;
×
759
            }
760
            break;
14✔
761

762
        case (ssize_t) -1:
×
763
            switch (errno) {
×
764
#ifdef ENODATA
765
                /* Cygwin seems to return this when pread reaches the end of
766
                 * the file. */
767
                case ENODATA:
×
768
#endif
769
                case EINTR:
770
                case EAGAIN:
771
                    break;
×
772

773
                default:
×
774
                    throw error(errno);
×
775
            }
776
            break;
×
777

778
        default:
701✔
779
            this->lb_alt_buffer.value().resize_by(rc);
701✔
780
            retval = true;
701✔
781
            break;
701✔
782
    }
783
    // log_debug("END preload read");
784

785
    if (start > this->lb_last_line_offset) {
715✔
786
        const auto* line_start = this->lb_alt_buffer.value().begin();
710✔
787
        if (this->lb_line_metadata && start == 0
64✔
788
            && this->lb_alt_buffer->size() > this->lb_piper_header_size)
774✔
789
        {
790
            line_start += this->lb_piper_header_size;
64✔
791
        }
792

793
        do {
794
            auto before = line_start - this->lb_alt_buffer->begin();
19,336✔
795
            const auto remaining = this->lb_alt_buffer.value().size() - before;
19,336✔
796
            const auto frag
797
                = string_fragment::from_bytes(line_start, remaining);
19,336✔
798
            auto utf_scan_res = is_utf8(frag, '\n');
19,336✔
799
            const auto* lf = utf_scan_res.remaining_ptr();
19,336✔
800
            this->lb_alt_line_starts.emplace_back(before);
19,336✔
801
            this->lb_alt_line_is_utf.emplace_back(utf_scan_res.is_valid());
19,336✔
802
            this->lb_alt_line_has_ansi.emplace_back(utf_scan_res.usr_has_ansi);
19,336✔
803

804
            line_start = lf;
19,336✔
805
        } while (line_start != nullptr
806
                 && line_start < this->lb_alt_buffer->end());
19,336✔
807
    }
808
    log_debug("END preload read");
715✔
809

810
    return retval;
715✔
811
}
715✔
812

813
bool
814
line_buffer::fill_range(file_off_t start,
3,169✔
815
                        ssize_t max_length,
816
                        scan_direction dir)
817
{
818
    auto retval = false;
3,169✔
819
    auto got_preload = false;
3,169✔
820

821
    require(start >= 0);
3,169✔
822

823
#if 1
824
    log_debug("BEGIN (%d) fill range %lld %zu (%lld) %zd",
3,169✔
825
              this->lb_fd.get(),
826
              start,
827
              max_length,
828
              this->lb_file_offset,
829
              this->lb_buffer.size());
830
#endif
831
    if (!lnav::pid::in_child && this->lb_loader_future.valid()
3,169✔
832
        && start >= this->lb_loader_file_offset.value())
6,338✔
833
    {
834
#if 1
835
        log_debug("fd(%d) getting preload! %d %d",
661✔
836
                  this->lb_fd.get(),
837
                  start,
838
                  this->lb_loader_file_offset.value());
839
#endif
840
        std::optional<std::chrono::system_clock::time_point> wait_start;
661✔
841

842
        if (this->lb_loader_future.wait_for(std::chrono::seconds(0))
661✔
843
            != std::future_status::ready)
661✔
844
        {
845
            wait_start = std::make_optional(std::chrono::system_clock::now());
132✔
846
        }
847
        retval = this->lb_loader_future.get();
661✔
848
        if (false && wait_start) {
849
            auto diff = std::chrono::system_clock::now() - wait_start.value();
850
            log_debug("wait done! %lld", diff.count());
851
        }
852
        // log_debug("got preload");
853
        this->lb_loader_future = {};
661✔
854
        this->lb_share_manager.invalidate_refs();
661✔
855
        this->lb_file_offset = this->lb_loader_file_offset.value();
661✔
856
        this->lb_loader_file_offset = std::nullopt;
661✔
857
        this->lb_buffer.swap(this->lb_alt_buffer.value());
661✔
858
        this->lb_alt_buffer.value().clear();
661✔
859
        this->lb_line_starts = std::move(this->lb_alt_line_starts);
661✔
860
        this->lb_alt_line_starts.clear();
661✔
861
        this->lb_line_is_utf = std::move(this->lb_alt_line_is_utf);
661✔
862
        this->lb_alt_line_is_utf.clear();
661✔
863
        this->lb_line_has_ansi = std::move(this->lb_alt_line_has_ansi);
661✔
864
        this->lb_alt_line_has_ansi.clear();
661✔
865
        this->lb_stats.s_used_preloads += 1;
661✔
866
        this->lb_next_line_start_index = 0;
661✔
867
        this->lb_next_buffer_offset = 0;
661✔
868
        got_preload = true;
661✔
869
    }
870
    if (this->in_range(start)
3,169✔
871
        && (got_preload || max_length == 0
3,198✔
872
            || this->in_range(start + max_length - 1)))
29✔
873
    {
874
        log_debug("fd(%d) cached!", this->lb_fd.get());
721✔
875
        /* Cache already has the data, nothing to do. */
876
        retval = true;
721✔
877
        if (this->lb_do_preloading && !lnav::pid::in_child && this->lb_seekable
721✔
878
            && this->lb_buffer.full() && !this->lb_loader_file_offset)
1,442✔
879
        {
880
            // log_debug("loader available start=%d", start);
881
            auto last_lf_iter = std::find(
882
                this->lb_buffer.rbegin(), this->lb_buffer.rend(), '\n');
3✔
883
            if (last_lf_iter != this->lb_buffer.rend()) {
3✔
884
                auto usable_size
885
                    = std::distance(last_lf_iter, this->lb_buffer.rend());
3✔
886
                // log_debug("found linefeed %d", usable_size);
887
                if (!this->lb_alt_buffer) {
3✔
888
                    // log_debug("allocating new buffer!");
889
                    this->lb_alt_buffer
890
                        = auto_buffer::alloc(this->lb_buffer.capacity());
×
891
                }
892
                this->lb_alt_buffer->resize(this->lb_buffer.size()
3✔
893
                                            - usable_size);
3✔
894
                memcpy(this->lb_alt_buffer.value().begin(),
6✔
895
                       this->lb_buffer.at(usable_size),
3✔
896
                       this->lb_alt_buffer->size());
897
                this->lb_loader_file_offset
898
                    = this->lb_file_offset + usable_size;
3✔
899
#if 0
900
                log_debug("load offset %d",
901
                          this->lb_loader_file_offset.value());
902
                log_debug("launch loader");
903
#endif
904
                auto prom = std::make_shared<std::promise<bool>>();
3✔
905
                this->lb_loader_future = prom->get_future();
3✔
906
                this->lb_stats.s_requested_preloads += 1;
3✔
907
                isc::to<io_looper&, io_looper_tag>().send(
3✔
908
                    [this, prom](auto& ioloop) mutable {
6✔
909
                        prom->set_value(this->load_next_buffer());
3✔
910
                    });
3✔
911
            }
3✔
912
        }
913
    } else if (this->lb_fd != -1) {
2,448✔
914
        ssize_t rc;
915

916
        log_debug("fd(%d) doing read", this->lb_fd.get());
2,448✔
917
        /* Make sure there is enough space, then */
918
        this->ensure_available(start, max_length, dir);
2,448✔
919

920
        safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
2,448✔
921

922
        /* ... read in the new data. */
923
        if (!this->lb_cached_fd && *gi) {
2,448✔
924
            // log_debug("old decomp start");
925
            if (this->lb_file_size != (ssize_t) -1 && this->in_range(start)
6✔
926
                && this->in_range(this->lb_file_size - 1))
15✔
927
            {
928
                rc = 0;
×
929
            } else {
930
                this->lb_stats.s_decompressions += 1;
9✔
931
                if (false && this->lb_last_line_offset > 0) {
932
                    this->lb_stats.s_hist[(this->lb_file_offset * 10)
933
                                          / this->lb_last_line_offset]
934
                        += 1;
935
                }
936
                rc = gi->read(this->lb_buffer.end(),
18✔
937
                              this->lb_file_offset + this->lb_buffer.size(),
9✔
938
                              this->lb_buffer.available());
939
                this->lb_compressed_offset = gi->get_source_offset();
9✔
940
                ensure(this->lb_compressed_offset >= 0);
9✔
941
                if (rc != -1 && (rc < (ssize_t) this->lb_buffer.available())) {
9✔
942
                    this->lb_file_size
943
                        = (this->lb_file_offset + this->lb_buffer.size() + rc);
9✔
944
                    log_info("fd(%d): rc (%zd) -- set file size to %llu",
9✔
945
                             this->lb_fd.get(),
946
                             rc,
947
                             this->lb_file_size);
948
                }
949
            }
950
#if 0
951
            log_debug("old decomp end -- %d+%d:%d",
952
                      this->lb_buffer.size(),
953
                      rc,
954
                      this->lb_buffer.capacity());
955
#endif
956
        }
957
#ifdef HAVE_BZLIB_H
958
        else if (!this->lb_cached_fd && this->lb_bz_file)
2,439✔
959
        {
960
            if (this->lb_file_size != (ssize_t) -1
4✔
961
                && (((ssize_t) start >= this->lb_file_size)
3✔
962
                    || (this->in_range(start)
1✔
963
                        && this->in_range(this->lb_file_size - 1))))
×
964
            {
965
                rc = 0;
1✔
966
            } else {
967
                lock_hack::guard guard;
1✔
968
                char scratch[32 * 1024];
969
                BZFILE* bz_file;
970
                file_off_t seek_to;
971
                int bzfd;
972

973
                /*
974
                 * Unfortunately, there is no bzseek, so we need to reopen the
975
                 * file every time we want to do a read.
976
                 */
977
                bzfd = dup(this->lb_fd);
1✔
978
                if (lseek(this->lb_fd, 0, SEEK_SET) < 0) {
1✔
979
                    close(bzfd);
×
980
                    throw error(errno);
×
981
                }
982
                if ((bz_file = BZ2_bzdopen(bzfd, "r")) == NULL) {
1✔
983
                    close(bzfd);
×
984
                    if (errno == 0) {
×
985
                        throw std::bad_alloc();
×
986
                    } else {
987
                        throw error(errno);
×
988
                    }
989
                }
990

991
                seek_to = this->lb_file_offset + this->lb_buffer.size();
1✔
992
                while (seek_to > 0) {
1✔
993
                    int count;
994

995
                    count = BZ2_bzread(
×
996
                        bz_file,
997
                        scratch,
998
                        std::min((size_t) seek_to, sizeof(scratch)));
×
999
                    seek_to -= count;
×
1000
                }
1001
                rc = BZ2_bzread(bz_file,
1✔
1002
                                this->lb_buffer.end(),
1✔
1003
                                this->lb_buffer.available());
1✔
1004
                this->lb_compressed_offset = 0;
1✔
1005
                BZ2_bzclose(bz_file);
1✔
1006

1007
                if (rc != -1 && (rc < (ssize_t) this->lb_buffer.available())) {
1✔
1008
                    this->lb_file_size
1009
                        = (this->lb_file_offset + this->lb_buffer.size() + rc);
1✔
1010
                    log_info("fd(%d): set file size to %llu",
1✔
1011
                             this->lb_fd.get(),
1012
                             this->lb_file_size);
1013
                }
1014
            }
1✔
1015
        }
1016
#endif
1017
        else if (this->lb_seekable)
2,437✔
1018
        {
1019
            this->lb_stats.s_preads += 1;
2,256✔
1020
            if (false && this->lb_last_line_offset > 0) {
1021
                this->lb_stats.s_hist[(this->lb_file_offset * 10)
1022
                                      / this->lb_last_line_offset]
1023
                    += 1;
1024
            }
1025
#if 0
1026
            log_debug("%d: pread %lld",
1027
                      this->lb_fd.get(),
1028
                      this->lb_file_offset + this->lb_buffer.size());
1029
#endif
1030
            rc = pread(this->lb_cached_fd ? this->lb_cached_fd.value().get()
4,512✔
1031
                                          : this->lb_fd.get(),
2,256✔
1032
                       this->lb_buffer.end(),
2,256✔
1033
                       this->lb_buffer.available(),
1034
                       this->lb_file_offset + this->lb_buffer.size());
2,256✔
1035
            // log_debug("pread rc %d", rc);
1036
        } else {
1037
            rc = read(this->lb_fd,
362✔
1038
                      this->lb_buffer.end(),
181✔
1039
                      this->lb_buffer.available());
1040
        }
1041
        // XXX For some reason, cygwin is giving us a bogus return value when
1042
        // up to the end of the file.
1043
        if (rc > (ssize_t) this->lb_buffer.available()) {
2,448✔
1044
            rc = -1;
×
1045
#ifdef ENODATA
1046
            errno = ENODATA;
×
1047
#else
1048
            errno = EAGAIN;
1049
#endif
1050
        }
1051
        switch (rc) {
2,448✔
1052
            case 0:
765✔
1053
                if (!this->lb_seekable) {
765✔
1054
                    this->lb_file_size
1055
                        = this->lb_file_offset + this->lb_buffer.size();
133✔
1056
                }
1057
                if (start < (file_off_t) this->lb_file_size) {
765✔
1058
                    retval = true;
7✔
1059
                }
1060

1061
                if (this->lb_compressed) {
765✔
1062
                    /*
1063
                     * For compressed files, increase the buffer size so we
1064
                     * don't have to spend as much time uncompressing the data.
1065
                     */
1066
                    if (this->lb_decompress_extra) {
4✔
1067
                        this->resize_buffer(MAX_COMPRESSED_BUFFER_SIZE);
4✔
1068
                    }
1069
                }
1070
                break;
765✔
1071

UNCOV
1072
            case (ssize_t) -1:
×
UNCOV
1073
                switch (errno) {
×
1074
#ifdef ENODATA
1075
                    /* Cygwin seems to return this when pread reaches the end of
1076
                     * the */
1077
                    /* file. */
UNCOV
1078
                    case ENODATA:
×
1079
#endif
1080
                    case EINTR:
1081
                    case EAGAIN:
UNCOV
1082
                        break;
×
1083

1084
                    default:
×
1085
                        throw error(errno);
×
1086
                }
UNCOV
1087
                break;
×
1088

1089
            default:
1,683✔
1090
                this->lb_buffer.resize_by(rc);
1,683✔
1091
                retval = true;
1,683✔
1092
                break;
1,683✔
1093
        }
1094

1095
        if (this->lb_do_preloading && !lnav::pid::in_child && this->lb_seekable
1,643✔
1096
            && this->lb_buffer.full() && !this->lb_loader_file_offset)
4,091✔
1097
        {
1098
            // log_debug("loader available2 start=%d", start);
1099
            auto last_lf_iter = std::find(
1100
                this->lb_buffer.rbegin(), this->lb_buffer.rend(), '\n');
5✔
1101
            if (last_lf_iter != this->lb_buffer.rend()) {
5✔
1102
                auto usable_size
1103
                    = std::distance(last_lf_iter, this->lb_buffer.rend());
5✔
1104
                // log_debug("found linefeed %d", usable_size);
1105
                if (!this->lb_alt_buffer) {
5✔
1106
                    // log_debug("allocating new buffer!");
1107
                    this->lb_alt_buffer
1108
                        = auto_buffer::alloc(this->lb_buffer.capacity());
×
1109
                } else if (this->lb_alt_buffer->capacity()
5✔
1110
                           < this->lb_buffer.capacity())
5✔
1111
                {
1112
                    this->lb_alt_buffer->expand_to(this->lb_buffer.capacity());
×
1113
                }
1114
                this->lb_alt_buffer->resize(this->lb_buffer.size()
5✔
1115
                                            - usable_size);
5✔
1116
                memcpy(this->lb_alt_buffer->begin(),
10✔
1117
                       this->lb_buffer.at(usable_size),
5✔
1118
                       this->lb_alt_buffer->size());
1119
                this->lb_loader_file_offset
1120
                    = this->lb_file_offset + usable_size;
5✔
1121
#if 0
1122
                log_debug("load offset %d",
1123
                          this->lb_loader_file_offset.value());
1124
                log_debug("launch loader");
1125
#endif
1126
                auto prom = std::make_shared<std::promise<bool>>();
5✔
1127
                this->lb_loader_future = prom->get_future();
5✔
1128
                this->lb_stats.s_requested_preloads += 1;
5✔
1129
                isc::to<io_looper&, io_looper_tag>().send(
5✔
1130
                    [this, prom](auto& ioloop) mutable {
10✔
1131
                        prom->set_value(this->load_next_buffer());
5✔
1132
                    });
5✔
1133
            }
5✔
1134
        }
1135
        ensure(this->lb_buffer.size() <= this->lb_buffer.capacity());
2,448✔
1136
    }
2,448✔
1137
    log_debug("END fill_range");
3,169✔
1138

1139
    return retval;
3,169✔
1140
}
1141

1142
Result<line_info, std::string>
1143
line_buffer::load_next_line(file_range prev_line)
20,463✔
1144
{
1145
    const char* line_start = nullptr;
20,463✔
1146
    bool done = false;
20,463✔
1147
    line_info retval;
20,463✔
1148

1149
    require(this->lb_fd != -1);
20,463✔
1150

1151
    if (this->lb_line_metadata && prev_line.fr_offset == 0) {
20,463✔
1152
        prev_line.fr_offset = this->lb_piper_header_size;
128✔
1153
    }
1154

1155
    auto offset = prev_line.next_offset();
20,463✔
1156
    ssize_t request_size = INITIAL_REQUEST_SIZE;
20,463✔
1157
    retval.li_file_range.fr_offset = offset;
20,463✔
1158
    if (this->lb_buffer.empty() || !this->in_range(offset)) {
20,463✔
1159
        this->fill_range(offset, this->lb_buffer.capacity());
2,456✔
1160
    } else if (offset
18,007✔
1161
               == this->lb_file_offset + (ssize_t) this->lb_buffer.size())
18,007✔
1162
    {
1163
        if (!this->fill_range(offset, INITIAL_REQUEST_SIZE)) {
×
1164
            retval.li_file_range.fr_offset = offset;
×
1165
            retval.li_file_range.fr_size = 0;
×
1166
            if (this->is_pipe()) {
×
1167
                retval.li_partial = !this->is_pipe_closed();
×
1168
            } else {
1169
                retval.li_partial = true;
×
1170
            }
1171
            return Ok(retval);
×
1172
        }
1173
    }
1174
    if (prev_line.next_offset() == 0) {
20,463✔
1175
        auto is_utf_res = is_utf8(string_fragment::from_bytes(
3,314✔
1176
            this->lb_buffer.begin(), this->lb_buffer.size()));
1,657✔
1177
        this->lb_is_utf8 = is_utf_res.is_valid();
1,657✔
1178
        if (!this->lb_is_utf8) {
1,657✔
1179
            log_warning("fd(%d): input is not utf8 -- %s",
12✔
1180
                        this->lb_fd.get(),
1181
                        is_utf_res.usr_message);
1182
        }
1183
    }
1184
    while (!done) {
40,851✔
1185
        auto old_retval_size = retval.li_file_range.fr_size;
20,473✔
1186
        const char* lf = nullptr;
20,473✔
1187

1188
        /* Find the data in the cache and */
1189
        line_start = this->get_range(offset, retval.li_file_range.fr_size);
20,473✔
1190
        /* ... look for the end-of-line or end-of-file. */
1191
        ssize_t utf8_end = -1;
20,473✔
1192

1193
        if (!retval.li_utf8_scan_result.is_valid()) {
20,473✔
1194
            retval.li_utf8_scan_result = {};
×
1195
        }
1196
        auto found_in_cache = false;
20,473✔
1197
        auto has_ansi = false;
20,473✔
1198
        auto valid_utf8 = true;
20,473✔
1199
        if (!this->lb_line_starts.empty()) {
20,473✔
1200
            auto buffer_offset = offset - this->lb_file_offset;
5,334✔
1201

1202
            if (this->lb_next_buffer_offset == buffer_offset) {
5,334✔
1203
                require(this->lb_next_line_start_index
5,270✔
1204
                        < this->lb_line_starts.size());
1205
                auto start_iter = this->lb_line_starts.begin()
10,540✔
1206
                    + this->lb_next_line_start_index;
5,270✔
1207
                auto next_line_iter = start_iter + 1;
5,270✔
1208
                if (next_line_iter != this->lb_line_starts.end()) {
5,270✔
1209
                    utf8_end = *next_line_iter - 1 - *start_iter;
5,153✔
1210
                    found_in_cache = true;
5,153✔
1211
                    lf = line_start + utf8_end;
5,153✔
1212
                    has_ansi = this->lb_line_has_ansi
1213
                                   [this->lb_next_line_start_index];
5,153✔
1214
                    valid_utf8
1215
                        = this->lb_line_is_utf[this->lb_next_line_start_index];
5,153✔
1216

1217
                    // log_debug("hit cache");
1218
                    this->lb_next_buffer_offset = *next_line_iter;
5,153✔
1219
                    this->lb_next_line_start_index += 1;
5,153✔
1220
                } else {
1221
                    // log_debug("no next iter");
1222
                }
1223
            } else {
1224
                auto start_iter = std::lower_bound(this->lb_line_starts.begin(),
64✔
1225
                                                   this->lb_line_starts.end(),
1226
                                                   buffer_offset);
1227
                if (start_iter != this->lb_line_starts.end()) {
64✔
1228
                    auto next_line_iter = start_iter + 1;
64✔
1229

1230
                    // log_debug("found offset %d %d", buffer_offset,
1231
                    // *start_iter);
1232
                    if (next_line_iter != this->lb_line_starts.end()) {
64✔
1233
                        auto start_index = std::distance(
37✔
1234
                            this->lb_line_starts.begin(), start_iter);
1235
                        utf8_end = *next_line_iter - 1 - *start_iter;
37✔
1236
                        found_in_cache = true;
37✔
1237
                        lf = line_start + utf8_end;
37✔
1238
                        has_ansi = this->lb_line_has_ansi[start_index];
37✔
1239
                        valid_utf8 = this->lb_line_is_utf[start_index];
37✔
1240

1241
                        this->lb_next_line_start_index = start_index + 1;
37✔
1242
                        this->lb_next_buffer_offset = *next_line_iter;
37✔
1243
                    } else {
1244
                        // log_debug("no next iter");
1245
                    }
1246
                } else {
1247
                    // log_debug("no buffer_offset found");
1248
                }
1249
            }
1250
        }
1251

1252
        if (found_in_cache && valid_utf8) {
20,473✔
1253
            retval.li_utf8_scan_result.usr_has_ansi = has_ansi;
5,138✔
1254
        } else {
1255
            auto frag = string_fragment::from_bytes(
15,335✔
1256
                line_start, retval.li_file_range.fr_size);
15,335✔
1257
            auto scan_res = is_utf8(frag, '\n');
15,335✔
1258
            lf = scan_res.remaining_ptr();
15,335✔
1259
            if (lf != nullptr) {
15,335✔
1260
                lf -= 1;
14,621✔
1261
            }
1262
            retval.li_utf8_scan_result = scan_res;
15,335✔
1263
            if (!scan_res.is_valid()) {
15,335✔
1264
                log_warning("fd(%d): line is not utf8 -- %lld",
60✔
1265
                            this->lb_fd.get(),
1266
                            retval.li_file_range.fr_offset);
1267
            }
1268
        }
1269

1270
        auto got_new_data = old_retval_size != retval.li_file_range.fr_size;
20,473✔
1271
#if 0
1272
        log_debug("load next loop %p reqsize %d lsize %d",
1273
                  lf,
1274
                  request_size,
1275
                  retval.li_file_range.fr_size);
1276
#endif
1277
        if (lf != nullptr
20,473✔
1278
            || (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE)
714✔
1279
            || (request_size >= MAX_LINE_BUFFER_SIZE)
714✔
1280
            || (!got_new_data
21,867✔
1281
                && (!this->is_pipe() || request_size > DEFAULT_INCREMENT)))
680✔
1282
        {
1283
            if ((lf != nullptr)
20,376✔
1284
                && ((size_t) (lf - line_start) >= MAX_LINE_BUFFER_SIZE - 1))
19,759✔
1285
            {
1286
                lf = nullptr;
×
1287
            }
1288
            if (lf != nullptr) {
20,376✔
1289
                retval.li_partial = false;
19,759✔
1290
                retval.li_file_range.fr_size = lf - line_start;
19,759✔
1291
                // delim
1292
                retval.li_file_range.fr_size += 1;
19,759✔
1293
                if (offset >= this->lb_last_line_offset) {
19,759✔
1294
                    this->lb_last_line_offset
1295
                        = offset + retval.li_file_range.fr_size;
18,972✔
1296
                }
1297
            } else {
1298
                if (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE) {
617✔
1299
                    log_warning("Line exceeded max size: offset=%zd", offset);
×
1300
                    retval.li_file_range.fr_size = MAX_LINE_BUFFER_SIZE - 1;
×
1301
                    retval.li_partial = false;
×
1302
                } else {
1303
                    retval.li_partial = true;
617✔
1304
                }
1305
                this->ensure_available(offset, retval.li_file_range.fr_size);
617✔
1306

1307
                if (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE) {
617✔
1308
                    retval.li_file_range.fr_size = MAX_LINE_BUFFER_SIZE - 1;
×
1309
                }
1310
                if (retval.li_partial) {
617✔
1311
                    /*
1312
                     * Since no delimiter was seen, we need to remember the
1313
                     * offset of the last line in the file so we don't
1314
                     * mistakenly return two partial lines to the caller.
1315
                     *
1316
                     *   1. read_line() - returns partial line
1317
                     *   2. file is written
1318
                     *   3. read_line() - returns the middle of partial line.
1319
                     */
1320
                    this->lb_last_line_offset = offset;
617✔
1321
                } else if (offset >= this->lb_last_line_offset) {
×
1322
                    this->lb_last_line_offset
1323
                        = offset + retval.li_file_range.fr_size;
×
1324
                }
1325
            }
1326

1327
            offset += retval.li_file_range.fr_size;
20,376✔
1328

1329
            done = true;
20,376✔
1330
        } else if (!retval.li_utf8_scan_result.is_valid()) {
97✔
1331
            retval.li_partial = true;
2✔
1332
            done = true;
2✔
1333
        } else {
1334
            if (!this->is_pipe() || !this->is_pipe_closed()) {
95✔
1335
                retval.li_partial = true;
32✔
1336
            }
1337
            request_size
1338
                = std::min<ssize_t>(this->lb_buffer.size() + DEFAULT_INCREMENT,
95✔
1339
                                    MAX_LINE_BUFFER_SIZE);
1340
        }
1341

1342
        if (!done
40,946✔
1343
            && !this->fill_range(
20,568✔
1344
                offset,
1345
                std::max(request_size, (ssize_t) this->lb_buffer.available())))
20,568✔
1346
        {
1347
            break;
85✔
1348
        }
1349
    }
1350

1351
    ensure(retval.li_file_range.fr_size <= (ssize_t) this->lb_buffer.size());
20,463✔
1352
    ensure(this->invariant());
20,463✔
1353
#if 0
1354
    log_debug("got line part %d %d",
1355
              retval.li_file_range.fr_offset,
1356
              (int) retval.li_partial);
1357
#endif
1358

1359
    retval.li_file_range.fr_metadata.m_has_ansi
1360
        = retval.li_utf8_scan_result.usr_has_ansi;
20,463✔
1361
    retval.li_file_range.fr_metadata.m_valid_utf
1362
        = retval.li_utf8_scan_result.is_valid();
20,463✔
1363

1364
    if (this->lb_line_metadata) {
20,463✔
1365
        auto sv = std::string_view{
1366
            line_start,
1367
            (size_t) retval.li_file_range.fr_size,
494✔
1368
        };
494✔
1369

1370
        auto scan_res = scn::scan<int64_t, int64_t, char>(sv, "{}.{}:{};");
494✔
1371
        if (scan_res) {
494✔
1372
            auto& [tv_sec, tv_usec, level] = scan_res->values();
413✔
1373
            retval.li_timestamp.tv_sec = tv_sec;
413✔
1374
            retval.li_timestamp.tv_usec = tv_usec;
413✔
1375
            retval.li_timestamp.tv_sec
1376
                = lnav::to_local_time(date::sys_seconds{std::chrono::seconds{
413✔
1377
                                          retval.li_timestamp.tv_sec}})
413✔
1378
                      .time_since_epoch()
413✔
1379
                      .count();
413✔
1380
            retval.li_level = abbrev2level(&level, 1);
413✔
1381
        }
1382
    }
1383

1384
    return Ok(retval);
20,463✔
1385
}
1386

1387
Result<shared_buffer_ref, std::string>
1388
line_buffer::read_range(file_range fr, scan_direction dir)
92,213✔
1389
{
1390
    shared_buffer_ref retval;
92,213✔
1391
    const char* line_start;
1392
    file_ssize_t avail;
1393

1394
#if 0
1395
    if (this->lb_last_line_offset != -1
1396
        && fr.fr_offset > this->lb_last_line_offset)
1397
    {
1398
        /*
1399
         * Don't return anything past the last known line.  The caller needs
1400
         * to try reading at the offset of the last line again.
1401
         */
1402
        return Err(
1403
            fmt::format(FMT_STRING("attempt to read past the known end of the "
1404
                                   "file: read-offset={}; last_line_offset={}"),
1405
                        fr.fr_offset,
1406
                        this->lb_last_line_offset));
1407
    }
1408
#endif
1409

1410
    if (!(this->in_range(fr.fr_offset)
183,870✔
1411
          && this->in_range(fr.fr_offset + fr.fr_size - 1)))
91,657✔
1412
    {
1413
        if (!this->fill_range(fr.fr_offset, fr.fr_size, dir)) {
618✔
1414
            return Err(std::string("unable to read file"));
×
1415
        }
1416
    }
1417
    line_start = this->get_range(fr.fr_offset, avail);
92,213✔
1418

1419
    if (fr.fr_size > avail) {
92,213✔
1420
        return Err(fmt::format(
×
1421
            FMT_STRING("short-read (need: {}; avail: {})"), fr.fr_size, avail));
×
1422
    }
1423
    if (this->lb_line_metadata) {
92,213✔
1424
        const auto* new_start
1425
            = static_cast<const char*>(memchr(line_start, ';', fr.fr_size));
2,303✔
1426
        if (new_start) {
2,303✔
1427
            auto offset = new_start - line_start + 1;
2,058✔
1428
            line_start += offset;
2,058✔
1429
            fr.fr_size -= offset;
2,058✔
1430
        }
1431
    }
1432
    retval.share(this->lb_share_manager, line_start, fr.fr_size);
92,213✔
1433
    retval.get_metadata() = fr.fr_metadata;
92,213✔
1434

1435
    return Ok(std::move(retval));
92,213✔
1436
}
92,213✔
1437

1438
Result<auto_buffer, std::string>
1439
line_buffer::peek_range(file_range fr,
3✔
1440
                        lnav::enums::bitset<peek_options> options)
1441
{
1442
    static const std::string SHORT_READ_MSG = "short read";
5✔
1443

1444
    require(this->lb_seekable);
3✔
1445

1446
    auto buf = auto_buffer::alloc(fr.fr_size);
3✔
1447

1448
    if (this->lb_cached_fd) {
3✔
1449
        auto rc = pread(this->lb_cached_fd.value().get(),
×
1450
                        buf.data(),
×
1451
                        fr.fr_size,
×
1452
                        fr.fr_offset);
1453
        if (rc == -1) {
×
1454
            return Err(lnav::from_errno().message());
×
1455
        }
1456
        if (!options.is_set<peek_options::allow_short_read>()
×
1457
            && rc != fr.fr_size)
×
1458
        {
1459
            return Err(SHORT_READ_MSG);
×
1460
        }
1461
        buf.resize(rc);
×
1462

1463
        return Ok(std::move(buf));
×
1464
    }
1465

1466
    if (this->lb_compressed) {
3✔
1467
        safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
×
1468

1469
        if (*gi) {
×
1470
            auto rc = gi->read(buf.data(), fr.fr_offset, fr.fr_size);
×
1471

1472
            if (rc == -1) {
×
1473
                return Err(lnav::from_errno().message());
×
1474
            }
1475
            if (rc != fr.fr_size) {
×
1476
                this->lb_file_size = gi->strm.total_out;
×
1477
                log_info("fd(%d): set file size to %llu",
×
1478
                         this->lb_fd.get(),
1479
                         this->lb_file_size);
1480
                if (!options.is_set<peek_options::allow_short_read>()) {
×
1481
                    return Err(SHORT_READ_MSG);
×
1482
                }
1483
            }
1484
            buf.resize(rc);
×
1485
            return Ok(std::move(buf));
×
1486
        }
1487
        if (this->lb_bz_file) {
×
1488
            lock_hack::guard guard;
×
1489
            char scratch[32 * 1024];
1490
            BZFILE* bz_file;
1491
            file_off_t seek_to;
1492
            int bzfd;
1493

1494
            /*
1495
             * Unfortunately, there is no bzseek, so we need to reopen the
1496
             * file every time we want to do a read.
1497
             */
1498
            bzfd = dup(this->lb_fd);
×
1499
            if (lseek(this->lb_fd, 0, SEEK_SET) < 0) {
×
1500
                close(bzfd);
×
1501
                throw error(errno);
×
1502
            }
1503
            if ((bz_file = BZ2_bzdopen(bzfd, "r")) == nullptr) {
×
1504
                close(bzfd);
×
1505
                if (errno == 0) {
×
1506
                    throw std::bad_alloc();
×
1507
                } else {
1508
                    throw error(errno);
×
1509
                }
1510
            }
1511

1512
            seek_to = fr.fr_offset;
×
1513
            while (seek_to > 0) {
×
1514
                int count;
1515

1516
                count = BZ2_bzread(bz_file,
×
1517
                                   scratch,
1518
                                   std::min((size_t) seek_to, sizeof(scratch)));
×
1519
                if (count <= 0) {
×
1520
                    break;
×
1521
                }
1522
                seek_to -= count;
×
1523
            }
1524
            auto rc = BZ2_bzread(bz_file, buf.data(), fr.fr_size);
×
1525
            this->lb_compressed_offset = 0;
×
1526
            BZ2_bzclose(bz_file);
×
1527

1528
            if (rc == -1) {
×
1529
                return Err(lnav::from_errno().message());
×
1530
            }
1531
            if (rc != fr.fr_size) {
×
1532
                if (options.is_set<peek_options::allow_short_read>()) {
×
1533
                    this->lb_file_size = fr.fr_offset + fr.fr_size;
×
1534
                    log_info("fd(%d): set file size to %llu",
×
1535
                             this->lb_fd.get(),
1536
                             this->lb_file_size);
1537
                } else {
1538
                    return Err(SHORT_READ_MSG);
×
1539
                }
1540
            }
1541
            buf.resize(rc);
×
1542
            return Ok(std::move(buf));
×
1543
        }
1544
    }
1545

1546
    auto rc = pread(this->lb_fd, buf.data(), fr.fr_size, fr.fr_offset);
3✔
1547
    if (rc == -1) {
3✔
1548
        return Err(lnav::from_errno().message());
×
1549
    }
1550
    if (!options.is_set<peek_options::allow_short_read>() && rc != fr.fr_size) {
3✔
1551
        return Err(SHORT_READ_MSG);
×
1552
    }
1553
    buf.resize(rc);
3✔
1554
    return Ok(std::move(buf));
3✔
1555
}
3✔
1556

1557
file_range
1558
line_buffer::get_available()
642✔
1559
{
1560
    return {
1561
        this->lb_file_offset,
642✔
1562
        static_cast<file_ssize_t>(this->lb_buffer.size()),
1,284✔
1563
    };
642✔
1564
}
1565

1566
line_buffer::gz_indexed::indexDict::indexDict(const z_stream& s,
×
1567
                                              const file_size_t size)
×
1568
{
1569
    assert((s.data_type & GZ_END_OF_BLOCK_MASK));
1570
    assert(!(s.data_type & GZ_END_OF_FILE_MASK));
1571
    assert(size >= s.avail_out + GZ_WINSIZE);
1572
    this->bits = s.data_type & GZ_BORROW_BITS_MASK;
×
1573
    this->in = s.total_in;
×
1574
    this->out = s.total_out;
×
1575
    auto last_byte_in = s.next_in[-1];
×
1576
    this->in_bits = last_byte_in >> (8 - this->bits);
×
1577
    // Copy the last 32k uncompressed data (sliding window) to our
1578
    // index
1579
    memcpy(this->index, s.next_out - GZ_WINSIZE, GZ_WINSIZE);
×
1580
}
1581

1582
int
1583
line_buffer::gz_indexed::indexDict::apply(z_streamp s)
×
1584
{
1585
    s->zalloc = Z_NULL;
×
1586
    s->zfree = Z_NULL;
×
1587
    s->opaque = Z_NULL;
×
1588
    s->avail_in = 0;
×
1589
    s->next_in = Z_NULL;
×
1590
    auto ret = inflateInit2(s, GZ_RAW_MODE);
×
1591
    if (ret != Z_OK) {
×
1592
        return ret;
×
1593
    }
1594
    if (this->bits) {
×
1595
        inflatePrime(s, this->bits, this->in_bits);
×
1596
    }
1597
    s->total_in = this->in;
×
1598
    s->total_out = this->out;
×
1599
    inflateSetDictionary(s, this->index, GZ_WINSIZE);
×
1600
    return ret;
×
1601
}
1602

1603
bool
1604
line_buffer::is_likely_to_flush(file_range prev_line)
×
1605
{
1606
    auto avail = this->get_available();
×
1607

1608
    if (prev_line.fr_offset < avail.fr_offset) {
×
1609
        return true;
×
1610
    }
1611
    auto prev_line_end = prev_line.fr_offset + prev_line.fr_size;
×
1612
    auto avail_end = avail.fr_offset + avail.fr_size;
×
1613
    if (avail_end < prev_line_end) {
×
1614
        return true;
×
1615
    }
1616
    auto remaining = avail_end - prev_line_end;
×
1617
    return remaining < INITIAL_REQUEST_SIZE;
×
1618
}
1619

1620
void
1621
line_buffer::quiesce()
42✔
1622
{
1623
    if (this->lb_loader_future.valid()) {
42✔
1624
        this->lb_loader_future.wait();
×
1625
    }
1626
}
42✔
1627

1628
static std::filesystem::path
1629
line_buffer_cache_path()
612✔
1630
{
1631
    return lnav::paths::workdir() / "buffer-cache";
1,224✔
1632
}
1633

1634
void
1635
line_buffer::enable_cache()
230✔
1636
{
1637
    if (!this->lb_compressed || this->lb_cached_fd) {
230✔
1638
        log_info("%d: skipping cache request (compressed=%d already-cached=%d)",
230✔
1639
                 this->lb_fd.get(),
1640
                 this->lb_compressed,
1641
                 (bool) this->lb_cached_fd);
1642
        return;
230✔
1643
    }
1644

1645
    struct stat st;
1646

1647
    if (fstat(this->lb_fd, &st) == -1) {
×
1648
        log_error("failed to fstat(%d) - %d", this->lb_fd.get(), errno);
×
1649
        return;
×
1650
    }
1651

1652
    auto cached_base_name = hasher()
×
1653
                                .update(st.st_dev)
×
1654
                                .update(st.st_ino)
×
1655
                                .update(st.st_size)
×
1656
                                .to_string();
×
1657
    auto cache_dir = line_buffer_cache_path() / cached_base_name.substr(0, 2);
×
1658

1659
    std::filesystem::create_directories(cache_dir);
×
1660

1661
    auto cached_file_name = fmt::format(FMT_STRING("{}.bin"), cached_base_name);
×
1662
    auto cached_file_path = cache_dir / cached_file_name;
×
1663
    auto cached_done_path
1664
        = cache_dir / fmt::format(FMT_STRING("{}.done"), cached_base_name);
×
1665

1666
    log_info(
×
1667
        "%d:cache file path: %s", this->lb_fd.get(), cached_file_path.c_str());
1668

1669
    auto fl = lnav::filesystem::file_lock(cached_file_path);
×
1670
    auto guard = lnav::filesystem::file_lock::guard(&fl);
×
1671

1672
    if (std::filesystem::exists(cached_done_path)) {
×
1673
        log_info("%d:using existing cache file", this->lb_fd.get());
×
1674
        auto open_res = lnav::filesystem::open_file(cached_file_path, O_RDWR);
×
1675
        if (open_res.isOk()) {
×
1676
            this->lb_cached_fd = open_res.unwrap();
×
1677
            return;
×
1678
        }
1679
        std::filesystem::remove(cached_done_path);
×
1680
    }
1681

1682
    auto create_res = lnav::filesystem::create_file(
1683
        cached_file_path, O_RDWR | O_TRUNC, 0600);
×
1684
    if (create_res.isErr()) {
×
1685
        log_error("failed to create cache file: %s -- %s",
×
1686
                  cached_file_path.c_str(),
1687
                  create_res.unwrapErr().c_str());
1688
        return;
×
1689
    }
1690

1691
    auto write_fd = create_res.unwrap();
×
1692
    auto done = false;
×
1693

1694
    static constexpr ssize_t FILL_LENGTH = 1024 * 1024;
1695
    auto off = file_off_t{0};
×
1696
    while (!done) {
×
1697
        log_debug("%d: caching file content at %lld", this->lb_fd.get(), off);
×
1698
        if (!this->fill_range(off, FILL_LENGTH)) {
×
1699
            log_debug("%d: caching finished", this->lb_fd.get());
×
1700
            done = true;
×
1701
        } else {
1702
            file_ssize_t avail;
1703

1704
            const auto* data = this->get_range(off, avail);
×
1705
            auto rc = write(write_fd, data, avail);
×
1706
            if (rc != avail) {
×
1707
                log_error("%d: short write!", this->lb_fd.get());
×
1708
                return;
×
1709
            }
1710

1711
            off += avail;
×
1712
        }
1713
    }
1714

1715
    lnav::filesystem::create_file(cached_done_path, O_WRONLY, 0600);
×
1716

1717
    this->lb_cached_fd = std::move(write_fd);
×
1718
}
1719

1720
std::future<void>
1721
line_buffer::cleanup_cache()
612✔
1722
{
1723
    return std::async(
1724
        std::launch::async, +[]() {
612✔
1725
            auto now = std::filesystem::file_time_type::clock::now();
612✔
1726
            auto cache_path = line_buffer_cache_path();
612✔
1727
            std::vector<std::filesystem::path> to_remove;
612✔
1728
            std::error_code ec;
612✔
1729

1730
            for (const auto& cache_subdir :
612✔
1731
                 std::filesystem::directory_iterator(cache_path, ec))
612✔
1732
            {
1733
                for (const auto& entry :
×
1734
                     std::filesystem::directory_iterator(cache_subdir, ec))
×
1735
                {
1736
                    auto mtime = std::filesystem::last_write_time(entry.path());
×
1737
                    auto exp_time = mtime + 1h;
×
1738
                    if (now < exp_time) {
×
1739
                        continue;
×
1740
                    }
1741

1742
                    to_remove.emplace_back(entry.path());
×
1743
                }
1744
            }
612✔
1745

1746
            for (auto& entry : to_remove) {
612✔
1747
                log_debug("removing compressed file cache: %s", entry.c_str());
×
1748
                std::filesystem::remove_all(entry, ec);
×
1749
            }
1750
        });
1,836✔
1751
}
1752

1753
void
1754
line_buffer::send_initial_load()
707✔
1755
{
1756
    if (!this->lb_seekable) {
707✔
1757
        log_warning("file is not seekable, not doing preload");
×
1758
        return;
×
1759
    }
1760

1761
    if (this->lb_loader_future.valid()) {
707✔
1762
        log_warning("preload is already active");
×
1763
        return;
×
1764
    }
1765

1766
    log_debug("sending initial load");
707✔
1767
    if (!this->lb_alt_buffer) {
707✔
1768
        // log_debug("allocating new buffer!");
1769
        this->lb_alt_buffer = auto_buffer::alloc(this->lb_buffer.capacity());
707✔
1770
    }
1771
    this->lb_loader_file_offset = 0;
707✔
1772
    auto prom = std::make_shared<std::promise<bool>>();
707✔
1773
    this->lb_loader_future = prom->get_future();
707✔
1774
    this->lb_stats.s_requested_preloads += 1;
707✔
1775
    isc::to<io_looper&, io_looper_tag>().send(
707✔
1776
        [this, prom](auto& ioloop) mutable {
1,414✔
1777
            prom->set_value(this->load_next_buffer());
707✔
1778
        });
707✔
1779
}
707✔
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