blob: 477bb0289ba7fbd9fe2c62e700e7e26f9220dd4b [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>
Daniel Willmann83c71342018-09-27 17:16:00 +020047#include <errno.h>
Harald Welte81bfef92018-09-25 16:34:19 +020048
49#include <osmocom/core/linuxlist.h>
50#include <osmocom/core/talloc.h>
51#include <osmocom/core/utils.h>
Max2e717c92019-02-13 18:00:06 +010052#include <osmocom/core/msgfile.h>
Harald Welte81bfef92018-09-25 16:34:19 +020053
54struct node {
55 struct node *parent; /* back-pointer */
56 struct llist_head list; /* part of parent->children */
57 struct llist_head children; /* our own children */
58 char *line;
59};
60
Max2e717c92019-02-13 18:00:06 +010061struct osmo_patch_entry {
62 struct llist_head list;
63 struct node *tree;
64};
65
Harald Welte81bfef92018-09-25 16:34:19 +020066/* allocate a new node */
67static struct node *node_alloc(void *ctx)
68{
69 struct node *node = talloc_zero(ctx, struct node);
70 OSMO_ASSERT(node);
71 INIT_LLIST_HEAD(&node->children);
72 return node;
73}
74
75/* allocate a new node as child of given parent */
76static struct node *node_alloc_child(struct node *parent)
77{
78 struct node *node = node_alloc(parent);
79 node->parent = parent;
80 llist_add_tail(&node->list, &parent->children);
81 return node;
82}
83
84/* find a given child specified by name/line string within given parent */
85static struct node *node_find_child(struct node *parent, const char *line)
86{
87 struct node *n;
88
89 llist_for_each_entry(n, &parent->children, list) {
90 if (!strcmp(line, n->line))
91 return n;
92 }
93 return NULL;
94}
95
96/* count the number of spaces / indent level */
97static int count_indent(const char *line)
98{
99 int i;
100
101 for (i = 0; i < strlen(line); i++) {
102 if (line[i] != ' ')
103 return i;
104 }
105 return i;
106}
107
108/* strip any triling CR / LF */
109static void chomp(char *line)
110{
111 while (1) {
112 int len = strlen(line);
113 if (len == 0)
114 return;
115 char *lastch = &line[len-1];
116 switch (*lastch) {
117 case '\n':
118 case '\r':
119 *lastch = '\0';
120 default:
121 return;
122 }
123 }
124}
125
126/* read a config file and parse it into a tree of nodes */
127static struct node *file_read(void *ctx, const char *fname)
128{
129 struct node *root, *last;
130 FILE *infile;
131 char line[1024];
132 int cur_indent = -1;
133 unsigned int line_num = 0;
134
135 infile = fopen(fname, "r");
Daniel Willmann83c71342018-09-27 17:16:00 +0200136 if (!infile) {
137 fprintf(stderr, "Could not open file '%s': %s\n",
138 fname, strerror(errno));
Harald Welte81bfef92018-09-25 16:34:19 +0200139 return NULL;
Daniel Willmann83c71342018-09-27 17:16:00 +0200140 }
Harald Welte81bfef92018-09-25 16:34:19 +0200141
142 root = node_alloc(ctx);
143 last = root;
144 while (fgets(line, sizeof(line), infile)) {
145 line_num++;
146 chomp(line);
147 int indent = count_indent(line);
148 struct node *n;
149 if (indent > cur_indent) {
150 if (indent > cur_indent+1) {
151 fprintf(stderr, "File '%s' isn't well-formed in line %u, aborting!\n",
152 fname, line_num);
Harald Welte41b6b5e2018-10-21 10:57:36 +0200153 fclose(infile);
Daniel Willmann83c71342018-09-27 17:16:00 +0200154 return NULL;
Harald Welte81bfef92018-09-25 16:34:19 +0200155 }
156 /* new child to last node */
157 n = node_alloc_child(last);
158 } else if (indent < cur_indent) {
Harald Welteeda6fe42018-09-25 22:51:20 +0200159 int i;
160 for (i = 0; i < cur_indent - indent; i++) {
Harald Welte81bfef92018-09-25 16:34:19 +0200161 /* go to parent, add another sibling */
162 if (last->parent)
163 last = last->parent;
164 }
165 n = node_alloc_child(last->parent);
166 } else {
167 /* add a new sibling (child of parent) */
168 n = node_alloc_child(last->parent);
169 }
170 n->line = talloc_strdup(n, line);
171
172 last = n;
173 cur_indent = indent;
174 }
175
Harald Welte41b6b5e2018-10-21 10:57:36 +0200176 fclose(infile);
Harald Welte81bfef92018-09-25 16:34:19 +0200177 return root;
178}
179
180static void append_patch(struct node *cfg, struct node *patch)
181{
182 struct node *n;
183
184 llist_for_each_entry(n, &patch->children, list) {
185 if (llist_empty(&n->children)) {
186 struct node *t;
187 /* we are an end-node, i.e. something that needs to be
188 * patched into the original tree. We do this by simply
189 * appending it to the list of siblings */
190 t = node_alloc_child(cfg);
191 t->line = talloc_strdup(t, n->line);
192 } else {
193 struct node *c;
194 /* we need to iterate / recurse further */
195
196 /* try to find the matching original node */
197 c = node_find_child(cfg, n->line);
198 if (!c) {
199 /* create it, if it's missing */
200 c = node_alloc_child(cfg);
201 c->line = talloc_strdup(c, n->line);
202 }
203 append_patch(c, n);
204 }
205 }
206}
207
208
209static int level = -1;
210
211static void dump_node(struct node *root, FILE *out, bool print_node_depth)
212{
213 struct node *n;
214 level++;
215
216 if (root->line) {
217 if (print_node_depth) {
Harald Welteeda6fe42018-09-25 22:51:20 +0200218 int i;
219 for (i = 0; i < level; i++)
Harald Welte81bfef92018-09-25 16:34:19 +0200220 fputc('*', out);
221 }
222
223 fprintf(out, "%s\n", root->line);
224 }
225
226 llist_for_each_entry(n, &root->children, list) {
227 dump_node(n, out, print_node_depth);
228 }
229 level--;
230}
231
232static void exit_usage(int rc)
233{
Max2e717c92019-02-13 18:00:06 +0100234 fprintf(stderr, "Usage: osmo-config-merge <config-file> <config-patch>...<config-patch> [--debug]\n");
Harald Welte81bfef92018-09-25 16:34:19 +0200235 exit(rc);
236}
237
238
239int main(int argc, char **argv)
240{
Max2e717c92019-02-13 18:00:06 +0100241 struct node *base_tree;
242 struct osmo_config_list *trees;
243 struct osmo_patch_entry *entry;
Harald Welte81bfef92018-09-25 16:34:19 +0200244 bool debug_enabled = false;
Max2e717c92019-02-13 18:00:06 +0100245 unsigned i;
Daniel Willmann83c71342018-09-27 17:16:00 +0200246 void *ctx;
Harald Welte81bfef92018-09-25 16:34:19 +0200247
248 if (argc < 3)
249 exit_usage(1);
250
Daniel Willmann83c71342018-09-27 17:16:00 +0200251 ctx = talloc_named_const(NULL, 0, "root");
252
Max2e717c92019-02-13 18:00:06 +0100253 base_tree = file_read(ctx, argv[1]);
254 trees = talloc_zero(ctx, struct osmo_config_list);
255 if (!base_tree || !trees) {
Daniel Willmann83c71342018-09-27 17:16:00 +0200256 talloc_free(ctx);
257 return 2;
258 }
259
Max2e717c92019-02-13 18:00:06 +0100260 INIT_LLIST_HEAD(&trees->entry);
261 for (i = 2; i < argc; i++) {
262 if (!strcmp(argv[i], "--debug"))
263 debug_enabled = true;
264 else {
265 entry = talloc_zero(trees, struct osmo_patch_entry);
266 if (!entry) {
267 talloc_free(ctx);
268 return 3;
269 }
270
271 entry->tree = file_read(ctx, argv[i]);
272 if (!entry->tree) {
273 talloc_free(ctx);
274 return 4;
275 }
276 llist_add_tail(&entry->list, &trees->entry);
277 }
278 }
279
280 if (llist_empty(&trees->entry))
281 exit_usage(1);
282
Harald Welte81bfef92018-09-25 16:34:19 +0200283 if (debug_enabled) {
284 fprintf(stderr, "====== dumping tree (base)\n");
285 dump_node(base_tree, stderr, true);
Harald Welte81bfef92018-09-25 16:34:19 +0200286 }
287
Max2e717c92019-02-13 18:00:06 +0100288 llist_for_each_entry(entry, &trees->entry, list) {
289 append_patch(base_tree, entry->tree);
290 if (debug_enabled) {
291 fprintf(stderr, "====== dumping tree (patch)\n");
292 dump_node(entry->tree, stderr, true);
293 }
294 }
Harald Welte81bfef92018-09-25 16:34:19 +0200295
296 if (debug_enabled)
297 fprintf(stderr, "====== dumping tree (patched)\n");
298 dump_node(base_tree, stdout, false);
299 fflush(stdout);
300
301 /* make AddressSanitizer / LeakSanitizer happy by recursively freeing the trees */
Max2e717c92019-02-13 18:00:06 +0100302 talloc_free(trees);
Harald Welte81bfef92018-09-25 16:34:19 +0200303 talloc_free(base_tree);
Daniel Willmann83c71342018-09-27 17:16:00 +0200304 talloc_free(ctx);
Vadim Yanitskiyf7aec792018-09-27 04:43:12 +0700305
306 return 0;
Harald Welte81bfef92018-09-25 16:34:19 +0200307}