1 /*
2 *
3 ***** BEGIN LICENSE BLOCK *****
4
5 Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
6 Copyright (C) 2017-2019 Olof Hagsand
7 Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
8
9 This file is part of CLIXON.
10
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22
23 Alternatively, the contents of this file may be used under the terms of
24 the GNU General Public License Version 3 or later (the "GPL"),
25 in which case the provisions of the GPL are applicable instead
26 of those above. If you wish to allow use of your version of this file only
27 under the terms of the GPL, and not to allow others to
28 use your version of this file under the terms of Apache License version 2,
29 indicate your decision by deleting the provisions above and replace them with
30 the notice and other provisions required by the GPL. If you do not delete
31 the provisions above, a recipient may use your version of this file under
32 the terms of any one of the Apache License version 2 or the GPL.
33
34 ***** END LICENSE BLOCK *****
35
36 *
37 * Translation / mapping code between formats
38 */
39 #ifdef HAVE_CONFIG_H
40 #include "clixon_config.h" /* generated by config & autoconf */
41 #endif
42
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <errno.h>
47 #include <ctype.h>
48 #include <string.h>
49 #include <syslog.h>
50 #include <fcntl.h>
51 #include <assert.h>
52 #include <arpa/inet.h>
53 #include <sys/param.h>
54 #include <netinet/in.h>
55
56 /* cligen */
57 #include <cligen/cligen.h>
58
59 /* clicon */
60
61 #include "clixon_string.h"
62 #include "clixon_queue.h"
63 #include "clixon_hash.h"
64 #include "clixon_handle.h"
65 #include "clixon_string.h"
66 #include "clixon_yang.h"
67 #include "clixon_xml.h"
68 #include "clixon_options.h"
69 #include "clixon_data.h"
70 #include "clixon_yang_module.h"
71 #include "clixon_plugin.h"
72 #include "clixon_xml_nsctx.h"
73 #include "clixon_xpath_ctx.h"
74 #include "clixon_xpath.h"
75 #include "clixon_log.h"
76 #include "clixon_err.h"
77 #include "clixon_netconf_lib.h"
78 #include "clixon_xml_sort.h"
79 #include "clixon_yang_type.h"
80 #include "clixon_xml_bind.h"
81
82 /*
83 * Local variables
84 */
85 static int _yang_unknown_anydata = 0;
86
87 /*! Kludge to equate unknown XML with anydata
88 * The problem with this is that its global and should be bound to a handle
89 */
90 int
xml_bind_yang_unknown_anydata(int val)91 xml_bind_yang_unknown_anydata(int val)
92 {
93 _yang_unknown_anydata = val;
94 return 0;
95 }
96
97 /*! After yang binding, bodies of containers and lists are stripped from XML bodies
98 * May apply to other nodes?
99 */
100 static int
strip_whitespace(cxobj * xt)101 strip_whitespace(cxobj *xt)
102 {
103 yang_stmt *yt;
104 enum rfc_6020 keyword;
105 cxobj *xc;
106
107 if ((yt = xml_spec(xt)) != NULL){
108 keyword = yang_keyword_get(yt);
109 if (keyword == Y_LIST || keyword == Y_CONTAINER){
110 xc = NULL;
111 while ((xc = xml_find_type(xt, NULL, "body", CX_BODY)) != NULL)
112 xml_purge(xc);
113 }
114 }
115 return 0;
116 }
117
118 /*! Associate XML node x with x:s parents yang:s matching child
119 *
120 * @param[in] xt XML tree node
121 * @param[out] xerr Reason for failure, or NULL
122 * @retval 1 OK Yang assignment made
123 * @retval 2 OK Yang assignment not made because yang parent is anyxml or anydata
124 * @retval 0 Yang assigment not made and xerr set
125 * @retval -1 Error
126 * @note retval = 2 is special
127 * @see populate_self_top
128 */
129 static int
populate_self_parent(cxobj * xt,cxobj * xsibling,cxobj ** xerr)130 populate_self_parent(cxobj *xt,
131 cxobj *xsibling,
132 cxobj **xerr)
133 {
134 int retval = -1;
135 yang_stmt *y = NULL; /* yang node */
136 yang_stmt *yparent; /* yang parent */
137 cxobj *xp = NULL; /* xml parent */
138 char *name;
139 char *ns = NULL; /* XML namespace of xt */
140 char *nsy = NULL; /* Yang namespace of xt */
141 cbuf *cb = NULL;
142
143 name = xml_name(xt);
144 /* optimization for massive lists - use the first element as role model */
145 if (xsibling &&
146 xml_child_nr_type(xt, CX_ATTR) == 0){
147 y = xml_spec(xsibling);
148 goto set;
149 }
150 xp = xml_parent(xt);
151 if (xp == NULL){
152 if (xerr &&
153 netconf_bad_element_xml(xerr, "application", name, "Missing parent") < 0)
154 goto done;
155 goto fail;
156 }
157 if ((yparent = xml_spec(xp)) == NULL){
158 if (xerr &&
159 netconf_bad_element_xml(xerr, "application", name, "Missing parent yang node") < 0)
160 goto done;
161 goto fail;
162 }
163 if (yang_keyword_get(yparent) == Y_ANYXML || yang_keyword_get(yparent) == Y_ANYDATA){
164 retval = 2;
165 goto done;
166 }
167 if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
168 goto done;
169 if ((y = yang_find_datanode(yparent, name)) == NULL){
170 if (_yang_unknown_anydata){
171 /* Add dummy Y_ANYDATA yang stmt, see ysp_add */
172 if ((y = yang_anydata_add(yparent, name)) < 0)
173 goto done;
174 xml_spec_set(xt, y);
175 retval = 2; /* treat as anydata */
176 clicon_log(LOG_WARNING,
177 "%s: %d: No YANG spec for %s, anydata used",
178 __FUNCTION__, __LINE__, name);
179 goto done;
180 }
181 if ((cb = cbuf_new()) == NULL){
182 clicon_err(OE_UNIX, errno, "cbuf_new");
183 goto done;
184 }
185 cprintf(cb, "Failed to find YANG spec of XML node: %s", name);
186 cprintf(cb, " with parent: %s", xml_name(xp));
187 if (ns)
188 cprintf(cb, " in namespace: %s", ns);
189 if (xerr &&
190 netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
191 goto done;
192 goto fail;
193 }
194 nsy = yang_find_mynamespace(y);
195 if (ns == NULL || nsy == NULL){
196 if (xerr &&
197 netconf_bad_element_xml(xerr, "application", name, "Missing namespace") < 0)
198 goto done;
199 goto fail;
200 }
201 /* Assign spec only if namespaces match */
202 if (strcmp(ns, nsy) != 0){
203 if (xerr &&
204 netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0)
205 goto done;
206 goto fail;
207 }
208 set:
209 xml_spec_set(xt, y);
210 #ifdef XML_EXPLICIT_INDEX
211 if (xml_search_index_p(xt))
212 xml_search_child_insert(xp, xt);
213 #endif
214 retval = 1;
215 done:
216 if (cb)
217 cbuf_free(cb);
218 return retval;
219 fail:
220 retval = 0;
221 goto done;
222 }
223
224 /*! Associate XML node x with yang spec y by going through all top-level modules and finding match
225 *
226 * @param[in] xt XML tree node
227 * @param[in] yspec Yang spec
228 * @param[out] xerr Reason for failure, or NULL
229 * @retval 1 OK yang assignment made
230 * @retval 0 yang assigment not made and xerr set
231 * @retval -1 Error
232 * @see populate_self_parent
233 */
234 static int
populate_self_top(cxobj * xt,yang_stmt * yspec,cxobj ** xerr)235 populate_self_top(cxobj *xt,
236 yang_stmt *yspec,
237 cxobj **xerr)
238 {
239 int retval = -1;
240 yang_stmt *y = NULL; /* yang node */
241 yang_stmt *ymod; /* yang module */
242 char *name;
243 char *ns = NULL; /* XML namespace of xt */
244 char *nsy = NULL; /* Yang namespace of xt */
245 cbuf *cb = NULL;
246 cxobj *xp;
247
248 name = xml_name(xt);
249 if (yspec == NULL){
250 if (xerr &&
251 netconf_bad_element_xml(xerr, "application", name, "Missing yang spec") < 0)
252 goto done;
253 goto fail;
254 }
255 if (ys_module_by_xml(yspec, xt, &ymod) < 0)
256 goto done;
257 if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
258 goto done;
259 /* ymod is "real" module, name may belong to included submodule */
260 if (ymod == NULL){
261 if (xerr){
262 if ((cb = cbuf_new()) == NULL){
263 clicon_err(OE_UNIX, errno, "cbuf_new");
264 goto done;
265 }
266 cprintf(cb, "Failed to find YANG spec of XML node: %s", name);
267 if ((xp = xml_parent(xt)) != NULL)
268 cprintf(cb, " with parent: %s", xml_name(xp));
269 if (ns)
270 cprintf(cb, " in namespace: %s", ns);
271 if (netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
272 goto done;
273 }
274 goto fail;
275 }
276
277 if ((y = yang_find_schemanode(ymod, name)) == NULL){ /* also rpc */
278 if (_yang_unknown_anydata){
279 /* Add dummy Y_ANYDATA yang stmt, see ysp_add */
280 if ((y = yang_anydata_add(ymod, name)) < 0)
281 goto done;
282 xml_spec_set(xt, y);
283 retval = 2; /* treat as anydata */
284 clicon_log(LOG_WARNING,
285 "%s: %d: No YANG spec for %s, anydata used",
286 __FUNCTION__, __LINE__, name);
287 goto done;
288 }
289 if ((cb = cbuf_new()) == NULL){
290 clicon_err(OE_UNIX, errno, "cbuf_new");
291 goto done;
292 }
293 cprintf(cb, "Failed to find YANG spec of XML node: %s", name);
294 if ((xp = xml_parent(xt)) != NULL)
295 cprintf(cb, " with parent: %s", xml_name(xp));
296 if (ns)
297 cprintf(cb, " in namespace: %s", ns);
298 if (xerr &&
299 netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
300 goto done;
301 goto fail;
302 }
303 nsy = yang_find_mynamespace(y);
304 if (ns == NULL || nsy == NULL){
305 if (xerr &&
306 netconf_bad_element_xml(xerr, "application", name, "Missing namespace") < 0)
307 goto done;
308 goto fail;
309 }
310 /* Assign spec only if namespaces match */
311 if (strcmp(ns, nsy) != 0){
312 if (xerr &&
313 netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0)
314 goto done;
315 goto fail;
316 }
317 xml_spec_set(xt, y);
318 retval = 1;
319 done:
320 if (cb)
321 cbuf_free(cb);
322 return retval;
323 fail:
324 retval = 0;
325 goto done;
326 }
327
328 /*! Find yang spec association of tree of XML nodes
329 *
330 * Populate xt:s children as top-level symbols
331 * This may be unnecessary if yspec is set on manual creation: x=xml_new(); xml_spec_set(x,y)
332 * @param[in] xt XML tree node
333 * @param[in] yb How to bind yang to XML top-level when parsing
334 * @param[in] yspec Yang spec
335 * @param[out] xerr Reason for failure, or NULL
336 * @retval 1 OK yang assignment made
337 * @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
338 * @retval -1 Error
339 * @code
340 * cxobj *xerr = NULL;
341 * if (xml_bind_yang(x, YB_MODULE, yspec, &xerr) < 0)
342 * err;
343 * @endcode
344 * @note For subs to anyxml nodes will not have spec set
345 * There are several functions in the API family
346 * @see xml_bind_yang_rpc for incoming rpc
347 * @see xml_bind_yang0 If the calling xml object should also be populated
348 */
349 int
xml_bind_yang(cxobj * xt,yang_bind yb,yang_stmt * yspec,cxobj ** xerr)350 xml_bind_yang(cxobj *xt,
351 yang_bind yb,
352 yang_stmt *yspec,
353 cxobj **xerr)
354 {
355 int retval = -1;
356 cxobj *xc; /* xml child */
357 int ret;
358 int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
359
360 strip_whitespace(xt);
361 xc = NULL; /* Apply on children */
362 while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
363 if ((ret = xml_bind_yang0(xc, yb, yspec, xerr)) < 0)
364 goto done;
365 if (ret == 0)
366 failed++;
367 }
368 if (failed)
369 goto fail;
370 retval = 1;
371 done:
372 return retval;
373 fail:
374 retval = 0;
375 goto done;
376 }
377
378 static int
xml_bind_yang0_opt(cxobj * xt,yang_bind yb,cxobj * xsibling,cxobj ** xerr)379 xml_bind_yang0_opt(cxobj *xt,
380 yang_bind yb,
381 cxobj *xsibling,
382 cxobj **xerr)
383 {
384 int retval = -1;
385 cxobj *xc; /* xml child */
386 int ret;
387 int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
388 yang_stmt *yc0 = NULL;
389 cxobj *xc0 = NULL;
390 cxobj *xs;
391 char *name0 = NULL;
392 char *prefix0 = NULL;
393 char *name;
394 char *prefix;
395
396 switch (yb){
397 case YB_PARENT:
398 if ((ret = populate_self_parent(xt, xsibling, xerr)) < 0)
399 goto done;
400 break;
401 default:
402 clicon_err(OE_XML, EINVAL, "Invalid yang binding: %d", yb);
403 goto done;
404 break;
405 }
406 if (ret == 0)
407 goto fail;
408 else if (ret == 2) /* ret=2 for anyxml from parent^ */
409 goto ok;
410 strip_whitespace(xt);
411 xc = NULL; /* Apply on children */
412 while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
413 /* It is xml2ns in populate_self_parent that needs improvement */
414 /* cache previous + prefix */
415 name = xml_name(xc);
416 prefix = xml_prefix(xc);
417 if (yc0 != NULL &&
418 clicon_strcmp(name0, name) == 0 &&
419 clicon_strcmp(prefix0, prefix) == 0){
420 if ((ret = xml_bind_yang0_opt(xc, YB_PARENT, xc0, xerr)) < 0)
421 goto done;
422 }
423 else if (xsibling &&
424 (xs = xml_find_type(xsibling, prefix, name, CX_ELMNT)) != NULL){
425 if ((ret = xml_bind_yang0_opt(xc, YB_PARENT, xs, xerr)) < 0)
426 goto done;
427 }
428 else if ((ret = xml_bind_yang0_opt(xc, YB_PARENT, NULL, xerr)) < 0)
429 goto done;
430 if (ret == 0)
431 failed++;
432 xc0 = xc;
433 yc0 = xml_spec(xc); /* cache */
434 name0 = xml_name(xc);
435 prefix0 = xml_prefix(xc);
436 }
437 if (failed)
438 goto fail;
439 ok:
440 retval = 1;
441 done:
442 return retval;
443 fail:
444 retval = 0;
445 goto done;
446 }
447
448 /*! Find yang spec association of tree of XML nodes
449 *
450 * @param[in] xt XML tree node
451 * @param[in] yb How to bind yang to XML top-level when parsing
452 * @param[in] yspec Yang spec
453 * @param[out] xerr Reason for failure, or NULL
454 * @retval 1 OK yang assignment made
455 * @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
456 * @retval -1 Error
457 * Populate xt as top-level node
458 * @see xml_bind_yang If only children of xt should be populated, not xt itself
459 */
460 int
xml_bind_yang0(cxobj * xt,yang_bind yb,yang_stmt * yspec,cxobj ** xerr)461 xml_bind_yang0(cxobj *xt,
462 yang_bind yb,
463 yang_stmt *yspec,
464 cxobj **xerr)
465 {
466 int retval = -1;
467 cxobj *xc; /* xml child */
468 int ret;
469 int failed = 0; /* we continue loop after failure, should we stop at fail?`*/
470
471 switch (yb){
472 case YB_MODULE:
473 if ((ret = populate_self_top(xt, yspec, xerr)) < 0)
474 goto done;
475 break;
476 case YB_PARENT:
477 if ((ret = populate_self_parent(xt, NULL, xerr)) < 0)
478 goto done;
479 break;
480 case YB_NONE:
481 ret = 1;
482 break;
483 default:
484 clicon_err(OE_XML, EINVAL, "Invalid yang binding: %d", yb);
485 goto done;
486 break;
487 }
488 if (ret == 0)
489 goto fail;
490 else if (ret == 2) /* ret=2 for anyxml from parent^ */
491 goto ok;
492 strip_whitespace(xt);
493 xc = NULL; /* Apply on children */
494 while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
495 if ((ret = xml_bind_yang0_opt(xc, YB_PARENT, NULL, xerr)) < 0)
496 goto done;
497 if (ret == 0)
498 failed++;
499 }
500 if (failed)
501 goto fail;
502 ok:
503 retval = 1;
504 done:
505 return retval;
506 fail:
507 retval = 0;
508 goto done;
509 }
510
511 /*! Find yang spec association of XML node for incoming RPC starting with <rpc>
512 *
513 * Incoming RPC has an "input" structure that is not taken care of by xml_bind_yang
514 * @param[in] xrpc XML rpc node
515 * @param[in] yspec Yang spec
516 * @param[out] xerr Reason for failure, or NULL
517 * @retval 1 OK yang assignment made
518 * @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
519 * @retval -1 Error
520 * The
521 * @code
522 * if (xml_bind_yang_rpc(h, x, NULL) < 0)
523 * err;
524 * @endcode
525 * @see xml_bind_yang For other generic cases
526 * @see xml_bind_yang_rpc_reply
527 */
528 int
xml_bind_yang_rpc(cxobj * xrpc,yang_stmt * yspec,cxobj ** xerr)529 xml_bind_yang_rpc(cxobj *xrpc,
530 yang_stmt *yspec,
531 cxobj **xerr)
532 {
533 int retval = -1;
534 yang_stmt *yrpc = NULL; /* yang node */
535 yang_stmt *ymod=NULL; /* yang module */
536 yang_stmt *yi = NULL; /* input */
537 cxobj *x;
538 int ret;
539 char *opname; /* top-level netconf operation */
540 char *rpcname; /* RPC name */
541 char *name;
542 cbuf *cb = NULL;
543 cxobj *xc;
544
545 opname = xml_name(xrpc);
546 if ((strcmp(opname, "hello")) == 0) /* Hello: dont bind, dont appear in any yang spec */
547 goto ok;
548 else if ((strcmp(opname, "notification")) == 0)
549 goto ok;
550 else if ((strcmp(opname, "rpc")) == 0)
551 ; /* continue */
552 else { /* Notify, rpc-reply? */
553 if (xerr &&
554 netconf_unknown_element_xml(xerr, "protocol", opname, "Unrecognized netconf operation") < 0)
555 goto done;
556 goto fail;
557 }
558 x = NULL;
559 while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) {
560 rpcname = xml_name(x);
561 if (ys_module_by_xml(yspec, x, &ymod) < 0)
562 goto done;
563 if (ymod == NULL){
564 if (xerr &&
565 netconf_unknown_element_xml(xerr, "application", rpcname, "Unrecognized RPC (wrong namespace?)") < 0)
566 goto done;
567 goto fail;
568 }
569 if ((yrpc = yang_find(ymod, Y_RPC, rpcname)) == NULL){
570 if (xerr &&
571 netconf_unknown_element_xml(xerr, "application", rpcname, "Unrecognized RPC") < 0)
572 goto done;
573 goto fail;
574 }
575 xml_spec_set(x, yrpc); /* required for validate */
576 if ((yi = yang_find(yrpc, Y_INPUT, NULL)) == NULL){
577 /* If no yang input spec but RPC has elements, return unknown element */
578 if (xml_child_nr_type(x, CX_ELMNT) != 0){
579 xc = xml_child_i_type(x, 0, CX_ELMNT); /* Pick first */
580 name = xml_name(xc);
581 if ((cb = cbuf_new()) == NULL){
582 clicon_err(OE_UNIX, errno, "cbuf_new");
583 goto done;
584 }
585 cprintf(cb, "Unrecognized parameter: %s in rpc: %s", name, rpcname);
586 if (xerr &&
587 netconf_unknown_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
588 goto done;
589 goto fail;
590 }
591 }
592 else{
593 /* xml_bind_yang need to have parent with yang spec for
594 * recursive population to work. Therefore, assign input yang
595 * to rpc level although not 100% intuitive */
596 xml_spec_set(x, yi);
597 if ((ret = xml_bind_yang(x, YB_PARENT, NULL, xerr)) < 0)
598 goto done;
599 if (ret == 0)
600 goto fail;
601 }
602 }
603 ok:
604 retval = 1;
605 done:
606 if (cb)
607 cbuf_free(cb);
608 return retval;
609 fail:
610 retval = 0;
611 goto done;
612 }
613
614 /*! Find yang spec association of XML node for outgoing RPC starting with <rpc-reply>
615 *
616 * Incoming RPC has an "input" structure that is not taken care of by xml_bind_yang
617 * @param[in] xrpc XML rpc node
618 * @param[in] name Name of RPC (not seen in output/reply)
619 * @param[in] yspec Yang spec
620 * @param[out] xerr Reason for failure, or NULL
621 * @retval 1 OK yang assignment made
622 * @retval 0 Partial or no yang assigment made (at least one failed) and xerr set
623 * @retval -1 Error
624 *
625 * @code
626 * if (xml_bind_yang_rpc_reply(x, "get-config", yspec, name) < 0)
627 * err;
628 * @endcode
629 * @see xml_bind_yang For other generic cases
630 */
631 int
xml_bind_yang_rpc_reply(cxobj * xrpc,char * name,yang_stmt * yspec,cxobj ** xerr)632 xml_bind_yang_rpc_reply(cxobj *xrpc,
633 char *name,
634 yang_stmt *yspec,
635 cxobj **xerr)
636 {
637 int retval = -1;
638 yang_stmt *yrpc = NULL; /* yang node */
639 yang_stmt *ymod=NULL; /* yang module */
640 yang_stmt *yo = NULL; /* output */
641 cxobj *x;
642 int ret;
643
644 if (strcmp(xml_name(xrpc), "rpc-reply")){
645 clicon_err(OE_UNIX, EINVAL, "rpc-reply expected");
646 goto done;
647 }
648 x = NULL;
649 while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) {
650 if (ys_module_by_xml(yspec, x, &ymod) < 0)
651 goto done;
652 if (ymod == NULL)
653 continue;
654 if ((yrpc = yang_find(ymod, Y_RPC, name)) == NULL)
655 continue;
656 // xml_spec_set(xrpc, yrpc);
657 if ((yo = yang_find(yrpc, Y_OUTPUT, NULL)) == NULL)
658 continue;
659 /* xml_bind_yang need to have parent with yang spec for
660 * recursive population to work. Therefore, assign input yang
661 * to rpc level although not 100% intuitive */
662 break;
663 }
664 if (yo != NULL){
665 xml_spec_set(xrpc, yo);
666 if ((ret = xml_bind_yang(xrpc, YB_MODULE, yspec, xerr)) < 0)
667 goto done;
668 if (ret == 0)
669 goto fail;
670 }
671 retval = 1;
672 done:
673 return retval;
674 fail:
675 retval = 0;
676 goto done;
677 }
678
679
680
681