+static int cf_ci_replace_child (oconfig_item_t *dst, oconfig_item_t *src,
+ int offset)
+{
+ oconfig_item_t *temp;
+ int i;
+
+ assert (offset >= 0);
+ assert (dst->children_num > offset);
+
+ /* Free the memory used by the replaced child. Usually that's the
+ * `Include "blah"' statement. */
+ temp = dst->children + offset;
+ for (i = 0; i < temp->values_num; i++)
+ {
+ if (temp->values[i].type == OCONFIG_TYPE_STRING)
+ {
+ sfree (temp->values[i].value.string);
+ }
+ }
+ sfree (temp->values);
+ temp = NULL;
+
+ /* If (src->children_num == 0) the array size is decreased. If offset
+ * is _not_ the last element, (offset < (src->children_num - 1)), then
+ * we need to move the trailing elements before resizing the array. */
+ if ((src->children_num == 0) && (offset < (src->children_num - 1)))
+ {
+ int nmemb = src->children_num - (offset + 1);
+ memmove (src->children + offset, src->children + offset + 1,
+ sizeof (oconfig_item_t) * nmemb);
+ }
+
+ /* Resize the memory containing the children to be big enough to hold
+ * all children. */
+ temp = (oconfig_item_t *) realloc (dst->children,
+ sizeof (oconfig_item_t)
+ * (dst->children_num + src->children_num - 1));
+ if (temp == NULL)
+ {
+ ERROR ("configfile: realloc failed.");
+ return (-1);
+ }
+ dst->children = temp;
+
+ /* If there are children behind the include statement, and they have
+ * not yet been moved because (src->children_num == 0), then move them
+ * to the end of the list, so that the new children have room before
+ * them. */
+ if ((src->children_num > 0)
+ && ((dst->children_num - (offset + 1)) > 0))
+ {
+ int nmemb = dst->children_num - (offset + 1);
+ int old_offset = offset + 1;
+ int new_offset = offset + src->children_num;
+
+ memmove (dst->children + new_offset,
+ dst->children + old_offset,
+ sizeof (oconfig_item_t) * nmemb);
+ }
+
+ /* Last but not least: If there are new childrem, copy them to the
+ * memory reserved for them. */
+ if (src->children_num > 0)
+ {
+ memcpy (dst->children + offset,
+ src->children,
+ sizeof (oconfig_item_t) * src->children_num);
+ }
+
+ /* Update the number of children. */
+ dst->children_num += (src->children_num - 1);
+
+ return (0);
+} /* int cf_ci_replace_child */
+
+static int cf_ci_append_children (oconfig_item_t *dst, oconfig_item_t *src)
+{
+ oconfig_item_t *temp;
+
+ if ((src == NULL) || (src->children_num == 0))
+ return (0);
+
+ temp = (oconfig_item_t *) realloc (dst->children,
+ sizeof (oconfig_item_t)
+ * (dst->children_num + src->children_num));
+ if (temp == NULL)
+ {
+ ERROR ("configfile: realloc failed.");
+ return (-1);
+ }
+ dst->children = temp;
+
+ memcpy (dst->children + dst->children_num,
+ src->children,
+ sizeof (oconfig_item_t)
+ * src->children_num);
+ dst->children_num += src->children_num;
+
+ return (0);
+} /* int cf_ci_append_children */
+
+#define CF_MAX_DEPTH 8
+static oconfig_item_t *cf_read_generic (const char *path, int depth);
+
+static int cf_include_all (oconfig_item_t *root, int depth)
+{
+ int i;
+
+ for (i = 0; i < root->children_num; i++)
+ {
+ oconfig_item_t *new;
+ oconfig_item_t *old;
+
+ /* Ignore all blocks, including `Include' blocks. */
+ if (root->children[i].children_num != 0)
+ continue;
+
+ if (strcasecmp (root->children[i].key, "Include") != 0)
+ continue;
+
+ old = root->children + i;
+
+ if ((old->values_num != 1)
+ || (old->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ ERROR ("configfile: `Include' needs exactly one string argument.");
+ continue;
+ }
+
+ new = cf_read_generic (old->values[0].value.string, depth + 1);
+ if (new == NULL)
+ continue;
+
+ /* Now replace the i'th child in `root' with `new'. */
+ cf_ci_replace_child (root, new, i);
+
+ sfree (new->values);
+ sfree (new);
+ } /* for (i = 0; i < root->children_num; i++) */
+
+ return (0);
+} /* int cf_include_all */
+
+static oconfig_item_t *cf_read_file (const char *file, int depth)
+{
+ oconfig_item_t *root;
+
+ assert (depth < CF_MAX_DEPTH);
+
+ root = oconfig_parse_file (file);
+ if (root == NULL)
+ {
+ ERROR ("configfile: Cannot read file `%s'.", file);
+ return (NULL);
+ }
+
+ cf_include_all (root, depth);
+
+ return (root);
+} /* oconfig_item_t *cf_read_file */
+
+static int cf_compare_string (const void *p1, const void *p2)
+{
+ return strcmp (*(const char **) p1, *(const char **) p2);
+}
+
+static oconfig_item_t *cf_read_dir (const char *dir, int depth)
+{
+ oconfig_item_t *root = NULL;
+ DIR *dh;
+ struct dirent *de;
+ char **filenames = NULL;
+ int filenames_num = 0;
+ int status;
+ int i;
+
+ assert (depth < CF_MAX_DEPTH);
+
+ dh = opendir (dir);
+ if (dh == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("configfile: opendir failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (NULL);
+ }
+
+ root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t));
+ if (root == NULL)
+ {
+ ERROR ("configfile: malloc failed.");
+ return (NULL);
+ }
+ memset (root, '\0', sizeof (oconfig_item_t));
+
+ while ((de = readdir (dh)) != NULL)
+ {
+ char name[1024];
+ char **tmp;
+
+ if ((de->d_name[0] == '.') || (de->d_name[0] == '\0'))
+ continue;
+
+ status = ssnprintf (name, sizeof (name), "%s/%s",
+ dir, de->d_name);
+ if ((status < 0) || ((size_t) status >= sizeof (name)))
+ {
+ ERROR ("configfile: Not including `%s/%s' because its"
+ " name is too long.",
+ dir, de->d_name);
+ for (i = 0; i < filenames_num; ++i)
+ free (filenames[i]);
+ free (filenames);
+ free (root);
+ return (NULL);
+ }
+
+ ++filenames_num;
+ tmp = (char **) realloc (filenames,
+ filenames_num * sizeof (*filenames));
+ if (tmp == NULL) {
+ ERROR ("configfile: realloc failed.");
+ for (i = 0; i < filenames_num - 1; ++i)
+ free (filenames[i]);
+ free (filenames);
+ free (root);
+ return (NULL);
+ }
+ filenames = tmp;
+
+ filenames[filenames_num - 1] = sstrdup (name);
+ }
+
+ qsort ((void *) filenames, filenames_num, sizeof (*filenames),
+ cf_compare_string);
+
+ for (i = 0; i < filenames_num; ++i)
+ {
+ oconfig_item_t *temp;
+ char *name = filenames[i];
+
+ temp = cf_read_generic (name, depth);
+ if (temp == NULL) {
+ int j;
+ for (j = i; j < filenames_num; ++j)
+ free (filenames[j]);
+ free (filenames);
+ oconfig_free (root);
+ return (NULL);
+ }
+
+ cf_ci_append_children (root, temp);
+ sfree (temp->children);
+ sfree (temp);
+
+ free (name);
+ }
+
+ free(filenames);
+ return (root);
+} /* oconfig_item_t *cf_read_dir */
+
+/*
+ * cf_read_generic
+ *
+ * Path is stat'ed and either cf_read_file or cf_read_dir is called
+ * accordingly.
+ *
+ * There are two versions of this function: If `wordexp' exists shell wildcards
+ * will be expanded and the function will include all matches found. If
+ * `wordexp' (or, more precisely, it's header file) is not available the
+ * simpler function is used which does not do any such expansion.
+ */
+#if HAVE_WORDEXP_H
+static oconfig_item_t *cf_read_generic (const char *path, int depth)
+{
+ oconfig_item_t *root = NULL;
+ int status;
+ const char *path_ptr;
+ wordexp_t we;
+ size_t i;
+
+ if (depth >= CF_MAX_DEPTH)
+ {
+ ERROR ("configfile: Not including `%s' because the maximum "
+ "nesting depth has been reached.", path);
+ return (NULL);
+ }
+
+ status = wordexp (path, &we, WRDE_NOCMD);
+ if (status != 0)
+ {
+ ERROR ("configfile: wordexp (%s) failed.", path);
+ return (NULL);
+ }
+
+ root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t));
+ if (root == NULL)
+ {
+ ERROR ("configfile: malloc failed.");
+ return (NULL);
+ }
+ memset (root, '\0', sizeof (oconfig_item_t));
+
+ /* wordexp() might return a sorted list already. That's not
+ * documented though, so let's make sure we get what we want. */
+ qsort ((void *) we.we_wordv, we.we_wordc, sizeof (*we.we_wordv),
+ cf_compare_string);
+
+ for (i = 0; i < we.we_wordc; i++)
+ {
+ oconfig_item_t *temp;
+ struct stat statbuf;
+
+ path_ptr = we.we_wordv[i];
+
+ status = stat (path_ptr, &statbuf);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("configfile: stat (%s) failed: %s",
+ path_ptr,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ oconfig_free (root);
+ return (NULL);
+ }
+
+ if (S_ISREG (statbuf.st_mode))
+ temp = cf_read_file (path_ptr, depth);
+ else if (S_ISDIR (statbuf.st_mode))
+ temp = cf_read_dir (path_ptr, depth);
+ else
+ {
+ ERROR ("configfile: %s is neither a file nor a "
+ "directory.", path);
+ continue;
+ }
+
+ if (temp == NULL) {
+ oconfig_free (root);
+ return (NULL);
+ }
+
+ cf_ci_append_children (root, temp);
+ sfree (temp->children);
+ sfree (temp);
+ }
+
+ wordfree (&we);
+
+ return (root);
+} /* oconfig_item_t *cf_read_generic */
+/* #endif HAVE_WORDEXP_H */
+
+#else /* if !HAVE_WORDEXP_H */
+static oconfig_item_t *cf_read_generic (const char *path, int depth)
+{
+ struct stat statbuf;
+ int status;
+
+ if (depth >= CF_MAX_DEPTH)
+ {
+ ERROR ("configfile: Not including `%s' because the maximum "
+ "nesting depth has been reached.", path);
+ return (NULL);
+ }
+
+ status = stat (path, &statbuf);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("configfile: stat (%s) failed: %s",
+ path,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (NULL);
+ }
+
+ if (S_ISREG (statbuf.st_mode))
+ return (cf_read_file (path, depth));
+ else if (S_ISDIR (statbuf.st_mode))
+ return (cf_read_dir (path, depth));
+
+ ERROR ("configfile: %s is neither a file nor a directory.", path);
+ return (NULL);
+} /* oconfig_item_t *cf_read_generic */
+#endif /* !HAVE_WORDEXP_H */
+