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

tstack / lnav / 23066365063-2835

13 Mar 2026 06:12PM UTC coverage: 68.989% (+0.01%) from 68.979%
23066365063-2835

push

github

tstack
[cmds] add sticky headers

Related to #1385

114 of 146 new or added lines in 6 files covered. (78.08%)

13 existing lines in 4 files now uncovered.

52488 of 76082 relevant lines covered (68.99%)

520548.33 hits per line

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

73.7
/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

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

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

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

140
static constexpr size_t MAX_SESSIONS = 8;
141
static constexpr size_t MAX_SESSION_FILE_COUNT = 256;
142

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

152
    struct timeval sl_time;
153
    intern_string_t sl_format_name;
154
    std::string sl_line_hash;
155
};
156

157
static std::vector<session_line> marked_session_lines;
158
static std::vector<session_line> offset_session_lines;
159

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

169
    if (lf == nullptr) {
24✔
170
        return false;
×
171
    }
172

173
    sqlite3_clear_bindings(stmt);
24✔
174

175
    auto line_iter = lf->begin() + cl;
24✔
176
    auto fr = lf->get_file_range(line_iter, false);
24✔
177
    auto read_result = lf->read_range(fr);
24✔
178

179
    if (read_result.isErr()) {
24✔
180
        return false;
×
181
    }
182

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

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

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

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

218
    int sfi_timestamp;
219
    std::string sfi_id;
220
    std::string sfi_path;
221
};
222

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

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

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

264
    session_info_list.sort();
26✔
265

266
    size_t session_loops = 0;
26✔
267

268
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
26✔
269
        const session_file_info& front = session_info_list.front();
×
270

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

289
    session_info_list.sort();
26✔
290

291
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
26✔
292
        const session_file_info& front = session_info_list.front();
×
293

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

304
void
305
init_session()
650✔
306
{
307
    lnav_data.ld_session_time = time(nullptr);
650✔
308
    lnav_data.ld_session_id.clear();
650✔
309
    session_data.sd_view_states[LNV_LOG].vs_top = -1;
650✔
310
}
650✔
311

312
static std::optional<std::string>
313
compute_session_id()
49✔
314
{
315
    bool has_files = false;
49✔
316
    hasher h;
49✔
317

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

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

340
    return h.to_string();
46✔
341
}
342

343
std::optional<session_pair_t>
344
scan_sessions()
26✔
345
{
346
    static_root_mem<glob_t, globfree> view_info_list;
26✔
347

348
    cleanup_session_data();
26✔
349

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

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

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

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

388
    session_file_names.sort();
25✔
389

390
    while (session_file_names.size() > MAX_SESSIONS) {
25✔
391
        const std::string& name = session_file_names.front().second;
×
392

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

401
    if (session_file_names.empty()) {
25✔
402
        return std::nullopt;
2✔
403
    }
404

405
    return std::make_optional(session_file_names.back());
23✔
406
}
26✔
407

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

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

445
    static auto op = lnav_operation{__FUNCTION__};
26✔
446

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

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

457
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
26✔
458
        return;
×
459
    }
460

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

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

482
        auto netloc_stmt = netloc_prep_res.unwrap();
24✔
483
        bool done = false;
24✔
484

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

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

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

523
        base_content_line = lss.get_file_base_content_line(file_iter);
25✔
524

525
        auto low_line_iter = lf->begin();
25✔
526
        auto high_line_iter = lf->end();
25✔
527

528
        --high_line_iter;
25✔
529

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

540
        date_time_scanner dts;
25✔
541
        bool done = false;
25✔
542
        int64_t last_mark_time = -1;
25✔
543

544
        while (!done) {
89✔
545
            int rc = sqlite3_step(stmt.in());
64✔
546

547
            switch (rc) {
64✔
548
                case SQLITE_OK:
25✔
549
                case SQLITE_DONE:
550
                    done = true;
25✔
551
                    break;
25✔
552

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

571
                    if (last_mark_time == -1) {
39✔
572
                        last_mark_time = mark_time;
24✔
573
                    } else if (last_mark_time != mark_time) {
15✔
574
                        done = true;
×
575
                        continue;
20✔
576
                    }
577

578
                    if (part_name == nullptr && !sticky) {
39✔
579
                        continue;
20✔
580
                    }
581

582
                    if (dts.scan(log_time,
19✔
583
                                 strlen(log_time),
584
                                 nullptr,
585
                                 &log_tm,
586
                                 log_tv)
587
                        == nullptr)
19✔
588
                    {
589
                        log_warning("bad log time: %s", log_time);
×
590
                        continue;
×
591
                    }
592

593
                    auto line_iter = format->lf_time_ordered
19✔
594
                        ? std::lower_bound(lf->begin(), lf->end(), log_tv)
19✔
595
                        : lf->begin();
1✔
596
                    while (line_iter != lf->end()) {
74✔
597
                        const auto line_tv = line_iter->get_timeval();
74✔
598

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

611
                        auto cl = content_line_t(
612
                            std::distance(lf->begin(), line_iter));
42✔
613
                        auto fr = lf->get_file_range(line_iter, false);
21✔
614
                        auto read_result = lf->read_range(fr);
21✔
615

616
                        if (read_result.isErr()) {
21✔
617
                            break;
×
618
                        }
619

620
                        auto sbr = read_result.unwrap();
21✔
621

622
                        auto line_hash
623
                            = hasher()
21✔
624
                                  .update(sbr.get_data(), sbr.length())
21✔
625
                                  .update(cl)
21✔
626
                                  .to_string();
21✔
627

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

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

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

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

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

754
                default: {
×
755
                    const char* errmsg;
756

757
                    errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
758
                    log_error(
×
759
                        "bookmark select error: code %d -- %s", rc, errmsg);
760
                    done = true;
×
761
                } break;
×
762
            }
763
        }
764

765
        sqlite3_reset(stmt.in());
25✔
766
    }
25✔
767
    log_info("END select bookmarks");
24✔
768

769
    log_info("BEGIN select time_offset");
24✔
770
    if (sqlite3_prepare_v2(db.in(), TIME_OFFSET_STMT, -1, stmt.out(), nullptr)
24✔
771
        != SQLITE_OK)
24✔
772
    {
773
        log_error("could not prepare time_offset select statement -- %s",
×
774
                  sqlite3_errmsg(db));
775
        return;
×
776
    }
777

778
    for (auto file_iter = lnav_data.ld_log_source.begin();
24✔
779
         file_iter != lnav_data.ld_log_source.end();
49✔
780
         ++file_iter)
25✔
781
    {
782
        auto lf = (*file_iter)->get_file();
25✔
783
        content_line_t base_content_line;
25✔
784

785
        if (lf == nullptr) {
25✔
786
            continue;
×
787
        }
788
        if (lf->size() == 0) {
25✔
789
            continue;
×
790
        }
791

792
        lss.find(lf->get_filename_as_string().c_str(), base_content_line);
25✔
793

794
        auto low_line_iter = lf->begin();
25✔
795
        auto high_line_iter = lf->end();
25✔
796

797
        --high_line_iter;
25✔
798

799
        if (bind_values(stmt.in(),
50✔
800
                        lnav_data.ld_session_load_time,
801
                        lf->get_content_id(),
25✔
802
                        lf->original_line_time(low_line_iter),
803
                        lf->original_line_time(high_line_iter),
804
                        lf->get_format()->get_name())
50✔
805
            != SQLITE_OK)
25✔
806
        {
807
            return;
×
808
        }
809

810
        date_time_scanner dts;
25✔
811
        auto done = false;
25✔
812
        int64_t last_mark_time = -1;
25✔
813

814
        while (!done) {
74✔
815
            const auto rc = sqlite3_step(stmt.in());
49✔
816

817
            switch (rc) {
49✔
818
                case SQLITE_OK:
25✔
819
                case SQLITE_DONE:
820
                    done = true;
25✔
821
                    break;
25✔
822

823
                case SQLITE_ROW: {
24✔
824
                    const auto* log_time
825
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
24✔
826
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
24✔
827
                    timeval log_tv;
828
                    exttm log_tm;
24✔
829

830
                    if (last_mark_time == -1) {
24✔
831
                        last_mark_time = mark_time;
24✔
832
                    } else if (last_mark_time != mark_time) {
×
833
                        done = true;
×
834
                        continue;
21✔
835
                    }
836

837
                    if (sqlite3_column_type(stmt.in(), 4) == SQLITE_NULL) {
24✔
838
                        continue;
21✔
839
                    }
840

841
                    if (!dts.scan(log_time,
3✔
842
                                  strlen(log_time),
843
                                  nullptr,
844
                                  &log_tm,
845
                                  log_tv))
846
                    {
847
                        continue;
×
848
                    }
849

850
                    auto line_iter
851
                        = lower_bound(lf->begin(), lf->end(), log_tv);
3✔
852
                    while (line_iter != lf->end()) {
6✔
853
                        auto line_tv = line_iter->get_timeval();
6✔
854

855
                        if ((line_tv.tv_sec != log_tv.tv_sec)
6✔
856
                            || (line_tv.tv_usec / 1000
3✔
857
                                != log_tv.tv_usec / 1000))
3✔
858
                        {
859
                            break;
860
                        }
861

862
                        int file_line = std::distance(lf->begin(), line_iter);
3✔
863
                        timeval offset;
864

865
                        offset_session_lines.emplace_back(
3✔
866
                            lf->original_line_time(line_iter),
3✔
867
                            lf->get_format_ptr()->get_name(),
3✔
868
                            lf->get_content_id());
3✔
869
                        offset.tv_sec = sqlite3_column_int64(stmt.in(), 4);
3✔
870
                        offset.tv_usec = sqlite3_column_int64(stmt.in(), 5);
3✔
871
                        lf->adjust_content_time(file_line, offset);
3✔
872

873
                        reload_needed = true;
3✔
874

875
                        ++line_iter;
3✔
876
                    }
877
                    break;
3✔
878
                }
879

880
                default: {
×
881
                    const auto* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
882
                    log_error(
×
883
                        "bookmark select error: code %d -- %s", rc, errmsg);
884
                    done = true;
×
885
                    break;
×
886
                }
887
            }
888
        }
889

890
        sqlite3_reset(stmt.in());
25✔
891
    }
25✔
892
    log_info("END select time_offset");
24✔
893

894
    if (reload_needed) {
24✔
895
        lnav_data.ld_views[LNV_LOG].reload_data();
16✔
896
    }
897
}
34✔
898

899
static int
900
read_files(yajlpp_parse_context* ypc,
22✔
901
           const unsigned char* str,
902
           size_t len,
903
           yajl_string_props_t*)
904
{
905
    return 1;
22✔
906
}
907

908
const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
909
    {level_names[LEVEL_TRACE], LEVEL_TRACE},
910
    {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5},
911
    {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4},
912
    {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3},
913
    {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2},
914
    {level_names[LEVEL_DEBUG], LEVEL_DEBUG},
915
    {level_names[LEVEL_INFO], LEVEL_INFO},
916
    {level_names[LEVEL_STATS], LEVEL_STATS},
917
    {level_names[LEVEL_NOTICE], LEVEL_NOTICE},
918
    {level_names[LEVEL_WARNING], LEVEL_WARNING},
919
    {level_names[LEVEL_ERROR], LEVEL_ERROR},
920
    {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL},
921
    {level_names[LEVEL_FATAL], LEVEL_FATAL},
922

923
    json_path_handler_base::ENUM_TERMINATOR,
924
};
925

926
static const json_path_container view_def_handlers = {
927
    json_path_handler("top_line").for_field(&view_state::vs_top),
928
    json_path_handler("focused_line").for_field(&view_state::vs_selection),
929
    json_path_handler("anchor").for_field(&view_state::vs_anchor),
930
    json_path_handler("search").for_field(&view_state::vs_search),
931
    json_path_handler("word_wrap").for_field(&view_state::vs_word_wrap),
932
    json_path_handler("filtering").for_field(&view_state::vs_filtering),
933
    json_path_handler("min_level")
934
        .with_enum_values(LEVEL_ENUM)
935
        .for_field(&view_state::vs_min_log_level),
936
    json_path_handler("commands#").for_field(&view_state::vs_commands),
937
};
938

939
static const json_path_container view_handlers = {
940
    yajlpp::pattern_property_handler("(?<view_name>[\\w\\-]+)")
941
        .with_obj_provider<view_state, session_data_t>(
942
            +[](const yajlpp_provider_context& ypc, session_data_t* root) {
1,679✔
943
                auto view_index_opt
944
                    = view_from_string(ypc.get_substr("view_name").c_str());
1,679✔
945
                if (view_index_opt) {
1,679✔
946
                    return &root->sd_view_states[view_index_opt.value()];
1,679✔
947
                }
948

949
                log_error("unknown view name: %s",
×
950
                          ypc.get_substr("view_name").c_str());
951
                static view_state dummy;
952
                return &dummy;
×
953
            })
954
        .with_children(view_def_handlers),
955
};
956

957
static const json_path_container file_state_handlers = {
958
    yajlpp::property_handler("visible")
959
        .with_description("Indicates whether the file is visible or not")
960
        .for_field(&file_state::fs_is_visible),
961
};
962

963
static const json_path_container file_states_handlers = {
964
    yajlpp::pattern_property_handler(R"((?<filename>[^/]+))")
965
        .with_description("Map of file names to file state objects")
966
        .with_obj_provider<file_state, session_data_t>(
967
            [](const auto& ypc, session_data_t* root) {
66✔
968
                auto fn = ypc.get_substr("filename");
66✔
969
                return &root->sd_file_states[fn];
132✔
970
            })
66✔
971
        .with_children(file_state_handlers),
972
};
973

974
static const typed_json_path_container<session_data_t> view_info_handlers = {
975
    yajlpp::property_handler("save-time")
976
        .for_field(&session_data_t::sd_save_time),
977
    yajlpp::property_handler("time-offset")
978
        .for_field(&session_data_t::sd_time_offset),
979
    json_path_handler("files#", read_files),
980
    yajlpp::property_handler("file-states").with_children(file_states_handlers),
981
    yajlpp::property_handler("views").with_children(view_handlers),
982
};
983

984
void
985
load_session()
26✔
986
{
987
    static auto op = lnav_operation{"load_session"};
26✔
988

989
    auto op_guard = lnav_opid_guard::internal(op);
26✔
990
    log_info("BEGIN load_session");
26✔
991
    scan_sessions() | [](const auto pair) {
26✔
992
        lnav_data.ld_session_load_time = pair.first.second;
23✔
993
        const auto& view_info_path = pair.second;
23✔
994
        auto view_info_src = intern_string::lookup(view_info_path.string());
23✔
995

996
        auto open_res = lnav::filesystem::open_file(view_info_path, O_RDONLY);
23✔
997
        if (open_res.isErr()) {
23✔
998
            log_error("cannot open session file: %s -- %s",
×
999
                      view_info_path.c_str(),
1000
                      open_res.unwrapErr().c_str());
1001
            return;
×
1002
        }
1003

1004
        auto fd = open_res.unwrap();
23✔
1005
        unsigned char buffer[1024];
1006
        ssize_t rc;
1007

1008
        log_info("loading session file: %s", view_info_path.c_str());
23✔
1009
        auto parser = view_info_handlers.parser_for(view_info_src);
23✔
1010
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
71✔
1011
            auto buf_frag = string_fragment::from_bytes(buffer, rc);
24✔
1012
            auto parse_res = parser.consume(buf_frag);
24✔
1013
            if (parse_res.isErr()) {
24✔
1014
                log_error("failed to load session: %s -- %s",
×
1015
                          view_info_path.c_str(),
1016
                          parse_res.unwrapErr()[0]
1017
                              .to_attr_line()
1018
                              .get_string()
1019
                              .c_str());
1020
                return;
×
1021
            }
1022
        }
1023

1024
        auto complete_res = parser.complete();
23✔
1025
        if (complete_res.isErr()) {
23✔
1026
            log_error("failed to load session: %s -- %s",
×
1027
                      view_info_path.c_str(),
1028
                      complete_res.unwrapErr()[0]
1029
                          .to_attr_line()
1030
                          .get_string()
1031
                          .c_str());
1032
            return;
×
1033
        }
1034
        session_data = complete_res.unwrap();
23✔
1035

1036
        bool log_changes = false;
23✔
1037

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

1041
            if (iter == session_data.sd_file_states.end()) {
24✔
1042
                continue;
2✔
1043
            }
1044

1045
            log_debug("found state for file: %s %d (%s)",
22✔
1046
                      lf->get_content_id().c_str(),
1047
                      iter->second.fs_is_visible,
1048
                      lf->get_filename_as_string().c_str());
1049
            lnav_data.ld_log_source.find_data(lf) |
22✔
1050
                [iter, &log_changes](auto ld) {
44✔
1051
                    if (ld->ld_visible != iter->second.fs_is_visible) {
22✔
1052
                        ld->get_file_ptr()->set_indexing(
1✔
1053
                            iter->second.fs_is_visible);
1✔
1054
                        ld->set_visibility(iter->second.fs_is_visible);
1✔
1055
                        log_changes = true;
1✔
1056
                    }
1057
                };
1058
        }
1059

1060
        if (log_changes) {
23✔
1061
            lnav_data.ld_log_source.text_filters_changed();
1✔
1062
        }
1063
    };
23✔
1064

1065
    lnav::events::publish(lnav_data.ld_db.in(),
26✔
1066
                          lnav::events::session::loaded{});
1067

1068
    log_info("END load_session");
26✔
1069
}
26✔
1070

1071
static void
1072
yajl_writer(void* context, const char* str, size_t len)
8,267✔
1073
{
1074
    FILE* file = (FILE*) context;
8,267✔
1075

1076
    fwrite(str, len, 1, file);
8,267✔
1077
}
8,267✔
1078

1079
static void
1080
save_user_bookmarks(sqlite3* db,
23✔
1081
                    sqlite3_stmt* stmt,
1082
                    bookmark_vector<content_line_t>& user_marks,
1083
                    bookmark_vector<content_line_t>& sticky_marks)
1084
{
1085
    auto& lss = lnav_data.ld_log_source;
23✔
1086

1087
    // Collect all lines that are either user-marked or sticky
1088
    tlx::btree_set<content_line_t> all_lines;
23✔
1089
    for (const auto& cl : user_marks.bv_tree) {
24✔
1090
        all_lines.insert(cl);
1✔
1091
    }
1092
    for (const auto& cl : sticky_marks.bv_tree) {
23✔
NEW
1093
        all_lines.insert(cl);
×
1094
    }
1095

1096
    for (const auto& cl_const : all_lines) {
24✔
1097
        auto cl = cl_const;
1✔
1098
        auto lf = lss.find(cl);
1✔
1099
        if (lf == nullptr) {
1✔
1100
            continue;
×
1101
        }
1102

1103
        sqlite3_clear_bindings(stmt);
1✔
1104

1105
        const auto line_iter = lf->begin() + cl;
1✔
1106
        auto fr = lf->get_file_range(line_iter, false);
1✔
1107
        auto read_result = lf->read_range(fr);
1✔
1108

1109
        if (read_result.isErr()) {
1✔
1110
            continue;
×
1111
        }
1112

1113
        auto line_hash = read_result
1114
                             .map([cl](auto sbr) {
2✔
1115
                                 return hasher()
2✔
1116
                                     .update(sbr.get_data(), sbr.length())
1✔
1117
                                     .update(cl)
1✔
1118
                                     .to_string();
2✔
1119
                             })
1120
                             .unwrap();
1✔
1121

1122
        if (bind_values(stmt,
3✔
1123
                        lf->original_line_time(line_iter),
1124
                        lf->get_format()->get_name(),
2✔
1125
                        line_hash,
1126
                        lnav_data.ld_session_time)
1127
            != SQLITE_OK)
1✔
1128
        {
1129
            continue;
×
1130
        }
1131

1132
        auto is_user = user_marks.bv_tree.find(cl)
1✔
1133
            != user_marks.bv_tree.end();
2✔
1134
        auto is_sticky = sticky_marks.bv_tree.find(cl)
1✔
1135
            != sticky_marks.bv_tree.end();
2✔
1136

1137
        // Use part_name "" for user marks, null for sticky-only
1138
        if (is_user) {
1✔
1139
            if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT)
1✔
1140
                != SQLITE_OK)
1✔
1141
            {
NEW
1142
                log_error("could not bind part name -- %s",
×
1143
                          sqlite3_errmsg(db));
NEW
1144
                return;
×
1145
            }
1146
        } else {
NEW
1147
            sqlite3_bind_null(stmt, 5);
×
1148
        }
1149

1150
        if (sqlite3_bind_int(stmt, 10, is_sticky ? 1 : 0) != SQLITE_OK) {
1✔
NEW
1151
            log_error("could not bind sticky -- %s", sqlite3_errmsg(db));
×
UNCOV
1152
            return;
×
1153
        }
1154

1155
        if (sqlite3_step(stmt) != SQLITE_DONE) {
1✔
1156
            log_error("could not execute bookmark insert statement -- %s",
×
1157
                      sqlite3_errmsg(db));
1158
            return;
×
1159
        }
1160

1161
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
1✔
1162
                                          lf->get_format_ptr()->get_name(),
1✔
1163
                                          line_hash);
1164

1165
        sqlite3_reset(stmt);
1✔
1166
    }
1✔
1167
}
23✔
1168

1169
static void
1170
save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
24✔
1171
{
1172
    for (const auto& bm_pair : lf->get_bookmark_metadata()) {
31✔
1173
        auto cl = content_line_t(bm_pair.first);
7✔
1174
        sqlite3_clear_bindings(stmt);
7✔
1175

1176
        auto line_iter = lf->begin() + cl;
7✔
1177
        auto fr = lf->get_file_range(line_iter, false);
7✔
1178
        auto read_result = lf->read_range(fr);
7✔
1179

1180
        if (read_result.isErr()) {
7✔
1181
            continue;
×
1182
        }
1183

1184
        auto line_hash = read_result
1185
                             .map([cl](auto sbr) {
14✔
1186
                                 return hasher()
14✔
1187
                                     .update(sbr.get_data(), sbr.length())
7✔
1188
                                     .update(cl)
7✔
1189
                                     .to_string();
14✔
1190
                             })
1191
                             .unwrap();
7✔
1192

1193
        if (bind_values(stmt,
21✔
1194
                        lf->original_line_time(line_iter),
1195
                        lf->get_format()->get_name(),
14✔
1196
                        line_hash,
1197
                        lnav_data.ld_session_time)
1198
            != SQLITE_OK)
7✔
1199
        {
1200
            continue;
×
1201
        }
1202

1203
        const auto& line_meta = bm_pair.second;
7✔
1204
        if (line_meta.empty(bookmark_metadata::categories::any)) {
7✔
1205
            continue;
×
1206
        }
1207

1208
        if (sqlite3_bind_text(stmt,
7✔
1209
                              5,
1210
                              line_meta.bm_name.c_str(),
1211
                              line_meta.bm_name.length(),
7✔
1212
                              SQLITE_TRANSIENT)
1213
            != SQLITE_OK)
7✔
1214
        {
1215
            log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1216
            return;
×
1217
        }
1218

1219
        if (sqlite3_bind_text(stmt,
7✔
1220
                              6,
1221
                              line_meta.bm_comment.c_str(),
1222
                              line_meta.bm_comment.length(),
7✔
1223
                              SQLITE_TRANSIENT)
1224
            != SQLITE_OK)
7✔
1225
        {
1226
            log_error("could not bind comment -- %s", sqlite3_errmsg(db));
×
1227
            return;
×
1228
        }
1229

1230
        std::string tags;
7✔
1231

1232
        if (!line_meta.bm_tags.empty()) {
7✔
1233
            yajlpp_gen gen;
4✔
1234

1235
            yajl_gen_config(gen, yajl_gen_beautify, false);
4✔
1236

1237
            {
1238
                yajlpp_array arr(gen);
4✔
1239

1240
                for (const auto& str : line_meta.bm_tags) {
8✔
1241
                    arr.gen(str);
4✔
1242
                }
1243
            }
4✔
1244

1245
            tags = gen.to_string_fragment().to_string();
4✔
1246
        }
4✔
1247

1248
        if (sqlite3_bind_text(
7✔
1249
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
7✔
1250
            != SQLITE_OK)
7✔
1251
        {
1252
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
1253
            return;
×
1254
        }
1255

1256
        if (!line_meta.bm_annotations.la_pairs.empty()) {
7✔
1257
            auto anno_str = logmsg_annotations_handlers.to_string(
1258
                line_meta.bm_annotations);
1✔
1259

1260
            if (sqlite3_bind_text(stmt,
1✔
1261
                                  8,
1262
                                  anno_str.c_str(),
1263
                                  anno_str.length(),
1✔
1264
                                  SQLITE_TRANSIENT)
1265
                != SQLITE_OK)
1✔
1266
            {
1267
                log_error("could not bind annotations -- %s",
×
1268
                          sqlite3_errmsg(db));
1269
                return;
×
1270
            }
1271
        } else {
1✔
1272
            sqlite3_bind_null(stmt, 8);
6✔
1273
        }
1274

1275
        if (line_meta.bm_opid.empty()) {
7✔
1276
            sqlite3_bind_null(stmt, 9);
7✔
1277
        } else {
1278
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1279
        }
1280

1281
        sqlite3_bind_int(stmt, 10, 0);
7✔
1282

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

1289
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
7✔
1290
                                          lf->get_format_ptr()->get_name(),
7✔
1291
                                          line_hash);
1292

1293
        sqlite3_reset(stmt);
7✔
1294
    }
7✔
1295
}
1296

1297
static void
1298
save_time_bookmarks()
23✔
1299
{
1300
    auto_sqlite3 db;
23✔
1301
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
23✔
1302
    auto_mem<char, sqlite3_free> errmsg;
23✔
1303
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
23✔
1304

1305
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
23✔
1306
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1307
        return;
×
1308
    }
1309

1310
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
23✔
1311
        != SQLITE_OK)
23✔
1312
    {
1313
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1314
        return;
×
1315
    }
1316

1317
    if (sqlite3_exec(
23✔
1318
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1319
        != SQLITE_OK)
23✔
1320
    {
1321
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1322
        return;
×
1323
    }
1324

1325
    {
1326
        static const char* UPDATE_NETLOCS_STMT
1327
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1328

1329
        std::set<std::string> netlocs;
23✔
1330

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

1334
        if (sqlite3_prepare_v2(
23✔
1335
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1336
            != SQLITE_OK)
23✔
1337
        {
1338
            log_error("could not prepare recent_netlocs statement -- %s",
×
1339
                      sqlite3_errmsg(db));
1340
            return;
×
1341
        }
1342

1343
        for (const auto& netloc : netlocs) {
23✔
1344
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1345

1346
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1347
                log_error("could not execute bookmark insert statement -- %s",
×
1348
                          sqlite3_errmsg(db));
1349
                return;
×
1350
            }
1351

1352
            sqlite3_reset(stmt.in());
×
1353
        }
1354
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
23✔
1355
    }
23✔
1356

1357
    auto& lss = lnav_data.ld_log_source;
23✔
1358
    auto& bm = lss.get_user_bookmarks();
23✔
1359

1360
    if (sqlite3_prepare_v2(db.in(),
23✔
1361
                           "DELETE FROM bookmarks WHERE "
1362
                           " log_time = ? and log_format = ? and log_hash = ? "
1363
                           " and session_time = ?",
1364
                           -1,
1365
                           stmt.out(),
1366
                           nullptr)
1367
        != SQLITE_OK)
23✔
1368
    {
1369
        log_error("could not prepare bookmark delete statement -- %s",
×
1370
                  sqlite3_errmsg(db));
1371
        return;
×
1372
    }
1373

1374
    for (auto& marked_session_line : marked_session_lines) {
25✔
1375
        sqlite3_clear_bindings(stmt.in());
2✔
1376

1377
        if (bind_values(stmt,
4✔
1378
                        marked_session_line.sl_time,
1379
                        marked_session_line.sl_format_name,
1380
                        marked_session_line.sl_line_hash,
2✔
1381
                        lnav_data.ld_session_time)
1382
            != SQLITE_OK)
2✔
1383
        {
1384
            continue;
×
1385
        }
1386

1387
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1388
            log_error("could not execute bookmark insert statement -- %s",
×
1389
                      sqlite3_errmsg(db));
1390
            return;
×
1391
        }
1392

1393
        sqlite3_reset(stmt.in());
2✔
1394
    }
1395

1396
    marked_session_lines.clear();
23✔
1397

1398
    if (sqlite3_prepare_v2(db.in(),
23✔
1399
                           "REPLACE INTO bookmarks"
1400
                           " (log_time, log_format, log_hash, session_time, "
1401
                           "part_name, comment, tags, annotations, log_opid,"
1402
                           " sticky)"
1403
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1404
                           -1,
1405
                           stmt.out(),
1406
                           nullptr)
1407
        != SQLITE_OK)
23✔
1408
    {
1409
        log_error("could not prepare bookmark replace statement -- %s",
×
1410
                  sqlite3_errmsg(db));
1411
        return;
×
1412
    }
1413

1414
    {
1415
        for (auto file_iter = lnav_data.ld_log_source.begin();
23✔
1416
             file_iter != lnav_data.ld_log_source.end();
47✔
1417
             ++file_iter)
24✔
1418
        {
1419
            auto lf = (*file_iter)->get_file();
24✔
1420

1421
            if (lf == nullptr) {
24✔
1422
                continue;
×
1423
            }
1424
            if (lf->size() == 0) {
24✔
1425
                continue;
×
1426
            }
1427

1428
            content_line_t base_content_line;
24✔
1429
            base_content_line = lss.get_file_base_content_line(file_iter);
24✔
1430
            base_content_line
1431
                = content_line_t(base_content_line + lf->size() - 1);
24✔
1432

1433
            if (!bind_line(db.in(),
24✔
1434
                           stmt.in(),
1435
                           base_content_line,
1436
                           lnav_data.ld_session_time))
24✔
1437
            {
1438
                continue;
×
1439
            }
1440

1441
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
24✔
1442
                log_error("could not bind log hash -- %s",
×
1443
                          sqlite3_errmsg(db.in()));
1444
                return;
×
1445
            }
1446

1447
            sqlite3_bind_int(stmt.in(), 10, 0);
24✔
1448

1449
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
24✔
1450
                log_error("could not execute bookmark insert statement -- %s",
×
1451
                          sqlite3_errmsg(db));
1452
                return;
×
1453
            }
1454

1455
            sqlite3_reset(stmt.in());
24✔
1456
        }
24✔
1457
    }
1458

1459
    save_user_bookmarks(db.in(),
23✔
1460
                        stmt.in(),
1461
                        bm[&textview_curses::BM_USER],
1462
                        bm[&textview_curses::BM_STICKY]);
1463
    for (const auto& ldd : lss) {
47✔
1464
        auto* lf = ldd->get_file_ptr();
24✔
1465
        if (lf == nullptr) {
24✔
1466
            continue;
×
1467
        }
1468

1469
        save_meta_bookmarks(db.in(), stmt.in(), lf);
24✔
1470
    }
1471

1472
    if (sqlite3_prepare_v2(db.in(),
23✔
1473
                           "DELETE FROM time_offset WHERE "
1474
                           " log_time = ? and log_format = ? and log_hash = ? "
1475
                           " and session_time = ?",
1476
                           -1,
1477
                           stmt.out(),
1478
                           NULL)
1479
        != SQLITE_OK)
23✔
1480
    {
1481
        log_error("could not prepare time_offset delete statement -- %s",
×
1482
                  sqlite3_errmsg(db));
1483
        return;
×
1484
    }
1485

1486
    for (auto& offset_session_line : offset_session_lines) {
24✔
1487
        sqlite3_clear_bindings(stmt.in());
1✔
1488

1489
        if (bind_values(stmt,
2✔
1490
                        offset_session_line.sl_time,
1491
                        offset_session_line.sl_format_name,
1492
                        offset_session_line.sl_line_hash,
1✔
1493
                        lnav_data.ld_session_time)
1494
            != SQLITE_OK)
1✔
1495
        {
1496
            continue;
×
1497
        }
1498

1499
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1500
            log_error("could not execute bookmark insert statement -- %s",
×
1501
                      sqlite3_errmsg(db));
1502
            return;
×
1503
        }
1504

1505
        sqlite3_reset(stmt.in());
1✔
1506
    }
1507

1508
    offset_session_lines.clear();
23✔
1509

1510
    if (sqlite3_prepare_v2(db.in(),
23✔
1511
                           "REPLACE INTO time_offset"
1512
                           " (log_time, log_format, log_hash, session_time, "
1513
                           "offset_sec, offset_usec)"
1514
                           " VALUES (?, ?, ?, ?, ?, ?)",
1515
                           -1,
1516
                           stmt.out(),
1517
                           nullptr)
1518
        != SQLITE_OK)
23✔
1519
    {
1520
        log_error("could not prepare time_offset replace statement -- %s",
×
1521
                  sqlite3_errmsg(db));
1522
        return;
×
1523
    }
1524

1525
    {
1526
        for (auto file_iter = lnav_data.ld_log_source.begin();
23✔
1527
             file_iter != lnav_data.ld_log_source.end();
47✔
1528
             ++file_iter)
24✔
1529
        {
1530
            auto lf = (*file_iter)->get_file();
24✔
1531
            if (lf == nullptr) {
24✔
1532
                continue;
×
1533
            }
1534
            if (lf->size() == 0) {
24✔
1535
                continue;
×
1536
            }
1537

1538
            if (bind_values(stmt,
72✔
1539
                            lf->original_line_time(lf->begin()),
1540
                            lf->get_format()->get_name(),
48✔
1541
                            lf->get_content_id(),
24✔
1542
                            lnav_data.ld_session_time)
1543
                != SQLITE_OK)
24✔
1544
            {
1545
                continue;
×
1546
            }
1547

1548
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
24✔
1549
                log_error("could not bind log hash -- %s",
×
1550
                          sqlite3_errmsg(db.in()));
1551
                return;
×
1552
            }
1553

1554
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
24✔
1555
                log_error("could not bind log hash -- %s",
×
1556
                          sqlite3_errmsg(db.in()));
1557
                return;
×
1558
            }
1559

1560
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
24✔
1561
                log_error("could not execute bookmark insert statement -- %s",
×
1562
                          sqlite3_errmsg(db));
1563
                return;
×
1564
            }
1565

1566
            sqlite3_reset(stmt.in());
24✔
1567
        }
24✔
1568
    }
1569

1570
    for (const auto& ls : lss) {
47✔
1571
        if (ls->get_file() == nullptr) {
24✔
1572
            continue;
21✔
1573
        }
1574

1575
        const auto lf = ls->get_file();
24✔
1576
        if (!lf->is_time_adjusted()) {
24✔
1577
            continue;
21✔
1578
        }
1579

1580
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
1581
        auto offset = lf->get_time_offset();
3✔
1582

1583
        bind_values(stmt.in(),
6✔
1584
                    lf->original_line_time(line_iter),
1585
                    lf->get_format()->get_name(),
6✔
1586
                    lf->get_content_id(),
3✔
1587
                    lnav_data.ld_session_time,
1588
                    offset.tv_sec,
1589
                    offset.tv_usec);
1590

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

1597
        sqlite3_reset(stmt.in());
3✔
1598
    }
24✔
1599

1600
    log_info("saved %d bookmarks", sqlite3_changes(db.in()));
23✔
1601

1602
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
23✔
1603
        != SQLITE_OK)
23✔
1604
    {
1605
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1606
        return;
×
1607
    }
1608

1609
    if (sqlite3_exec(db.in(), BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
23✔
1610
        != SQLITE_OK)
23✔
1611
    {
1612
        log_error("unable to delete old bookmarks -- %s", errmsg.in());
×
1613
        return;
×
1614
    }
1615
    auto bookmark_changes = sqlite3_changes(db.in());
23✔
1616
    if (bookmark_changes > 0) {
23✔
1617
        log_info("deleted %d old bookmarks", bookmark_changes);
×
1618
    }
1619

1620
    if (sqlite3_exec(db.in(), NETLOC_LRU_STMT, nullptr, nullptr, errmsg.out())
23✔
1621
        != SQLITE_OK)
23✔
1622
    {
1623
        log_error("unable to delete old netlocs -- %s", errmsg.in());
×
1624
        return;
×
1625
    }
1626
    auto netloc_changes = sqlite3_changes(db.in());
23✔
1627
    if (netloc_changes > 0) {
23✔
1628
        log_info("deleted %d old netlocs", netloc_changes);
×
1629
    }
1630
}
23✔
1631

1632
static void
1633
save_session_with_id(const std::string& session_id)
22✔
1634
{
1635
    auto_mem<FILE> file(fclose);
22✔
1636
    yajl_gen handle = nullptr;
22✔
1637

1638
    /* TODO: save the last search query */
1639

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

1642
    auto view_base_name
1643
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
44✔
1644
                      session_id,
1645
                      lnav_data.ld_session_time,
1646
                      getppid());
22✔
1647
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
22✔
1648
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
22✔
1649

1650
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
22✔
1651
        perror("Unable to open session file");
×
1652
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
22✔
1653
        perror("Unable to create yajl_gen object");
×
1654
    } else {
1655
        yajl_gen_config(
22✔
1656
            handle, yajl_gen_print_callback, yajl_writer, file.in());
1657

1658
        {
1659
            yajlpp_map root_map(handle);
22✔
1660

1661
            root_map.gen("save-time");
22✔
1662
            root_map.gen((long long) time(nullptr));
22✔
1663

1664
            root_map.gen("time-offset");
22✔
1665
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
22✔
1666

1667
            root_map.gen("files");
22✔
1668

1669
            {
1670
                yajlpp_array file_list(handle);
22✔
1671

1672
                for (auto& ld_file_name :
22✔
1673
                     lnav_data.ld_active_files.fc_file_names)
68✔
1674
                {
1675
                    file_list.gen(ld_file_name.first);
24✔
1676
                }
1677
            }
22✔
1678

1679
            root_map.gen("file-states");
22✔
1680

1681
            {
1682
                yajlpp_map file_states(handle);
22✔
1683

1684
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
46✔
1685
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
24✔
1686

1687
                    file_states.gen(lf->get_filename().native());
24✔
1688

1689
                    {
1690
                        yajlpp_map file_state(handle);
24✔
1691

1692
                        file_state.gen("visible");
24✔
1693
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
24✔
1694
                    }
24✔
1695
                }
1696
            }
22✔
1697

1698
            root_map.gen("views");
22✔
1699

1700
            {
1701
                yajlpp_map top_view_map(handle);
22✔
1702

1703
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
220✔
1704
                    auto& tc = lnav_data.ld_views[lpc];
198✔
1705
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
198✔
1706

1707
                    top_view_map.gen(lnav_view_strings[lpc]);
198✔
1708

1709
                    yajlpp_map view_map(handle);
198✔
1710

1711
                    view_map.gen("top_line");
198✔
1712

1713
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
198✔
1714
                        view_map.gen(-1LL);
185✔
1715
                    } else {
1716
                        view_map.gen((long long) tc.get_top());
13✔
1717
                    }
1718

1719
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
247✔
1720
                        && tc.get_inner_height() > 0_vl
2✔
1721
                        && tc.get_selection() != tc.get_inner_height() - 1)
247✔
1722
                    {
1723
                        auto sel = tc.get_selection();
1✔
1724
                        if (sel) {
1✔
1725
                            view_map.gen("focused_line");
1✔
1726
                            view_map.gen((long long) sel.value());
1✔
1727

1728
                            if (ta != nullptr) {
1✔
1729
                                auto anchor_opt
1730
                                    = ta->anchor_for_row(sel.value());
1✔
1731
                                if (anchor_opt) {
1✔
1732
                                    view_map.gen("anchor");
1✔
1733
                                    view_map.gen(anchor_opt.value());
1✔
1734
                                }
1735
                            }
1✔
1736
                        }
1737
                    }
1738

1739
                    view_map.gen("search");
198✔
1740
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
198✔
1741

1742
                    view_map.gen("word_wrap");
198✔
1743
                    view_map.gen(tc.get_word_wrap());
198✔
1744

1745
                    auto* tss = tc.get_sub_source();
198✔
1746
                    if (tss == nullptr) {
198✔
1747
                        continue;
44✔
1748
                    }
1749

1750
                    view_map.gen("filtering");
154✔
1751
                    view_map.gen(tss->tss_apply_filters);
154✔
1752

1753
                    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
154✔
1754
                        view_map.gen("min_level");
×
1755
                        view_map.gen(level_names[tss->get_min_log_level()]);
×
1756
                    }
1757

1758
                    view_map.gen("commands");
154✔
1759
                    yajlpp_array cmd_array(handle);
154✔
1760

1761
                    tss->add_commands_for_session(
154✔
1762
                        [&](auto& cmd) { cmd_array.gen(cmd); });
165✔
1763
                }
198✔
1764
            }
22✔
1765
        }
22✔
1766

1767
        yajl_gen_clear(handle);
22✔
1768
        yajl_gen_free(handle);
22✔
1769

1770
        fclose(file.release());
22✔
1771

1772
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
22✔
1773

1774
        log_info("Saved session: %s", view_file_name.c_str());
22✔
1775
    }
1776
}
22✔
1777

1778
void
1779
save_session()
23✔
1780
{
1781
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
23✔
1782
        log_info("secure mode is enabled, not saving session");
×
1783
        return;
×
1784
    }
1785

1786
    static auto op = lnav_operation{"save_session"};
23✔
1787

1788
    auto op_guard = lnav_opid_guard::internal(op);
23✔
1789

1790
    log_debug("BEGIN save_session");
23✔
1791
    save_time_bookmarks();
23✔
1792

1793
    const auto opt_session_id = compute_session_id();
23✔
1794
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
44✔
1795
    for (const auto& pair : lnav_data.ld_session_id) {
27✔
1796
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
1797
            continue;
3✔
1798
        }
1799
        save_session_with_id(pair.first);
1✔
1800
    }
1801
    log_debug("END save_session");
23✔
1802
}
23✔
1803

1804
void
1805
reset_session()
6✔
1806
{
1807
    log_info("reset session: time=%lld", lnav_data.ld_session_time);
6✔
1808

1809
    save_session();
6✔
1810

1811
    lnav_data.ld_session_time = time(nullptr);
6✔
1812
    session_data.sd_file_states.clear();
6✔
1813

1814
    for (auto& tc : lnav_data.ld_views) {
60✔
1815
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
1816
        auto& hmap = tc.get_highlights();
54✔
1817
        auto hl_iter = hmap.begin();
54✔
1818

1819
        while (hl_iter != hmap.end()) {
552✔
1820
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
1821
                ++hl_iter;
498✔
1822
            } else {
1823
                hmap.erase(hl_iter++);
×
1824
            }
1825
        }
1826

1827
        if (ttt != nullptr) {
54✔
1828
            ttt->clear_min_max_row_times();
36✔
1829
        }
1830
    }
1831

1832
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
1833
        lf->reset_state();
8✔
1834
    }
1835

1836
    // XXX clean this up
1837
    lnav_data.ld_log_source.lss_highlighters.clear();
6✔
1838
    lnav_data.ld_log_source.set_force_rebuild();
6✔
1839
    lnav_data.ld_log_source.set_marked_only(false);
6✔
1840
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
1841
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
1842
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
1843
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
1844
    rebuild_indexes(std::nullopt);
6✔
1845

1846
    lnav_data.ld_db_row_source.reset_user_state();
6✔
1847

1848
    auto* tss = static_cast<timeline_source*>(
1849
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
6✔
1850
    if (tss != nullptr) {
6✔
1851
        tss->ts_hidden_row_types.clear();
6✔
1852
    }
1853

1854
    for (auto& tc : lnav_data.ld_views) {
60✔
1855
        text_sub_source* tss = tc.get_sub_source();
54✔
1856

1857
        if (tss == nullptr) {
54✔
1858
            continue;
12✔
1859
        }
1860
        tss->get_filters().clear_filters();
42✔
1861
        tss->tss_apply_filters = true;
42✔
1862
        tss->text_filters_changed();
42✔
1863
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
1864
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
1865
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
1866
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
1867
        tc.get_bookmarks()[&textview_curses::BM_STICKY].clear();
42✔
1868
        tc.reload_data();
42✔
1869
    }
1870

1871
    lnav_data.ld_filter_view.reload_data();
6✔
1872
    lnav_data.ld_files_view.reload_data();
6✔
1873
    for (const auto& format : log_format::get_root_formats()) {
462✔
1874
        auto* elf = dynamic_cast<external_log_format*>(format.get());
456✔
1875

1876
        if (elf == nullptr) {
456✔
1877
            continue;
30✔
1878
        }
1879

1880
        bool changed = false;
426✔
1881
        for (const auto& vd : elf->elf_value_defs) {
6,264✔
1882
            if (vd.second->vd_meta.lvm_user_hidden) {
5,838✔
1883
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
1884
                changed = true;
×
1885
            }
1886
        }
1887
        if (changed) {
426✔
1888
            elf->elf_value_defs_state->vds_generation += 1;
×
1889
        }
1890
    }
1891
}
6✔
1892

1893
void
1894
lnav::session::apply_view_commands()
25✔
1895
{
1896
    static auto op = lnav_operation{__FUNCTION__};
25✔
1897

1898
    auto op_guard = lnav_opid_guard::internal(op);
25✔
1899

1900
    log_debug("applying view commands");
25✔
1901
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
250✔
1902
        const auto& vs = session_data.sd_view_states[view_index];
225✔
1903
        auto& tview = lnav_data.ld_views[view_index];
225✔
1904

1905
        lnav::set::small<std::string> curr_cmds;
225✔
1906
        auto* tss = tview.get_sub_source();
225✔
1907
        if (tview.get_sub_source() != nullptr) {
225✔
1908
            tss->tss_apply_filters = vs.vs_filtering;
175✔
1909
            if (vs.vs_min_log_level) {
175✔
1910
                tss->set_min_log_level(vs.vs_min_log_level.value());
×
1911
            }
1912
            tss->add_commands_for_session([&](auto& cmd) {
175✔
1913
                auto cmd_sf = string_fragment::from_str(cmd)
5✔
1914
                                  .split_when(string_fragment::tag1{' '})
5✔
1915
                                  .first;
1916
                curr_cmds.insert(cmd_sf.to_string());
5✔
1917
            });
5✔
1918
        }
1919
        for (const auto& cmdline : vs.vs_commands) {
232✔
1920
            auto cmdline_sf = string_fragment::from_str(cmdline);
7✔
1921
            auto active = ensure_view(&tview);
7✔
1922
            auto [cmd_sf, _cmdline_rem]
7✔
1923
                = cmdline_sf.split_when(string_fragment::tag1{' '});
7✔
1924
            if (curr_cmds.contains(cmd_sf.to_string())) {
7✔
1925
                log_debug("view %s command '%.*s' already active",
3✔
1926
                          tview.get_title().c_str(),
1927
                          cmd_sf.length(),
1928
                          cmd_sf.data());
1929
                continue;
3✔
1930
            }
1931
            auto exec_cmd_res
1932
                = execute_command(lnav_data.ld_exec_context, cmdline);
4✔
1933
            if (exec_cmd_res.isOk()) {
4✔
1934
                log_info("Result: %s", exec_cmd_res.unwrap().c_str());
4✔
1935
            } else {
1936
                log_error("Result: %s",
×
1937
                          exec_cmd_res.unwrapErr()
1938
                              .to_attr_line()
1939
                              .get_string()
1940
                              .c_str());
1941
            }
1942
            if (!active) {
4✔
1943
                lnav_data.ld_view_stack.pop_back();
×
1944
                lnav_data.ld_view_stack.top() | [](auto* tc) {
×
1945
                    // XXX
1946
                    if (tc == &lnav_data.ld_views[LNV_TIMELINE]) {
×
1947
                        auto tss = tc->get_sub_source();
×
1948
                        tss->text_filters_changed();
×
1949
                        tc->reload_data();
×
1950
                    }
1951
                };
1952
            }
1953
        }
4✔
1954
    }
225✔
1955
}
25✔
1956

1957
void
1958
lnav::session::restore_view_states()
25✔
1959
{
1960
    static auto op = lnav_operation{__FUNCTION__};
25✔
1961

1962
    auto op_guard = lnav_opid_guard::internal(op);
25✔
1963

1964
    log_debug("restoring view states");
25✔
1965
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
250✔
1966
        const auto& vs = session_data.sd_view_states[view_index];
225✔
1967
        auto& tview = lnav_data.ld_views[view_index];
225✔
1968
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
225✔
1969

1970
        if (!vs.vs_search.empty()) {
225✔
1971
            tview.execute_search(vs.vs_search);
×
1972
            tview.set_follow_search_for(-1, {});
×
1973
        }
1974
        tview.set_word_wrap(vs.vs_word_wrap);
225✔
1975
        auto has_loc = tview.get_selection().has_value();
225✔
1976
        if (!has_loc && vs.vs_top >= 0
59✔
1977
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
284✔
1978
                || tview.get_top() == tview.get_top_for_last_row()))
225✔
1979
        {
1980
            log_info("restoring %s view top: %d",
16✔
1981
                     lnav_view_strings[view_index].data(),
1982
                     (int) vs.vs_top);
1983
            tview.set_top(vis_line_t(vs.vs_top), true);
16✔
1984
        }
1985
        if (!has_loc && vs.vs_selection) {
225✔
1986
            log_info("restoring %s view selection: %d",
×
1987
                     lnav_view_strings[view_index].data(),
1988
                     (int) vs.vs_selection.value_or(-1_vl));
1989
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
×
1990
        }
1991
        auto sel = tview.get_selection();
225✔
1992
        if (!has_loc && sel && ta != nullptr && vs.vs_anchor
59✔
1993
            && !vs.vs_anchor->empty())
284✔
1994
        {
1995
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
1996

1997
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
1998
                log_info("%s view anchor mismatch %s != %s",
×
1999
                         lnav_view_strings[view_index].data(),
2000
                         curr_anchor.value_or("").c_str(),
2001
                         vs.vs_anchor.value().c_str());
2002

2003
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
2004
                if (row_opt) {
×
2005
                    tview.set_selection(row_opt.value());
×
2006
                }
2007
            }
2008
        }
2009
        sel = tview.get_selection();
225✔
2010
        if (!sel) {
225✔
2011
            auto height = tview.get_inner_height();
59✔
2012
            if (height == 0) {
59✔
2013
            } else if (view_index == LNV_TEXT) {
1✔
2014
                auto lf = lnav_data.ld_text_source.current_file();
×
2015
                if (lf != nullptr) {
×
2016
                    switch (lf->get_text_format().value_or(
×
2017
                        text_format_t::TF_BINARY))
×
2018
                    {
2019
                        case text_format_t::TF_PLAINTEXT:
×
2020
                        case text_format_t::TF_LOG: {
2021
                            if (height > 0_vl) {
×
2022
                                tview.set_selection(height - 1_vl);
×
2023
                            }
2024
                            break;
×
2025
                        }
2026
                        default:
×
2027
                            tview.set_selection(0_vl);
×
2028
                            break;
×
2029
                    }
2030
                }
2031
            } else if (view_index == LNV_LOG) {
1✔
2032
                tview.set_selection(height - 1_vl);
×
2033
            } else {
2034
                tview.set_selection(0_vl);
1✔
2035
            }
2036
        }
2037
        log_info("%s view actual top/selection: %d/%d",
225✔
2038
                 lnav_view_strings[view_index].data(),
2039
                 (int) tview.get_top(),
2040
                 (int) tview.get_selection().value_or(-1_vl));
2041
    }
2042
}
25✔
2043

2044
void
2045
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
2046
{
2047
    constexpr const char* STMT = R"(
×
2048
       INSERT INTO regex101_entries
2049
          (format_name, regex_name, permalink, delete_code)
2050
          VALUES (?, ?, ?, ?);
2051
)";
2052

2053
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2054
    auto_sqlite3 db;
×
2055

2056
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2057
        return;
×
2058
    }
2059

2060
    auto_mem<char, sqlite3_free> errmsg;
×
2061
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2062
        != SQLITE_OK)
×
2063
    {
2064
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2065
        return;
×
2066
    }
2067

2068
    auto prep_res = prepare_stmt(db.in(),
2069
                                 STMT,
2070
                                 ei.re_format_name,
×
2071
                                 ei.re_regex_name,
×
2072
                                 ei.re_permalink,
×
2073
                                 ei.re_delete_code);
×
2074

2075
    if (prep_res.isErr()) {
×
2076
        return;
×
2077
    }
2078

2079
    auto ps = prep_res.unwrap();
×
2080

2081
    ps.execute();
×
2082
}
2083

2084
template<>
2085
struct from_sqlite<lnav::session::regex101::entry> {
2086
    inline lnav::session::regex101::entry operator()(int argc,
2087
                                                     sqlite3_value** argv,
2088
                                                     int argi)
2089
    {
2090
        return {
2091
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
2092
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
2093
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
2094
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
2095
        };
2096
    }
2097
};
2098

2099
Result<std::vector<lnav::session::regex101::entry>, std::string>
2100
lnav::session::regex101::get_entries()
×
2101
{
2102
    constexpr const char* STMT = R"(
×
2103
       SELECT * FROM regex101_entries;
2104
)";
2105

2106
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2107
    auto_sqlite3 db;
×
2108

2109
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2110
        return Err(std::string());
×
2111
    }
2112

2113
    auto_mem<char, sqlite3_free> errmsg;
×
2114
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2115
        != SQLITE_OK)
×
2116
    {
2117
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2118
        return Err(std::string(errmsg));
×
2119
    }
2120

2121
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
2122
    bool done = false;
×
2123
    std::vector<entry> retval;
×
2124

2125
    while (!done) {
×
2126
        auto fetch_res = ps.fetch_row<entry>();
×
2127

2128
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
2129
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2130
        }
2131

2132
        fetch_res.match(
×
2133
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
2134
            [](const prepared_stmt::fetch_error&) {},
×
2135
            [&retval](entry en) { retval.emplace_back(en); });
×
2136
    }
2137
    return Ok(retval);
×
2138
}
2139

2140
void
2141
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2142
                                      const std::string& regex_name)
2143
{
2144
    constexpr const char* STMT = R"(
×
2145
       DELETE FROM regex101_entries WHERE
2146
          format_name = ? AND regex_name = ?;
2147
)";
2148

2149
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2150
    auto_sqlite3 db;
×
2151

2152
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2153
        return;
×
2154
    }
2155

2156
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2157

2158
    if (prep_res.isErr()) {
×
2159
        return;
×
2160
    }
2161

2162
    auto ps = prep_res.unwrap();
×
2163

2164
    ps.execute();
×
2165
}
2166

2167
lnav::session::regex101::get_result_t
2168
lnav::session::regex101::get_entry(const std::string& format_name,
×
2169
                                   const std::string& regex_name)
2170
{
2171
    constexpr const char* STMT = R"(
×
2172
       SELECT * FROM regex101_entries WHERE
2173
          format_name = ? AND regex_name = ?;
2174
    )";
2175

2176
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2177
    auto_sqlite3 db;
×
2178

2179
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2180
        return error{std::string()};
×
2181
    }
2182

2183
    auto_mem<char, sqlite3_free> errmsg;
×
2184
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2185
        != SQLITE_OK)
×
2186
    {
2187
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2188
        return error{std::string(errmsg)};
×
2189
    }
2190

2191
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2192
    if (prep_res.isErr()) {
×
2193
        return error{prep_res.unwrapErr()};
×
2194
    }
2195

2196
    auto ps = prep_res.unwrap();
×
2197
    return ps.fetch_row<entry>().match(
×
2198
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
2199
            return error{fe.fe_msg};
×
2200
        },
2201
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
2202
            return no_entry{};
×
2203
        },
2204
        [](const entry& en) -> get_result_t { return en; });
×
2205
}
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