/* * Copyright (c) 2024 Jiri Svoboda * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup libsif * @{ */ /** * @file Structured Information Format * * Structured Information Format (SIF) is an API that allows an application * to maintain data in a persistent repository in a format that is * structured (and hence extensible). * * SIF is meant to be used as the basis for the storage backend used to * maintain application or configuration data. SIF is *not* a (relational) * database (not even close). The structure of a SIF repository is quite * similar to an XML document that contains just tags with attributes * (but no text). */ #include #include #include #include #include #include #include #include #include "../include/sif.h" #include "../private/sif.h" static errno_t sif_export_node(sif_node_t *, FILE *); static errno_t sif_import_node(sif_node_t *, FILE *, sif_node_t **, bool *); static sif_attr_t *sif_node_first_attr(sif_node_t *); static sif_attr_t *sif_node_next_attr(sif_attr_t *); static void sif_attr_delete(sif_attr_t *); static void *sif_attr_getkey(odlink_t *); static int sif_attr_cmp(void *, void *); /** Determine if character can start a name. * * @param c Character * @return @c true iff the character can start a name */ static bool sif_is_name_start_char(char c) { return isalpha(c) || c == '_'; } /** Determine if character can continue a name. * * @param c Character * @return @c true iff the character can continue a name */ static bool sif_is_name_char(char c) { return isalnum(c) || c == '-' || c == '.'; } /** Create new SIF node. * * @param parent Parent node * @return Pointer to new node on success or @c NULL if out of memory */ static sif_node_t *sif_node_new(sif_node_t *parent) { sif_node_t *node; node = calloc(1, sizeof(sif_node_t)); if (node == NULL) return NULL; node->parent = parent; odict_initialize(&node->attrs, sif_attr_getkey, sif_attr_cmp); list_initialize(&node->children); return node; } /** Delete SIF node. * * Delete a SIF node that has been already unlinked from the tree. * This will also delete any attributes or child nodes. * * @param node Node */ static void sif_node_delete(sif_node_t *node) { sif_attr_t *attr; sif_node_t *child; if (node == NULL) return; assert(!link_used(&node->lparent)); if (node->ntype != NULL) free(node->ntype); attr = sif_node_first_attr(node); while (attr != NULL) { odict_remove(&attr->lattrs); sif_attr_delete(attr); attr = sif_node_first_attr(node); } child = sif_node_first_child(node); while (child != NULL) { list_remove(&child->lparent); sif_node_delete(child); child = sif_node_first_child(node); } free(node); } /** Create new SIF attribute. * * @param node Containing node * @return Pointer to new node on success or @c NULL if out of memory */ static sif_attr_t *sif_attr_new(sif_node_t *node) { sif_attr_t *attr; attr = calloc(1, sizeof(sif_attr_t)); if (attr == NULL) return NULL; attr->node = node; return attr; } /** Delete SIF attribute. * * Delete a SIF attribute that has been already unlinked from is node. * * @param attr Attribute */ static void sif_attr_delete(sif_attr_t *attr) { if (attr == NULL) return; assert(!odlink_used(&attr->lattrs)); if (attr->aname != NULL) free(attr->aname); if (attr->avalue != NULL) free(attr->avalue); free(attr); } /** Create SIF document. * * @param rdoc Place to store pointer to new document. * * @return EOK on success or error code */ errno_t sif_new(sif_doc_t **rdoc) { sif_doc_t *doc; sif_node_t *root = NULL; errno_t rc; doc = calloc(1, sizeof(sif_doc_t)); if (doc == NULL) return ENOMEM; root = sif_node_new(NULL); if (root == NULL) { rc = ENOMEM; goto error; } root->ntype = str_dup("sif"); if (root->ntype == NULL) { rc = ENOMEM; goto error; } doc->root = root; *rdoc = doc; return EOK; error: sif_node_delete(root); free(doc); return rc; } /** Load SIF document. * * @param fname File name * @param rdoc Place to store pointer to new document. * * @return EOK on success or error code */ errno_t sif_load(const char *fname, sif_doc_t **rdoc) { sif_doc_t *doc; sif_node_t *root = NULL; errno_t rc; bool endtag; FILE *f = NULL; doc = calloc(1, sizeof(sif_doc_t)); if (doc == NULL) return ENOMEM; doc->fname = str_dup(fname); if (doc->fname == NULL) { rc = ENOMEM; goto error; } f = fopen(fname, "r"); if (f == NULL) { rc = EIO; goto error; } rc = sif_import_node(NULL, f, &root, &endtag); if (rc != EOK || endtag == true) goto error; if (str_cmp(root->ntype, "sif") != 0) { rc = EIO; goto error; } fclose(f); doc->root = root; *rdoc = doc; return EOK; error: sif_node_delete(root); free(doc); if (f != NULL) fclose(f); return rc; } /** Delete SIF document. * * @param doc SIF document * @return EOK on success or error code */ void sif_delete(sif_doc_t *doc) { sif_node_delete(doc->root); free(doc); } /** Return root node. * * @param doc SIF document */ sif_node_t *sif_get_root(sif_doc_t *doc) { return doc->root; } /** Get first child of a node. * * @param parent Parent node * @return First child node or @c NULL if @a parent has no children */ sif_node_t *sif_node_first_child(sif_node_t *parent) { link_t *link; link = list_first(&parent->children); if (link == NULL) return NULL; return list_get_instance(link, sif_node_t, lparent); } /** Get next child of a node. * * @param current Current node * @return Next child (i.e. next sibling of @a current) */ sif_node_t *sif_node_next_child(sif_node_t *current) { link_t *link; link = list_next(¤t->lparent, ¤t->parent->children); if (link == NULL) return NULL; return list_get_instance(link, sif_node_t, lparent); } /** Get node type. * * @param node SIF node * @return Pointer to string, valid until next modification. */ const char *sif_node_get_type(sif_node_t *node) { return node->ntype; } /** Get node attribute. * * @param node SIF node * @param aname Attribute name * * @return Attribute value or @c NULL if attribute is not set */ const char *sif_node_get_attr(sif_node_t *node, const char *aname) { odlink_t *link; sif_attr_t *attr; link = odict_find_eq(&node->attrs, (void *)aname, NULL); if (link == NULL) return NULL; attr = odict_get_instance(link, sif_attr_t, lattrs); return attr->avalue; } /** Save SIF document to file. * * * @param doc SIF document * @param fname File name * @return EOK on success or error code */ errno_t sif_save(sif_doc_t *doc, const char *fname) { FILE *f = NULL; errno_t rc; f = fopen(fname, "w"); if (f == NULL) { rc = EIO; goto error; } rc = sif_export_node(doc->root, f); if (rc != EOK) goto error; if (fflush(f) == EOF) { rc = EIO; goto error; } fclose(f); return EOK; error: if (f != NULL) fclose(f); return rc; } /** Prepend new child. * * Create a new child and prepend it at the beginning of children list of * @a parent. * * @param parent Parent node * @param ctype Child type * @param rchild Place to store pointer to new child * * @return EOK on success or ENOMEM if out of memory */ errno_t sif_node_prepend_child(sif_node_t *parent, const char *ctype, sif_node_t **rchild) { sif_node_t *child; child = sif_node_new(parent); if (child == NULL) return ENOMEM; child->ntype = str_dup(ctype); if (child->ntype == NULL) { sif_node_delete(child); return ENOMEM; } list_prepend(&child->lparent, &parent->children); *rchild = child; return EOK; } /** Append new child. * * Create a new child and append it at the end of children list of @a parent. * * @param parent Parent node * @param ctype Child type * @param rchild Place to store pointer to new child * * @return EOK on success or ENOMEM if out of memory */ errno_t sif_node_append_child(sif_node_t *parent, const char *ctype, sif_node_t **rchild) { sif_node_t *child; child = sif_node_new(parent); if (child == NULL) return ENOMEM; child->ntype = str_dup(ctype); if (child->ntype == NULL) { sif_node_delete(child); return ENOMEM; } list_append(&child->lparent, &parent->children); *rchild = child; return EOK; } /** Insert new child before existing child. * * Create a new child and insert it before an existing child. * * @param sibling Sibling before which to insert * @param ctype Child type * @param rchild Place to store pointer to new child * * @return EOK on success or ENOMEM if out of memory */ errno_t sif_node_insert_before(sif_node_t *sibling, const char *ctype, sif_node_t **rchild) { sif_node_t *child; child = sif_node_new(sibling->parent); if (child == NULL) return ENOMEM; child->ntype = str_dup(ctype); if (child->ntype == NULL) { sif_node_delete(child); return ENOMEM; } list_insert_before(&child->lparent, &sibling->lparent); *rchild = child; return EOK; } /** Insert new child after existing child. * * Create a new child and insert it after an existing child. * * @param sibling Sibling after which to insert * @param ctype Child type * @param rchild Place to store pointer to new child * * @return EOK on success or ENOMEM if out of memory */ errno_t sif_node_insert_after(sif_node_t *sibling, const char *ctype, sif_node_t **rchild) { sif_node_t *child; child = sif_node_new(sibling->parent); if (child == NULL) return ENOMEM; child->ntype = str_dup(ctype); if (child->ntype == NULL) { sif_node_delete(child); return ENOMEM; } list_insert_after(&child->lparent, &sibling->lparent); *rchild = child; return EOK; } /** Destroy SIF node. * * @param node Node to destroy */ void sif_node_destroy(sif_node_t *node) { list_remove(&node->lparent); sif_node_delete(node); } /** Set node attribute. * * @param node SIF node * @param aname Attribute name * @param value Attribute value * * @return EOK on success, ENOMEM if out of memory */ errno_t sif_node_set_attr(sif_node_t *node, const char *aname, const char *avalue) { odlink_t *link; sif_attr_t *attr; char *cvalue; link = odict_find_eq(&node->attrs, (void *)aname, NULL); if (link != NULL) { attr = odict_get_instance(link, sif_attr_t, lattrs); cvalue = str_dup(avalue); if (cvalue == NULL) return ENOMEM; free(attr->avalue); attr->avalue = cvalue; } else { attr = sif_attr_new(node); if (attr == NULL) return ENOMEM; attr->aname = str_dup(aname); if (attr->aname == NULL) { sif_attr_delete(attr); return ENOMEM; } attr->avalue = str_dup(avalue); if (attr->avalue == NULL) { sif_attr_delete(attr); return ENOMEM; } odict_insert(&attr->lattrs, &node->attrs, NULL); } return EOK; } /** Unset node attribute. * * @param node Node * @param aname Attribute name */ void sif_node_unset_attr(sif_node_t *node, const char *aname) { odlink_t *link; sif_attr_t *attr; link = odict_find_eq(&node->attrs, (void *)aname, NULL); if (link == NULL) return; attr = odict_get_instance(link, sif_attr_t, lattrs); odict_remove(link); sif_attr_delete(attr); } /** Export node name to file. * * Export node name to file. * * @param str String * @param f File * @return EOK on success, EIO on I/O error */ static errno_t sif_export_name(const char *str, FILE *f) { if (fputs(str, f) == EOF) return EIO; return EOK; } /** Export string to file. * * Export string to file (the string is double-quoted and escaped). * * @param str String * @param f File * @return EOK on success, EIO on I/O error */ static errno_t sif_export_string(const char *str, FILE *f) { const char *cp; if (fputc('"', f) == EOF) return EIO; cp = str; while (*cp != '\0') { if (*cp == '<') { if (fputs("<", f) == EOF) return EIO; } else if (*cp == '"') { if (fputs(""", f) == EOF) return EIO; } else { if (fputc(*cp, f) == EOF) return EIO; } ++cp; } if (fputc('"', f) == EOF) return EIO; return EOK; } /** Read characters from file, make sure they match the specified sequence. * * @param f File * @param chars Expected sequence of characters to be read * * @return EOK on success, EIO on I/O error or mismatch */ static errno_t sif_get_verify_chars(FILE *f, const char *chars) { const char *cp; char c; cp = chars; while (*cp != '\0') { c = fgetc(f); if (c != *cp) return EIO; ++cp; } return EOK; } /** Import name from file. * * * @param f File * @param rstr Place to store pointer to newly allocated string * @return EOK on success, EIO on I/O error */ static errno_t sif_import_name(FILE *f, char **rstr) { char *str; char *nstr; size_t str_size; size_t sidx; int c; errno_t rc; str_size = 1; sidx = 0; str = malloc(str_size + 1); if (str == NULL) return ENOMEM; c = fgetc(f); if (!sif_is_name_start_char(c)) { rc = EIO; goto error; } while (true) { if (sidx >= str_size) { str_size *= 2; nstr = realloc(str, str_size + 1); if (nstr == NULL) { rc = ENOMEM; goto error; } str = nstr; } str[sidx++] = c; c = fgetc(f); if (!sif_is_name_char(c)) break; } ungetc(c, f); str[sidx] = '\0'; *rstr = str; return EOK; error: free(str); return rc; } /** Import string from file. * * Import string from file (the string in the file must be * properly quoted and escaped). * * @param f File * @param rstr Place to store pointer to newly allocated string * @return EOK on success, EIO on I/O error */ static errno_t sif_import_string(FILE *f, char **rstr) { char *str; char *nstr; size_t str_size; size_t sidx; int c; errno_t rc; str_size = 1; sidx = 0; str = malloc(str_size + 1); if (str == NULL) return ENOMEM; c = fgetc(f); if (c != '"') { rc = EIO; goto error; } while (true) { c = fgetc(f); if (c == EOF) { rc = EIO; goto error; } if (c == '"') break; if (c == '&') { c = fgetc(f); if (c == EOF) { rc = EIO; goto error; } if (c == 'q') { rc = sif_get_verify_chars(f, "uot;"); if (rc != EOK) goto error; } else if (c == 'l') { rc = sif_get_verify_chars(f, "t;"); if (rc != EOK) goto error; } else { rc = EIO; goto error; } } if (sidx >= str_size) { str_size *= 2; nstr = realloc(str, str_size + 1); if (nstr == NULL) { rc = ENOMEM; goto error; } str = nstr; } str[sidx++] = c; } str[sidx] = '\0'; *rstr = str; return EOK; error: free(str); return rc; } /** Import SIF attribute from file. * * @param node Node under which attribute shou * @param f File * @param rattr Place to store pointer to imported SIF attribute * @return EOK on success, EIO on I/O error */ static errno_t sif_import_attr(sif_node_t *node, FILE *f, sif_attr_t **rattr) { errno_t rc; char *aname = NULL; char *avalue = NULL; sif_attr_t *attr; int c; rc = sif_import_name(f, &aname); if (rc != EOK) goto error; c = fgetc(f); if (c != '=') { rc = EIO; goto error; } rc = sif_import_string(f, &avalue); if (rc != EOK) goto error; attr = sif_attr_new(node); if (attr == NULL) { rc = ENOMEM; goto error; } attr->aname = aname; attr->avalue = avalue; *rattr = attr; return EOK; error: if (aname != NULL) free(aname); if (avalue != NULL) free(avalue); return rc; } /** Export SIF attribute to file. * * @param attr SIF attribute * @param f File * @return EOK on success, EIO on I/O error */ static errno_t sif_export_attr(sif_attr_t *attr, FILE *f) { errno_t rc; rc = sif_export_name(attr->aname, f); if (rc != EOK) return rc; if (fputc('=', f) == EOF) return EIO; rc = sif_export_string(attr->avalue, f); if (rc != EOK) return rc; return EOK; } /** Export SIF node to file. * * @param node SIF node * @param f File * @return EOK on success, EIO on I/O error */ static errno_t sif_export_node(sif_node_t *node, FILE *f) { errno_t rc; sif_attr_t *attr; sif_node_t *child; if (fputc('<', f) == EOF) return EIO; rc = sif_export_name(node->ntype, f); if (rc != EOK) return rc; /* Attributes */ attr = sif_node_first_attr(node); while (attr != NULL) { if (fputc(' ', f) == EOF) return EIO; rc = sif_export_attr(attr, f); if (rc != EOK) return rc; attr = sif_node_next_attr(attr); } if (fputs(">\n", f) == EOF) return EIO; /* Child nodes */ child = sif_node_first_child(node); while (child != NULL) { rc = sif_export_node(child, f); if (rc != EOK) return rc; child = sif_node_next_child(child); } if (fputs("ntype, f); if (rc != EOK) return rc; if (fputs(">\n", f) == EOF) return EIO; return EOK; } /** Import SIF node from file. * * @param parent Parent node * @param f File * @param rnode Place to store pointer to imported node * @param rendtag Place to store @c true iff end tag is encountered * @return EOK on success, EIO on I/O error */ static errno_t sif_import_node(sif_node_t *parent, FILE *f, sif_node_t **rnode, bool *rendtag) { errno_t rc; sif_node_t *node = NULL; sif_node_t *child; sif_attr_t *attr = NULL; bool endtag; bool cendtag; char *ntype; int c; node = sif_node_new(parent); if (node == NULL) return ENOMEM; c = fgetc(f); while (isspace(c)) c = fgetc(f); if (c != '<') { rc = EIO; goto error; } c = fgetc(f); if (c == '/') { endtag = true; } else { endtag = false; ungetc(c, f); } rc = sif_import_name(f, &ntype); if (rc != EOK) goto error; node->ntype = ntype; /* Attributes */ c = fgetc(f); if (c == EOF) { rc = EIO; goto error; } while (c != '>') { /* End tags cannot have attributes */ if (endtag) { rc = EIO; goto error; } while (isspace(c)) c = fgetc(f); ungetc(c, f); rc = sif_import_attr(node, f, &attr); if (rc != EOK) goto error; odict_insert(&attr->lattrs, &node->attrs, NULL); c = fgetc(f); if (c == EOF) { rc = EIO; goto error; } } /* Child nodes */ if (!endtag) { while (true) { rc = sif_import_node(node, f, &child, &cendtag); if (rc != EOK) goto error; if (cendtag) { sif_node_delete(child); break; } list_append(&child->lparent, &node->children); } } *rnode = node; *rendtag = endtag; return EOK; error: sif_node_delete(node); return rc; } /** Get first attribute or a node. * * @param node SIF node * @return First attribute or @c NULL if there is none */ static sif_attr_t *sif_node_first_attr(sif_node_t *node) { odlink_t *link; link = odict_first(&node->attrs); if (link == NULL) return NULL; return odict_get_instance(link, sif_attr_t, lattrs); } /** Get next attribute or a node. * * @param cur Current attribute * @return Next attribute or @c NULL if there is none */ static sif_attr_t *sif_node_next_attr(sif_attr_t *cur) { odlink_t *link; link = odict_next(&cur->lattrs, &cur->node->attrs); if (link == NULL) return NULL; return odict_get_instance(link, sif_attr_t, lattrs); } /** Get key callback for ordered dictionary of node attributes. * * @param link Ordered dictionary link of attribute * @return Pointer to attribute name */ static void *sif_attr_getkey(odlink_t *link) { return (void *)odict_get_instance(link, sif_attr_t, lattrs)->aname; } /** Comparison callback for ordered dictionary of node attributes. * * @param a Name of first attribute * @param b Name of second attribute * @return Less than zero, zero or greater than zero, if a < b, a == b, a > b, * respectively. */ static int sif_attr_cmp(void *a, void *b) { char *ca, *cb; ca = (char *)a; cb = (char *)b; return str_cmp(ca, cb); } /** @} */