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