In MOP_VALUE_SET the get_modifications_iter() function returns changes in reverse

When the operation is MOP_CREATED or MOP_MODIFIED, then get_modifications_iter() returns all the changes in the correct order, meaning that in the list returned the C_XMLBEGIN tags will be before the C_XMLEND tags. But if the operation is MOP_VALUE_SET I get them in reverse order, C_XMLEND before C_XMLBEGIN.

Does this differ from what is described in the confd_lib_cdb man page regarding cdb_get_modifications_iter()?

The man pages don’t mention anything about the MOP_VALUE_SET being different from the MOP_CREATED or MOP_MODIFIED in the ordering of the get_modifications_iter() list. That is my issue here, I have to reverse the list before I give to my parser.
There is another aspect in which MOP_VALUE_SET modifications need special handling. The list from get_modifications_iter() will contain XMLBEGIN and XMLEND values, their tag will be the name of the leaf that’s changed. It will also contain the actual value with the correct confd_type. Which results in an xml like this:


This makes it seem like the leaf “leaf-name” is inside a container called “leaf-name”.

xml didn’t appear correctly, trying again:


I do not believe there is any benefit in calling cdb_get_modifications_iter() from your iter() function on MOP_VALUE_SET.

Full main() and print_modifications() from a modified iter_c example for reference:

static void print_modifications(confd_tag_value_t *val, int nvals,
                                struct confd_cs_node *start_node,
                                int start_indent)
    int i, indent = start_indent;
    struct confd_cs_node root, *pnode = start_node, *node;
    char tmpbuf[BUFSIZ];
    char *tmp;

    for (i=0; i<nvals; i++) {
        if (indent == start_indent && start_node == NULL) {
            node = confd_find_cs_root(CONFD_GET_TAG_NS(&val[i]));
            root.children = node;
            pnode = &root;
        switch (CONFD_GET_TAG_VALUE(&val[i])->type) {
        case C_XMLBEGIN:
            tmp = "begin";
            if (pnode != NULL)
                pnode = confd_find_cs_node_child(pnode, val[i].tag);
        case C_XMLBEGINDEL:
            tmp = "begin-deleted";
            if (pnode != NULL)
                pnode = confd_find_cs_node_child(pnode, val[i].tag);
        case C_XMLEND:
            tmp = "end";
            if (pnode != NULL)
                pnode = pnode->parent;
            indent -= 2;
        case C_XMLTAG:
            tmp = "created";
        case C_NOEXISTS:
            tmp = "deleted";
            if (pnode == NULL ||
                (node = confd_find_cs_node_child(pnode, val[i].tag)) == NULL ||
                confd_val2str(node->info.type, CONFD_GET_TAG_VALUE(&val[i]),
                              tmpbuf, sizeof(tmpbuf)) == CONFD_ERR) {
                confd_pp_value(tmpbuf, sizeof(tmpbuf),

	if(node != NULL) {
	    char tmpbuf2[BUFSIZ];
            confd_pp_value(tmpbuf2, BUFSIZ, node->info.defval);
        printf("%*s%s default=%s", indent, "", "", tmpbuf2);

            tmp = tmpbuf;
        fprintf(stderr, "%*s%s %s\n", indent, "",
               confd_hash2str(CONFD_GET_TAG_TAG(&val[i])), tmp);
        switch (CONFD_GET_TAG_VALUE(&val[i])->type) {
        case C_XMLBEGIN:
        case C_XMLBEGINDEL:
            indent += 2;

static void free_tag_values(confd_tag_value_t *tv, int n)
    int i;

    for (i = 0; i < n; i++) {

static enum cdb_iter_ret do_iter(confd_hkeypath_t *kp,
                                 enum cdb_iter_op op,
                                 confd_value_t *oldv,
                                 confd_value_t *newv,
                                 void *state)
    char tmppath[BUFSIZ];
    char tmpbuf1[BUFSIZ], tmpbuf2[BUFSIZ];
    char *opstr = "";
    int cdbsock = *((int *)state);
    confd_pp_kpath(tmppath, BUFSIZ, kp);
    switch (op) {
    case MOP_CREATED:   opstr = "created";  break;
    case MOP_DELETED:   opstr = "deleted";  break;
    case MOP_VALUE_SET: opstr = "set";      break;
    case MOP_MODIFIED:  opstr = "modified"; break;
    default: opstr = "?";

    tmpbuf1[0] = tmpbuf2[0] = 0;
    if (oldv) confd_pp_value(tmpbuf1, BUFSIZ, oldv);
    if (newv) confd_pp_value(tmpbuf2, BUFSIZ, newv);

    fprintf(stderr, "%s %s", tmppath, opstr);
    if (oldv || newv) {
        fprintf(stderr, " (%s -> %s)", tmpbuf1, tmpbuf2);
    fprintf(stderr, "\n");
    if (kp->v[0][0].type != C_XMLTAG &&
        (op == MOP_CREATED || op == MOP_MODIFIED)) {
        int nvals;
        confd_tag_value_t *val;
        int rc;
        /* a created or modified list entry */
        rc = cdb_get_modifications_iter(cdbsock, CDB_GET_MODS_INCLUDE_LISTS,
                                        &val, &nvals);
        if (CONFD_OK != rc) {
            fprintf(stderr, "cdb_get_modifications_iter:%s\n",
        fprintf(stderr, "  get_modifications_iter:\n");
        print_modifications(val, nvals, confd_find_cs_node(kp, kp->len), 2);
        free_tag_values(val, nvals);
    return ITER_RECURSE;

int main(int argc, char **argv)
    struct sockaddr_in addr;
    int c, status, subsock, sock;
    int headpoint;
    enum confd_debug_level dbgl = CONFD_TRACE;
    char *confd_addr = "";
    int confd_port = CONFD_PORT;

    while ((c = getopt(argc, argv, "dta:p:")) != EOF) {
        switch (c) {
        case 'd': dbgl = CONFD_DEBUG; break;
        case 's': dbgl = CONFD_SILENT; break;
        case 'a': confd_addr = optarg; break;
        case 'p': confd_port = atoi(optarg); break;

    addr.sin_addr.s_addr = inet_addr(confd_addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(confd_port);

    confd_init(argv[0], stderr, dbgl);

    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
        confd_fatal("%s: Failed to create socket", argv[0]);

    if (confd_load_schemas((struct sockaddr*)&addr,
                           sizeof (struct sockaddr_in)) != CONFD_OK)
        confd_fatal("%s: Failed to load schemas from confd\n", argv[0]);

    if (cdb_connect(sock, CDB_DATA_SOCKET, (struct sockaddr *)&addr,
                    sizeof(struct sockaddr_in)) != CONFD_OK)
        confd_fatal("%s: Failed to connect to ConfD", argv[0]);

    if ((subsock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
        confd_fatal("Failed to open socket\n");

    if (cdb_connect(subsock, CDB_SUBSCRIPTION_SOCKET, (struct sockaddr*)&addr,
                      sizeof (struct sockaddr_in)) < 0)
        confd_fatal("Failed to cdb_connect() to confd \n");

    /* setup subscription point */

    if ((status = cdb_subscribe(subsock, 3, root__ns, &headpoint,
        != CONFD_OK) {
        confd_fatal("Terminate: subscribe %d\n", status);
    if (cdb_subscribe_done(subsock) != CONFD_OK)
        confd_fatal("cdb_subscribe_done() failed");

    while (1) {
        int status;
        struct pollfd set[1];

        set[0].fd = subsock;
        set[0].events = POLLIN;
        set[0].revents = 0;

        if (poll(&set[0], sizeof(set)/sizeof(*set), -1) < 0) {
            if (errno != EINTR) {
                perror("Poll failed:");

        if (set[0].revents & POLLIN) {
            int sub_points[1];
            int reslen;

            if ((status = cdb_read_subscription_socket(subsock,
                                                       &reslen)) != CONFD_OK) {
                confd_fatal("terminate sub_read: %d\n", status);
            if (reslen > 0) {
                fprintf(stderr, "*** Config updated \n");
                cdb_diff_iterate(subsock, sub_points[0], do_iter,
                                 ITER_WANT_PREV, (void*)&subsock);
            if ((status = cdb_sync_subscription_socket(subsock,
                != CONFD_OK) {
                confd_fatal("failed to sync subscription: %d\n", status);

Yes, it would be a very expensive way to collect information that you already have in the arguments to the iter() function. If you want a “tag value” representation of the “modification” of the leaf, you could do

    confd_tag_value_t tv;
    CONFD_SET_TAG_VALUE(&tv, CONFD_GET_XMLTAG(&kp->v[0][0]), newv);
    CONFD_SET_TAG_NS(&tv, CONFD_GET_XMLTAG_NS(&kp->v[0][0]));

But typically you will already have this from a call to cdb_get_modifications_iter() for an ancestor list entry. I.e. when you do call cdb_get_modifications_iter(), you would typically return ITER_CONTINUE rather than ITER_RECURSE from the iter() function, since you don’t want to recurse down into the modifications that you already collected with cdb_get_modifications_iter().

Thank you for your replies. Actually I’m using the python api so I don’t have the cool macros. But I am using ITER_CONTINUE not ITER_RECURSE.
This would be a peculiarity of my app that needs path+xml when a value is updated or created. I already have created reverse_list and skip_same_tag_name to workaround this… I was hoping for a more uniform way for all operations.


For ConfD Python API questions, I recommend reaching out to the Tail-f support.