blob: c76e42d5aa56ae1bb0f015483f71a5f4241d2d6a [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) {
Harald Welteeda6fe42018-09-25 22:51:20 +0200148 int i;
149 for (i = 0; i < cur_indent - indent; i++) {
Harald Welte81bfef92018-09-25 16:34:19 +0200150 /* go to parent, add another sibling */
151 if (last->parent)
152 last = last->parent;
153 }
154 n = node_alloc_child(last->parent);
155 } else {
156 /* add a new sibling (child of parent) */
157 n = node_alloc_child(last->parent);
158 }
159 n->line = talloc_strdup(n, line);
160
161 last = n;
162 cur_indent = indent;
163 }
164
165 return root;
166}
167
168static void append_patch(struct node *cfg, struct node *patch)
169{
170 struct node *n;
171
172 llist_for_each_entry(n, &patch->children, list) {
173 if (llist_empty(&n->children)) {
174 struct node *t;
175 /* we are an end-node, i.e. something that needs to be
176 * patched into the original tree. We do this by simply
177 * appending it to the list of siblings */
178 t = node_alloc_child(cfg);
179 t->line = talloc_strdup(t, n->line);
180 } else {
181 struct node *c;
182 /* we need to iterate / recurse further */
183
184 /* try to find the matching original node */
185 c = node_find_child(cfg, n->line);
186 if (!c) {
187 /* create it, if it's missing */
188 c = node_alloc_child(cfg);
189 c->line = talloc_strdup(c, n->line);
190 }
191 append_patch(c, n);
192 }
193 }
194}
195
196
197static int level = -1;
198
199static void dump_node(struct node *root, FILE *out, bool print_node_depth)
200{
201 struct node *n;
202 level++;
203
204 if (root->line) {
205 if (print_node_depth) {
Harald Welteeda6fe42018-09-25 22:51:20 +0200206 int i;
207 for (i = 0; i < level; i++)
Harald Welte81bfef92018-09-25 16:34:19 +0200208 fputc('*', out);
209 }
210
211 fprintf(out, "%s\n", root->line);
212 }
213
214 llist_for_each_entry(n, &root->children, list) {
215 dump_node(n, out, print_node_depth);
216 }
217 level--;
218}
219
220static void exit_usage(int rc)
221{
222 fprintf(stderr, "Usage: osmo-config-merge <config-file> <config-patch> [--debug]\n");
223 exit(rc);
224}
225
226
227int main(int argc, char **argv)
228{
229 const char *base_fname, *patch_fname;
230 struct node *base_tree, *patch_tree;
231 bool debug_enabled = false;
232
233 void *ctx = talloc_named_const(NULL, 0, "root");
234
235 if (argc < 3)
236 exit_usage(1);
237
238 base_fname = argv[1];
239 patch_fname = argv[2];
240
241 if (argc > 3) {
242 if (!strcmp(argv[3], "--debug"))
243 debug_enabled = true;
244 else
245 exit_usage(1);
246 }
247
248 base_tree = file_read(ctx, base_fname);
249 patch_tree = file_read(ctx, patch_fname);
250
251 if (debug_enabled) {
252 fprintf(stderr, "====== dumping tree (base)\n");
253 dump_node(base_tree, stderr, true);
254 fprintf(stderr, "====== dumping tree (patch)\n");
255 dump_node(patch_tree, stderr, true);
256 }
257
258 append_patch(base_tree, patch_tree);
259
260 if (debug_enabled)
261 fprintf(stderr, "====== dumping tree (patched)\n");
262 dump_node(base_tree, stdout, false);
263 fflush(stdout);
264
265 /* make AddressSanitizer / LeakSanitizer happy by recursively freeing the trees */
266 talloc_free(patch_tree);
267 talloc_free(base_tree);
Vadim Yanitskiyf7aec792018-09-27 04:43:12 +0700268
269 return 0;
Harald Welte81bfef92018-09-25 16:34:19 +0200270}