On 10/08/2014 04:31 AM, Lukas Slebodnik wrote:
On (29/09/14 07:45), Dmitri Pal wrote:
On 09/10/2014 11:31 PM, Dmitri Pal wrote:
Hello,
Extensive travel in recent months allowed me to finish this code.
Here is the updated design:
https://fedorahosted.org/sssd/wiki/DesignDocs/ding-libs/INIConfigMerge
Patches:
0001 - Fix in case the Ref array is empty and we need to print/debug it
0002 - Declaration of the new function to do access checks
0003 - Big patch with core functionality
0004 - Updated access check code to use new internal access control
function
0005 - File with expected output for unit test validation
0006 - Makefile and related changes to start building new code
I am attaching a new patch on top of the other patches.
It can be squashed with patch 3 after review.
It improves sorting and scanning. See patch comment.
No rush, take your time. :-)
_______________________________________________
sssd-devel mailing list
sssd-devel@lists.fedorahosted.org
https://lists.fedorahosted.org/mailman/listinfo/sssd-devel
--
Thank you,
Dmitri Pal
Sr. Engineering Manager IdM portfolio
Red Hat, Inc.
>From aff0f323b2996df7a8f1539f46ca651d2d9fd5e8 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 16:38:19 +0200
Subject: [PATCH 1/6] [RA] Print info when array is empty
---
refarray/ref_array.c | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
ACK
>From 9f80182fecbf1c1800a22ff279bf8915f4196f42 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 16:39:58 +0200
Subject: [PATCH 2/6] [INI] Declaring new internal access check function
---
ini/ini_config_priv.h | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/ini/ini_config_priv.h b/ini/ini_config_priv.h
ACK
>From 8f0209cb995fd88e71e96efb0b98a0256df58d5d Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 16:43:31 +0200
Subject: [PATCH 4/6] [INI] Refactored access control check
The patch includes implementation of the new internal function.
---
ini/ini_fileobj.c | 66 +++++++++++++++++++++++++++++++++++++---------------
1 files changed, 47 insertions(+), 19 deletions(-)
diff --git a/ini/ini_fileobj.c b/ini/ini_fileobj.c
ACK,
but it would be better to swap order of 3rd patch and this patch (4th)
reason :
* the 2nd patch add declaration to the private header file
* the 3rd patch use this function
* the 4th patch add definition of this function.
>From 09a23b41da6f7a4584f652c4a5a95bb158b0dfe2 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 16:42:00 +0200
Subject: [PATCH 3/6] [INI] New function to merge snippets
The patch includes the implementation of the function
and the unit test
---
ini/ini_augment.c | 924 ++++++++++++++++++++++++++++++++++++++++++++++++++
ini/ini_augment_ut.c | 353 +++++++++++++++++++
2 files changed, 1277 insertions(+), 0 deletions(-)
create mode 100644 ini/ini_augment.c
create mode 100644 ini/ini_augment_ut.c
diff --git a/ini/ini_augment.c b/ini/ini_augment.c
//snip
+/* Prepare array of regular expressions */
+static int ini_aug_regex_prepare(const char **patterns,
+ struct ref_array *ra_err,
+ struct ref_array **ra_regex)
+{
//snip
+ /* Run through the list and save precompiled patterns */
+ while (*pat) {
+ TRACE_INFO_STRING("Pattern:", *pat);
+
+ preg = calloc(1, sizeof(regex_t));
+ if (preg == NULL) {
+ TRACE_ERROR_NUMBER("Failed to create array.", ENOMEM);
+ ref_array_destroy(ra);
+ return ENOMEM;
+ }
+ reg_err = regcomp(preg, *pat, REG_NOSUB);
+ if (reg_err) {
+ /* Get size, allocate buffer, record error... */
+ buf_size = regerror(reg_err, preg, NULL, 0);
+ err_str = malloc (buf_size);
+ if (err_str == NULL) {
+ TRACE_ERROR_NUMBER("Failed to create array.", ENOMEM);
+ ref_array_destroy(ra);
+ free(preg);
+ return ENOMEM;
+ }
+ regerror(reg_err, preg, err_str, buf_size);
+ free(preg);
+ ini_aug_add_string(ra_err,
+ "Failed to process expression: %s."
+ " Compilation returned error: %s",
+ *pat, err_str);
+
+ /* All error processing is done - advance to next pattern */
+ pat++;
+ continue;
// memory allocated in err_str should be released
// before continue.
+ }
+ /* In case of no error add compiled expression into the buffer */
+ error = ref_array_append(ra, (void *)&preg);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to add element to array.", error);
+ ref_array_destroy(ra);
+ free(preg);
+ return error;
+ }
+ /* Advance */
+ pat++;
+ }
+ }
+
+ *ra_regex = ra;
+ /* ref_array_debug(*ra_regex, 1); */
+
+ TRACE_FLOW_EXIT();
+ return EOK;
+}
+
//snip
+
+/* Construct snippet lists based on the directory */
+static int ini_aug_construct_list(char *dirname ,
+ const char **patterns,
+ struct access_check *check_perm,
+ struct ref_array *ra_list,
+ struct ref_array *ra_err)
+{
+
+ int error = EOK;
+ DIR *dir = NULL;
+ struct dirent *entry = NULL;
+ char *snipname = NULL;
+ char fullname[PATH_MAX + 1] = {0};
+ struct ref_array *ra_regex = NULL;
+ bool match = false;
+
+ TRACE_FLOW_ENTRY();
+
+ /* Prepare patterns */
+ error = ini_aug_regex_prepare(patterns,
+ ra_err,
+ &ra_regex);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to prepare regex array.", error);
+ return error;
+ }
+
+ /* Open directory */
+ errno = 0;
+ dir = opendir(dirname);
+ if (!dir) {
+ error = errno;
+ if (error == ENOMEM) {
+ TRACE_ERROR_NUMBER("No memory to open dir.", ENOMEM);
+ ref_array_destroy(ra_regex);
+ return ENOMEM;
+ }
+ /* Log an error, it is a recoverable error */
+ add_dir_open_error(error, dirname, ra_err);
//ra_regex should be destroyed here.
+ return EOK;
+ }
A directory is opened, but it is not closed in case of errors in next lines.
It would be cause of potential resource leak.
+
+ /* Loop through the directory */
+ while ((entry = readdir(dir)) != NULL)
+ {
+ TRACE_INFO_STRING("Processing", entry->d_name);
+
+ /* Always skip current and parent dirs */
+ if ((strncmp(entry->d_name,
+ INI_CURRENT_DIR,
+ sizeof(INI_CURRENT_DIR)) == 0) ||
+ (strncmp(entry->d_name,
+ INI_PARENT_DIR,
+ sizeof(INI_PARENT_DIR)) == 0)) continue;
+
+ /* Match names */
+ match = ini_aug_match_name(entry->d_name, ra_regex);
+
+ if (match) {
+
+ snprintf(fullname, PATH_MAX, "%s/%s", dirname, entry->d_name);
+
+ if(ini_check_file_perm(fullname, check_perm, ra_err)) {
+
+ /* Dup name and add to the array */
+ snipname = NULL;
+ snipname = strdup(fullname);
+ if (error) {
^^^^^
snipname shouldbe tested here.
+ TRACE_ERROR_NUMBER("Failed to dup string.", ENOMEM);
+ ref_array_destroy(ra_regex);
//add closedir(dir) here
+ return ENOMEM;
+ }
+
+ error = ref_array_append(ra_list, (void *)&snipname);
+ if (error) {
+ TRACE_ERROR_NUMBER("No memory to add file to "
+ "the snippet list.",
+ ENOMEM);
+ ref_array_destroy(ra_regex);
//add closedir(dir) here
+ return ENOMEM;
+ }
+ }
+ }
+ else {
+ ini_aug_add_string(ra_err,
+ "File %s did not match provided patterns."
+ " Skipping.",
+ entry->d_name);
+ }
+ }
+
+ closedir(dir);
+ ref_array_destroy(ra_regex);
+
+ ini_aug_sort_list(ra_list);
+
+ TRACE_FLOW_EXIT();
+ return EOK;
+}
>From d91b8bd98854a00448c021a42a551e5700a6ab82 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 22:53:19 +0200
Subject: [PATCH 5/6] [INI] Test file for unit test
---
ini/ini.d/merge.validator | 60 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 60 insertions(+), 0 deletions(-)
create mode 100644 ini/ini.d/merge.validator
diff --git a/ini/ini.d/merge.validator b/ini/ini.d/merge.validator
ACK
>From 9ce3ef526118ee8cf5e5dd8d28ddce971bba606e Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 22:55:02 +0200
Subject: [PATCH 6/6] [INI] Make the merge function build
---
Makefile.am | 15 +++++-
ini/ini_configobj.h | 115 +++++++++++++++++++++++++++++++++++++++++++++++++
ini/libini_config.sym | 1 +
3 files changed, 128 insertions(+), 3 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 32fcfae..7f38e50 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -255,6 +255,7 @@ libini_config_la_SOURCES = \
ini/ini_get_valueobj.c \
ini/ini_get_array_valueobj.c \
ini/ini_list_valueobj.c \
+ ini/ini_augment.c \
trace/trace.h
libini_config_la_DEPENDENCIES = ini/libini_config.sym
libini_config_la_LIBADD = \
@@ -286,19 +287,23 @@ dist_noinst_DATA += \
ini/ini.d/real32be.conf \
ini/ini.d/real32le.conf \
ini/ini.d/symbols.conf \
- ini/ini.d/new_line.conf
+ ini/ini.d/new_line.conf \
+ ini/ini.d/merge.validator
check_PROGRAMS += \
ini_config_ut \
ini_comment_ut \
ini_valueobj_ut \
- ini_parse_ut
+ ini_parse_ut \
+ ini_augment_ut
TESTS += \
ini_config_ut \
ini_comment_ut \
ini_valueobj_ut \
- ini_parse_ut
+ ini_parse_ut \
+ ini_augment_ut
+
ini_config_ut_SOURCES = ini/ini_config_ut.c
ini_config_ut_LDADD = \
@@ -314,6 +319,9 @@ ini_valueobj_ut_LDADD = libini_config.la libbasicobjects.la
ini_parse_ut_SOURCES = ini/ini_parse_ut.c
ini_parse_ut_LDADD = libini_config.la libcollection.la libbasicobjects.la
+ini_augment_ut_SOURCES = ini/ini_augment_ut.c
+ini_augment_ut_LDADD = libini_config.la libcollection.la libbasicobjects.la
+
The test ini_augment_ut calls functions from libpath_utils libref_array
therefore it should be linked with this libraries. Otherwise it causes problems
on distributions with disabled lik all deplibs.
This version works. // you can use different style of wrapping long lines.
ini_augment_ut_LDADD = libini_config.la libcollection.la \
libpath_utils.la libref_array.la
ini_config-docs:
if HAVE_DOXYGEN
cd ini; \
@@ -324,6 +332,7 @@ clean-local-ini_config:
rm -f ./*.out
rm -f test.ini
rm -f ./foo.conf ./bom* #From ini_parse_ut
+ rm -f ./merge.validator.in #From ini_augment_ut
##############################################################################
# Additional rules
//snip
diff --git a/ini/libini_config.sym b/ini/libini_config.sym
index 3ca15bb..33d2246 100644
--- a/ini/libini_config.sym
+++ b/ini/libini_config.sym
@@ -62,6 +62,7 @@ global:
ini_config_parse;
ini_config_copy;
ini_config_merge;
+ ini_config_augment;
ini_config_set_wrap;
ini_config_serialize;
ini_get_section_list;
It works butit is not right solution. The purpose of version symbol file is to
help linker distiguish between two version of libraries with backward
compatible changes. (just new functions were added).
Without version symbol file, linker cannot detect differences due to the same
soname.
sh$ objdump -p .libs/libini_config.so | grep SONAME
SONAME libini_config.so.5
The right approach is to add new version as in refarray/libref_array.sym.
I will attach file which can be squased to your patches.
>From 78c072f8be307fd5a37944274cd509b22aefa5c2 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Mon, 29 Sep 2014 07:38:17 -0400
Subject: [PATCH 7/7] [INI] Improve sorting and scanning
The sort function is replced with locale based one.
The function to read directory entries is replaced with the reentrant one.
---
ini/ini_augment.c | 30 ++++++++++++++++++++++++++----
1 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/ini/ini_augment.c b/ini/ini_augment.c
index 7f59d38..13ab882 100644
--- a/ini/ini_augment.c
+++ b/ini/ini_augment.c
@@ -28,9 +28,11 @@
#include <dirent.h>
#include <stdio.h>
#include <string.h>
+#include <stddef.h>
#include <limits.h>
#include <sys/types.h>
#include <regex.h>
+#include <unistd.h>
#include "trace.h"
#include "collection.h"
#include "collection_tools.h"
@@ -320,9 +322,9 @@ static void ini_aug_sort_list(struct ref_array *ra_list)
i = k;
j = k + 1;
- while ((j > 0) && (strncmp(*((char **) ref_array_get(ra_list, i,
NULL)),
- *((char **) ref_array_get(ra_list, j,
NULL)),
- PATH_MAX) > 0)) {
+ while ((j > 0) &&
+ (strcoll(*((char **) ref_array_get(ra_list, i, NULL)),
+ *((char **) ref_array_get(ra_list, j, NULL)))) > 0) {
ref_array_swap(ra_list, i, j);
i--;
j--;
I should wrote in 3rd patch. This version cause warning in static analysers.
Variable "i" can be underflowed. It is not problem, because "j" will be zero.
On the other hand, difference between "i" and "j" is constant (1). We can
remove one variable.
@@ -354,10 +356,12 @@ static int ini_aug_construct_list(char *dirname ,
int error = EOK;
DIR *dir = NULL;
struct dirent *entry = NULL;
+ struct dirent *entryp = NULL;
char *snipname = NULL;
char fullname[PATH_MAX + 1] = {0};
struct ref_array *ra_regex = NULL;
bool match = false;
+ int len = 0;
TRACE_FLOW_ENTRY();
@@ -385,9 +389,24 @@ static int ini_aug_construct_list(char *dirname ,
return EOK;
}
+ /* Allocate memory for entry (as said in man pages)*/
+ len = offsetof(struct dirent, d_name) + pathconf(dirname, _PC_NAME_MAX) +
1;
+ entry = malloc(len);
^^^^^
//result allocation should be tested here.
In case of failure, directory should be closed and ra_regex destroyed.
+
/* Loop through the directory */
- while ((entry = readdir(dir)) != NULL)
+ while (true)
{
+ error = readdir_r(dir, entry, &entryp);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to rid directory.", error);
^^^
s/rid/read/ ?
+ ref_array_destroy(ra_regex);
+ free(entry);
// directory should be closed here as well. (resource leak)
+ return error;
+ }
+
+ /* Stop looping if we reached the end */
+ if (entryp == NULL) break;
+
TRACE_INFO_STRING("Processing", entry->d_name);
/* Always skip current and parent dirs */
@@ -413,6 +432,7 @@ static int ini_aug_construct_list(char *dirname ,
if (error) {
TRACE_ERROR_NUMBER("Failed to dup string.", ENOMEM);
ref_array_destroy(ra_regex);
+ free(entry);
return ENOMEM;
}
@@ -422,6 +442,7 @@ static int ini_aug_construct_list(char *dirname ,
"the snippet list.",
ENOMEM);
ref_array_destroy(ra_regex);
+ free(entry);
return ENOMEM;
}
}
@@ -434,6 +455,7 @@ static int ini_aug_construct_list(char *dirname ,
}
}
+ free(entry);
closedir(dir);
ref_array_destroy(ra_regex);
--
1.7.1
There is also lots of coverity warnings, becaue return value of function
ref_array_get is not tested in this module, but it is tested in other modules.
Error: NULL_RETURNS (CWE-476): [#def1]
ding-libs-0.4.0/ini/ini_augment.c:236: returned_null: "ref_array_get" returns
null.
ding-libs-0.4.0/refarray/ref_array.c:226:9: return_null: Explicitly returning
null.
ding-libs-0.4.0/ini/ini_augment.c:236: dereference: Dereferencing a null pointer
"ref_array_get(ra_regex, i, NULL)".
Error: FORWARD_NULL (CWE-476): [#def2]
ding-libs-0.4.0/ini/ini_augment.c:237: var_deref_model: Passing "NULL" to
"regexec", which dereferences it.
Error: NULL_RETURNS (CWE-476): [#def3]
ding-libs-0.4.0/ini/ini_augment.c:325: returned_null: "ref_array_get" returns
null.
ding-libs-0.4.0/refarray/ref_array.c:226:9: return_null: Explicitly returning
null.
ding-libs-0.4.0/ini/ini_augment.c:325: dereference: Dereferencing a null pointer
"ref_array_get(ra_list, j - 1U, NULL)".
Error: NULL_RETURNS (CWE-476): [#def4]
ding-libs-0.4.0/ini/ini_augment.c:325: returned_null: "ref_array_get" returns
null.
ding-libs-0.4.0/refarray/ref_array.c:226:9: return_null: Explicitly returning
null.
ding-libs-0.4.0/ini/ini_augment.c:325: dereference: Dereferencing a null pointer
"ref_array_get(ra_list, j, NULL)".
Error: CHECKED_RETURN (CWE-252): [#def5]
ding-libs-0.4.0/ini/ini_augment.c:66: check_return: Calling "ref_array_append"
without checking return value (as is done elsewhere 11 out of 12 times).
ding-libs-0.4.0/ini/ini_augment.c:194: example_assign: Example 1: Assigning: "error" = return
value from "ref_array_append(ra, (void *)&preg)".
ding-libs-0.4.0/ini/ini_augment.c:195: example_checked: Example 1 (cont.): "error" has
its value checked in "error".
ding-libs-0.4.0/ini/ini_comment.c:653: example_assign: Example 2: Assigning: "error" = return
value from "ref_array_append(ic->ra, (void *)&sb_new)".
ding-libs-0.4.0/ini/ini_comment.c:654: example_checked: Example 2 (cont.): "error" has
its value checked in "error".
ding-libs-0.4.0/ini/ini_comment.c:280: example_assign: Example 3: Assigning: "error" = return
value from "ref_array_append(ic->ra, (void *)&elem)".
ding-libs-0.4.0/ini/ini_comment.c:281: example_checked: Example 3 (cont.): "error" has
its value checked in "error".
ding-libs-0.4.0/ini/ini_valueobj.c:148: example_assign: Example 4: Assigning: "error" =
return value from "ref_array_append(raw_lines, (void *)©)".
ding-libs-0.4.0/ini/ini_valueobj.c:149: example_checked: Example 4 (cont.): "error" has
its value checked in "error".
ding-libs-0.4.0/ini/ini_valueobj.c:503: example_assign: Example 5: Assigning: "error" =
return value from "ref_array_append(raw_lines, (void *)&strvalue)".
ding-libs-0.4.0/ini/ini_valueobj.c:504: example_checked: Example 5 (cont.): "error" has
its value checked in "error".
Attaching two small promised patches
LS
Hi,
On top of the original 6+1 patches which I did not touch and squash (I
just changed the order of them as suggested by Lukas plus put 7th patch
right after the change to module ini_augment.c)
I added 3 patches:
- 0006-Fixing-the-main.path - addresses all feedback provided above. I
Hope I did not miss anything. I did not squash it so it is easy to see
that things have been fixed.
- 0008-Cleaning-Unit-test.patch - does some cleaning of the code in the
unit test and also makes it not fail in your case by sorting the results
before comparison. See the not in the patch
- 0010-Correction-to-make-file.patch - corrects the make file and fixes
symbols file as Lukas proposed. The only thing that is not addressed is
the recommendation for linking ref_array explicitly. I am not sure I get
it. The library libini_config_la already includes everything needed see
below (line 261 Makefile.am):
libini_config_la_LIBADD = \
libcollection.la \
libpath_utils.la \
libref_array.la \
libbasicobjects.la
Also I did not merge the proposed sorting function patch. I had to
address de-referencing issue to I blended it in first and then add
additional cleanup on top.
Once reviewed the patches 4-5-6, 7-8, 9-10 can be squashed respectfully
before pushing.
Thanks for review!
--
Thank you,
Dmitri Pal
Sr. Engineering Manager IdM portfolio
Red Hat, Inc.
>From c443d984a19df8fbbdb7337563ea4ec0ea2d693d Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 22:55:02 +0200
Subject: [PATCH 09/10] [INI] Make the merge function build
---
Makefile.am | 15 +++++-
ini/ini_configobj.h | 115 +++++++++++++++++++++++++++++++++++++++++++++++++
ini/libini_config.sym | 1 +
3 files changed, 128 insertions(+), 3 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 32fcfae..7f38e50 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -255,6 +255,7 @@ libini_config_la_SOURCES = \
ini/ini_get_valueobj.c \
ini/ini_get_array_valueobj.c \
ini/ini_list_valueobj.c \
+ ini/ini_augment.c \
trace/trace.h
libini_config_la_DEPENDENCIES = ini/libini_config.sym
libini_config_la_LIBADD = \
@@ -286,19 +287,23 @@ dist_noinst_DATA += \
ini/ini.d/real32be.conf \
ini/ini.d/real32le.conf \
ini/ini.d/symbols.conf \
- ini/ini.d/new_line.conf
+ ini/ini.d/new_line.conf \
+ ini/ini.d/merge.validator
check_PROGRAMS += \
ini_config_ut \
ini_comment_ut \
ini_valueobj_ut \
- ini_parse_ut
+ ini_parse_ut \
+ ini_augment_ut
TESTS += \
ini_config_ut \
ini_comment_ut \
ini_valueobj_ut \
- ini_parse_ut
+ ini_parse_ut \
+ ini_augment_ut
+
ini_config_ut_SOURCES = ini/ini_config_ut.c
ini_config_ut_LDADD = \
@@ -314,6 +319,9 @@ ini_valueobj_ut_LDADD = libini_config.la libbasicobjects.la
ini_parse_ut_SOURCES = ini/ini_parse_ut.c
ini_parse_ut_LDADD = libini_config.la libcollection.la libbasicobjects.la
+ini_augment_ut_SOURCES = ini/ini_augment_ut.c
+ini_augment_ut_LDADD = libini_config.la libcollection.la libbasicobjects.la
+
ini_config-docs:
if HAVE_DOXYGEN
cd ini; \
@@ -324,6 +332,7 @@ clean-local-ini_config:
rm -f ./*.out
rm -f test.ini
rm -f ./foo.conf ./bom* #From ini_parse_ut
+ rm -f ./merge.validator.in #From ini_augment_ut
##############################################################################
# Additional rules
diff --git a/ini/ini_configobj.h b/ini/ini_configobj.h
index 4ef98dc..620b545 100644
--- a/ini/ini_configobj.h
+++ b/ini/ini_configobj.h
@@ -374,6 +374,46 @@ enum INI_GET {
* @}
*/
+
+/**
+ * @defgroup augment Constants and structures related to augmentation.
+ *
+ * @{
+ */
+
+/** Structure to pass access check parameters to augmentation function.
+ *
+ * flags Define what to check.
+ * One can check file
+ * permissions with mask,
+ * uid, and gid of the file.
+ * uid Expected uid of the file.
+ * gid Expected gid of the file.
+ * mode Expected mode of the file.
+ * mask Mask to use in the mode check.
+ * Mask is always adjusted to
+ * include at least S_IRWXU,
+ * S_IRWXG and S_IRWXO
+ */
+struct access_check {
+ uint32_t flags;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ mode_t mask;
+};
+
+/** Enumeration of augmentation modes. */
+enum augmode {
+ INI_AUG_ANY = 0, /**< Allow any augmentation. */
+ INI_AUG_ADD = 1, /**< Allow only new sections. */
+ INI_AUG_OVER = 2 /**< Allow section updates. */
+};
+
+/**
+ * @}
+ */
+
/**
* @brief Name of the default section.
*
@@ -780,6 +820,81 @@ int ini_config_merge(struct ini_cfgobj *first,
uint32_t collision_flags,
struct ini_cfgobj **result);
+
+/**
+ * @brief Augment configuration
+ *
+ * Function merges the main configuration file
+ * with the configuration file snippets
+ * read from a specified directory.
+ *
+ * @param[in] base_cfg A configuration object
+ * that will be augmented.
+ * @param[in] path Path to a directory where
+ * configuration snippets
+ * will be read from.
+ * @param[in] patterns List of regular expressions
+ * that the name of a snippet file
+ * has to match to be considered
+ * for merge.
+ * @param[in] sections List of regular expressions
+ * that the section names in the snippet
+ * file need to match. If file contains
+ * sections that do not match any patterns
+ * the file is skipped and error is recorded.
+ * @param[in] check_perm Pointer to structure that
+ * holds criteria for the
+ * access check.
+ * @param[in] error_level Flags that control actions
+ * in case of parsing error in a snippet file.
+ * @param[in] collision_flags These flags control how the potential
+ * collisions between keys and sections
+ * within the snippet file will be handled.
+ * For more information
+ * see collision flag definitions.
+ * @param[in] parse_flags Flags that control parsing process,
+ * for example how to handle spaces at
+ * the beginning of the line.
+ * @param[in] merge_flags Flags that control handling
+ * of the duplicate sections or keys
+ * during merging of the snippets.
+ * They are different from the collision flags
+ * because duplicate sections and keys inside
+ * are snippets most likely will be handled as
+ * 'last value wins' while during merge
+ * the attempt to overwrite
+ * a specific section might be treated as
+ * an error.
+ * @param[out] result_cfg A new configuration object,
+ * the result of the merge.
+ * @param[out] error_list List of strings that
+ * contains all encountered
+ * errors.
+ * It can be NULL, in this case list of errors
+ * is not populated.
+ * @param[out] success_list List of strings that
+ * contains file names of snippets that were
+ * successfully merged.
+ * It can be NULL, in this case list of files
+ * is not populated.
+ *
+ * @return 0 - Success.
+ * @return EINVAL - Invalid parameter.
+ * @return ENOMEM - No memory.
+ */
+int ini_config_augment(struct ini_cfgobj *base_cfg,
+ const char *path,
+ const char **patterns,
+ const char **sections,
+ struct access_check *check_perm,
+ int error_level,
+ uint32_t collision_flags,
+ uint32_t parse_flags,
+ uint32_t merge_flags,
+ struct ini_cfgobj **result_cfg,
+ struct ref_array **error_list,
+ struct ref_array **success_list);
+
/**
* @brief Set the folding boundary
*
diff --git a/ini/libini_config.sym b/ini/libini_config.sym
index 3ca15bb..33d2246 100644
--- a/ini/libini_config.sym
+++ b/ini/libini_config.sym
@@ -62,6 +62,7 @@ global:
ini_config_parse;
ini_config_copy;
ini_config_merge;
+ ini_config_augment;
ini_config_set_wrap;
ini_config_serialize;
ini_get_section_list;
--
1.7.1
>From aff0f323b2996df7a8f1539f46ca651d2d9fd5e8 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 16:38:19 +0200
Subject: [PATCH 01/10] [RA] Print info when array is empty
---
refarray/ref_array.c | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
diff --git a/refarray/ref_array.c b/refarray/ref_array.c
index 273ac4f..e5becf9 100644
--- a/refarray/ref_array.c
+++ b/refarray/ref_array.c
@@ -548,6 +548,11 @@ void ref_array_debug(struct ref_array *ra, int num)
{
int i,j;
+ if (!ra) {
+ printf("\nARRAY is NULL\n");
+ return;
+ }
+
printf("\nARRAY DUMP START\n");
printf("Length = %u\n", ra->len);
printf("Size = %u\n", ra->size);
--
1.7.1
>From e3c1ecbe080b25285868e86676ca85259dad6f27 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 19 Oct 2014 11:44:40 -0400
Subject: [PATCH 10/10] Correction to make file
---
Makefile.am | 3 ++-
ini/libini_config.sym | 9 ++++++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 7f38e50..83bed48 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -322,6 +322,7 @@ ini_parse_ut_LDADD = libini_config.la libcollection.la libbasicobjects.la
ini_augment_ut_SOURCES = ini/ini_augment_ut.c
ini_augment_ut_LDADD = libini_config.la libcollection.la libbasicobjects.la
+
ini_config-docs:
if HAVE_DOXYGEN
cd ini; \
@@ -332,7 +333,7 @@ clean-local-ini_config:
rm -f ./*.out
rm -f test.ini
rm -f ./foo.conf ./bom* #From ini_parse_ut
- rm -f ./merge.validator.in #From ini_augment_ut
+ rm -f ./merge.validator.* #From ini_augment_ut
##############################################################################
# Additional rules
diff --git a/ini/libini_config.sym b/ini/libini_config.sym
index 33d2246..d6a0afd 100644
--- a/ini/libini_config.sym
+++ b/ini/libini_config.sym
@@ -62,7 +62,6 @@ global:
ini_config_parse;
ini_config_copy;
ini_config_merge;
- ini_config_augment;
ini_config_set_wrap;
ini_config_serialize;
ini_get_section_list;
@@ -136,3 +135,11 @@ global:
local:
*;
};
+
+
+INI_CONFIG_1.2.0 {
+global:
+ /* ini_configobj.h */
+ ini_config_augment;
+} INI_CONFIG_1.1.0;
+
--
1.7.1
>From 1d65750452836633021768659ccdbe4b47d0c16e Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 19 Oct 2014 22:06:56 -0400
Subject: [PATCH 08/10] Cleaning Unit test
---
ini/ini_augment_ut.c | 53 ++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 45 insertions(+), 8 deletions(-)
diff --git a/ini/ini_augment_ut.c b/ini/ini_augment_ut.c
index 94d610e..1451d77 100644
--- a/ini/ini_augment_ut.c
+++ b/ini/ini_augment_ut.c
@@ -189,7 +189,6 @@ int print_list_to_file(struct ref_array *list,
static int basic_test(void)
{
int error = EOK;
- int error1 = EOK;
char indir[PATH_MAX];
char srcname[PATH_MAX];
char filename[PATH_MAX];
@@ -238,8 +237,8 @@ static int basic_test(void)
/* When run in dev environment there can be some temp files which
* we need to clean. */
- snprintf(command, PATH_MAX * 3, "rm %s/*~", indir);
- system(command);
+ snprintf(command, PATH_MAX * 3, "rm %s/*~ > /dev/null 2>&1", indir);
+ (void)system(command);
/* Make the file path independent */
snprintf(srcname, PATH_MAX, "%s/ini/ini.d/merge.validator",
@@ -291,12 +290,50 @@ static int basic_test(void)
}
else INIOUT(col_debug_collection(result_cfg->cfg, COL_TRAVERSE_DEFAULT));
- error = print_list_to_file(error_list, "merge.validator.out", "w");
- error1 = print_list_to_file(success_list, "merge.validator.out", "a");
- /* Save error */
- if ((error1 !=0) && (error == 0)) error = error1;
+ /* Print results to file */
+ if ((print_list_to_file(error_list, resname, "w")) ||
+ (print_list_to_file(success_list, resname, "a"))) {
+ printf("Failed to save results in %s.\n", resname);
+ ref_array_destroy(error_list);
+ ref_array_destroy(success_list);
+ ini_config_destroy(in_cfg);
+ ini_config_destroy(result_cfg);
+ return -1;
+ }
+
+ /* NOTE: The order of the scanning of the files in the ini.d directory
+ * is not predicatble so before comparing the results we have to sort
+ * them since otherwise the projected output and real output might
+ * not match.
+ */
+
+ snprintf(command, PATH_MAX * 3, "sort %s > %s2", filename, filename);
+ error = system(command);
+ if ((error) || (WEXITSTATUS(error))) {
+ printf("Failed to run first sort command %d %d.\n", error,
+ WEXITSTATUS(error));
+ ref_array_destroy(error_list);
+ ref_array_destroy(success_list);
+ ini_config_destroy(in_cfg);
+ ini_config_destroy(result_cfg);
+ return -1;
+ }
+
+ snprintf(command, PATH_MAX * 3, "sort %s > %s2", resname, resname);
+ error = system(command);
+ error = system(command);
+ if ((error) || (WEXITSTATUS(error))) {
+ printf("Failed to run second sort command %d %d.\n", error,
+ WEXITSTATUS(error));
+ ref_array_destroy(error_list);
+ ref_array_destroy(success_list);
+ ini_config_destroy(in_cfg);
+ ini_config_destroy(result_cfg);
+ return -1;
+ }
+
- snprintf(command, PATH_MAX * 3, "diff -q %s %s", filename, resname);
+ snprintf(command, PATH_MAX * 3, "diff -q %s2 %s2", filename, resname);
error = system(command);
INIOUT(printf("Comparison of %s %s returned: %d\n",
filename, resname, error));
--
1.7.1
>From d5850026872bad5042251202ce599e8dd0cd8b84 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 16:43:31 +0200
Subject: [PATCH 03/10] [INI] Refactored access control check
The patch includes implementation of the new internal function.
---
ini/ini_fileobj.c | 66 +++++++++++++++++++++++++++++++++++++---------------
1 files changed, 47 insertions(+), 19 deletions(-)
diff --git a/ini/ini_fileobj.c b/ini/ini_fileobj.c
index da8339e..252c23e 100644
--- a/ini/ini_fileobj.c
+++ b/ini/ini_fileobj.c
@@ -705,14 +705,13 @@ const struct stat *ini_config_get_stat(struct ini_cfgfile *file_ctx)
return ret;
}
-
/* Check access */
-int ini_config_access_check(struct ini_cfgfile *file_ctx,
- uint32_t flags,
- uid_t uid,
- gid_t gid,
- mode_t mode,
- mode_t mask)
+int access_check_int(struct stat *file_stats,
+ uint32_t flags,
+ uid_t uid,
+ gid_t gid,
+ mode_t mode,
+ mode_t mask)
{
mode_t st_mode;
@@ -722,23 +721,18 @@ int ini_config_access_check(struct ini_cfgfile *file_ctx,
INI_ACCESS_CHECK_GID |
INI_ACCESS_CHECK_UID;
- if ((file_ctx == NULL) || (flags == 0)) {
+ if (flags == 0) {
TRACE_ERROR_NUMBER("Invalid parameter.", EINVAL);
return EINVAL;
}
- if (file_ctx->stats_read == 0) {
- TRACE_ERROR_NUMBER("Stats were not collected.", EINVAL);
- return EINVAL;
- }
-
/* Check mode */
if (flags & INI_ACCESS_CHECK_MODE) {
TRACE_INFO_NUMBER("File mode as saved.",
- file_ctx->file_stats.st_mode);
+ file_stats->st_mode);
- st_mode = file_ctx->file_stats.st_mode;
+ st_mode = file_stats->st_mode;
st_mode &= S_IRWXU | S_IRWXG | S_IRWXO;
TRACE_INFO_NUMBER("File mode adjusted.", st_mode);
@@ -761,8 +755,8 @@ int ini_config_access_check(struct ini_cfgfile *file_ctx,
/* Check uid */
if (flags & INI_ACCESS_CHECK_UID) {
- if (file_ctx->file_stats.st_uid != uid) {
- TRACE_ERROR_NUMBER("GID:", file_ctx->file_stats.st_uid);
+ if (file_stats->st_uid != uid) {
+ TRACE_ERROR_NUMBER("GID:", file_stats->st_uid);
TRACE_ERROR_NUMBER("GID passed in.", uid);
TRACE_ERROR_NUMBER("Access denied.", EACCES);
return EACCES;
@@ -771,8 +765,8 @@ int ini_config_access_check(struct ini_cfgfile *file_ctx,
/* Check gid */
if (flags & INI_ACCESS_CHECK_GID) {
- if (file_ctx->file_stats.st_gid != gid) {
- TRACE_ERROR_NUMBER("GID:", file_ctx->file_stats.st_gid);
+ if (file_stats->st_gid != gid) {
+ TRACE_ERROR_NUMBER("GID:", file_stats->st_gid);
TRACE_ERROR_NUMBER("GID passed in.", gid);
TRACE_ERROR_NUMBER("Access denied.", EACCES);
return EACCES;
@@ -784,6 +778,40 @@ int ini_config_access_check(struct ini_cfgfile *file_ctx,
}
+/* Check access */
+int ini_config_access_check(struct ini_cfgfile *file_ctx,
+ uint32_t flags,
+ uid_t uid,
+ gid_t gid,
+ mode_t mode,
+ mode_t mask)
+{
+ int error = EOK;
+
+ TRACE_FLOW_ENTRY();
+
+ if (file_ctx == NULL) {
+ TRACE_ERROR_NUMBER("Invalid parameter.", EINVAL);
+ return EINVAL;
+ }
+
+ if (file_ctx->stats_read == 0) {
+ TRACE_ERROR_NUMBER("Stats were not collected.", EINVAL);
+ return EINVAL;
+ }
+
+ error = access_check_int(&(file_ctx->file_stats),
+ flags,
+ uid,
+ gid,
+ mode,
+ mask);
+
+ TRACE_FLOW_EXIT();
+ return error;
+
+}
+
/* Determines if two file contexts are different by comparing:
* - time stamp
* - device ID
--
1.7.1
>From d6117bb3020dc3b5c1df07ea95b45bd02699333b Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 19 Oct 2014 22:07:50 -0400
Subject: [PATCH 06/10] Fixing the main file
---
ini/ini_augment.c | 54 +++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/ini/ini_augment.c b/ini/ini_augment.c
index 13ab882..8b40f49 100644
--- a/ini/ini_augment.c
+++ b/ini/ini_augment.c
@@ -63,7 +63,8 @@ static void ini_aug_add_string(struct ref_array *ra,
if(vasprintf(&result, format, args )) {
TRACE_INFO_STRING("String:", result);
- ref_array_append(ra, (void *)&result);
+ /* This is a best effort assignment. error is not checked */
+ (void)ref_array_append(ra, (void *)&result);
}
va_end(args);
@@ -184,6 +185,7 @@ static int ini_aug_regex_prepare(const char **patterns,
"Failed to process expression: %s."
" Compilation returned error: %s",
*pat, err_str);
+ free(err_str);
/* All error processing is done - advance to next pattern */
pat++;
@@ -233,6 +235,7 @@ static bool ini_aug_match_name(char *name,
for (i = 0; i < len; i++) {
preg = *((regex_t **)ref_array_get(ra_regex, i, NULL));
+ if (preg == NULL) continue;
if (regexec(preg, name, 0, NULL, 0) == 0) {
TRACE_INFO_NUMBER("Name matched regex number:", i);
match = true;
@@ -300,7 +303,9 @@ static bool ini_check_file_perm(char *name,
/* Sort array */
static void ini_aug_sort_list(struct ref_array *ra_list)
{
- unsigned len = 0, i = 0, j = 0, k = 0;
+ unsigned len = 0, j = 0, k = 0;
+ char **item1 = NULL;
+ char **item2 = NULL;
TRACE_FLOW_ENTRY();
@@ -319,14 +324,20 @@ static void ini_aug_sort_list(struct ref_array *ra_list)
*/
for (k = 0; k < len-1; k++) {
- i = k;
j = k + 1;
-
- while ((j > 0) &&
- (strcoll(*((char **) ref_array_get(ra_list, i, NULL)),
- *((char **) ref_array_get(ra_list, j, NULL)))) > 0) {
- ref_array_swap(ra_list, i, j);
- i--;
+ while (j > 0) {
+ item1 = (char **) ref_array_get(ra_list, j - 1, NULL);
+ item2 = (char **) ref_array_get(ra_list, j, NULL);
+ /* Swap items if they are not NULL and string comparison
+ * indicates that they need to be swapped or if the first
+ * one is NULL but second is not. That would push
+ * NULL elements of the array to the end of the array.
+ */
+ if (((item1 && item2) &&
+ (strcoll(*item1,*item2)) > 0) ||
+ (!item1 && item2)) {
+ ref_array_swap(ra_list, j - 1, j);
+ }
j--;
}
}
@@ -386,25 +397,33 @@ static int ini_aug_construct_list(char *dirname ,
}
/* Log an error, it is a recoverable error */
add_dir_open_error(error, dirname, ra_err);
+ ref_array_destroy(ra_regex);
return EOK;
}
/* Allocate memory for entry (as said in man pages)*/
len = offsetof(struct dirent, d_name) + pathconf(dirname, _PC_NAME_MAX) + 1;
entry = malloc(len);
+ if (entry == NULL) {
+ TRACE_ERROR_NUMBER("Failed to allocate memory.", ENOMEM);
+ ref_array_destroy(ra_regex);
+ closedir(dir);
+ return ENOMEM;
+ }
/* Loop through the directory */
while (true)
{
error = readdir_r(dir, entry, &entryp);
if (error) {
- TRACE_ERROR_NUMBER("Failed to rid directory.", error);
+ TRACE_ERROR_NUMBER("Failed to read directory.", error);
ref_array_destroy(ra_regex);
+ closedir(dir);
free(entry);
return error;
}
- /* Stop looping if we reached the end */
+ /* Stop looping if we reached the end */
if (entryp == NULL) break;
TRACE_INFO_STRING("Processing", entry->d_name);
@@ -429,9 +448,10 @@ static int ini_aug_construct_list(char *dirname ,
/* Dup name and add to the array */
snipname = NULL;
snipname = strdup(fullname);
- if (error) {
+ if (snipname == NULL) {
TRACE_ERROR_NUMBER("Failed to dup string.", ENOMEM);
ref_array_destroy(ra_regex);
+ closedir(dir);
free(entry);
return ENOMEM;
}
@@ -442,6 +462,7 @@ static int ini_aug_construct_list(char *dirname ,
"the snippet list.",
ENOMEM);
ref_array_destroy(ra_regex);
+ closedir(dir);
free(entry);
return ENOMEM;
}
@@ -653,6 +674,7 @@ static int ini_aug_apply(struct ini_cfgobj *cfg,
bool skip = false;
struct ref_array *ra_regex = NULL;
char *snip_name = NULL;
+ char **snip_name_ptr = NULL;
TRACE_FLOW_ENTRY();
@@ -693,7 +715,11 @@ static int ini_aug_apply(struct ini_cfgobj *cfg,
}
/* Process snippet */
- snip_name = *((char **)ref_array_get (ra_list, i, NULL));
+ snip_name_ptr = (char **)ref_array_get (ra_list, i, NULL);
+ if (snip_name_ptr == NULL) continue;
+ snip_name = *snip_name_ptr;
+ if (snip_name == NULL) continue;
+
TRACE_INFO_STRING("Processing", snip_name);
/* Open file */
@@ -786,7 +812,7 @@ static int ini_aug_apply(struct ini_cfgobj *cfg,
ref_array_destroy(ra_regex);
return error;
}
- else if
+ else if
((error == EEXIST) &&
((((merge_flags & INI_MS_MASK) == INI_MS_DETECT) &&
((merge_flags & INI_MV2S_MASK) != INI_MV2S_ERROR)) ||
--
1.7.1
>From a5bb87620762985c71634c294185753fa3aec9da Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 22:53:19 +0200
Subject: [PATCH 07/10] [INI] Test file for unit test
---
ini/ini.d/merge.validator | 60 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 60 insertions(+), 0 deletions(-)
create mode 100644 ini/ini.d/merge.validator
diff --git a/ini/ini.d/merge.validator b/ini/ini.d/merge.validator
new file mode 100644
index 0000000..dc6d6aa
--- /dev/null
+++ b/ini/ini.d/merge.validator
@@ -0,0 +1,60 @@
+File merge.validator did not match provided patterns. Skipping.
+File real8.conf did not match provided patterns. Skipping.
+File new_line.conf did not match provided patterns. Skipping.
+File real32be.conf did not match provided patterns. Skipping.
+File real32le.conf did not match provided patterns. Skipping.
+File real16be.conf did not match provided patterns. Skipping.
+File real16le.conf did not match provided patterns. Skipping.
+File foo.conf.in did not match provided patterns. Skipping.
+Errors detected while parsing: %s%s/comment.conf.
+Error (9) on line 22: Invalid space character at the beginning of the line.
+Error (9) on line 24: Invalid space character at the beginning of the line.
+Error (9) on line 26: Invalid space character at the beginning of the line.
+Error (15) on line 32: Incomplete comment at the end of the file.
+No sections found in file %s%s/comment.conf. Skipping.
+Section [section_a] found in file %s%s/first.conf is not allowed.
+Section [section_c] found in file %s%s/first.conf is not allowed.
+Section [section_b] found in file %s%s/first.conf is not allowed.
+File %s%s/first.conf contains sections that are not allowed. Skipping.
+Section [section_a] found in file %s%s/mergecheck.conf is not allowed.
+Section [section_c] found in file %s%s/mergecheck.conf is not allowed.
+Section [section_b] found in file %s%s/mergecheck.conf is not allowed.
+Section [section_d] found in file %s%s/mergecheck.conf is not allowed.
+File %s%s/mergecheck.conf contains sections that are not allowed. Skipping.
+Section [service] found in file %s%s/mysssd.conf is not allowed.
+Section [sssd] found in file %s%s/mysssd.conf is not allowed.
+Section [nss] found in file %s%s/mysssd.conf is not allowed.
+Section [pam] found in file %s%s/mysssd.conf is not allowed.
+Section [domain] found in file %s%s/mysssd.conf is not allowed.
+File %s%s/mysssd.conf contains sections that are not allowed. Skipping.
+Section [section_a] found in file %s%s/second.conf is not allowed.
+Section [section_b] found in file %s%s/second.conf is not allowed.
+Section [section_d] found in file %s%s/second.conf is not allowed.
+File %s%s/second.conf contains sections that are not allowed. Skipping.
+Section [section1] found in file %s%s/sexpect.conf is not allowed.
+Section [section2] found in file %s%s/sexpect.conf is not allowed.
+File %s%s/sexpect.conf contains sections that are not allowed. Skipping.
+Section [section1] found in file %s%s/smerge.conf is not allowed.
+Section [section2] found in file %s%s/smerge.conf is not allowed.
+File %s%s/smerge.conf contains sections that are not allowed. Skipping.
+Errors detected while parsing: %s%s/space.conf.
+Error (9) on line 1: Invalid space character at the beginning of the line.
+Error (9) on line 2: Invalid space character at the beginning of the line.
+Error (9) on line 3: Invalid space character at the beginning of the line.
+Error (9) on line 4: Invalid space character at the beginning of the line.
+No sections found in file %s%s/space.conf. Skipping.
+Section [info] found in file %s%s/symbols.conf is not allowed.
+Section [languages] found in file %s%s/symbols.conf is not allowed.
+Section [text] found in file %s%s/symbols.conf is not allowed.
+File %s%s/symbols.conf contains sections that are not allowed. Skipping.
+Errors detected while parsing: %s%s/test.conf.
+Error (9) on line 11: Invalid space character at the beginning of the line.
+Error (9) on line 12: Invalid space character at the beginning of the line.
+Error (9) on line 14: Invalid space character at the beginning of the line.
+Error (9) on line 15: Invalid space character at the beginning of the line.
+Error (9) on line 16: Invalid space character at the beginning of the line.
+Error (9) on line 26: Invalid space character at the beginning of the line.
+Error (9) on line 35: Invalid space character at the beginning of the line.
+No sections found in file %s%s/test.conf. Skipping.
+%s%s/ipa.conf
+%s%s/real.conf
--
1.7.1
>From 442bfc2c665ff18fce539106812e855ca542a5be Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Mon, 29 Sep 2014 07:38:17 -0400
Subject: [PATCH 05/10] [INI] Improve sorting and scanning
The sort function is replced with locale based one.
The function to read directory entries is replaced with the reentrant one.
---
ini/ini_augment.c | 30 ++++++++++++++++++++++++++----
1 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/ini/ini_augment.c b/ini/ini_augment.c
index 7f59d38..13ab882 100644
--- a/ini/ini_augment.c
+++ b/ini/ini_augment.c
@@ -28,9 +28,11 @@
#include <dirent.h>
#include <stdio.h>
#include <string.h>
+#include <stddef.h>
#include <limits.h>
#include <sys/types.h>
#include <regex.h>
+#include <unistd.h>
#include "trace.h"
#include "collection.h"
#include "collection_tools.h"
@@ -320,9 +322,9 @@ static void ini_aug_sort_list(struct ref_array *ra_list)
i = k;
j = k + 1;
- while ((j > 0) && (strncmp(*((char **) ref_array_get(ra_list, i, NULL)),
- *((char **) ref_array_get(ra_list, j, NULL)),
- PATH_MAX) > 0)) {
+ while ((j > 0) &&
+ (strcoll(*((char **) ref_array_get(ra_list, i, NULL)),
+ *((char **) ref_array_get(ra_list, j, NULL)))) > 0) {
ref_array_swap(ra_list, i, j);
i--;
j--;
@@ -354,10 +356,12 @@ static int ini_aug_construct_list(char *dirname ,
int error = EOK;
DIR *dir = NULL;
struct dirent *entry = NULL;
+ struct dirent *entryp = NULL;
char *snipname = NULL;
char fullname[PATH_MAX + 1] = {0};
struct ref_array *ra_regex = NULL;
bool match = false;
+ int len = 0;
TRACE_FLOW_ENTRY();
@@ -385,9 +389,24 @@ static int ini_aug_construct_list(char *dirname ,
return EOK;
}
+ /* Allocate memory for entry (as said in man pages)*/
+ len = offsetof(struct dirent, d_name) + pathconf(dirname, _PC_NAME_MAX) + 1;
+ entry = malloc(len);
+
/* Loop through the directory */
- while ((entry = readdir(dir)) != NULL)
+ while (true)
{
+ error = readdir_r(dir, entry, &entryp);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to rid directory.", error);
+ ref_array_destroy(ra_regex);
+ free(entry);
+ return error;
+ }
+
+ /* Stop looping if we reached the end */
+ if (entryp == NULL) break;
+
TRACE_INFO_STRING("Processing", entry->d_name);
/* Always skip current and parent dirs */
@@ -413,6 +432,7 @@ static int ini_aug_construct_list(char *dirname ,
if (error) {
TRACE_ERROR_NUMBER("Failed to dup string.", ENOMEM);
ref_array_destroy(ra_regex);
+ free(entry);
return ENOMEM;
}
@@ -422,6 +442,7 @@ static int ini_aug_construct_list(char *dirname ,
"the snippet list.",
ENOMEM);
ref_array_destroy(ra_regex);
+ free(entry);
return ENOMEM;
}
}
@@ -434,6 +455,7 @@ static int ini_aug_construct_list(char *dirname ,
}
}
+ free(entry);
closedir(dir);
ref_array_destroy(ra_regex);
--
1.7.1
>From 9f80182fecbf1c1800a22ff279bf8915f4196f42 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 16:39:58 +0200
Subject: [PATCH 02/10] [INI] Declaring new internal access check function
---
ini/ini_config_priv.h | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/ini/ini_config_priv.h b/ini/ini_config_priv.h
index e1292f7..331363f 100644
--- a/ini/ini_config_priv.h
+++ b/ini/ini_config_priv.h
@@ -99,6 +99,13 @@ int valid_collision_flags(uint32_t collision_flags);
/* Empty section */
int empty_section(struct collection_item *sec);
+/* Internal access check function */
+int access_check_int(struct stat *file_stats,
+ uint32_t flags,
+ uid_t uid,
+ gid_t gid,
+ mode_t mode,
+ mode_t mask);
#endif
--
1.7.1
>From 9bad4d86ab68b1af1c0afd06ae826dd27aed4ef3 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <d...@dpal.csb>
Date: Sun, 7 Sep 2014 16:42:00 +0200
Subject: [PATCH 04/10] [INI] New function to merge snippets
The patch includes the implementation of the function
and the unit test
---
ini/ini_augment.c | 924 ++++++++++++++++++++++++++++++++++++++++++++++++++
ini/ini_augment_ut.c | 353 +++++++++++++++++++
2 files changed, 1277 insertions(+), 0 deletions(-)
create mode 100644 ini/ini_augment.c
create mode 100644 ini/ini_augment_ut.c
diff --git a/ini/ini_augment.c b/ini/ini_augment.c
new file mode 100644
index 0000000..7f59d38
--- /dev/null
+++ b/ini/ini_augment.c
@@ -0,0 +1,924 @@
+/*
+ INI LIBRARY
+
+ Module represents part of the INI interface.
+ The main function in this module allows to merge
+ snippets of different config files.
+
+ Copyright (C) Dmitri Pal <d...@redhat.com> 2014
+
+ INI Library is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ INI Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with INI Library. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _GNU_SOURCE
+#include "config.h"
+#include <errno.h>
+#include <stdarg.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <regex.h>
+#include "trace.h"
+#include "collection.h"
+#include "collection_tools.h"
+#include "ini_configobj.h"
+#include "ini_config_priv.h"
+#include "ini_defines.h"
+#include "path_utils.h"
+
+/* Constants to match */
+#define INI_CURRENT_DIR "."
+#define INI_PARENT_DIR ".."
+
+/* Size of incremental growth for ref of the array of strings */
+#define INI_AUG_ARR_SIZE_INC 50
+
+
+/* Function to add an error to the array */
+static void ini_aug_add_string(struct ref_array *ra,
+ const char *format,
+ ...)
+{
+ va_list args;
+ char *result = NULL;
+
+ TRACE_FLOW_ENTRY();
+
+ va_start(args, format);
+
+ if(vasprintf(&result, format, args )) {
+ TRACE_INFO_STRING("String:", result);
+ ref_array_append(ra, (void *)&result);
+ }
+
+ va_end(args);
+
+ TRACE_FLOW_EXIT();
+}
+
+/* Add error about opening directory */
+static void add_dir_open_error(int error, char *dirname,
+ struct ref_array *ra_err)
+{
+
+ TRACE_FLOW_ENTRY();
+
+ switch(error) {
+ case EACCES:
+ ini_aug_add_string(ra_err,
+ "Permission denied opening %s.",
+ dirname);
+ break;
+ case EMFILE:
+ case ENFILE:
+ ini_aug_add_string(ra_err,
+ "Too many file descriptors in use while opening %s.",
+ dirname);
+ break;
+ case ENOENT:
+ ini_aug_add_string(ra_err,
+ "Directory %s does not exist.",
+ dirname);
+ break;
+ case ENOTDIR:
+ ini_aug_add_string(ra_err,
+ "Path %s is not a directory.",
+ dirname);
+ break;
+ case ENOMEM:
+ ini_aug_add_string(ra_err,
+ "Insufficient memory while opening %s.",
+ dirname);
+ break;
+ default:
+ ini_aug_add_string(ra_err,
+ "Unknown error while opening %s.",
+ dirname);
+ break;
+ }
+
+ TRACE_FLOW_EXIT();
+}
+
+/* Cleanup callback for regex array */
+static void regex_cleanup(void *elem,
+ ref_array_del_enum type,
+ void *data)
+{
+ TRACE_FLOW_ENTRY();
+ regfree(*((regex_t **)elem));
+ free(*((regex_t **)elem));
+ TRACE_FLOW_EXIT();
+}
+
+
+/* Prepare array of regular expressions */
+static int ini_aug_regex_prepare(const char **patterns,
+ struct ref_array *ra_err,
+ struct ref_array **ra_regex)
+{
+ int error = EOK;
+ int reg_err = 0;
+ char **pat = NULL;
+ struct ref_array *ra = NULL;
+ regex_t *preg = NULL;
+ size_t buf_size = 0;
+ char *err_str = NULL;
+
+ TRACE_FLOW_ENTRY();
+
+ if (patterns) {
+
+ /* Create array to mark bad patterns */
+ error = ref_array_create(&ra,
+ sizeof(regex_t *),
+ INI_AUG_ARR_SIZE_INC,
+ regex_cleanup,
+ NULL);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to create array.", error);
+ return error;
+ }
+
+ memcpy(&pat, &patterns, sizeof(char **));
+
+ /* Run through the list and save precompiled patterns */
+ while (*pat) {
+ TRACE_INFO_STRING("Pattern:", *pat);
+
+ preg = calloc(1, sizeof(regex_t));
+ if (preg == NULL) {
+ TRACE_ERROR_NUMBER("Failed to create array.", ENOMEM);
+ ref_array_destroy(ra);
+ return ENOMEM;
+ }
+ reg_err = regcomp(preg, *pat, REG_NOSUB);
+ if (reg_err) {
+ /* Get size, allocate buffer, record error... */
+ buf_size = regerror(reg_err, preg, NULL, 0);
+ err_str = malloc (buf_size);
+ if (err_str == NULL) {
+ TRACE_ERROR_NUMBER("Failed to create array.", ENOMEM);
+ ref_array_destroy(ra);
+ free(preg);
+ return ENOMEM;
+ }
+ regerror(reg_err, preg, err_str, buf_size);
+ free(preg);
+ ini_aug_add_string(ra_err,
+ "Failed to process expression: %s."
+ " Compilation returned error: %s",
+ *pat, err_str);
+
+ /* All error processing is done - advance to next pattern */
+ pat++;
+ continue;
+ }
+ /* In case of no error add compiled expression into the buffer */
+ error = ref_array_append(ra, (void *)&preg);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to add element to array.", error);
+ ref_array_destroy(ra);
+ free(preg);
+ return error;
+ }
+ /* Advance */
+ pat++;
+ }
+ }
+
+ *ra_regex = ra;
+ /* ref_array_debug(*ra_regex, 1); */
+
+ TRACE_FLOW_EXIT();
+ return EOK;
+}
+
+/* Match file name */
+static bool ini_aug_match_name(char *name,
+ struct ref_array *ra_regex)
+{
+ uint32_t len = 0;
+ uint32_t i = 0;
+ bool match = false;
+ regex_t *preg = NULL;
+
+ TRACE_FLOW_ENTRY();
+
+ len = ref_array_len(ra_regex);
+ if (len == 0) {
+ /* List is empty - nothing to do */
+ TRACE_FLOW_EXIT();
+ return true;
+ }
+
+ TRACE_INFO_STRING("Name to match:", name);
+ TRACE_INFO_NUMBER("Number of regexes:", len);
+ /* ref_array_debug(ra_regex, 1);*/
+
+ for (i = 0; i < len; i++) {
+ preg = *((regex_t **)ref_array_get(ra_regex, i, NULL));
+ if (regexec(preg, name, 0, NULL, 0) == 0) {
+ TRACE_INFO_NUMBER("Name matched regex number:", i);
+ match = true;
+ break;
+ }
+ }
+
+ TRACE_FLOW_EXIT();
+ return match;
+}
+
+/* Check if this is a file and validate permission */
+static bool ini_check_file_perm(char *name,
+ struct access_check *check_perm,
+ struct ref_array *ra_err)
+{
+ bool ret = false;
+ int error = EOK;
+ struct stat file_info;
+
+ TRACE_FLOW_ENTRY();
+
+ errno = 0;
+ if (stat(name, &file_info) == -1) {
+ error = errno;
+ TRACE_ERROR_NUMBER("Failed to get file stats", error);
+ ini_aug_add_string(ra_err,
+ "Failed to read metadata for file %s."
+ " Skipping.",
+ name);
+ return false;
+ }
+
+ if (!S_ISREG(file_info.st_mode)) {
+ ini_aug_add_string(ra_err,
+ "File %s is not a regular file. Skipping.",
+ name);
+ return false;
+ }
+
+ if ((check_perm) && (check_perm->flags)) {
+ error = access_check_int(&file_info,
+ check_perm->flags,
+ check_perm->uid,
+ check_perm->gid,
+ check_perm->mode,
+ check_perm->mask);
+ if(error) {
+ TRACE_ERROR_NUMBER("Access check returned", error);
+ ini_aug_add_string(ra_err,
+ "File %s did not pass access check. Skipping.",
+ name);
+ return false;
+ }
+ }
+
+ ret = true;
+
+ TRACE_INFO_STRING("Returning", (ret ? "true" : "false"));
+ TRACE_FLOW_EXIT();
+
+ return ret;
+}
+
+/* Sort array */
+static void ini_aug_sort_list(struct ref_array *ra_list)
+{
+ unsigned len = 0, i = 0, j = 0, k = 0;
+
+ TRACE_FLOW_ENTRY();
+
+ len = ref_array_len(ra_list);
+ if (len == 0) return;
+
+ /* If have trace output array before sorting */
+/*
+#ifdef HAVE_TRACE
+ for (i = 0; i < len; i++) {
+ TRACE_INFO_STRING("Before:",
+ *((char **) ref_array_get(ra_list, i, NULL)));
+
+ }
+#endif
+*/
+
+ for (k = 0; k < len-1; k++) {
+ i = k;
+ j = k + 1;
+
+ while ((j > 0) && (strncmp(*((char **) ref_array_get(ra_list, i, NULL)),
+ *((char **) ref_array_get(ra_list, j, NULL)),
+ PATH_MAX) > 0)) {
+ ref_array_swap(ra_list, i, j);
+ i--;
+ j--;
+ }
+ }
+
+ /* And after sorting */
+/*
+#ifdef HAVE_TRACE
+ for (i = 0; i < len; i++) {
+ TRACE_INFO_STRING("After:",
+ *((char **) ref_array_get(ra_list, i, NULL)));
+
+ }
+#endif
+*/
+
+ TRACE_FLOW_EXIT();
+}
+
+/* Construct snippet lists based on the directory */
+static int ini_aug_construct_list(char *dirname ,
+ const char **patterns,
+ struct access_check *check_perm,
+ struct ref_array *ra_list,
+ struct ref_array *ra_err)
+{
+
+ int error = EOK;
+ DIR *dir = NULL;
+ struct dirent *entry = NULL;
+ char *snipname = NULL;
+ char fullname[PATH_MAX + 1] = {0};
+ struct ref_array *ra_regex = NULL;
+ bool match = false;
+
+ TRACE_FLOW_ENTRY();
+
+ /* Prepare patterns */
+ error = ini_aug_regex_prepare(patterns,
+ ra_err,
+ &ra_regex);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to prepare regex array.", error);
+ return error;
+ }
+
+ /* Open directory */
+ errno = 0;
+ dir = opendir(dirname);
+ if (!dir) {
+ error = errno;
+ if (error == ENOMEM) {
+ TRACE_ERROR_NUMBER("No memory to open dir.", ENOMEM);
+ ref_array_destroy(ra_regex);
+ return ENOMEM;
+ }
+ /* Log an error, it is a recoverable error */
+ add_dir_open_error(error, dirname, ra_err);
+ return EOK;
+ }
+
+ /* Loop through the directory */
+ while ((entry = readdir(dir)) != NULL)
+ {
+ TRACE_INFO_STRING("Processing", entry->d_name);
+
+ /* Always skip current and parent dirs */
+ if ((strncmp(entry->d_name,
+ INI_CURRENT_DIR,
+ sizeof(INI_CURRENT_DIR)) == 0) ||
+ (strncmp(entry->d_name,
+ INI_PARENT_DIR,
+ sizeof(INI_PARENT_DIR)) == 0)) continue;
+
+ /* Match names */
+ match = ini_aug_match_name(entry->d_name, ra_regex);
+
+ if (match) {
+
+ snprintf(fullname, PATH_MAX, "%s/%s", dirname, entry->d_name);
+
+ if(ini_check_file_perm(fullname, check_perm, ra_err)) {
+
+ /* Dup name and add to the array */
+ snipname = NULL;
+ snipname = strdup(fullname);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to dup string.", ENOMEM);
+ ref_array_destroy(ra_regex);
+ return ENOMEM;
+ }
+
+ error = ref_array_append(ra_list, (void *)&snipname);
+ if (error) {
+ TRACE_ERROR_NUMBER("No memory to add file to "
+ "the snippet list.",
+ ENOMEM);
+ ref_array_destroy(ra_regex);
+ return ENOMEM;
+ }
+ }
+ }
+ else {
+ ini_aug_add_string(ra_err,
+ "File %s did not match provided patterns."
+ " Skipping.",
+ entry->d_name);
+ }
+ }
+
+ closedir(dir);
+ ref_array_destroy(ra_regex);
+
+ ini_aug_sort_list(ra_list);
+
+ TRACE_FLOW_EXIT();
+ return EOK;
+}
+
+/* Construct the full dir path */
+static int ini_aug_expand_path(const char *path, char **fullname)
+{
+ int error = EOK;
+ char *dirname = NULL;
+
+ TRACE_FLOW_ENTRY();
+ TRACE_INFO_STRING("Input path", path);
+
+ dirname = malloc(PATH_MAX + 1);
+ if (!dirname) {
+ TRACE_ERROR_NUMBER("Failed to allocate memory for file path.", ENOMEM);
+ return ENOMEM;
+ }
+
+ /* Make the path */
+ error = make_normalized_absolute_path(dirname,
+ PATH_MAX,
+ path);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to resolve path", error);
+ free(dirname);
+ /* This is a recoverable error */
+ *fullname = NULL;
+ }
+ else *fullname = dirname;
+
+ TRACE_INFO_STRING("Output path", *fullname);
+ TRACE_FLOW_EXIT();
+
+ return EOK;
+}
+
+/* Prepare the lists of the files that need to be merged */
+static int ini_aug_preprare(const char *path,
+ const char **patterns,
+ struct access_check *check_perm,
+ struct ref_array *ra_list,
+ struct ref_array *ra_err)
+{
+ int error = EOK;
+ char *dirname = NULL;
+
+ TRACE_FLOW_ENTRY();
+
+ /* Contruct path */
+ error = ini_aug_expand_path(path, &dirname);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to allocate memory for dir path.", error);
+ return error;
+ }
+
+ /* Was it a good path? */
+ if (!dirname) {
+ TRACE_ERROR_NUMBER("Failed to resolve path", error);
+ ini_aug_add_string(ra_err, "Could not resolve directory path %s.",
+ path);
+ /* Path might not exist so it is a recoverable error */
+ return EOK;
+ }
+
+ /* Construct snipet lists */
+ error = ini_aug_construct_list(dirname,
+ patterns,
+ check_perm,
+ ra_list,
+ ra_err);
+ free(dirname);
+
+ TRACE_FLOW_EXIT();
+ return error;
+}
+
+/* Cleanup callback for string arrays */
+static void array_cleanup(void *elem,
+ ref_array_del_enum type,
+ void *data)
+{
+ TRACE_FLOW_ENTRY();
+ free(*((char **)elem));
+ TRACE_FLOW_EXIT();
+}
+
+/* Check that sections are in the given list */
+static int ini_aug_match_sec(struct ini_cfgobj *snip_cfg,
+ struct ref_array *ra_regex,
+ struct ref_array *ra_err,
+ char *snip_name,
+ bool *skip)
+{
+ int error = EOK;
+ char **section_list = NULL;
+ char **section_iter = NULL;
+ int size = 0;
+ bool match = false;
+ int match_count = 0;
+ int section_count = 0;
+
+ TRACE_FLOW_ENTRY();
+
+ section_list = ini_get_section_list(snip_cfg, &size, &error);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed create section list", error);
+ return error;
+ }
+
+ if (section_list == NULL) {
+ /* No sections in the file */
+ ini_aug_add_string(ra_err, "No sections found in file %s. Skipping.",
+ snip_name);
+ *skip = true;
+ TRACE_FLOW_EXIT();
+ return EOK;
+ }
+
+ section_iter = section_list;
+
+ while (*section_iter) {
+ match = ini_aug_match_name(*section_iter, ra_regex);
+ if (match) {
+ match_count++;
+ TRACE_INFO_STRING("Matched section", *section_iter);
+ }
+ else {
+ TRACE_INFO_STRING("Section not matched", *section_iter);
+ ini_aug_add_string(ra_err, "Section [%s] found in file %s is"
+ " not allowed.",
+ *section_iter, snip_name);
+ }
+ section_count++;
+ section_iter++;
+ }
+
+ ini_free_section_list(section_list);
+
+ /* Just in case check that we processed anything */
+ if (section_count == 0) {
+ ini_aug_add_string(ra_err, "No sections found in file %s. Skipping.",
+ snip_name);
+ *skip = true;
+ TRACE_FLOW_EXIT();
+ return EOK;
+ }
+
+ /* Were all sections matched? */
+ if (section_count != match_count) {
+ /* Snippet containes sections that are not allowed */
+ ini_aug_add_string(ra_err, "File %s contains sections that"
+ " are not allowed. Skipping.",
+ snip_name);
+ *skip = true;
+ TRACE_FLOW_EXIT();
+ return EOK;
+ }
+
+ /* Everything matched OK so we give green light to merge */
+ TRACE_INFO_STRING("File will be included", snip_name);
+ *skip = false;
+ TRACE_FLOW_EXIT();
+ return EOK;
+}
+
+
+/* Apply snippets */
+static int ini_aug_apply(struct ini_cfgobj *cfg,
+ struct ref_array *ra_list,
+ const char **sections,
+ int error_level,
+ uint32_t collision_flags,
+ uint32_t parse_flags,
+ uint32_t merge_flags,
+ struct ref_array *ra_err,
+ struct ref_array *ra_ok,
+ struct ini_cfgobj **out_cfg)
+{
+ int error = EOK;
+ uint32_t len = 0;
+ uint32_t i = 0;
+ uint32_t j = 0;
+ struct ini_cfgfile *file_ctx = NULL;
+ struct ini_cfgobj *snip_cfg = NULL;
+ struct ini_cfgobj *res_cfg = NULL;
+ struct ini_cfgobj *tmp_cfg = NULL;
+ char **error_list = NULL;
+ unsigned cnt = 0;
+ bool skip = false;
+ struct ref_array *ra_regex = NULL;
+ char *snip_name = NULL;
+
+ TRACE_FLOW_ENTRY();
+
+ len = ref_array_len(ra_list);
+ if (len == 0) {
+ /* List is empty - nothing to do */
+ *out_cfg = NULL;
+ TRACE_FLOW_EXIT();
+ return EOK;
+ }
+
+ error = ini_config_copy(cfg, &res_cfg);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to copy config object", error);
+ return error;
+ }
+
+ /* Prepare patterns */
+ error = ini_aug_regex_prepare(sections,
+ ra_err,
+ &ra_regex);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to prepare regex array.", error);
+ ini_config_destroy(res_cfg);
+ return error;
+ }
+
+ /* Loop through the snippets */
+ for (i = 0; i < len; i++) {
+
+ /* Prepare config object */
+ error = ini_config_create(&snip_cfg);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to create config object", error);
+ ini_config_destroy(res_cfg);
+ ref_array_destroy(ra_regex);
+ return error;
+ }
+
+ /* Process snippet */
+ snip_name = *((char **)ref_array_get (ra_list, i, NULL));
+ TRACE_INFO_STRING("Processing", snip_name);
+
+ /* Open file */
+ error = ini_config_file_open(snip_name,
+ INI_META_NONE,
+ &file_ctx);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to open snippet.", error);
+ ini_aug_add_string(ra_err, "Failed to open file %s.", snip_name);
+ ini_config_destroy(snip_cfg);
+ /* We can recover so go on */
+ continue;
+ }
+
+ TRACE_INFO_NUMBER("Error level:", error_level);
+ TRACE_INFO_NUMBER("Collision flags:", collision_flags);
+ TRACE_INFO_NUMBER("Parse level:", parse_flags);
+
+ /* Read config */
+ error = ini_config_parse(file_ctx,
+ error_level,
+ collision_flags,
+ parse_flags,
+ snip_cfg);
+
+ ini_config_file_destroy(file_ctx);
+ file_ctx = NULL;
+
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to parse configuration.", error);
+ cnt = ini_config_error_count(snip_cfg);
+ if (cnt) {
+ ini_aug_add_string(ra_err,
+ "Errors detected while parsing: %s.",
+ snip_name);
+
+ /* Extract errors */
+ error = ini_config_get_errors(snip_cfg, &error_list);
+ if (error) {
+ TRACE_ERROR_NUMBER("Can't get errors.", error);
+ ini_config_destroy(snip_cfg);
+ ini_config_destroy(res_cfg);
+ ref_array_destroy(ra_regex);
+ return error;
+ }
+
+ /* Copy errors into error array */
+ for (j=0; j< cnt; j++) {
+ ini_aug_add_string(ra_err, error_list[j]);
+ }
+ ini_config_free_errors(error_list);
+ }
+ /* The snippet was malformed, this is OK, go on */
+ if (error_level != INI_STOP_ON_NONE) {
+ ini_aug_add_string(ra_err,
+ "Due to errors file %s is not considered."
+ " Skipping.",
+ snip_name);
+ ini_config_destroy(snip_cfg);
+ continue;
+ }
+ /* If we are told to not stop try to process anyway */
+ }
+
+ /* Validate that file contains only allowed sections */
+ if (sections) {
+ /* Use a safe default, function should update it anyways
+ * but it is better to not merge than to allow bad snippet */
+ skip = true;
+ error = ini_aug_match_sec(snip_cfg, ra_regex, ra_err,
+ snip_name, &skip);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to validate section.", error);
+ ini_config_destroy(snip_cfg);
+ ini_config_destroy(res_cfg);
+ ref_array_destroy(ra_regex);
+ return error;
+ }
+ }
+
+ /* Merge */
+ if (!skip) {
+ /* col_debug_collection(res_cfg->cfg, COL_TRAVERSE_DEFAULT); */
+ error = ini_config_merge(res_cfg, snip_cfg, merge_flags, &tmp_cfg);
+ if (error) {
+ if (error == ENOMEM) {
+ TRACE_ERROR_NUMBER("Merge failed.", error);
+ ini_config_destroy(snip_cfg);
+ ini_config_destroy(res_cfg);
+ ref_array_destroy(ra_regex);
+ return error;
+ }
+ else if
+ ((error == EEXIST) &&
+ ((((merge_flags & INI_MS_MASK) == INI_MS_DETECT) &&
+ ((merge_flags & INI_MV2S_MASK) != INI_MV2S_ERROR)) ||
+ (((merge_flags & INI_MS_MASK) != INI_MS_ERROR) &&
+ ((merge_flags & INI_MV2S_MASK) == INI_MV2S_DETECT)))) {
+ TRACE_ERROR_NUMBER("Got error in detect mode", error);
+ /* Fall through! */
+ }
+ else {
+ ini_aug_add_string(ra_err,
+ "Errors during merge."
+ " Snippet ignored %s.",
+ snip_name);
+ /* The snippet failed to merge, this is OK, go on */
+ TRACE_INFO_NUMBER("Merge failure.Continue. Error", error);
+ ini_config_destroy(snip_cfg);
+ continue;
+ }
+ }
+ TRACE_INFO_STRING("Merged file.", snip_name);
+ /* col_debug_collection(tmp_cfg->cfg, COL_TRAVERSE_DEFAULT); */
+ ini_config_destroy(res_cfg);
+ res_cfg = tmp_cfg;
+
+ /* Record that snippet was successfully merged */
+ ini_aug_add_string(ra_ok, "%s", snip_name);
+ }
+ /* Cleanup */
+ ini_config_destroy(snip_cfg);
+ }
+
+ ref_array_destroy(ra_regex);
+ *out_cfg = res_cfg;
+ TRACE_FLOW_EXIT();
+ return error;
+}
+
+/* Function to merge additional snippets of the config file
+ * from a provided directory.
+ */
+int ini_config_augment(struct ini_cfgobj *base_cfg,
+ const char *path,
+ const char **patterns,
+ const char **sections,
+ struct access_check *check_perm,
+ int error_level,
+ uint32_t collision_flags,
+ uint32_t parse_flags,
+ uint32_t merge_flags,
+ struct ini_cfgobj **result_cfg,
+ struct ref_array **error_list,
+ struct ref_array **success_list)
+{
+ int error = EOK;
+ /* The internal list that will hold snippet file names */
+ struct ref_array *ra_list = NULL;
+ /* List of error strings that will be returned to the caller */
+ struct ref_array *ra_err = NULL;
+ /* List of files that were merged */
+ struct ref_array *ra_ok = NULL;
+ /* Resulting configuration object */
+ struct ini_cfgobj *out_cfg = NULL;
+
+ /* Check arguments */
+ if (base_cfg == NULL) {
+ TRACE_ERROR_NUMBER("Invalid argument", EINVAL);
+ return EINVAL;
+ }
+
+ if (result_cfg == NULL) {
+ TRACE_ERROR_NUMBER("Invalid argument", EINVAL);
+ return EINVAL;
+ }
+
+
+ /* Create arrays for lists */
+ if ((ref_array_create(&ra_list,
+ sizeof(char *),
+ INI_AUG_ARR_SIZE_INC,
+ array_cleanup,
+ NULL) != 0) ||
+ (ref_array_create(&ra_err,
+ sizeof(char *),
+ INI_AUG_ARR_SIZE_INC * 5,
+ array_cleanup,
+ NULL) != 0) ||
+ (ref_array_create(&ra_ok,
+ sizeof(char *),
+ INI_AUG_ARR_SIZE_INC * 5,
+ array_cleanup,
+ NULL) != 0)) {
+ TRACE_ERROR_NUMBER("Failed to allocate memory for arrays.",
+ ENOMEM);
+ ref_array_destroy(ra_list);
+ ref_array_destroy(ra_err);
+ ref_array_destroy(ra_ok);
+ return ENOMEM;
+ }
+
+ /* Construct snipet lists */
+ error = ini_aug_preprare(path,
+ patterns,
+ check_perm,
+ ra_list,
+ ra_err);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to prepare lists of snippets.",
+ error);
+ ref_array_destroy(ra_list);
+ ref_array_destroy(ra_err);
+ ref_array_destroy(ra_ok);
+ return error;
+ }
+
+ /* Apply snippets */
+ error = ini_aug_apply(base_cfg,
+ ra_list,
+ sections,
+ error_level,
+ collision_flags,
+ parse_flags,
+ merge_flags,
+ ra_err,
+ ra_ok,
+ &out_cfg);
+ if (error) {
+ TRACE_ERROR_NUMBER("Failed to process snippet list.",
+ error);
+ ref_array_destroy(ra_list);
+ ref_array_destroy(ra_err);
+ ref_array_destroy(ra_ok);
+ return error;
+ }
+
+ /* Cleanup */
+ ref_array_destroy(ra_list);
+
+ *result_cfg = out_cfg;
+
+ if (error_list) {
+ *error_list = ra_err;
+ }
+ else {
+ ref_array_destroy(ra_err);
+ }
+
+ if (success_list) {
+ *success_list = ra_ok;
+ }
+ else {
+ ref_array_destroy(ra_ok);
+ }
+
+ TRACE_FLOW_EXIT();
+ return error;
+}
+
diff --git a/ini/ini_augment_ut.c b/ini/ini_augment_ut.c
new file mode 100644
index 0000000..94d610e
--- /dev/null
+++ b/ini/ini_augment_ut.c
@@ -0,0 +1,353 @@
+/*
+ INI LIBRARY
+
+ Unit test for the comment object.
+
+ Copyright (C) Dmitri Pal <d...@redhat.com> 2014
+
+ INI Library is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ INI Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with INI Library. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+/* #define TRACE_LEVEL 7 */
+#define TRACE_HOME
+#include "trace.h"
+#include "ini_configobj.h"
+#include "ini_config_priv.h"
+#include "collection_tools.h"
+#include "path_utils.h"
+
+int verbose = 0;
+
+#define INIOUT(foo) \
+ do { \
+ if (verbose) { printf("%30s(%4d): ",__FUNCTION__,__LINE__); foo; } \
+ } while(0)
+
+typedef int (*test_fn)(void);
+
+void print_list(struct ref_array *list);
+int print_list_to_file(struct ref_array *list,
+ const char *filename,
+ const char *mode);
+static int expand_path(const char *path, char **fullname);
+
+
+/* Construct the full dir path */
+static int expand_path(const char *path, char **fullname)
+{
+ int error = EOK;
+ char *dirname = NULL;
+
+ TRACE_FLOW_ENTRY();
+ TRACE_INFO_STRING("Input path", path);
+
+ dirname = malloc(PATH_MAX + 1);
+ if (!dirname) {
+ INIOUT(printf("Failed to allocate memory for file path."));
+ return ENOMEM;
+ }
+
+ /* Make the path */
+ error = make_normalized_absolute_path(dirname,
+ PATH_MAX,
+ path);
+ if (error) {
+ INIOUT(printf("Failed to resolve path %d\n", error));
+ free(dirname);
+ return error;
+ }
+ else *fullname = dirname;
+
+ TRACE_INFO_STRING("Output path", *fullname);
+ TRACE_FLOW_EXIT();
+
+ return EOK;
+}
+
+static int prepare_results(const char *srcdir,
+ const char *srcfile,
+ const char *destfile)
+{
+ int error = EOK;
+ char *exp_src= NULL;
+ FILE *fsrc = NULL;
+ FILE *fout = NULL;
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t rd;
+
+ TRACE_FLOW_ENTRY();
+
+ error = expand_path(srcdir, &exp_src);
+ if (error) {
+ INIOUT(printf("Expand path returned error %d\n", error));
+ return error;
+ }
+
+ INIOUT(printf("Source file: %s\n", srcfile));
+ INIOUT(printf("Output file: %s\n", destfile));
+
+ fsrc = fopen(srcfile, "r");
+ if (!fsrc) {
+ error = errno;
+ free(exp_src);
+ INIOUT(printf("Failed to open source file %d\n", error));
+ return error;
+ }
+
+ fout = fopen(destfile, "w");
+ if (!fsrc) {
+ error = errno;
+ fclose(fsrc);
+ free(exp_src);
+ INIOUT(printf("Failed to open output file %d\n", error));
+ return error;
+ }
+
+ INIOUT(printf("Path %s\n", exp_src));
+
+ while ((rd = getline(&line, &len, fsrc)) != -1) {
+ if (strchr(line, '%')) fprintf(fout, line, exp_src, "/ini/ini.d");
+ else fprintf(fout, "%s", line);
+ }
+
+ if (line)
+ free(line);
+
+ fclose(fsrc);
+ fclose(fout);
+ free(exp_src);
+
+ TRACE_FLOW_EXIT();
+ return EOK;
+}
+
+/* Function to print contents of the list */
+void print_list(struct ref_array *list)
+{
+ uint32_t i = 0;
+ char *ret = NULL;
+ void *ptr = NULL;
+
+ for (;;) {
+ ptr = ref_array_get(list, i, &ret);
+ if (ptr) {
+ INIOUT(printf("%s\n", ret));
+ i++;
+ }
+ else break;
+ }
+}
+
+/* Function to print contents of the list */
+int print_list_to_file(struct ref_array *list,
+ const char *filename,
+ const char *mode)
+{
+ uint32_t i = 0;
+ char *ret = NULL;
+ void *ptr = NULL;
+ FILE *file = NULL;
+
+ file = fopen(filename, mode);
+ if (file) {
+ for (;;) {
+ ptr = ref_array_get(list, i, &ret);
+ if (ptr) {
+ fprintf(file,"%s\n", ret);
+ i++;
+ }
+ else break;
+ }
+ }
+ else {
+ printf("Failed to open file for results\n");
+ return -1;
+ }
+ fclose(file);
+ return 0;
+}
+
+
+/* Basic test */
+static int basic_test(void)
+{
+ int error = EOK;
+ int error1 = EOK;
+ char indir[PATH_MAX];
+ char srcname[PATH_MAX];
+ char filename[PATH_MAX];
+ char resname[PATH_MAX];
+ char command[PATH_MAX * 3];
+ char *builddir = NULL;
+ char *srcdir = NULL;
+ struct ini_cfgobj *in_cfg = NULL;
+ struct ini_cfgobj *result_cfg = NULL;
+ struct ref_array *error_list = NULL;
+ struct ref_array *success_list = NULL;
+ struct access_check ac = { INI_ACCESS_CHECK_MODE,
+ 0,
+ 0,
+ 0444,
+ 0444 };
+
+ /* Match all that do not start with 'r'
+ * and end with '.conf' and then match all
+ * ending with '.conf' */
+ const char **patterns = (const char *[]) { "#",
+ "^[^r][a-z]*\\.conf$",
+ "^real\\.conf$",
+ NULL };
+
+ /* Match all that do not start with 'r'
+ * and end with '.conf' and then match all
+ * ending with '.conf' */
+ const char **sections = (const char *[]) { "config",
+ "monitor",
+ "domains",
+ "services",
+ "provider",
+ NULL };
+
+
+ INIOUT(printf("<==== Start ====>\n"));
+
+ srcdir = getenv("srcdir");
+
+ builddir = getenv("builddir");
+
+ snprintf(indir, PATH_MAX, "%s/ini/ini.d",
+ (srcdir == NULL) ? "." : srcdir);
+
+
+ /* When run in dev environment there can be some temp files which
+ * we need to clean. */
+ snprintf(command, PATH_MAX * 3, "rm %s/*~", indir);
+ system(command);
+
+ /* Make the file path independent */
+ snprintf(srcname, PATH_MAX, "%s/ini/ini.d/merge.validator",
+ (srcdir == NULL) ? "." : srcdir);
+
+ snprintf(filename, PATH_MAX, "%s/merge.validator.in",
+ (builddir == NULL) ? "." : builddir);
+
+
+ snprintf(resname, PATH_MAX, "%s/merge.validator.out",
+ (builddir == NULL) ? "." : builddir);
+
+ /* Prepare results file so that we can compare results */
+ error = prepare_results(srcdir, srcname, filename);
+ if (error) {
+ INIOUT(printf("Failed to results file. Error %d.\n", error));
+ return error;
+ }
+
+ /* Create config collection */
+ error = ini_config_create(&in_cfg);
+ if (error) {
+ INIOUT(printf("Failed to create collection. Error %d.\n", error));
+ return error;
+ }
+
+ error = ini_config_augment(in_cfg,
+ indir,
+ patterns,
+ sections,
+ &ac,
+ INI_STOP_ON_NONE,
+ INI_MV1S_DETECT|INI_MV2S_DETECT|INI_MS_DETECT,
+ INI_PARSE_NOSPACE|INI_PARSE_NOTAB,
+ INI_MV2S_DETECT|INI_MS_DETECT,
+ &result_cfg,
+ &error_list,
+ &success_list);
+ if (error) {
+ INIOUT(printf("Augmentation failed with error %d!\n", error));
+ }
+
+ print_list(error_list);
+ print_list(success_list);
+
+ if (!result_cfg) {
+ error = -1;
+ printf("Configuration is empty.\n");
+ }
+ else INIOUT(col_debug_collection(result_cfg->cfg, COL_TRAVERSE_DEFAULT));
+
+ error = print_list_to_file(error_list, "merge.validator.out", "w");
+ error1 = print_list_to_file(success_list, "merge.validator.out", "a");
+ /* Save error */
+ if ((error1 !=0) && (error == 0)) error = error1;
+
+ snprintf(command, PATH_MAX * 3, "diff -q %s %s", filename, resname);
+ error = system(command);
+ INIOUT(printf("Comparison of %s %s returned: %d\n",
+ filename, resname, error));
+
+ if ((error) || (WEXITSTATUS(error))) {
+ printf("Failed to run diff command %d %d.\n", error,
+ WEXITSTATUS(error));
+ ref_array_destroy(error_list);
+ ref_array_destroy(success_list);
+ ini_config_destroy(in_cfg);
+ ini_config_destroy(result_cfg);
+ return -1;
+ }
+
+ /* Cleanup */
+ ref_array_destroy(error_list);
+ ref_array_destroy(success_list);
+ ini_config_destroy(in_cfg);
+ ini_config_destroy(result_cfg);
+
+ INIOUT(printf("<==== End ====>\n"));
+
+ return error;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int error = EOK;
+ test_fn tests[] = { basic_test,
+ NULL };
+ test_fn t;
+ int i = 0;
+ char *var;
+
+ if ((argc > 1) && (strcmp(argv[1], "-v") == 0)) verbose = 1;
+ else {
+ var = getenv("COMMON_TEST_VERBOSE");
+ if (var) verbose = 1;
+ }
+
+ INIOUT(printf("Start\n"));
+
+ while ((t = tests[i++])) {
+ error = t();
+ if (error) {
+ printf("Failed with error %d!\n", error);
+ return error;
+ }
+ }
+
+ INIOUT(printf("Success!\n"));
+ return 0;
+}
--
1.7.1
_______________________________________________
sssd-devel mailing list
sssd-devel@lists.fedorahosted.org
https://lists.fedorahosted.org/mailman/listinfo/sssd-devel