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

tstack / lnav / 20114321350-2741

10 Dec 2025 09:41PM UTC coverage: 68.836% (-0.07%) from 68.908%
20114321350-2741

push

github

tstack
[filters] add level filter

41 of 135 new or added lines in 10 files covered. (30.37%)

447 existing lines in 8 files now uncovered.

51534 of 74865 relevant lines covered (68.84%)

434761.7 hits per line

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

73.66
/src/session_data.cc
1
/**
2
 * Copyright (c) 2013, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * @file session_data.cc
30
 */
31

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

35
#include "session_data.hh"
36

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

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

64
session_data_t session_data;
65
recent_refs_t recent_refs;
66

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

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

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

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

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

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

100
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
101

102
    PRIMARY KEY (netloc)
103
);
104

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

111
    PRIMARY KEY (format_name, regex_name),
112

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

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

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

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

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

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

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

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

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

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

170
    sqlite3_clear_bindings(stmt);
24✔
171

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

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

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

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

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

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

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

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

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

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

261
    session_info_list.sort();
26✔
262

263
    size_t session_loops = 0;
26✔
264

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

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

286
    session_info_list.sort();
26✔
287

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

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

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

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

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

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

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

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

345
    cleanup_session_data();
26✔
346

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

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

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

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

385
    session_file_names.sort();
25✔
386

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

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

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

402
    return std::make_optional(session_file_names.back());
23✔
403
}
26✔
404

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

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

441
    static auto op = lnav_operation{__FUNCTION__};
26✔
442

443
    auto op_guard = lnav_opid_guard::internal(op);
26✔
444
    auto& lss = lnav_data.ld_log_source;
26✔
445
    auto_sqlite3 db;
26✔
446
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
26✔
447
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
26✔
448
    bool reload_needed = false;
26✔
449
    auto_mem<char, sqlite3_free> errmsg;
26✔
450

451
    log_info("loading bookmark db: %s", db_path.c_str());
26✔
452

453
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
26✔
454
        return;
×
455
    }
456

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

469
    {
470
        auto netloc_prep_res
471
            = prepare_stmt(db.in(), "SELECT netloc FROM recent_netlocs");
26✔
472
        if (netloc_prep_res.isErr()) {
26✔
473
            log_error("unable to get netlocs: %s",
2✔
474
                      netloc_prep_res.unwrapErr().c_str());
475
            return;
2✔
476
        }
477

478
        auto netloc_stmt = netloc_prep_res.unwrap();
24✔
479
        bool done = false;
24✔
480

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

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

505
    for (auto file_iter = lnav_data.ld_log_source.begin();
24✔
506
         file_iter != lnav_data.ld_log_source.end();
49✔
507
         ++file_iter)
25✔
508
    {
509
        auto lf = (*file_iter)->get_file();
25✔
510
        if (lf == nullptr) {
25✔
511
            continue;
×
512
        }
513
        if (lf->size() == 0) {
25✔
514
            continue;
×
515
        }
516
        const auto* format = lf->get_format_ptr();
25✔
517
        content_line_t base_content_line;
25✔
518

519
        base_content_line = lss.get_file_base_content_line(file_iter);
25✔
520

521
        auto low_line_iter = lf->begin();
25✔
522
        auto high_line_iter = lf->end();
25✔
523

524
        --high_line_iter;
25✔
525

526
        if (bind_values(stmt.in(),
25✔
527
                        lnav_data.ld_session_load_time,
528
                        lf->original_line_time(low_line_iter),
529
                        lf->original_line_time(high_line_iter),
530
                        lf->get_format()->get_name())
50✔
531
            != SQLITE_OK)
25✔
532
        {
533
            return;
×
534
        }
535

536
        date_time_scanner dts;
25✔
537
        bool done = false;
25✔
538
        int64_t last_mark_time = -1;
25✔
539

540
        while (!done) {
89✔
541
            int rc = sqlite3_step(stmt.in());
64✔
542

543
            switch (rc) {
64✔
544
                case SQLITE_OK:
25✔
545
                case SQLITE_DONE:
546
                    done = true;
25✔
547
                    break;
25✔
548

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

566
                    if (last_mark_time == -1) {
39✔
567
                        last_mark_time = mark_time;
24✔
568
                    } else if (last_mark_time != mark_time) {
15✔
569
                        done = true;
×
570
                        continue;
20✔
571
                    }
572

573
                    if (part_name == nullptr) {
39✔
574
                        continue;
20✔
575
                    }
576

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

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

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

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

611
                        if (read_result.isErr()) {
21✔
612
                            break;
×
613
                        }
614

615
                        auto sbr = read_result.unwrap();
21✔
616

617
                        auto line_hash
618
                            = hasher()
21✔
619
                                  .update(sbr.get_data(), sbr.length())
21✔
620
                                  .update(cl)
21✔
621
                                  .to_string();
21✔
622

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

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

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

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

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

745
                default: {
×
746
                    const char* errmsg;
747

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

756
        sqlite3_reset(stmt.in());
25✔
757
    }
25✔
758
    log_info("END select bookmarks");
24✔
759

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

769
    for (auto file_iter = lnav_data.ld_log_source.begin();
24✔
770
         file_iter != lnav_data.ld_log_source.end();
49✔
771
         ++file_iter)
25✔
772
    {
773
        auto lf = (*file_iter)->get_file();
25✔
774
        content_line_t base_content_line;
25✔
775

776
        if (lf == nullptr) {
25✔
777
            continue;
×
778
        }
779
        if (lf->size() == 0) {
25✔
780
            continue;
×
781
        }
782

783
        lss.find(lf->get_filename_as_string().c_str(), base_content_line);
25✔
784

785
        auto low_line_iter = lf->begin();
25✔
786
        auto high_line_iter = lf->end();
25✔
787

788
        --high_line_iter;
25✔
789

790
        if (bind_values(stmt.in(),
50✔
791
                        lnav_data.ld_session_load_time,
792
                        lf->get_content_id(),
25✔
793
                        lf->original_line_time(low_line_iter),
794
                        lf->original_line_time(high_line_iter),
795
                        lf->get_format()->get_name())
50✔
796
            != SQLITE_OK)
25✔
797
        {
798
            return;
×
799
        }
800

801
        date_time_scanner dts;
25✔
802
        auto done = false;
25✔
803
        int64_t last_mark_time = -1;
25✔
804

805
        while (!done) {
74✔
806
            const auto rc = sqlite3_step(stmt.in());
49✔
807

808
            switch (rc) {
49✔
809
                case SQLITE_OK:
25✔
810
                case SQLITE_DONE:
811
                    done = true;
25✔
812
                    break;
25✔
813

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

821
                    if (last_mark_time == -1) {
24✔
822
                        last_mark_time = mark_time;
24✔
823
                    } else if (last_mark_time != mark_time) {
×
824
                        done = true;
×
825
                        continue;
21✔
826
                    }
827

828
                    if (sqlite3_column_type(stmt.in(), 4) == SQLITE_NULL) {
24✔
829
                        continue;
21✔
830
                    }
831

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

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

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

853
                        int file_line = std::distance(lf->begin(), line_iter);
3✔
854
                        timeval offset;
855

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

864
                        reload_needed = true;
3✔
865

866
                        ++line_iter;
3✔
867
                    }
868
                    break;
3✔
869
                }
870

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

881
        sqlite3_reset(stmt.in());
25✔
882
    }
25✔
883
    log_info("END select time_offset");
24✔
884

885
    if (reload_needed) {
24✔
886
        lnav_data.ld_views[LNV_LOG].reload_data();
16✔
887
    }
888
}
34✔
889

890
static int
891
read_files(yajlpp_parse_context* ypc,
22✔
892
           const unsigned char* str,
893
           size_t len,
894
           yajl_string_props_t*)
895
{
896
    return 1;
22✔
897
}
898

899
const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
900
    {level_names[LEVEL_TRACE], LEVEL_TRACE},
901
    {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5},
902
    {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4},
903
    {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3},
904
    {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2},
905
    {level_names[LEVEL_DEBUG], LEVEL_DEBUG},
906
    {level_names[LEVEL_INFO], LEVEL_INFO},
907
    {level_names[LEVEL_STATS], LEVEL_STATS},
908
    {level_names[LEVEL_NOTICE], LEVEL_NOTICE},
909
    {level_names[LEVEL_WARNING], LEVEL_WARNING},
910
    {level_names[LEVEL_ERROR], LEVEL_ERROR},
911
    {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL},
912
    {level_names[LEVEL_FATAL], LEVEL_FATAL},
913

914
    json_path_handler_base::ENUM_TERMINATOR,
915
};
916

917
static const json_path_container view_def_handlers = {
918
    json_path_handler("top_line").for_field(&view_state::vs_top),
919
    json_path_handler("focused_line").for_field(&view_state::vs_selection),
920
    json_path_handler("anchor").for_field(&view_state::vs_anchor),
921
    json_path_handler("search").for_field(&view_state::vs_search),
922
    json_path_handler("word_wrap").for_field(&view_state::vs_word_wrap),
923
    json_path_handler("filtering").for_field(&view_state::vs_filtering),
924
    json_path_handler("min_level")
925
        .with_enum_values(LEVEL_ENUM)
926
        .for_field(&view_state::vs_min_log_level),
927
    json_path_handler("commands#").for_field(&view_state::vs_commands),
928
};
929

930
static const json_path_container view_handlers = {
931
    yajlpp::pattern_property_handler("(?<view_name>[\\w\\-]+)")
932
        .with_obj_provider<view_state, session_data_t>(
933
            +[](const yajlpp_provider_context& ypc, session_data_t* root) {
1,679✔
934
                auto view_index_opt
935
                    = view_from_string(ypc.get_substr("view_name").c_str());
1,679✔
936
                if (view_index_opt) {
1,679✔
937
                    return &root->sd_view_states[view_index_opt.value()];
1,679✔
938
                }
939

940
                log_error("unknown view name: %s",
×
941
                          ypc.get_substr("view_name").c_str());
942
                static view_state dummy;
943
                return &dummy;
×
944
            })
945
        .with_children(view_def_handlers),
946
};
947

948
static const json_path_container file_state_handlers = {
949
    yajlpp::property_handler("visible")
950
        .with_description("Indicates whether the file is visible or not")
951
        .for_field(&file_state::fs_is_visible),
952
};
953

954
static const json_path_container file_states_handlers = {
955
    yajlpp::pattern_property_handler(R"((?<filename>[^/]+))")
956
        .with_description("Map of file names to file state objects")
957
        .with_obj_provider<file_state, session_data_t>(
958
            [](const auto& ypc, session_data_t* root) {
66✔
959
                auto fn = ypc.get_substr("filename");
66✔
960
                return &root->sd_file_states[fn];
132✔
961
            })
66✔
962
        .with_children(file_state_handlers),
963
};
964

965
static const typed_json_path_container<session_data_t> view_info_handlers = {
966
    yajlpp::property_handler("save-time")
967
        .for_field(&session_data_t::sd_save_time),
968
    yajlpp::property_handler("time-offset")
969
        .for_field(&session_data_t::sd_time_offset),
970
    json_path_handler("files#", read_files),
971
    yajlpp::property_handler("file-states").with_children(file_states_handlers),
972
    yajlpp::property_handler("views").with_children(view_handlers),
973
};
974

975
void
976
load_session()
26✔
977
{
978
    static auto op = lnav_operation{"load_session"};
26✔
979

980
    auto op_guard = lnav_opid_guard::internal(op);
26✔
981
    log_info("BEGIN load_session");
26✔
982
    scan_sessions() | [](const auto pair) {
26✔
983
        lnav_data.ld_session_load_time = pair.first.second;
23✔
984
        const auto& view_info_path = pair.second;
23✔
985
        auto view_info_src = intern_string::lookup(view_info_path.string());
23✔
986

987
        auto open_res = lnav::filesystem::open_file(view_info_path, O_RDONLY);
23✔
988
        if (open_res.isErr()) {
23✔
989
            log_error("cannot open session file: %s -- %s",
×
990
                      view_info_path.c_str(),
991
                      open_res.unwrapErr().c_str());
992
            return;
×
993
        }
994

995
        auto fd = open_res.unwrap();
23✔
996
        unsigned char buffer[1024];
997
        ssize_t rc;
998

999
        log_info("loading session file: %s", view_info_path.c_str());
23✔
1000
        auto parser = view_info_handlers.parser_for(view_info_src);
23✔
1001
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
71✔
1002
            auto buf_frag = string_fragment::from_bytes(buffer, rc);
24✔
1003
            auto parse_res = parser.consume(buf_frag);
24✔
1004
            if (parse_res.isErr()) {
24✔
1005
                log_error("failed to load session: %s -- %s",
×
1006
                          view_info_path.c_str(),
1007
                          parse_res.unwrapErr()[0]
1008
                              .to_attr_line()
1009
                              .get_string()
1010
                              .c_str());
1011
                return;
×
1012
            }
1013
        }
1014

1015
        auto complete_res = parser.complete();
23✔
1016
        if (complete_res.isErr()) {
23✔
1017
            log_error("failed to load session: %s -- %s",
×
1018
                      view_info_path.c_str(),
1019
                      complete_res.unwrapErr()[0]
1020
                          .to_attr_line()
1021
                          .get_string()
1022
                          .c_str());
1023
            return;
×
1024
        }
1025
        session_data = complete_res.unwrap();
23✔
1026

1027
        bool log_changes = false;
23✔
1028

1029
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
47✔
1030
            auto iter = session_data.sd_file_states.find(lf->get_filename());
24✔
1031

1032
            if (iter == session_data.sd_file_states.end()) {
24✔
1033
                continue;
2✔
1034
            }
1035

1036
            log_debug("found state for file: %s %d (%s)",
22✔
1037
                      lf->get_content_id().c_str(),
1038
                      iter->second.fs_is_visible,
1039
                      lf->get_filename_as_string().c_str());
1040
            lnav_data.ld_log_source.find_data(lf) |
22✔
1041
                [iter, &log_changes](auto ld) {
44✔
1042
                    if (ld->ld_visible != iter->second.fs_is_visible) {
22✔
1043
                        ld->get_file_ptr()->set_indexing(
1✔
1044
                            iter->second.fs_is_visible);
1✔
1045
                        ld->set_visibility(iter->second.fs_is_visible);
1✔
1046
                        log_changes = true;
1✔
1047
                    }
1048
                };
1049
        }
1050

1051
        if (log_changes) {
23✔
1052
            lnav_data.ld_log_source.text_filters_changed();
1✔
1053
        }
1054
    };
23✔
1055

1056
    lnav::events::publish(lnav_data.ld_db.in(),
26✔
1057
                          lnav::events::session::loaded{});
1058

1059
    log_info("END load_session");
26✔
1060
}
26✔
1061

1062
static void
1063
yajl_writer(void* context, const char* str, size_t len)
8,267✔
1064
{
1065
    FILE* file = (FILE*) context;
8,267✔
1066

1067
    fwrite(str, len, 1, file);
8,267✔
1068
}
8,267✔
1069

1070
static void
1071
save_user_bookmarks(sqlite3* db,
23✔
1072
                    sqlite3_stmt* stmt,
1073
                    bookmark_vector<content_line_t>& user_marks)
1074
{
1075
    auto& lss = lnav_data.ld_log_source;
23✔
1076

1077
    for (auto iter = user_marks.bv_tree.begin();
23✔
1078
         iter != user_marks.bv_tree.end();
24✔
1079
         ++iter)
1✔
1080
    {
1081
        content_line_t cl = *iter;
1✔
1082
        auto lf = lss.find(cl);
1✔
1083
        if (lf == nullptr) {
1✔
1084
            continue;
×
1085
        }
1086

1087
        sqlite3_clear_bindings(stmt);
1✔
1088

1089
        const auto line_iter = lf->begin() + cl;
1✔
1090
        auto fr = lf->get_file_range(line_iter, false);
1✔
1091
        auto read_result = lf->read_range(fr);
1✔
1092

1093
        if (read_result.isErr()) {
1✔
1094
            continue;
×
1095
        }
1096

1097
        auto line_hash = read_result
1098
                             .map([cl](auto sbr) {
2✔
1099
                                 return hasher()
2✔
1100
                                     .update(sbr.get_data(), sbr.length())
1✔
1101
                                     .update(cl)
1✔
1102
                                     .to_string();
2✔
1103
                             })
1104
                             .unwrap();
1✔
1105

1106
        if (bind_values(stmt,
3✔
1107
                        lf->original_line_time(line_iter),
1108
                        lf->get_format()->get_name(),
2✔
1109
                        line_hash,
1110
                        lnav_data.ld_session_time)
1111
            != SQLITE_OK)
1✔
1112
        {
1113
            continue;
×
1114
        }
1115

1116
        if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT) != SQLITE_OK) {
1✔
1117
            log_error("could not bind log hash -- %s", sqlite3_errmsg(db));
×
1118
            return;
×
1119
        }
1120

1121
        if (sqlite3_step(stmt) != SQLITE_DONE) {
1✔
1122
            log_error("could not execute bookmark insert statement -- %s",
×
1123
                      sqlite3_errmsg(db));
1124
            return;
×
1125
        }
1126

1127
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
1✔
1128
                                          lf->get_format_ptr()->get_name(),
1✔
1129
                                          line_hash);
1130

1131
        sqlite3_reset(stmt);
1✔
1132
    }
1✔
1133
}
1134

1135
static void
1136
save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
24✔
1137
{
1138
    for (const auto& bm_pair : lf->get_bookmark_metadata()) {
31✔
1139
        auto cl = content_line_t(bm_pair.first);
7✔
1140
        sqlite3_clear_bindings(stmt);
7✔
1141

1142
        auto line_iter = lf->begin() + cl;
7✔
1143
        auto fr = lf->get_file_range(line_iter, false);
7✔
1144
        auto read_result = lf->read_range(fr);
7✔
1145

1146
        if (read_result.isErr()) {
7✔
1147
            continue;
×
1148
        }
1149

1150
        auto line_hash = read_result
1151
                             .map([cl](auto sbr) {
14✔
1152
                                 return hasher()
14✔
1153
                                     .update(sbr.get_data(), sbr.length())
7✔
1154
                                     .update(cl)
7✔
1155
                                     .to_string();
14✔
1156
                             })
1157
                             .unwrap();
7✔
1158

1159
        if (bind_values(stmt,
21✔
1160
                        lf->original_line_time(line_iter),
1161
                        lf->get_format()->get_name(),
14✔
1162
                        line_hash,
1163
                        lnav_data.ld_session_time)
1164
            != SQLITE_OK)
7✔
1165
        {
1166
            continue;
×
1167
        }
1168

1169
        const auto& line_meta = bm_pair.second;
7✔
1170
        if (line_meta.empty(bookmark_metadata::categories::any)) {
7✔
1171
            continue;
×
1172
        }
1173

1174
        if (sqlite3_bind_text(stmt,
7✔
1175
                              5,
1176
                              line_meta.bm_name.c_str(),
1177
                              line_meta.bm_name.length(),
7✔
1178
                              SQLITE_TRANSIENT)
1179
            != SQLITE_OK)
7✔
1180
        {
1181
            log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1182
            return;
×
1183
        }
1184

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

1196
        std::string tags;
7✔
1197

1198
        if (!line_meta.bm_tags.empty()) {
7✔
1199
            yajlpp_gen gen;
4✔
1200

1201
            yajl_gen_config(gen, yajl_gen_beautify, false);
4✔
1202

1203
            {
1204
                yajlpp_array arr(gen);
4✔
1205

1206
                for (const auto& str : line_meta.bm_tags) {
8✔
1207
                    arr.gen(str);
4✔
1208
                }
1209
            }
4✔
1210

1211
            tags = gen.to_string_fragment().to_string();
4✔
1212
        }
4✔
1213

1214
        if (sqlite3_bind_text(
7✔
1215
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
7✔
1216
            != SQLITE_OK)
7✔
1217
        {
1218
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
1219
            return;
×
1220
        }
1221

1222
        if (!line_meta.bm_annotations.la_pairs.empty()) {
7✔
1223
            auto anno_str = logmsg_annotations_handlers.to_string(
1224
                line_meta.bm_annotations);
1✔
1225

1226
            if (sqlite3_bind_text(stmt,
1✔
1227
                                  8,
1228
                                  anno_str.c_str(),
1229
                                  anno_str.length(),
1✔
1230
                                  SQLITE_TRANSIENT)
1231
                != SQLITE_OK)
1✔
1232
            {
1233
                log_error("could not bind annotations -- %s",
×
1234
                          sqlite3_errmsg(db));
1235
                return;
×
1236
            }
1237
        } else {
1✔
1238
            sqlite3_bind_null(stmt, 8);
6✔
1239
        }
1240

1241
        if (line_meta.bm_opid.empty()) {
7✔
1242
            sqlite3_bind_null(stmt, 9);
7✔
1243
        } else {
1244
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1245
        }
1246

1247
        if (sqlite3_step(stmt) != SQLITE_DONE) {
7✔
1248
            log_error("could not execute bookmark insert statement -- %s",
×
1249
                      sqlite3_errmsg(db));
1250
            return;
×
1251
        }
1252

1253
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
7✔
1254
                                          lf->get_format_ptr()->get_name(),
7✔
1255
                                          line_hash);
1256

1257
        sqlite3_reset(stmt);
7✔
1258
    }
7✔
1259
}
1260

1261
static void
1262
save_time_bookmarks()
23✔
1263
{
1264
    auto_sqlite3 db;
23✔
1265
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
23✔
1266
    auto_mem<char, sqlite3_free> errmsg;
23✔
1267
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
23✔
1268

1269
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
23✔
1270
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1271
        return;
×
1272
    }
1273

1274
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
23✔
1275
        != SQLITE_OK)
23✔
1276
    {
1277
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1278
        return;
×
1279
    }
1280

1281
    if (sqlite3_exec(
23✔
1282
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1283
        != SQLITE_OK)
23✔
1284
    {
1285
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1286
        return;
×
1287
    }
1288

1289
    {
1290
        static const char* UPDATE_NETLOCS_STMT
1291
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1292

1293
        std::set<std::string> netlocs;
23✔
1294

1295
        isc::to<tailer::looper&, services::remote_tailer_t>().send_and_wait(
23✔
1296
            [&netlocs](auto& tlooper) { netlocs = tlooper.active_netlocs(); });
46✔
1297

1298
        if (sqlite3_prepare_v2(
23✔
1299
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1300
            != SQLITE_OK)
23✔
1301
        {
1302
            log_error("could not prepare recent_netlocs statement -- %s",
×
1303
                      sqlite3_errmsg(db));
1304
            return;
×
1305
        }
1306

1307
        for (const auto& netloc : netlocs) {
23✔
1308
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1309

1310
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1311
                log_error("could not execute bookmark insert statement -- %s",
×
1312
                          sqlite3_errmsg(db));
1313
                return;
×
1314
            }
1315

1316
            sqlite3_reset(stmt.in());
×
1317
        }
1318
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
23✔
1319
    }
23✔
1320

1321
    auto& lss = lnav_data.ld_log_source;
23✔
1322
    auto& bm = lss.get_user_bookmarks();
23✔
1323

1324
    if (sqlite3_prepare_v2(db.in(),
23✔
1325
                           "DELETE FROM bookmarks WHERE "
1326
                           " log_time = ? and log_format = ? and log_hash = ? "
1327
                           " and session_time = ?",
1328
                           -1,
1329
                           stmt.out(),
1330
                           nullptr)
1331
        != SQLITE_OK)
23✔
1332
    {
1333
        log_error("could not prepare bookmark delete statement -- %s",
×
1334
                  sqlite3_errmsg(db));
1335
        return;
×
1336
    }
1337

1338
    for (auto& marked_session_line : marked_session_lines) {
25✔
1339
        sqlite3_clear_bindings(stmt.in());
2✔
1340

1341
        if (bind_values(stmt,
4✔
1342
                        marked_session_line.sl_time,
1343
                        marked_session_line.sl_format_name,
1344
                        marked_session_line.sl_line_hash,
2✔
1345
                        lnav_data.ld_session_time)
1346
            != SQLITE_OK)
2✔
1347
        {
1348
            continue;
×
1349
        }
1350

1351
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1352
            log_error("could not execute bookmark insert statement -- %s",
×
1353
                      sqlite3_errmsg(db));
1354
            return;
×
1355
        }
1356

1357
        sqlite3_reset(stmt.in());
2✔
1358
    }
1359

1360
    marked_session_lines.clear();
23✔
1361

1362
    if (sqlite3_prepare_v2(db.in(),
23✔
1363
                           "REPLACE INTO bookmarks"
1364
                           " (log_time, log_format, log_hash, session_time, "
1365
                           "part_name, comment, tags, annotations, log_opid)"
1366
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
1367
                           -1,
1368
                           stmt.out(),
1369
                           nullptr)
1370
        != SQLITE_OK)
23✔
1371
    {
1372
        log_error("could not prepare bookmark replace statement -- %s",
×
1373
                  sqlite3_errmsg(db));
1374
        return;
×
1375
    }
1376

1377
    {
1378
        for (auto file_iter = lnav_data.ld_log_source.begin();
23✔
1379
             file_iter != lnav_data.ld_log_source.end();
47✔
1380
             ++file_iter)
24✔
1381
        {
1382
            auto lf = (*file_iter)->get_file();
24✔
1383

1384
            if (lf == nullptr) {
24✔
1385
                continue;
×
1386
            }
1387
            if (lf->size() == 0) {
24✔
1388
                continue;
×
1389
            }
1390

1391
            content_line_t base_content_line;
24✔
1392
            base_content_line = lss.get_file_base_content_line(file_iter);
24✔
1393
            base_content_line
1394
                = content_line_t(base_content_line + lf->size() - 1);
24✔
1395

1396
            if (!bind_line(db.in(),
24✔
1397
                           stmt.in(),
1398
                           base_content_line,
1399
                           lnav_data.ld_session_time))
24✔
1400
            {
1401
                continue;
×
1402
            }
1403

1404
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
24✔
1405
                log_error("could not bind log hash -- %s",
×
1406
                          sqlite3_errmsg(db.in()));
1407
                return;
×
1408
            }
1409

1410
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
24✔
1411
                log_error("could not execute bookmark insert statement -- %s",
×
1412
                          sqlite3_errmsg(db));
1413
                return;
×
1414
            }
1415

1416
            sqlite3_reset(stmt.in());
24✔
1417
        }
24✔
1418
    }
1419

1420
    save_user_bookmarks(db.in(), stmt.in(), bm[&textview_curses::BM_USER]);
23✔
1421
    for (const auto& ldd : lss) {
47✔
1422
        auto* lf = ldd->get_file_ptr();
24✔
1423
        if (lf == nullptr) {
24✔
1424
            continue;
×
1425
        }
1426

1427
        save_meta_bookmarks(db.in(), stmt.in(), lf);
24✔
1428
    }
1429

1430
    if (sqlite3_prepare_v2(db.in(),
23✔
1431
                           "DELETE FROM time_offset WHERE "
1432
                           " log_time = ? and log_format = ? and log_hash = ? "
1433
                           " and session_time = ?",
1434
                           -1,
1435
                           stmt.out(),
1436
                           NULL)
1437
        != SQLITE_OK)
23✔
1438
    {
1439
        log_error("could not prepare time_offset delete statement -- %s",
×
1440
                  sqlite3_errmsg(db));
1441
        return;
×
1442
    }
1443

1444
    for (auto& offset_session_line : offset_session_lines) {
24✔
1445
        sqlite3_clear_bindings(stmt.in());
1✔
1446

1447
        if (bind_values(stmt,
2✔
1448
                        offset_session_line.sl_time,
1449
                        offset_session_line.sl_format_name,
1450
                        offset_session_line.sl_line_hash,
1✔
1451
                        lnav_data.ld_session_time)
1452
            != SQLITE_OK)
1✔
1453
        {
1454
            continue;
×
1455
        }
1456

1457
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1458
            log_error("could not execute bookmark insert statement -- %s",
×
1459
                      sqlite3_errmsg(db));
1460
            return;
×
1461
        }
1462

1463
        sqlite3_reset(stmt.in());
1✔
1464
    }
1465

1466
    offset_session_lines.clear();
23✔
1467

1468
    if (sqlite3_prepare_v2(db.in(),
23✔
1469
                           "REPLACE INTO time_offset"
1470
                           " (log_time, log_format, log_hash, session_time, "
1471
                           "offset_sec, offset_usec)"
1472
                           " VALUES (?, ?, ?, ?, ?, ?)",
1473
                           -1,
1474
                           stmt.out(),
1475
                           nullptr)
1476
        != SQLITE_OK)
23✔
1477
    {
1478
        log_error("could not prepare time_offset replace statement -- %s",
×
1479
                  sqlite3_errmsg(db));
1480
        return;
×
1481
    }
1482

1483
    {
1484
        for (auto file_iter = lnav_data.ld_log_source.begin();
23✔
1485
             file_iter != lnav_data.ld_log_source.end();
47✔
1486
             ++file_iter)
24✔
1487
        {
1488
            auto lf = (*file_iter)->get_file();
24✔
1489
            if (lf == nullptr) {
24✔
1490
                continue;
×
1491
            }
1492
            if (lf->size() == 0) {
24✔
1493
                continue;
×
1494
            }
1495

1496
            if (bind_values(stmt,
72✔
1497
                            lf->original_line_time(lf->begin()),
1498
                            lf->get_format()->get_name(),
48✔
1499
                            lf->get_content_id(),
24✔
1500
                            lnav_data.ld_session_time)
1501
                != SQLITE_OK)
24✔
1502
            {
1503
                continue;
×
1504
            }
1505

1506
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
24✔
1507
                log_error("could not bind log hash -- %s",
×
1508
                          sqlite3_errmsg(db.in()));
1509
                return;
×
1510
            }
1511

1512
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
24✔
1513
                log_error("could not bind log hash -- %s",
×
1514
                          sqlite3_errmsg(db.in()));
1515
                return;
×
1516
            }
1517

1518
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
24✔
1519
                log_error("could not execute bookmark insert statement -- %s",
×
1520
                          sqlite3_errmsg(db));
1521
                return;
×
1522
            }
1523

1524
            sqlite3_reset(stmt.in());
24✔
1525
        }
24✔
1526
    }
1527

1528
    for (const auto& ls : lss) {
47✔
1529
        if (ls->get_file() == nullptr) {
24✔
1530
            continue;
21✔
1531
        }
1532

1533
        const auto lf = ls->get_file();
24✔
1534
        if (!lf->is_time_adjusted()) {
24✔
1535
            continue;
21✔
1536
        }
1537

1538
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
1539
        auto offset = lf->get_time_offset();
3✔
1540

1541
        bind_values(stmt.in(),
6✔
1542
                    lf->original_line_time(line_iter),
1543
                    lf->get_format()->get_name(),
6✔
1544
                    lf->get_content_id(),
3✔
1545
                    lnav_data.ld_session_time,
1546
                    offset.tv_sec,
1547
                    offset.tv_usec);
1548

1549
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
3✔
1550
            log_error("could not execute bookmark insert statement -- %s",
×
1551
                      sqlite3_errmsg(db));
1552
            return;
×
1553
        }
1554

1555
        sqlite3_reset(stmt.in());
3✔
1556
    }
24✔
1557

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

1565
    if (sqlite3_exec(db.in(), BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
23✔
1566
        != SQLITE_OK)
23✔
1567
    {
1568
        log_error("unable to delete old bookmarks -- %s", errmsg.in());
×
1569
        return;
×
1570
    }
1571

1572
    if (sqlite3_exec(db.in(), NETLOC_LRU_STMT, nullptr, nullptr, errmsg.out())
23✔
1573
        != SQLITE_OK)
23✔
1574
    {
1575
        log_error("unable to delete old netlocs -- %s", errmsg.in());
×
1576
        return;
×
1577
    }
1578
}
23✔
1579

1580
static void
1581
save_session_with_id(const std::string& session_id)
22✔
1582
{
1583
    auto_mem<FILE> file(fclose);
22✔
1584
    yajl_gen handle = nullptr;
22✔
1585

1586
    /* TODO: save the last search query */
1587

1588
    log_info("saving session with id: %s", session_id.c_str());
22✔
1589

1590
    auto view_base_name
1591
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
44✔
1592
                      session_id,
1593
                      lnav_data.ld_session_time,
1594
                      getppid());
22✔
1595
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
22✔
1596
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
22✔
1597

1598
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
22✔
1599
        perror("Unable to open session file");
×
1600
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
22✔
1601
        perror("Unable to create yajl_gen object");
×
1602
    } else {
1603
        yajl_gen_config(
22✔
1604
            handle, yajl_gen_print_callback, yajl_writer, file.in());
1605

1606
        {
1607
            yajlpp_map root_map(handle);
22✔
1608

1609
            root_map.gen("save-time");
22✔
1610
            root_map.gen((long long) time(nullptr));
22✔
1611

1612
            root_map.gen("time-offset");
22✔
1613
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
22✔
1614

1615
            root_map.gen("files");
22✔
1616

1617
            {
1618
                yajlpp_array file_list(handle);
22✔
1619

1620
                for (auto& ld_file_name :
22✔
1621
                     lnav_data.ld_active_files.fc_file_names)
68✔
1622
                {
1623
                    file_list.gen(ld_file_name.first);
24✔
1624
                }
1625
            }
22✔
1626

1627
            root_map.gen("file-states");
22✔
1628

1629
            {
1630
                yajlpp_map file_states(handle);
22✔
1631

1632
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
46✔
1633
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
24✔
1634

1635
                    file_states.gen(lf->get_filename().native());
24✔
1636

1637
                    {
1638
                        yajlpp_map file_state(handle);
24✔
1639

1640
                        file_state.gen("visible");
24✔
1641
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
24✔
1642
                    }
24✔
1643
                }
1644
            }
22✔
1645

1646
            root_map.gen("views");
22✔
1647

1648
            {
1649
                yajlpp_map top_view_map(handle);
22✔
1650

1651
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
220✔
1652
                    auto& tc = lnav_data.ld_views[lpc];
198✔
1653
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
198✔
1654

1655
                    top_view_map.gen(lnav_view_strings[lpc]);
198✔
1656

1657
                    yajlpp_map view_map(handle);
198✔
1658

1659
                    view_map.gen("top_line");
198✔
1660

1661
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
198✔
1662
                        view_map.gen(-1LL);
185✔
1663
                    } else {
1664
                        view_map.gen((long long) tc.get_top());
13✔
1665
                    }
1666

1667
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
247✔
1668
                        && tc.get_inner_height() > 0_vl
2✔
1669
                        && tc.get_selection() != tc.get_inner_height() - 1)
247✔
1670
                    {
1671
                        auto sel = tc.get_selection();
1✔
1672
                        if (sel) {
1✔
1673
                            view_map.gen("focused_line");
1✔
1674
                            view_map.gen((long long) sel.value());
1✔
1675

1676
                            if (ta != nullptr) {
1✔
1677
                                auto anchor_opt
1678
                                    = ta->anchor_for_row(sel.value());
1✔
1679
                                if (anchor_opt) {
1✔
1680
                                    view_map.gen("anchor");
1✔
1681
                                    view_map.gen(anchor_opt.value());
1✔
1682
                                }
1683
                            }
1✔
1684
                        }
1685
                    }
1686

1687
                    view_map.gen("search");
198✔
1688
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
198✔
1689

1690
                    view_map.gen("word_wrap");
198✔
1691
                    view_map.gen(tc.get_word_wrap());
198✔
1692

1693
                    auto* tss = tc.get_sub_source();
198✔
1694
                    if (tss == nullptr) {
198✔
1695
                        continue;
44✔
1696
                    }
1697

1698
                    view_map.gen("filtering");
154✔
1699
                    view_map.gen(tss->tss_apply_filters);
154✔
1700

1701
                    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
154✔
NEW
1702
                        view_map.gen("min_level");
×
NEW
1703
                        view_map.gen(level_names[tss->get_min_log_level()]);
×
1704
                    }
1705

1706
                    view_map.gen("commands");
154✔
1707
                    yajlpp_array cmd_array(handle);
154✔
1708

1709
                    tss->add_commands_for_session(
154✔
1710
                        [&](auto& cmd) { cmd_array.gen(cmd); });
165✔
1711
                }
198✔
1712
            }
22✔
1713
        }
22✔
1714

1715
        yajl_gen_clear(handle);
22✔
1716
        yajl_gen_free(handle);
22✔
1717

1718
        fclose(file.release());
22✔
1719

1720
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
22✔
1721

1722
        log_info("Saved session: %s", view_file_name.c_str());
22✔
1723
    }
1724
}
22✔
1725

1726
void
1727
save_session()
23✔
1728
{
1729
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
23✔
1730
        log_info("secure mode is enabled, not saving session");
×
1731
        return;
×
1732
    }
1733

1734
    static auto op = lnav_operation{"save_session"};
23✔
1735

1736
    auto op_guard = lnav_opid_guard::internal(op);
23✔
1737

1738
    log_debug("BEGIN save_session");
23✔
1739
    save_time_bookmarks();
23✔
1740

1741
    const auto opt_session_id = compute_session_id();
23✔
1742
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
44✔
1743
    for (const auto& pair : lnav_data.ld_session_id) {
27✔
1744
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
1745
            continue;
3✔
1746
        }
1747
        save_session_with_id(pair.first);
1✔
1748
    }
1749
    log_debug("END save_session");
23✔
1750
}
23✔
1751

1752
void
1753
reset_session()
6✔
1754
{
1755
    log_info("reset session: time=%lld", lnav_data.ld_session_time);
6✔
1756

1757
    save_session();
6✔
1758

1759
    lnav_data.ld_session_time = time(nullptr);
6✔
1760
    session_data.sd_file_states.clear();
6✔
1761

1762
    for (auto& tc : lnav_data.ld_views) {
60✔
1763
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
1764
        auto& hmap = tc.get_highlights();
54✔
1765
        auto hl_iter = hmap.begin();
54✔
1766

1767
        while (hl_iter != hmap.end()) {
552✔
1768
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
1769
                ++hl_iter;
498✔
1770
            } else {
1771
                hmap.erase(hl_iter++);
×
1772
            }
1773
        }
1774

1775
        if (ttt != nullptr) {
54✔
1776
            ttt->clear_min_max_row_times();
36✔
1777
        }
1778
    }
1779

1780
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
1781
        lf->reset_state();
8✔
1782
    }
1783

1784
    // XXX clean this up
1785
    lnav_data.ld_log_source.set_force_rebuild();
6✔
1786
    lnav_data.ld_log_source.set_marked_only(false);
6✔
1787
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
1788
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
1789
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
1790
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
1791
    rebuild_indexes(std::nullopt);
6✔
1792

1793
    lnav_data.ld_db_row_source.reset_user_state();
6✔
1794

1795
    for (auto& tc : lnav_data.ld_views) {
60✔
1796
        text_sub_source* tss = tc.get_sub_source();
54✔
1797

1798
        if (tss == nullptr) {
54✔
1799
            continue;
12✔
1800
        }
1801
        tss->get_filters().clear_filters();
42✔
1802
        tss->tss_apply_filters = true;
42✔
1803
        tss->text_filters_changed();
42✔
1804
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
1805
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
1806
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
1807
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
1808
        tc.reload_data();
42✔
1809
    }
1810

1811
    lnav_data.ld_filter_view.reload_data();
6✔
1812
    lnav_data.ld_files_view.reload_data();
6✔
1813
    for (const auto& format : log_format::get_root_formats()) {
444✔
1814
        auto* elf = dynamic_cast<external_log_format*>(format.get());
438✔
1815

1816
        if (elf == nullptr) {
438✔
1817
            continue;
30✔
1818
        }
1819

1820
        bool changed = false;
408✔
1821
        for (const auto& vd : elf->elf_value_defs) {
5,916✔
1822
            if (vd.second->vd_meta.lvm_user_hidden) {
5,508✔
1823
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
1824
                changed = true;
×
1825
            }
1826
        }
1827
        if (changed) {
408✔
1828
            elf->elf_value_defs_state->vds_generation += 1;
×
1829
        }
1830
    }
1831
}
6✔
1832

1833
void
1834
lnav::session::apply_view_commands()
25✔
1835
{
1836
    static auto op = lnav_operation{__FUNCTION__};
25✔
1837

1838
    auto op_guard = lnav_opid_guard::internal(op);
25✔
1839

1840
    log_debug("applying view commands");
25✔
1841
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
250✔
1842
        const auto& vs = session_data.sd_view_states[view_index];
225✔
1843
        auto& tview = lnav_data.ld_views[view_index];
225✔
1844

1845
        lnav::set::small<std::string> curr_cmds;
225✔
1846
        auto* tss = tview.get_sub_source();
225✔
1847
        if (tview.get_sub_source() != nullptr) {
225✔
1848
            tss->tss_apply_filters = vs.vs_filtering;
175✔
1849
            if (vs.vs_min_log_level) {
175✔
NEW
1850
                tss->set_min_log_level(vs.vs_min_log_level.value());
×
1851
            }
1852
            tss->add_commands_for_session([&](auto& cmd) {
175✔
1853
                auto cmd_sf = string_fragment::from_str(cmd)
5✔
1854
                                  .split_when(string_fragment::tag1{' '})
5✔
1855
                                  .first;
1856
                curr_cmds.insert(cmd_sf.to_string());
5✔
1857
            });
5✔
1858
        }
1859
        for (const auto& cmdline : vs.vs_commands) {
232✔
1860
            auto cmdline_sf = string_fragment::from_str(cmdline);
7✔
1861
            auto active = ensure_view(&tview);
7✔
1862
            auto [cmd_sf, _cmdline_rem]
7✔
1863
                = cmdline_sf.split_when(string_fragment::tag1{' '});
7✔
1864
            if (curr_cmds.contains(cmd_sf.to_string())) {
7✔
1865
                log_debug("view %s command '%.*s' already active",
3✔
1866
                          tview.get_title().c_str(),
1867
                          cmd_sf.length(),
1868
                          cmd_sf.data());
1869
                continue;
3✔
1870
            }
1871
            auto exec_cmd_res
1872
                = execute_command(lnav_data.ld_exec_context, cmdline);
4✔
1873
            if (exec_cmd_res.isOk()) {
4✔
1874
                log_info("Result: %s", exec_cmd_res.unwrap().c_str());
4✔
1875
            } else {
1876
                log_error("Result: %s",
×
1877
                          exec_cmd_res.unwrapErr()
1878
                              .to_attr_line()
1879
                              .get_string()
1880
                              .c_str());
1881
            }
1882
            if (!active) {
4✔
1883
                lnav_data.ld_view_stack.pop_back();
×
1884
                lnav_data.ld_view_stack.top() | [](auto* tc) {
×
1885
                    // XXX
1886
                    if (tc == &lnav_data.ld_views[LNV_TIMELINE]) {
×
1887
                        auto tss = tc->get_sub_source();
×
1888
                        tss->text_filters_changed();
×
1889
                        tc->reload_data();
×
1890
                    }
1891
                };
1892
            }
1893
        }
4✔
1894
    }
225✔
1895
}
25✔
1896

1897
void
1898
lnav::session::restore_view_states()
25✔
1899
{
1900
    static auto op = lnav_operation{__FUNCTION__};
25✔
1901

1902
    auto op_guard = lnav_opid_guard::internal(op);
25✔
1903

1904
    log_debug("restoring view states");
25✔
1905
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
250✔
1906
        const auto& vs = session_data.sd_view_states[view_index];
225✔
1907
        auto& tview = lnav_data.ld_views[view_index];
225✔
1908
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
225✔
1909

1910
        if (!vs.vs_search.empty()) {
225✔
1911
            tview.execute_search(vs.vs_search);
×
1912
            tview.set_follow_search_for(-1, {});
×
1913
        }
1914
        tview.set_word_wrap(vs.vs_word_wrap);
225✔
1915
        auto has_loc = tview.get_selection().has_value();
225✔
1916
        if (!has_loc && vs.vs_top >= 0
59✔
1917
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
284✔
1918
                || tview.get_top() == tview.get_top_for_last_row()))
225✔
1919
        {
1920
            log_info("restoring %s view top: %d",
16✔
1921
                     lnav_view_strings[view_index].data(),
1922
                     (int) vs.vs_top);
1923
            tview.set_top(vis_line_t(vs.vs_top), true);
16✔
1924
        }
1925
        if (!has_loc && vs.vs_selection) {
225✔
1926
            log_info("restoring %s view selection: %d",
×
1927
                     lnav_view_strings[view_index].data(),
1928
                     (int) vs.vs_selection.value_or(-1_vl));
1929
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
×
1930
        }
1931
        auto sel = tview.get_selection();
225✔
1932
        if (!has_loc && sel && ta != nullptr && vs.vs_anchor
59✔
1933
            && !vs.vs_anchor->empty())
284✔
1934
        {
1935
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
1936

1937
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
1938
                log_info("%s view anchor mismatch %s != %s",
×
1939
                         lnav_view_strings[view_index].data(),
1940
                         curr_anchor.value_or("").c_str(),
1941
                         vs.vs_anchor.value().c_str());
1942

1943
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
1944
                if (row_opt) {
×
1945
                    tview.set_selection(row_opt.value());
×
1946
                }
1947
            }
1948
        }
1949
        sel = tview.get_selection();
225✔
1950
        if (!sel) {
225✔
1951
            auto height = tview.get_inner_height();
59✔
1952
            if (height == 0) {
59✔
1953
            } else if (view_index == LNV_TEXT) {
1✔
1954
                auto lf = lnav_data.ld_text_source.current_file();
×
1955
                if (lf != nullptr) {
×
1956
                    switch (lf->get_text_format()) {
×
1957
                        case text_format_t::TF_UNKNOWN:
×
1958
                        case text_format_t::TF_LOG: {
1959
                            if (height > 0_vl) {
×
1960
                                tview.set_selection(height - 1_vl);
×
1961
                            }
1962
                            break;
×
1963
                        }
1964
                        default:
×
1965
                            tview.set_selection(0_vl);
×
1966
                            break;
×
1967
                    }
1968
                }
1969
            } else if (view_index == LNV_LOG) {
1✔
1970
                tview.set_selection(height - 1_vl);
×
1971
            } else {
1972
                tview.set_selection(0_vl);
1✔
1973
            }
1974
        }
1975
        log_info("%s view actual top/selection: %d/%d",
225✔
1976
                 lnav_view_strings[view_index].data(),
1977
                 (int) tview.get_top(),
1978
                 (int) tview.get_selection().value_or(-1_vl));
1979
    }
1980
}
25✔
1981

1982
void
1983
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
1984
{
1985
    constexpr const char* STMT = R"(
×
1986
       INSERT INTO regex101_entries
1987
          (format_name, regex_name, permalink, delete_code)
1988
          VALUES (?, ?, ?, ?);
1989
)";
1990

1991
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
1992
    auto_sqlite3 db;
×
1993

1994
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
1995
        return;
×
1996
    }
1997

1998
    auto_mem<char, sqlite3_free> errmsg;
×
1999
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2000
        != SQLITE_OK)
×
2001
    {
2002
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2003
        return;
×
2004
    }
2005

2006
    auto prep_res = prepare_stmt(db.in(),
2007
                                 STMT,
2008
                                 ei.re_format_name,
×
2009
                                 ei.re_regex_name,
×
2010
                                 ei.re_permalink,
×
2011
                                 ei.re_delete_code);
×
2012

2013
    if (prep_res.isErr()) {
×
2014
        return;
×
2015
    }
2016

2017
    auto ps = prep_res.unwrap();
×
2018

2019
    ps.execute();
×
2020
}
2021

2022
template<>
2023
struct from_sqlite<lnav::session::regex101::entry> {
2024
    inline lnav::session::regex101::entry operator()(int argc,
2025
                                                     sqlite3_value** argv,
2026
                                                     int argi)
2027
    {
2028
        return {
2029
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
2030
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
2031
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
2032
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
2033
        };
2034
    }
2035
};
2036

2037
Result<std::vector<lnav::session::regex101::entry>, std::string>
2038
lnav::session::regex101::get_entries()
×
2039
{
2040
    constexpr const char* STMT = R"(
×
2041
       SELECT * FROM regex101_entries;
2042
)";
2043

2044
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2045
    auto_sqlite3 db;
×
2046

2047
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2048
        return Err(std::string());
×
2049
    }
2050

2051
    auto_mem<char, sqlite3_free> errmsg;
×
2052
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2053
        != SQLITE_OK)
×
2054
    {
2055
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2056
        return Err(std::string(errmsg));
×
2057
    }
2058

2059
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
2060
    bool done = false;
×
2061
    std::vector<entry> retval;
×
2062

2063
    while (!done) {
×
2064
        auto fetch_res = ps.fetch_row<entry>();
×
2065

2066
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
2067
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2068
        }
2069

2070
        fetch_res.match(
×
2071
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
2072
            [](const prepared_stmt::fetch_error&) {},
×
2073
            [&retval](entry en) { retval.emplace_back(en); });
×
2074
    }
2075
    return Ok(retval);
×
2076
}
2077

2078
void
2079
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2080
                                      const std::string& regex_name)
2081
{
2082
    constexpr const char* STMT = R"(
×
2083
       DELETE FROM regex101_entries WHERE
2084
          format_name = ? AND regex_name = ?;
2085
)";
2086

2087
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2088
    auto_sqlite3 db;
×
2089

2090
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2091
        return;
×
2092
    }
2093

2094
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2095

2096
    if (prep_res.isErr()) {
×
2097
        return;
×
2098
    }
2099

2100
    auto ps = prep_res.unwrap();
×
2101

2102
    ps.execute();
×
2103
}
2104

2105
lnav::session::regex101::get_result_t
2106
lnav::session::regex101::get_entry(const std::string& format_name,
×
2107
                                   const std::string& regex_name)
2108
{
2109
    constexpr const char* STMT = R"(
×
2110
       SELECT * FROM regex101_entries WHERE
2111
          format_name = ? AND regex_name = ?;
2112
    )";
2113

2114
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2115
    auto_sqlite3 db;
×
2116

2117
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2118
        return error{std::string()};
×
2119
    }
2120

2121
    auto_mem<char, sqlite3_free> errmsg;
×
2122
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2123
        != SQLITE_OK)
×
2124
    {
2125
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2126
        return error{std::string(errmsg)};
×
2127
    }
2128

2129
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2130
    if (prep_res.isErr()) {
×
2131
        return error{prep_res.unwrapErr()};
×
2132
    }
2133

2134
    auto ps = prep_res.unwrap();
×
2135
    return ps.fetch_row<entry>().match(
×
2136
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
2137
            return error{fe.fe_msg};
×
2138
        },
2139
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
2140
            return no_entry{};
×
2141
        },
2142
        [](const entry& en) -> get_result_t { return en; });
×
2143
}
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