Can I use cdb_get_modifications() to extend the 1-2-3 intro example to display subscriber changes?

It is quite easy to just call the cdb_get_modifications() function after reception of a subscription notification to retrieve all the changes that caused the subscription notification.

To implement such a solution, all that has to be done to the intro/1-2-3-start-query-model example is to add the get modifications call and some printout helpers. The result would be something like this:

Quick demo:

Terminal 1:

$ pwd
/Users/demo/tailf/confd-6.0/examples.confd/intro/1-2-3-start-query-model
$ make clean all start
...

Terminal 2 demo configuration:

$ make cli
...
> config
...
% set dhcp SharedNetworks sharedNetwork test SubNets subNet 127.0.0.1 255.255.255.0 routers test 
...
% set dhcp SharedNetworks sharedNetwork test SubNets subNet 127.0.0.1 255.255.255.0 maxLeaseTime 5s
...
% commit

Terminal 1 printout:

...
Modifications committed:
SharedNetworks begin
  sharedNetwork begin
    name test
    SubNets begin
      subNet begin
        net 127.0.0.1
        mask 255.255.255.0
        maxLeaseTime PT5S.0
        routers test
      subNet end
    SubNets end
  sharedNetwork end
SharedNetworks end
...

Full main() and print_modifications() from the modified 1-2-3 intro example:

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);
            break;
        case C_XMLBEGINDEL:
            tmp = "begin-deleted";
            if (pnode != NULL)
                pnode = confd_find_cs_node_child(pnode, val[i].tag);
            break;
        case C_XMLEND:
            tmp = "end";
            if (pnode != NULL)
                pnode = pnode->parent;
            indent -= 2;
            break;
        case C_XMLTAG:
            tmp = "created";
            break;
        case C_NOEXISTS:
            tmp = "deleted";
            break;
        default:
            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),
                               CONFD_GET_TAG_VALUE(&val[i]));
            }
            tmp = tmpbuf;
        }
        printf("%*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;
            break;
        default:
            break;
        }
    }
}

int main(int argc, char **argv)
{
    struct sockaddr_in addr;
    int subsock;
    int status;
    int spoint;

    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(CONFD_PORT);

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

    if (confd_load_schemas((struct sockaddr*)&addr,
                            sizeof (struct sockaddr_in)) != CONFD_OK)
        confd_fatal("Failed to load schemas from confd\n");
    /*                                                                                                                                                                             
     * Setup subscriptions                                                                                                                                                         
     */
    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");

    if ((status = cdb_subscribe(subsock, 3, dhcpd__ns, &spoint, "/dhcp"))
        != CONFD_OK) {
        fprintf(stderr, "Terminate: subscribe %d\n", status);
        exit(0);
    }
    if (cdb_subscribe_done(subsock) != CONFD_OK)
        confd_fatal("cdb_subscribe_done() failed");
    printf("Subscription point = %d\n", spoint);

    /*                                                                                                                                                                             
     * Read initial config                                                                                                                                                         
     */
    if ((status = read_conf(&addr)) != CONFD_OK) {
        fprintf(stderr, "Terminate: read_conf %d\n", status);
        exit(0);
    }
    rename("dhcpd.conf.tmp", "dhcpd.conf");
    /* This is the place to HUP the daemon */

    while (1) {
        static int poll_fail_counter=0;
        struct pollfd set[1];

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

        if (poll(&set[0], 1, -1) < 0) {
            perror("Poll failed:");
            if(++poll_fail_counter < 10)
                continue;
            fprintf(stderr, "Too many poll failures, terminating\n");
            exit(1);
        }

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

            if ((status = cdb_read_subscription_socket(subsock,
                                                       &sub_points[0],
                                                       &reslen)) != CONFD_OK) {
                fprintf(stderr, "terminate sub_read: %d\n", status);
                exit(1);
            }
            if (reslen > 0) {
                confd_tag_value_t *values;
                int nvalues, i;
                if ((status = cdb_get_modifications(subsock,
                                                    sub_points[0],
                                                    CDB_GET_MODS_INCLUDE_LISTS,
                                                    &values,
                                                    &nvalues,
                                                    "/dhcp")) == CONFD_OK) {
                    printf("Modifications to subscription committed:\n");
                    print_modifications(values, nvalues, NULL, 0);
                    for (i = 0; i < nvalues; i++)
                        confd_free_value(CONFD_GET_TAG_VALUE(&values[i]));
                    free(values);
                } else {
                    fprintf(stderr, "failed to get modifications %d\n", status);
                }

                if ((status = read_conf(&addr)) != CONFD_OK) {
                    fprintf(stderr, "Terminate: read_conf %d\n", status);
                    exit(1);
                }
            }
            fprintf(stderr, "Read new config, updating dhcpd config \n");
            rename("dhcpd.conf.tmp", "dhcpd.conf");
            /* this is the place to HUP the daemon */

            if ((status = cdb_sync_subscription_socket(subsock,
                                                       CDB_DONE_PRIORITY))
                != CONFD_OK) {
                fprintf(stderr, "failed to sync subscription: %d\n", status);
                exit(1);
            }
        }
    }
}