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