blob: 131486c1fb42caf48feddc9e722f51c7b03bdc35 [file] [log] [blame]
Harald Welte81bfef92018-09-25 16:34:19 +02001/*! \file osmo-config-merge.c
2 * Utility program for merging config files with patches */
3/*
4 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
5 *
6 * All Rights Reserved
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 */
23
24/*
25 This utility allows you to merge an incremental config "patch"
26 into an osmocom-style config file.
27
28 The patch file follows the same syntax as the original config file.
29
30 It works by appending the leaf nodes of the patch file to the respective
31 nodes of the input config file.
32
33 This process allows configuration file changes/updates to be performed
34 in a more stable/reliable way than by means of [unified] diff files,
35 as they break every time the context lines break.
36
37 osmo-config-merge doesn't suffer from this problem, as it understands
38 the tree-like nature of VTY config files.
39
40 NITE: This only works with configuration files that have proper
41 indenting, i.e. every level in the hierarchy must be indented excatly
42 one character, not multiple.
43 */
44
45#include <stdio.h>
46#include <string.h>
47
48#include <osmocom/core/linuxlist.h>
49#include <osmocom/core/talloc.h>
50#include <osmocom/core/utils.h>
51
52struct node {
53 struct node *parent; /* back-pointer */
54 struct llist_head list; /* part of parent->children */
55 struct llist_head children; /* our own children */
56 char *line;
57};
58
59/* allocate a new node */
60static struct node *node_alloc(void *ctx)
61{
62 struct node *node = talloc_zero(ctx, struct node);
63 OSMO_ASSERT(node);
64 INIT_LLIST_HEAD(&node->children);
65 return node;
66}
67
68/* allocate a new node as child of given parent */
69static struct node *node_alloc_child(struct node *parent)
70{
71 struct node *node = node_alloc(parent);
72 node->parent = parent;
73 llist_add_tail(&node->list, &parent->children);
74 return node;
75}
76
77/* find a given child specified by name/line string within given parent */
78static struct node *node_find_child(struct node *parent, const char *line)
79{
80 struct node *n;
81
82 llist_for_each_entry(n, &parent->children, list) {
83 if (!strcmp(line, n->line))
84 return n;
85 }
86 return NULL;
87}
88
89/* count the number of spaces / indent level */
90static int count_indent(const char *line)
91{
92 int i;
93
94 for (i = 0; i < strlen(line); i++) {
95 if (line[i] != ' ')
96 return i;
97 }
98 return i;
99}
100
101/* strip any triling CR / LF */
102static void chomp(char *line)
103{
104 while (1) {
105 int len = strlen(line);
106 if (len == 0)
107 return;
108 char *lastch = &line[len-1];
109 switch (*lastch) {
110 case '\n':
111 case '\r':
112 *lastch = '\0';
113 default:
114 return;
115 }
116 }
117}
118
119/* read a config file and parse it into a tree of nodes */
120static struct node *file_read(void *ctx, const char *fname)
121{
122 struct node *root, *last;
123 FILE *infile;
124 char line[1024];
125 int cur_indent = -1;
126 unsigned int line_num = 0;
127
128 infile = fopen(fname, "r");
129 if (!infile)
130 return NULL;
131
132 root = node_alloc(ctx);
133 last = root;
134 while (fgets(line, sizeof(line), infile)) {
135 line_num++;
136 chomp(line);
137 int indent = count_indent(line);
138 struct node *n;
139 if (indent > cur_indent) {
140 if (indent > cur_indent+1) {
141 fprintf(stderr, "File '%s' isn't well-formed in line %u, aborting!\n",
142 fname, line_num);
143 exit(2);
144 }
145 /* new child to last node */
146 n = node_alloc_child(last);
147 } else if (indent < cur_indent) {
148 for (int i = 0; i < cur_indent - indent; i++) {
149 /* go to parent, add another sibling */
150 if (last->parent)
151 last = last->parent;
152 }
153 n = node_alloc_child(last->parent);
154 } else {
155 /* add a new sibling (child of parent) */
156 n = node_alloc_child(last->parent);
157 }
158 n->line = talloc_strdup(n, line);
159
160 last = n;
161 cur_indent = indent;
162 }
163
164 return root;
165}
166
167static void append_patch(struct node *cfg, struct node *patch)
168{
169 struct node *n;
170
171 llist_for_each_entry(n, &patch->children, list) {
172 if (llist_empty(&n->children)) {
173 struct node *t;
174 /* we are an end-node, i.e. something that needs to be
175 * patched into the original tree. We do this by simply
176 * appending it to the list of siblings */
177 t = node_alloc_child(cfg);
178 t->line = talloc_strdup(t, n->line);
179 } else {
180 struct node *c;
181 /* we need to iterate / recurse further */
182
183 /* try to find the matching original node */
184 c = node_find_child(cfg, n->line);
185 if (!c) {
186 /* create it, if it's missing */
187 c = node_alloc_child(cfg);
188 c->line = talloc_strdup(c, n->line);
189 }
190 append_patch(c, n);
191 }
192 }
193}
194
195
196static int level = -1;
197
198static void dump_node(struct node *root, FILE *out, bool print_node_depth)
199{
200 struct node *n;
201 level++;
202
203 if (root->line) {
204 if (print_node_depth) {
205 for (int i = 0; i < level; i++)
206 fputc('*', out);
207 }
208
209 fprintf(out, "%s\n", root->line);
210 }
211
212 llist_for_each_entry(n, &root->children, list) {
213 dump_node(n, out, print_node_depth);
214 }
215 level--;
216}
217
218static void exit_usage(int rc)
219{
220 fprintf(stderr, "Usage: osmo-config-merge <config-file> <config-patch> [--debug]\n");
221 exit(rc);
222}
223
224
225int main(int argc, char **argv)
226{
227 const char *base_fname, *patch_fname;
228 struct node *base_tree, *patch_tree;
229 bool debug_enabled = false;
230
231 void *ctx = talloc_named_const(NULL, 0, "root");
232
233 if (argc < 3)
234 exit_usage(1);
235
236 base_fname = argv[1];
237 patch_fname = argv[2];
238
239 if (argc > 3) {
240 if (!strcmp(argv[3], "--debug"))
241 debug_enabled = true;
242 else
243 exit_usage(1);
244 }
245
246 base_tree = file_read(ctx, base_fname);
247 patch_tree = file_read(ctx, patch_fname);
248
249 if (debug_enabled) {
250 fprintf(stderr, "====== dumping tree (base)\n");
251 dump_node(base_tree, stderr, true);
252 fprintf(stderr, "====== dumping tree (patch)\n");
253 dump_node(patch_tree, stderr, true);
254 }
255
256 append_patch(base_tree, patch_tree);
257
258 if (debug_enabled)
259 fprintf(stderr, "====== dumping tree (patched)\n");
260 dump_node(base_tree, stdout, false);
261 fflush(stdout);
262
263 /* make AddressSanitizer / LeakSanitizer happy by recursively freeing the trees */
264 talloc_free(patch_tree);
265 talloc_free(base_tree);
266}