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

tstack / lnav / 25348852825-3024

04 May 2026 11:18PM UTC coverage: 69.963% (+0.7%) from 69.226%
25348852825-3024

push

github

tstack
[ui] horizontal scroll should work on columns

Related to #1685

7 of 141 new or added lines in 5 files covered. (4.96%)

7760 existing lines in 84 files now uncovered.

57014 of 81492 relevant lines covered (69.96%)

622491.44 hits per line

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

68.83
/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)
804✔
86
{
87
}
804✔
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,833✔
138
{
139
    if ((this->inbuf = auto_mem<Bytef>::malloc(Z_BUFSIZE)) == nullptr) {
1,833✔
140
        throw std::bad_alloc();
×
141
    }
142
}
1,833✔
143

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

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

163
    // initialize inflate struct
164
    int rc = inflateInit2(&this->strm, GZ_HEADER_MODE);
29✔
165
    this->strm.avail_in = 0;
29✔
166
    if (rc != Z_OK) {
29✔
167
        throw(rc);  // FIXME: exception wrapper
×
168
    }
169
}
29✔
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)
9✔
191
{
192
    this->close();
9✔
193
    this->init_stream();
9✔
194
    this->gz_fd = fd;
9✔
195

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

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

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

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

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

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

231
            switch (gz_hd.done) {
9✔
232
                case 0:
×
233
                    log_debug("%d: no gzip header data", fd);
×
234
                    break;
×
235
                case 1:
9✔
236
                    hd.h_mtime.tv_sec = gz_hd.time;
9✔
237
                    name[sizeof(name) - 1] = '\0';
9✔
238
                    comment[sizeof(comment) - 1] = '\0';
9✔
239
                    hd.h_name = std::string((char*) name);
18✔
240
                    hd.h_comment = std::string((char*) comment);
9✔
241
                    log_info(
9✔
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;
9✔
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
}
9✔
263

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

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

301
            if (this->strm.total_in >= last + SYNCPOINT_SIZE
28✔
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;
15✔
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)
15✔
365
{
366
    if (offset != this->strm.total_out) {
15✔
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);
15✔
372

373
    return bytes;
15✔
374
}
375

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

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

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

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

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

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

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

404
        if (*gi) {
3,631✔
405
            gi->close();
9✔
406
        }
407
    }
3,631✔
408

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

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

421
            /* It's a pipe, start with a zero offset. */
422
            newoff = 0;
88✔
423
            this->lb_seekable = false;
88✔
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,710✔
428
                auto piper_hdr_opt = lnav::piper::read_header(fd, gz_id);
1,677✔
429

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

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

436
                    auto meta_sf = string_fragment::from_bytes(meta_buf.in(),
124✔
437
                                                               meta_buf.size());
438
                    auto meta_parse_res
439
                        = lnav::piper::header_handlers.parser_for(SRC).of(
248✔
440
                            meta_sf);
124✔
441
                    if (meta_parse_res.isErr()) {
124✔
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;
124✔
451
                    this->lb_file_offset
452
                        = lnav::piper::HEADER_SIZE + meta_buf.size();
124✔
453
                    this->lb_piper_header_size = this->lb_file_offset;
124✔
454
                    this->lb_header = meta_parse_res.unwrap();
124✔
455
                } else if (gz_id[0] == '\037' && gz_id[1] == '\213') {
1,677✔
456
                    int gzfd = dup(fd);
9✔
457

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

465
                    this->lb_gz_file.writeAccess()->open(gzfd, hdr);
9✔
466
                    this->lb_compressed = true;
9✔
467
                    this->lb_file_time = hdr.h_mtime.tv_sec;
9✔
468
                    if (this->lb_file_time < 0) {
9✔
469
                        this->lb_file_time = 0;
×
470
                    }
471
                    this->lb_compressed_offset = 0;
9✔
472
                    if (!hdr.empty()) {
9✔
473
                        this->lb_header = std::move(hdr);
9✔
474
                    }
475
                    if (this->lb_decompress_extra) {
9✔
476
                        this->resize_buffer(INITIAL_COMPRESSED_BUFFER_SIZE);
5✔
477
                    }
478
                }
9✔
479
#ifdef HAVE_BZLIB_H
480
                else if (gz_id[0] == 'B' && gz_id[1] == 'Z')
1,544✔
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,677✔
500
            this->lb_seekable = true;
1,710✔
501
        }
502
    }
503
    this->lb_file_offset = newoff;
3,631✔
504
    this->lb_buffer.clear();
3,631✔
505
    this->lb_fd = std::move(fd);
3,631✔
506

507
    ensure(this->invariant());
3,631✔
508
}
3,631✔
509

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

523
void
524
line_buffer::ensure_available(file_off_t start,
4,905✔
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);
4,905✔
531

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

534
    if (this->lb_file_size != -1) {
4,905✔
535
        if (start + (file_off_t) max_length > this->lb_file_size) {
119✔
536
            max_length = (this->lb_file_size - start);
103✔
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
9,810✔
545
        || start > (file_off_t) (this->lb_file_offset + this->lb_buffer.size()))
4,905✔
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,496✔
552
        prefill = 0;
1,496✔
553
        this->lb_buffer.clear();
1,496✔
554

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

571
        if (this->lb_file_size == (ssize_t) -1) {
1,496✔
572
            this->lb_file_offset = start;
1,496✔
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;
3,409✔
594
    }
595
    require(this->lb_file_offset <= start);
4,905✔
596
    require(prefill <= (ssize_t) this->lb_buffer.size());
4,905✔
597

598
    ssize_t available
599
        = this->lb_buffer.capacity() - (start - this->lb_file_offset);
4,905✔
600
    if (max_length > available) {
4,905✔
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();
896✔
607

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

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

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

631
    // log_debug("loader here!");
632
    auto retval = false;
894✔
633
    const auto start = this->lb_loader_file_offset.value();
894✔
634
    ssize_t rc = 0;
894✔
635
    safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
894✔
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) {
894✔
645
        if (this->lb_file_size != (ssize_t) -1 && this->in_range(start)
×
646
            && this->in_range(this->lb_file_size - 1))
5✔
647
        {
648
            rc = 0;
×
649
        } else {
650
            // log_debug("async decomp start");
651
            rc = gi->read(this->lb_alt_buffer.value().end(),
10✔
652
                          start + this->lb_alt_buffer.value().size(),
5✔
653
                          this->lb_alt_buffer.value().available());
5✔
654
            this->lb_compressed_offset = gi->get_source_offset();
5✔
655
            ensure(this->lb_compressed_offset >= 0);
5✔
656
            if (rc != -1
5✔
657
                && rc < (ssize_t) this->lb_alt_buffer.value().available()
5✔
658
                && (start + (ssize_t) this->lb_alt_buffer.value().size() + rc
15✔
659
                    > this->lb_file_size))
5✔
660
            {
661
                this->lb_file_size
662
                    = (start + this->lb_alt_buffer.value().size() + rc);
5✔
663
                log_info("fd(%d): set file size to %llu",
5✔
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)
889✔
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,776✔
743
                                      : this->lb_fd.get(),
888✔
744
                   this->lb_alt_buffer.value().end(),
888✔
745
                   this->lb_alt_buffer.value().available(),
888✔
746
                   start + this->lb_alt_buffer.value().size());
888✔
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()) {
894✔
751
        rc = -1;
×
752
#ifdef ENODATA
753
        errno = ENODATA;
×
754
#else
755
        errno = EAGAIN;
756
#endif
757
    }
758
    switch (rc) {
894✔
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:
880✔
782
            this->lb_alt_buffer.value().resize_by(rc);
880✔
783
            retval = true;
880✔
784
            break;
880✔
785
    }
786

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

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

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

814
    return retval;
894✔
815
}
894✔
816

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

824
    require(start >= 0);
4,285✔
825

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

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

919
        // log_debug("fd(%d) doing read", this->lb_fd.get());
920
        /* Make sure there is enough space, then */
921
        this->ensure_available(start, max_length, dir);
4,091✔
922

923
        safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
4,091✔
924

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

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

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

997
                    count = BZ2_bzread(
×
998
                        bz_file,
999
                        scratch,
1000
                        std::min((size_t) seek_to, sizeof(scratch)));
×
1001
                    if (count <= 0) {
×
1002
                        break;
×
1003
                    }
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)
4,069✔
1023
        {
1024
            this->lb_stats.s_preads += 1;
3,839✔
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()
7,678✔
1035
                                          : this->lb_fd.get(),
3,839✔
1036
                       this->lb_buffer.end(),
3,839✔
1037
                       this->lb_buffer.available(),
1038
                       this->lb_file_offset + this->lb_buffer.size());
3,839✔
1039
            // log_debug("pread rc %d", rc);
1040
        } else {
1041
            rc = read(this->lb_fd,
460✔
1042
                      this->lb_buffer.end(),
230✔
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()) {
4,091✔
1048
            rc = -1;
×
1049
#ifdef ENODATA
1050
            errno = ENODATA;
×
1051
#else
1052
            errno = EAGAIN;
1053
#endif
1054
        }
1055
        switch (rc) {
4,091✔
1056
            case 0:
1,834✔
1057
                if (!this->lb_seekable) {
1,834✔
1058
                    this->lb_file_size
1059
                        = this->lb_file_offset + this->lb_buffer.size();
164✔
1060
                }
1061
                if (start < (file_off_t) this->lb_file_size) {
1,834✔
1062
                    retval = true;
18✔
1063
                }
1064

1065
                if (this->lb_compressed) {
1,834✔
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) {
14✔
1071
                        this->resize_buffer(MAX_COMPRESSED_BUFFER_SIZE);
12✔
1072
                    }
1073
                }
1074
                break;
1,834✔
1075

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

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

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

1099
        if (this->lb_do_preloading && !lnav::pid::in_child && this->lb_seekable
3,059✔
1100
            && this->lb_buffer.full() && !this->lb_loader_file_offset)
7,150✔
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');
×
1105
            if (last_lf_iter != this->lb_buffer.rend()) {
×
1106
                auto usable_size
1107
                    = std::distance(last_lf_iter, this->lb_buffer.rend());
×
1108
                // log_debug("found linefeed %d", usable_size);
1109
                if (!this->lb_alt_buffer) {
×
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()
×
1114
                           < this->lb_buffer.capacity())
×
1115
                {
1116
                    this->lb_alt_buffer->expand_to(this->lb_buffer.capacity());
×
1117
                }
1118
                this->lb_alt_buffer->resize(this->lb_buffer.size()
×
1119
                                            - usable_size);
×
1120
                memcpy(this->lb_alt_buffer->begin(),
×
1121
                       this->lb_buffer.at(usable_size),
×
1122
                       this->lb_alt_buffer->size());
1123
                this->lb_loader_file_offset
1124
                    = this->lb_file_offset + usable_size;
×
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>>();
×
1131
                this->lb_loader_future = prom->get_future();
×
1132
                this->lb_stats.s_requested_preloads += 1;
×
1133
                isc::to<io_looper&, io_looper_tag>().send(
×
1134
                    [this, prom](auto& ioloop) mutable {
×
1135
                        prom->set_value(this->load_next_buffer());
×
1136
                    });
×
1137
            }
1138
        }
1139
        ensure(this->lb_buffer.size() <= this->lb_buffer.capacity());
4,091✔
1140
    }
4,091✔
1141
    // log_debug("END fill_range");
1142

1143
    return retval;
4,285✔
1144
}
1145

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

1153
    require(this->lb_fd != -1);
23,822✔
1154

1155
    if (this->lb_line_metadata && prev_line.fr_offset == 0) {
23,822✔
1156
        prev_line.fr_offset = this->lb_piper_header_size;
124✔
1157
    }
1158
    if (this->lb_bom_size > 0 && prev_line.fr_offset == 0) {
23,822✔
1159
        prev_line.fr_offset = this->lb_bom_size;
1✔
1160
    }
1161

1162
    auto offset = prev_line.next_offset();
23,822✔
1163
    ssize_t request_size = INITIAL_REQUEST_SIZE;
23,822✔
1164
    retval.li_file_range.fr_offset = offset;
23,822✔
1165
    if (this->lb_buffer.empty() || !this->in_range(offset)) {
23,822✔
1166
        this->fill_range(offset, this->lb_buffer.capacity());
3,141✔
1167
    } else if (offset
20,681✔
1168
               == this->lb_file_offset + (ssize_t) this->lb_buffer.size())
20,681✔
1169
    {
1170
        if (!this->fill_range(offset, INITIAL_REQUEST_SIZE)) {
×
1171
            retval.li_file_range.fr_offset = offset;
×
UNCOV
1172
            retval.li_file_range.fr_size = 0;
×
1173
            if (this->is_pipe()) {
×
UNCOV
1174
                retval.li_partial = !this->is_pipe_closed();
×
1175
            } else {
UNCOV
1176
                retval.li_partial = true;
×
1177
            }
UNCOV
1178
            return Ok(retval);
×
1179
        }
1180
    }
1181
    if (prev_line.next_offset() == 0) {
23,822✔
1182
        // Skip a leading UTF-8 BOM so files produced by Excel,
1183
        // PowerShell, and other Windows-oriented tools don't leak the
1184
        // three-byte marker into the first line.  The bytes stay in
1185
        // lb_buffer but are rendered invisible by fast-forwarding the
1186
        // first line's start offset (mirrors lb_piper_header_size).
1187
        if (this->lb_buffer.size() >= 3
2,141✔
1188
            && (uint8_t) this->lb_buffer[0] == 0xEF
2,094✔
1189
            && (uint8_t) this->lb_buffer[1] == 0xBB
4✔
1190
            && (uint8_t) this->lb_buffer[2] == 0xBF)
4,235✔
1191
        {
1192
            this->lb_bom_size = 3;
4✔
1193
            prev_line.fr_offset = this->lb_bom_size;
4✔
1194
            offset = prev_line.next_offset();
4✔
1195
            retval.li_file_range.fr_offset = offset;
4✔
1196
        }
1197
        auto is_utf_res = is_utf8(string_fragment::from_bytes(
4,282✔
1198
            this->lb_buffer.begin(), this->lb_buffer.size()));
2,141✔
1199
        this->lb_is_utf8 = is_utf_res.is_valid();
2,141✔
1200
        if (!this->lb_is_utf8) {
2,141✔
1201
            log_warning("fd(%d): input is not utf8 -- %s",
19✔
1202
                        this->lb_fd.get(),
1203
                        is_utf_res.usr_message);
1204
        }
1205
    }
1206
    while (!done) {
47,550✔
1207
        auto old_retval_size = retval.li_file_range.fr_size;
23,837✔
1208
        const char* lf = nullptr;
23,837✔
1209

1210
        /* Find the data in the cache and */
1211
        line_start = this->get_range(offset, retval.li_file_range.fr_size);
23,837✔
1212
        /* ... look for the end-of-line or end-of-file. */
1213
        ssize_t utf8_end = -1;
23,837✔
1214

1215
        if (!retval.li_utf8_scan_result.is_valid()) {
23,837✔
1216
            retval.li_utf8_scan_result = {};
5✔
1217
        }
1218
        auto found_in_cache = false;
23,837✔
1219
        auto has_ansi = false;
23,837✔
1220
        auto valid_utf8 = true;
23,837✔
1221
        auto col_width = size_t{0};
23,837✔
1222
        if (!this->lb_line_starts.empty()) {
23,837✔
1223
            auto buffer_offset = offset - this->lb_file_offset;
3✔
1224

1225
            if (this->lb_next_buffer_offset == buffer_offset) {
3✔
1226
                require(this->lb_next_line_start_index
3✔
1227
                        < this->lb_line_starts.size());
1228
                auto start_iter = this->lb_line_starts.begin()
6✔
1229
                    + this->lb_next_line_start_index;
3✔
1230
                auto next_line_iter = start_iter + 1;
3✔
1231
                if (next_line_iter != this->lb_line_starts.end()) {
3✔
1232
                    utf8_end = *next_line_iter - 1 - *start_iter;
3✔
1233
                    found_in_cache = true;
3✔
1234
                    lf = line_start + utf8_end;
3✔
1235
                    has_ansi = this->lb_line_has_ansi
1236
                                   [this->lb_next_line_start_index];
3✔
1237
                    valid_utf8
1238
                        = this->lb_line_is_utf[this->lb_next_line_start_index];
3✔
1239
                    col_width = this->lb_line_col_widths
3✔
1240
                                    [this->lb_next_line_start_index];
3✔
1241

1242
                    // log_debug("hit cache");
1243
                    this->lb_next_buffer_offset = *next_line_iter;
3✔
1244
                    this->lb_next_line_start_index += 1;
3✔
1245
                } else {
1246
                    // log_debug("no next iter");
1247
                }
1248
            } else {
1249
                auto start_iter = std::lower_bound(this->lb_line_starts.begin(),
×
1250
                                                   this->lb_line_starts.end(),
1251
                                                   buffer_offset);
UNCOV
1252
                if (start_iter != this->lb_line_starts.end()) {
×
UNCOV
1253
                    auto next_line_iter = start_iter + 1;
×
1254

1255
                    // log_debug("found offset %d %d", buffer_offset,
1256
                    // *start_iter);
UNCOV
1257
                    if (next_line_iter != this->lb_line_starts.end()) {
×
UNCOV
1258
                        auto start_index = std::distance(
×
1259
                            this->lb_line_starts.begin(), start_iter);
UNCOV
1260
                        utf8_end = *next_line_iter - 1 - *start_iter;
×
UNCOV
1261
                        found_in_cache = true;
×
UNCOV
1262
                        lf = line_start + utf8_end;
×
UNCOV
1263
                        has_ansi = this->lb_line_has_ansi[start_index];
×
UNCOV
1264
                        valid_utf8 = this->lb_line_is_utf[start_index];
×
UNCOV
1265
                        col_width = this->lb_line_col_widths[start_index];
×
1266

UNCOV
1267
                        this->lb_next_line_start_index = start_index + 1;
×
UNCOV
1268
                        this->lb_next_buffer_offset = *next_line_iter;
×
1269
                    } else {
1270
                        // log_debug("no next iter");
1271
                    }
1272
                } else {
1273
                    // log_debug("no buffer_offset found");
1274
                }
1275
            }
1276
        }
1277

1278
        if (found_in_cache && valid_utf8) {
23,837✔
1279
            retval.li_utf8_scan_result.usr_has_ansi = has_ansi;
3✔
1280
            retval.li_utf8_scan_result.usr_column_width_guess = col_width;
3✔
1281
        } else {
1282
            auto frag = string_fragment::from_bytes(
23,834✔
1283
                line_start, retval.li_file_range.fr_size);
23,834✔
1284
            auto scan_res = is_utf8(frag, '\n');
23,834✔
1285
            lf = scan_res.remaining_ptr();
23,834✔
1286
            if (lf != nullptr) {
23,834✔
1287
                lf -= 1;
22,896✔
1288
            }
1289
            retval.li_utf8_scan_result = scan_res;
23,834✔
1290
            if (!scan_res.is_valid()) {
23,834✔
1291
                log_warning("fd(%d): line is not utf8 -- %lld:%d",
91✔
1292
                            this->lb_fd.get(),
1293
                            retval.li_file_range.fr_offset,
1294
                            scan_res.usr_valid_frag.length());
1295
            }
1296
        }
1297

1298
        auto got_new_data = old_retval_size != retval.li_file_range.fr_size;
23,837✔
1299
#if 0
1300
        log_debug("load next loop %p reqsize %d lsize %d",
1301
                  lf,
1302
                  request_size,
1303
                  retval.li_file_range.fr_size);
1304
#endif
1305
        if (lf != nullptr
23,837✔
1306
            || (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE)
938✔
1307
            || (request_size >= MAX_LINE_BUFFER_SIZE)
938✔
1308
            || (!got_new_data
25,672✔
1309
                && (!this->is_pipe() || request_size > DEFAULT_INCREMENT)))
897✔
1310
        {
1311
            if ((lf != nullptr)
23,713✔
1312
                && ((size_t) (lf - line_start) >= MAX_LINE_BUFFER_SIZE - 1))
22,899✔
1313
            {
UNCOV
1314
                lf = nullptr;
×
1315
            }
1316
            if (lf != nullptr) {
23,713✔
1317
                retval.li_partial = false;
22,899✔
1318
                retval.li_file_range.fr_size = lf - line_start;
22,899✔
1319
                // delim
1320
                retval.li_file_range.fr_size += 1;
22,899✔
1321
                if (offset >= this->lb_last_line_offset) {
22,899✔
1322
                    this->lb_last_line_offset
1323
                        = offset + retval.li_file_range.fr_size;
21,808✔
1324
                }
1325
            } else {
1326
                if (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE) {
814✔
UNCOV
1327
                    log_warning("Line exceeded max size: offset=%zd", offset);
×
UNCOV
1328
                    retval.li_file_range.fr_size = MAX_LINE_BUFFER_SIZE - 1;
×
UNCOV
1329
                    retval.li_partial = false;
×
1330
                } else {
1331
                    retval.li_partial = true;
814✔
1332
                }
1333
                this->ensure_available(offset, retval.li_file_range.fr_size);
814✔
1334

1335
                if (retval.li_file_range.fr_size >= MAX_LINE_BUFFER_SIZE) {
814✔
UNCOV
1336
                    retval.li_file_range.fr_size = MAX_LINE_BUFFER_SIZE - 1;
×
1337
                }
1338
                if (retval.li_partial) {
814✔
1339
                    /*
1340
                     * Since no delimiter was seen, we need to remember the
1341
                     * offset of the last line in the file so we don't
1342
                     * mistakenly return two partial lines to the caller.
1343
                     *
1344
                     *   1. read_line() - returns partial line
1345
                     *   2. file is written
1346
                     *   3. read_line() - returns the middle of partial line.
1347
                     */
1348
                    this->lb_last_line_offset = offset;
814✔
UNCOV
1349
                } else if (offset >= this->lb_last_line_offset) {
×
1350
                    this->lb_last_line_offset
UNCOV
1351
                        = offset + retval.li_file_range.fr_size;
×
1352
                }
1353
            }
1354

1355
            offset += retval.li_file_range.fr_size;
23,713✔
1356
            done = true;
23,713✔
1357
        } else {
1358
            if (!this->is_pipe() || !this->is_pipe_closed()) {
124✔
1359
                retval.li_partial = true;
46✔
1360
            }
1361
            request_size
1362
                = std::min<ssize_t>(this->lb_buffer.size() + DEFAULT_INCREMENT,
124✔
1363
                                    MAX_LINE_BUFFER_SIZE);
1364
        }
1365

1366
        if (!done
47,674✔
1367
            && !this->fill_range(
23,961✔
1368
                offset,
1369
                std::max(request_size, (ssize_t) this->lb_buffer.available())))
23,961✔
1370
        {
1371
            break;
109✔
1372
        }
1373
    }
1374

1375
    ensure(retval.li_file_range.fr_size <= (ssize_t) this->lb_buffer.size());
23,822✔
1376
    ensure(this->invariant());
23,822✔
1377
#if 0
1378
    log_debug("got line part %d %d",
1379
              retval.li_file_range.fr_offset,
1380
              (int) retval.li_partial);
1381
#endif
1382

1383
    retval.li_file_range.fr_metadata.m_has_ansi
1384
        = retval.li_utf8_scan_result.usr_has_ansi;
23,822✔
1385
    retval.li_file_range.fr_metadata.m_valid_utf
1386
        = retval.li_utf8_scan_result.is_valid();
23,822✔
1387

1388
    if (this->lb_line_metadata && retval.li_file_range.fr_size > 0) {
23,822✔
1389
        auto sv = std::string_view{
1390
            line_start,
1391
            (size_t) retval.li_file_range.fr_size,
445✔
1392
        };
445✔
1393

1394
        auto scan_res = scn::scan<int64_t, int64_t, char>(sv, "{}.{}:{};");
445✔
1395
        if (scan_res) {
445✔
1396
            auto& [tv_sec, tv_usec, level] = scan_res->values();
445✔
1397
            retval.li_timestamp.tv_sec = tv_sec;
445✔
1398
            retval.li_timestamp.tv_usec = tv_usec;
445✔
1399
            retval.li_timestamp.tv_sec
1400
                = lnav::to_local_time(date::sys_seconds{std::chrono::seconds{
445✔
1401
                                          retval.li_timestamp.tv_sec}})
445✔
1402
                      .time_since_epoch()
445✔
1403
                      .count();
445✔
1404
            retval.li_level = abbrev2level(&level, 1);
445✔
1405
        }
1406
    }
1407

1408
    return Ok(retval);
23,822✔
1409
}
1410

1411
Result<shared_buffer_ref, std::string>
1412
line_buffer::read_range(file_range fr, scan_direction dir)
107,947✔
1413
{
1414
    shared_buffer_ref retval;
107,947✔
1415
    const char* line_start;
1416
    file_ssize_t avail;
1417

1418
#if 0
1419
    if (this->lb_last_line_offset != -1
1420
        && fr.fr_offset > this->lb_last_line_offset)
1421
    {
1422
        /*
1423
         * Don't return anything past the last known line.  The caller needs
1424
         * to try reading at the offset of the last line again.
1425
         */
1426
        return Err(
1427
            fmt::format(FMT_STRING("attempt to read past the known end of the "
1428
                                   "file: read-offset={}; last_line_offset={}"),
1429
                        fr.fr_offset,
1430
                        this->lb_last_line_offset));
1431
    }
1432
#endif
1433

1434
    if (!(this->in_range(fr.fr_offset)
215,065✔
1435
          && this->in_range(fr.fr_offset + fr.fr_size - 1)))
107,118✔
1436
    {
1437
        if (!this->fill_range(fr.fr_offset, fr.fr_size, dir)) {
1,020✔
UNCOV
1438
            return Err(std::string("unable to read file"));
×
1439
        }
1440
    }
1441
    line_start = this->get_range(fr.fr_offset, avail);
107,947✔
1442

1443
    if (fr.fr_size > avail) {
107,947✔
UNCOV
1444
        return Err(fmt::format(
×
UNCOV
1445
            FMT_STRING("short-read (need: {}; avail: {})"), fr.fr_size, avail));
×
1446
    }
1447
    if (this->lb_line_metadata) {
107,947✔
1448
        const auto* new_start
1449
            = static_cast<const char*>(memchr(line_start, ';', fr.fr_size));
2,545✔
1450
        if (new_start) {
2,545✔
1451
            auto offset = new_start - line_start + 1;
2,545✔
1452
            line_start += offset;
2,545✔
1453
            fr.fr_size -= offset;
2,545✔
1454
        }
1455
    }
1456
    retval.share(this->lb_share_manager, line_start, fr.fr_size);
107,947✔
1457
    retval.get_metadata() = fr.fr_metadata;
107,947✔
1458

1459
    return Ok(std::move(retval));
107,947✔
1460
}
107,947✔
1461

1462
Result<auto_buffer, std::string>
1463
line_buffer::peek_range(file_range fr,
1✔
1464
                        lnav::enums::bitset<peek_options> options)
1465
{
1466
    static const std::string SHORT_READ_MSG = "short read";
3✔
1467

1468
    require(this->lb_seekable);
1✔
1469

1470
    auto buf = auto_buffer::alloc(fr.fr_size);
1✔
1471

1472
    if (this->lb_cached_fd) {
1✔
1473
        auto rc = pread(this->lb_cached_fd.value().get(),
×
UNCOV
1474
                        buf.data(),
×
1475
                        fr.fr_size,
×
1476
                        fr.fr_offset);
UNCOV
1477
        if (rc == -1) {
×
1478
            return Err(lnav::from_errno().message());
×
1479
        }
UNCOV
1480
        if (!options.is_set<peek_options::allow_short_read>()
×
1481
            && rc != fr.fr_size)
×
1482
        {
1483
            return Err(SHORT_READ_MSG);
×
1484
        }
UNCOV
1485
        buf.resize(rc);
×
1486

1487
        return Ok(std::move(buf));
×
1488
    }
1489

1490
    if (this->lb_compressed) {
1✔
1491
        safe::WriteAccess<safe_gz_indexed> gi(this->lb_gz_file);
×
1492

UNCOV
1493
        if (*gi) {
×
1494
            auto rc = gi->read(buf.data(), fr.fr_offset, fr.fr_size);
×
1495

UNCOV
1496
            if (rc == -1) {
×
UNCOV
1497
                return Err(lnav::from_errno().message());
×
1498
            }
UNCOV
1499
            if (rc != fr.fr_size) {
×
UNCOV
1500
                this->lb_file_size = gi->strm.total_out;
×
UNCOV
1501
                log_info("fd(%d): set file size to %llu",
×
1502
                         this->lb_fd.get(),
1503
                         this->lb_file_size);
UNCOV
1504
                if (!options.is_set<peek_options::allow_short_read>()) {
×
1505
                    return Err(SHORT_READ_MSG);
×
1506
                }
1507
            }
1508
            buf.resize(rc);
×
UNCOV
1509
            return Ok(std::move(buf));
×
1510
        }
1511
#ifdef HAVE_BZLIB_H
1512
        if (this->lb_bz_file) {
×
1513
            lock_hack::guard guard;
×
1514
            char scratch[32 * 1024];
1515
            BZFILE* bz_file;
1516
            file_off_t seek_to;
1517
            int bzfd;
1518

1519
            /*
1520
             * Unfortunately, there is no bzseek, so we need to reopen the
1521
             * file every time we want to do a read.
1522
             */
1523
            bzfd = dup(this->lb_fd);
×
UNCOV
1524
            if (lseek(this->lb_fd, 0, SEEK_SET) < 0) {
×
1525
                close(bzfd);
×
1526
                throw error(errno);
×
1527
            }
UNCOV
1528
            if ((bz_file = BZ2_bzdopen(bzfd, "r")) == nullptr) {
×
1529
                close(bzfd);
×
UNCOV
1530
                if (errno == 0) {
×
1531
                    throw std::bad_alloc();
×
1532
                } else {
1533
                    throw error(errno);
×
1534
                }
1535
            }
1536

UNCOV
1537
            seek_to = fr.fr_offset;
×
1538
            while (seek_to > 0) {
×
1539
                int count;
1540

1541
                count = BZ2_bzread(bz_file,
×
1542
                                   scratch,
UNCOV
1543
                                   std::min((size_t) seek_to, sizeof(scratch)));
×
UNCOV
1544
                if (count <= 0) {
×
1545
                    break;
×
1546
                }
UNCOV
1547
                seek_to -= count;
×
1548
            }
1549
            auto rc = BZ2_bzread(bz_file, buf.data(), fr.fr_size);
×
UNCOV
1550
            this->lb_compressed_offset = 0;
×
UNCOV
1551
            BZ2_bzclose(bz_file);
×
1552

UNCOV
1553
            if (rc == -1) {
×
UNCOV
1554
                return Err(lnav::from_errno().message());
×
1555
            }
UNCOV
1556
            if (rc != fr.fr_size) {
×
UNCOV
1557
                if (options.is_set<peek_options::allow_short_read>()) {
×
1558
                    this->lb_file_size = fr.fr_offset + fr.fr_size;
×
UNCOV
1559
                    log_info("fd(%d): set file size to %llu",
×
1560
                             this->lb_fd.get(),
1561
                             this->lb_file_size);
1562
                } else {
UNCOV
1563
                    return Err(SHORT_READ_MSG);
×
1564
                }
1565
            }
UNCOV
1566
            buf.resize(rc);
×
UNCOV
1567
            return Ok(std::move(buf));
×
1568
        }
1569
#else
1570
        assert(!this->lb_bz_file);
1571
#endif
1572
    }
1573

1574
    auto rc = pread(this->lb_fd, buf.data(), fr.fr_size, fr.fr_offset);
1✔
1575
    if (rc == -1) {
1✔
1576
        return Err(lnav::from_errno().message());
×
1577
    }
1578
    if (!options.is_set<peek_options::allow_short_read>() && rc != fr.fr_size) {
1✔
UNCOV
1579
        return Err(SHORT_READ_MSG);
×
1580
    }
1581
    buf.resize(rc);
1✔
1582
    return Ok(std::move(buf));
1✔
1583
}
1✔
1584

1585
file_range
1586
line_buffer::get_available()
827✔
1587
{
1588
    return {
1589
        this->lb_file_offset,
827✔
1590
        static_cast<file_ssize_t>(this->lb_buffer.size()),
1,654✔
1591
    };
827✔
1592
}
1593

UNCOV
1594
line_buffer::gz_indexed::indexDict::indexDict(const z_stream& s,
×
1595
                                              const file_size_t size)
×
1596
{
1597
    assert((s.data_type & GZ_END_OF_BLOCK_MASK));
1598
    assert(!(s.data_type & GZ_END_OF_FILE_MASK));
1599
    assert(size >= s.avail_out + GZ_WINSIZE);
1600
    this->bits = s.data_type & GZ_BORROW_BITS_MASK;
×
1601
    this->in = s.total_in;
×
1602
    this->out = s.total_out;
×
UNCOV
1603
    auto last_byte_in = s.next_in[-1];
×
1604
    this->in_bits = last_byte_in >> (8 - this->bits);
×
1605
    // Copy the last 32k uncompressed data (sliding window) to our
1606
    // index
1607
    memcpy(this->index, s.next_out - GZ_WINSIZE, GZ_WINSIZE);
×
1608
}
1609

1610
int
UNCOV
1611
line_buffer::gz_indexed::indexDict::apply(z_streamp s)
×
1612
{
UNCOV
1613
    s->zalloc = Z_NULL;
×
1614
    s->zfree = Z_NULL;
×
UNCOV
1615
    s->opaque = Z_NULL;
×
1616
    s->avail_in = 0;
×
UNCOV
1617
    s->next_in = Z_NULL;
×
1618
    auto ret = inflateInit2(s, GZ_RAW_MODE);
×
1619
    if (ret != Z_OK) {
×
UNCOV
1620
        return ret;
×
1621
    }
1622
    if (this->bits) {
×
1623
        inflatePrime(s, this->bits, this->in_bits);
×
1624
    }
UNCOV
1625
    s->total_in = this->in;
×
1626
    s->total_out = this->out;
×
1627
    inflateSetDictionary(s, this->index, GZ_WINSIZE);
×
UNCOV
1628
    return ret;
×
1629
}
1630

1631
bool
UNCOV
1632
line_buffer::is_likely_to_flush(file_range prev_line)
×
1633
{
1634
    auto avail = this->get_available();
×
1635

UNCOV
1636
    if (prev_line.fr_offset < avail.fr_offset) {
×
UNCOV
1637
        return true;
×
1638
    }
UNCOV
1639
    auto prev_line_end = prev_line.fr_offset + prev_line.fr_size;
×
UNCOV
1640
    auto avail_end = avail.fr_offset + avail.fr_size;
×
UNCOV
1641
    if (avail_end < prev_line_end) {
×
UNCOV
1642
        return true;
×
1643
    }
UNCOV
1644
    auto remaining = avail_end - prev_line_end;
×
UNCOV
1645
    return remaining < INITIAL_REQUEST_SIZE;
×
1646
}
1647

1648
void
1649
line_buffer::quiesce()
40✔
1650
{
1651
    if (this->lb_loader_future.valid()) {
40✔
UNCOV
1652
        this->lb_loader_future.wait();
×
1653
    }
1654
}
40✔
1655

1656
static std::filesystem::path
1657
line_buffer_cache_path()
775✔
1658
{
1659
    return lnav::paths::workdir() / "buffer-cache";
1,550✔
1660
}
1661

1662
void
1663
line_buffer::enable_cache()
340✔
1664
{
1665
    if (!this->lb_compressed || this->lb_cached_fd) {
340✔
1666
        log_info("%d: skipping cache request (compressed=%d already-cached=%d)",
340✔
1667
                 this->lb_fd.get(),
1668
                 this->lb_compressed,
1669
                 (bool) this->lb_cached_fd);
1670
        return;
340✔
1671
    }
1672

1673
    struct stat st;
1674

UNCOV
1675
    if (fstat(this->lb_fd, &st) == -1) {
×
1676
        log_error("failed to fstat(%d) - %d", this->lb_fd.get(), errno);
×
UNCOV
1677
        return;
×
1678
    }
1679

1680
    auto cached_base_name = hasher()
×
UNCOV
1681
                                .update(st.st_dev)
×
1682
                                .update(st.st_ino)
×
1683
                                .update(st.st_size)
×
1684
                                .to_string();
×
1685
    auto cache_dir = line_buffer_cache_path() / cached_base_name.substr(0, 2);
×
1686

1687
    std::filesystem::create_directories(cache_dir);
×
1688

1689
    auto cached_file_name = fmt::format(FMT_STRING("{}.bin"), cached_base_name);
×
UNCOV
1690
    auto cached_file_path = cache_dir / cached_file_name;
×
1691
    auto cached_done_path
UNCOV
1692
        = cache_dir / fmt::format(FMT_STRING("{}.done"), cached_base_name);
×
1693

1694
    log_info(
×
1695
        "%d:cache file path: %s", this->lb_fd.get(), cached_file_path.c_str());
1696

UNCOV
1697
    auto fl = lnav::filesystem::file_lock(cached_file_path);
×
1698
    auto guard = lnav::filesystem::file_lock::guard(&fl);
×
1699

UNCOV
1700
    if (std::filesystem::exists(cached_done_path)) {
×
1701
        log_info("%d:using existing cache file", this->lb_fd.get());
×
1702
        auto open_res = lnav::filesystem::open_file(cached_file_path, O_RDWR);
×
UNCOV
1703
        if (open_res.isOk()) {
×
UNCOV
1704
            this->lb_cached_fd = open_res.unwrap();
×
1705
            return;
×
1706
        }
1707
        std::filesystem::remove(cached_done_path);
×
1708
    }
1709

1710
    auto create_res = lnav::filesystem::create_file(
UNCOV
1711
        cached_file_path, O_RDWR | O_TRUNC, 0600);
×
UNCOV
1712
    if (create_res.isErr()) {
×
UNCOV
1713
        log_error("failed to create cache file: %s -- %s",
×
1714
                  cached_file_path.c_str(),
1715
                  create_res.unwrapErr().c_str());
1716
        return;
×
1717
    }
1718

1719
    auto write_fd = create_res.unwrap();
×
1720
    auto done = false;
×
1721

1722
    static constexpr ssize_t FILL_LENGTH = 1024 * 1024;
UNCOV
1723
    auto off = file_off_t{0};
×
1724
    while (!done) {
×
UNCOV
1725
        log_debug("%d: caching file content at %lld", this->lb_fd.get(), off);
×
UNCOV
1726
        if (!this->fill_range(off, FILL_LENGTH)) {
×
UNCOV
1727
            log_debug("%d: caching finished", this->lb_fd.get());
×
1728
            done = true;
×
1729
        } else {
1730
            file_ssize_t avail;
1731

UNCOV
1732
            const auto* data = this->get_range(off, avail);
×
UNCOV
1733
            auto rc = write(write_fd, data, avail);
×
UNCOV
1734
            if (rc == -1 && errno == EINTR) {
×
UNCOV
1735
                continue;
×
1736
            }
UNCOV
1737
            if (rc != avail) {
×
UNCOV
1738
                log_error("%d: short write!", this->lb_fd.get());
×
UNCOV
1739
                return;
×
1740
            }
1741

UNCOV
1742
            off += avail;
×
1743
        }
1744
    }
1745

1746
    lnav::filesystem::create_file(cached_done_path, O_WRONLY, 0600);
×
1747

UNCOV
1748
    this->lb_cached_fd = std::move(write_fd);
×
1749
}
1750

1751
std::future<void>
1752
line_buffer::cleanup_cache()
775✔
1753
{
1754
    return std::async(
1755
        std::launch::async, +[]() {
775✔
1756
            auto now = std::filesystem::file_time_type::clock::now();
775✔
1757
            auto cache_path = line_buffer_cache_path();
775✔
1758
            std::vector<std::filesystem::path> to_remove;
775✔
1759
            std::error_code ec;
775✔
1760

1761
            for (const auto& cache_subdir :
775✔
1762
                 std::filesystem::directory_iterator(cache_path, ec))
775✔
1763
            {
UNCOV
1764
                for (const auto& entry :
×
UNCOV
1765
                     std::filesystem::directory_iterator(cache_subdir, ec))
×
1766
                {
UNCOV
1767
                    auto mtime = std::filesystem::last_write_time(entry.path());
×
UNCOV
1768
                    auto exp_time = mtime + 1h;
×
UNCOV
1769
                    if (now < exp_time) {
×
1770
                        continue;
×
1771
                    }
1772

UNCOV
1773
                    to_remove.emplace_back(entry.path());
×
1774
                }
1775
            }
775✔
1776

1777
            for (auto& entry : to_remove) {
775✔
UNCOV
1778
                log_debug("removing compressed file cache: %s", entry.c_str());
×
UNCOV
1779
                std::filesystem::remove_all(entry, ec);
×
1780
            }
1781
        });
2,325✔
1782
}
1783

1784
void
1785
line_buffer::send_initial_load()
891✔
1786
{
1787
    if (!this->lb_seekable) {
891✔
UNCOV
1788
        log_warning("file is not seekable, not doing preload");
×
UNCOV
1789
        return;
×
1790
    }
1791

1792
    if (this->lb_loader_future.valid()) {
891✔
UNCOV
1793
        log_warning("preload is already active");
×
UNCOV
1794
        return;
×
1795
    }
1796

1797
    log_debug("sending initial load");
891✔
1798
    if (!this->lb_alt_buffer) {
891✔
1799
        // log_debug("allocating new buffer!");
1800
        this->lb_alt_buffer = auto_buffer::alloc(this->lb_buffer.capacity());
891✔
1801
    }
1802
    this->lb_loader_file_offset = 0;
891✔
1803
    auto prom = std::make_shared<std::promise<bool>>();
891✔
1804
    this->lb_loader_future = prom->get_future();
891✔
1805
    this->lb_stats.s_requested_preloads += 1;
891✔
1806
    isc::to<io_looper&, io_looper_tag>().send(
891✔
1807
        [this, prom](auto& ioloop) mutable {
1,782✔
1808
            prom->set_value(this->load_next_buffer());
891✔
1809
        });
891✔
1810
}
891✔
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