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

tstack / lnav / 23612272155-2889

26 Mar 2026 06:47PM UTC coverage: 69.031% (+0.03%) from 69.004%
23612272155-2889

push

github

tstack
[breakpoints] move breakpoints into logfile_sub_source

127 of 180 new or added lines in 10 files covered. (70.56%)

435 existing lines in 4 files now uncovered.

52876 of 76598 relevant lines covered (69.03%)

528416.58 hits per line

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

74.28
/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 "timeline_source.hh"
61
#include "vtab_module.hh"
62
#include "yajlpp/yajlpp.hh"
63
#include "yajlpp/yajlpp_def.hh"
64

65
session_data_t session_data;
66
recent_refs_t recent_refs;
67

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

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

84
    PRIMARY KEY (log_time, log_format, log_hash, session_time)
85
);
86

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

96
    PRIMARY KEY (log_time, log_format, log_hash, session_time)
97
);
98

99
CREATE TABLE IF NOT EXISTS recent_netlocs (
100
    netloc text,
101

102
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
103

104
    PRIMARY KEY (netloc)
105
);
106

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

113
    PRIMARY KEY (format_name, regex_name),
114

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

121
CREATE TABLE IF NOT EXISTS text_bookmarks (
122
    file_path    text NOT NULL,
123
    line_number  integer NOT NULL,
124
    line_hash    text NOT NULL,
125
    mark_type    text NOT NULL DEFAULT 'user',
126
    session_time integer NOT NULL,
127

128
    PRIMARY KEY (file_path, line_number, mark_type),
129

130
    CHECK(line_number >= 0),
131
    CHECK(mark_type IN ('user', 'sticky'))
132
);
133
)";
134

135
static const char* const BOOKMARK_LRU_STMT
136
    = "DELETE FROM bookmarks WHERE access_time <= "
137
      "  (SELECT DISTINCT access_time FROM bookmarks "
138
      "   ORDER BY access_time DESC LIMIT 1 OFFSET 50000)";
139

140
static const char* const NETLOC_LRU_STMT
141
    = "DELETE FROM recent_netlocs WHERE access_time <= "
142
      "  (SELECT DISTINCT access_time FROM bookmarks "
143
      "   ORDER BY access_time DESC LIMIT 1 OFFSET 10)";
144

145
static const char* const TEXT_BOOKMARK_LRU_STMT
146
    = "DELETE FROM text_bookmarks WHERE session_time <= "
147
      "  (SELECT DISTINCT session_time FROM text_bookmarks "
148
      "   ORDER BY session_time DESC LIMIT 1 OFFSET 50000)";
149

150
static const char* const UPGRADE_STMTS[] = {
151
    R"(ALTER TABLE bookmarks ADD COLUMN comment text DEFAULT '';)",
152
    R"(ALTER TABLE bookmarks ADD COLUMN tags text DEFAULT '';)",
153
    R"(ALTER TABLE bookmarks ADD COLUMN annotations text DEFAULT NULL;)",
154
    R"(ALTER TABLE bookmarks ADD COLUMN log_opid text DEFAULT NULL;)",
155
    R"(ALTER TABLE bookmarks ADD COLUMN sticky integer DEFAULT 0;)",
156
};
157

158
static constexpr size_t MAX_SESSIONS = 8;
159
static constexpr size_t MAX_SESSION_FILE_COUNT = 256;
160

161
struct session_line {
162
    session_line(struct timeval tv,
13✔
163
                 intern_string_t format_name,
164
                 std::string line_hash)
165
        : sl_time(tv), sl_format_name(format_name),
13✔
166
          sl_line_hash(std::move(line_hash))
13✔
167
    {
168
    }
13✔
169

170
    struct timeval sl_time;
171
    intern_string_t sl_format_name;
172
    std::string sl_line_hash;
173
};
174

175
static std::vector<session_line> marked_session_lines;
176
static std::vector<session_line> offset_session_lines;
177

178
static bool
179
bind_line(sqlite3* db,
25✔
180
          sqlite3_stmt* stmt,
181
          content_line_t cl,
182
          time_t session_time)
183
{
184
    auto& lss = lnav_data.ld_log_source;
25✔
185
    auto lf = lss.find(cl);
25✔
186

187
    if (lf == nullptr) {
25✔
188
        return false;
×
189
    }
190

191
    sqlite3_clear_bindings(stmt);
25✔
192

193
    auto line_iter = lf->begin() + cl;
25✔
194
    auto fr = lf->get_file_range(line_iter, false);
25✔
195
    auto read_result = lf->read_range(fr);
25✔
196

197
    if (read_result.isErr()) {
25✔
198
        return false;
×
199
    }
200

201
    auto line_hash = read_result
202
                         .map([cl](auto sbr) {
50✔
203
                             return hasher()
50✔
204
                                 .update(sbr.get_data(), sbr.length())
25✔
205
                                 .update(cl)
25✔
206
                                 .to_string();
50✔
207
                         })
208
                         .unwrap();
25✔
209

210
    return bind_values(stmt,
50✔
211
                       lf->original_line_time(line_iter),
212
                       lf->get_format()->get_name(),
50✔
213
                       line_hash,
214
                       session_time)
215
        == SQLITE_OK;
25✔
216
}
25✔
217

218
struct session_file_info {
219
    session_file_info(int timestamp, std::string id, std::string path)
52✔
220
        : sfi_timestamp(timestamp), sfi_id(std::move(id)),
52✔
221
          sfi_path(std::move(path))
52✔
222
    {
223
    }
52✔
224

225
    bool operator<(const session_file_info& other) const
78✔
226
    {
227
        if (this->sfi_timestamp < other.sfi_timestamp) {
78✔
228
            return true;
×
229
        }
230
        if (this->sfi_path < other.sfi_path) {
78✔
231
            return true;
×
232
        }
233
        return false;
78✔
234
    }
235

236
    int sfi_timestamp;
237
    std::string sfi_id;
238
    std::string sfi_path;
239
};
240

241
static void
242
cleanup_session_data()
28✔
243
{
244
    static_root_mem<glob_t, globfree> session_file_list;
28✔
245
    std::list<struct session_file_info> session_info_list;
28✔
246
    std::map<std::string, int> session_count;
28✔
247
    auto session_file_pattern = lnav::paths::dotlnav() / "*-*.ts*.json";
28✔
248

249
    if (glob(
28✔
250
            session_file_pattern.c_str(), 0, nullptr, session_file_list.inout())
251
        == 0)
28✔
252
    {
253
        for (size_t lpc = 0; lpc < session_file_list->gl_pathc; lpc++) {
77✔
254
            const char* path = session_file_list->gl_pathv[lpc];
52✔
255
            char hash_id[64];
256
            int timestamp;
257
            const char* base;
258

259
            base = strrchr(path, '/');
52✔
260
            if (base == nullptr) {
52✔
261
                continue;
×
262
            }
263
            base += 1;
52✔
264
            if (sscanf(base, "file-%63[^.].ts%d.json", hash_id, &timestamp)
52✔
265
                == 2)
52✔
266
            {
267
                session_count[hash_id] += 1;
×
268
                session_info_list.emplace_back(timestamp, hash_id, path);
×
269
            }
270
            if (sscanf(base,
52✔
271
                       "view-info-%63[^.].ts%d.ppid%*d.json",
272
                       hash_id,
273
                       &timestamp)
274
                == 2)
52✔
275
            {
276
                session_count[hash_id] += 1;
52✔
277
                session_info_list.emplace_back(timestamp, hash_id, path);
52✔
278
            }
279
        }
280
    }
281

282
    session_info_list.sort();
28✔
283

284
    size_t session_loops = 0;
28✔
285

286
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
28✔
287
        const session_file_info& front = session_info_list.front();
×
288

289
        session_loops += 1;
×
290
        if (session_loops < MAX_SESSION_FILE_COUNT
×
291
            && session_count[front.sfi_id] == 1)
×
292
        {
293
            session_info_list.splice(session_info_list.end(),
×
294
                                     session_info_list,
295
                                     session_info_list.begin());
×
296
        } else {
297
            if (remove(front.sfi_path.c_str()) != 0) {
×
298
                log_error("Unable to remove session file: %s -- %s",
×
299
                          front.sfi_path.c_str(),
300
                          strerror(errno));
301
            }
302
            session_count[front.sfi_id] -= 1;
×
303
            session_info_list.pop_front();
×
304
        }
305
    }
306

307
    session_info_list.sort();
28✔
308

309
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
28✔
310
        const session_file_info& front = session_info_list.front();
×
311

312
        if (remove(front.sfi_path.c_str()) != 0) {
×
313
            log_error("Unable to remove session file: %s -- %s",
×
314
                      front.sfi_path.c_str(),
315
                      strerror(errno));
316
        }
317
        session_count[front.sfi_id] -= 1;
×
318
        session_info_list.pop_front();
×
319
    }
320
}
28✔
321

322
void
323
init_session()
663✔
324
{
325
    lnav_data.ld_session_time = time(nullptr);
663✔
326
    lnav_data.ld_session_id.clear();
663✔
327
    session_data.sd_view_states[LNV_LOG].vs_top = -1;
663✔
328
}
663✔
329

330
static std::optional<std::string>
331
compute_session_id()
53✔
332
{
333
    bool has_files = false;
53✔
334
    hasher h;
53✔
335

336
    for (auto& ld_file_name : lnav_data.ld_active_files.fc_file_names) {
107✔
337
        if (!ld_file_name.second.loo_include_in_session) {
54✔
338
            continue;
×
339
        }
340
        has_files = true;
54✔
341
        h.update(ld_file_name.first);
54✔
342
    }
343
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
107✔
344
        if (lf->is_valid_filename()) {
54✔
345
            continue;
53✔
346
        }
347
        if (!lf->get_open_options().loo_include_in_session) {
1✔
348
            continue;
×
349
        }
350

351
        has_files = true;
1✔
352
        h.update(lf->get_content_id());
1✔
353
    }
354
    if (!has_files) {
53✔
355
        return std::nullopt;
3✔
356
    }
357

358
    return h.to_string();
50✔
359
}
360

361
std::optional<session_pair_t>
362
scan_sessions()
28✔
363
{
364
    static_root_mem<glob_t, globfree> view_info_list;
28✔
365

366
    cleanup_session_data();
28✔
367

368
    const auto session_id = compute_session_id();
28✔
369
    if (!session_id) {
28✔
370
        return std::nullopt;
1✔
371
    }
372
    auto& session_file_names = lnav_data.ld_session_id[session_id.value()];
27✔
373
    session_file_names.clear();
27✔
374

375
    auto view_info_pattern_base
376
        = fmt::format(FMT_STRING("view-info-{}.*.json"), session_id.value());
81✔
377
    auto view_info_pattern = lnav::paths::dotlnav() / view_info_pattern_base;
27✔
378
    if (glob(view_info_pattern.c_str(), 0, nullptr, view_info_list.inout())
27✔
379
        == 0)
27✔
380
    {
381
        for (size_t lpc = 0; lpc < view_info_list->gl_pathc; lpc++) {
63✔
382
            const char* path = view_info_list->gl_pathv[lpc];
38✔
383
            int timestamp, ppid, rc;
384
            const char* base;
385

386
            base = strrchr(path, '/');
38✔
387
            if (base == nullptr) {
38✔
388
                continue;
×
389
            }
390
            base += 1;
38✔
391
            if ((rc = sscanf(base,
38✔
392
                             "view-info-%*[^.].ts%d.ppid%d.json",
393
                             &timestamp,
394
                             &ppid))
395
                == 2)
38✔
396
            {
397
                ppid_time_pair_t ptp;
38✔
398

399
                ptp.first = (ppid == getppid()) ? 1 : 0;
38✔
400
                ptp.second = timestamp;
38✔
401
                session_file_names.emplace_back(ptp, path);
38✔
402
            }
403
        }
404
    }
405

406
    session_file_names.sort();
27✔
407

408
    while (session_file_names.size() > MAX_SESSIONS) {
27✔
409
        const std::string& name = session_file_names.front().second;
×
410

411
        if (remove(name.c_str()) != 0) {
×
412
            log_error("Unable to remove session: %s -- %s",
×
413
                      name.c_str(),
414
                      strerror(errno));
415
        }
416
        session_file_names.pop_front();
×
417
    }
418

419
    if (session_file_names.empty()) {
27✔
420
        return std::nullopt;
2✔
421
    }
422

423
    return std::make_optional(session_file_names.back());
25✔
424
}
28✔
425

426
static void load_text_bookmarks(sqlite3* db);
427

428
void
429
load_time_bookmarks()
28✔
430
{
431
    static const char* const BOOKMARK_STMT = R"(
432
       SELECT
433
         log_time,
434
         log_format,
435
         log_hash,
436
         session_time,
437
         part_name,
438
         access_time,
439
         comment,
440
         tags,
441
         annotations,
442
         log_opid,
443
         sticky,
444
         session_time=? AS same_session
445
       FROM bookmarks WHERE
446
         log_time BETWEEN ? AND ? AND
447
         log_format = ?
448
       ORDER BY same_session DESC, session_time DESC
449
)";
450

451
    static const char* const TIME_OFFSET_STMT = R"(
452
       SELECT
453
         *,
454
         session_time=? AS same_session
455
       FROM time_offset
456
       WHERE
457
         log_hash = ? AND
458
         log_time BETWEEN ? AND ? AND
459
         log_format = ?
460
       ORDER BY
461
         same_session DESC,
462
         session_time DESC
463
)";
464

465
    static auto op = lnav_operation{__FUNCTION__};
28✔
466

467
    auto op_guard = lnav_opid_guard::internal(op);
28✔
468
    auto& lss = lnav_data.ld_log_source;
28✔
469
    auto_sqlite3 db;
28✔
470
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
28✔
471
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
28✔
472
    bool reload_needed = false;
28✔
473
    auto_mem<char, sqlite3_free> errmsg;
28✔
474

475
    log_info("loading bookmark db: %s", db_path.c_str());
28✔
476

477
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
28✔
478
        return;
×
479
    }
480

481
    for (const char* upgrade_stmt : UPGRADE_STMTS) {
168✔
482
        auto rc = sqlite3_exec(
140✔
483
            db.in(), upgrade_stmt, nullptr, nullptr, errmsg.out());
484
        if (rc != SQLITE_OK) {
140✔
485
            auto exterr = sqlite3_extended_errcode(db.in());
140✔
486
            log_error("unable to upgrade bookmark table -- (%d/%d) %s",
140✔
487
                      rc,
488
                      exterr,
489
                      errmsg.in());
490
        }
491
    }
492

493
    {
494
        auto netloc_prep_res
495
            = prepare_stmt(db.in(), "SELECT netloc FROM recent_netlocs");
28✔
496
        if (netloc_prep_res.isErr()) {
28✔
497
            log_error("unable to get netlocs: %s",
2✔
498
                      netloc_prep_res.unwrapErr().c_str());
499
            return;
2✔
500
        }
501

502
        auto netloc_stmt = netloc_prep_res.unwrap();
26✔
503
        bool done = false;
26✔
504

505
        while (!done) {
52✔
506
            done = netloc_stmt.fetch_row<std::string>().match(
52✔
507
                [](const std::string& netloc) {
×
508
                    recent_refs.rr_netlocs.insert(netloc);
×
509
                    return false;
×
510
                },
511
                [](const prepared_stmt::fetch_error& fe) {
×
512
                    log_error("failed to fetch netloc row: %s",
×
513
                              fe.fe_msg.c_str());
514
                    return true;
×
515
                },
516
                [](prepared_stmt::end_of_rows) { return true; });
52✔
517
        }
518
    }
28✔
519

520
    log_info("BEGIN select bookmarks");
26✔
521
    if (sqlite3_prepare_v2(db.in(), BOOKMARK_STMT, -1, stmt.out(), nullptr)
26✔
522
        != SQLITE_OK)
26✔
523
    {
524
        log_error("could not prepare bookmark select statement -- %s",
×
525
                  sqlite3_errmsg(db));
526
        return;
×
527
    }
528

529
    for (auto file_iter = lnav_data.ld_log_source.begin();
26✔
530
         file_iter != lnav_data.ld_log_source.end();
52✔
531
         ++file_iter)
26✔
532
    {
533
        auto lf = (*file_iter)->get_file();
26✔
534
        if (lf == nullptr) {
26✔
535
            continue;
×
536
        }
537
        if (lf->size() == 0) {
26✔
538
            continue;
×
539
        }
540
        const auto* format = lf->get_format_ptr();
26✔
541
        content_line_t base_content_line;
26✔
542

543
        base_content_line = lss.get_file_base_content_line(file_iter);
26✔
544

545
        auto low_line_iter = lf->begin();
26✔
546
        auto high_line_iter = lf->end();
26✔
547

548
        --high_line_iter;
26✔
549

550
        if (bind_values(stmt.in(),
26✔
551
                        lnav_data.ld_session_load_time,
552
                        lf->original_line_time(low_line_iter),
553
                        lf->original_line_time(high_line_iter),
554
                        lf->get_format()->get_name())
52✔
555
            != SQLITE_OK)
26✔
556
        {
557
            return;
×
558
        }
559

560
        date_time_scanner dts;
26✔
561
        bool done = false;
26✔
562
        int64_t last_mark_time = -1;
26✔
563

564
        while (!done) {
92✔
565
            int rc = sqlite3_step(stmt.in());
66✔
566

567
            switch (rc) {
66✔
568
                case SQLITE_OK:
26✔
569
                case SQLITE_DONE:
570
                    done = true;
26✔
571
                    break;
26✔
572

573
                case SQLITE_ROW: {
40✔
574
                    const char* log_time
575
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
40✔
576
                    const char* log_hash
577
                        = (const char*) sqlite3_column_text(stmt.in(), 2);
40✔
578
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
40✔
579
                    const char* part_name
580
                        = (const char*) sqlite3_column_text(stmt.in(), 4);
40✔
581
                    const char* comment
582
                        = (const char*) sqlite3_column_text(stmt.in(), 6);
40✔
583
                    const char* tags
584
                        = (const char*) sqlite3_column_text(stmt.in(), 7);
40✔
585
                    const auto annotations = sqlite3_column_text(stmt.in(), 8);
40✔
586
                    const auto log_opid = sqlite3_column_text(stmt.in(), 9);
40✔
587
                    int sticky = sqlite3_column_int(stmt.in(), 10);
40✔
588
                    timeval log_tv;
589
                    exttm log_tm;
40✔
590

591
                    if (last_mark_time == -1) {
40✔
592
                        last_mark_time = mark_time;
25✔
593
                    } else if (last_mark_time != mark_time) {
15✔
594
                        done = true;
×
595
                        continue;
21✔
596
                    }
597

598
                    if (part_name == nullptr && !sticky) {
40✔
599
                        continue;
21✔
600
                    }
601

602
                    if (dts.scan(log_time,
19✔
603
                                 strlen(log_time),
604
                                 nullptr,
605
                                 &log_tm,
606
                                 log_tv)
607
                        == nullptr)
19✔
608
                    {
609
                        log_warning("bad log time: %s", log_time);
×
610
                        continue;
×
611
                    }
612

613
                    auto line_iter = format->lf_time_ordered
19✔
614
                        ? std::lower_bound(lf->begin(), lf->end(), log_tv)
19✔
615
                        : lf->begin();
1✔
616
                    while (line_iter != lf->end()) {
74✔
617
                        const auto line_tv = line_iter->get_timeval();
74✔
618

619
                        // NB: only milliseconds were stored in the DB, but the
620
                        // internal rep stores micros now.
621
                        if (line_tv.tv_sec != log_tv.tv_sec
74✔
622
                            || line_tv.tv_usec / 1000 != log_tv.tv_usec / 1000)
39✔
623
                        {
624
                            if (format->lf_time_ordered) {
53✔
625
                                break;
19✔
626
                            }
627
                            ++line_iter;
53✔
628
                            continue;
55✔
629
                        }
630

631
                        auto cl = content_line_t(
632
                            std::distance(lf->begin(), line_iter));
42✔
633
                        auto fr = lf->get_file_range(line_iter, false);
21✔
634
                        auto read_result = lf->read_range(fr);
21✔
635

636
                        if (read_result.isErr()) {
21✔
637
                            break;
×
638
                        }
639

640
                        auto sbr = read_result.unwrap();
21✔
641

642
                        auto line_hash
643
                            = hasher()
21✔
644
                                  .update(sbr.get_data(), sbr.length())
21✔
645
                                  .update(cl)
21✔
646
                                  .to_string();
21✔
647

648
                        if (line_hash != log_hash) {
21✔
649
                            // Using the formatted line for JSON-lines logs was
650
                            // a mistake in earlier versions. To carry forward
651
                            // older bookmarks, we need to replicate the bad
652
                            // behavior.
653
                            auto hack_read_res
654
                                = lf->read_line(line_iter, {false, true});
2✔
655
                            if (hack_read_res.isErr()) {
2✔
656
                                break;
×
657
                            }
658
                            auto hack_sbr = hack_read_res.unwrap();
2✔
659
                            auto hack_hash = hasher()
2✔
660
                                                 .update(hack_sbr.get_data(),
2✔
661
                                                         hack_sbr.length())
662
                                                 .update(cl)
2✔
663
                                                 .to_string();
2✔
664
                            if (hack_hash == log_hash) {
2✔
665
                                log_trace("needed hack to match line: %s:%d",
×
666
                                          lf->get_filename_as_string().c_str(),
667
                                          (int) cl);
668
                            } else {
669
                                ++line_iter;
2✔
670
                                continue;
2✔
671
                            }
672
                        }
6✔
673
                        auto& bm_meta = lf->get_bookmark_metadata();
19✔
674
                        auto line_number = static_cast<uint32_t>(
675
                            std::distance(lf->begin(), line_iter));
19✔
676
                        content_line_t line_cl
677
                            = content_line_t(base_content_line + line_number);
19✔
678
                        bool meta = false;
19✔
679

680
                        if (part_name != nullptr && part_name[0] != '\0') {
19✔
681
                            lss.set_user_mark(&textview_curses::BM_PARTITION,
7✔
682
                                              line_cl);
683
                            bm_meta[line_number].bm_name = part_name;
7✔
684
                            meta = true;
7✔
685
                        }
686
                        if (comment != nullptr && comment[0] != '\0') {
19✔
687
                            lss.set_user_mark(&textview_curses::BM_META,
7✔
688
                                              line_cl);
689
                            bm_meta[line_number].bm_comment = comment;
7✔
690
                            meta = true;
7✔
691
                        }
692
                        if (tags != nullptr && tags[0] != '\0') {
19✔
693
                            auto_mem<yajl_val_s> tag_list(yajl_tree_free);
9✔
694
                            char error_buffer[1024];
695

696
                            tag_list = yajl_tree_parse(
697
                                tags, error_buffer, sizeof(error_buffer));
9✔
698
                            if (!YAJL_IS_ARRAY(tag_list.in())) {
9✔
699
                                log_error("invalid tags column: %s", tags);
×
700
                            } else {
701
                                lss.set_user_mark(&textview_curses::BM_META,
9✔
702
                                                  line_cl);
703
                                for (size_t lpc = 0;
18✔
704
                                     lpc < tag_list.in()->u.array.len;
18✔
705
                                     lpc++)
706
                                {
707
                                    yajl_val elem
708
                                        = tag_list.in()->u.array.values[lpc];
9✔
709

710
                                    if (!YAJL_IS_STRING(elem)) {
9✔
711
                                        continue;
×
712
                                    }
713
                                    bookmark_metadata::KNOWN_TAGS.insert(
9✔
714
                                        elem->u.string);
9✔
715
                                    bm_meta[line_number].add_tag(
27✔
716
                                        elem->u.string);
9✔
717
                                }
718
                            }
719
                            meta = true;
9✔
720
                        }
9✔
721
                        if (annotations != nullptr && annotations[0] != '\0') {
19✔
722
                            static const intern_string_t SRC
723
                                = intern_string::lookup("annotations");
3✔
724

725
                            const auto anno_sf
726
                                = string_fragment::from_c_str(annotations);
1✔
727
                            auto parse_res
728
                                = logmsg_annotations_handlers.parser_for(SRC)
2✔
729
                                      .of(anno_sf);
1✔
730
                            if (parse_res.isErr()) {
1✔
731
                                log_error(
×
732
                                    "unable to parse annotations JSON -- "
733
                                    "%s",
734
                                    parse_res.unwrapErr()[0]
735
                                        .to_attr_line()
736
                                        .get_string()
737
                                        .c_str());
738
                            } else if (bm_meta.find(line_number)
1✔
739
                                       == bm_meta.end())
2✔
740
                            {
741
                                lss.set_user_mark(&textview_curses::BM_META,
1✔
742
                                                  line_cl);
743
                                bm_meta[line_number].bm_annotations
1✔
744
                                    = parse_res.unwrap();
2✔
745
                                meta = true;
1✔
746
                            } else {
747
                                meta = true;
×
748
                            }
749
                        }
1✔
750
                        if (log_opid != nullptr && log_opid[0] != '\0') {
19✔
751
                            auto opid_sf
752
                                = string_fragment::from_c_str(log_opid);
×
753
                            lf->set_logline_opid(line_number, opid_sf);
×
754
                            meta = true;
×
755
                        }
756
                        if (!meta && part_name != nullptr) {
19✔
757
                            marked_session_lines.emplace_back(
2✔
758
                                lf->original_line_time(line_iter),
2✔
759
                                format->get_name(),
2✔
760
                                line_hash);
761
                            lss.set_user_mark(&textview_curses::BM_USER,
2✔
762
                                              line_cl);
763
                        }
764
                        if (sticky) {
19✔
765
                            lss.set_user_mark(&textview_curses::BM_STICKY,
×
766
                                              line_cl);
767
                        }
768
                        reload_needed = true;
19✔
769
                        break;
19✔
770
                    }
63✔
771
                    break;
19✔
772
                }
773

774
                default: {
×
775
                    const char* errmsg;
776

777
                    errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
778
                    log_error(
×
779
                        "bookmark select error: code %d -- %s", rc, errmsg);
780
                    done = true;
×
781
                } break;
×
782
            }
783
        }
784

785
        sqlite3_reset(stmt.in());
26✔
786
    }
26✔
787
    log_info("END select bookmarks");
26✔
788

789
    log_info("BEGIN select time_offset");
26✔
790
    if (sqlite3_prepare_v2(db.in(), TIME_OFFSET_STMT, -1, stmt.out(), nullptr)
26✔
791
        != SQLITE_OK)
26✔
792
    {
793
        log_error("could not prepare time_offset select statement -- %s",
×
794
                  sqlite3_errmsg(db));
795
        return;
×
796
    }
797

798
    for (auto file_iter = lnav_data.ld_log_source.begin();
26✔
799
         file_iter != lnav_data.ld_log_source.end();
52✔
800
         ++file_iter)
26✔
801
    {
802
        auto lf = (*file_iter)->get_file();
26✔
803
        content_line_t base_content_line;
26✔
804

805
        if (lf == nullptr) {
26✔
806
            continue;
×
807
        }
808
        if (lf->size() == 0) {
26✔
809
            continue;
×
810
        }
811

812
        lss.find(lf->get_filename_as_string().c_str(), base_content_line);
26✔
813

814
        auto low_line_iter = lf->begin();
26✔
815
        auto high_line_iter = lf->end();
26✔
816

817
        --high_line_iter;
26✔
818

819
        if (bind_values(stmt.in(),
52✔
820
                        lnav_data.ld_session_load_time,
821
                        lf->get_content_id(),
26✔
822
                        lf->original_line_time(low_line_iter),
823
                        lf->original_line_time(high_line_iter),
824
                        lf->get_format()->get_name())
52✔
825
            != SQLITE_OK)
26✔
826
        {
827
            return;
×
828
        }
829

830
        date_time_scanner dts;
26✔
831
        auto done = false;
26✔
832
        int64_t last_mark_time = -1;
26✔
833

834
        while (!done) {
77✔
835
            const auto rc = sqlite3_step(stmt.in());
51✔
836

837
            switch (rc) {
51✔
838
                case SQLITE_OK:
26✔
839
                case SQLITE_DONE:
840
                    done = true;
26✔
841
                    break;
26✔
842

843
                case SQLITE_ROW: {
25✔
844
                    const auto* log_time
845
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
25✔
846
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
25✔
847
                    timeval log_tv;
848
                    exttm log_tm;
25✔
849

850
                    if (last_mark_time == -1) {
25✔
851
                        last_mark_time = mark_time;
25✔
852
                    } else if (last_mark_time != mark_time) {
×
853
                        done = true;
×
854
                        continue;
22✔
855
                    }
856

857
                    if (sqlite3_column_type(stmt.in(), 4) == SQLITE_NULL) {
25✔
858
                        continue;
22✔
859
                    }
860

861
                    if (!dts.scan(log_time,
3✔
862
                                  strlen(log_time),
863
                                  nullptr,
864
                                  &log_tm,
865
                                  log_tv))
866
                    {
867
                        continue;
×
868
                    }
869

870
                    auto line_iter
871
                        = lower_bound(lf->begin(), lf->end(), log_tv);
3✔
872
                    while (line_iter != lf->end()) {
6✔
873
                        auto line_tv = line_iter->get_timeval();
6✔
874

875
                        if ((line_tv.tv_sec != log_tv.tv_sec)
6✔
876
                            || (line_tv.tv_usec / 1000
3✔
877
                                != log_tv.tv_usec / 1000))
3✔
878
                        {
879
                            break;
880
                        }
881

882
                        int file_line = std::distance(lf->begin(), line_iter);
3✔
883
                        timeval offset;
884

885
                        offset_session_lines.emplace_back(
3✔
886
                            lf->original_line_time(line_iter),
3✔
887
                            lf->get_format_ptr()->get_name(),
3✔
888
                            lf->get_content_id());
3✔
889
                        offset.tv_sec = sqlite3_column_int64(stmt.in(), 4);
3✔
890
                        offset.tv_usec = sqlite3_column_int64(stmt.in(), 5);
3✔
891
                        lf->adjust_content_time(file_line, offset);
3✔
892

893
                        reload_needed = true;
3✔
894

895
                        ++line_iter;
3✔
896
                    }
897
                    break;
3✔
898
                }
899

900
                default: {
×
901
                    const auto* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
902
                    log_error(
×
903
                        "bookmark select error: code %d -- %s", rc, errmsg);
904
                    done = true;
×
905
                    break;
×
906
                }
907
            }
908
        }
909

910
        sqlite3_reset(stmt.in());
26✔
911
    }
26✔
912
    log_info("END select time_offset");
26✔
913

914
    if (reload_needed) {
26✔
915
        lnav_data.ld_views[LNV_LOG].reload_data();
16✔
916
    }
917

918
    load_text_bookmarks(db.in());
26✔
919
}
36✔
920

921
static int
922
read_files(yajlpp_parse_context* ypc,
24✔
923
           const unsigned char* str,
924
           size_t len,
925
           yajl_string_props_t*)
926
{
927
    return 1;
24✔
928
}
929

930
const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
931
    {level_names[LEVEL_TRACE], LEVEL_TRACE},
932
    {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5},
933
    {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4},
934
    {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3},
935
    {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2},
936
    {level_names[LEVEL_DEBUG], LEVEL_DEBUG},
937
    {level_names[LEVEL_INFO], LEVEL_INFO},
938
    {level_names[LEVEL_STATS], LEVEL_STATS},
939
    {level_names[LEVEL_NOTICE], LEVEL_NOTICE},
940
    {level_names[LEVEL_WARNING], LEVEL_WARNING},
941
    {level_names[LEVEL_ERROR], LEVEL_ERROR},
942
    {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL},
943
    {level_names[LEVEL_FATAL], LEVEL_FATAL},
944

945
    json_path_handler_base::ENUM_TERMINATOR,
946
};
947

948
static const json_path_container view_def_handlers = {
949
    json_path_handler("top_line").for_field(&view_state::vs_top),
950
    json_path_handler("focused_line").for_field(&view_state::vs_selection),
951
    json_path_handler("anchor").for_field(&view_state::vs_anchor),
952
    json_path_handler("search").for_field(&view_state::vs_search),
953
    json_path_handler("word_wrap").for_field(&view_state::vs_word_wrap),
954
    json_path_handler("filtering").for_field(&view_state::vs_filtering),
955
    json_path_handler("min_level")
956
        .with_enum_values(LEVEL_ENUM)
957
        .for_field(&view_state::vs_min_log_level),
958
    json_path_handler("commands#").for_field(&view_state::vs_commands),
959
};
960

961
static const json_path_container view_handlers = {
962
    yajlpp::pattern_property_handler("(?<view_name>[\\w\\-]+)")
963
        .with_obj_provider<view_state, session_data_t>(
964
            +[](const yajlpp_provider_context& ypc, session_data_t* root) {
1,825✔
965
                auto view_index_opt
966
                    = view_from_string(ypc.get_substr("view_name").c_str());
1,825✔
967
                if (view_index_opt) {
1,825✔
968
                    return &root->sd_view_states[view_index_opt.value()];
1,825✔
969
                }
970

971
                log_error("unknown view name: %s",
×
972
                          ypc.get_substr("view_name").c_str());
973
                static view_state dummy;
974
                return &dummy;
×
975
            })
976
        .with_children(view_def_handlers),
977
};
978

979
static const json_path_container file_state_handlers = {
980
    yajlpp::property_handler("visible")
981
        .with_description("Indicates whether the file is visible or not")
982
        .for_field(&file_state::fs_is_visible),
983
};
984

985
static const json_path_container file_states_handlers = {
986
    yajlpp::pattern_property_handler(R"((?<filename>[^/]+))")
987
        .with_description("Map of file names to file state objects")
988
        .with_obj_provider<file_state, session_data_t>(
989
            [](const auto& ypc, session_data_t* root) {
72✔
990
                auto fn = ypc.get_substr("filename");
72✔
991
                return &root->sd_file_states[fn];
144✔
992
            })
72✔
993
        .with_children(file_state_handlers),
994
};
995

996
static const typed_json_path_container<session_data_t> view_info_handlers = {
997
    yajlpp::property_handler("save-time")
998
        .for_field(&session_data_t::sd_save_time),
999
    yajlpp::property_handler("time-offset")
1000
        .for_field(&session_data_t::sd_time_offset),
1001
    json_path_handler("files#", read_files),
1002
    yajlpp::property_handler("file-states").with_children(file_states_handlers),
1003
    yajlpp::property_handler("views").with_children(view_handlers),
1004
};
1005

1006
void
1007
load_session()
28✔
1008
{
1009
    static auto op = lnav_operation{"load_session"};
28✔
1010

1011
    auto op_guard = lnav_opid_guard::internal(op);
28✔
1012
    log_info("BEGIN load_session");
28✔
1013
    scan_sessions() | [](const auto pair) {
28✔
1014
        lnav_data.ld_session_load_time = pair.first.second;
25✔
1015
        const auto& view_info_path = pair.second;
25✔
1016
        auto view_info_src = intern_string::lookup(view_info_path.string());
25✔
1017

1018
        auto open_res = lnav::filesystem::open_file(view_info_path, O_RDONLY);
25✔
1019
        if (open_res.isErr()) {
25✔
1020
            log_error("cannot open session file: %s -- %s",
×
1021
                      view_info_path.c_str(),
1022
                      open_res.unwrapErr().c_str());
1023
            return;
×
1024
        }
1025

1026
        auto fd = open_res.unwrap();
25✔
1027
        unsigned char buffer[1024];
1028
        ssize_t rc;
1029

1030
        log_info("loading session file: %s", view_info_path.c_str());
25✔
1031
        auto parser = view_info_handlers.parser_for(view_info_src);
25✔
1032
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
77✔
1033
            auto buf_frag = string_fragment::from_bytes(buffer, rc);
26✔
1034
            auto parse_res = parser.consume(buf_frag);
26✔
1035
            if (parse_res.isErr()) {
26✔
1036
                log_error("failed to load session: %s -- %s",
×
1037
                          view_info_path.c_str(),
1038
                          parse_res.unwrapErr()[0]
1039
                              .to_attr_line()
1040
                              .get_string()
1041
                              .c_str());
1042
                return;
×
1043
            }
1044
        }
1045

1046
        auto complete_res = parser.complete();
25✔
1047
        if (complete_res.isErr()) {
25✔
1048
            log_error("failed to load session: %s -- %s",
×
1049
                      view_info_path.c_str(),
1050
                      complete_res.unwrapErr()[0]
1051
                          .to_attr_line()
1052
                          .get_string()
1053
                          .c_str());
1054
            return;
×
1055
        }
1056
        session_data = complete_res.unwrap();
25✔
1057

1058
        bool log_changes = false;
25✔
1059

1060
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
51✔
1061
            auto iter = session_data.sd_file_states.find(lf->get_filename());
26✔
1062

1063
            if (iter == session_data.sd_file_states.end()) {
26✔
1064
                continue;
2✔
1065
            }
1066

1067
            log_debug("found state for file: %s %d (%s)",
24✔
1068
                      lf->get_content_id().c_str(),
1069
                      iter->second.fs_is_visible,
1070
                      lf->get_filename_as_string().c_str());
1071
            lnav_data.ld_log_source.find_data(lf) |
24✔
1072
                [iter, &log_changes](auto ld) {
46✔
1073
                    if (ld->ld_visible != iter->second.fs_is_visible) {
23✔
1074
                        ld->get_file_ptr()->set_indexing(
1✔
1075
                            iter->second.fs_is_visible);
1✔
1076
                        ld->set_visibility(iter->second.fs_is_visible);
1✔
1077
                        log_changes = true;
1✔
1078
                    }
1079
                };
1080
        }
1081

1082
        if (log_changes) {
25✔
1083
            lnav_data.ld_log_source.text_filters_changed();
1✔
1084
        }
1085
    };
25✔
1086

1087
    lnav::events::publish(lnav_data.ld_db.in(),
28✔
1088
                          lnav::events::session::loaded{});
1089

1090
    log_info("END load_session");
28✔
1091
}
28✔
1092

1093
static void
1094
yajl_writer(void* context, const char* str, size_t len)
9,014✔
1095
{
1096
    FILE* file = (FILE*) context;
9,014✔
1097

1098
    fwrite(str, len, 1, file);
9,014✔
1099
}
9,014✔
1100

1101
static void
1102
save_user_bookmarks(sqlite3* db,
25✔
1103
                    sqlite3_stmt* stmt,
1104
                    bookmark_vector<content_line_t>& user_marks,
1105
                    bookmark_vector<content_line_t>& sticky_marks)
1106
{
1107
    auto& lss = lnav_data.ld_log_source;
25✔
1108

1109
    // Collect all lines that are either user-marked or sticky
1110
    tlx::btree_set<content_line_t> all_lines;
25✔
1111
    for (const auto& cl : user_marks.bv_tree) {
26✔
1112
        all_lines.insert(cl);
1✔
1113
    }
1114
    for (const auto& cl : sticky_marks.bv_tree) {
25✔
1115
        all_lines.insert(cl);
×
1116
    }
1117

1118
    for (const auto& cl_const : all_lines) {
26✔
1119
        auto cl = cl_const;
1✔
1120
        auto lf = lss.find(cl);
1✔
1121
        if (lf == nullptr) {
1✔
1122
            continue;
×
1123
        }
1124

1125
        sqlite3_clear_bindings(stmt);
1✔
1126

1127
        const auto line_iter = lf->begin() + cl;
1✔
1128
        auto fr = lf->get_file_range(line_iter, false);
1✔
1129
        auto read_result = lf->read_range(fr);
1✔
1130

1131
        if (read_result.isErr()) {
1✔
1132
            continue;
×
1133
        }
1134

1135
        auto line_hash = read_result
1136
                             .map([cl](auto sbr) {
2✔
1137
                                 return hasher()
2✔
1138
                                     .update(sbr.get_data(), sbr.length())
1✔
1139
                                     .update(cl)
1✔
1140
                                     .to_string();
2✔
1141
                             })
1142
                             .unwrap();
1✔
1143

1144
        if (bind_values(stmt,
3✔
1145
                        lf->original_line_time(line_iter),
1146
                        lf->get_format()->get_name(),
2✔
1147
                        line_hash,
1148
                        lnav_data.ld_session_time)
1149
            != SQLITE_OK)
1✔
1150
        {
1151
            continue;
×
1152
        }
1153

1154
        auto is_user = user_marks.bv_tree.find(cl) != user_marks.bv_tree.end();
1✔
1155
        auto is_sticky
1156
            = sticky_marks.bv_tree.find(cl) != sticky_marks.bv_tree.end();
1✔
1157

1158
        // Use part_name "" for user marks, null for sticky-only
1159
        if (is_user) {
1✔
1160
            if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT)
1✔
1161
                != SQLITE_OK)
1✔
1162
            {
1163
                log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1164
                return;
×
1165
            }
1166
        } else {
1167
            sqlite3_bind_null(stmt, 5);
×
1168
        }
1169

1170
        if (sqlite3_bind_int(stmt, 10, is_sticky ? 1 : 0) != SQLITE_OK) {
1✔
1171
            log_error("could not bind sticky -- %s", sqlite3_errmsg(db));
×
1172
            return;
×
1173
        }
1174

1175
        if (sqlite3_step(stmt) != SQLITE_DONE) {
1✔
1176
            log_error("could not execute bookmark insert statement -- %s",
×
1177
                      sqlite3_errmsg(db));
1178
            return;
×
1179
        }
1180

1181
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
1✔
1182
                                          lf->get_format_ptr()->get_name(),
1✔
1183
                                          line_hash);
1184

1185
        sqlite3_reset(stmt);
1✔
1186
    }
1✔
1187
}
25✔
1188

1189
static void
1190
save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
25✔
1191
{
1192
    for (const auto& bm_pair : lf->get_bookmark_metadata()) {
32✔
1193
        auto cl = content_line_t(bm_pair.first);
7✔
1194
        sqlite3_clear_bindings(stmt);
7✔
1195

1196
        auto line_iter = lf->begin() + cl;
7✔
1197
        auto fr = lf->get_file_range(line_iter, false);
7✔
1198
        auto read_result = lf->read_range(fr);
7✔
1199

1200
        if (read_result.isErr()) {
7✔
1201
            continue;
×
1202
        }
1203

1204
        auto line_hash = read_result
1205
                             .map([cl](auto sbr) {
14✔
1206
                                 return hasher()
14✔
1207
                                     .update(sbr.get_data(), sbr.length())
7✔
1208
                                     .update(cl)
7✔
1209
                                     .to_string();
14✔
1210
                             })
1211
                             .unwrap();
7✔
1212

1213
        if (bind_values(stmt,
21✔
1214
                        lf->original_line_time(line_iter),
1215
                        lf->get_format()->get_name(),
14✔
1216
                        line_hash,
1217
                        lnav_data.ld_session_time)
1218
            != SQLITE_OK)
7✔
1219
        {
1220
            continue;
×
1221
        }
1222

1223
        const auto& line_meta = bm_pair.second;
7✔
1224
        if (line_meta.empty(bookmark_metadata::categories::any)) {
7✔
1225
            continue;
×
1226
        }
1227

1228
        if (sqlite3_bind_text(stmt,
7✔
1229
                              5,
1230
                              line_meta.bm_name.c_str(),
1231
                              line_meta.bm_name.length(),
7✔
1232
                              SQLITE_TRANSIENT)
1233
            != SQLITE_OK)
7✔
1234
        {
1235
            log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1236
            return;
×
1237
        }
1238

1239
        if (sqlite3_bind_text(stmt,
7✔
1240
                              6,
1241
                              line_meta.bm_comment.c_str(),
1242
                              line_meta.bm_comment.length(),
7✔
1243
                              SQLITE_TRANSIENT)
1244
            != SQLITE_OK)
7✔
1245
        {
1246
            log_error("could not bind comment -- %s", sqlite3_errmsg(db));
×
1247
            return;
×
1248
        }
1249

1250
        std::string tags;
7✔
1251

1252
        if (!line_meta.bm_tags.empty()) {
7✔
1253
            yajlpp_gen gen;
4✔
1254

1255
            yajl_gen_config(gen, yajl_gen_beautify, false);
4✔
1256

1257
            {
1258
                yajlpp_array arr(gen);
4✔
1259

1260
                for (const auto& str : line_meta.bm_tags) {
8✔
1261
                    arr.gen(str);
4✔
1262
                }
1263
            }
4✔
1264

1265
            tags = gen.to_string_fragment().to_string();
4✔
1266
        }
4✔
1267

1268
        if (sqlite3_bind_text(
7✔
1269
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
7✔
1270
            != SQLITE_OK)
7✔
1271
        {
1272
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
1273
            return;
×
1274
        }
1275

1276
        if (!line_meta.bm_annotations.la_pairs.empty()) {
7✔
1277
            auto anno_str = logmsg_annotations_handlers.to_string(
1278
                line_meta.bm_annotations);
1✔
1279

1280
            if (sqlite3_bind_text(stmt,
1✔
1281
                                  8,
1282
                                  anno_str.c_str(),
1283
                                  anno_str.length(),
1✔
1284
                                  SQLITE_TRANSIENT)
1285
                != SQLITE_OK)
1✔
1286
            {
1287
                log_error("could not bind annotations -- %s",
×
1288
                          sqlite3_errmsg(db));
1289
                return;
×
1290
            }
1291
        } else {
1✔
1292
            sqlite3_bind_null(stmt, 8);
6✔
1293
        }
1294

1295
        if (line_meta.bm_opid.empty()) {
7✔
1296
            sqlite3_bind_null(stmt, 9);
7✔
1297
        } else {
1298
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1299
        }
1300

1301
        sqlite3_bind_int(stmt, 10, 0);
7✔
1302

1303
        if (sqlite3_step(stmt) != SQLITE_DONE) {
7✔
1304
            log_error("could not execute bookmark insert statement -- %s",
×
1305
                      sqlite3_errmsg(db));
1306
            return;
×
1307
        }
1308

1309
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
7✔
1310
                                          lf->get_format_ptr()->get_name(),
7✔
1311
                                          line_hash);
1312

1313
        sqlite3_reset(stmt);
7✔
1314
    }
7✔
1315
}
1316

1317
static void
1318
save_text_bookmarks(sqlite3* db)
25✔
1319
{
1320
    auto& tss = lnav_data.ld_text_source;
25✔
1321

1322
    if (tss.empty()) {
25✔
1323
        return;
24✔
1324
    }
1325

1326
    tss.copy_bookmarks_to_current_file();
1✔
1327

1328
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1329

1330
    if (sqlite3_prepare_v2(db,
1✔
1331
                           "DELETE FROM text_bookmarks WHERE session_time = ?",
1332
                           -1,
1333
                           stmt.out(),
1334
                           nullptr)
1335
        != SQLITE_OK)
1✔
1336
    {
1337
        log_error("could not prepare text_bookmarks delete -- %s",
×
1338
                  sqlite3_errmsg(db));
1339
        return;
×
1340
    }
1341
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_time);
1✔
1342
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1343
        log_error("could not execute text_bookmarks delete -- %s",
×
1344
                  sqlite3_errmsg(db));
1345
        return;
×
1346
    }
1347

1348
    if (sqlite3_prepare_v2(
1✔
1349
            db,
1350
            "REPLACE INTO text_bookmarks"
1351
            " (file_path, line_number, line_hash, mark_type, session_time)"
1352
            " VALUES (?, ?, ?, ?, ?)",
1353
            -1,
1354
            stmt.out(),
1355
            nullptr)
1356
        != SQLITE_OK)
1✔
1357
    {
1358
        log_error("could not prepare text_bookmarks replace statement -- %s",
×
1359
                  sqlite3_errmsg(db));
1360
        return;
×
1361
    }
1362

1363
    for (const auto& fvs : tss.get_file_states()) {
2✔
1364
        auto& lf = fvs->fvs_file;
1✔
1365
        if (lf == nullptr || lf->size() == 0) {
1✔
1366
            continue;
×
1367
        }
1368

1369
        auto file_path = lf->get_path_for_key().string();
1✔
1370
        auto line_count = static_cast<int>(lf->size());
1✔
1371

1372
        static const bookmark_type_t* SAVE_TYPES[] = {
1373
            &textview_curses::BM_USER,
1374
            &textview_curses::BM_STICKY,
1375
        };
1376

1377
        for (const auto* bm_type : SAVE_TYPES) {
3✔
1378
            auto& bv = fvs->fvs_bookmarks[bm_type];
2✔
1379
            if (bv.empty()) {
2✔
1380
                continue;
1✔
1381
            }
1382

1383
            for (const auto& vl : bv.bv_tree) {
2✔
1384
                if (static_cast<int>(vl) >= line_count) {
1✔
1385
                    continue;
×
1386
                }
1387

1388
                auto line_iter = lf->begin() + static_cast<int>(vl);
1✔
1389
                auto fr = lf->get_file_range(line_iter, false);
1✔
1390
                auto read_result = lf->read_range(fr);
1✔
1391

1392
                if (read_result.isErr()) {
1✔
1393
                    continue;
×
1394
                }
1395

1396
                auto line_hash
1397
                    = read_result
1398
                          .map([](auto sbr) {
2✔
1399
                              return hasher()
2✔
1400
                                  .update(sbr.get_data(), sbr.length())
1✔
1401
                                  .to_string();
2✔
1402
                          })
1403
                          .unwrap();
1✔
1404

1405
                sqlite3_clear_bindings(stmt.in());
1✔
1406
                bind_to_sqlite(stmt.in(), 1, file_path);
1✔
1407
                sqlite3_bind_int(stmt.in(), 2, static_cast<int>(vl));
1✔
1408
                bind_to_sqlite(stmt.in(), 3, line_hash);
1✔
1409
                bind_to_sqlite(stmt.in(), 4, bm_type->get_name());
1✔
1410
                sqlite3_bind_int64(stmt.in(), 5, lnav_data.ld_session_time);
1✔
1411

1412
                if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1413
                    log_error("could not execute text_bookmarks insert -- %s",
×
1414
                              sqlite3_errmsg(db));
1415
                    return;
×
1416
                }
1417

1418
                sqlite3_reset(stmt.in());
1✔
1419
            }
1✔
1420
        }
1421
    }
1✔
1422
}
1✔
1423

1424
static void
1425
load_text_bookmarks(sqlite3* db)
26✔
1426
{
1427
    static const char* const TEXT_BOOKMARK_STMT = R"(
1428
        SELECT line_number, line_hash, mark_type
1429
        FROM text_bookmarks
1430
        WHERE file_path = ?
1431
        ORDER BY line_number
1432
    )";
1433

1434
    auto& tss = lnav_data.ld_text_source;
26✔
1435
    auto& tc = lnav_data.ld_views[LNV_TEXT];
26✔
1436

1437
    if (tss.empty()) {
26✔
1438
        return;
25✔
1439
    }
1440

1441
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1442
    if (sqlite3_prepare_v2(db, TEXT_BOOKMARK_STMT, -1, stmt.out(), nullptr)
1✔
1443
        != SQLITE_OK)
1✔
1444
    {
1445
        log_error("could not prepare text_bookmarks select -- %s",
×
1446
                  sqlite3_errmsg(db));
1447
        return;
×
1448
    }
1449

1450
    for (const auto& fvs : tss.get_file_states()) {
2✔
1451
        auto& lf = fvs->fvs_file;
1✔
1452
        if (lf == nullptr || lf->size() == 0) {
1✔
1453
            continue;
×
1454
        }
1455

1456
        auto file_path = lf->get_path_for_key().string();
1✔
1457
        auto line_count = static_cast<int>(lf->size());
1✔
1458
        sqlite3_reset(stmt.in());
1✔
1459
        sqlite3_clear_bindings(stmt.in());
1✔
1460
        bind_to_sqlite(stmt.in(), 1, file_path);
1✔
1461

1462
        bool done = false;
1✔
1463
        while (!done) {
3✔
1464
            auto rc = sqlite3_step(stmt.in());
2✔
1465

1466
            switch (rc) {
2✔
1467
                case SQLITE_OK:
1✔
1468
                case SQLITE_DONE:
1469
                    done = true;
1✔
1470
                    break;
1✔
1471

1472
                case SQLITE_ROW: {
1✔
1473
                    auto line_number = sqlite3_column_int(stmt.in(), 0);
1✔
1474
                    auto* stored_hash
1475
                        = (const char*) sqlite3_column_text(stmt.in(), 1);
1✔
1476
                    auto* mark_type
1477
                        = (const char*) sqlite3_column_text(stmt.in(), 2);
1✔
1478

1479
                    if (line_number >= line_count) {
1✔
1480
                        continue;
×
1481
                    }
1482

1483
                    auto bm_type_opt = bookmark_type_t::find_type(mark_type);
1✔
1484
                    if (!bm_type_opt) {
1✔
1485
                        continue;
×
1486
                    }
1487

1488
                    auto line_iter = lf->begin() + line_number;
1✔
1489
                    auto fr = lf->get_file_range(line_iter, false);
1✔
1490
                    auto read_result = lf->read_range(fr);
1✔
1491

1492
                    if (read_result.isErr()) {
1✔
1493
                        continue;
×
1494
                    }
1495

1496
                    auto line_hash
1497
                        = read_result
1498
                              .map([](auto sbr) {
2✔
1499
                                  return hasher()
2✔
1500
                                      .update(sbr.get_data(), sbr.length())
1✔
1501
                                      .to_string();
2✔
1502
                              })
1503
                              .unwrap();
1✔
1504

1505
                    if (line_hash != stored_hash) {
1✔
1506
                        log_warning("text bookmark hash mismatch at %s:%d",
×
1507
                                    file_path.c_str(),
1508
                                    line_number);
1509
                        continue;
×
1510
                    }
1511

1512
                    auto vl = vis_line_t(line_number);
1✔
1513
                    fvs->fvs_bookmarks[bm_type_opt.value()].insert_once(vl);
1✔
1514
                    break;
1✔
1515
                }
2✔
1516

1517
                default:
×
1518
                    log_error("text bookmark select error: %d -- %s",
×
1519
                              rc,
1520
                              sqlite3_errmsg(db));
1521
                    done = true;
×
1522
                    break;
×
1523
            }
1524
        }
1525
    }
1✔
1526

1527
    // Copy the front file's bookmarks into the textview
1528
    if (!tss.get_file_states().empty()) {
1✔
1529
        auto& front = tss.get_file_states().front();
1✔
1530
        tc.get_bookmarks()[&textview_curses::BM_USER]
1✔
1531
            = front->fvs_bookmarks[&textview_curses::BM_USER];
2✔
1532
        tc.get_bookmarks()[&textview_curses::BM_STICKY]
1✔
1533
            = front->fvs_bookmarks[&textview_curses::BM_STICKY];
2✔
1534
    }
1535

1536
    tc.reload_data();
1✔
1537
}
1✔
1538

1539
static void
1540
save_time_bookmarks()
25✔
1541
{
1542
    auto_sqlite3 db;
25✔
1543
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
25✔
1544
    auto_mem<char, sqlite3_free> errmsg;
25✔
1545
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
25✔
1546

1547
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
25✔
1548
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1549
        return;
×
1550
    }
1551

1552
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
25✔
1553
        != SQLITE_OK)
25✔
1554
    {
1555
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1556
        return;
×
1557
    }
1558

1559
    if (sqlite3_exec(
25✔
1560
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1561
        != SQLITE_OK)
25✔
1562
    {
1563
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1564
        return;
×
1565
    }
1566

1567
    {
1568
        static const char* UPDATE_NETLOCS_STMT
1569
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1570

1571
        std::set<std::string> netlocs;
25✔
1572

1573
        isc::to<tailer::looper&, services::remote_tailer_t>().send_and_wait(
25✔
1574
            [&netlocs](auto& tlooper) { netlocs = tlooper.active_netlocs(); });
50✔
1575

1576
        if (sqlite3_prepare_v2(
25✔
1577
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1578
            != SQLITE_OK)
25✔
1579
        {
1580
            log_error("could not prepare recent_netlocs statement -- %s",
×
1581
                      sqlite3_errmsg(db));
1582
            return;
×
1583
        }
1584

1585
        for (const auto& netloc : netlocs) {
25✔
1586
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1587

1588
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1589
                log_error("could not execute bookmark insert statement -- %s",
×
1590
                          sqlite3_errmsg(db));
1591
                return;
×
1592
            }
1593

1594
            sqlite3_reset(stmt.in());
×
1595
        }
1596
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
25✔
1597
    }
25✔
1598

1599
    auto& lss = lnav_data.ld_log_source;
25✔
1600
    auto& bm = lss.get_user_bookmarks();
25✔
1601

1602
    if (sqlite3_prepare_v2(db.in(),
25✔
1603
                           "DELETE FROM bookmarks WHERE "
1604
                           " log_time = ? and log_format = ? and log_hash = ? "
1605
                           " and session_time = ?",
1606
                           -1,
1607
                           stmt.out(),
1608
                           nullptr)
1609
        != SQLITE_OK)
25✔
1610
    {
1611
        log_error("could not prepare bookmark delete statement -- %s",
×
1612
                  sqlite3_errmsg(db));
1613
        return;
×
1614
    }
1615

1616
    for (auto& marked_session_line : marked_session_lines) {
27✔
1617
        sqlite3_clear_bindings(stmt.in());
2✔
1618

1619
        if (bind_values(stmt,
4✔
1620
                        marked_session_line.sl_time,
1621
                        marked_session_line.sl_format_name,
1622
                        marked_session_line.sl_line_hash,
2✔
1623
                        lnav_data.ld_session_time)
1624
            != SQLITE_OK)
2✔
1625
        {
1626
            continue;
×
1627
        }
1628

1629
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1630
            log_error("could not execute bookmark insert statement -- %s",
×
1631
                      sqlite3_errmsg(db));
1632
            return;
×
1633
        }
1634

1635
        sqlite3_reset(stmt.in());
2✔
1636
    }
1637

1638
    marked_session_lines.clear();
25✔
1639

1640
    if (sqlite3_prepare_v2(db.in(),
25✔
1641
                           "REPLACE INTO bookmarks"
1642
                           " (log_time, log_format, log_hash, session_time, "
1643
                           "part_name, comment, tags, annotations, log_opid,"
1644
                           " sticky)"
1645
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1646
                           -1,
1647
                           stmt.out(),
1648
                           nullptr)
1649
        != SQLITE_OK)
25✔
1650
    {
1651
        log_error("could not prepare bookmark replace statement -- %s",
×
1652
                  sqlite3_errmsg(db));
1653
        return;
×
1654
    }
1655

1656
    {
1657
        for (auto file_iter = lnav_data.ld_log_source.begin();
25✔
1658
             file_iter != lnav_data.ld_log_source.end();
50✔
1659
             ++file_iter)
25✔
1660
        {
1661
            auto lf = (*file_iter)->get_file();
25✔
1662

1663
            if (lf == nullptr) {
25✔
1664
                continue;
×
1665
            }
1666
            if (lf->size() == 0) {
25✔
1667
                continue;
×
1668
            }
1669

1670
            content_line_t base_content_line;
25✔
1671
            base_content_line = lss.get_file_base_content_line(file_iter);
25✔
1672
            base_content_line
1673
                = content_line_t(base_content_line + lf->size() - 1);
25✔
1674

1675
            if (!bind_line(db.in(),
25✔
1676
                           stmt.in(),
1677
                           base_content_line,
1678
                           lnav_data.ld_session_time))
25✔
1679
            {
1680
                continue;
×
1681
            }
1682

1683
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
25✔
1684
                log_error("could not bind log hash -- %s",
×
1685
                          sqlite3_errmsg(db.in()));
1686
                return;
×
1687
            }
1688

1689
            sqlite3_bind_int(stmt.in(), 10, 0);
25✔
1690

1691
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
25✔
1692
                log_error("could not execute bookmark insert statement -- %s",
×
1693
                          sqlite3_errmsg(db));
1694
                return;
×
1695
            }
1696

1697
            sqlite3_reset(stmt.in());
25✔
1698
        }
25✔
1699
    }
1700

1701
    save_user_bookmarks(db.in(),
25✔
1702
                        stmt.in(),
1703
                        bm[&textview_curses::BM_USER],
1704
                        bm[&textview_curses::BM_STICKY]);
1705
    for (const auto& ldd : lss) {
50✔
1706
        auto* lf = ldd->get_file_ptr();
25✔
1707
        if (lf == nullptr) {
25✔
1708
            continue;
×
1709
        }
1710

1711
        save_meta_bookmarks(db.in(), stmt.in(), lf);
25✔
1712
    }
1713

1714
    if (sqlite3_prepare_v2(db.in(),
25✔
1715
                           "DELETE FROM time_offset WHERE "
1716
                           " log_time = ? and log_format = ? and log_hash = ? "
1717
                           " and session_time = ?",
1718
                           -1,
1719
                           stmt.out(),
1720
                           NULL)
1721
        != SQLITE_OK)
25✔
1722
    {
1723
        log_error("could not prepare time_offset delete statement -- %s",
×
1724
                  sqlite3_errmsg(db));
1725
        return;
×
1726
    }
1727

1728
    for (auto& offset_session_line : offset_session_lines) {
26✔
1729
        sqlite3_clear_bindings(stmt.in());
1✔
1730

1731
        if (bind_values(stmt,
2✔
1732
                        offset_session_line.sl_time,
1733
                        offset_session_line.sl_format_name,
1734
                        offset_session_line.sl_line_hash,
1✔
1735
                        lnav_data.ld_session_time)
1736
            != SQLITE_OK)
1✔
1737
        {
1738
            continue;
×
1739
        }
1740

1741
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1742
            log_error("could not execute bookmark insert statement -- %s",
×
1743
                      sqlite3_errmsg(db));
1744
            return;
×
1745
        }
1746

1747
        sqlite3_reset(stmt.in());
1✔
1748
    }
1749

1750
    offset_session_lines.clear();
25✔
1751

1752
    if (sqlite3_prepare_v2(db.in(),
25✔
1753
                           "REPLACE INTO time_offset"
1754
                           " (log_time, log_format, log_hash, session_time, "
1755
                           "offset_sec, offset_usec)"
1756
                           " VALUES (?, ?, ?, ?, ?, ?)",
1757
                           -1,
1758
                           stmt.out(),
1759
                           nullptr)
1760
        != SQLITE_OK)
25✔
1761
    {
1762
        log_error("could not prepare time_offset replace statement -- %s",
×
1763
                  sqlite3_errmsg(db));
1764
        return;
×
1765
    }
1766

1767
    {
1768
        for (auto file_iter = lnav_data.ld_log_source.begin();
25✔
1769
             file_iter != lnav_data.ld_log_source.end();
50✔
1770
             ++file_iter)
25✔
1771
        {
1772
            auto lf = (*file_iter)->get_file();
25✔
1773
            if (lf == nullptr) {
25✔
1774
                continue;
×
1775
            }
1776
            if (lf->size() == 0) {
25✔
1777
                continue;
×
1778
            }
1779

1780
            if (bind_values(stmt,
75✔
1781
                            lf->original_line_time(lf->begin()),
1782
                            lf->get_format()->get_name(),
50✔
1783
                            lf->get_content_id(),
25✔
1784
                            lnav_data.ld_session_time)
1785
                != SQLITE_OK)
25✔
1786
            {
1787
                continue;
×
1788
            }
1789

1790
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
25✔
1791
                log_error("could not bind log hash -- %s",
×
1792
                          sqlite3_errmsg(db.in()));
1793
                return;
×
1794
            }
1795

1796
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
25✔
1797
                log_error("could not bind log hash -- %s",
×
1798
                          sqlite3_errmsg(db.in()));
1799
                return;
×
1800
            }
1801

1802
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
25✔
1803
                log_error("could not execute bookmark insert statement -- %s",
×
1804
                          sqlite3_errmsg(db));
1805
                return;
×
1806
            }
1807

1808
            sqlite3_reset(stmt.in());
25✔
1809
        }
25✔
1810
    }
1811

1812
    for (const auto& ls : lss) {
50✔
1813
        if (ls->get_file() == nullptr) {
25✔
1814
            continue;
22✔
1815
        }
1816

1817
        const auto lf = ls->get_file();
25✔
1818
        if (!lf->is_time_adjusted()) {
25✔
1819
            continue;
22✔
1820
        }
1821

1822
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
1823
        auto offset = lf->get_time_offset();
3✔
1824

1825
        bind_values(stmt.in(),
6✔
1826
                    lf->original_line_time(line_iter),
1827
                    lf->get_format()->get_name(),
6✔
1828
                    lf->get_content_id(),
3✔
1829
                    lnav_data.ld_session_time,
1830
                    offset.tv_sec,
1831
                    offset.tv_usec);
1832

1833
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
3✔
1834
            log_error("could not execute bookmark insert statement -- %s",
×
1835
                      sqlite3_errmsg(db));
1836
            return;
×
1837
        }
1838

1839
        sqlite3_reset(stmt.in());
3✔
1840
    }
25✔
1841

1842
    save_text_bookmarks(db.in());
25✔
1843

1844
    log_info("saved %d bookmarks", sqlite3_changes(db.in()));
25✔
1845

1846
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
25✔
1847
        != SQLITE_OK)
25✔
1848
    {
1849
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1850
        return;
×
1851
    }
1852

1853
    if (sqlite3_exec(db.in(), BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
25✔
1854
        != SQLITE_OK)
25✔
1855
    {
1856
        log_error("unable to delete old bookmarks -- %s", errmsg.in());
×
1857
        return;
×
1858
    }
1859
    auto bookmark_changes = sqlite3_changes(db.in());
25✔
1860
    if (bookmark_changes > 0) {
25✔
1861
        log_info("deleted %d old bookmarks", bookmark_changes);
×
1862
    }
1863

1864
    if (sqlite3_exec(db.in(), NETLOC_LRU_STMT, nullptr, nullptr, errmsg.out())
25✔
1865
        != SQLITE_OK)
25✔
1866
    {
1867
        log_error("unable to delete old netlocs -- %s", errmsg.in());
×
1868
        return;
×
1869
    }
1870
    auto netloc_changes = sqlite3_changes(db.in());
25✔
1871
    if (netloc_changes > 0) {
25✔
1872
        log_info("deleted %d old netlocs", netloc_changes);
×
1873
    }
1874

1875
    if (sqlite3_exec(
25✔
1876
            db.in(), TEXT_BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
1877
        != SQLITE_OK)
25✔
1878
    {
1879
        log_error("unable to delete old text bookmarks -- %s", errmsg.in());
×
1880
        return;
×
1881
    }
1882
    auto text_bm_changes = sqlite3_changes(db.in());
25✔
1883
    if (text_bm_changes > 0) {
25✔
1884
        log_info("deleted %d old text bookmarks", text_bm_changes);
×
1885
    }
1886
}
25✔
1887

1888
static void
1889
save_session_with_id(const std::string& session_id)
24✔
1890
{
1891
    auto_mem<FILE> file(fclose);
24✔
1892
    yajl_gen handle = nullptr;
24✔
1893

1894
    /* TODO: save the last search query */
1895

1896
    log_info("saving session with id: %s", session_id.c_str());
24✔
1897

1898
    auto view_base_name
1899
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
48✔
1900
                      session_id,
1901
                      lnav_data.ld_session_time,
1902
                      getppid());
24✔
1903
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
24✔
1904
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
24✔
1905

1906
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
24✔
1907
        perror("Unable to open session file");
×
1908
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
24✔
1909
        perror("Unable to create yajl_gen object");
×
1910
    } else {
1911
        yajl_gen_config(
24✔
1912
            handle, yajl_gen_print_callback, yajl_writer, file.in());
1913

1914
        {
1915
            yajlpp_map root_map(handle);
24✔
1916

1917
            root_map.gen("save-time");
24✔
1918
            root_map.gen((long long) time(nullptr));
24✔
1919

1920
            root_map.gen("time-offset");
24✔
1921
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
24✔
1922

1923
            root_map.gen("files");
24✔
1924

1925
            {
1926
                yajlpp_array file_list(handle);
24✔
1927

1928
                for (auto& ld_file_name :
24✔
1929
                     lnav_data.ld_active_files.fc_file_names)
74✔
1930
                {
1931
                    file_list.gen(ld_file_name.first);
26✔
1932
                }
1933
            }
24✔
1934

1935
            root_map.gen("file-states");
24✔
1936

1937
            {
1938
                yajlpp_map file_states(handle);
24✔
1939

1940
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
50✔
1941
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
26✔
1942

1943
                    file_states.gen(lf->get_filename().native());
26✔
1944

1945
                    {
1946
                        yajlpp_map file_state(handle);
26✔
1947

1948
                        file_state.gen("visible");
26✔
1949
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
26✔
1950
                    }
26✔
1951
                }
1952
            }
24✔
1953

1954
            root_map.gen("views");
24✔
1955

1956
            {
1957
                yajlpp_map top_view_map(handle);
24✔
1958

1959
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
240✔
1960
                    auto& tc = lnav_data.ld_views[lpc];
216✔
1961
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
216✔
1962

1963
                    top_view_map.gen(lnav_view_strings[lpc]);
216✔
1964

1965
                    yajlpp_map view_map(handle);
216✔
1966

1967
                    view_map.gen("top_line");
216✔
1968

1969
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
216✔
1970
                        view_map.gen(-1LL);
203✔
1971
                    } else {
1972
                        view_map.gen((long long) tc.get_top());
13✔
1973
                    }
1974

1975
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
269✔
1976
                        && tc.get_inner_height() > 0_vl
2✔
1977
                        && tc.get_selection() != tc.get_inner_height() - 1)
269✔
1978
                    {
1979
                        auto sel = tc.get_selection();
1✔
1980
                        if (sel) {
1✔
1981
                            view_map.gen("focused_line");
1✔
1982
                            view_map.gen((long long) sel.value());
1✔
1983

1984
                            if (ta != nullptr) {
1✔
1985
                                auto anchor_opt
1986
                                    = ta->anchor_for_row(sel.value());
1✔
1987
                                if (anchor_opt) {
1✔
1988
                                    view_map.gen("anchor");
1✔
1989
                                    view_map.gen(anchor_opt.value());
1✔
1990
                                }
1991
                            }
1✔
1992
                        }
1993
                    }
1994

1995
                    view_map.gen("search");
216✔
1996
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
216✔
1997

1998
                    view_map.gen("word_wrap");
216✔
1999
                    view_map.gen(tc.get_word_wrap());
216✔
2000

2001
                    auto* tss = tc.get_sub_source();
216✔
2002
                    if (tss == nullptr) {
216✔
2003
                        continue;
48✔
2004
                    }
2005

2006
                    view_map.gen("filtering");
168✔
2007
                    view_map.gen(tss->tss_apply_filters);
168✔
2008

2009
                    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
168✔
2010
                        view_map.gen("min_level");
×
2011
                        view_map.gen(level_names[tss->get_min_log_level()]);
×
2012
                    }
2013

2014
                    view_map.gen("commands");
168✔
2015
                    yajlpp_array cmd_array(handle);
168✔
2016

2017
                    tss->add_commands_for_session(
168✔
2018
                        [&](auto& cmd) { cmd_array.gen(cmd); });
180✔
2019
                }
216✔
2020
            }
24✔
2021
        }
24✔
2022

2023
        yajl_gen_clear(handle);
24✔
2024
        yajl_gen_free(handle);
24✔
2025

2026
        fclose(file.release());
24✔
2027

2028
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
24✔
2029

2030
        log_info("Saved session: %s", view_file_name.c_str());
24✔
2031
    }
2032
}
24✔
2033

2034
void
2035
save_session()
25✔
2036
{
2037
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
25✔
2038
        log_info("secure mode is enabled, not saving session");
×
2039
        return;
×
2040
    }
2041

2042
    static auto op = lnav_operation{"save_session"};
25✔
2043

2044
    auto op_guard = lnav_opid_guard::internal(op);
25✔
2045

2046
    log_debug("BEGIN save_session");
25✔
2047
    save_time_bookmarks();
25✔
2048

2049
    const auto opt_session_id = compute_session_id();
25✔
2050
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
48✔
2051
    for (const auto& pair : lnav_data.ld_session_id) {
29✔
2052
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
2053
            continue;
3✔
2054
        }
2055
        save_session_with_id(pair.first);
1✔
2056
    }
2057
    log_debug("END save_session");
25✔
2058
}
25✔
2059

2060
void
2061
reset_session()
6✔
2062
{
2063
    log_info("reset session: time=%lld", lnav_data.ld_session_time);
6✔
2064

2065
    save_session();
6✔
2066

2067
    lnav_data.ld_session_time = time(nullptr);
6✔
2068
    session_data.sd_file_states.clear();
6✔
2069

2070
    for (auto& tc : lnav_data.ld_views) {
60✔
2071
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
2072
        auto& hmap = tc.get_highlights();
54✔
2073
        auto hl_iter = hmap.begin();
54✔
2074

2075
        while (hl_iter != hmap.end()) {
552✔
2076
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
2077
                ++hl_iter;
498✔
2078
            } else {
2079
                hmap.erase(hl_iter++);
×
2080
            }
2081
        }
2082

2083
        if (ttt != nullptr) {
54✔
2084
            ttt->clear_min_max_row_times();
36✔
2085
        }
2086
    }
2087

2088
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
2089
        lf->reset_state();
8✔
2090
    }
2091

2092
    lnav_data.ld_log_source.get_breakpoints().clear();
6✔
2093
    lnav_data.ld_log_source.lss_highlighters.clear();
6✔
2094
    lnav_data.ld_log_source.set_force_rebuild();
6✔
2095
    lnav_data.ld_log_source.set_marked_only(false);
6✔
2096
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
2097
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
2098
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
2099
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
2100
    rebuild_indexes(std::nullopt);
6✔
2101

2102
    lnav_data.ld_db_row_source.reset_user_state();
6✔
2103

2104
    auto* tss = static_cast<timeline_source*>(
2105
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
6✔
2106
    if (tss != nullptr) {
6✔
2107
        tss->ts_hidden_row_types.clear();
6✔
2108
    }
2109

2110
    for (auto& tc : lnav_data.ld_views) {
60✔
2111
        text_sub_source* tss = tc.get_sub_source();
54✔
2112

2113
        if (tss == nullptr) {
54✔
2114
            continue;
12✔
2115
        }
2116
        tss->get_filters().clear_filters();
42✔
2117
        tss->tss_apply_filters = true;
42✔
2118
        tss->text_filters_changed();
42✔
2119
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
2120
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
2121
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
2122
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
2123
        tc.get_bookmarks()[&textview_curses::BM_STICKY].clear();
42✔
2124
        tc.reload_data();
42✔
2125
    }
2126

2127
    for (auto& fvs : lnav_data.ld_text_source.get_file_states()) {
6✔
UNCOV
2128
        fvs->fvs_bookmarks.clear();
×
2129
    }
2130

2131
    lnav_data.ld_filter_view.reload_data();
6✔
2132
    lnav_data.ld_files_view.reload_data();
6✔
2133
    for (const auto& format : log_format::get_root_formats()) {
462✔
2134
        auto* elf = dynamic_cast<external_log_format*>(format.get());
456✔
2135

2136
        if (elf == nullptr) {
456✔
2137
            continue;
30✔
2138
        }
2139

2140
        bool changed = false;
426✔
2141
        for (const auto& vd : elf->elf_value_defs) {
6,264✔
2142
            if (vd.second->vd_meta.lvm_user_hidden) {
5,838✔
UNCOV
2143
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
UNCOV
2144
                changed = true;
×
2145
            }
2146
        }
2147
        if (changed) {
426✔
UNCOV
2148
            elf->elf_value_defs_state->vds_generation += 1;
×
2149
        }
2150
    }
2151
}
6✔
2152

2153
void
2154
lnav::session::apply_view_commands()
27✔
2155
{
2156
    static auto op = lnav_operation{__FUNCTION__};
27✔
2157

2158
    auto op_guard = lnav_opid_guard::internal(op);
27✔
2159

2160
    log_debug("applying view commands");
27✔
2161
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
270✔
2162
        const auto& vs = session_data.sd_view_states[view_index];
243✔
2163
        auto& tview = lnav_data.ld_views[view_index];
243✔
2164

2165
        lnav::set::small<std::string> curr_cmds;
243✔
2166
        auto* tss = tview.get_sub_source();
243✔
2167
        if (tview.get_sub_source() != nullptr) {
243✔
2168
            tss->tss_apply_filters = vs.vs_filtering;
189✔
2169
            if (vs.vs_min_log_level) {
189✔
UNCOV
2170
                tss->set_min_log_level(vs.vs_min_log_level.value());
×
2171
            }
2172
            tss->add_commands_for_session([&](auto& cmd) {
189✔
2173
                auto cmd_sf = string_fragment::from_str(cmd)
5✔
2174
                                  .split_when(string_fragment::tag1{' '})
5✔
2175
                                  .first;
2176
                curr_cmds.insert(cmd_sf.to_string());
5✔
2177
            });
5✔
2178
        }
2179
        for (const auto& cmdline : vs.vs_commands) {
251✔
2180
            auto cmdline_sf = string_fragment::from_str(cmdline);
8✔
2181
            auto active = ensure_view(&tview);
8✔
2182
            auto [cmd_sf, _cmdline_rem]
8✔
2183
                = cmdline_sf.split_when(string_fragment::tag1{' '});
8✔
2184
            if (curr_cmds.contains(cmd_sf.to_string())) {
8✔
2185
                log_debug("view %s command '%.*s' already active",
3✔
2186
                          tview.get_title().c_str(),
2187
                          cmd_sf.length(),
2188
                          cmd_sf.data());
2189
                continue;
3✔
2190
            }
2191
            auto exec_cmd_res
2192
                = execute_command(lnav_data.ld_exec_context, cmdline);
5✔
2193
            if (exec_cmd_res.isOk()) {
5✔
2194
                log_info("Result: %s", exec_cmd_res.unwrap().c_str());
5✔
2195
            } else {
2196
                log_error("Result: %s",
×
2197
                          exec_cmd_res.unwrapErr()
2198
                              .to_attr_line()
2199
                              .get_string()
2200
                              .c_str());
2201
            }
2202
            if (!active) {
5✔
UNCOV
2203
                lnav_data.ld_view_stack.pop_back();
×
UNCOV
2204
                lnav_data.ld_view_stack.top() | [](auto* tc) {
×
2205
                    // XXX
UNCOV
2206
                    if (tc == &lnav_data.ld_views[LNV_TIMELINE]) {
×
UNCOV
2207
                        auto tss = tc->get_sub_source();
×
UNCOV
2208
                        tss->text_filters_changed();
×
UNCOV
2209
                        tc->reload_data();
×
2210
                    }
2211
                };
2212
            }
2213
        }
5✔
2214
    }
243✔
2215
}
27✔
2216

2217
void
2218
lnav::session::restore_view_states()
27✔
2219
{
2220
    static auto op = lnav_operation{__FUNCTION__};
27✔
2221

2222
    auto op_guard = lnav_opid_guard::internal(op);
27✔
2223

2224
    log_debug("restoring view states");
27✔
2225
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
270✔
2226
        const auto& vs = session_data.sd_view_states[view_index];
243✔
2227
        auto& tview = lnav_data.ld_views[view_index];
243✔
2228
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
243✔
2229

2230
        if (!vs.vs_search.empty()) {
243✔
UNCOV
2231
            tview.execute_search(vs.vs_search);
×
UNCOV
2232
            tview.set_follow_search_for(-1, {});
×
2233
        }
2234
        tview.set_word_wrap(vs.vs_word_wrap);
243✔
2235
        auto has_loc = tview.get_selection().has_value();
243✔
2236
        if (!has_loc && vs.vs_top >= 0
63✔
2237
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
306✔
2238
                || tview.get_top() == tview.get_top_for_last_row()))
243✔
2239
        {
2240
            log_info("restoring %s view top: %d",
16✔
2241
                     lnav_view_strings[view_index].data(),
2242
                     (int) vs.vs_top);
2243
            tview.set_top(vis_line_t(vs.vs_top), true);
16✔
2244
        }
2245
        if (!has_loc && vs.vs_selection) {
243✔
UNCOV
2246
            log_info("restoring %s view selection: %d",
×
2247
                     lnav_view_strings[view_index].data(),
2248
                     (int) vs.vs_selection.value_or(-1_vl));
UNCOV
2249
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
×
2250
        }
2251
        auto sel = tview.get_selection();
243✔
2252
        if (!has_loc && sel && ta != nullptr && vs.vs_anchor
63✔
2253
            && !vs.vs_anchor->empty())
306✔
2254
        {
UNCOV
2255
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
2256

2257
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
2258
                log_info("%s view anchor mismatch %s != %s",
×
2259
                         lnav_view_strings[view_index].data(),
2260
                         curr_anchor.value_or("").c_str(),
2261
                         vs.vs_anchor.value().c_str());
2262

UNCOV
2263
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
UNCOV
2264
                if (row_opt) {
×
UNCOV
2265
                    tview.set_selection(row_opt.value());
×
2266
                }
2267
            }
2268
        }
2269
        sel = tview.get_selection();
243✔
2270
        if (!sel) {
243✔
2271
            auto height = tview.get_inner_height();
63✔
2272
            if (height == 0) {
63✔
2273
            } else if (view_index == LNV_TEXT) {
1✔
2274
                auto lf = lnav_data.ld_text_source.current_file();
×
2275
                if (lf != nullptr) {
×
UNCOV
2276
                    switch (lf->get_text_format().value_or(
×
2277
                        text_format_t::TF_BINARY))
×
2278
                    {
2279
                        case text_format_t::TF_PLAINTEXT:
×
2280
                        case text_format_t::TF_LOG: {
2281
                            if (height > 0_vl) {
×
UNCOV
2282
                                tview.set_selection(height - 1_vl);
×
2283
                            }
UNCOV
2284
                            break;
×
2285
                        }
UNCOV
2286
                        default:
×
UNCOV
2287
                            tview.set_selection(0_vl);
×
UNCOV
2288
                            break;
×
2289
                    }
2290
                }
2291
            } else if (view_index == LNV_LOG) {
1✔
UNCOV
2292
                tview.set_selection(height - 1_vl);
×
2293
            } else {
2294
                tview.set_selection(0_vl);
1✔
2295
            }
2296
        }
2297
        log_info("%s view actual top/selection: %d/%d",
243✔
2298
                 lnav_view_strings[view_index].data(),
2299
                 (int) tview.get_top(),
2300
                 (int) tview.get_selection().value_or(-1_vl));
2301
    }
2302
}
27✔
2303

2304
void
UNCOV
2305
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
2306
{
2307
    constexpr const char* STMT = R"(
×
2308
       INSERT INTO regex101_entries
2309
          (format_name, regex_name, permalink, delete_code)
2310
          VALUES (?, ?, ?, ?);
2311
)";
2312

2313
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2314
    auto_sqlite3 db;
×
2315

UNCOV
2316
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2317
        return;
×
2318
    }
2319

UNCOV
2320
    auto_mem<char, sqlite3_free> errmsg;
×
UNCOV
2321
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
UNCOV
2322
        != SQLITE_OK)
×
2323
    {
2324
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2325
        return;
×
2326
    }
2327

2328
    auto prep_res = prepare_stmt(db.in(),
2329
                                 STMT,
UNCOV
2330
                                 ei.re_format_name,
×
UNCOV
2331
                                 ei.re_regex_name,
×
2332
                                 ei.re_permalink,
×
UNCOV
2333
                                 ei.re_delete_code);
×
2334

UNCOV
2335
    if (prep_res.isErr()) {
×
UNCOV
2336
        return;
×
2337
    }
2338

UNCOV
2339
    auto ps = prep_res.unwrap();
×
2340

UNCOV
2341
    ps.execute();
×
2342
}
2343

2344
template<>
2345
struct from_sqlite<lnav::session::regex101::entry> {
2346
    inline lnav::session::regex101::entry operator()(int argc,
2347
                                                     sqlite3_value** argv,
2348
                                                     int argi)
2349
    {
2350
        return {
UNCOV
2351
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
UNCOV
2352
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
2353
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
UNCOV
2354
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
2355
        };
2356
    }
2357
};
2358

2359
Result<std::vector<lnav::session::regex101::entry>, std::string>
2360
lnav::session::regex101::get_entries()
×
2361
{
2362
    constexpr const char* STMT = R"(
×
2363
       SELECT * FROM regex101_entries;
2364
)";
2365

2366
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2367
    auto_sqlite3 db;
×
2368

UNCOV
2369
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2370
        return Err(std::string());
×
2371
    }
2372

UNCOV
2373
    auto_mem<char, sqlite3_free> errmsg;
×
2374
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2375
        != SQLITE_OK)
×
2376
    {
UNCOV
2377
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2378
        return Err(std::string(errmsg));
×
2379
    }
2380

2381
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
2382
    bool done = false;
×
UNCOV
2383
    std::vector<entry> retval;
×
2384

2385
    while (!done) {
×
2386
        auto fetch_res = ps.fetch_row<entry>();
×
2387

2388
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
UNCOV
2389
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2390
        }
2391

UNCOV
2392
        fetch_res.match(
×
UNCOV
2393
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
2394
            [](const prepared_stmt::fetch_error&) {},
×
UNCOV
2395
            [&retval](entry en) { retval.emplace_back(en); });
×
2396
    }
2397
    return Ok(retval);
×
2398
}
2399

2400
void
UNCOV
2401
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2402
                                      const std::string& regex_name)
2403
{
UNCOV
2404
    constexpr const char* STMT = R"(
×
2405
       DELETE FROM regex101_entries WHERE
2406
          format_name = ? AND regex_name = ?;
2407
)";
2408

2409
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
UNCOV
2410
    auto_sqlite3 db;
×
2411

2412
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
UNCOV
2413
        return;
×
2414
    }
2415

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

UNCOV
2418
    if (prep_res.isErr()) {
×
UNCOV
2419
        return;
×
2420
    }
2421

UNCOV
2422
    auto ps = prep_res.unwrap();
×
2423

2424
    ps.execute();
×
2425
}
2426

2427
lnav::session::regex101::get_result_t
UNCOV
2428
lnav::session::regex101::get_entry(const std::string& format_name,
×
2429
                                   const std::string& regex_name)
2430
{
UNCOV
2431
    constexpr const char* STMT = R"(
×
2432
       SELECT * FROM regex101_entries WHERE
2433
          format_name = ? AND regex_name = ?;
2434
    )";
2435

2436
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2437
    auto_sqlite3 db;
×
2438

UNCOV
2439
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2440
        return error{std::string()};
×
2441
    }
2442

UNCOV
2443
    auto_mem<char, sqlite3_free> errmsg;
×
2444
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2445
        != SQLITE_OK)
×
2446
    {
UNCOV
2447
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
UNCOV
2448
        return error{std::string(errmsg)};
×
2449
    }
2450

2451
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2452
    if (prep_res.isErr()) {
×
UNCOV
2453
        return error{prep_res.unwrapErr()};
×
2454
    }
2455

UNCOV
2456
    auto ps = prep_res.unwrap();
×
2457
    return ps.fetch_row<entry>().match(
×
UNCOV
2458
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
UNCOV
2459
            return error{fe.fe_msg};
×
2460
        },
UNCOV
2461
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
UNCOV
2462
            return no_entry{};
×
2463
        },
UNCOV
2464
        [](const entry& en) -> get_result_t { return en; });
×
2465
}
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