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

tstack / lnav / 17589970077-2502

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

push

github

tstack
[format] add fields for source file/line

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

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

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

95.0
/src/line_buffer.hh
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.hh
30
 */
31

32
#ifndef line_buffer_hh
33
#define line_buffer_hh
34

35
#include <array>
36
#include <exception>
37
#include <future>
38
#include <vector>
39

40
#include <errno.h>
41
#include <sys/types.h>
42
#include <unistd.h>
43
#include <zlib.h>
44

45
#include "base/auto_fd.hh"
46
#include "base/auto_mem.hh"
47
#include "base/file_range.hh"
48
#include "base/is_utf8.hh"
49
#include "base/lnav.gzip.hh"
50
#include "base/piper.file.hh"
51
#include "base/result.h"
52
#include "log_level.hh"
53
#include "mapbox/variant.hpp"
54
#include "safe/safe.h"
55
#include "shared_buffer.hh"
56

57
struct line_info {
58
    file_range li_file_range;
59
    struct timeval li_timestamp{0, 0};
60
    log_level_t li_level{LEVEL_UNKNOWN};
61
    bool li_partial{false};
62
    utf8_scan_result li_utf8_scan_result{};
63
};
64

65
/**
66
 * Buffer for reading whole lines out of file descriptors.  The class presents
67
 * a stateless interface, callers specify the offset where a line starts and
68
 * the class takes care of caching the surrounding range and locating the
69
 * delimiter.
70
 *
71
 * XXX A bit of a wheel reinvention, but I'm not sure how well the libraries
72
 * handle non-blocking I/O...
73
 */
74
class line_buffer {
75
public:
76
    static const ssize_t DEFAULT_LINE_BUFFER_SIZE;
77
    static const ssize_t MAX_LINE_BUFFER_SIZE;
78
    class error : public std::exception {
79
    public:
UNCOV
80
        explicit error(int err) : e_err(err) {}
×
81

82
        int e_err;
83
    };
84

85
#define GZ_WINSIZE           32768U /*> gzip's max supported dictionary is 15-bits */
86
#define GZ_RAW_MODE          (-15) /*> Raw inflate data mode */
87
#define GZ_HEADER_MODE       (15 + 32) /*> Automatic zstd or gzip decoding */
88
#define GZ_BORROW_BITS_MASK  7 /*> Bits (0-7) consumed in previous block */
89
#define GZ_END_OF_BLOCK_MASK 128 /*> Stopped because reached end-of-block */
90
#define GZ_END_OF_FILE_MASK  64 /*> Stopped because reached end-of-file */
91

92
    /**
93
     * A memoized gzip file reader that can do random file access faster than
94
     * gzseek/gzread alone.
95
     */
96
    class gz_indexed {
97
    public:
98
        gz_indexed();
99
        gz_indexed(gz_indexed&& other) = default;
100
        ~gz_indexed() { this->close(); }
1,317✔
101

102
        inline operator bool() const { return this->gz_fd != -1; }
7,552✔
103

104
        uLong get_source_offset() const
13✔
105
        {
106
            return !!*this ? this->strm.total_in + this->strm.avail_in : 0;
13✔
107
        }
108

109
        void close();
110
        void init_stream();
111
        void continue_stream();
112
        void open(int fd, lnav::gzip::header& hd);
113
        int stream_data(void* buf, size_t size);
114
        void seek(off_t offset);
115

116
        /**
117
         * Decompress bytes from the gz file returning at most `size` bytes.
118
         * offset is the byte-offset in the decompressed data stream.
119
         */
120
        int read(void* buf, size_t offset, size_t size);
121

122
        struct indexDict {
123
            off_t in = 0;
124
            off_t out = 0;
125
            unsigned char bits = 0;
126
            unsigned char in_bits = 0;
127
            Bytef index[GZ_WINSIZE];
128
            indexDict(z_stream const& s, const file_size_t size);
129

130
            int apply(z_streamp s);
131
        };
132

133
        line_buffer* parent;
134
        z_stream strm{}; /*< gzip streams structure */
135
        std::vector<indexDict>
136
            syncpoints; /*< indexed dictionaries as discovered */
137
        auto_mem<Bytef> inbuf; /*< Compressed data buffer */
138
        int gz_fd = -1; /*< The file to read data from. */
139
    };
140

141
    /** Construct an empty line_buffer. */
142
    line_buffer();
143

144
    line_buffer(line_buffer&& other) = delete;
145

146
    virtual ~line_buffer();
147

148
    void set_do_preloading(bool value) { this->lb_do_preloading = value; }
660✔
149

150
    bool get_do_preloading() const { return this->lb_do_preloading; }
151

152
    /** @param fd The file descriptor that data should be pulled from. */
153
    void set_fd(auto_fd& fd);
154

155
    /** @return The file descriptor that data should be pulled from. */
156
    int get_fd() const { return this->lb_fd; }
4,711✔
157

158
    time_t get_file_time() const { return this->lb_file_time; }
720,090✔
159

160
    /**
161
     * @return The size of the file or the amount of data pulled from a pipe.
162
     */
163
    file_ssize_t get_file_size() const { return this->lb_file_size; }
3,244✔
164

165
    bool is_pipe() const { return !this->lb_seekable; }
820✔
166

167
    bool is_pipe_closed() const
187✔
168
    {
169
        return !this->lb_seekable && (this->lb_file_size != -1);
187✔
170
    }
171

172
    bool is_compressed() const { return this->lb_compressed; }
24,771✔
173

174
    bool is_header_utf8() const { return this->lb_is_utf8; }
631✔
175

176
    bool has_line_metadata() const { return this->lb_line_metadata; }
11,812✔
177

178
    file_off_t get_read_offset(file_off_t off) const
15,635✔
179
    {
180
        if (this->is_compressed()) {
15,635✔
181
            return this->lb_compressed_offset;
20✔
182
        }
183
        return off;
15,615✔
184
    }
185

186
    bool is_data_available(file_off_t off, file_off_t stat_size) const
3,899✔
187
    {
188
        if (this->is_compressed()) {
3,899✔
189
            return (this->lb_file_size == -1 || off < this->lb_file_size);
20✔
190
        }
191
        return off < stat_size;
3,879✔
192
    }
193

194
    /**
195
     * Attempt to load the next line into the buffer.
196
     *
197
     * @param prev_line The range of the previous line.
198
     * @return If the read was successful, information about the line.
199
     *   Otherwise, an error message.
200
     */
201
    Result<line_info, std::string> load_next_line(file_range prev_line = {});
202

203
    enum class scan_direction {
204
        forward,
205
        backward,
206
    };
207

208
    Result<shared_buffer_ref, std::string> read_range(
209
        file_range fr, scan_direction dir = scan_direction::forward);
210

211
    file_range get_available();
212

213
    bool is_likely_to_flush(file_range prev_line);
214

215
    void flush_at(file_off_t off)
1,092✔
216
    {
217
        if (this->in_range(off)) {
1,092✔
218
            this->lb_buffer.resize(off - this->lb_file_offset);
456✔
219
        } else {
220
            this->lb_buffer.clear();
636✔
221
        }
222
    }
1,092✔
223

224
    /** Release any resources held by this object. */
225
    void reset()
73✔
226
    {
227
        this->lb_fd.reset();
73✔
228

229
        this->lb_file_offset = 0;
73✔
230
        this->lb_file_size = (ssize_t) -1;
73✔
231
        this->lb_buffer.resize(0);
73✔
232
        this->lb_last_line_offset = -1;
73✔
233
    }
73✔
234

235
    /** Check the invariants for this object. */
236
    bool invariant() const
21,696✔
237
    {
238
        require(this->lb_buffer.size() <= this->lb_buffer.capacity());
21,696✔
239

240
        return true;
21,696✔
241
    }
242

243
    void quiesce();
244

245
    struct stats {
246
        bool empty() const
862✔
247
        {
248
            return this->s_decompressions == 0 && this->s_preads == 0
860✔
249
                && this->s_requested_preloads == 0
607✔
250
                && this->s_used_preloads == 0;
1,722✔
251
        }
252

253
        uint32_t s_decompressions{0};
254
        uint32_t s_preads{0};
255
        uint32_t s_requested_preloads{0};
256
        uint32_t s_used_preloads{0};
257
        std::array<uint32_t, 10> s_hist{};
258
    };
259

260
    struct stats consume_stats() { return std::exchange(this->lb_stats, {}); }
862✔
261

262
    size_t get_buffer_size() const { return this->lb_buffer.size(); }
262✔
263

264
    using file_header_t
265
        = mapbox::util::variant<lnav::gzip::header, lnav::piper::header>;
266

267
    const file_header_t& get_header_data() const { return this->lb_header; }
660✔
268

269
    void enable_cache();
270

271
    file_ssize_t get_piper_header_size() const
87✔
272
    {
273
        return this->lb_piper_header_size;
87✔
274
    }
275

276
    bool is_piper() const { return this->lb_piper_header_size > 0; }
5,480✔
277

278
    size_t line_count_guess() const { return this->lb_line_starts.size(); }
279

UNCOV
280
    const std::string& get_decompress_error() const
×
281
    {
UNCOV
282
        return this->lb_decompress_error;
×
283
    }
284

285
    void send_initial_load();
286

287
    [[nodiscard]] static std::future<void> cleanup_cache();
288

289
private:
290
    /**
291
     * @param off The file offset to check for in the buffer.
292
     * @return True if the given offset is cached in the buffer.
293
     */
294
    bool in_range(file_off_t off) const
187,705✔
295
    {
296
        return this->lb_file_offset <= off
187,705✔
297
            && off
373,953✔
298
            < (this->lb_file_offset + (file_ssize_t) this->lb_buffer.size());
373,953✔
299
    }
300

301
    void resize_buffer(size_t new_max);
302

303
    /**
304
     * Ensure there is enough room in the buffer to cache a range of data from
305
     * the file.  First, this method will check to see if there is enough room
306
     * from where 'start' begins in the buffer to the maximum buffer size.  If
307
     * this is not enough, the currently cached data at 'start' will be moved
308
     * to the beginning of the buffer, overwriting any cached data earlier in
309
     * the file.  Finally, if this is still not enough, the buffer will be
310
     * reallocated to make more room.
311
     *
312
     * @param start The file offset of the start of the line.
313
     * @param max_length The amount of data to be cached in the buffer.
314
     */
315
    void ensure_available(file_off_t start,
316
                          ssize_t max_length,
317
                          scan_direction dir = scan_direction::forward);
318

319
    /**
320
     * Fill the buffer with the given range of data from the file.
321
     *
322
     * @param start The file offset where data should start to be read from the
323
     * file.
324
     * @param max_length The maximum amount of data to read from the file.
325
     * @return True if any data was read from the file.
326
     */
327
    bool fill_range(file_off_t start,
328
                    ssize_t max_length,
329
                    scan_direction dir = scan_direction::forward);
330

331
    /**
332
     * After a successful fill, the cached data can be retrieved with this
333
     * method.
334
     *
335
     * @param start The file offset to retrieve cached data for.
336
     * @param avail_out On return, the amount of data currently cached at the
337
     * given offset.
338
     * @return A pointer to the start of the cached data in the internal
339
     * buffer.
340
     */
341
    const char* get_range(file_off_t start, file_ssize_t& avail_out) const
101,484✔
342
    {
343
        size_t buffer_offset = start - this->lb_file_offset;
101,484✔
344

345
        require(buffer_offset >= 0);
346
        require(this->lb_buffer.size() >= buffer_offset);
101,484✔
347

348
        const auto* retval = this->lb_buffer.at(buffer_offset);
101,484✔
349
        avail_out = this->lb_buffer.size() - buffer_offset;
101,484✔
350

351
        return retval;
101,484✔
352
    }
353

354
    bool load_next_buffer();
355

356
    using safe_gz_indexed = safe::Safe<gz_indexed>;
357

358
    shared_buffer lb_share_manager;
359

360
    auto_fd lb_fd; /*< The file to read data from. */
361
    safe_gz_indexed lb_gz_file; /*< File reader for gzipped files. */
362
    bool lb_bz_file{false}; /*< Flag set for bzip2 compressed files. */
363
    bool lb_line_metadata{false};
364
    file_ssize_t lb_piper_header_size{0};
365

366
    auto_buffer lb_buffer{auto_buffer::alloc(DEFAULT_LINE_BUFFER_SIZE)};
367
    std::optional<auto_buffer> lb_alt_buffer;
368
    std::vector<uint32_t> lb_alt_line_starts;
369
    std::vector<bool> lb_alt_line_is_utf;
370
    std::vector<bool> lb_alt_line_has_ansi;
371
    std::future<bool> lb_loader_future;
372
    std::optional<file_off_t> lb_loader_file_offset;
373

374
    file_off_t lb_compressed_offset{
375
        0}; /*< The offset into the compressed file. */
376
    file_ssize_t lb_file_size{
377
        -1}; /*<
378
              * The size of the file.  When lb_fd refers to
379
              * a pipe, this is set to the amount of data
380
              * read from the pipe when EOF is reached.
381
              */
382
    file_off_t lb_file_offset{0}; /*<
383
                                   * Data cached in the buffer comes from this
384
                                   * offset in the file.
385
                                   */
386
    time_t lb_file_time{0};
387
    bool lb_seekable{false}; /*< Flag set for seekable file descriptors. */
388
    bool lb_compressed{false};
389
    bool lb_is_utf8{true};
390
    bool lb_do_preloading{false};
391
    file_off_t lb_last_line_offset{-1}; /*< */
392

393
    std::vector<uint32_t> lb_line_starts;
394
    file_off_t lb_next_buffer_offset{0};
395
    size_t lb_next_line_start_index{0};
396
    std::vector<bool> lb_line_is_utf;
397
    std::vector<bool> lb_line_has_ansi;
398
    stats lb_stats;
399

400
    std::optional<auto_fd> lb_cached_fd;
401

402
    file_header_t lb_header{mapbox::util::no_init{}};
403

404
    std::string lb_decompress_error;
405
};
406

407
#endif
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