Coverage Report

Created: 2025-11-11 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/frr/lib/northbound_db.c
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
 * Copyright (C) 2018  NetDEF, Inc.
4
 *                     Renato Westphal
5
 */
6
7
#include <zebra.h>
8
9
#include "libfrr.h"
10
#include "log.h"
11
#include "lib_errors.h"
12
#include "command.h"
13
#include "db.h"
14
#include "northbound.h"
15
#include "northbound_db.h"
16
17
int nb_db_init(void)
18
0
{
19
#ifdef HAVE_CONFIG_ROLLBACKS
20
  /*
21
   * NOTE: the delete_tail SQL trigger is used to implement a ring buffer
22
   * where only the last N transactions are recorded in the configuration
23
   * log.
24
   */
25
  if (db_execute(
26
        "BEGIN TRANSACTION;\n"
27
        "  CREATE TABLE IF NOT EXISTS transactions(\n"
28
        "    client         CHAR(32)             NOT NULL,\n"
29
        "    date           DATETIME             DEFAULT CURRENT_TIMESTAMP,\n"
30
        "    comment        CHAR(80)             ,\n"
31
        "    configuration  TEXT                 NOT NULL\n"
32
        "  );\n"
33
        "  CREATE TRIGGER IF NOT EXISTS delete_tail\n"
34
        "    AFTER INSERT ON transactions\n"
35
        "    FOR EACH ROW\n"
36
        "    BEGIN\n"
37
        "    DELETE\n"
38
        "    FROM\n"
39
        "      transactions\n"
40
        "    WHERE\n"
41
        "      rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
42
        "    END;\n"
43
        "COMMIT;",
44
        NB_DLFT_MAX_CONFIG_ROLLBACKS, NB_DLFT_MAX_CONFIG_ROLLBACKS)
45
      != 0)
46
    return NB_ERR;
47
#endif /* HAVE_CONFIG_ROLLBACKS */
48
49
0
  return NB_OK;
50
0
}
51
52
int nb_db_transaction_save(const struct nb_transaction *transaction,
53
         uint32_t *transaction_id)
54
0
{
55
#ifdef HAVE_CONFIG_ROLLBACKS
56
  struct sqlite3_stmt *ss;
57
  const char *client_name;
58
  char *config_str = NULL;
59
  int ret = NB_ERR;
60
61
  /*
62
   * Use a transaction to ensure consistency between the INSERT and SELECT
63
   * queries.
64
   */
65
  if (db_execute("BEGIN TRANSACTION;") != 0)
66
    return NB_ERR;
67
68
  ss = db_prepare(
69
    "INSERT INTO transactions\n"
70
    "  (client, comment, configuration)\n"
71
    "VALUES\n"
72
    "  (?, ?, ?);");
73
  if (!ss)
74
    goto exit;
75
76
  client_name = nb_client_name(transaction->context.client);
77
  /*
78
   * Always record configurations in the XML format, save the default
79
   * values too, as this covers the case where defaults may change.
80
   */
81
  if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
82
        LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)
83
      != 0)
84
    goto exit;
85
86
  if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name),
87
         transaction->comment, strlen(transaction->comment),
88
         config_str ? config_str : "",
89
         config_str ? strlen(config_str) : 0)
90
      != 0)
91
    goto exit;
92
93
  if (db_run(ss) != SQLITE_OK)
94
    goto exit;
95
96
  db_finalize(&ss);
97
98
  /*
99
   * transaction_id is an optional output parameter that provides the ID
100
   * of the recorded transaction.
101
   */
102
  if (transaction_id) {
103
    ss = db_prepare("SELECT last_insert_rowid();");
104
    if (!ss)
105
      goto exit;
106
107
    if (db_run(ss) != SQLITE_ROW)
108
      goto exit;
109
110
    if (db_loadf(ss, "%i", transaction_id) != 0)
111
      goto exit;
112
113
    db_finalize(&ss);
114
  }
115
116
  if (db_execute("COMMIT;") != 0)
117
    goto exit;
118
119
  ret = NB_OK;
120
121
exit:
122
  if (config_str)
123
    free(config_str);
124
  if (ss)
125
    db_finalize(&ss);
126
  if (ret != NB_OK)
127
    (void)db_execute("ROLLBACK TRANSACTION;");
128
129
  return ret;
130
#else  /* HAVE_CONFIG_ROLLBACKS */
131
0
  return NB_OK;
132
0
#endif /* HAVE_CONFIG_ROLLBACKS */
133
0
}
134
135
struct nb_config *nb_db_transaction_load(uint32_t transaction_id)
136
0
{
137
0
  struct nb_config *config = NULL;
138
#ifdef HAVE_CONFIG_ROLLBACKS
139
  struct lyd_node *dnode;
140
  const char *config_str;
141
  struct sqlite3_stmt *ss;
142
  LY_ERR err;
143
144
  ss = db_prepare(
145
    "SELECT\n"
146
    "  configuration\n"
147
    "FROM\n"
148
    "  transactions\n"
149
    "WHERE\n"
150
    "  rowid=?;");
151
  if (!ss)
152
    return NULL;
153
154
  if (db_bindf(ss, "%d", transaction_id) != 0)
155
    goto exit;
156
157
  if (db_run(ss) != SQLITE_ROW)
158
    goto exit;
159
160
  if (db_loadf(ss, "%s", &config_str) != 0)
161
    goto exit;
162
163
  err = lyd_parse_data_mem(ly_native_ctx, config_str, LYD_XML,
164
         LYD_PARSE_STRICT | LYD_PARSE_NO_STATE,
165
         LYD_VALIDATE_NO_STATE, &dnode);
166
  if (err || !dnode)
167
    flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_data_mem() failed",
168
        __func__);
169
  else
170
    config = nb_config_new(dnode);
171
172
exit:
173
  db_finalize(&ss);
174
#endif /* HAVE_CONFIG_ROLLBACKS */
175
176
0
  return config;
177
0
}
178
179
int nb_db_clear_transactions(unsigned int n_oldest)
180
0
{
181
#ifdef HAVE_CONFIG_ROLLBACKS
182
  /* Delete oldest N entries. */
183
  if (db_execute("DELETE\n"
184
           "FROM\n"
185
           "  transactions\n"
186
           "WHERE\n"
187
           "  ROWID IN (\n"
188
           "    SELECT\n"
189
           "      ROWID\n"
190
           "    FROM\n"
191
           "      transactions\n"
192
           "    ORDER BY ROWID ASC LIMIT %u\n"
193
           "  );",
194
           n_oldest)
195
      != 0)
196
    return NB_ERR;
197
#endif /* HAVE_CONFIG_ROLLBACKS */
198
199
0
  return NB_OK;
200
0
}
201
202
int nb_db_set_max_transactions(unsigned int max)
203
0
{
204
#ifdef HAVE_CONFIG_ROLLBACKS
205
  /*
206
   * Delete old entries if necessary and update the SQL trigger that
207
   * auto-deletes old entries.
208
   */
209
  if (db_execute("BEGIN TRANSACTION;\n"
210
           "  DELETE\n"
211
           "  FROM\n"
212
           "    transactions\n"
213
           "  WHERE\n"
214
           "    ROWID IN (\n"
215
           "      SELECT\n"
216
           "        ROWID\n"
217
           "      FROM\n"
218
           "        transactions\n"
219
           "      ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n"
220
           "    );\n"
221
           "  DROP TRIGGER delete_tail;\n"
222
           "  CREATE TRIGGER delete_tail\n"
223
           "  AFTER INSERT ON transactions\n"
224
           "    FOR EACH ROW\n"
225
           "    BEGIN\n"
226
           "    DELETE\n"
227
           "    FROM\n"
228
           "      transactions\n"
229
           "    WHERE\n"
230
           "      rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
231
           "    END;\n"
232
           "COMMIT;",
233
           max, max, max)
234
      != 0)
235
    return NB_ERR;
236
#endif /* HAVE_CONFIG_ROLLBACKS */
237
238
0
  return NB_OK;
239
0
}
240
241
int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id,
242
              const char *client_name,
243
              const char *date,
244
              const char *comment),
245
             void *arg)
246
0
{
247
#ifdef HAVE_CONFIG_ROLLBACKS
248
  struct sqlite3_stmt *ss;
249
250
  /* Send SQL query and parse the result. */
251
  ss = db_prepare(
252
    "SELECT\n"
253
    "  rowid, client, date, comment\n"
254
    "FROM\n"
255
    "  transactions\n"
256
    "ORDER BY\n"
257
    "  rowid DESC;");
258
  if (!ss)
259
    return NB_ERR;
260
261
  while (db_run(ss) == SQLITE_ROW) {
262
    int transaction_id;
263
    const char *client_name;
264
    const char *date;
265
    const char *comment;
266
    int ret;
267
268
    ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name,
269
             &date, &comment);
270
    if (ret != 0)
271
      continue;
272
273
    (*func)(arg, transaction_id, client_name, date, comment);
274
  }
275
276
  db_finalize(&ss);
277
#endif /* HAVE_CONFIG_ROLLBACKS */
278
279
0
  return NB_OK;
280
0
}