blob: ed2039ac80306aac564d68156c0950a2f2929518 [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 *
Harald Welte81bfef92018-09-25 16:34:19 +020018 */
19
20/*
21 This utility allows you to merge an incremental config "patch"
22 into an osmocom-style config file.
23
24 The patch file follows the same syntax as the original config file.
25
26 It works by appending the leaf nodes of the patch file to the respective
27 nodes of the input config file.
28
29 This process allows configuration file changes/updates to be performed
30 in a more stable/reliable way than by means of [unified] diff files,
31 as they break every time the context lines break.
32
33 osmo-config-merge doesn't suffer from this problem, as it understands
34 the tree-like nature of VTY config files.
35
36 NITE: This only works with configuration files that have proper
37 indenting, i.e. every level in the hierarchy must be indented excatly
38 one character, not multiple.
39 */
40
41#include <stdio.h>
42#include <string.h>
Daniel Willmann83c71342018-09-27 17:16:00 +020043#include <errno.h>
Harald Welte81bfef92018-09-25 16:34:19 +020044
45#include <osmocom/core/linuxlist.h>
46#include <osmocom/core/talloc.h>
47#include <osmocom/core/utils.h>
Max2e717c92019-02-13 18:00:06 +010048#include <osmocom/core/msgfile.h>
Harald Welte81bfef92018-09-25 16:34:19 +020049
50struct node {
51 struct node *parent; /* back-pointer */
52 struct llist_head list; /* part of parent->children */
53 struct llist_head children; /* our own children */
54 char *line;
55};
56
Max2e717c92019-02-13 18:00:06 +010057struct osmo_patch_entry {
58 struct llist_head list;
59 struct node *tree;
60};
61
Harald Welte81bfef92018-09-25 16:34:19 +020062/* allocate a new node */
63static struct node *node_alloc(void *ctx)
64{
65 struct node *node = talloc_zero(ctx, struct node);
66 OSMO_ASSERT(node);
67 INIT_LLIST_HEAD(&node->children);
68 return node;
69}
70
71/* allocate a new node as child of given parent */
72static struct node *node_alloc_child(struct node *parent)
73{
74 struct node *node = node_alloc(parent);
75 node->parent = parent;
76 llist_add_tail(&node->list, &parent->children);
77 return node;
78}
79
80/* find a given child specified by name/line string within given parent */
81static struct node *node_find_child(struct node *parent, const char *line)
82{
83 struct node *n;
84
85 llist_for_each_entry(n, &parent->children, list) {
86 if (!strcmp(line, n->line))
87 return n;
88 }
89 return NULL;
90}
91
92/* count the number of spaces / indent level */
93static int count_indent(const char *line)
94{
95 int i;
96
97 for (i = 0; i < strlen(line); i++) {
98 if (line[i] != ' ')
99 return i;
100 }
101 return i;
102}
103
104/* strip any triling CR / LF */
105static void chomp(char *line)
106{
107 while (1) {
108 int len = strlen(line);
109 if (len == 0)
110 return;
111 char *lastch = &line[len-1];
112 switch (*lastch) {
113 case '\n':
114 case '\r':
115 *lastch = '\0';
116 default:
117 return;
118 }
119 }
120}
121
122/* read a config file and parse it into a tree of nodes */
123static struct node *file_read(void *ctx, const char *fname)
124{
125 struct node *root, *last;
126 FILE *infile;
127 char line[1024];
128 int cur_indent = -1;
129 unsigned int line_num = 0;
130
131 infile = fopen(fname, "r");
Daniel Willmann83c71342018-09-27 17:16:00 +0200132 if (!infile) {
133 fprintf(stderr, "Could not open file '%s': %s\n",
134 fname, strerror(errno));
Harald Welte81bfef92018-09-25 16:34:19 +0200135 return NULL;
Daniel Willmann83c71342018-09-27 17:16:00 +0200136 }
Harald Welte81bfef92018-09-25 16:34:19 +0200137
138 root = node_alloc(ctx);
139 last = root;
140 while (fgets(line, sizeof(line), infile)) {
141 line_num++;
142 chomp(line);
143 int indent = count_indent(line);
144 struct node *n;
145 if (indent > cur_indent) {
146 if (indent > cur_indent+1) {
147 fprintf(stderr, "File '%s' isn't well-formed in line %u, aborting!\n",
148 fname, line_num);
Harald Welte41b6b5e2018-10-21 10:57:36 +0200149 fclose(infile);
Daniel Willmann83c71342018-09-27 17:16:00 +0200150 return NULL;
Harald Welte81bfef92018-09-25 16:34:19 +0200151 }
152 /* new child to last node */
153 n = node_alloc_child(last);
154 } else if (indent < cur_indent) {
Harald Welteeda6fe42018-09-25 22:51:20 +0200155 int i;
156 for (i = 0; i < cur_indent - indent; i++) {
Harald Welte81bfef92018-09-25 16:34:19 +0200157 /* go to parent, add another sibling */
158 if (last->parent)
159 last = last->parent;
160 }
161 n = node_alloc_child(last->parent);
162 } else {
163 /* add a new sibling (child of parent) */
164 n = node_alloc_child(last->parent);
165 }
166 n->line = talloc_strdup(n, line);
167
168 last = n;
169 cur_indent = indent;
170 }
171
Harald Welte41b6b5e2018-10-21 10:57:36 +0200172 fclose(infile);
Harald Welte81bfef92018-09-25 16:34:19 +0200173 return root;
174}
175
176static void append_patch(struct node *cfg, struct node *patch)
177{
178 struct node *n;
179
180 llist_for_each_entry(n, &patch->children, list) {
181 if (llist_empty(&n->children)) {
182 struct node *t;
183 /* we are an end-node, i.e. something that needs to be
184 * patched into the original tree. We do this by simply
185 * appending it to the list of siblings */
186 t = node_alloc_child(cfg);
187 t->line = talloc_strdup(t, n->line);
188 } else {
189 struct node *c;
190 /* we need to iterate / recurse further */
191
192 /* try to find the matching original node */
193 c = node_find_child(cfg, n->line);
194 if (!c) {
195 /* create it, if it's missing */
196 c = node_alloc_child(cfg);
197 c->line = talloc_strdup(c, n->line);
198 }
199 append_patch(c, n);
200 }
201 }
202}
203
204
205static int level = -1;
206
207static void dump_node(struct node *root, FILE *out, bool print_node_depth)
208{
209 struct node *n;
210 level++;
211
212 if (root->line) {
213 if (print_node_depth) {
Harald Welteeda6fe42018-09-25 22:51:20 +0200214 int i;
215 for (i = 0; i < level; i++)
Harald Welte81bfef92018-09-25 16:34:19 +0200216 fputc('*', out);
217 }
218
219 fprintf(out, "%s\n", root->line);
220 }
221
222 llist_for_each_entry(n, &root->children, list) {
223 dump_node(n, out, print_node_depth);
224 }
225 level--;
226}
227
228static void exit_usage(int rc)
229{
Max2e717c92019-02-13 18:00:06 +0100230 fprintf(stderr, "Usage: osmo-config-merge <config-file> <config-patch>...<config-patch> [--debug]\n");
Harald Welte81bfef92018-09-25 16:34:19 +0200231 exit(rc);
232}
233
234
235int main(int argc, char **argv)
236{
Max2e717c92019-02-13 18:00:06 +0100237 struct node *base_tree;
238 struct osmo_config_list *trees;
239 struct osmo_patch_entry *entry;
Harald Welte81bfef92018-09-25 16:34:19 +0200240 bool debug_enabled = false;
Max2e717c92019-02-13 18:00:06 +0100241 unsigned i;
Daniel Willmann83c71342018-09-27 17:16:00 +0200242 void *ctx;
Harald Welte81bfef92018-09-25 16:34:19 +0200243
244 if (argc < 3)
245 exit_usage(1);
246
Daniel Willmann83c71342018-09-27 17:16:00 +0200247 ctx = talloc_named_const(NULL, 0, "root");
248
Max2e717c92019-02-13 18:00:06 +0100249 base_tree = file_read(ctx, argv[1]);
250 trees = talloc_zero(ctx, struct osmo_config_list);
251 if (!base_tree || !trees) {
Daniel Willmann83c71342018-09-27 17:16:00 +0200252 talloc_free(ctx);
253 return 2;
254 }
255
Max2e717c92019-02-13 18:00:06 +0100256 INIT_LLIST_HEAD(&trees->entry);
257 for (i = 2; i < argc; i++) {
258 if (!strcmp(argv[i], "--debug"))
259 debug_enabled = true;
260 else {
261 entry = talloc_zero(trees, struct osmo_patch_entry);
262 if (!entry) {
263 talloc_free(ctx);
264 return 3;
265 }
266
267 entry->tree = file_read(ctx, argv[i]);
268 if (!entry->tree) {
269 talloc_free(ctx);
270 return 4;
271 }
272 llist_add_tail(&entry->list, &trees->entry);
273 }
274 }
275
276 if (llist_empty(&trees->entry))
277 exit_usage(1);
278
Harald Welte81bfef92018-09-25 16:34:19 +0200279 if (debug_enabled) {
280 fprintf(stderr, "====== dumping tree (base)\n");
281 dump_node(base_tree, stderr, true);
Harald Welte81bfef92018-09-25 16:34:19 +0200282 }
283
Max2e717c92019-02-13 18:00:06 +0100284 llist_for_each_entry(entry, &trees->entry, list) {
285 append_patch(base_tree, entry->tree);
286 if (debug_enabled) {
287 fprintf(stderr, "====== dumping tree (patch)\n");
288 dump_node(entry->tree, stderr, true);
289 }
290 }
Harald Welte81bfef92018-09-25 16:34:19 +0200291
292 if (debug_enabled)
293 fprintf(stderr, "====== dumping tree (patched)\n");
294 dump_node(base_tree, stdout, false);
295 fflush(stdout);
296
297 /* make AddressSanitizer / LeakSanitizer happy by recursively freeing the trees */
Max2e717c92019-02-13 18:00:06 +0100298 talloc_free(trees);
Harald Welte81bfef92018-09-25 16:34:19 +0200299 talloc_free(base_tree);
Daniel Willmann83c71342018-09-27 17:16:00 +0200300 talloc_free(ctx);
Vadim Yanitskiyf7aec792018-09-27 04:43:12 +0700301
302 return 0;
Harald Welte81bfef92018-09-25 16:34:19 +0200303}