1 /*
2 *
3 ***** BEGIN LICENSE BLOCK *****
4
5 Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
6
7 This file is part of CLIXON.
8
9 Licensed under the Apache License, Version 2.0 (the "License");
10 you may not use this file except in compliance with the License.
11 You may obtain a copy of the License at
12
13 http://www.apache.org/licenses/LICENSE-2.0
14
15 Unless required by applicable law or agreed to in writing, software
16 distributed under the License is distributed on an "AS IS" BASIS,
17 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 See the License for the specific language governing permissions and
19 limitations under the License.
20
21 Alternatively, the contents of this file may be used under the terms of
22 the GNU General Public License Version 3 or later (the "GPL"),
23 in which case the provisions of the GPL are applicable instead
24 of those above. If you wish to allow use of your version of this file only
25 under the terms of the GPL, and not to allow others to
26 use your version of this file under the terms of Apache License version 2,
27 indicate your decision by deleting the provisions above and replace them with
28 the notice and other provisions required by the GPL. If you do not delete
29 the provisions above, a recipient may use your version of this file under
30 the terms of any one of the Apache License version 2 or the GPL.
31
32 ***** END LICENSE BLOCK *****
33
34 * Clixon Datastore (XMLDB)
35 * Saves Clixon data as clear-text XML (or JSON)
36 */
37
38 #ifdef HAVE_CONFIG_H
39 #include "clixon_config.h" /* generated by config & autoconf */
40 #endif
41
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <errno.h>
46 #include <signal.h>
47 #include <syslog.h>
48 #include <dlfcn.h>
49 #include <fcntl.h>
50 #include <time.h>
51 #include <signal.h>
52 #include <libgen.h>
53 #include <dirent.h>
54 #include <sys/stat.h>
55 #include <sys/time.h>
56 #include <sys/wait.h>
57 #include <sys/param.h>
58
59 /* cligen */
60 #include <cligen/cligen.h>
61
62 /* clixon */
63 #include "clixon_err.h"
64 #include "clixon_log.h"
65 #include "clixon_queue.h"
66 #include "clixon_hash.h"
67 #include "clixon_handle.h"
68 #include "clixon_string.h"
69 #include "clixon_file.h"
70 #include "clixon_yang.h"
71 #include "clixon_xml.h"
72 #include "clixon_yang_module.h"
73 #include "clixon_plugin.h"
74 #include "clixon_options.h"
75 #include "clixon_data.h"
76 #include "clixon_datastore.h"
77 #include "clixon_datastore_write.h"
78 #include "clixon_datastore_read.h"
79
80
81 /*! Translate from symbolic database name to actual filename in file-system
82 * @param[in] th text handle handle
83 * @param[in] db Symbolic database name, eg "candidate", "running"
84 * @param[out] filename Filename. Unallocate after use with free()
85 * @retval 0 OK
86 * @retval -1 Error
87 * @note Could need a way to extend which databases exists, eg to register new.
88 * The currently allowed databases are:
89 * candidate, tmp, running, result
90 * The filename reside in CLICON_XMLDB_DIR option
91 */
92 int
xmldb_db2file(clicon_handle h,const char * db,char ** filename)93 xmldb_db2file(clicon_handle h,
94 const char *db,
95 char **filename)
96 {
97 int retval = -1;
98 cbuf *cb = NULL;
99 char *dir;
100
101 if ((cb = cbuf_new()) == NULL){
102 clicon_err(OE_XML, errno, "cbuf_new");
103 goto done;
104 }
105 if ((dir = clicon_xmldb_dir(h)) == NULL){
106 clicon_err(OE_XML, errno, "dbdir not set");
107 goto done;
108 }
109 cprintf(cb, "%s/%s_db", dir, db);
110 if ((*filename = strdup4(cbuf_get(cb))) == NULL){
111 clicon_err(OE_UNIX, errno, "strdup");
112 goto done;
113 }
114 retval = 0;
115 done:
116 if (cb)
117 cbuf_free(cb);
118 return retval;
119 }
120
121 /*! Ensure database name is correct
122 * @param[in] db Name of database
123 * @retval 0 OK
124 * @retval -1 Failed validate, xret set to error
125 * XXX why is this function here? should be handled by netconf yang validation
126 */
127 int
xmldb_validate_db(const char * db)128 xmldb_validate_db(const char *db)
129 {
130 if (strcmp(db, "running") != 0 &&
131 strcmp(db, "candidate") != 0 &&
132 strcmp(db, "startup") != 0 &&
133 strcmp(db, "tmp") != 0)
134 return -1;
135 return 0;
136 }
137
138 /*! Connect to a datastore plugin, allocate resources to be used in API calls
139 * @param[in] h Clicon handle
140 * @retval 0 OK
141 * @retval -1 Error
142 */
143 int
xmldb_connect(clicon_handle h)144 xmldb_connect(clicon_handle h)
145 {
146 return 0;
147 }
148
149 /*! Disconnect from a datastore plugin and deallocate resources
150 * @param[in] handle Disconect and deallocate from this handle
151 * @retval 0 OK
152 * @retval -1 Error
153 */
154 int
xmldb_disconnect(clicon_handle h)155 xmldb_disconnect(clicon_handle h)
156 {
157 int retval = -1;
158 char **keys = NULL;
159 size_t klen;
160 int i;
161 db_elmnt *de;
162
163 if (clicon_hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0)
164 goto done;
165 for(i = 0; i < klen; i++)
166 if ((de = clicon_hash_value(clicon_db_elmnt(h), keys[i], NULL)) != NULL){
167 if (de->de_xml){
168 xml_free(de->de_xml);
169 de->de_xml = NULL;
170 }
171 }
172 retval = 0;
173 done:
174 if (keys)
175 free(keys);
176 return retval;
177 }
178
179 /*! Copy database from db1 to db2
180 * @param[in] h Clicon handle
181 * @param[in] from Source database
182 * @param[in] to Destination database
183 * @retval -1 Error
184 * @retval 0 OK
185 */
186 int
xmldb_copy(clicon_handle h,const char * from,const char * to)187 xmldb_copy(clicon_handle h,
188 const char *from,
189 const char *to)
190 {
191 int retval = -1;
192 char *fromfile = NULL;
193 char *tofile = NULL;
194 db_elmnt *de1 = NULL; /* from */
195 db_elmnt *de2 = NULL; /* to */
196 db_elmnt de0 = {0,};
197 cxobj *x1 = NULL; /* from */
198 cxobj *x2 = NULL; /* to */
199
200 /* XXX lock */
201 if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
202 /* Copy in-memory cache */
203 /* 1. "to" xml tree in x1 */
204 if ((de1 = clicon_db_elmnt_get(h, from)) != NULL)
205 x1 = de1->de_xml;
206 if ((de2 = clicon_db_elmnt_get(h, to)) != NULL)
207 x2 = de2->de_xml;
208 if (x1 == NULL && x2 == NULL){
209 /* do nothing */
210 }
211 else if (x1 == NULL){ /* free x2 and set to NULL */
212 xml_free(x2);
213 x2 = NULL;
214 }
215 else if (x2 == NULL){ /* create x2 and copy from x1 */
216 if ((x2 = xml_new(xml_name(x1), NULL, CX_ELMNT)) == NULL)
217 goto done;
218 if (xml_copy(x1, x2) < 0)
219 goto done;
220 }
221 else{ /* copy x1 to x2 */
222 xml_free(x2);
223 if ((x2 = xml_new(xml_name(x1), NULL, CX_ELMNT)) == NULL)
224 goto done;
225 if (xml_copy(x1, x2) < 0)
226 goto done;
227 }
228 /* always set cache although not strictly necessary in case 1
229 * above, but logic gets complicated due to differences with
230 * de and de->de_xml */
231 if (de2)
232 de0 = *de2;
233 de0.de_xml = x2; /* The new tree */
234 clicon_db_elmnt_set(h, to, &de0);
235 }
236 /* Copy the files themselves (above only in-memory cache) */
237 if (xmldb_db2file(h, from, &fromfile) < 0)
238 goto done;
239 if (xmldb_db2file(h, to, &tofile) < 0)
240 goto done;
241 if (clicon_file_copy(fromfile, tofile) < 0)
242 goto done;
243 retval = 0;
244 done:
245 if (fromfile)
246 free(fromfile);
247 if (tofile)
248 free(tofile);
249 return retval;
250
251 }
252
253 /*! Lock database
254 * @param[in] h Clicon handle
255 * @param[in] db Database
256 * @param[in] id Session id
257 * @retval -1 Error
258 * @retval 0 OK
259 */
260 int
xmldb_lock(clicon_handle h,const char * db,uint32_t id)261 xmldb_lock(clicon_handle h,
262 const char *db,
263 uint32_t id)
264 {
265 db_elmnt *de = NULL;
266 db_elmnt de0 = {0,};
267
268 if ((de = clicon_db_elmnt_get(h, db)) != NULL)
269 de0 = *de;
270 de0.de_id = id;
271 clicon_db_elmnt_set(h, db, &de0);
272 clicon_debug(1, "%s: locked by %u", db, id);
273 return 0;
274 }
275
276 /*! Unlock database
277 * @param[in] h Clicon handle
278 * @param[in] db Database
279 * @retval -1 Error
280 * @retval 0 OK
281 * Assume all sanity checks have been made
282 */
283 int
xmldb_unlock(clicon_handle h,const char * db)284 xmldb_unlock(clicon_handle h,
285 const char *db)
286 {
287 db_elmnt *de = NULL;
288
289 if ((de = clicon_db_elmnt_get(h, db)) != NULL){
290 de->de_id = 0;
291 clicon_db_elmnt_set(h, db, de);
292 }
293 return 0;
294 }
295
296 /*! Unlock all databases locked by session-id (eg process dies)
297 * @param[in] h Clicon handle
298 * @param[in] id Session id
299 * @retval -1 Error
300 * @retval 0 OK
301 */
302 int
xmldb_unlock_all(clicon_handle h,uint32_t id)303 xmldb_unlock_all(clicon_handle h,
304 uint32_t id)
305 {
306 int retval = -1;
307 char **keys = NULL;
308 size_t klen;
309 int i;
310 db_elmnt *de;
311
312 if (clicon_hash_keys(clicon_db_elmnt(h), &keys, &klen) < 0)
313 goto done;
314 for (i = 0; i < klen; i++)
315 if ((de = clicon_db_elmnt_get(h, keys[i])) != NULL &&
316 de->de_id == id){
317 de->de_id = 0;
318 clicon_db_elmnt_set(h, keys[i], de);
319 }
320 retval = 0;
321 done:
322 if (keys)
323 free(keys);
324 return retval;
325 }
326
327 /*! Check if database is locked
328 * @param[in] h Clicon handle
329 * @param[in] db Database
330 * @retval -1 Error
331 * @retval 0 Not locked
332 * @retval >0 Session id of locker
333 */
334 uint32_t
xmldb_islocked(clicon_handle h,const char * db)335 xmldb_islocked(clicon_handle h,
336 const char *db)
337 {
338 db_elmnt *de;
339
340 if ((de = clicon_db_elmnt_get(h, db)) == NULL)
341 return 0;
342 return de->de_id;
343 }
344
345 /*! Check if db exists
346 * @param[in] h Clicon handle
347 * @param[in] db Database
348 * @retval -1 Error
349 * @retval 0 No it does not exist
350 * @retval 1 Yes it exists
351 */
352 int
xmldb_exists(clicon_handle h,const char * db)353 xmldb_exists(clicon_handle h,
354 const char *db)
355 {
356 int retval = -1;
357 char *filename = NULL;
358 struct stat sb;
359
360 if (xmldb_db2file(h, db, &filename) < 0)
361 goto done;
362 if (lstat(filename, &sb) < 0)
363 retval = 0;
364 else
365 retval = 1;
366 done:
367 if (filename)
368 free(filename);
369 return retval;
370 }
371
372 /*! Clear database cache if any for mem/size optimization only
373 * @param[in] h Clicon handle
374 * @param[in] db Database
375 * @retval -1 Error
376 * @retval 0 OK
377 */
378 int
xmldb_clear(clicon_handle h,const char * db)379 xmldb_clear(clicon_handle h,
380 const char *db)
381 {
382 cxobj *xt = NULL;
383 db_elmnt *de = NULL;
384
385 if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
386 if ((de = clicon_db_elmnt_get(h, db)) != NULL){
387 if ((xt = de->de_xml) != NULL){
388 xml_free(xt);
389 de->de_xml = NULL;
390 }
391 }
392 }
393 return 0;
394 }
395
396 /*! Delete database, clear cache if any. Remove file
397 * @param[in] h Clicon handle
398 * @param[in] db Database
399 * @retval -1 Error
400 * @retval 0 OK
401 */
402 int
xmldb_delete(clicon_handle h,const char * db)403 xmldb_delete(clicon_handle h,
404 const char *db)
405 {
406 int retval = -1;
407 char *filename = NULL;
408 struct stat sb;
409
410 if (xmldb_clear(h, db) < 0)
411 goto done;
412 if (xmldb_db2file(h, db, &filename) < 0)
413 goto done;
414 if (lstat(filename, &sb) == 0)
415 if (truncate(filename, 0) < 0){
416 clicon_err(OE_DB, errno, "truncate %s", filename);
417 goto done;
418 }
419 retval = 0;
420 done:
421 if (filename)
422 free(filename);
423 return retval;
424 }
425
426 /*! Create a database. Open database for writing.
427 * @param[in] h Clicon handle
428 * @param[in] db Database
429 * @retval 0 OK
430 * @retval -1 Error
431 */
432 int
xmldb_create(clicon_handle h,const char * db)433 xmldb_create(clicon_handle h,
434 const char *db)
435 {
436 int retval = -1;
437 char *filename = NULL;
438 int fd = -1;
439 db_elmnt *de = NULL;
440 cxobj *xt = NULL;
441
442 if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
443 if ((de = clicon_db_elmnt_get(h, db)) != NULL){
444 if ((xt = de->de_xml) != NULL){
445 xml_free(xt);
446 de->de_xml = NULL;
447 }
448 }
449 }
450 if (xmldb_db2file(h, db, &filename) < 0)
451 goto done;
452 if ((fd = open(filename, O_CREAT|O_WRONLY, S_IRWXU)) == -1) {
453 clicon_err(OE_UNIX, errno, "open(%s)", filename);
454 goto done;
455 }
456 retval = 0;
457 done:
458 if (filename)
459 free(filename);
460 if (fd != -1)
461 close(fd);
462 return retval;
463 }
464
465 /*! Create an XML database. If it exists already, delete it before creating
466 * Utility function.
467 * @param[in] h Clixon handle
468 * @param[in] db Symbolic database name, eg "candidate", "running"
469 */
470 int
xmldb_db_reset(clicon_handle h,char * db)471 xmldb_db_reset(clicon_handle h,
472 char *db)
473 {
474 if (xmldb_exists(h, db) == 1){
475 if (xmldb_delete(h, db) != 0 && errno != ENOENT)
476 return -1;
477 }
478 if (xmldb_create(h, db) < 0)
479 return -1;
480 return 0;
481 }
482
483 /*! Get datastore XML cache
484 * @param[in] h Clicon handle
485 * @param[in] db Database name
486 * @retval xml XML cached tree or NULL
487 */
488 cxobj *
xmldb_cache_get(clicon_handle h,const char * db)489 xmldb_cache_get(clicon_handle h,
490 const char *db)
491 {
492 db_elmnt *de;
493
494 if ((de = clicon_db_elmnt_get(h, db)) == NULL)
495 return NULL;
496 return de->de_xml;
497 }
498
499 /*! Get modified flag from datastore
500 * @param[in] h Clicon handle
501 * @param[in] db Database name
502 * @retval -1 Error (datastore does not exist)
503 * @retval 0 Db is not modified
504 * @retval 1 Db is modified
505 * @note This only makes sense for "candidate", see RFC 6241 Sec 7.5
506 * @note This only works if db cache is used,...
507 */
508 int
xmldb_modified_get(clicon_handle h,const char * db)509 xmldb_modified_get(clicon_handle h,
510 const char *db)
511 {
512 db_elmnt *de;
513
514 if ((de = clicon_db_elmnt_get(h, db)) == NULL){
515 clicon_err(OE_CFG, EFAULT, "datastore %s does not exist", db);
516 return -1;
517 }
518 return de->de_modified;
519 }
520
521 /*! Get empty flag from datastore (the datastore was empty ON LOAD)
522 * @param[in] h Clicon handle
523 * @param[in] db Database name
524 * @retval -1 Error (datastore does not exist)
525 * @retval 0 Db was not empty on load
526 * @retval 1 Db was empty on load
527 */
528 int
xmldb_empty_get(clicon_handle h,const char * db)529 xmldb_empty_get(clicon_handle h,
530 const char *db)
531 {
532 db_elmnt *de;
533
534 if ((de = clicon_db_elmnt_get(h, db)) == NULL){
535 clicon_err(OE_CFG, EFAULT, "datastore %s does not exist", db);
536 return -1;
537 }
538 return de->de_empty;
539 }
540
541 /*! Get modified flag from datastore
542 * @param[in] h Clicon handle
543 * @param[in] db Database name
544 * @param[in] value 0 or 1
545 * @retval -1 Error (datastore does not exist)
546 * @retval 0 OK
547 * @note This only makes sense for "candidate", see RFC 6241 Sec 7.5
548 * @note This only works if db cache is used,...
549 */
550 int
xmldb_modified_set(clicon_handle h,const char * db,int value)551 xmldb_modified_set(clicon_handle h,
552 const char *db,
553 int value)
554 {
555 db_elmnt *de;
556
557 if ((de = clicon_db_elmnt_get(h, db)) == NULL){
558 clicon_err(OE_CFG, EFAULT, "datastore %s does not exist", db);
559 return -1;
560 }
561 de->de_modified = value;
562 return 0;
563 }
564