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

tstack / lnav / 20281835752-2752

16 Dec 2025 08:31PM UTC coverage: 68.903% (+0.03%) from 68.87%
20281835752-2752

push

github

tstack
[tests] update test data

51677 of 75000 relevant lines covered (68.9%)

434192.37 hits per line

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

72.11
/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 "log_level.hh"
60
#include "piper.header.hh"
61
#include "scn/scan.h"
62
#include "yajlpp/yajlpp_def.hh"
63

64
using namespace std::chrono_literals;
65

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

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

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

77
struct io_looper_tag {};
78

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

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

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

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

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

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

113
        return retval;
2✔
114
    }
115

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

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

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

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

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

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

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

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

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

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

180
    init_stream();
8✔
181

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

373
    return bytes;
13✔
374
}
375

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

380
    ensure(this->invariant());
1,433✔
381
}
1,433✔
382

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

389
    auto empty_fd = auto_fd();
1,433✔
390

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

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

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

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

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

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

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

427
            if (pread(fd, gz_id, sizeof(gz_id), 0) == sizeof(gz_id)) {
1,341✔
428
                auto piper_hdr_opt = lnav::piper::read_header(fd, gz_id);
1,308✔
429

430
                if (piper_hdr_opt) {
1,308✔
431
                    static const intern_string_t SRC
432
                        = intern_string::lookup("piper");
202✔
433

434
                    auto meta_buf = std::move(piper_hdr_opt.value());
120✔
435

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

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

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

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

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

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

507
    ensure(this->invariant());
2,843✔
508
}
2,843✔
509

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

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

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

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

534
    if (this->lb_file_size != -1) {
3,084✔
535
        if (start + (file_off_t) max_length > this->lb_file_size) {
93✔
536
            max_length = (this->lb_file_size - start);
80✔
537
        }
538
    }
539

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

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

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

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

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

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

625
bool
626
line_buffer::load_next_buffer()
714✔
627
{
628
    static auto op = lnav_operation{"load_next_buffer"};
714✔
629
    auto op_guard = lnav_opid_guard::internal(op);
714✔
630

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

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

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

709
            seek_to = start + this->lb_alt_buffer.value().size();
1✔
710
            while (seek_to > 0) {
1✔
711
                int count;
712

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

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

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

776
                default:
×
777
                    throw error(errno);
×
778
            }
779
            break;
×
780

781
        default:
700✔
782
            this->lb_alt_buffer.value().resize_by(rc);
700✔
783
            retval = true;
700✔
784
            break;
700✔
785
    }
786

787
    if (start > this->lb_last_line_offset) {
714✔
788
        const auto* line_start = this->lb_alt_buffer.value().begin();
709✔
789
        if (this->lb_line_metadata && start == 0
60✔
790
            && this->lb_alt_buffer->size() > this->lb_piper_header_size)
769✔
791
        {
792
            line_start += this->lb_piper_header_size;
60✔
793
        }
794

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

808
            line_start = lf;
19,385✔
809
        } while (line_start != nullptr
810
                 && line_start < this->lb_alt_buffer->end());
19,385✔
811
    }
812
    // log_debug("END preload read");
813

814
    return retval;
714✔
815
}
714✔
816

817
bool
818
line_buffer::fill_range(file_off_t start,
3,181✔
819
                        ssize_t max_length,
820
                        scan_direction dir)
821
{
822
    auto retval = false;
3,181✔
823
    auto got_preload = false;
3,181✔
824

825
    require(start >= 0);
3,181✔
826

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

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

922
        // log_debug("fd(%d) doing read", this->lb_fd.get());
923
        /* Make sure there is enough space, then */
924
        this->ensure_available(start, max_length, dir);
2,461✔
925

926
        safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
2,461✔
927

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

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

996
                seek_to = this->lb_file_offset + this->lb_buffer.size();
1✔
997
                while (seek_to > 0) {
1✔
998
                    int count;
999

1000
                    count = BZ2_bzread(
×
1001
                        bz_file,
1002
                        scratch,
1003
                        std::min((size_t) seek_to, sizeof(scratch)));
×
1004
                    seek_to -= count;
×
1005
                }
1006
                rc = BZ2_bzread(bz_file,
1✔
1007
                                this->lb_buffer.end(),
1✔
1008
                                this->lb_buffer.available());
1✔
1009
                this->lb_compressed_offset = 0;
1✔
1010
                BZ2_bzclose(bz_file);
1✔
1011

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

1065
                if (this->lb_compressed) {
771✔
1066
                    /*
1067
                     * For compressed files, increase the buffer size so we
1068
                     * don't have to spend as much time uncompressing the data.
1069
                     */
1070
                    if (this->lb_decompress_extra) {
6✔
1071
                        this->resize_buffer(MAX_COMPRESSED_BUFFER_SIZE);
5✔
1072
                    }
1073
                }
1074
                break;
771✔
1075

1076
            case (ssize_t) -1:
2✔
1077
                switch (errno) {
2✔
1078
#ifdef ENODATA
1079
                    /* Cygwin seems to return this when pread reaches the end of
1080
                     * the */
1081
                    /* file. */
1082
                    case ENODATA:
2✔
1083
#endif
1084
                    case EINTR:
1085
                    case EAGAIN:
1086
                        break;
2✔
1087

1088
                    default:
×
1089
                        throw error(errno);
×
1090
                }
1091
                break;
2✔
1092

1093
            default:
1,688✔
1094
                this->lb_buffer.resize_by(rc);
1,688✔
1095
                retval = true;
1,688✔
1096
                break;
1,688✔
1097
        }
1098

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

1143
    return retval;
3,181✔
1144
}
1145

1146
Result<line_info, std::string>
1147
line_buffer::load_next_line(file_range prev_line)
20,594✔
1148
{
1149
    const char* line_start = nullptr;
20,594✔
1150
    bool done = false;
20,594✔
1151
    line_info retval;
20,594✔
1152

1153
    require(this->lb_fd != -1);
20,594✔
1154

1155
    if (this->lb_line_metadata && prev_line.fr_offset == 0) {
20,594✔
1156
        prev_line.fr_offset = this->lb_piper_header_size;
120✔
1157
    }
1158

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

1192
        /* Find the data in the cache and */
1193
        line_start = this->get_range(offset, retval.li_file_range.fr_size);
20,606✔
1194
        /* ... look for the end-of-line or end-of-file. */
1195
        ssize_t utf8_end = -1;
20,606✔
1196

1197
        if (!retval.li_utf8_scan_result.is_valid()) {
20,606✔
1198
            retval.li_utf8_scan_result = {};
2✔
1199
        }
1200
        auto found_in_cache = false;
20,606✔
1201
        auto has_ansi = false;
20,606✔
1202
        auto valid_utf8 = true;
20,606✔
1203
        auto col_width = size_t{0};
20,606✔
1204
        if (!this->lb_line_starts.empty()) {
20,606✔
1205
            auto buffer_offset = offset - this->lb_file_offset;
5,340✔
1206

1207
            if (this->lb_next_buffer_offset == buffer_offset) {
5,340✔
1208
                require(this->lb_next_line_start_index
5,280✔
1209
                        < this->lb_line_starts.size());
1210
                auto start_iter = this->lb_line_starts.begin()
10,560✔
1211
                    + this->lb_next_line_start_index;
5,280✔
1212
                auto next_line_iter = start_iter + 1;
5,280✔
1213
                if (next_line_iter != this->lb_line_starts.end()) {
5,280✔
1214
                    utf8_end = *next_line_iter - 1 - *start_iter;
5,162✔
1215
                    found_in_cache = true;
5,162✔
1216
                    lf = line_start + utf8_end;
5,162✔
1217
                    has_ansi = this->lb_line_has_ansi
1218
                                   [this->lb_next_line_start_index];
5,162✔
1219
                    valid_utf8
1220
                        = this->lb_line_is_utf[this->lb_next_line_start_index];
5,162✔
1221
                    col_width = this->lb_line_col_widths
5,162✔
1222
                                    [this->lb_next_line_start_index];
5,162✔
1223

1224
                    // log_debug("hit cache");
1225
                    this->lb_next_buffer_offset = *next_line_iter;
5,162✔
1226
                    this->lb_next_line_start_index += 1;
5,162✔
1227
                } else {
1228
                    // log_debug("no next iter");
1229
                }
1230
            } else {
1231
                auto start_iter = std::lower_bound(this->lb_line_starts.begin(),
60✔
1232
                                                   this->lb_line_starts.end(),
1233
                                                   buffer_offset);
1234
                if (start_iter != this->lb_line_starts.end()) {
60✔
1235
                    auto next_line_iter = start_iter + 1;
60✔
1236

1237
                    // log_debug("found offset %d %d", buffer_offset,
1238
                    // *start_iter);
1239
                    if (next_line_iter != this->lb_line_starts.end()) {
60✔
1240
                        auto start_index = std::distance(
37✔
1241
                            this->lb_line_starts.begin(), start_iter);
1242
                        utf8_end = *next_line_iter - 1 - *start_iter;
37✔
1243
                        found_in_cache = true;
37✔
1244
                        lf = line_start + utf8_end;
37✔
1245
                        has_ansi = this->lb_line_has_ansi[start_index];
37✔
1246
                        valid_utf8 = this->lb_line_is_utf[start_index];
37✔
1247
                        col_width = this->lb_line_col_widths[start_index];
37✔
1248

1249
                        this->lb_next_line_start_index = start_index + 1;
37✔
1250
                        this->lb_next_buffer_offset = *next_line_iter;
37✔
1251
                    } else {
1252
                        // log_debug("no next iter");
1253
                    }
1254
                } else {
1255
                    // log_debug("no buffer_offset found");
1256
                }
1257
            }
1258
        }
1259

1260
        if (found_in_cache && valid_utf8) {
20,606✔
1261
            retval.li_utf8_scan_result.usr_has_ansi = has_ansi;
5,146✔
1262
            retval.li_utf8_scan_result.usr_column_width_guess = col_width;
5,146✔
1263
        } else {
1264
            auto frag = string_fragment::from_bytes(
15,460✔
1265
                line_start, retval.li_file_range.fr_size);
15,460✔
1266
            auto scan_res = is_utf8(frag, '\n');
15,460✔
1267
            lf = scan_res.remaining_ptr();
15,460✔
1268
            if (lf != nullptr) {
15,460✔
1269
                lf -= 1;
14,739✔
1270
            }
1271
            retval.li_utf8_scan_result = scan_res;
15,460✔
1272
            if (!scan_res.is_valid()) {
15,460✔
1273
                log_warning("fd(%d): line is not utf8 -- %lld:%d",
84✔
1274
                            this->lb_fd.get(),
1275
                            retval.li_file_range.fr_offset,
1276
                            scan_res.usr_valid_frag.length());
1277
            }
1278
        }
1279

1280
        auto got_new_data = old_retval_size != retval.li_file_range.fr_size;
20,606✔
1281
#if 0
1282
        log_debug("load next loop %p reqsize %d lsize %d",
1283
                  lf,
1284
                  request_size,
1285
                  retval.li_file_range.fr_size);
1286
#endif
1287
        if (lf != nullptr
20,606✔
1288
            || (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE)
721✔
1289
            || (request_size >= MAX_LINE_BUFFER_SIZE)
721✔
1290
            || (!got_new_data
22,014✔
1291
                && (!this->is_pipe() || request_size > DEFAULT_INCREMENT)))
687✔
1292
        {
1293
            if ((lf != nullptr)
20,508✔
1294
                && ((size_t) (lf - line_start) >= MAX_LINE_BUFFER_SIZE - 1))
19,885✔
1295
            {
1296
                lf = nullptr;
×
1297
            }
1298
            if (lf != nullptr) {
20,508✔
1299
                retval.li_partial = false;
19,885✔
1300
                retval.li_file_range.fr_size = lf - line_start;
19,885✔
1301
                // delim
1302
                retval.li_file_range.fr_size += 1;
19,885✔
1303
                if (offset >= this->lb_last_line_offset) {
19,885✔
1304
                    this->lb_last_line_offset
1305
                        = offset + retval.li_file_range.fr_size;
19,095✔
1306
                }
1307
            } else {
1308
                if (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE) {
623✔
1309
                    log_warning("Line exceeded max size: offset=%zd", offset);
×
1310
                    retval.li_file_range.fr_size = MAX_LINE_BUFFER_SIZE - 1;
×
1311
                    retval.li_partial = false;
×
1312
                } else {
1313
                    retval.li_partial = true;
623✔
1314
                }
1315
                this->ensure_available(offset, retval.li_file_range.fr_size);
623✔
1316

1317
                if (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE) {
623✔
1318
                    retval.li_file_range.fr_size = MAX_LINE_BUFFER_SIZE - 1;
×
1319
                }
1320
                if (retval.li_partial) {
623✔
1321
                    /*
1322
                     * Since no delimiter was seen, we need to remember the
1323
                     * offset of the last line in the file so we don't
1324
                     * mistakenly return two partial lines to the caller.
1325
                     *
1326
                     *   1. read_line() - returns partial line
1327
                     *   2. file is written
1328
                     *   3. read_line() - returns the middle of partial line.
1329
                     */
1330
                    this->lb_last_line_offset = offset;
623✔
1331
                } else if (offset >= this->lb_last_line_offset) {
×
1332
                    this->lb_last_line_offset
1333
                        = offset + retval.li_file_range.fr_size;
×
1334
                }
1335
            }
1336

1337
            offset += retval.li_file_range.fr_size;
20,508✔
1338
            done = true;
20,508✔
1339
        } else {
1340
            if (!this->is_pipe() || !this->is_pipe_closed()) {
98✔
1341
                retval.li_partial = true;
35✔
1342
            }
1343
            request_size
1344
                = std::min<ssize_t>(this->lb_buffer.size() + DEFAULT_INCREMENT,
98✔
1345
                                    MAX_LINE_BUFFER_SIZE);
1346
        }
1347

1348
        if (!done
41,212✔
1349
            && !this->fill_range(
20,704✔
1350
                offset,
1351
                std::max(request_size, (ssize_t) this->lb_buffer.available())))
20,704✔
1352
        {
1353
            break;
86✔
1354
        }
1355
    }
1356

1357
    ensure(retval.li_file_range.fr_size <= (ssize_t) this->lb_buffer.size());
20,594✔
1358
    ensure(this->invariant());
20,594✔
1359
#if 0
1360
    log_debug("got line part %d %d",
1361
              retval.li_file_range.fr_offset,
1362
              (int) retval.li_partial);
1363
#endif
1364

1365
    retval.li_file_range.fr_metadata.m_has_ansi
1366
        = retval.li_utf8_scan_result.usr_has_ansi;
20,594✔
1367
    retval.li_file_range.fr_metadata.m_valid_utf
1368
        = retval.li_utf8_scan_result.is_valid();
20,594✔
1369

1370
    if (this->lb_line_metadata) {
20,594✔
1371
        auto sv = std::string_view{
1372
            line_start,
1373
            (size_t) retval.li_file_range.fr_size,
486✔
1374
        };
486✔
1375

1376
        auto scan_res = scn::scan<int64_t, int64_t, char>(sv, "{}.{}:{};");
486✔
1377
        if (scan_res) {
486✔
1378
            auto& [tv_sec, tv_usec, level] = scan_res->values();
405✔
1379
            retval.li_timestamp.tv_sec = tv_sec;
405✔
1380
            retval.li_timestamp.tv_usec = tv_usec;
405✔
1381
            retval.li_timestamp.tv_sec
1382
                = lnav::to_local_time(date::sys_seconds{std::chrono::seconds{
405✔
1383
                                          retval.li_timestamp.tv_sec}})
405✔
1384
                      .time_since_epoch()
405✔
1385
                      .count();
405✔
1386
            retval.li_level = abbrev2level(&level, 1);
405✔
1387
        }
1388
    }
1389

1390
    return Ok(retval);
20,594✔
1391
}
1392

1393
Result<shared_buffer_ref, std::string>
1394
line_buffer::read_range(file_range fr, scan_direction dir)
92,203✔
1395
{
1396
    shared_buffer_ref retval;
92,203✔
1397
    const char* line_start;
1398
    file_ssize_t avail;
1399

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

1416
    if (!(this->in_range(fr.fr_offset)
183,845✔
1417
          && this->in_range(fr.fr_offset + fr.fr_size - 1)))
91,642✔
1418
    {
1419
        if (!this->fill_range(fr.fr_offset, fr.fr_size, dir)) {
623✔
1420
            return Err(std::string("unable to read file"));
×
1421
        }
1422
    }
1423
    line_start = this->get_range(fr.fr_offset, avail);
92,203✔
1424

1425
    if (fr.fr_size > avail) {
92,203✔
1426
        return Err(fmt::format(
×
1427
            FMT_STRING("short-read (need: {}; avail: {})"), fr.fr_size, avail));
×
1428
    }
1429
    if (this->lb_line_metadata) {
92,203✔
1430
        const auto* new_start
1431
            = static_cast<const char*>(memchr(line_start, ';', fr.fr_size));
2,268✔
1432
        if (new_start) {
2,268✔
1433
            auto offset = new_start - line_start + 1;
2,023✔
1434
            line_start += offset;
2,023✔
1435
            fr.fr_size -= offset;
2,023✔
1436
        }
1437
    }
1438
    retval.share(this->lb_share_manager, line_start, fr.fr_size);
92,203✔
1439
    retval.get_metadata() = fr.fr_metadata;
92,203✔
1440

1441
    return Ok(std::move(retval));
92,203✔
1442
}
92,203✔
1443

1444
Result<auto_buffer, std::string>
1445
line_buffer::peek_range(file_range fr,
3✔
1446
                        lnav::enums::bitset<peek_options> options)
1447
{
1448
    static const std::string SHORT_READ_MSG = "short read";
5✔
1449

1450
    require(this->lb_seekable);
3✔
1451

1452
    auto buf = auto_buffer::alloc(fr.fr_size);
3✔
1453

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

1469
        return Ok(std::move(buf));
×
1470
    }
1471

1472
    if (this->lb_compressed) {
3✔
1473
        safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
×
1474

1475
        if (*gi) {
×
1476
            auto rc = gi->read(buf.data(), fr.fr_offset, fr.fr_size);
×
1477

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

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

1519
            seek_to = fr.fr_offset;
×
1520
            while (seek_to > 0) {
×
1521
                int count;
1522

1523
                count = BZ2_bzread(bz_file,
×
1524
                                   scratch,
1525
                                   std::min((size_t) seek_to, sizeof(scratch)));
×
1526
                if (count <= 0) {
×
1527
                    break;
×
1528
                }
1529
                seek_to -= count;
×
1530
            }
1531
            auto rc = BZ2_bzread(bz_file, buf.data(), fr.fr_size);
×
1532
            this->lb_compressed_offset = 0;
×
1533
            BZ2_bzclose(bz_file);
×
1534

1535
            if (rc == -1) {
×
1536
                return Err(lnav::from_errno().message());
×
1537
            }
1538
            if (rc != fr.fr_size) {
×
1539
                if (options.is_set<peek_options::allow_short_read>()) {
×
1540
                    this->lb_file_size = fr.fr_offset + fr.fr_size;
×
1541
                    log_info("fd(%d): set file size to %llu",
×
1542
                             this->lb_fd.get(),
1543
                             this->lb_file_size);
1544
                } else {
1545
                    return Err(SHORT_READ_MSG);
×
1546
                }
1547
            }
1548
            buf.resize(rc);
×
1549
            return Ok(std::move(buf));
×
1550
        }
1551
#else
1552
        assert(!this->lb_bz_file);
1553
#endif
1554
    }
1555

1556
    auto rc = pread(this->lb_fd, buf.data(), fr.fr_size, fr.fr_offset);
3✔
1557
    if (rc == -1) {
3✔
1558
        return Err(lnav::from_errno().message());
×
1559
    }
1560
    if (!options.is_set<peek_options::allow_short_read>() && rc != fr.fr_size) {
3✔
1561
        return Err(SHORT_READ_MSG);
×
1562
    }
1563
    buf.resize(rc);
3✔
1564
    return Ok(std::move(buf));
3✔
1565
}
3✔
1566

1567
file_range
1568
line_buffer::get_available()
641✔
1569
{
1570
    return {
1571
        this->lb_file_offset,
641✔
1572
        static_cast<file_ssize_t>(this->lb_buffer.size()),
1,282✔
1573
    };
641✔
1574
}
1575

1576
line_buffer::gz_indexed::indexDict::indexDict(const z_stream& s,
×
1577
                                              const file_size_t size)
×
1578
{
1579
    assert((s.data_type & GZ_END_OF_BLOCK_MASK));
1580
    assert(!(s.data_type & GZ_END_OF_FILE_MASK));
1581
    assert(size >= s.avail_out + GZ_WINSIZE);
1582
    this->bits = s.data_type & GZ_BORROW_BITS_MASK;
×
1583
    this->in = s.total_in;
×
1584
    this->out = s.total_out;
×
1585
    auto last_byte_in = s.next_in[-1];
×
1586
    this->in_bits = last_byte_in >> (8 - this->bits);
×
1587
    // Copy the last 32k uncompressed data (sliding window) to our
1588
    // index
1589
    memcpy(this->index, s.next_out - GZ_WINSIZE, GZ_WINSIZE);
×
1590
}
1591

1592
int
1593
line_buffer::gz_indexed::indexDict::apply(z_streamp s)
×
1594
{
1595
    s->zalloc = Z_NULL;
×
1596
    s->zfree = Z_NULL;
×
1597
    s->opaque = Z_NULL;
×
1598
    s->avail_in = 0;
×
1599
    s->next_in = Z_NULL;
×
1600
    auto ret = inflateInit2(s, GZ_RAW_MODE);
×
1601
    if (ret != Z_OK) {
×
1602
        return ret;
×
1603
    }
1604
    if (this->bits) {
×
1605
        inflatePrime(s, this->bits, this->in_bits);
×
1606
    }
1607
    s->total_in = this->in;
×
1608
    s->total_out = this->out;
×
1609
    inflateSetDictionary(s, this->index, GZ_WINSIZE);
×
1610
    return ret;
×
1611
}
1612

1613
bool
1614
line_buffer::is_likely_to_flush(file_range prev_line)
×
1615
{
1616
    auto avail = this->get_available();
×
1617

1618
    if (prev_line.fr_offset < avail.fr_offset) {
×
1619
        return true;
×
1620
    }
1621
    auto prev_line_end = prev_line.fr_offset + prev_line.fr_size;
×
1622
    auto avail_end = avail.fr_offset + avail.fr_size;
×
1623
    if (avail_end < prev_line_end) {
×
1624
        return true;
×
1625
    }
1626
    auto remaining = avail_end - prev_line_end;
×
1627
    return remaining < INITIAL_REQUEST_SIZE;
×
1628
}
1629

1630
void
1631
line_buffer::quiesce()
42✔
1632
{
1633
    if (this->lb_loader_future.valid()) {
42✔
1634
        this->lb_loader_future.wait();
×
1635
    }
1636
}
42✔
1637

1638
static std::filesystem::path
1639
line_buffer_cache_path()
615✔
1640
{
1641
    return lnav::paths::workdir() / "buffer-cache";
1,230✔
1642
}
1643

1644
void
1645
line_buffer::enable_cache()
231✔
1646
{
1647
    if (!this->lb_compressed || this->lb_cached_fd) {
231✔
1648
        log_info("%d: skipping cache request (compressed=%d already-cached=%d)",
231✔
1649
                 this->lb_fd.get(),
1650
                 this->lb_compressed,
1651
                 (bool) this->lb_cached_fd);
1652
        return;
231✔
1653
    }
1654

1655
    struct stat st;
1656

1657
    if (fstat(this->lb_fd, &st) == -1) {
×
1658
        log_error("failed to fstat(%d) - %d", this->lb_fd.get(), errno);
×
1659
        return;
×
1660
    }
1661

1662
    auto cached_base_name = hasher()
×
1663
                                .update(st.st_dev)
×
1664
                                .update(st.st_ino)
×
1665
                                .update(st.st_size)
×
1666
                                .to_string();
×
1667
    auto cache_dir = line_buffer_cache_path() / cached_base_name.substr(0, 2);
×
1668

1669
    std::filesystem::create_directories(cache_dir);
×
1670

1671
    auto cached_file_name = fmt::format(FMT_STRING("{}.bin"), cached_base_name);
×
1672
    auto cached_file_path = cache_dir / cached_file_name;
×
1673
    auto cached_done_path
1674
        = cache_dir / fmt::format(FMT_STRING("{}.done"), cached_base_name);
×
1675

1676
    log_info(
×
1677
        "%d:cache file path: %s", this->lb_fd.get(), cached_file_path.c_str());
1678

1679
    auto fl = lnav::filesystem::file_lock(cached_file_path);
×
1680
    auto guard = lnav::filesystem::file_lock::guard(&fl);
×
1681

1682
    if (std::filesystem::exists(cached_done_path)) {
×
1683
        log_info("%d:using existing cache file", this->lb_fd.get());
×
1684
        auto open_res = lnav::filesystem::open_file(cached_file_path, O_RDWR);
×
1685
        if (open_res.isOk()) {
×
1686
            this->lb_cached_fd = open_res.unwrap();
×
1687
            return;
×
1688
        }
1689
        std::filesystem::remove(cached_done_path);
×
1690
    }
1691

1692
    auto create_res = lnav::filesystem::create_file(
1693
        cached_file_path, O_RDWR | O_TRUNC, 0600);
×
1694
    if (create_res.isErr()) {
×
1695
        log_error("failed to create cache file: %s -- %s",
×
1696
                  cached_file_path.c_str(),
1697
                  create_res.unwrapErr().c_str());
1698
        return;
×
1699
    }
1700

1701
    auto write_fd = create_res.unwrap();
×
1702
    auto done = false;
×
1703

1704
    static constexpr ssize_t FILL_LENGTH = 1024 * 1024;
1705
    auto off = file_off_t{0};
×
1706
    while (!done) {
×
1707
        log_debug("%d: caching file content at %lld", this->lb_fd.get(), off);
×
1708
        if (!this->fill_range(off, FILL_LENGTH)) {
×
1709
            log_debug("%d: caching finished", this->lb_fd.get());
×
1710
            done = true;
×
1711
        } else {
1712
            file_ssize_t avail;
1713

1714
            const auto* data = this->get_range(off, avail);
×
1715
            auto rc = write(write_fd, data, avail);
×
1716
            if (rc != avail) {
×
1717
                log_error("%d: short write!", this->lb_fd.get());
×
1718
                return;
×
1719
            }
1720

1721
            off += avail;
×
1722
        }
1723
    }
1724

1725
    lnav::filesystem::create_file(cached_done_path, O_WRONLY, 0600);
×
1726

1727
    this->lb_cached_fd = std::move(write_fd);
×
1728
}
1729

1730
std::future<void>
1731
line_buffer::cleanup_cache()
615✔
1732
{
1733
    return std::async(
1734
        std::launch::async, +[]() {
615✔
1735
            auto now = std::filesystem::file_time_type::clock::now();
615✔
1736
            auto cache_path = line_buffer_cache_path();
615✔
1737
            std::vector<std::filesystem::path> to_remove;
615✔
1738
            std::error_code ec;
615✔
1739

1740
            for (const auto& cache_subdir :
615✔
1741
                 std::filesystem::directory_iterator(cache_path, ec))
615✔
1742
            {
1743
                for (const auto& entry :
×
1744
                     std::filesystem::directory_iterator(cache_subdir, ec))
×
1745
                {
1746
                    auto mtime = std::filesystem::last_write_time(entry.path());
×
1747
                    auto exp_time = mtime + 1h;
×
1748
                    if (now < exp_time) {
×
1749
                        continue;
×
1750
                    }
1751

1752
                    to_remove.emplace_back(entry.path());
×
1753
                }
1754
            }
615✔
1755

1756
            for (auto& entry : to_remove) {
615✔
1757
                log_debug("removing compressed file cache: %s", entry.c_str());
×
1758
                std::filesystem::remove_all(entry, ec);
×
1759
            }
1760
        });
1,845✔
1761
}
1762

1763
void
1764
line_buffer::send_initial_load()
706✔
1765
{
1766
    if (!this->lb_seekable) {
706✔
1767
        log_warning("file is not seekable, not doing preload");
×
1768
        return;
×
1769
    }
1770

1771
    if (this->lb_loader_future.valid()) {
706✔
1772
        log_warning("preload is already active");
×
1773
        return;
×
1774
    }
1775

1776
    log_debug("sending initial load");
706✔
1777
    if (!this->lb_alt_buffer) {
706✔
1778
        // log_debug("allocating new buffer!");
1779
        this->lb_alt_buffer = auto_buffer::alloc(this->lb_buffer.capacity());
706✔
1780
    }
1781
    this->lb_loader_file_offset = 0;
706✔
1782
    auto prom = std::make_shared<std::promise<bool>>();
706✔
1783
    this->lb_loader_future = prom->get_future();
706✔
1784
    this->lb_stats.s_requested_preloads += 1;
706✔
1785
    isc::to<io_looper&, io_looper_tag>().send(
706✔
1786
        [this, prom](auto& ioloop) mutable {
1,412✔
1787
            prom->set_value(this->load_next_buffer());
706✔
1788
        });
706✔
1789
}
706✔
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