• 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

72.84
/src/session_data.cc
1
/**
2
 * Copyright (c) 2013, 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 session_data.cc
30
 */
31

32
#include <algorithm>
33
#include <utility>
34

35
#include "session_data.hh"
36

37
#include <glob.h>
38
#include <stdio.h>
39
#include <sys/types.h>
40
#include <yajl/api/yajl_tree.h>
41

42
#include "base/fs_util.hh"
43
#include "base/isc.hh"
44
#include "base/opt_util.hh"
45
#include "base/paths.hh"
46
#include "bookmarks.json.hh"
47
#include "bound_tags.hh"
48
#include "command_executor.hh"
49
#include "config.h"
50
#include "hasher.hh"
51
#include "lnav.events.hh"
52
#include "lnav.hh"
53
#include "lnav.indexing.hh"
54
#include "log_format_ext.hh"
55
#include "logfile.hh"
56
#include "service_tags.hh"
57
#include "sql_util.hh"
58
#include "sqlitepp.client.hh"
59
#include "tailer/tailer.looper.hh"
60
#include "vtab_module.hh"
61
#include "yajlpp/yajlpp.hh"
62
#include "yajlpp/yajlpp_def.hh"
63

64
session_data_t session_data;
65
recent_refs_t recent_refs;
66

67
static const char* const LOG_METADATA_NAME = "log_metadata.db";
68

69
static const char* const META_TABLE_DEF = R"(
70
CREATE TABLE IF NOT EXISTS bookmarks (
71
    log_time datetime,
72
    log_format varchar(64),
73
    log_hash varchar(128),
74
    session_time integer,
75
    part_name text,
76
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
77
    comment text DEFAULT '',
78
    tags text DEFAULT '',
79
    annotations text DEFAULT NULL,
80
    log_opid text DEFAULT NULL,
81

82
    PRIMARY KEY (log_time, log_format, log_hash, session_time)
83
);
84

85
CREATE TABLE IF NOT EXISTS time_offset (
86
    log_time datetime,
87
    log_format varchar(64),
88
    log_hash varchar(128),
89
    session_time integer,
90
    offset_sec integer,
91
    offset_usec integer,
92
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
93

94
    PRIMARY KEY (log_time, log_format, log_hash, session_time)
95
);
96

97
CREATE TABLE IF NOT EXISTS recent_netlocs (
98
    netloc text,
99

100
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
101

102
    PRIMARY KEY (netloc)
103
);
104

105
CREATE TABLE IF NOT EXISTS regex101_entries (
106
    format_name text NOT NULL,
107
    regex_name text NOT NULL,
108
    permalink text NOT NULL,
109
    delete_code text NOT NULL,
110

111
    PRIMARY KEY (format_name, regex_name),
112

113
    CHECK(
114
       format_name  <> '' AND
115
       regex_name   <> '' AND
116
       permalink    <> '')
117
);
118
)";
119

120
static const char* const BOOKMARK_LRU_STMT
121
    = "DELETE FROM bookmarks WHERE access_time <= "
122
      "  (SELECT access_time FROM bookmarks "
123
      "   ORDER BY access_time DESC LIMIT 1 OFFSET 50000)";
124

125
static const char* const NETLOC_LRU_STMT
126
    = "DELETE FROM recent_netlocs WHERE access_time <= "
127
      "  (SELECT access_time FROM bookmarks "
128
      "   ORDER BY access_time DESC LIMIT 1 OFFSET 10)";
129

130
static const char* const UPGRADE_STMTS[] = {
131
    R"(ALTER TABLE bookmarks ADD COLUMN comment text DEFAULT '';)",
132
    R"(ALTER TABLE bookmarks ADD COLUMN tags text DEFAULT '';)",
133
    R"(ALTER TABLE bookmarks ADD COLUMN annotations text DEFAULT NULL;)",
134
    R"(ALTER TABLE bookmarks ADD COLUMN log_opid text DEFAULT NULL;)",
135
};
136

137
static constexpr size_t MAX_SESSIONS = 8;
138
static constexpr size_t MAX_SESSION_FILE_COUNT = 256;
139

140
struct session_line {
141
    session_line(struct timeval tv,
12✔
142
                 intern_string_t format_name,
143
                 std::string line_hash)
144
        : sl_time(tv), sl_format_name(format_name),
12✔
145
          sl_line_hash(std::move(line_hash))
12✔
146
    {
147
    }
12✔
148

149
    struct timeval sl_time;
150
    intern_string_t sl_format_name;
151
    std::string sl_line_hash;
152
};
153

154
static std::vector<session_line> marked_session_lines;
155
static std::vector<session_line> offset_session_lines;
156

157
static bool
158
bind_line(sqlite3* db,
21✔
159
          sqlite3_stmt* stmt,
160
          content_line_t cl,
161
          time_t session_time)
162
{
163
    auto& lss = lnav_data.ld_log_source;
21✔
164
    auto lf = lss.find(cl);
21✔
165

166
    if (lf == nullptr) {
21✔
UNCOV
167
        return false;
×
168
    }
169

170
    sqlite3_clear_bindings(stmt);
21✔
171

172
    auto line_iter = lf->begin() + cl;
21✔
173
    auto fr = lf->get_file_range(line_iter, false);
21✔
174
    auto read_result = lf->read_range(fr);
21✔
175

176
    if (read_result.isErr()) {
21✔
UNCOV
177
        return false;
×
178
    }
179

180
    auto line_hash = read_result
181
                         .map([cl](auto sbr) {
42✔
182
                             return hasher()
42✔
183
                                 .update(sbr.get_data(), sbr.length())
21✔
184
                                 .update(cl)
21✔
185
                                 .to_string();
42✔
186
                         })
187
                         .unwrap();
21✔
188

189
    return bind_values(stmt,
42✔
190
                       lf->original_line_time(line_iter),
191
                       lf->get_format()->get_name(),
42✔
192
                       line_hash,
193
                       session_time)
194
        == SQLITE_OK;
21✔
195
}
21✔
196

197
struct session_file_info {
198
    session_file_info(int timestamp, std::string id, std::string path)
46✔
199
        : sfi_timestamp(timestamp), sfi_id(std::move(id)),
46✔
200
          sfi_path(std::move(path))
46✔
201
    {
202
    }
46✔
203

204
    bool operator<(const session_file_info& other) const
72✔
205
    {
206
        if (this->sfi_timestamp < other.sfi_timestamp) {
72✔
UNCOV
207
            return true;
×
208
        }
209
        if (this->sfi_path < other.sfi_path) {
72✔
UNCOV
210
            return true;
×
211
        }
212
        return false;
72✔
213
    }
214

215
    int sfi_timestamp;
216
    std::string sfi_id;
217
    std::string sfi_path;
218
};
219

220
static void
221
cleanup_session_data()
22✔
222
{
223
    static_root_mem<glob_t, globfree> session_file_list;
22✔
224
    std::list<struct session_file_info> session_info_list;
22✔
225
    std::map<std::string, int> session_count;
22✔
226
    auto session_file_pattern = lnav::paths::dotlnav() / "*-*.ts*.json";
22✔
227

228
    if (glob(
22✔
229
            session_file_pattern.c_str(), 0, nullptr, session_file_list.inout())
230
        == 0)
22✔
231
    {
232
        for (size_t lpc = 0; lpc < session_file_list->gl_pathc; lpc++) {
67✔
233
            const char* path = session_file_list->gl_pathv[lpc];
46✔
234
            char hash_id[64];
235
            int timestamp;
236
            const char* base;
237

238
            base = strrchr(path, '/');
46✔
239
            if (base == nullptr) {
46✔
UNCOV
240
                continue;
×
241
            }
242
            base += 1;
46✔
243
            if (sscanf(base, "file-%63[^.].ts%d.json", hash_id, &timestamp)
46✔
244
                == 2)
46✔
245
            {
UNCOV
246
                session_count[hash_id] += 1;
×
UNCOV
247
                session_info_list.emplace_back(timestamp, hash_id, path);
×
248
            }
249
            if (sscanf(base,
46✔
250
                       "view-info-%63[^.].ts%d.ppid%*d.json",
251
                       hash_id,
252
                       &timestamp)
253
                == 2)
46✔
254
            {
255
                session_count[hash_id] += 1;
46✔
256
                session_info_list.emplace_back(timestamp, hash_id, path);
46✔
257
            }
258
        }
259
    }
260

261
    session_info_list.sort();
22✔
262

263
    size_t session_loops = 0;
22✔
264

265
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
22✔
266
        const session_file_info& front = session_info_list.front();
×
267

268
        session_loops += 1;
×
UNCOV
269
        if (session_loops < MAX_SESSION_FILE_COUNT
×
270
            && session_count[front.sfi_id] == 1)
×
271
        {
272
            session_info_list.splice(session_info_list.end(),
×
273
                                     session_info_list,
274
                                     session_info_list.begin());
×
275
        } else {
UNCOV
276
            if (remove(front.sfi_path.c_str()) != 0) {
×
UNCOV
277
                log_error("Unable to remove session file: %s -- %s",
×
278
                          front.sfi_path.c_str(),
279
                          strerror(errno));
280
            }
UNCOV
281
            session_count[front.sfi_id] -= 1;
×
UNCOV
282
            session_info_list.pop_front();
×
283
        }
284
    }
285

286
    session_info_list.sort();
22✔
287

288
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
22✔
289
        const session_file_info& front = session_info_list.front();
×
290

UNCOV
291
        if (remove(front.sfi_path.c_str()) != 0) {
×
UNCOV
292
            log_error("Unable to remove session file: %s -- %s",
×
293
                      front.sfi_path.c_str(),
294
                      strerror(errno));
295
        }
UNCOV
296
        session_count[front.sfi_id] -= 1;
×
UNCOV
297
        session_info_list.pop_front();
×
298
    }
299
}
22✔
300

301
void
302
init_session()
564✔
303
{
304
    lnav_data.ld_session_time = time(nullptr);
564✔
305
    lnav_data.ld_session_id.clear();
564✔
306
    session_data.sd_view_states[LNV_LOG].vs_top = -1;
564✔
307
}
564✔
308

309
static std::optional<std::string>
310
compute_session_id()
41✔
311
{
312
    bool has_files = false;
41✔
313
    hasher h;
41✔
314

315
    for (auto& ld_file_name : lnav_data.ld_active_files.fc_file_names) {
85✔
316
        if (!ld_file_name.second.loo_include_in_session) {
44✔
UNCOV
317
            continue;
×
318
        }
319
        has_files = true;
44✔
320
        h.update(ld_file_name.first);
44✔
321
    }
322
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
85✔
323
        if (lf->is_valid_filename()) {
44✔
324
            continue;
43✔
325
        }
326
        if (!lf->get_open_options().loo_include_in_session) {
1✔
UNCOV
327
            continue;
×
328
        }
329

330
        has_files = true;
1✔
331
        h.update(lf->get_content_id());
1✔
332
    }
333
    if (!has_files) {
41✔
334
        return std::nullopt;
1✔
335
    }
336

337
    return h.to_string();
40✔
338
}
339

340
std::optional<session_pair_t>
341
scan_sessions()
22✔
342
{
343
    static_root_mem<glob_t, globfree> view_info_list;
22✔
344

345
    cleanup_session_data();
22✔
346

347
    const auto session_id = compute_session_id();
22✔
348
    if (!session_id) {
22✔
UNCOV
349
        return std::nullopt;
×
350
    }
351
    auto& session_file_names = lnav_data.ld_session_id[session_id.value()];
22✔
352
    session_file_names.clear();
22✔
353

354
    auto view_info_pattern_base
355
        = fmt::format(FMT_STRING("view-info-{}.*.json"), session_id.value());
66✔
356
    auto view_info_pattern = lnav::paths::dotlnav() / view_info_pattern_base;
22✔
357
    if (glob(view_info_pattern.c_str(), 0, nullptr, view_info_list.inout())
22✔
358
        == 0)
22✔
359
    {
360
        for (size_t lpc = 0; lpc < view_info_list->gl_pathc; lpc++) {
55✔
361
            const char* path = view_info_list->gl_pathv[lpc];
34✔
362
            int timestamp, ppid, rc;
363
            const char* base;
364

365
            base = strrchr(path, '/');
34✔
366
            if (base == nullptr) {
34✔
UNCOV
367
                continue;
×
368
            }
369
            base += 1;
34✔
370
            if ((rc = sscanf(base,
34✔
371
                             "view-info-%*[^.].ts%d.ppid%d.json",
372
                             &timestamp,
373
                             &ppid))
374
                == 2)
34✔
375
            {
376
                ppid_time_pair_t ptp;
34✔
377

378
                ptp.first = (ppid == getppid()) ? 1 : 0;
34✔
379
                ptp.second = timestamp;
34✔
380
                session_file_names.emplace_back(ptp, path);
34✔
381
            }
382
        }
383
    }
384

385
    session_file_names.sort();
22✔
386

387
    while (session_file_names.size() > MAX_SESSIONS) {
22✔
UNCOV
388
        const std::string& name = session_file_names.front().second;
×
389

390
        if (remove(name.c_str()) != 0) {
×
UNCOV
391
            log_error("Unable to remove session: %s -- %s",
×
392
                      name.c_str(),
393
                      strerror(errno));
394
        }
UNCOV
395
        session_file_names.pop_front();
×
396
    }
397

398
    if (session_file_names.empty()) {
22✔
399
        return std::nullopt;
1✔
400
    }
401

402
    return std::make_optional(session_file_names.back());
21✔
403
}
22✔
404

405
void
406
load_time_bookmarks()
22✔
407
{
408
    static const char* const BOOKMARK_STMT = R"(
409
       SELECT
410
         log_time,
411
         log_format,
412
         log_hash,
413
         session_time,
414
         part_name,
415
         access_time,
416
         comment,
417
         tags,
418
         annotations,
419
         log_opid,
420
         session_time=? AS same_session
421
       FROM bookmarks WHERE
422
         log_time BETWEEN ? AND ? AND
423
         log_format = ?
424
       ORDER BY same_session DESC, session_time DESC
425
)";
426

427
    static const char* const TIME_OFFSET_STMT = R"(
428
       SELECT
429
         *,
430
         session_time=? AS same_session
431
       FROM time_offset
432
       WHERE
433
         log_hash = ? AND
434
         log_time BETWEEN ? AND ? AND
435
         log_format = ?
436
       ORDER BY
437
         same_session DESC,
438
         session_time DESC
439
)";
440

441
    auto& lss = lnav_data.ld_log_source;
22✔
442
    auto_sqlite3 db;
22✔
443
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
22✔
444
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
22✔
445
    bool reload_needed = false;
22✔
446
    auto_mem<char, sqlite3_free> errmsg;
22✔
447

448
    log_info("loading bookmark db: %s", db_path.c_str());
22✔
449

450
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
22✔
UNCOV
451
        return;
×
452
    }
453

454
    for (const char* upgrade_stmt : UPGRADE_STMTS) {
110✔
455
        auto rc = sqlite3_exec(
88✔
456
            db.in(), upgrade_stmt, nullptr, nullptr, errmsg.out());
457
        if (rc != SQLITE_OK) {
88✔
458
            auto exterr = sqlite3_extended_errcode(db.in());
88✔
459
            log_error("unable to upgrade bookmark table -- (%d/%d) %s",
88✔
460
                      rc,
461
                      exterr,
462
                      errmsg.in());
463
        }
464
    }
465

466
    {
467
        auto netloc_prep_res
468
            = prepare_stmt(db.in(), "SELECT netloc FROM recent_netlocs");
22✔
469
        if (netloc_prep_res.isErr()) {
22✔
470
            log_error("unable to get netlocs: %s",
1✔
471
                      netloc_prep_res.unwrapErr().c_str());
472
            return;
1✔
473
        }
474

475
        auto netloc_stmt = netloc_prep_res.unwrap();
21✔
476
        bool done = false;
21✔
477

478
        while (!done) {
42✔
479
            done = netloc_stmt.fetch_row<std::string>().match(
42✔
UNCOV
480
                [](const std::string& netloc) {
×
UNCOV
481
                    recent_refs.rr_netlocs.insert(netloc);
×
UNCOV
482
                    return false;
×
483
                },
UNCOV
484
                [](const prepared_stmt::fetch_error& fe) {
×
485
                    log_error("failed to fetch netloc row: %s",
×
486
                              fe.fe_msg.c_str());
487
                    return true;
×
488
                },
489
                [](prepared_stmt::end_of_rows) { return true; });
42✔
490
        }
491
    }
22✔
492

493
    log_info("BEGIN select bookmarks");
21✔
494
    if (sqlite3_prepare_v2(db.in(), BOOKMARK_STMT, -1, stmt.out(), nullptr)
21✔
495
        != SQLITE_OK)
21✔
496
    {
UNCOV
497
        log_error("could not prepare bookmark select statement -- %s",
×
498
                  sqlite3_errmsg(db));
499
        return;
×
500
    }
501

502
    for (auto file_iter = lnav_data.ld_log_source.begin();
21✔
503
         file_iter != lnav_data.ld_log_source.end();
43✔
504
         ++file_iter)
22✔
505
    {
506
        auto lf = (*file_iter)->get_file();
22✔
507
        if (lf == nullptr) {
22✔
UNCOV
508
            continue;
×
509
        }
510
        if (lf->size() == 0) {
22✔
UNCOV
511
            continue;
×
512
        }
513
        const auto* format = lf->get_format_ptr();
22✔
514
        content_line_t base_content_line;
22✔
515

516
        base_content_line = lss.get_file_base_content_line(file_iter);
22✔
517

518
        auto low_line_iter = lf->begin();
22✔
519
        auto high_line_iter = lf->end();
22✔
520

521
        --high_line_iter;
22✔
522

523
        if (bind_values(stmt.in(),
22✔
524
                        lnav_data.ld_session_load_time,
525
                        lf->original_line_time(low_line_iter),
526
                        lf->original_line_time(high_line_iter),
527
                        lf->get_format()->get_name())
44✔
528
            != SQLITE_OK)
22✔
529
        {
UNCOV
530
            return;
×
531
        }
532

533
        date_time_scanner dts;
22✔
534
        bool done = false;
22✔
535
        int64_t last_mark_time = -1;
22✔
536

537
        while (!done) {
81✔
538
            int rc = sqlite3_step(stmt.in());
59✔
539

540
            switch (rc) {
59✔
541
                case SQLITE_OK:
22✔
542
                case SQLITE_DONE:
543
                    done = true;
22✔
544
                    break;
22✔
545

546
                case SQLITE_ROW: {
37✔
547
                    const char* log_time
548
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
37✔
549
                    const char* log_hash
550
                        = (const char*) sqlite3_column_text(stmt.in(), 2);
37✔
551
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
37✔
552
                    const char* part_name
553
                        = (const char*) sqlite3_column_text(stmt.in(), 4);
37✔
554
                    const char* comment
555
                        = (const char*) sqlite3_column_text(stmt.in(), 6);
37✔
556
                    const char* tags
557
                        = (const char*) sqlite3_column_text(stmt.in(), 7);
37✔
558
                    const auto annotations = sqlite3_column_text(stmt.in(), 8);
37✔
559
                    const auto log_opid = sqlite3_column_text(stmt.in(), 9);
37✔
560
                    timeval log_tv;
561
                    exttm log_tm;
37✔
562

563
                    if (last_mark_time == -1) {
37✔
564
                        last_mark_time = mark_time;
22✔
565
                    } else if (last_mark_time != mark_time) {
15✔
UNCOV
566
                        done = true;
×
567
                        continue;
18✔
568
                    }
569

570
                    if (part_name == nullptr) {
37✔
571
                        continue;
18✔
572
                    }
573

574
                    if (dts.scan(log_time,
19✔
575
                                 strlen(log_time),
576
                                 nullptr,
577
                                 &log_tm,
578
                                 log_tv)
579
                        == nullptr)
19✔
580
                    {
UNCOV
581
                        log_warning("bad log time: %s", log_time);
×
UNCOV
582
                        continue;
×
583
                    }
584

585
                    auto line_iter = format->lf_time_ordered
19✔
586
                        ? std::lower_bound(lf->begin(), lf->end(), log_tv)
19✔
587
                        : lf->begin();
1✔
588
                    while (line_iter != lf->end()) {
74✔
589
                        const auto line_tv = line_iter->get_timeval();
74✔
590

591
                        // NB: only milliseconds were stored in the DB, but the
592
                        // internal rep stores micros now.
593
                        if (line_tv.tv_sec != log_tv.tv_sec
74✔
594
                            || line_tv.tv_usec / 1000 != log_tv.tv_usec / 1000)
39✔
595
                        {
596
                            if (format->lf_time_ordered) {
53✔
597
                                break;
19✔
598
                            }
599
                            ++line_iter;
53✔
600
                            continue;
55✔
601
                        }
602

603
                        auto cl = content_line_t(
604
                            std::distance(lf->begin(), line_iter));
42✔
605
                        auto fr = lf->get_file_range(line_iter, false);
21✔
606
                        auto read_result = lf->read_range(fr);
21✔
607

608
                        if (read_result.isErr()) {
21✔
UNCOV
609
                            break;
×
610
                        }
611

612
                        auto sbr = read_result.unwrap();
21✔
613

614
                        auto line_hash
615
                            = hasher()
21✔
616
                                  .update(sbr.get_data(), sbr.length())
21✔
617
                                  .update(cl)
21✔
618
                                  .to_string();
21✔
619

620
                        if (line_hash != log_hash) {
21✔
621
                            // Using the formatted line for JSON-lines logs was
622
                            // a mistake in earlier versions. To carry forward
623
                            // older bookmarks, we need to replicate the bad
624
                            // behavior.
625
                            auto hack_read_res
626
                                = lf->read_line(line_iter, {false, true});
2✔
627
                            if (hack_read_res.isErr()) {
2✔
UNCOV
628
                                break;
×
629
                            }
630
                            auto hack_sbr = hack_read_res.unwrap();
2✔
631
                            auto hack_hash = hasher()
2✔
632
                                                 .update(hack_sbr.get_data(),
2✔
633
                                                         hack_sbr.length())
634
                                                 .update(cl)
2✔
635
                                                 .to_string();
2✔
636
                            if (hack_hash == log_hash) {
2✔
UNCOV
637
                                log_trace("needed hack to match line: %s:%d",
×
638
                                          lf->get_filename_as_string().c_str(),
639
                                          cl);
640
                            } else {
641
                                ++line_iter;
2✔
642
                                continue;
2✔
643
                            }
644
                        }
6✔
645
                        auto& bm_meta = lf->get_bookmark_metadata();
19✔
646
                        auto line_number = static_cast<uint32_t>(
647
                            std::distance(lf->begin(), line_iter));
19✔
648
                        content_line_t line_cl
649
                            = content_line_t(base_content_line + line_number);
19✔
650
                        bool meta = false;
19✔
651

652
                        if (part_name != nullptr && part_name[0] != '\0') {
19✔
653
                            lss.set_user_mark(&textview_curses::BM_PARTITION,
7✔
654
                                              line_cl);
655
                            bm_meta[line_number].bm_name = part_name;
7✔
656
                            meta = true;
7✔
657
                        }
658
                        if (comment != nullptr && comment[0] != '\0') {
19✔
659
                            lss.set_user_mark(&textview_curses::BM_META,
7✔
660
                                              line_cl);
661
                            bm_meta[line_number].bm_comment = comment;
7✔
662
                            meta = true;
7✔
663
                        }
664
                        if (tags != nullptr && tags[0] != '\0') {
19✔
665
                            auto_mem<yajl_val_s> tag_list(yajl_tree_free);
9✔
666
                            char error_buffer[1024];
667

668
                            tag_list = yajl_tree_parse(
669
                                tags, error_buffer, sizeof(error_buffer));
9✔
670
                            if (!YAJL_IS_ARRAY(tag_list.in())) {
9✔
UNCOV
671
                                log_error("invalid tags column: %s", tags);
×
672
                            } else {
673
                                lss.set_user_mark(&textview_curses::BM_META,
9✔
674
                                                  line_cl);
675
                                for (size_t lpc = 0;
18✔
676
                                     lpc < tag_list.in()->u.array.len;
18✔
677
                                     lpc++)
678
                                {
679
                                    yajl_val elem
680
                                        = tag_list.in()->u.array.values[lpc];
9✔
681

682
                                    if (!YAJL_IS_STRING(elem)) {
9✔
683
                                        continue;
×
684
                                    }
685
                                    bookmark_metadata::KNOWN_TAGS.insert(
9✔
686
                                        elem->u.string);
9✔
687
                                    bm_meta[line_number].add_tag(
27✔
688
                                        elem->u.string);
9✔
689
                                }
690
                            }
691
                            meta = true;
9✔
692
                        }
9✔
693
                        if (annotations != nullptr && annotations[0] != '\0') {
19✔
694
                            static const intern_string_t SRC
695
                                = intern_string::lookup("annotations");
3✔
696

697
                            const auto anno_sf
698
                                = string_fragment::from_c_str(annotations);
1✔
699
                            auto parse_res
700
                                = logmsg_annotations_handlers.parser_for(SRC)
2✔
701
                                      .of(anno_sf);
1✔
702
                            if (parse_res.isErr()) {
1✔
703
                                log_error(
×
704
                                    "unable to parse annotations JSON -- "
705
                                    "%s",
706
                                    parse_res.unwrapErr()[0]
707
                                        .to_attr_line()
708
                                        .get_string()
709
                                        .c_str());
710
                            } else if (bm_meta.find(line_number)
1✔
711
                                       == bm_meta.end())
2✔
712
                            {
713
                                lss.set_user_mark(&textview_curses::BM_META,
1✔
714
                                                  line_cl);
715
                                bm_meta[line_number].bm_annotations
1✔
716
                                    = parse_res.unwrap();
2✔
717
                                meta = true;
1✔
718
                            } else {
UNCOV
719
                                meta = true;
×
720
                            }
721
                        }
1✔
722
                        if (log_opid != nullptr && log_opid[0] != '\0') {
19✔
723
                            auto opid_sf
724
                                = string_fragment::from_c_str(log_opid);
×
UNCOV
725
                            lf->set_logline_opid(line_number, opid_sf);
×
726
                            meta = true;
×
727
                        }
728
                        if (!meta) {
19✔
729
                            marked_session_lines.emplace_back(
2✔
730
                                lf->original_line_time(line_iter),
2✔
731
                                format->get_name(),
2✔
732
                                line_hash);
733
                            lss.set_user_mark(&textview_curses::BM_USER,
2✔
734
                                              line_cl);
735
                        }
736
                        reload_needed = true;
19✔
737
                        break;
19✔
738
                    }
63✔
739
                    break;
19✔
740
                }
741

UNCOV
742
                default: {
×
743
                    const char* errmsg;
744

UNCOV
745
                    errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
UNCOV
746
                    log_error(
×
747
                        "bookmark select error: code %d -- %s", rc, errmsg);
UNCOV
748
                    done = true;
×
UNCOV
749
                } break;
×
750
            }
751
        }
752

753
        sqlite3_reset(stmt.in());
22✔
754
    }
22✔
755
    log_info("END select bookmarks");
21✔
756

757
    log_info("BEGIN select time_offset");
21✔
758
    if (sqlite3_prepare_v2(db.in(), TIME_OFFSET_STMT, -1, stmt.out(), nullptr)
21✔
759
        != SQLITE_OK)
21✔
760
    {
UNCOV
761
        log_error("could not prepare time_offset select statement -- %s",
×
762
                  sqlite3_errmsg(db));
UNCOV
763
        return;
×
764
    }
765

766
    for (auto file_iter = lnav_data.ld_log_source.begin();
21✔
767
         file_iter != lnav_data.ld_log_source.end();
43✔
768
         ++file_iter)
22✔
769
    {
770
        auto lf = (*file_iter)->get_file();
22✔
771
        content_line_t base_content_line;
22✔
772

773
        if (lf == nullptr) {
22✔
UNCOV
774
            continue;
×
775
        }
776
        if (lf->size() == 0) {
22✔
UNCOV
777
            continue;
×
778
        }
779

780
        lss.find(lf->get_filename_as_string().c_str(), base_content_line);
22✔
781

782
        auto low_line_iter = lf->begin();
22✔
783
        auto high_line_iter = lf->end();
22✔
784

785
        --high_line_iter;
22✔
786

787
        if (bind_values(stmt.in(),
44✔
788
                        lnav_data.ld_session_load_time,
789
                        lf->get_content_id(),
22✔
790
                        lf->original_line_time(low_line_iter),
791
                        lf->original_line_time(high_line_iter),
792
                        lf->get_format()->get_name())
44✔
793
            != SQLITE_OK)
22✔
794
        {
UNCOV
795
            return;
×
796
        }
797

798
        date_time_scanner dts;
22✔
799
        auto done = false;
22✔
800
        int64_t last_mark_time = -1;
22✔
801

802
        while (!done) {
66✔
803
            const auto rc = sqlite3_step(stmt.in());
44✔
804

805
            switch (rc) {
44✔
806
                case SQLITE_OK:
22✔
807
                case SQLITE_DONE:
808
                    done = true;
22✔
809
                    break;
22✔
810

811
                case SQLITE_ROW: {
22✔
812
                    const auto* log_time
813
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
22✔
814
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
22✔
815
                    timeval log_tv;
816
                    exttm log_tm;
22✔
817

818
                    if (last_mark_time == -1) {
22✔
819
                        last_mark_time = mark_time;
22✔
UNCOV
820
                    } else if (last_mark_time != mark_time) {
×
UNCOV
821
                        done = true;
×
822
                        continue;
19✔
823
                    }
824

825
                    if (sqlite3_column_type(stmt.in(), 4) == SQLITE_NULL) {
22✔
826
                        continue;
19✔
827
                    }
828

829
                    if (!dts.scan(log_time,
3✔
830
                                  strlen(log_time),
831
                                  nullptr,
832
                                  &log_tm,
833
                                  log_tv))
834
                    {
835
                        continue;
×
836
                    }
837

838
                    auto line_iter
839
                        = lower_bound(lf->begin(), lf->end(), log_tv);
3✔
840
                    while (line_iter != lf->end()) {
6✔
841
                        auto line_tv = line_iter->get_timeval();
6✔
842

843
                        if ((line_tv.tv_sec != log_tv.tv_sec)
6✔
844
                            || (line_tv.tv_usec / 1000
3✔
845
                                != log_tv.tv_usec / 1000))
3✔
846
                        {
847
                            break;
848
                        }
849

850
                        int file_line = std::distance(lf->begin(), line_iter);
3✔
851
                        timeval offset;
852

853
                        offset_session_lines.emplace_back(
3✔
854
                            lf->original_line_time(line_iter),
3✔
855
                            lf->get_format_ptr()->get_name(),
3✔
856
                            lf->get_content_id());
3✔
857
                        offset.tv_sec = sqlite3_column_int64(stmt.in(), 4);
3✔
858
                        offset.tv_usec = sqlite3_column_int64(stmt.in(), 5);
3✔
859
                        lf->adjust_content_time(file_line, offset);
3✔
860

861
                        reload_needed = true;
3✔
862

863
                        ++line_iter;
3✔
864
                    }
865
                    break;
3✔
866
                }
867

UNCOV
868
                default: {
×
UNCOV
869
                    const auto* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
UNCOV
870
                    log_error(
×
871
                        "bookmark select error: code %d -- %s", rc, errmsg);
UNCOV
872
                    done = true;
×
UNCOV
873
                    break;
×
874
                }
875
            }
876
        }
877

878
        sqlite3_reset(stmt.in());
22✔
879
    }
22✔
880
    log_info("END select time_offset");
21✔
881

882
    if (reload_needed) {
21✔
883
        lnav_data.ld_views[LNV_LOG].reload_data();
16✔
884
    }
885
}
25✔
886

887
static int
888
read_files(yajlpp_parse_context* ypc,
20✔
889
           const unsigned char* str,
890
           size_t len,
891
           yajl_string_props_t*)
892
{
893
    return 1;
20✔
894
}
895

896
static const json_path_container view_def_handlers = {
897
    json_path_handler("top_line").for_field(&view_state::vs_top),
898
    json_path_handler("focused_line").for_field(&view_state::vs_selection),
899
    json_path_handler("anchor").for_field(&view_state::vs_anchor),
900
    json_path_handler("search").for_field(&view_state::vs_search),
901
    json_path_handler("word_wrap").for_field(&view_state::vs_word_wrap),
902
    json_path_handler("filtering").for_field(&view_state::vs_filtering),
903
    json_path_handler("commands#").for_field(&view_state::vs_commands),
904
};
905

906
static const struct json_path_container view_handlers = {
907
    yajlpp::pattern_property_handler("(?<view_name>[\\w\\-]+)")
908
        .with_obj_provider<view_state, session_data_t>(
909
            +[](const yajlpp_provider_context& ypc, session_data_t* root) {
1,534✔
910
                auto view_index_opt
911
                    = view_from_string(ypc.get_substr("view_name").c_str());
1,534✔
912
                if (view_index_opt) {
1,534✔
913
                    return &root->sd_view_states[view_index_opt.value()];
1,534✔
914
                }
915

UNCOV
916
                log_error("unknown view name: %s",
×
917
                          ypc.get_substr("view_name").c_str());
918
                static view_state dummy;
UNCOV
919
                return &dummy;
×
920
            })
921
        .with_children(view_def_handlers),
922
};
923

924
static const struct json_path_container file_state_handlers = {
925
    yajlpp::property_handler("visible")
926
        .with_description("Indicates whether the file is visible or not")
927
        .for_field(&file_state::fs_is_visible),
928
};
929

930
static const struct json_path_container file_states_handlers = {
931
    yajlpp::pattern_property_handler(R"((?<filename>[^/]+))")
932
        .with_description("Map of file names to file state objects")
933
        .with_obj_provider<file_state, session_data_t>(
934
            [](const auto& ypc, session_data_t* root) {
60✔
935
                auto fn = ypc.get_substr("filename");
60✔
936
                return &root->sd_file_states[fn];
120✔
937
            })
60✔
938
        .with_children(file_state_handlers),
939
};
940

941
static const typed_json_path_container<session_data_t> view_info_handlers = {
942
    yajlpp::property_handler("save-time")
943
        .for_field(&session_data_t::sd_save_time),
944
    yajlpp::property_handler("time-offset")
945
        .for_field(&session_data_t::sd_time_offset),
946
    json_path_handler("files#", read_files),
947
    yajlpp::property_handler("file-states").with_children(file_states_handlers),
948
    yajlpp::property_handler("views").with_children(view_handlers),
949
};
950

951
void
952
load_session()
22✔
953
{
954
    log_info("BEGIN load_session");
22✔
955
    scan_sessions() | [](const auto pair) {
22✔
956
        lnav_data.ld_session_load_time = pair.first.second;
21✔
957
        const auto& view_info_path = pair.second;
21✔
958
        auto view_info_src = intern_string::lookup(view_info_path.string());
21✔
959

960
        auto open_res = lnav::filesystem::open_file(view_info_path, O_RDONLY);
21✔
961
        if (open_res.isErr()) {
21✔
UNCOV
962
            log_error("cannot open session file: %s -- %s",
×
963
                      view_info_path.c_str(),
964
                      open_res.unwrapErr().c_str());
UNCOV
965
            return;
×
966
        }
967

968
        auto fd = open_res.unwrap();
21✔
969
        unsigned char buffer[1024];
970
        ssize_t rc;
971

972
        log_info("loading session file: %s", view_info_path.c_str());
21✔
973
        auto parser = view_info_handlers.parser_for(view_info_src);
21✔
974
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
69✔
975
            auto buf_frag = string_fragment::from_bytes(buffer, rc);
24✔
976
            auto parse_res = parser.consume(buf_frag);
24✔
977
            if (parse_res.isErr()) {
24✔
UNCOV
978
                log_error("failed to load session: %s -- %s",
×
979
                          view_info_path.c_str(),
980
                          parse_res.unwrapErr()[0]
981
                              .to_attr_line()
982
                              .get_string()
983
                              .c_str());
UNCOV
984
                return;
×
985
            }
986
        }
987

988
        auto complete_res = parser.complete();
21✔
989
        if (complete_res.isErr()) {
21✔
UNCOV
990
            log_error("failed to load session: %s -- %s",
×
991
                      view_info_path.c_str(),
992
                      complete_res.unwrapErr()[0]
993
                          .to_attr_line()
994
                          .get_string()
995
                          .c_str());
UNCOV
996
            return;
×
997
        }
998
        session_data = complete_res.unwrap();
21✔
999

1000
        bool log_changes = false;
21✔
1001

1002
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
43✔
1003
            auto iter = session_data.sd_file_states.find(lf->get_filename());
22✔
1004

1005
            if (iter == session_data.sd_file_states.end()) {
22✔
1006
                continue;
2✔
1007
            }
1008

1009
            log_debug("found state for file: %s %d (%s)",
20✔
1010
                      lf->get_content_id().c_str(),
1011
                      iter->second.fs_is_visible,
1012
                      lf->get_filename_as_string().c_str());
1013
            lnav_data.ld_log_source.find_data(lf) |
20✔
1014
                [iter, &log_changes](auto ld) {
40✔
1015
                    if (ld->ld_visible != iter->second.fs_is_visible) {
20✔
1016
                        ld->get_file_ptr()->set_indexing(
1✔
1017
                            iter->second.fs_is_visible);
1✔
1018
                        ld->set_visibility(iter->second.fs_is_visible);
1✔
1019
                        log_changes = true;
1✔
1020
                    }
1021
                };
1022
        }
1023

1024
        if (log_changes) {
21✔
1025
            lnav_data.ld_log_source.text_filters_changed();
1✔
1026
        }
1027
    };
21✔
1028

1029
    lnav::events::publish(lnav_data.ld_db.in(),
22✔
1030
                          lnav::events::session::loaded{});
44✔
1031

1032
    log_info("END load_session");
22✔
1033
}
22✔
1034

1035
static void
1036
yajl_writer(void* context, const char* str, size_t len)
7,160✔
1037
{
1038
    FILE* file = (FILE*) context;
7,160✔
1039

1040
    fwrite(str, len, 1, file);
7,160✔
1041
}
7,160✔
1042

1043
static void
1044
save_user_bookmarks(sqlite3* db,
19✔
1045
                    sqlite3_stmt* stmt,
1046
                    bookmark_vector<content_line_t>& user_marks)
1047
{
1048
    auto& lss = lnav_data.ld_log_source;
19✔
1049

1050
    for (auto iter = user_marks.bv_tree.begin();
19✔
1051
         iter != user_marks.bv_tree.end();
20✔
1052
         ++iter)
1✔
1053
    {
1054
        content_line_t cl = *iter;
1✔
1055
        auto lf = lss.find(cl);
1✔
1056
        if (lf == nullptr) {
1✔
1057
            continue;
×
1058
        }
1059

1060
        sqlite3_clear_bindings(stmt);
1✔
1061

1062
        const auto line_iter = lf->begin() + cl;
1✔
1063
        auto fr = lf->get_file_range(line_iter, false);
1✔
1064
        auto read_result = lf->read_range(fr);
1✔
1065

1066
        if (read_result.isErr()) {
1✔
UNCOV
1067
            continue;
×
1068
        }
1069

1070
        auto line_hash = read_result
1071
                             .map([cl](auto sbr) {
2✔
1072
                                 return hasher()
2✔
1073
                                     .update(sbr.get_data(), sbr.length())
1✔
1074
                                     .update(cl)
1✔
1075
                                     .to_string();
2✔
1076
                             })
1077
                             .unwrap();
1✔
1078

1079
        if (bind_values(stmt,
3✔
1080
                        lf->original_line_time(line_iter),
1081
                        lf->get_format()->get_name(),
2✔
1082
                        line_hash,
1083
                        lnav_data.ld_session_time)
1084
            != SQLITE_OK)
1✔
1085
        {
UNCOV
1086
            continue;
×
1087
        }
1088

1089
        if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT) != SQLITE_OK) {
1✔
UNCOV
1090
            log_error("could not bind log hash -- %s", sqlite3_errmsg(db));
×
UNCOV
1091
            return;
×
1092
        }
1093

1094
        if (sqlite3_step(stmt) != SQLITE_DONE) {
1✔
UNCOV
1095
            log_error("could not execute bookmark insert statement -- %s",
×
1096
                      sqlite3_errmsg(db));
UNCOV
1097
            return;
×
1098
        }
1099

1100
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
1✔
1101
                                          lf->get_format_ptr()->get_name(),
1✔
1102
                                          line_hash);
1103

1104
        sqlite3_reset(stmt);
1✔
1105
    }
1✔
1106
}
1107

1108
static void
1109
save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
21✔
1110
{
1111
    for (const auto& bm_pair : lf->get_bookmark_metadata()) {
27✔
1112
        auto cl = content_line_t(bm_pair.first);
6✔
1113
        sqlite3_clear_bindings(stmt);
6✔
1114

1115
        auto line_iter = lf->begin() + cl;
6✔
1116
        auto fr = lf->get_file_range(line_iter, false);
6✔
1117
        auto read_result = lf->read_range(fr);
6✔
1118

1119
        if (read_result.isErr()) {
6✔
1120
            continue;
×
1121
        }
1122

1123
        auto line_hash = read_result
1124
                             .map([cl](auto sbr) {
12✔
1125
                                 return hasher()
12✔
1126
                                     .update(sbr.get_data(), sbr.length())
6✔
1127
                                     .update(cl)
6✔
1128
                                     .to_string();
12✔
1129
                             })
1130
                             .unwrap();
6✔
1131

1132
        if (bind_values(stmt,
18✔
1133
                        lf->original_line_time(line_iter),
1134
                        lf->get_format()->get_name(),
12✔
1135
                        line_hash,
1136
                        lnav_data.ld_session_time)
1137
            != SQLITE_OK)
6✔
1138
        {
UNCOV
1139
            continue;
×
1140
        }
1141

1142
        const auto& line_meta = bm_pair.second;
6✔
1143
        if (line_meta.empty(bookmark_metadata::categories::any)) {
6✔
UNCOV
1144
            continue;
×
1145
        }
1146

1147
        if (sqlite3_bind_text(stmt,
6✔
1148
                              5,
1149
                              line_meta.bm_name.c_str(),
1150
                              line_meta.bm_name.length(),
6✔
1151
                              SQLITE_TRANSIENT)
1152
            != SQLITE_OK)
6✔
1153
        {
UNCOV
1154
            log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
UNCOV
1155
            return;
×
1156
        }
1157

1158
        if (sqlite3_bind_text(stmt,
6✔
1159
                              6,
1160
                              line_meta.bm_comment.c_str(),
1161
                              line_meta.bm_comment.length(),
6✔
1162
                              SQLITE_TRANSIENT)
1163
            != SQLITE_OK)
6✔
1164
        {
UNCOV
1165
            log_error("could not bind comment -- %s", sqlite3_errmsg(db));
×
UNCOV
1166
            return;
×
1167
        }
1168

1169
        std::string tags;
6✔
1170

1171
        if (!line_meta.bm_tags.empty()) {
6✔
1172
            yajlpp_gen gen;
3✔
1173

1174
            yajl_gen_config(gen, yajl_gen_beautify, false);
3✔
1175

1176
            {
1177
                yajlpp_array arr(gen);
3✔
1178

1179
                for (const auto& str : line_meta.bm_tags) {
6✔
1180
                    arr.gen(str);
3✔
1181
                }
1182
            }
3✔
1183

1184
            tags = gen.to_string_fragment().to_string();
3✔
1185
        }
3✔
1186

1187
        if (sqlite3_bind_text(
6✔
1188
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
6✔
1189
            != SQLITE_OK)
6✔
1190
        {
UNCOV
1191
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
UNCOV
1192
            return;
×
1193
        }
1194

1195
        if (!line_meta.bm_annotations.la_pairs.empty()) {
6✔
1196
            auto anno_str = logmsg_annotations_handlers.to_string(
1197
                line_meta.bm_annotations);
1✔
1198

1199
            if (sqlite3_bind_text(stmt,
1✔
1200
                                  8,
1201
                                  anno_str.c_str(),
1202
                                  anno_str.length(),
1✔
1203
                                  SQLITE_TRANSIENT)
1204
                != SQLITE_OK)
1✔
1205
            {
UNCOV
1206
                log_error("could not bind annotations -- %s",
×
1207
                          sqlite3_errmsg(db));
1208
                return;
×
1209
            }
1210
        } else {
1✔
1211
            sqlite3_bind_null(stmt, 8);
5✔
1212
        }
1213

1214
        if (line_meta.bm_opid.empty()) {
6✔
1215
            sqlite3_bind_null(stmt, 9);
6✔
1216
        } else {
UNCOV
1217
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1218
        }
1219

1220
        if (sqlite3_step(stmt) != SQLITE_DONE) {
6✔
UNCOV
1221
            log_error("could not execute bookmark insert statement -- %s",
×
1222
                      sqlite3_errmsg(db));
1223
            return;
×
1224
        }
1225

1226
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
6✔
1227
                                          lf->get_format_ptr()->get_name(),
6✔
1228
                                          line_hash);
1229

1230
        sqlite3_reset(stmt);
6✔
1231
    }
6✔
1232
}
1233

1234
static void
1235
save_time_bookmarks()
19✔
1236
{
1237
    auto_sqlite3 db;
19✔
1238
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
19✔
1239
    auto_mem<char, sqlite3_free> errmsg;
19✔
1240
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
19✔
1241

1242
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
19✔
UNCOV
1243
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
UNCOV
1244
        return;
×
1245
    }
1246

1247
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
19✔
1248
        != SQLITE_OK)
19✔
1249
    {
UNCOV
1250
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1251
        return;
×
1252
    }
1253

1254
    if (sqlite3_exec(
19✔
1255
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1256
        != SQLITE_OK)
19✔
1257
    {
UNCOV
1258
        log_error("unable to begin transaction -- %s", errmsg.in());
×
UNCOV
1259
        return;
×
1260
    }
1261

1262
    {
1263
        static const char* UPDATE_NETLOCS_STMT
1264
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1265

1266
        std::set<std::string> netlocs;
19✔
1267

1268
        isc::to<tailer::looper&, services::remote_tailer_t>().send_and_wait(
19✔
1269
            [&netlocs](auto& tlooper) { netlocs = tlooper.active_netlocs(); });
38✔
1270

1271
        if (sqlite3_prepare_v2(
19✔
1272
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1273
            != SQLITE_OK)
19✔
1274
        {
UNCOV
1275
            log_error("could not prepare recent_netlocs statement -- %s",
×
1276
                      sqlite3_errmsg(db));
UNCOV
1277
            return;
×
1278
        }
1279

1280
        for (const auto& netloc : netlocs) {
19✔
UNCOV
1281
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1282

UNCOV
1283
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
UNCOV
1284
                log_error("could not execute bookmark insert statement -- %s",
×
1285
                          sqlite3_errmsg(db));
1286
                return;
×
1287
            }
1288

UNCOV
1289
            sqlite3_reset(stmt.in());
×
1290
        }
1291
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
19✔
1292
    }
19✔
1293

1294
    auto& lss = lnav_data.ld_log_source;
19✔
1295
    auto& bm = lss.get_user_bookmarks();
19✔
1296

1297
    if (sqlite3_prepare_v2(db.in(),
19✔
1298
                           "DELETE FROM bookmarks WHERE "
1299
                           " log_time = ? and log_format = ? and log_hash = ? "
1300
                           " and session_time = ?",
1301
                           -1,
1302
                           stmt.out(),
1303
                           nullptr)
1304
        != SQLITE_OK)
19✔
1305
    {
UNCOV
1306
        log_error("could not prepare bookmark delete statement -- %s",
×
1307
                  sqlite3_errmsg(db));
UNCOV
1308
        return;
×
1309
    }
1310

1311
    for (auto& marked_session_line : marked_session_lines) {
21✔
1312
        sqlite3_clear_bindings(stmt.in());
2✔
1313

1314
        if (bind_values(stmt,
4✔
1315
                        marked_session_line.sl_time,
1316
                        marked_session_line.sl_format_name,
1317
                        marked_session_line.sl_line_hash,
2✔
1318
                        lnav_data.ld_session_time)
1319
            != SQLITE_OK)
2✔
1320
        {
UNCOV
1321
            continue;
×
1322
        }
1323

1324
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
UNCOV
1325
            log_error("could not execute bookmark insert statement -- %s",
×
1326
                      sqlite3_errmsg(db));
UNCOV
1327
            return;
×
1328
        }
1329

1330
        sqlite3_reset(stmt.in());
2✔
1331
    }
1332

1333
    marked_session_lines.clear();
19✔
1334

1335
    if (sqlite3_prepare_v2(db.in(),
19✔
1336
                           "REPLACE INTO bookmarks"
1337
                           " (log_time, log_format, log_hash, session_time, "
1338
                           "part_name, comment, tags, annotations, log_opid)"
1339
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
1340
                           -1,
1341
                           stmt.out(),
1342
                           nullptr)
1343
        != SQLITE_OK)
19✔
1344
    {
1345
        log_error("could not prepare bookmark replace statement -- %s",
×
1346
                  sqlite3_errmsg(db));
UNCOV
1347
        return;
×
1348
    }
1349

1350
    {
1351
        for (auto file_iter = lnav_data.ld_log_source.begin();
19✔
1352
             file_iter != lnav_data.ld_log_source.end();
40✔
1353
             ++file_iter)
21✔
1354
        {
1355
            auto lf = (*file_iter)->get_file();
21✔
1356

1357
            if (lf == nullptr) {
21✔
UNCOV
1358
                continue;
×
1359
            }
1360
            if (lf->size() == 0) {
21✔
UNCOV
1361
                continue;
×
1362
            }
1363

1364
            content_line_t base_content_line;
21✔
1365
            base_content_line = lss.get_file_base_content_line(file_iter);
21✔
1366
            base_content_line
1367
                = content_line_t(base_content_line + lf->size() - 1);
21✔
1368

1369
            if (!bind_line(db.in(),
21✔
1370
                           stmt.in(),
1371
                           base_content_line,
1372
                           lnav_data.ld_session_time))
1373
            {
UNCOV
1374
                continue;
×
1375
            }
1376

1377
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
21✔
UNCOV
1378
                log_error("could not bind log hash -- %s",
×
1379
                          sqlite3_errmsg(db.in()));
UNCOV
1380
                return;
×
1381
            }
1382

1383
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
21✔
UNCOV
1384
                log_error("could not execute bookmark insert statement -- %s",
×
1385
                          sqlite3_errmsg(db));
UNCOV
1386
                return;
×
1387
            }
1388

1389
            sqlite3_reset(stmt.in());
21✔
1390
        }
21✔
1391
    }
1392

1393
    save_user_bookmarks(db.in(), stmt.in(), bm[&textview_curses::BM_USER]);
19✔
1394
    for (const auto& ldd : lss) {
40✔
1395
        auto* lf = ldd->get_file_ptr();
21✔
1396
        if (lf == nullptr) {
21✔
UNCOV
1397
            continue;
×
1398
        }
1399

1400
        save_meta_bookmarks(db.in(), stmt.in(), lf);
21✔
1401
    }
1402

1403
    if (sqlite3_prepare_v2(db.in(),
19✔
1404
                           "DELETE FROM time_offset WHERE "
1405
                           " log_time = ? and log_format = ? and log_hash = ? "
1406
                           " and session_time = ?",
1407
                           -1,
1408
                           stmt.out(),
1409
                           NULL)
1410
        != SQLITE_OK)
19✔
1411
    {
UNCOV
1412
        log_error("could not prepare time_offset delete statement -- %s",
×
1413
                  sqlite3_errmsg(db));
UNCOV
1414
        return;
×
1415
    }
1416

1417
    for (auto& offset_session_line : offset_session_lines) {
20✔
1418
        sqlite3_clear_bindings(stmt.in());
1✔
1419

1420
        if (bind_values(stmt,
2✔
1421
                        offset_session_line.sl_time,
1422
                        offset_session_line.sl_format_name,
1423
                        offset_session_line.sl_line_hash,
1✔
1424
                        lnav_data.ld_session_time)
1425
            != SQLITE_OK)
1✔
1426
        {
UNCOV
1427
            continue;
×
1428
        }
1429

1430
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
UNCOV
1431
            log_error("could not execute bookmark insert statement -- %s",
×
1432
                      sqlite3_errmsg(db));
UNCOV
1433
            return;
×
1434
        }
1435

1436
        sqlite3_reset(stmt.in());
1✔
1437
    }
1438

1439
    offset_session_lines.clear();
19✔
1440

1441
    if (sqlite3_prepare_v2(db.in(),
19✔
1442
                           "REPLACE INTO time_offset"
1443
                           " (log_time, log_format, log_hash, session_time, "
1444
                           "offset_sec, offset_usec)"
1445
                           " VALUES (?, ?, ?, ?, ?, ?)",
1446
                           -1,
1447
                           stmt.out(),
1448
                           nullptr)
1449
        != SQLITE_OK)
19✔
1450
    {
UNCOV
1451
        log_error("could not prepare time_offset replace statement -- %s",
×
1452
                  sqlite3_errmsg(db));
UNCOV
1453
        return;
×
1454
    }
1455

1456
    {
1457
        for (auto file_iter = lnav_data.ld_log_source.begin();
19✔
1458
             file_iter != lnav_data.ld_log_source.end();
40✔
1459
             ++file_iter)
21✔
1460
        {
1461
            auto lf = (*file_iter)->get_file();
21✔
1462
            if (lf == nullptr) {
21✔
UNCOV
1463
                continue;
×
1464
            }
1465
            if (lf->size() == 0) {
21✔
UNCOV
1466
                continue;
×
1467
            }
1468

1469
            if (bind_values(stmt,
63✔
1470
                            lf->original_line_time(lf->begin()),
1471
                            lf->get_format()->get_name(),
42✔
1472
                            lf->get_content_id(),
21✔
1473
                            lnav_data.ld_session_time)
1474
                != SQLITE_OK)
21✔
1475
            {
UNCOV
1476
                continue;
×
1477
            }
1478

1479
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
21✔
UNCOV
1480
                log_error("could not bind log hash -- %s",
×
1481
                          sqlite3_errmsg(db.in()));
UNCOV
1482
                return;
×
1483
            }
1484

1485
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
21✔
UNCOV
1486
                log_error("could not bind log hash -- %s",
×
1487
                          sqlite3_errmsg(db.in()));
1488
                return;
×
1489
            }
1490

1491
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
21✔
UNCOV
1492
                log_error("could not execute bookmark insert statement -- %s",
×
1493
                          sqlite3_errmsg(db));
UNCOV
1494
                return;
×
1495
            }
1496

1497
            sqlite3_reset(stmt.in());
21✔
1498
        }
21✔
1499
    }
1500

1501
    for (const auto& ls : lss) {
40✔
1502
        if (ls->get_file() == nullptr) {
21✔
1503
            continue;
18✔
1504
        }
1505

1506
        const auto lf = ls->get_file();
21✔
1507
        if (!lf->is_time_adjusted()) {
21✔
1508
            continue;
18✔
1509
        }
1510

1511
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
1512
        auto offset = lf->get_time_offset();
3✔
1513

1514
        bind_values(stmt.in(),
6✔
1515
                    lf->original_line_time(line_iter),
1516
                    lf->get_format()->get_name(),
6✔
1517
                    lf->get_content_id(),
3✔
1518
                    lnav_data.ld_session_time,
1519
                    offset.tv_sec,
1520
                    offset.tv_usec);
1521

1522
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
3✔
UNCOV
1523
            log_error("could not execute bookmark insert statement -- %s",
×
1524
                      sqlite3_errmsg(db));
1525
            return;
×
1526
        }
1527

1528
        sqlite3_reset(stmt.in());
3✔
1529
    }
21✔
1530

1531
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
19✔
1532
        != SQLITE_OK)
19✔
1533
    {
UNCOV
1534
        log_error("unable to begin transaction -- %s", errmsg.in());
×
UNCOV
1535
        return;
×
1536
    }
1537

1538
    if (sqlite3_exec(db.in(), BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
19✔
1539
        != SQLITE_OK)
19✔
1540
    {
UNCOV
1541
        log_error("unable to delete old bookmarks -- %s", errmsg.in());
×
UNCOV
1542
        return;
×
1543
    }
1544

1545
    if (sqlite3_exec(db.in(), NETLOC_LRU_STMT, nullptr, nullptr, errmsg.out())
19✔
1546
        != SQLITE_OK)
19✔
1547
    {
UNCOV
1548
        log_error("unable to delete old netlocs -- %s", errmsg.in());
×
UNCOV
1549
        return;
×
1550
    }
1551
}
19✔
1552

1553
static void
1554
save_session_with_id(const std::string& session_id)
19✔
1555
{
1556
    auto_mem<FILE> file(fclose);
19✔
1557
    yajl_gen handle = nullptr;
19✔
1558

1559
    /* TODO: save the last search query */
1560

1561
    log_info("saving session with id: %s", session_id.c_str());
19✔
1562

1563
    auto view_base_name
1564
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
38✔
1565
                      session_id,
1566
                      lnav_data.ld_session_time,
1567
                      getppid());
19✔
1568
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
19✔
1569
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
19✔
1570

1571
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
19✔
UNCOV
1572
        perror("Unable to open session file");
×
1573
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
19✔
UNCOV
1574
        perror("Unable to create yajl_gen object");
×
1575
    } else {
1576
        yajl_gen_config(
19✔
1577
            handle, yajl_gen_print_callback, yajl_writer, file.in());
1578

1579
        {
1580
            yajlpp_map root_map(handle);
19✔
1581

1582
            root_map.gen("save-time");
19✔
1583
            root_map.gen((long long) time(nullptr));
19✔
1584

1585
            root_map.gen("time-offset");
19✔
1586
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
19✔
1587

1588
            root_map.gen("files");
19✔
1589

1590
            {
1591
                yajlpp_array file_list(handle);
19✔
1592

1593
                for (auto& ld_file_name :
19✔
1594
                     lnav_data.ld_active_files.fc_file_names)
59✔
1595
                {
1596
                    file_list.gen(ld_file_name.first);
21✔
1597
                }
1598
            }
19✔
1599

1600
            root_map.gen("file-states");
19✔
1601

1602
            {
1603
                yajlpp_map file_states(handle);
19✔
1604

1605
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
40✔
1606
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
21✔
1607

1608
                    file_states.gen(lf->get_filename().native());
21✔
1609

1610
                    {
1611
                        yajlpp_map file_state(handle);
21✔
1612

1613
                        file_state.gen("visible");
21✔
1614
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
21✔
1615
                    }
21✔
1616
                }
1617
            }
19✔
1618

1619
            root_map.gen("views");
19✔
1620

1621
            {
1622
                yajlpp_map top_view_map(handle);
19✔
1623

1624
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
190✔
1625
                    auto& tc = lnav_data.ld_views[lpc];
171✔
1626
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
171✔
1627

1628
                    top_view_map.gen(lnav_view_strings[lpc]);
171✔
1629

1630
                    yajlpp_map view_map(handle);
171✔
1631

1632
                    view_map.gen("top_line");
171✔
1633

1634
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
171✔
1635
                        view_map.gen(-1LL);
164✔
1636
                    } else {
1637
                        view_map.gen((long long) tc.get_top());
7✔
1638
                    }
1639

1640
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
209✔
1641
                        && tc.get_inner_height() > 0_vl
19✔
1642
                        && tc.get_selection() != tc.get_inner_height() - 1)
209✔
1643
                    {
1644
                        auto sel = tc.get_selection();
1✔
1645
                        if (sel) {
1✔
1646
                            view_map.gen("focused_line");
1✔
1647
                            view_map.gen((long long) sel.value());
1✔
1648

1649
                            if (ta != nullptr) {
1✔
1650
                                auto anchor_opt
UNCOV
1651
                                    = ta->anchor_for_row(sel.value());
×
1652
                                if (anchor_opt) {
×
UNCOV
1653
                                    view_map.gen("anchor");
×
UNCOV
1654
                                    view_map.gen(anchor_opt.value());
×
1655
                                }
1656
                            }
1657
                        }
1658
                    }
1659

1660
                    view_map.gen("search");
171✔
1661
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
171✔
1662

1663
                    view_map.gen("word_wrap");
171✔
1664
                    view_map.gen(tc.get_word_wrap());
171✔
1665

1666
                    auto tss = tc.get_sub_source();
171✔
1667
                    if (tss == nullptr) {
171✔
1668
                        continue;
38✔
1669
                    }
1670

1671
                    view_map.gen("filtering");
133✔
1672
                    view_map.gen(tss->tss_apply_filters);
133✔
1673

1674
                    auto& fs = tss->get_filters();
133✔
1675

1676
                    view_map.gen("commands");
133✔
1677
                    yajlpp_array cmd_array(handle);
133✔
1678

1679
                    for (const auto& filter : fs) {
136✔
1680
                        auto cmd = filter->to_command();
3✔
1681

1682
                        if (cmd.empty()) {
3✔
UNCOV
1683
                            continue;
×
1684
                        }
1685

1686
                        cmd_array.gen(cmd);
3✔
1687

1688
                        if (!filter->is_enabled()) {
3✔
1689
                            cmd_array.gen("disable-filter " + filter->get_id());
1✔
1690
                        }
1691
                    }
3✔
1692

1693
                    auto& hmap = lnav_data.ld_views[lpc].get_highlights();
133✔
1694

1695
                    for (auto& hl : hmap) {
1,558✔
1696
                        if (hl.first.first != highlight_source_t::INTERACTIVE) {
1,425✔
1697
                            continue;
1,425✔
1698
                        }
UNCOV
1699
                        cmd_array.gen("highlight " + hl.first.second);
×
1700
                    }
1701

1702
                    auto* ttt = dynamic_cast<text_time_translator*>(tss);
133✔
1703
                    if (ttt != nullptr) {
133✔
1704
                        for (const auto& format :
114✔
1705
                             log_format::get_root_formats())
8,388✔
1706
                        {
1707
                            auto field_states = format->get_field_states();
8,160✔
1708

1709
                            for (const auto& fs_pair : field_states) {
110,736✔
1710
                                if (!fs_pair.second.lvm_user_hidden) {
102,576✔
1711
                                    continue;
102,564✔
1712
                                }
1713

1714
                                if (fs_pair.second.lvm_user_hidden.value()) {
12✔
1715
                                    cmd_array.gen(
12✔
1716
                                        "hide-fields "
1717
                                        + format->get_name().to_string() + "."
24✔
1718
                                        + fs_pair.first.to_string());
48✔
1719
                                } else if (fs_pair.second.lvm_hidden) {
×
UNCOV
1720
                                    cmd_array.gen(
×
1721
                                        "show-fields "
UNCOV
1722
                                        + format->get_name().to_string() + "."
×
UNCOV
1723
                                        + fs_pair.first.to_string());
×
1724
                                }
1725
                            }
1726
                        }
8,160✔
1727

1728
                        auto min_time_opt = ttt->get_min_row_time();
114✔
1729
                        auto max_time_opt = ttt->get_max_row_time();
114✔
1730
                        char min_time_str[32], max_time_str[32];
1731

1732
                        if (min_time_opt) {
114✔
1733
                            sql_strftime(min_time_str,
1✔
1734
                                         sizeof(min_time_str),
1735
                                         min_time_opt.value());
1✔
1736
                            cmd_array.gen("hide-lines-before "
1✔
1737
                                          + std::string(min_time_str));
3✔
1738
                        }
1739
                        if (max_time_opt) {
114✔
UNCOV
1740
                            sql_strftime(max_time_str,
×
1741
                                         sizeof(max_time_str),
1742
                                         max_time_opt.value());
×
UNCOV
1743
                            cmd_array.gen("hide-lines-after "
×
UNCOV
1744
                                          + std::string(max_time_str));
×
1745
                        }
1746

1747
                        if (lpc == LNV_LOG) {
114✔
1748
                            auto& lss = lnav_data.ld_log_source;
19✔
1749
                            auto mark_expr = lss.get_sql_marker_text();
19✔
1750
                            if (!mark_expr.empty()) {
19✔
UNCOV
1751
                                cmd_array.gen("mark-expr " + mark_expr);
×
1752
                            }
1753
                        }
19✔
1754
                    }
1755
                }
171✔
1756
            }
19✔
1757
        }
19✔
1758

1759
        yajl_gen_clear(handle);
19✔
1760
        yajl_gen_free(handle);
19✔
1761

1762
        fclose(file.release());
19✔
1763

1764
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
19✔
1765

1766
        log_info("Saved session: %s", view_file_name.c_str());
19✔
1767
    }
1768
}
19✔
1769

1770
void
1771
save_session()
19✔
1772
{
1773
    if (lnav_data.ld_flags & LNF_SECURE_MODE) {
19✔
UNCOV
1774
        log_info("secure mode is enabled, not saving session");
×
UNCOV
1775
        return;
×
1776
    }
1777

1778
    log_debug("BEGIN save_session");
19✔
1779
    save_time_bookmarks();
19✔
1780

1781
    const auto opt_session_id = compute_session_id();
19✔
1782
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
37✔
1783
    for (const auto& pair : lnav_data.ld_session_id) {
22✔
1784
        if (opt_session_id && pair.first == opt_session_id.value()) {
3✔
1785
            continue;
2✔
1786
        }
1787
        save_session_with_id(pair.first);
1✔
1788
    }
1789
    log_debug("END save_session");
19✔
1790
}
19✔
1791

1792
void
1793
reset_session()
6✔
1794
{
1795
    log_info("reset session: time=%d", lnav_data.ld_session_time);
6✔
1796

1797
    save_session();
6✔
1798

1799
    lnav_data.ld_session_time = time(nullptr);
6✔
1800
    session_data.sd_file_states.clear();
6✔
1801

1802
    for (auto& tc : lnav_data.ld_views) {
60✔
1803
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
1804
        auto& hmap = tc.get_highlights();
54✔
1805
        auto hl_iter = hmap.begin();
54✔
1806

1807
        while (hl_iter != hmap.end()) {
552✔
1808
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
1809
                ++hl_iter;
498✔
1810
            } else {
UNCOV
1811
                hmap.erase(hl_iter++);
×
1812
            }
1813
        }
1814

1815
        if (ttt != nullptr) {
54✔
1816
            ttt->clear_min_max_row_times();
36✔
1817
        }
1818
    }
1819

1820
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
1821
        lf->reset_state();
8✔
1822
    }
1823

1824
    // XXX clean this up
1825
    lnav_data.ld_log_source.set_force_rebuild();
6✔
1826
    lnav_data.ld_log_source.set_marked_only(false);
6✔
1827
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
1828
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
1829
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
1830
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
1831
    rebuild_indexes(std::nullopt);
6✔
1832

1833
    lnav_data.ld_db_row_source.reset_user_state();
6✔
1834

1835
    for (auto& tc : lnav_data.ld_views) {
60✔
1836
        text_sub_source* tss = tc.get_sub_source();
54✔
1837

1838
        if (tss == nullptr) {
54✔
1839
            continue;
12✔
1840
        }
1841
        tss->get_filters().clear_filters();
42✔
1842
        tss->tss_apply_filters = true;
42✔
1843
        tss->text_filters_changed();
42✔
1844
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
1845
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
1846
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
1847
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
1848
        tc.reload_data();
42✔
1849
    }
1850

1851
    lnav_data.ld_filter_view.reload_data();
6✔
1852
    lnav_data.ld_files_view.reload_data();
6✔
1853
    for (const auto& format : log_format::get_root_formats()) {
432✔
1854
        auto* elf = dynamic_cast<external_log_format*>(format.get());
426✔
1855

1856
        if (elf == nullptr) {
426✔
1857
            continue;
30✔
1858
        }
1859

1860
        bool changed = false;
396✔
1861
        for (const auto& vd : elf->elf_value_defs) {
5,730✔
1862
            if (vd.second->vd_meta.lvm_user_hidden) {
5,334✔
UNCOV
1863
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
UNCOV
1864
                changed = true;
×
1865
            }
1866
        }
1867
        if (changed) {
396✔
UNCOV
1868
            elf->elf_value_defs_state->vds_generation += 1;
×
1869
        }
1870
    }
1871
}
6✔
1872

1873
void
1874
lnav::session::restore_view_states()
21✔
1875
{
1876
    log_debug("restoring view states");
21✔
1877
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
210✔
1878
        const auto& vs = session_data.sd_view_states[view_index];
189✔
1879
        auto& tview = lnav_data.ld_views[view_index];
189✔
1880
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
189✔
1881
        bool has_loc = false;
189✔
1882

1883
        if (view_index == LNV_TEXT) {
189✔
1884
            auto lf = lnav_data.ld_text_source.current_file();
21✔
1885
            if (lf != nullptr) {
21✔
UNCOV
1886
                const auto& init_loc = lf->get_open_options().loo_init_location;
×
UNCOV
1887
                has_loc = init_loc.valid() || init_loc.is<file_location_tail>();
×
UNCOV
1888
                if (!has_loc) {
×
UNCOV
1889
                    switch (lf->get_text_format()) {
×
UNCOV
1890
                        case text_format_t::TF_UNKNOWN:
×
1891
                        case text_format_t::TF_LOG:
UNCOV
1892
                            break;
×
UNCOV
1893
                        default:
×
1894
                            if (vs.vs_top == 0 && tview.get_top() > 0) {
×
UNCOV
1895
                                log_debug("setting to 0");
×
UNCOV
1896
                                tview.set_top(0_vl);
×
1897
                            }
UNCOV
1898
                            break;
×
1899
                    }
1900
                }
1901
            }
1902
        } else if (view_index == LNV_LOG) {
189✔
1903
            file_location_t log_loc{mapbox::util::no_init{}};
21✔
1904
            for (const auto& ld : lnav_data.ld_log_source) {
43✔
1905
                const auto* lf = ld->get_file_ptr();
22✔
1906
                if (lf == nullptr) {
22✔
1907
                    continue;
×
1908
                }
1909

1910
                const auto& init_loc = lf->get_open_options().loo_init_location;
22✔
1911
                if (init_loc.valid() && init_loc.is<std::string>()) {
22✔
UNCOV
1912
                    has_loc = true;
×
UNCOV
1913
                    log_loc = init_loc;
×
1914
                }
1915
            }
1916

1917
            if (log_loc.valid()) {
21✔
1918
                auto anchor = log_loc.get<std::string>();
×
UNCOV
1919
                auto row_opt = lnav_data.ld_log_source.row_for_anchor(anchor);
×
UNCOV
1920
                if (row_opt) {
×
UNCOV
1921
                    log_info("setting LOG view to desired loc: %s",
×
1922
                             anchor.c_str());
UNCOV
1923
                    tview.set_selection(row_opt.value());
×
1924
                }
1925
            }
1926
        }
21✔
1927

1928
        if (!vs.vs_search.empty()) {
189✔
UNCOV
1929
            tview.execute_search(vs.vs_search);
×
UNCOV
1930
            tview.set_follow_search_for(-1, {});
×
1931
        }
1932
        tview.set_word_wrap(vs.vs_word_wrap);
189✔
1933
        if (tview.get_sub_source() != nullptr) {
189✔
1934
            tview.get_sub_source()->tss_apply_filters = vs.vs_filtering;
147✔
1935
        }
1936
        for (const auto& cmdline : vs.vs_commands) {
204✔
1937
            auto active = ensure_view(&tview);
15✔
1938
            auto exec_cmd_res
1939
                = execute_command(lnav_data.ld_exec_context, cmdline);
15✔
1940
            if (exec_cmd_res.isOk()) {
15✔
1941
                log_info("Result: %s", exec_cmd_res.unwrap().c_str());
13✔
1942
            } else {
1943
                log_error("Result: %s",
6✔
1944
                          exec_cmd_res.unwrapErr()
1945
                              .to_attr_line()
1946
                              .get_string()
1947
                              .c_str());
1948
            }
1949
            if (!active) {
15✔
1950
                lnav_data.ld_view_stack.pop_back();
10✔
1951
                lnav_data.ld_view_stack.top() | [](auto* tc) {
10✔
1952
                    // XXX
1953
                    if (tc == &lnav_data.ld_views[LNV_TIMELINE]) {
10✔
UNCOV
1954
                        auto tss = tc->get_sub_source();
×
UNCOV
1955
                        tss->text_filters_changed();
×
UNCOV
1956
                        tc->reload_data();
×
1957
                    }
1958
                };
10✔
1959
            }
1960
        }
15✔
1961

1962
        if (!has_loc && vs.vs_top >= 0
189✔
1963
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
378✔
1964
                || tview.get_top() == tview.get_top_for_last_row()))
189✔
1965
        {
1966
            log_info("restoring %s view top: %d",
4✔
1967
                     lnav_view_strings[view_index].data(),
1968
                     vs.vs_top);
1969
            tview.set_top(vis_line_t(vs.vs_top), true);
4✔
1970
            tview.set_selection(-1_vl);
4✔
1971
        }
1972
        if (!has_loc && vs.vs_selection) {
189✔
1973
            log_info("restoring %s view selection: %d",
1✔
1974
                     lnav_view_strings[view_index].data(),
1975
                     vs.vs_selection.value());
1976
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
1✔
1977
        }
1978
        auto sel = tview.get_selection();
189✔
1979
        if (sel && ta != nullptr && vs.vs_anchor && !vs.vs_anchor->empty()) {
189✔
1980
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
1981

UNCOV
1982
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
UNCOV
1983
                log_info("%s view anchor mismatch %s != %s",
×
1984
                         lnav_view_strings[view_index].data(),
1985
                         curr_anchor.value_or("").c_str(),
1986
                         vs.vs_anchor.value().c_str());
1987

1988
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
1989
                if (row_opt) {
×
UNCOV
1990
                    tview.set_selection(row_opt.value());
×
1991
                }
1992
            }
1993
        }
1994
        sel = tview.get_selection();
189✔
1995
        if (!sel && tview.get_top() == 0_vl && tview.listview_rows(tview) > 0) {
189✔
1996
            tview.set_selection(0_vl);
×
1997
        }
1998
        log_info("%s view actual top/selection: %d/%d",
189✔
1999
                 lnav_view_strings[view_index].data(),
2000
                 tview.get_top(),
2001
                 tview.get_selection().value_or(-1_vl));
2002
    }
2003
}
21✔
2004

2005
void
2006
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
2007
{
2008
    constexpr const char* STMT = R"(
×
2009
       INSERT INTO regex101_entries
2010
          (format_name, regex_name, permalink, delete_code)
2011
          VALUES (?, ?, ?, ?);
2012
)";
2013

UNCOV
2014
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2015
    auto_sqlite3 db;
×
2016

UNCOV
2017
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
UNCOV
2018
        return;
×
2019
    }
2020

2021
    auto_mem<char, sqlite3_free> errmsg;
×
UNCOV
2022
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2023
        != SQLITE_OK)
×
2024
    {
UNCOV
2025
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
UNCOV
2026
        return;
×
2027
    }
2028

2029
    auto prep_res = prepare_stmt(db.in(),
2030
                                 STMT,
UNCOV
2031
                                 ei.re_format_name,
×
UNCOV
2032
                                 ei.re_regex_name,
×
2033
                                 ei.re_permalink,
×
UNCOV
2034
                                 ei.re_delete_code);
×
2035

UNCOV
2036
    if (prep_res.isErr()) {
×
UNCOV
2037
        return;
×
2038
    }
2039

UNCOV
2040
    auto ps = prep_res.unwrap();
×
2041

2042
    ps.execute();
×
2043
}
2044

2045
template<>
2046
struct from_sqlite<lnav::session::regex101::entry> {
2047
    inline lnav::session::regex101::entry operator()(int argc,
2048
                                                     sqlite3_value** argv,
2049
                                                     int argi)
2050
    {
2051
        return {
UNCOV
2052
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
UNCOV
2053
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
2054
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
2055
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
2056
        };
2057
    }
2058
};
2059

2060
Result<std::vector<lnav::session::regex101::entry>, std::string>
UNCOV
2061
lnav::session::regex101::get_entries()
×
2062
{
2063
    constexpr const char* STMT = R"(
×
2064
       SELECT * FROM regex101_entries;
2065
)";
2066

2067
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2068
    auto_sqlite3 db;
×
2069

2070
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
UNCOV
2071
        return Err(std::string());
×
2072
    }
2073

UNCOV
2074
    auto_mem<char, sqlite3_free> errmsg;
×
2075
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
UNCOV
2076
        != SQLITE_OK)
×
2077
    {
UNCOV
2078
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
UNCOV
2079
        return Err(std::string(errmsg));
×
2080
    }
2081

UNCOV
2082
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
UNCOV
2083
    bool done = false;
×
UNCOV
2084
    std::vector<entry> retval;
×
2085

UNCOV
2086
    while (!done) {
×
UNCOV
2087
        auto fetch_res = ps.fetch_row<entry>();
×
2088

UNCOV
2089
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
UNCOV
2090
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2091
        }
2092

UNCOV
2093
        fetch_res.match(
×
UNCOV
2094
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
UNCOV
2095
            [](const prepared_stmt::fetch_error&) {},
×
UNCOV
2096
            [&retval](entry en) { retval.emplace_back(en); });
×
2097
    }
UNCOV
2098
    return Ok(retval);
×
2099
}
2100

2101
void
UNCOV
2102
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2103
                                      const std::string& regex_name)
2104
{
UNCOV
2105
    constexpr const char* STMT = R"(
×
2106
       DELETE FROM regex101_entries WHERE
2107
          format_name = ? AND regex_name = ?;
2108
)";
2109

UNCOV
2110
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
UNCOV
2111
    auto_sqlite3 db;
×
2112

UNCOV
2113
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
UNCOV
2114
        return;
×
2115
    }
2116

UNCOV
2117
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2118

UNCOV
2119
    if (prep_res.isErr()) {
×
UNCOV
2120
        return;
×
2121
    }
2122

UNCOV
2123
    auto ps = prep_res.unwrap();
×
2124

UNCOV
2125
    ps.execute();
×
2126
}
2127

2128
lnav::session::regex101::get_result_t
UNCOV
2129
lnav::session::regex101::get_entry(const std::string& format_name,
×
2130
                                   const std::string& regex_name)
2131
{
UNCOV
2132
    constexpr const char* STMT = R"(
×
2133
       SELECT * FROM regex101_entries WHERE
2134
          format_name = ? AND regex_name = ?;
2135
    )";
2136

UNCOV
2137
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
UNCOV
2138
    auto_sqlite3 db;
×
2139

UNCOV
2140
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
UNCOV
2141
        return error{std::string()};
×
2142
    }
2143

UNCOV
2144
    auto_mem<char, sqlite3_free> errmsg;
×
UNCOV
2145
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
UNCOV
2146
        != SQLITE_OK)
×
2147
    {
UNCOV
2148
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
UNCOV
2149
        return error{std::string(errmsg)};
×
2150
    }
2151

UNCOV
2152
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
UNCOV
2153
    if (prep_res.isErr()) {
×
UNCOV
2154
        return error{prep_res.unwrapErr()};
×
2155
    }
2156

UNCOV
2157
    auto ps = prep_res.unwrap();
×
UNCOV
2158
    return ps.fetch_row<entry>().match(
×
UNCOV
2159
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
UNCOV
2160
            return error{fe.fe_msg};
×
2161
        },
UNCOV
2162
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
UNCOV
2163
            return no_entry{};
×
2164
        },
UNCOV
2165
        [](const entry& en) -> get_result_t { return en; });
×
2166
}
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