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

tstack / lnav / 22507085525-2793

27 Feb 2026 10:49PM UTC coverage: 68.948% (-0.02%) from 68.966%
22507085525-2793

push

github

tstack
fix mixup of O_CLOEXEC with FD_CLOEXEC

52007 of 75429 relevant lines covered (68.95%)

440500.48 hits per line

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

58.35
/src/file_collection.cc
1
/**
2
 * Copyright (c) 2020, 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 file_collection.cc
30
 */
31

32
#include <map>
33
#include <memory>
34
#include <mutex>
35
#include <unordered_map>
36

37
#include "file_collection.hh"
38

39
#include <glob.h>
40

41
#include "base/fs_util.hh"
42
#include "base/humanize.network.hh"
43
#include "base/isc.hh"
44
#include "base/itertools.hh"
45
#include "base/opt_util.hh"
46
#include "base/string_util.hh"
47
#include "config.h"
48
#include "file_converter_manager.hh"
49
#include "logfile.hh"
50
#include "service_tags.hh"
51
#include "tailer/tailer.looper.hh"
52

53
static std::mutex REALPATH_CACHE_MUTEX;
54
static std::unordered_map<std::string, std::string> REALPATH_CACHE;
55

56
void
57
child_poller::send_sigint()
×
58
{
59
    if (this->cp_child) {
×
60
        kill(this->cp_child->in(), SIGINT);
×
61
    }
62
}
63

64
child_poll_result_t
65
child_poller::poll(file_collection& fc)
7✔
66
{
67
    if (!this->cp_child) {
7✔
68
        return child_poll_result_t::FINISHED;
×
69
    }
70

71
    auto poll_res = std::move(this->cp_child.value()).poll();
7✔
72
    this->cp_child = std::nullopt;
7✔
73
    return poll_res.match(
14✔
74
        [this](auto_pid<process_state::running>& alive) {
×
75
            this->cp_child = std::move(alive);
1✔
76
            return child_poll_result_t::ALIVE;
1✔
77
        },
78
        [this, &fc](auto_pid<process_state::finished>& finished) {
7✔
79
            require(this->cp_finalizer);
6✔
80

81
            this->cp_finalizer(fc, finished);
6✔
82
            this->cp_exit_status = finished.exit_status();
6✔
83
            return child_poll_result_t::FINISHED;
6✔
84
        });
7✔
85
}
7✔
86

87
file_collection::limits_t::limits_t()
564✔
88
{
89
    static constexpr rlim_t RESERVED_FDS = 32;
90
    static constexpr rlim_t HIGH_FD = 24;
91

92
    struct rlimit rl;
93

94
    if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
564✔
95
        this->l_fds = rl.rlim_cur;
564✔
96
    } else {
97
        log_error("getrlimit() failed -- %s", strerror(errno));
×
98

99
        this->l_fds = 8192;
×
100
    }
101

102
    if (this->l_fds < RESERVED_FDS) {
564✔
103
        this->l_high_fd = this->l_fds;
×
104
        this->l_open_files = this->l_fds;
×
105
    } else {
106
        this->l_high_fd = this->l_fds - HIGH_FD;
564✔
107
        this->l_open_files = this->l_fds - RESERVED_FDS;
564✔
108
    }
109

110
    log_info("fd limit: %zu; open file limit: %zu",
564✔
111
             (size_t) this->l_fds,
112
             (size_t) this->l_open_files);
113
}
564✔
114

115
const file_collection::limits_t&
116
file_collection::get_limits()
3,478✔
117
{
118
    static const limits_t INSTANCE;
3,478✔
119

120
    return INSTANCE;
3,478✔
121
}
122

123
void
124
file_collection::close_files(const std::vector<std::shared_ptr<logfile>>& files)
577✔
125
{
126
    for (const auto& lf : files) {
1,209✔
127
        auto actual_path_opt = lf->get_actual_path();
632✔
128

129
        if (actual_path_opt) {
632✔
130
            std::lock_guard<std::mutex> lg(REALPATH_CACHE_MUTEX);
622✔
131
            auto path_str = actual_path_opt.value().string();
622✔
132

133
            for (auto iter = REALPATH_CACHE.begin();
622✔
134
                 iter != REALPATH_CACHE.end();)
1,338✔
135
            {
136
                if (iter->first == path_str || iter->second == path_str) {
716✔
137
                    iter = REALPATH_CACHE.erase(iter);
622✔
138
                } else {
139
                    ++iter;
94✔
140
                }
141
            }
142
        } else {
622✔
143
            this->fc_file_names.erase(lf->get_filename());
10✔
144
        }
145
        auto file_iter
146
            = std::find(this->fc_files.begin(), this->fc_files.end(), lf);
632✔
147
        if (file_iter != this->fc_files.end()) {
632✔
148
            this->fc_files.erase(file_iter);
632✔
149
        }
150
    }
632✔
151
    this->fc_files_generation += 1;
577✔
152

153
    this->regenerate_unique_file_names();
577✔
154
}
577✔
155

156
void
157
file_collection::regenerate_unique_file_names()
1,805✔
158
{
159
    unique_path_generator upg;
1,805✔
160

161
    for (const auto& lf : this->fc_files) {
3,138✔
162
        upg.add_source(lf);
1,333✔
163
    }
164

165
    upg.generate();
1,805✔
166

167
    this->fc_largest_path_length = 0;
1,805✔
168
    {
169
        safe::ReadAccess<safe_name_to_stubs> errs(*this->fc_name_to_stubs);
1,805✔
170

171
        for (const auto& pair : *errs) {
1,805✔
172
            auto path = std::filesystem::path(pair.first).filename().string();
×
173

174
            if (path.length() > this->fc_largest_path_length) {
×
175
                this->fc_largest_path_length = path.length();
×
176
            }
177
        }
178
    }
1,805✔
179
    for (const auto& lf : this->fc_files) {
3,138✔
180
        const auto& path = lf->get_unique_path();
1,333✔
181

182
        if (path.native().length() > this->fc_largest_path_length) {
1,333✔
183
            this->fc_largest_path_length = path.native().length();
1,219✔
184
        }
185
    }
186
    for (const auto& pair : this->fc_other_files) {
1,860✔
187
        switch (pair.second.ofd_format) {
55✔
188
            case file_format_t::UNKNOWN:
55✔
189
            case file_format_t::UNSUPPORTED:
190
            case file_format_t::ARCHIVE:
191
            case file_format_t::MULTIPLEXED:
192
            case file_format_t::SQLITE_DB: {
193
                auto bn = std::filesystem::path(pair.first).filename().string();
55✔
194
                if (bn.length() > this->fc_largest_path_length) {
55✔
195
                    this->fc_largest_path_length = bn.length();
49✔
196
                }
197
                break;
55✔
198
            }
55✔
199
            case file_format_t::REMOTE: {
×
200
                if (pair.first.length() > this->fc_largest_path_length) {
×
201
                    this->fc_largest_path_length = pair.first.length();
×
202
                }
203
                break;
×
204
            }
205
        }
206
    }
207
}
1,805✔
208

209
void
210
file_collection::merge(file_collection& other)
3,697✔
211
{
212
    bool do_regen = !other.fc_files.empty() || !other.fc_other_files.empty()
2,495✔
213
        || !other.fc_name_to_stubs->readAccess()->empty();
6,192✔
214

215
    this->fc_recursive = this->fc_recursive || other.fc_recursive;
3,697✔
216
    this->fc_rotated = this->fc_rotated || other.fc_rotated;
3,697✔
217
    if (other.fc_files_high_mark) {
3,697✔
218
        if (this->fc_files_high_mark) {
×
219
            this->fc_files_high_mark
220
                = std::min(this->fc_files_high_mark.value(),
×
221
                           other.fc_files_high_mark.value());
×
222
        } else {
223
            this->fc_files_high_mark = other.fc_files_high_mark;
×
224
        }
225
    }
226

227
    this->fc_synced_files.insert(other.fc_synced_files.begin(),
3,697✔
228
                                 other.fc_synced_files.end());
229

230
    std::map<std::string, file_stub_info> new_stubs;
3,697✔
231
    {
232
        safe::ReadAccess<safe_name_to_stubs> errs(*other.fc_name_to_stubs);
3,697✔
233

234
        new_stubs.insert(errs->cbegin(), errs->cend());
3,697✔
235
    }
3,697✔
236
    {
237
        safe::WriteAccess<safe_name_to_stubs> errs(*this->fc_name_to_stubs);
3,697✔
238

239
        errs->insert(new_stubs.begin(), new_stubs.end());
3,697✔
240
    }
3,697✔
241
    if (!other.fc_file_names.empty()) {
3,697✔
242
        this->fc_files_generation += 1;
34✔
243
    }
244
    if (this->fc_file_names.empty()) {
3,697✔
245
        this->fc_file_names = other.fc_file_names;
979✔
246
    } else {
247
        for (const auto& fn_pair : other.fc_file_names) {
2,730✔
248
            this->fc_file_names[fn_pair.first] = fn_pair.second;
12✔
249
        }
250
    }
251
    if (!other.fc_files.empty()) {
3,697✔
252
        for (const auto& lf : other.fc_files) {
2,459✔
253
            this->fc_name_to_stubs->writeAccess()->erase(lf->get_filename());
1,257✔
254
        }
255
        this->fc_files.insert(
2,404✔
256
            this->fc_files.end(), other.fc_files.begin(), other.fc_files.end());
1,202✔
257
        this->fc_files_generation += 1;
1,202✔
258
    }
259
    for (auto& pair : other.fc_renamed_files) {
3,697✔
260
        pair.first->set_filename(pair.second);
×
261
    }
262
    this->fc_closed_files.insert(other.fc_closed_files.begin(),
3,697✔
263
                                 other.fc_closed_files.end());
264
    this->fc_other_files.insert(other.fc_other_files.begin(),
3,697✔
265
                                other.fc_other_files.end());
266
    if (!other.fc_child_pollers.empty()) {
3,697✔
267
        this->fc_child_pollers.insert(
×
268
            this->fc_child_pollers.begin(),
×
269
            std::make_move_iterator(other.fc_child_pollers.begin()),
270
            std::make_move_iterator(other.fc_child_pollers.end()));
271
        other.fc_child_pollers.clear();
×
272
    }
273

274
    if (do_regen) {
3,697✔
275
        this->regenerate_unique_file_names();
1,224✔
276
    }
277
}
3,697✔
278

279
/**
280
 * Functor used to compare files based on their device and inode number.
281
 */
282
struct same_file {
283
    explicit same_file(const std::filesystem::path& filename,
875✔
284
                       const struct stat& stat)
285
        : sf_filename(filename), sf_stat(stat)
875✔
286
    {
287
    }
875✔
288

289
    /**
290
     * Compare the given log file against the 'stat' given in the constructor.
291
     * @param  lf The log file to compare.
292
     * @return    True if the dev/inode values in the stat given in the
293
     *   constructor matches the stat in the logfile object.
294
     */
295
    bool operator()(const std::shared_ptr<logfile>& lf) const
355✔
296
    {
297
        if (lf->is_closed()) {
355✔
298
            return false;
1✔
299
        }
300

301
        if (lf->get_actual_path()
708✔
302
            && lf->get_actual_path().value() == this->sf_filename)
708✔
303
        {
304
            return true;
242✔
305
        }
306

307
        const auto& lf_loo = lf->get_open_options();
112✔
308

309
        if (lf_loo.loo_temp_dev != 0
112✔
310
            && this->sf_stat.st_dev == lf_loo.loo_temp_dev
×
311
            && this->sf_stat.st_ino == lf_loo.loo_temp_ino)
×
312
        {
313
            return true;
×
314
        }
315

316
        return this->sf_stat.st_dev == lf->get_stat().st_dev
112✔
317
            && this->sf_stat.st_ino == lf->get_stat().st_ino;
112✔
318
    }
319

320
    const std::filesystem::path& sf_filename;
321
    const struct stat& sf_stat;
322
};
323

324
/**
325
 * Try to load the given file as a log file.  If the file has not already been
326
 * loaded, it will be loaded.  If the file has already been loaded, the file
327
 * name will be updated.
328
 *
329
 * @param filename The file name to check.
330
 * @param fd       An already-opened descriptor for 'filename'.
331
 * @param required Specifies whether or not the file must exist and be valid.
332
 */
333
std::optional<std::future<file_collection>>
334
file_collection::watch_logfile(const std::string& filename,
888✔
335
                               logfile_open_options& loo,
336
                               bool required)
337
{
338
    static auto op = lnav_operation{__FUNCTION__};
888✔
339

340
    struct stat st;
341
    int rc;
342
    auto op_guard = lnav_opid_guard::internal(op);
888✔
343
    auto filename_key = loo.loo_filename.empty() ? filename : loo.loo_filename;
888✔
344
    if (this->fc_closed_files.count(filename)
888✔
345
        || this->fc_closed_files.count(filename_key))
888✔
346
    {
347
        log_trace("%s: file is closed, ignore", filename.c_str());
4✔
348
        return std::nullopt;
4✔
349
    }
350

351
    rc = stat(filename.c_str(), &st);
884✔
352

353
    if (rc == 0) {
884✔
354
        if (S_ISDIR(st.st_mode) && this->fc_recursive) {
884✔
355
            std::string wilddir = filename + "/*";
9✔
356

357
            if (this->fc_file_names.find(wilddir) == this->fc_file_names.end())
9✔
358
            {
359
                file_collection retval;
2✔
360

361
                retval.fc_file_names.insert2(
2✔
362
                    wilddir,
363
                    logfile_open_options()
4✔
364
                        .with_non_utf_visibility(false)
2✔
365
                        .with_visible_size_limit(256 * 1024)
2✔
366
                        .with_time_range(loo.loo_time_range));
2✔
367
                return lnav::futures::make_ready_future(std::move(retval));
2✔
368
            }
2✔
369
            return std::nullopt;
7✔
370
        }
9✔
371
        if (!S_ISREG(st.st_mode)) {
875✔
372
            if (required) {
×
373
                rc = -1;
×
374
                errno = EINVAL;
×
375
            } else {
376
                return std::nullopt;
×
377
            }
378
        }
379
        {
380
            safe::WriteAccess<safe_name_to_stubs> errs(*this->fc_name_to_stubs);
875✔
381

382
            auto err_iter = errs->find(filename_key);
875✔
383
            if (err_iter != errs->end()) {
875✔
384
                if (err_iter->second.fsi_mtime != st.st_mtime) {
×
385
                    log_debug("clearing error info for file: %s",
×
386
                              filename_key.c_str());
387
                    errs->erase(err_iter);
×
388
                }
389
            }
390
        }
875✔
391
    }
392
    if (rc == -1) {
875✔
393
        if (required) {
×
394
            auto ec = lnav::from_errno();
×
395
            log_error("failed to open required file: %s -- %s",
×
396
                      filename.c_str(),
397
                      ec.message().c_str());
398
            file_collection retval;
×
399
            auto um = lnav::console::user_message::error(
×
400
                          attr_line_t("failed to open required file ")
×
401
                              .append_quoted(lnav::roles::file(filename)))
×
402
                          .with_reason(ec.message());
×
403
            retval.fc_name_to_stubs->writeAccess()->emplace(filename,
×
404
                                                            file_stub_info{
×
405
                                                                filename,
406
                                                                time(nullptr),
×
407
                                                                um.move(),
408
                                                            });
409
            return lnav::futures::make_ready_future(std::move(retval));
×
410
        }
411
        return std::nullopt;
×
412
    }
413

414
    if (this->fc_new_stats | lnav::itertools::find_if([&st](const auto& elem) {
1,750✔
415
            return st.st_ino == elem.st_ino && st.st_dev == elem.st_dev;
176✔
416
        }))
875✔
417
    {
418
        log_trace("same stat: %s", filename.c_str());
×
419
        // this file is probably a link that we have already scanned in this
420
        // pass.
421
        return std::nullopt;
×
422
    }
423

424
    this->fc_new_stats.emplace_back(st);
875✔
425

426
    const auto fn_path = std::filesystem::path(filename);
875✔
427
    const auto file_iter = std::find_if(
875✔
428
        this->fc_files.begin(), this->fc_files.end(), same_file(fn_path, st));
429

430
    if (file_iter == this->fc_files.end()) {
875✔
431
        if (this->fc_other_files.find(filename) != this->fc_other_files.end()) {
633✔
432
            return std::nullopt;
×
433
        }
434

435
        if (this->fc_files_high_mark
633✔
436
            && this->fc_files.size() >= this->fc_files_high_mark.value())
633✔
437
        {
438
            log_trace("too many open files, cannot open %s", filename.c_str());
×
439
            return std::nullopt;
×
440
        }
441

442
        require(this->fc_progress.get() != nullptr);
633✔
443

444
        auto func = [filename,
633✔
445
                     st,
446
                     loo,
447
                     prog = this->fc_progress,
633✔
448
                     errs = this->fc_name_to_stubs]() mutable {
633✔
449
            static auto inner_op = lnav_operation{"watch_new_file"};
633✔
450

451
            file_collection retval;
633✔
452

453
            {
454
                safe::ReadAccess<safe_name_to_stubs> errs_inner(*errs);
633✔
455

456
                if (errs_inner->find(filename) != errs_inner->end()) {
633✔
457
                    // The file is broken, no reason to try and reopen
458
                    return retval;
×
459
                }
460
            }
633✔
461

462
            auto op_guard = lnav_opid_guard::internal(inner_op);
633✔
463

464
            log_debug("watching new file: %s", filename.c_str());
633✔
465

466
            auto ff_res = detect_file_format(filename);
633✔
467

468
            loo.loo_file_format = ff_res.dffr_file_format;
633✔
469
            switch (ff_res.dffr_file_format) {
633✔
470
                case file_format_t::SQLITE_DB:
1✔
471
                case file_format_t::UNSUPPORTED:
472
                    retval.fc_other_files[filename].ofd_format
2✔
473
                        = ff_res.dffr_file_format;
1✔
474
                    retval.fc_other_files[filename].ofd_details
1✔
475
                        = ff_res.dffr_details;
1✔
476
                    break;
1✔
477

478
                case file_format_t::MULTIPLEXED: {
10✔
479
                    log_info("%s: file is multiplexed, creating piper",
10✔
480
                             filename.c_str());
481

482
                    auto open_res
483
                        = lnav::filesystem::open_file(filename, O_RDONLY);
10✔
484
                    if (open_res.isOk()) {
10✔
485
                        auto looper_options = lnav::piper::options{};
10✔
486
                        looper_options.with_follow(loo.loo_follow);
10✔
487
                        auto create_res
488
                            = lnav::piper::create_looper(filename,
10✔
489
                                                         open_res.unwrap(),
10✔
490
                                                         auto_fd{-1},
20✔
491
                                                         looper_options);
10✔
492

493
                        if (create_res.isOk()) {
10✔
494
                            auto& ofd = retval.fc_other_files[filename];
10✔
495

496
                            ofd.ofd_format = ff_res.dffr_file_format;
10✔
497
                            ofd.ofd_details = ff_res.dffr_details;
10✔
498
                            retval.fc_file_names[filename] = loo;
10✔
499
                            retval.fc_file_names[filename].with_piper(
20✔
500
                                create_res.unwrap());
20✔
501
                        }
502
                    }
10✔
503
                    break;
10✔
504
                }
10✔
505

506
                case file_format_t::ARCHIVE: {
×
507
                    std::optional<
508
                        std::list<archive_manager::extract_progress>::iterator>
509
                        prog_iter_opt;
×
510

511
                    if (loo.loo_source == logfile_name_source::ARCHIVE) {
×
512
                        // Don't try to open nested archives
513
                        return retval;
×
514
                    }
515

516
                    auto res = archive_manager::walk_archive_files(
517
                        filename,
×
518
                        [prog, &prog_iter_opt](const auto& path,
×
519
                                               const auto total) {
520
                            safe::WriteAccess<safe_scan_progress> sp(*prog);
×
521

522
                            prog_iter_opt | [&sp](auto prog_iter) {
×
523
                                sp->sp_extractions.erase(prog_iter);
×
524
                            };
525
                            auto prog_iter = sp->sp_extractions.emplace(
×
526
                                sp->sp_extractions.begin(), path, total);
×
527
                            prog_iter_opt = prog_iter;
×
528

529
                            return &(*prog_iter);
×
530
                        },
×
531
                        [&filename, &retval, &loo](const auto& tmp_path,
×
532
                                                   const auto& entry) {
533
                            auto arc_path = std::filesystem::relative(
×
534
                                entry.path(), tmp_path);
535
                            auto custom_name = filename / arc_path;
×
536
                            bool is_visible = true;
×
537

538
                            if (entry.file_size() == 0) {
×
539
                                log_info("hiding empty archive file: %s",
×
540
                                         entry.path().c_str());
541
                                is_visible = false;
×
542
                            }
543

544
                            log_info("adding file from archive: %s/%s",
×
545
                                     filename.c_str(),
546
                                     entry.path().c_str());
547
                            retval.fc_file_names[entry.path().string()]
×
548
                                .with_filename(custom_name.string())
×
549
                                .with_source(logfile_name_source::ARCHIVE)
×
550
                                .with_visibility(is_visible)
×
551
                                .with_non_utf_visibility(false)
×
552
                                .with_visible_size_limit(256 * 1024)
×
553
                                .with_time_range(loo.loo_time_range);
×
554
                        });
×
555
                    if (res.isErr()) {
×
556
                        log_error("archive extraction failed: %s",
×
557
                                  res.unwrapErr().c_str());
558
                        auto um = lnav::console::user_message::error(
×
559
                                      attr_line_t("failed to extract archive ")
×
560
                                          .append_quoted(
×
561
                                              lnav::roles::file(filename)))
×
562
                                      .with_reason(res.unwrapErr());
×
563
                        retval.clear();
×
564
                        retval.fc_name_to_stubs->writeAccess()->emplace(
×
565
                            filename,
×
566
                            file_stub_info{
×
567
                                filename,
×
568
                                st.st_mtime,
×
569
                                um.move(),
570
                            });
571
                    } else {
×
572
                        auto& ofd = retval.fc_other_files[filename];
×
573
                        ofd.ofd_format = ff_res.dffr_file_format;
×
574
                        ofd.ofd_details = ff_res.dffr_details;
×
575
                    }
576
                    {
577
                        prog_iter_opt | [&prog](auto prog_iter) {
×
578
                            prog->writeAccess()->sp_extractions.erase(
×
579
                                prog_iter);
580
                        };
581
                    }
582
                    break;
×
583
                }
584

585
                default: {
622✔
586
                    auto filename_to_open = filename;
622✔
587

588
                    loo.loo_match_details = ff_res.dffr_details;
622✔
589
                    auto eff = detect_mime_type(filename);
622✔
590

591
                    if (eff) {
622✔
592
                        auto cr = file_converter_manager::convert(eff.value(),
×
593
                                                                  filename);
×
594

595
                        if (cr.isErr()) {
×
596
                            auto um = lnav::console::user_message::error(
×
597
                                          attr_line_t("failed to convert file ")
×
598
                                              .append_quoted(
×
599
                                                  lnav::roles::file(filename)))
×
600
                                          .with_reason(cr.unwrapErr());
×
601
                            retval.fc_name_to_stubs->writeAccess()->emplace(
×
602
                                filename,
×
603
                                file_stub_info{
×
604
                                    filename,
×
605
                                    st.st_mtime,
×
606
                                    um.move(),
607
                                });
608
                            break;
×
609
                        }
610

611
                        auto convert_res = cr.unwrap();
×
612
                        auto poller = std::make_shared<child_poller>(
613
                            eff->eff_converter,
×
614
                            filename,
×
615
                            std::move(convert_res.cr_child),
×
616
                            [filename,
×
617
                             st,
618
                             error_queue = convert_res.cr_error_queue](
619
                                auto& fc, auto& child) {
620
                                if (child.was_normal_exit()
×
621
                                    && child.exit_status() == EXIT_SUCCESS)
×
622
                                {
623
                                    log_info("converter[%d] exited normally",
×
624
                                             child.in());
625
                                    return;
×
626
                                }
627
                                log_error("converter[%d] exited with %d",
×
628
                                          child.in(),
629
                                          child.status());
630
                                auto reason = fmt::format(
×
631
                                    FMT_STRING("{}"),
×
632
                                    fmt::join(*error_queue, "\n"));
×
633
                                auto um
×
634
                                    = lnav::console::user_message::error(
635
                                          attr_line_t("failed to convert file ")
×
636
                                              .append_quoted(
×
637
                                                  lnav::roles::file(filename)))
×
638
                                          .with_reason(reason);
×
639
                                fc.fc_name_to_stubs->writeAccess()->emplace(
×
640
                                    filename,
×
641
                                    file_stub_info{
642
                                        filename,
×
643
                                        st.st_mtime,
×
644
                                        um.move(),
645
                                    });
646
                            });
×
647
                        retval.fc_child_pollers.emplace_back(poller);
×
648
                        loo.with_filename(filename);
×
649
                        loo.with_stat_for_temp(st);
×
650
                        loo.with_child_poller(poller);
×
651
                        loo.loo_format_name = eff->eff_format_name;
×
652
                        filename_to_open = convert_res.cr_destination;
×
653
                    }
654

655
                    log_info("loading new file: filename=%s", filename.c_str());
622✔
656

657
                    auto open_res = logfile::open(filename_to_open, loo);
622✔
658
                    if (open_res.isOk()) {
622✔
659
                        retval.fc_files.push_back(open_res.unwrap());
622✔
660
                    } else {
661
                        auto um = lnav::console::user_message::error(
×
662
                                      attr_line_t("failed to open file ")
×
663
                                          .append_quoted(lnav::roles::file(
×
664
                                              filename_to_open)))
665
                                      .with_reason(open_res.unwrapErr());
×
666
                        retval.fc_name_to_stubs->writeAccess()->emplace(
×
667
                            filename,
×
668
                            file_stub_info{
×
669
                                filename,
×
670
                                st.st_mtime,
×
671
                                um.move(),
672
                            });
673
                    }
674
                    break;
622✔
675
                }
622✔
676
            }
677

678
            return retval;
633✔
679
        };
1,266✔
680

681
        return std::async(std::launch::async, std::move(func));
633✔
682
    }
633✔
683

684
    auto lf = *file_iter;
242✔
685

686
    if (lf->is_valid_filename() && lf->get_filename() != filename) {
242✔
687
        /* The file is already loaded, but has been found under a different
688
         * name.  We just need to update the stored file name.
689
         */
690
        file_collection retval;
×
691

692
        log_info("renamed file: %s -> %s",
×
693
                 lf->get_filename().c_str(),
694
                 filename.c_str());
695
        retval.fc_renamed_files.emplace_back(lf, filename);
×
696
        return lnav::futures::make_ready_future(std::move(retval));
×
697
    }
698

699
    return std::nullopt;
242✔
700
}
888✔
701

702
/**
703
 * Expand a glob pattern and call watch_logfile with the file names that match
704
 * the pattern.
705
 * @param path     The glob pattern to expand.
706
 * @param required Passed to watch_logfile.
707
 */
708
void
709
file_collection::expand_filename(
2,843✔
710
    lnav::futures::future_queue<file_collection>& fq,
711
    const std::string& path,
712
    logfile_open_options& loo,
713
    bool required)
714
{
715
    static_root_mem<glob_t, globfree> gl;
2,843✔
716

717
    {
718
        std::lock_guard lg(REALPATH_CACHE_MUTEX);
2,843✔
719

720
        if (REALPATH_CACHE.find(path) != REALPATH_CACHE.end()) {
2,843✔
721
            return;
2,057✔
722
        }
723
    }
2,843✔
724

725
    if (lnav::filesystem::is_url(path)) {
786✔
726
        return;
×
727
    }
728

729
    auto filename_key = loo.loo_filename.empty() ? path : loo.loo_filename;
786✔
730
#if defined(__MSYS__)
731
    auto win_path = lnav::filesystem::escape_glob_for_win(path);
732
    auto glob_rc = glob(win_path.c_str(), GLOB_NOCHECK, nullptr, gl.inout());
733
#else
734
    auto glob_rc = glob(path.c_str(), GLOB_NOCHECK, nullptr, gl.inout());
786✔
735
#endif
736
    if (glob_rc == 0) {
786✔
737
        if (gl->gl_pathc == 1 /*&& gl.gl_matchc == 0*/) {
786✔
738
            /* It's a pattern that doesn't match any files
739
             * yet, allow it through since we'll load it in
740
             * dynamically.
741
             */
742
            if (access(gl->gl_pathv[0], F_OK) == -1) {
702✔
743
                auto rp_opt = humanize::network::path::from_str(path);
9✔
744
                if (rp_opt) {
9✔
745
                    auto iter = this->fc_other_files.find(path);
×
746
                    auto rp = *rp_opt;
×
747

748
                    if (iter != this->fc_other_files.end()) {
×
749
                        return;
×
750
                    }
751

752
                    file_collection retval;
×
753
                    logfile_open_options_base loo_base{loo};
×
754

755
                    isc::to<tailer::looper&, services::remote_tailer_t>().send(
×
756
                        [rp, loo_base](auto& tlooper) {
×
757
                            tlooper.add_remote(rp, loo_base);
×
758
                        });
×
759
                    retval.fc_other_files[path] = file_format_t::REMOTE;
×
760
                    {
761
                        this->fc_progress->writeAccess()
×
762
                            ->sp_tailers[fmt::to_string(rp.home())]
×
763
                            .tp_message = "Initializing...";
×
764
                    }
765

766
                    fq.push_back(
×
767
                        lnav::futures::make_ready_future(std::move(retval)));
×
768
                    return;
×
769
                }
770

771
                required = false;
9✔
772
            }
9✔
773
        }
774
        if (gl->gl_pathc > 1 || strcmp(path.c_str(), gl->gl_pathv[0]) != 0) {
786✔
775
            required = false;
232✔
776
        }
777

778
        std::lock_guard lg(REALPATH_CACHE_MUTEX);
786✔
779
        for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
1,686✔
780
            auto path_str = std::string(gl->gl_pathv[lpc]);
900✔
781
            auto iter = REALPATH_CACHE.find(path_str);
900✔
782

783
            if (iter == REALPATH_CACHE.end()) {
900✔
784
                auto_mem<char> abspath;
646✔
785

786
                if ((abspath = realpath(gl->gl_pathv[lpc], nullptr)) == nullptr)
646✔
787
                {
788
                    auto* errmsg = strerror(errno);
9✔
789

790
                    if (required) {
9✔
791
                        fprintf(stderr,
×
792
                                "Cannot find file: %s -- %s",
793
                                gl->gl_pathv[lpc],
×
794
                                errmsg);
795
                    } else if (loo.loo_filename.empty()) {
9✔
796
                        auto in_map
797
                            = this->fc_name_to_stubs->readAccess()->count(
×
798
                                  path_str)
799
                            > 0;
×
800

801
                        if (!in_map) {
×
802
                            file_collection retval;
×
803
                            if (gl->gl_pathc == 1 && path == path_str) {
×
804
                                log_error("failed to find path: %s (%s) -- %s",
×
805
                                          filename_key.c_str(),
806
                                          path.c_str(),
807
                                          errmsg);
808
                                auto um
809
                                    = lnav::console::user_message::error(
×
810
                                          attr_line_t("failed to find path of ")
×
811
                                              .append_quoted(lnav::roles::file(
×
812
                                                  filename_key)))
813
                                          .with_reason(errmsg);
×
814
                                retval.fc_name_to_stubs->writeAccess()->emplace(
×
815
                                    filename_key,
816
                                    file_stub_info{
×
817
                                        filename_key,
818
                                        time(nullptr),
×
819
                                        um.move(),
820
                                    });
821
                            } else {
×
822
                                log_error("failed to find path: %s -- %s",
×
823
                                          path_str.c_str(),
824
                                          errmsg);
825
                                auto um
826
                                    = lnav::console::user_message::error(
×
827
                                          attr_line_t("failed to find path of ")
×
828
                                              .append_quoted(
×
829
                                                  lnav::roles::file(path_str)))
×
830
                                          .with_reason(errmsg);
×
831
                                retval.fc_name_to_stubs->writeAccess()->emplace(
×
832
                                    path_str,
833
                                    file_stub_info{
×
834
                                        path_str,
835
                                        time(nullptr),
×
836
                                        um.move(),
837
                                    });
838
                            }
839
                            fq.push_back(lnav::futures::make_ready_future(
×
840
                                std::move(retval)));
×
841
                        }
842
                    }
843
                    continue;
9✔
844
                }
9✔
845

846
                auto p = REALPATH_CACHE.emplace(path_str, abspath.in());
637✔
847

848
                iter = p.first;
637✔
849
            }
646✔
850

851
            if (required || access(iter->second.c_str(), R_OK) == 0) {
891✔
852
                auto future_opt = watch_logfile(iter->second, loo, required);
888✔
853
                if (future_opt) {
888✔
854
                    auto fut = std::move(future_opt.value());
635✔
855
                    if (fq.push_back(std::move(fut))
635✔
856
                        == lnav::progress_result_t::interrupt)
635✔
857
                    {
858
                        break;
×
859
                    }
860
                }
635✔
861
            }
888✔
862
        }
900✔
863
    } else if (glob_rc != GLOB_NOMATCH) {
786✔
864
        log_error("glob(%s) failed -- %s", path.c_str(), strerror(errno));
×
865
    }
866
}
2,843✔
867

868
file_collection
869
file_collection::rescan_files(bool required)
3,033✔
870
{
871
    file_collection retval;
3,033✔
872
    lnav::futures::future_queue<file_collection> fq(
873
        [this, &retval](std::future<file_collection>& fc) {
×
874
            const auto& lim = get_limits();
635✔
875
            try {
876
                auto v = fc.get();
635✔
877

878
                if (!v.fc_files.empty()
635✔
879
                    && v.fc_files.back()->get_fd() > lim.l_high_fd)
635✔
880
                {
881
                    log_warning(
×
882
                        "open file FD is too high (%d > %llu), dropping...",
883
                        v.fc_files.back()->get_fd(),
884
                        lim.l_high_fd);
885
                    v.fc_files.clear();
×
886
                    retval.fc_files_high_mark
×
887
                        = this->fc_files.size() + retval.fc_files.size();
×
888
                    log_debug("setting high mark to %zu",
×
889
                              retval.fc_files_high_mark.value());
890
                }
891
                retval.merge(v);
635✔
892
            } catch (const std::exception& e) {
635✔
893
                log_error("rescan future exception: %s", e.what());
×
894
            } catch (...) {
×
895
                log_error("unknown exception thrown by rescan future");
×
896
            }
×
897

898
            if (retval.fc_files.size() < 100
635✔
899
                && this->fc_files.size() + retval.fc_files.size()
1,270✔
900
                    < lim.l_open_files)
635✔
901
            {
902
                return lnav::progress_result_t::ok;
635✔
903
            }
904
            return lnav::progress_result_t::interrupt;
×
905
        });
3,033✔
906

907
    this->fc_new_stats.clear();
3,033✔
908
    for (auto& pair : this->fc_file_names) {
5,876✔
909
        if (this->fc_files.size() + retval.fc_files.size()
2,843✔
910
            >= get_limits().l_open_files)
2,843✔
911
        {
912
            log_warning("too many files open, breaking...");
×
913
            break;
×
914
        }
915

916
        if (pair.second.loo_piper) {
2,843✔
917
            this->expand_filename(
158✔
918
                fq,
919
                pair.second.loo_piper->get_out_pattern().string(),
316✔
920
                pair.second,
158✔
921
                required);
922
            if (!pair.second.loo_piper.value().get_demux_id().empty()
316✔
923
                && this->fc_other_files.count(pair.first) == 0)
316✔
924
            {
925
                auto& ofd = retval.fc_other_files[pair.first];
2✔
926
                ofd.ofd_format = file_format_t::MULTIPLEXED;
2✔
927
                ofd.ofd_details
928
                    = pair.second.loo_piper.value().get_demux_details();
2✔
929
            }
930
        } else {
931
            this->expand_filename(fq, pair.first, pair.second, required);
2,685✔
932
            if (this->fc_rotated) {
2,685✔
933
                std::string path = pair.first + ".*";
×
934

935
                this->expand_filename(fq, path, pair.second, false);
×
936
            }
937
        }
938

939
        if (retval.fc_files.size() >= 100) {
2,843✔
940
            log_debug("too many new files, breaking...");
×
941
            break;
×
942
        }
943
    }
944

945
    fq.pop_to();
3,033✔
946

947
    return retval;
6,066✔
948
}
3,033✔
949

950
void
951
file_collection::request_close(const std::shared_ptr<logfile>& lf)
8✔
952
{
953
    lf->close();
8✔
954
    this->fc_files_generation += 1;
8✔
955
}
8✔
956

957
size_t
958
file_collection::initial_indexing_pipers() const
7✔
959
{
960
    size_t retval = 0;
7✔
961

962
    for (const auto& pair : this->fc_file_names) {
11✔
963
        if (pair.second.loo_piper
4✔
964
            && pair.second.loo_piper->get_loop_count() == 0)
4✔
965
        {
966
            retval += 1;
×
967
        }
968
    }
969

970
    return retval;
7✔
971
}
972

973
size_t
974
file_collection::active_pipers() const
2,903✔
975
{
976
    size_t retval = 0;
2,903✔
977
    for (const auto& pair : this->fc_file_names) {
5,633✔
978
        if (pair.second.loo_piper && !pair.second.loo_piper->is_finished()) {
2,730✔
979
            retval += 1;
6✔
980
        }
981
    }
982

983
    return retval;
2,903✔
984
}
985

986
size_t
987
file_collection::finished_pipers()
10✔
988
{
989
    size_t retval = 0;
10✔
990

991
    for (auto& pair : this->fc_file_names) {
15✔
992
        if (pair.second.loo_piper) {
5✔
993
            retval += pair.second.loo_piper->consume_finished();
×
994
        }
995
    }
996

997
    return retval;
10✔
998
}
999

1000
file_collection
1001
file_collection::copy()
7✔
1002
{
1003
    file_collection retval;
7✔
1004

1005
    retval.merge(*this);
7✔
1006
    retval.fc_progress = this->fc_progress;
7✔
1007
    return retval;
7✔
1008
}
×
1009

1010
void
1011
file_collection::clear()
769✔
1012
{
1013
    this->fc_name_to_stubs->writeAccess()->clear();
769✔
1014
    this->fc_file_names.clear();
769✔
1015
    this->fc_files.clear();
769✔
1016
    this->fc_renamed_files.clear();
769✔
1017
    this->fc_closed_files.clear();
769✔
1018
    this->fc_other_files.clear();
769✔
1019
    this->fc_new_stats.clear();
769✔
1020
}
769✔
1021

1022
bool
1023
file_collection::is_below_open_file_limit() const
6,091✔
1024
{
1025
    return !this->fc_files_high_mark
6,091✔
1026
        || this->fc_files.size() < this->fc_files_high_mark.value();
6,091✔
1027
}
1028

1029
size_t
1030
file_collection::other_file_format_count(file_format_t ff) const
×
1031
{
1032
    size_t retval = 0;
×
1033

1034
    for (const auto& pair : this->fc_other_files) {
×
1035
        if (pair.second.ofd_format == ff) {
×
1036
            retval += 1;
×
1037
        }
1038
    }
1039

1040
    return retval;
×
1041
}
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