• 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

68.39
/src/grep_proc.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 grep_proc.cc
30
 */
31

32
#include "grep_proc.hh"
33

34
#include <errno.h>
35
#include <fcntl.h>
36
#include <signal.h>
37
#include <stdio.h>
38
#include <string.h>
39
#include <sys/wait.h>
40
#include <unistd.h>
41

42
#include "base/auto_pid.hh"
43
#include "base/itertools.enumerate.hh"
44
#include "base/lnav_log.hh"
45
#include "base/opt_util.hh"
46
#include "base/string_util.hh"
47
#include "config.h"
48
#include "lnav_util.hh"
49
#include "scn/scan.h"
50
#include "vis_line.hh"
51

52
template<typename LineType>
53
grep_proc<LineType>::grep_proc(std::shared_ptr<lnav::pcre2pp::code> code,
20✔
54
                               grep_proc_source<LineType>& gps,
55
                               std::shared_ptr<pollable_supervisor> ps)
56
    : pollable(ps, pollable::category::background), gp_pcre(code),
20✔
57
      gp_source(gps)
20✔
58
{
59
    require(this->invariant());
20✔
60

61
    gps.register_proc(this);
20✔
62
}
20✔
63

64
template<typename LineType>
65
grep_proc<LineType>::~grep_proc()
21✔
66
{
67
    this->invalidate();
20✔
68
}
21✔
69

70
template<typename LineType>
71
void
72
grep_proc<LineType>::start()
58✔
73
{
74
    require(this->invariant());
58✔
75

76
    if (this->gp_sink) {
58✔
77
        // XXX hack to make sure threads used by line_buffer are not active
78
        // before the fork.
79
        this->gp_sink->grep_quiesce();
56✔
80
    }
81

82
    log_info(
58✔
83
        "grep_proc(%p): start with highest %d", this, this->gp_highest_line);
84
    if (this->gp_child_started || this->gp_queue.empty()) {
58✔
85
        log_debug("grep_proc(%p): nothing to do?", this);
8✔
86
        return;
8✔
87
    }
88
    for (const auto& [index, elem] : lnav::itertools::enumerate(this->gp_queue))
161✔
89
    {
90
        log_info("  queue[%d]: [%d:%d)", index, elem.first, elem.second);
61✔
91
    }
92

93
    auto_pipe in_pipe(STDIN_FILENO);
50✔
94
    auto_pipe out_pipe(STDOUT_FILENO);
50✔
95
    auto_pipe err_pipe(STDERR_FILENO);
50✔
96

97
    /* Get ahold of some pipes for stdout and stderr. */
98
    if (out_pipe.open() < 0) {
50✔
UNCOV
99
        throw error(errno);
×
100
    }
101

102
    if (err_pipe.open() < 0) {
50✔
UNCOV
103
        throw error(errno);
×
104
    }
105

106
    if ((this->gp_child = fork()) < 0) {
50✔
UNCOV
107
        throw error(errno);
×
108
    }
109

110
    in_pipe.after_fork(this->gp_child);
50✔
111
    out_pipe.after_fork(this->gp_child);
50✔
112
    err_pipe.after_fork(this->gp_child);
50✔
113

114
    if (this->gp_child != 0) {
50✔
115
        log_perror(fcntl(out_pipe.read_end(), F_SETFL, O_NONBLOCK));
50✔
116
        log_perror(fcntl(out_pipe.read_end(), F_SETFD, 1));
50✔
117
        this->gp_line_buffer.set_fd(out_pipe.read_end());
50✔
118

119
        log_perror(fcntl(err_pipe.read_end(), F_SETFL, O_NONBLOCK));
50✔
120
        log_perror(fcntl(err_pipe.read_end(), F_SETFD, 1));
50✔
121
        require(this->gp_err_pipe.get() == -1);
50✔
122
        this->gp_err_pipe = std::move(err_pipe.read_end());
50✔
123
        this->gp_child_started = true;
50✔
124
        this->gp_child_queue_size = this->gp_queue.size();
50✔
125

126
        this->gp_queue.clear();
50✔
127

128
        log_debug("grep_proc(%p): started child %d", this, this->gp_child);
50✔
129
        return;
50✔
130
    }
131

132
    /* In the child... */
UNCOV
133
    lnav::pid::in_child = true;
×
134

135
    /*
136
     * Restore the default signal handlers so we don't hang around
137
     * forever if there is a problem.
138
     */
UNCOV
139
    signal(SIGINT, SIG_DFL);
×
UNCOV
140
    signal(SIGTERM, SIG_DFL);
×
141

UNCOV
142
    this->child_init();
×
143

UNCOV
144
    this->child_loop();
×
145

UNCOV
146
    _exit(0);
×
147
}
50✔
148

149
template<typename LineType>
150
void
UNCOV
151
grep_proc<LineType>::child_loop()
×
152
{
UNCOV
153
    auto md = lnav::pcre2pp::match_data::unitialized();
×
154
    char outbuf[BUFSIZ * 2];
UNCOV
155
    std::string line_value;
×
156

157
    /* Make sure buffering is on, not sure of the state in the parent. */
UNCOV
158
    if (setvbuf(stdout, outbuf, _IOFBF, BUFSIZ * 2) < 0) {
×
UNCOV
159
        perror("setvbuf");
×
160
    }
161
    lnav_log_file
162
        = make_optional_from_nullable(fopen("/tmp/lnav.grep.err", "a"));
×
163
    line_value.reserve(BUFSIZ * 2);
×
UNCOV
164
    while (!this->gp_queue.empty()) {
×
165
        LineType start_line = this->gp_queue.front().first;
×
UNCOV
166
        LineType stop_line = this->gp_queue.front().second;
×
167
        bool done = false;
×
UNCOV
168
        LineType line;
×
169

UNCOV
170
        this->gp_queue.pop_front();
×
UNCOV
171
        for (line = this->gp_source.grep_initial_line(start_line,
×
172
                                                      this->gp_highest_line);
UNCOV
173
             line != -1 && (stop_line == -1 || line < stop_line) && !done;
×
174
             this->gp_source.grep_next_line(line))
×
175
        {
UNCOV
176
            line_value.clear();
×
177
            auto val_res
UNCOV
178
                = this->gp_source.grep_value_for_line(line, line_value);
×
UNCOV
179
            if (!val_res) {
×
180
                done = true;
×
181
            } else {
UNCOV
182
                auto li = val_res.value();
×
UNCOV
183
                uint32_t re_opts = 0;
×
184
                if (li.li_utf8_scan_result.is_valid()) {
×
185
                    re_opts = PCRE2_NO_UTF_CHECK;
×
186
                }
187
                auto match_res = this->gp_pcre->capture_from(line_value)
×
188
                                     .into(md)
×
189
                                     .matches(re_opts)
190
                                     .ignore_error();
×
UNCOV
191
                if (match_res) {
×
192
                    fmt::println(stdout, FMT_STRING("{}"), (int) line);
×
193
                }
194
            }
195

196
            if (((line + 1) % 10000) == 0) {
×
197
                /* Periodically flush the buffer so the parent sees progress */
198
                this->child_batch();
×
199
            }
200
        }
201

202
        if (line != -1 && stop_line == -1) {
×
203
            // When scanning to the end of the source, we need to return the
204
            // highest line that was seen so that the next request that
205
            // continues from the end works properly.
206
            fmt::println(stdout, FMT_STRING("h{}"), line - 1);
×
207
        }
208
        this->gp_highest_line = line - 1_vl;
×
209
        this->child_term();
×
210
    }
211
}
212

213
template<typename LineType>
214
void
215
grep_proc<LineType>::cleanup()
73✔
216
{
217
    if (this->gp_child != -1 && this->gp_child != 0) {
73✔
218
        int status = 0;
50✔
219

220
        kill(this->gp_child, SIGTERM);
50✔
221
        while (waitpid(this->gp_child, &status, 0) < 0 && (errno == EINTR)) {
50✔
222
            ;
223
        }
224
        require(!WIFSIGNALED(status) || WTERMSIG(status) != SIGABRT);
50✔
225

226
        log_info("cleaned up grep child %d", this->gp_child);
50✔
227
        this->gp_child = -1;
50✔
228
        this->gp_child_started = false;
50✔
229

230
        if (this->gp_sink) {
50✔
231
            for (size_t lpc = 0; lpc < this->gp_child_queue_size; lpc++) {
109✔
232
                this->gp_sink->grep_end(*this);
60✔
233
            }
234
        }
235
    }
236

237
    if (this->gp_err_pipe != -1) {
73✔
238
        this->gp_err_pipe.reset();
24✔
239
    }
240

241
    this->gp_pipe_range.clear();
73✔
242
    this->gp_line_buffer.reset();
73✔
243

244
    ensure(this->invariant());
73✔
245

246
    if (!this->gp_queue.empty()) {
73✔
247
        this->start();
2✔
248
    }
249
}
73✔
250

251
template<typename LineType>
252
void
253
grep_proc<LineType>::dispatch_line(const string_fragment& line)
31✔
254
{
255
    require(line.is_valid());
31✔
256

257
    auto sv = line.to_string_view();
31✔
258
    auto h_scan_res = scn::scan<int>(sv, "h{}");
31✔
259
    if (h_scan_res) {
31✔
260
        this->gp_highest_line = LineType{h_scan_res->value()};
15✔
261
    } else {
262
        auto ll_scan_res = scn::scan<int>(sv, "{}");
16✔
263
        if (ll_scan_res) {
16✔
264
            this->gp_last_line = LineType{ll_scan_res->value()};
16✔
265
            /* Starting a new line with matches. */
266
            ensure(this->gp_last_line >= 0);
16✔
267
            /* Pass the match offsets to the sink delegate. */
268
            if (this->gp_sink != nullptr) {
16✔
269
                this->gp_sink->grep_match(*this, this->gp_last_line);
16✔
270
            }
271
        } else {
UNCOV
272
            log_error("bad line from child -- %s", line);
×
273
        }
274
    }
275
}
31✔
276

277
template<typename LineType>
278
void
279
grep_proc<LineType>::check_poll_set(const std::vector<struct pollfd>& pollfds)
51✔
280
{
281
    require(this->invariant());
51✔
282

283
    if (this->gp_err_pipe != -1 && pollfd_ready(pollfds, this->gp_err_pipe)) {
51✔
284
        char buffer[1024 + 1];
285
        ssize_t rc;
286

287
        rc = read(this->gp_err_pipe, buffer, sizeof(buffer) - 1);
26✔
288
        if (rc > 0) {
26✔
289
            static const char* PREFIX = ": ";
290

UNCOV
291
            buffer[rc] = '\0';
×
UNCOV
292
            if (strncmp(buffer, PREFIX, strlen(PREFIX)) == 0) {
×
293
                char* lf;
294

UNCOV
295
                if ((lf = strchr(buffer, '\n')) != nullptr) {
×
UNCOV
296
                    *lf = '\0';
×
297
                }
UNCOV
298
                if (this->gp_control != nullptr) {
×
UNCOV
299
                    this->gp_control->grep_error(&buffer[strlen(PREFIX)]);
×
300
                }
301
            }
302
        } else if (rc == 0) {
26✔
303
            this->gp_err_pipe.reset();
26✔
304
        }
305
    }
306

307
    if (this->gp_line_buffer.get_fd() != -1
51✔
308
        && pollfd_ready(pollfds, this->gp_line_buffer.get_fd()))
51✔
309
    {
310
        try {
311
            static const int MAX_LOOPS = 100;
312

313
            int loop_count = 0;
30✔
314
            bool drained = false;
30✔
315

316
            while (loop_count < MAX_LOOPS) {
122✔
317
                auto load_result
61✔
318
                    = this->gp_line_buffer.load_next_line(this->gp_pipe_range);
319

320
                if (load_result.isErr()) {
61✔
UNCOV
321
                    log_error("failed to read from grep_proc child: %s",
×
322
                              load_result.unwrapErr().c_str());
323
                    break;
×
324
                }
325

326
                auto li = load_result.unwrap();
61✔
327

328
                if (li.li_file_range.empty()) {
61✔
329
                    drained = true;
30✔
330
                    break;
30✔
331
                }
332

333
                this->gp_pipe_range = li.li_file_range;
31✔
334
                this->gp_line_buffer.read_range(li.li_file_range)
31✔
335
                    .then([this](auto sbr) {
62✔
336
                        sbr.rtrim(is_line_ending);
31✔
337
                        this->dispatch_line(sbr.to_string_fragment());
31✔
338
                    });
339

340
                loop_count += 1;
31✔
341
            }
342

343
            if (this->gp_sink != nullptr) {
30✔
344
                this->gp_sink->grep_end_batch(*this);
30✔
345
            }
346

347
            if (drained && this->gp_line_buffer.is_pipe_closed()) {
30✔
348
                this->cleanup();
29✔
349
            }
350
        } catch (line_buffer::error& e) {
×
UNCOV
351
            this->cleanup();
×
352
        }
353
    }
354

355
    ensure(this->invariant());
51✔
356
}
51✔
357

358
template<typename LineType>
359
grep_proc<LineType>&
360
grep_proc<LineType>::invalidate()
44✔
361
{
362
    if (this->gp_sink) {
44✔
363
        for (size_t lpc = 0; lpc < this->gp_queue.size(); lpc++) {
49✔
364
            this->gp_sink->grep_end(*this);
6✔
365
        }
366
    }
367
    this->gp_queue.clear();
44✔
368
    this->cleanup();
44✔
369
    return *this;
44✔
370
}
371

372
template<typename LineType>
373
void
374
grep_proc<LineType>::update_poll_set(std::vector<struct pollfd>& pollfds)
103✔
375
{
376
    if (this->gp_line_buffer.get_fd() != -1) {
103✔
377
        pollfds.push_back(
41✔
378
            (struct pollfd) {this->gp_line_buffer.get_fd(), POLLIN, 0});
41✔
379
    }
380
    if (this->gp_err_pipe.get() != -1) {
103✔
381
        pollfds.push_back((struct pollfd) {this->gp_err_pipe, POLLIN, 0});
41✔
382
    }
383
}
103✔
384

385
template class grep_proc<vis_line_t>;
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