text: /*
   *
    ***** BEGIN LICENSE BLOCK *****
   
    Copyright (C) 2009-2019 Olof Hagsand
    Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
  
    This file is part of CLIXON.
  
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
  
      http://www.apache.org/licenses/LICENSE-2.0
  
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
  
    Alternatively, the contents of this file may be used under the terms of
    the GNU General Public License Version 3 or later (the "GPL"),
    in which case the provisions of the GPL are applicable instead
    of those above. If you wish to allow use of your version of this file only
    under the terms of the GPL, and not to allow others to
    use your version of this file under the terms of Apache License version 2, 
    indicate your decision by deleting the provisions above and replace them with
    the  notice and other provisions required by the GPL. If you do not delete
    the provisions above, a recipient may use your version of this file under
    the terms of any one of the Apache License version 2 or the GPL.
  
    ***** END LICENSE BLOCK *****
  
   * Yang module and feature handling
   * @see https://tools.ietf.org/html/rfc7895
   */
  
  #ifdef HAVE_CONFIG_H
  #include "clixon_config.h" /* generated by config & autoconf */
  #endif
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <limits.h>
  #include <ctype.h>
  #include <unistd.h>
  #include <string.h>
  #include <arpa/inet.h>
  #include <regex.h>
  #include <dirent.h>
  #include <sys/types.h>
  #include <fcntl.h>
  #include <syslog.h>
  #include <sys/stat.h>
  #include <netinet/in.h>
  #include <sys/param.h>
  
  /* cligen */
  #include <cligen/cligen.h>
  
  /* clicon */
  #include "clixon_log.h"
  #include "clixon_err.h"
  #include "clixon_string.h"
  #include "clixon_queue.h"
  #include "clixon_hash.h"
  #include "clixon_handle.h"
  #include "clixon_file.h"
  #include "clixon_yang.h"
  #include "clixon_xml.h"
  #include "clixon_xml_io.h"
  #include "clixon_xml_nsctx.h"
  #include "clixon_xpath_ctx.h"
  #include "clixon_xpath.h"
  #include "clixon_options.h"
  #include "clixon_data.h"
  #include "clixon_yang_module.h"
  #include "clixon_plugin.h"
  #include "clixon_netconf_lib.h"
  #include "clixon_xml_map.h"
  #include "clixon_yang_parse_lib.h"
  
  modstate_diff_t *
  modstate_diff_new(void)
  {
      modstate_diff_t *md;
  
      if ((md = malloc(sizeof(modstate_diff_t))) == NULL){
  	clicon_err(OE_UNIX, errno, "malloc");
  	return NULL;
      }
      memset(md, 0, sizeof(modstate_diff_t));
      return md;
  }
  
  int
  modstate_diff_free(modstate_diff_t *md)
  {
      if (md == NULL)
  	return 0;
      if (md->md_content_id)
         free(md->md_content_id);
      if (md->md_diff)
  	xml_free(md->md_diff);
      free(md);
      return 0;
  }
  
  /*! Init the Yang module library
   *
   * Load RFC7895 yang spec, module-set-id, etc.
   * @param[in]     h       Clicon handle
   * @see netconf_module_load
   */
  int
  yang_modules_init(clicon_handle h)
  {
      int        retval = -1;
      yang_stmt *yspec;
  
      yspec = clicon_dbspec_yang(h);	
      if (!clicon_option_bool(h, "CLICON_YANG_LIBRARY"))
  	goto ok;
      /* Ensure module-set-id is set */
      if (!clicon_option_exists(h, "CLICON_MODULE_SET_ID")){
  	clicon_err(OE_CFG, ENOENT, "CLICON_MODULE_SET_ID must be defined when CLICON_YANG_LIBRARY is enabled");
  	goto done;
      }
      /* Ensure revision exists is set */
      if (yang_spec_parse_module(h, "ietf-yang-library", NULL, yspec)< 0)
  	goto done;
      /* Find revision */
      if (yang_modules_revision(h) == NULL){
  	clicon_err(OE_CFG, ENOENT, "Yang client library yang spec has no revision");
  	goto done;
      }
   ok:
       retval = 0;
   done:
       return retval;
  }
  
  /*! Return RFC7895 revision (if parsed)
   * @param[in]    h        Clicon handle
   * @retval       revision String (dont free)
   * @retval       NULL     Error: RFC7895 not loaded or revision not found
   */
  char *
  yang_modules_revision(clicon_handle h)
  {
      yang_stmt *yspec;
      yang_stmt *ymod;
      yang_stmt *yrev;
      char      *revision = NULL;
  
      yspec = clicon_dbspec_yang(h);
      if ((ymod = yang_find(yspec, Y_MODULE, "ietf-yang-library")) != NULL ||
  	(ymod = yang_find(yspec, Y_SUBMODULE, "ietf-yang-library")) != NULL){
  	if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL){
  	    revision = yang_argument_get(yrev);
  	}
      }
      return revision;
  }
  
  /*! Actually build the yang modules state XML tree according to RFC8525
   *
   * This assumes CLICON_YANG_LIBRARY is enabled
   * If also CLICON_MODULE_LIBRARY_RFC7895 is set, module-state is built according to RFC7895 instead
   * @see RFC8525 
   */
  static int
  yms_build(clicon_handle    h,
  	  yang_stmt       *yspec,
  	  char            *msid,
  	  int              brief,
  	  cbuf            *cb)
  {
      int         retval = -1;
      yang_stmt  *ylib = NULL; /* ietf-yang-library */
      char       *module = "ietf-yang-library";
      yang_stmt  *ys;
      yang_stmt  *yc;
      yang_stmt  *ymod;        /* generic module */
      yang_stmt  *yns = NULL;  /* namespace */
      yang_stmt  *yinc;
      yang_stmt  *ysub;
      char       *name;
  
      if ((ylib = yang_find(yspec, Y_MODULE, module)) == NULL &&
  	(ylib = yang_find(yspec, Y_SUBMODULE, module)) == NULL){
              clicon_err(OE_YANG, 0, "%s not found", module);
              goto done;
          }
      if ((yns = yang_find(ylib, Y_NAMESPACE, NULL)) == NULL){
  	clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
  	goto done;
      }
  
      if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
  	cprintf(cb,"<modules-state xmlns=\"%s\">", yang_argument_get(yns));
  	cprintf(cb,"<module-set-id>%s</module-set-id>", msid);
      }
      else { /* RFC 8525 */
  	cprintf(cb,"<yang-library xmlns=\"%s\">", yang_argument_get(yns));
  	cprintf(cb,"<content-id>%s</content-id>", msid);
  	cprintf(cb,"<module-set><name>default</name>");
      }
      ymod = NULL;
      while ((ymod = yn_each(yspec, ymod)) != NULL) {
  	if (yang_keyword_get(ymod) != Y_MODULE)
  	    continue;
  	cprintf(cb,"<module>");
  	cprintf(cb,"<name>%s</name>", yang_argument_get(ymod));
  	if ((ys = yang_find(ymod, Y_REVISION, NULL)) != NULL)
  	    cprintf(cb,"<revision>%s</revision>", yang_argument_get(ys));
  	else{
  	    /* RFC7895 1 If no (such) revision statement exists, the module's or 
  	       submodule's revision is the zero-length string. */
  	    cprintf(cb,"<revision></revision>");
  	}
  	if ((ys = yang_find(ymod, Y_NAMESPACE, NULL)) != NULL)
  	    cprintf(cb,"<namespace>%s</namespace>", yang_argument_get(ys));
  	else
  	    cprintf(cb,"<namespace></namespace>");
  	/* This follows order in rfc 7895: feature, conformance-type, 
  	   submodules */
  	if (!brief){
  	    yc = NULL;
  	    while ((yc = yn_each(ymod, yc)) != NULL) {
  		switch(yang_keyword_get(yc)){
  		case Y_FEATURE:
  		    if (yang_cv_get(yc) && cv_bool_get(yang_cv_get(yc)))
  			cprintf(cb,"<feature>%s</feature>", yang_argument_get(yc));
  		    break;
  		default:
  		    break;
  		}
  	    }
  	    if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
  		cprintf(cb, "<conformance-type>implement</conformance-type>");
  	}
  	yinc = NULL;
  	while ((yinc = yn_each(ymod, yinc)) != NULL) {
  	    if (yang_keyword_get(yinc) != Y_INCLUDE)
  		continue;
  	    cprintf(cb,"<submodule>");
  	    name = yang_argument_get(yinc);
  	    cprintf(cb,"<name>%s</name>", name);
  	    if ((ysub = yang_find(yspec, Y_SUBMODULE, name)) != NULL){
  		if ((ys = yang_find(ysub, Y_REVISION, NULL)) != NULL)
  		    cprintf(cb,"<revision>%s</revision>", yang_argument_get(ys));
  		else
  		    cprintf(cb,"<revision></revision>");
  	    }
  	    cprintf(cb,"</submodule>");
  	}
  	cprintf(cb,"</module>");
      }
      if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
  	cprintf(cb,"</modules-state>");
      }
      else{
  	cprintf(cb,"</module-set></yang-library>");
      }
      retval = 0;
   done:
      return retval;
  }
  
  /*! Get modules state according to RFC 7895
   * @param[in]     h       Clicon handle
   * @param[in]     yspec   Yang spec
   * @param[in]     xpath   XML Xpath
   * @param[in]     nsc     XML Namespace context for xpath
   * @param[in]     brief   Just name, revision and uri (no cache)
   * @param[in,out] xret    Existing XML tree, merge x into this
   * @retval       -1       Error (fatal)
   * @retval        0       Statedata callback failed
   * @retval        1       OK
   * @notes NYI: schema, deviation
  x      +--ro modules-state
  x         +--ro module-set-id    string
  x         +--ro module* [name revision]
  x            +--ro name                yang:yang-identifier
  x            +--ro revision            union
              +--ro schema?             inet:uri
  x            +--ro namespace           inet:uri
              +--ro feature*            yang:yang-identifier
              +--ro deviation* [name revision]
              |  +--ro name        yang:yang-identifier
              |  +--ro revision    union
              +--ro conformance-type    enumeration
              +--ro submodule* [name revision]
                 +--ro name        yang:yang-identifier
                 +--ro revision    union
                 +--ro schema?     inet:uri
   * @see netconf_hello_server
   */
  int
  yang_modules_state_get(clicon_handle    h,
                         yang_stmt       *yspec,
                         char            *xpath,
  		       cvec            *nsc,
  		       int              brief,
                         cxobj          **xret)
  {
      int         retval = -1;
      cxobj      *x = NULL; /* Top tree, some juggling w top symbol */
      char       *msid; /* modules-set-id */
      cxobj      *xc;   /* original cache */
      cbuf       *cb = NULL;
      int         ret;
      cxobj     **xvec = NULL;
      size_t      xlen;
      int         i;
  
      msid = clicon_option_str(h, "CLICON_MODULE_SET_ID"); /* In RFC 8525 changed to "content-id" */
      if ((xc = clicon_modst_cache_get(h, brief)) != NULL){
  	cxobj *xw; /* tmp top wrap object */
  	/* xc is here: <modules-state>... 
  	 * need to wrap it for xpath: <top><modules-state> */
  	/* xc is also original tree, need to copy it */
  	if ((xw = xml_wrap(xc, "top")) == NULL)
  	    goto done;
          if (xpath_first(xw, nsc, "%s", xpath)){
              if ((x = xml_dup(xc)) == NULL) /* Make copy and use below */
                  goto done;
          }
  	if (xml_rootchild_node(xw, xc) < 0)  /* Unwrap x / free xw */
  	    goto done;
      }
      else { /* No cache -> build the tree */
  	if ((cb = cbuf_new()) == NULL){
  	    clicon_err(OE_UNIX, 0, "clicon buffer");
  	    goto done;
  	}
  	/* Build a cb string: <modules-state>... */
  	if (yms_build(h, yspec, msid, brief, cb) < 0)
  	    goto done;
  	/* Parse cb, x is on the form: <top><modules-state>... 
  	 * Note, list is not sorted since it is state (should not be)
  	 */
  	if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){
  	    if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
  		goto done;
  	    goto fail;
  	}
  	if (xml_rootchild(x, 0, &x) < 0)
  	    goto done;
  	/* x is now: <modules-state>... */
  	if (clicon_modst_cache_set(h, brief, x) < 0) /* move to fn above? */
  	    goto done;
      }
      if (x){ /* x is here a copy (This code is ugly and I think wrong) */
  	/* Wrap x (again) with new top-level node "top" which xpath wants */
  	if ((x = xml_wrap(x, "top")) < 0)
  	    goto done;
  	/* extract xpath part of module-state tree */
  	if (xpath_vec(x, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
  	    goto done;
  	if (xvec != NULL){
  	    for (i=0; i<xlen; i++)
  		xml_flag_set(xvec[i], XML_FLAG_MARK);
  	}
  	/* Remove everything that is not marked */
  	if (xml_tree_prune_flagged_sub(x, XML_FLAG_MARK, 1, NULL) < 0)
  	    goto done;
  
  	if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
  	    goto done;
  	if (ret == 0)
  	    goto fail;
      }
      retval = 1;
   done:
      clicon_debug(1, "%s %d", __FUNCTION__, retval);
      if (xvec)
  	free(xvec);
      if (cb)
          cbuf_free(cb);
      if (x)
          xml_free(x);
      return retval;
   fail:
      retval = 0;
      goto done;
  }
  
  /*! For single module state with namespace, get revisions and send upgrade callbacks
   * @param[in]  h        Clicon handle
   * @param[in]  xt       Top-level XML tree to be updated (includes other ns as well)
   * @param[in]  xmod     XML module state diff (for one yang module)
   * @param[in]  ns       Namespace of module state we are looking for
   * @param[out] cbret    Netconf error message if invalid
   * @retval     1        OK
   * @retval     0        Validation failed (cbret set)
   * @retval    -1        Error
   */
  static int
  mod_ns_upgrade(clicon_handle h,
  	       cxobj        *xt,
  	       cxobj        *xmod,
  	       char         *ns,
  	       cbuf         *cbret)
  {
      int        retval = -1;
      char      *b; /* string body */
      yang_stmt *ymod;
      yang_stmt *yrev;
      uint32_t   from = 0;
      uint32_t   to = 0;
      int        ret;
      yang_stmt *yspec;
  
      /* If modified or removed get from revision from file */
      if (xml_flag(xmod, (XML_FLAG_CHANGE|XML_FLAG_DEL)) != 0x0){
  	if ((b = xml_find_body(xmod, "revision")) != NULL) /* Module revision */
  	    if (ys_parse_date_arg(b, &from) < 0)
  		goto done;
      }
      /* If modified or added get to revision from system */
      if (xml_flag(xmod, (XML_FLAG_CHANGE|XML_FLAG_ADD)) != 0x0){
  	yspec = clicon_dbspec_yang(h);
  	if ((ymod = yang_find_module_by_namespace(yspec, ns)) == NULL)
  	    goto fail;
  	if ((yrev = yang_find(ymod, Y_REVISION, NULL)) == NULL){
  	    retval = 1;
  	    goto done;
  	}
  	if (ys_parse_date_arg(yang_argument_get(yrev), &to) < 0)
  	    goto done;
      }
      if ((ret = upgrade_callback_call(h, xt, ns,
  				     xml_flag(xmod, (XML_FLAG_ADD|XML_FLAG_DEL|XML_FLAG_CHANGE)),
  				     from, to, cbret)) < 0)
  	goto done;
      if (ret == 0) /* XXX ignore and continue? */
  	goto fail;
      retval = 1;
   done:
      return retval;
   fail:
      retval = 0;
      goto done;
  }
  
  /*! Upgrade XML
   * @param[in]  h    Clicon handle
   * @param[in]  xt   XML tree (to upgrade)
   * @param[in]  msd  Modules-state differences of xt
   * @param[out] cbret Netconf error message if invalid
   * @retval     1    OK
   * @retval     0    Validation failed (cbret set)
   * @retval    -1    Error
   */
  int
  clixon_module_upgrade(clicon_handle    h,
  		      cxobj           *xt,
  		      modstate_diff_t *msd,
     		      cbuf            *cbret)
  {
      int      retval = -1;
      char   *ns;           /* Namespace */
      cxobj  *xmod;           /* XML module state diff */
      int     ret;
  
      if (msd == NULL){
  	clicon_err(OE_CFG, EINVAL, "No modstate");
  	goto done;
      }
      if (msd->md_status == 0) /* No modstate in startup */
  	goto ok;
      /* Iterate through xml modified module state */
      xmod = NULL;
      while ((xmod = xml_child_each(msd->md_diff, xmod, CX_ELMNT)) != NULL) {
  	/* Extract namespace */
  	if ((ns = xml_find_body(xmod, "namespace")) == NULL)
  	    goto done;
  	/* Extract revisions and make callbacks */
  	if ((ret = mod_ns_upgrade(h, xt, xmod, ns, cbret)) < 0)
  	    goto done;
  	if (ret == 0)
  	    goto fail;
      }
   ok:
      retval = 1;
   done:
      return retval;
   fail:
      retval = 0;
      goto done;
  }
  
  /*! Given a yang statement and a prefix, return yang module to that relative prefix
   * Note, not the other module but the proxy import statement only
   * @param[in]  ys      A yang statement
   * @param[in]  prefix  prefix
   * @retval     ymod    Yang module statement if found
   * @retval     NULL    not found
   * @note Prefixes are relative to the module they are defined
   * @see yang_find_module_by_name
   * @see yang_find_module_by_namespace
   * @see yang_find_namespace_by_prefix
   */
  yang_stmt *
  yang_find_module_by_prefix(yang_stmt *ys, 
  			   char      *prefix)
  {
      yang_stmt *yimport;
      yang_stmt *yprefix;
      yang_stmt *my_ymod;
      yang_stmt *ymod = NULL;
      yang_stmt *yspec;
      char      *myprefix;
  
      if ((yspec = ys_spec(ys)) == NULL){
  	clicon_err(OE_YANG, 0, "My yang spec not found");
  	goto done;
      }
      /* First try own module */
      if ((my_ymod = ys_module(ys)) == NULL){
  	clicon_err(OE_YANG, 0, "My yang module not found");
  	goto done;
      }
      myprefix = yang_find_myprefix(ys);
      if (myprefix && strcmp(myprefix, prefix) == 0){
  	ymod = my_ymod;
  	goto done;
      }
      /* If no match, try imported modules */
      yimport = NULL;
      while ((yimport = yn_each(my_ymod, yimport)) != NULL) {
  	if (yang_keyword_get(yimport) != Y_IMPORT)
  	    continue;
  	if ((yprefix = yang_find(yimport, Y_PREFIX, NULL)) != NULL &&
  	    strcmp(yang_argument_get(yprefix), prefix) == 0){
  	    break;
  	}
      }
      if (yimport){
  	if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yimport))) == NULL){
  	    clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s", 
  		       prefix);	
  	    yimport = NULL;
  	    goto done; /* unresolved */
  	}
      }
   done:
      return ymod;
  }
  
  /* Get module from its own prefix 
   * This is really not a valid usecase, a kludge for the identityref derived
   * list workaround (IDENTITYREF_KLUDGE)
   * Actually, for canonical prefixes it is!
   */ 
  yang_stmt *
  yang_find_module_by_prefix_yspec(yang_stmt *yspec, 
  				 char      *prefix)
  {
      yang_stmt *ymod = NULL;
      yang_stmt *yprefix;
      
      while ((ymod = yn_each(yspec, ymod)) != NULL) 
  	if (yang_keyword_get(ymod) == Y_MODULE &&
  	    (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL &&
  	    strcmp(yang_argument_get(yprefix), prefix) == 0)
  	    return ymod;
      return NULL;
  }
  
  /*! Given a yang spec and a namespace, return yang module 
   *
   * @param[in]  yspec      A yang specification
   * @param[in]  ns         namespace
   * @retval     ymod       Yang module statement if found
   * @retval     NULL       not found
   * @see yang_find_module_by_name
   * @see yang_find_module_by_prefix    module-specific prefix
   * @see yang_find_prefix_by_namespace
   */
  yang_stmt *
  yang_find_module_by_namespace(yang_stmt *yspec, 
  			      char      *ns)
  {
      yang_stmt *ymod = NULL;
  
      if (ns == NULL)
  	goto done;
      while ((ymod = yn_each(yspec, ymod)) != NULL) {
  	if (yang_find(ymod, Y_NAMESPACE, ns) != NULL)
  	    break;
      }
   done:
      return ymod;
  }
  
  /*! Given a yang spec, a namespace and revision, return yang module 
   *
   * @param[in]  yspec      A yang specification
   * @param[in]  ns         Namespace
   * @param[in]  rev        Revision
   * @retval     ymod       Yang module statement if found
   * @retval     NULL       not found
   * @see yang_find_module_by_namespace
   * @note a module may have many revisions, but only the first is significant
   */
  yang_stmt *
  yang_find_module_by_namespace_revision(yang_stmt  *yspec, 
  				       const char *ns,
      				       const char *rev)
  {
      yang_stmt *ymod = NULL;
      yang_stmt *yrev;
      char      *rev1;
  
      if (ns == NULL || rev == NULL){
  	clicon_err(OE_CFG, EINVAL, "No ns or rev");
  	goto done;
      }
      while ((ymod = yn_each(yspec, ymod)) != NULL) {
  	if (yang_find(ymod, Y_NAMESPACE, ns) != NULL)
  	    /* Get FIRST revision */
  	    if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL){
  		rev1 = yang_argument_get(yrev);
  		if (strcmp(rev, rev1) == 0)
  		    break; /* return this ymod */
  	    }
      }
   done:
      return ymod;
  }
  
  /*! Given a yang spec, name and revision, return yang module 
   *
   * @param[in]  yspec      A yang specification
   * @param[in]  name       Name
   * @param[in]  rev        Revision
   * @retval     ymod       Yang module statement if found
   * @retval     NULL       not found
   * @see yang_find_module_by_namespace
   * @note a module may have many revisions, but only the first is significant
   */
  yang_stmt *
  yang_find_module_by_name_revision(yang_stmt  *yspec, 
  				  const char *name,
  				  const char *rev)
  {
      yang_stmt *ymod = NULL;
      yang_stmt *yrev;
      char      *rev1;
  
      if (name == NULL){
  	clicon_err(OE_CFG, EINVAL, "No ns or rev");
  	goto done;
      }
      while ((ymod = yn_each(yspec, ymod)) != NULL) {
  	if (yang_keyword_get(ymod) != Y_MODULE)
  	    continue;
  	if (strcmp(yang_argument_get(ymod), name) != 0)
  	    continue;
  	if (rev == NULL)
  	    break; /* Matching revision is NULL, match that */
  	/* Get FIRST revision */
  	if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL){
  	    rev1 = yang_argument_get(yrev);
  	    if (strcmp(rev, rev1) == 0)
  		break; /* return this ymod */
  	}
      }
   done:
      return ymod;
  }
  
  /*! Given a yang spec and a module name, return yang module or submodule
   *
   * @param[in]  yspec      A yang specification
   * @param[in]  name       Name of module
   * @retval     ymod       Yang module statement if found
   * @retval     NULL       not found
   * @see yang_find_module_by_namespace
   * @see yang_find_module_by_prefix    module-specific prefix
   */
  yang_stmt *
  yang_find_module_by_name(yang_stmt *yspec, 
  			 char      *name)
  {
      yang_stmt *ymod = NULL;
      
      while ((ymod = yn_each(yspec, ymod)) != NULL) 
  	if ((yang_keyword_get(ymod) == Y_MODULE || yang_keyword_get(ymod) == Y_SUBMODULE) &&
  	    strcmp(yang_argument_get(ymod), name)==0)
  	    return ymod;
      return NULL;
  }
  
  /*! Callback for handling RFC 7952 annotations
   *
   * A server indicates that it is prepared to handle that annotation according to the
   * annotation's definition.  That is, an annotation advertised by the
   * server may be attached to an instance of a data node defined in any
   * YANG module that is implemented by the server.
   * Possibly add them to yang parsing, cardinality, etc?
   * as described in Section 3.
   * Note this is called by the module using the extension md:annotate, not by 
   * ietf-yang-metadata.yang
   * @see yang_metadata_annotation_check
   */
  static int
  ietf_yang_metadata_extension_cb(clicon_handle h,
  				yang_stmt    *yext,
  				yang_stmt    *ys)
  {
      int        retval = -1;
      char      *extname;
      char      *modname;
      yang_stmt *ymod;
      char      *name;
      
      ymod = ys_module(yext);
      modname = yang_argument_get(ymod);
      extname = yang_argument_get(yext);
      if (strcmp(modname, "ietf-yang-metadata") != 0 || strcmp(extname, "annotation") != 0)
  	goto ok;
      name = cv_string_get(yang_cv_get(ys));
      clicon_debug(1, "%s Enabled extension:%s:%s:%s", __FUNCTION__, modname, extname, name);
      /* XXX Nothing yet - this should signal that xml attribute annotations are allowed 
       * Possibly, add an "annotation" YANG node.
       */
   ok:
      retval = 0;
      // done:
      return retval;
  }
  
  /*! Check annotation extension
   *
   * @param[in]   xa     XML attribute      
   * @param[in]   ys     YANG something
   * @param[out]  ismeta Set to 1 if this is an annotation
   * @retval      0      OK
   * @retval      -1     Error
   * @see ietf_yang_metadata_extension_cb
   * XXX maybe a cache would be appropriate?
   * XXX: return type?
   */
  int
  yang_metadata_annotation_check(cxobj     *xa,
  			       yang_stmt *ymod,
  			       int       *ismeta)
  {
      int        retval = -1;
      yang_stmt *yma = NULL;
      char      *name;
      cg_var    *cv;
  	    
      /* Loop through annotations */
      while ((yma = yn_each(ymod, yma)) != NULL){ 
  	/* Assume here md:annotation is written using canonical prefix */
  	if (yang_keyword_get(yma) != Y_UNKNOWN)
  	    continue;
  	if (strcmp(yang_argument_get(yma), "md:annotation") != 0)
  	    continue;
  	if ((cv = yang_cv_get(yma)) != NULL &&
  	    (name = cv_string_get(cv)) != NULL){
  	    if (strcmp(name, xml_name(xa)) == 0){
  		/* XXX: yang_find(yma,Y_TYPE,0) */
  		*ismeta = 1;
  		break;
  	    }
  	}
      }
      retval = 0;
      // done:
      return retval;
  }
  
  /*! In case ietf-yang-metadata is loaded by application, handle annotation extension 
   * Consider moving fn
   * Must be called after clixon_plugin_module_init
   */
  int
  yang_metadata_init(clicon_handle h)
  {
      int              retval = -1;
      clixon_plugin_t *cp = NULL;
  
  
      /* Create a pseudo-plugin to create extension callback to set the ietf-yang-meta
       * yang-data extension for api-root top-level restconf function.
       */
      if (clixon_pseudo_plugin(h, "pseudo yang metadata", &cp) < 0)
  	goto done;
      clixon_plugin_api_get(cp)->ca_extension = ietf_yang_metadata_extension_cb;
      retval = 0;
   done:
      return retval;
  }
  