blob: afaf86b53154f71749ec72c8c6b177bfb4022212 [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>
52
53struct node {
54 struct node *parent; /* back-pointer */
55 struct llist_head list; /* part of parent->children */
56 struct llist_head children; /* our own children */
57 char *line;
58};
59
60/* allocate a new node */
61static struct node *node_alloc(void *ctx)
62{
63 struct node *node = talloc_zero(ctx, struct node);
64 OSMO_ASSERT(node);
65 INIT_LLIST_HEAD(&node->children);
66 return node;
67}
68
69/* allocate a new node as child of given parent */
70static struct node *node_alloc_child(struct node *parent)
71{
72 struct node *node = node_alloc(parent);
73 node->parent = parent;
74 llist_add_tail(&node->list, &parent->children);
75 return node;
76}
77
78/* find a given child specified by name/line string within given parent */
79static struct node *node_find_child(struct node *parent, const char *line)
80{
81 struct node *n;
82
83 llist_for_each_entry(n, &parent->children, list) {
84 if (!strcmp(line, n->line))
85 return n;
86 }
87 return NULL;
88}
89
90/* count the number of spaces / indent level */
91static int count_indent(const char *line)
92{
93 int i;
94
95 for (i = 0; i < strlen(line); i++) {
96 if (line[i] != ' ')
97 return i;
98 }
99 return i;
100}
101
102/* strip any triling CR / LF */
103static void chomp(char *line)
104{
105 while (1) {
106 int len = strlen(line);
107 if (len == 0)
108 return;
109 char *lastch = &line[len-1];
110 switch (*lastch) {
111 case '\n':
112 case '\r':
113 *lastch = '\0';
114 default:
115 return;
116 }
117 }
118}
119
120/* read a config file and parse it into a tree of nodes */
121static struct node *file_read(void *ctx, const char *fname)
122{
123 struct node *root, *last;
124 FILE *infile;
125 char line[1024];
126 int cur_indent = -1;
127 unsigned int line_num = 0;
128
129 infile = fopen(fname, "r");
Daniel Willmann83c71342018-09-27 17:16:00 +0200130 if (!infile) {
131 fprintf(stderr, "Could not open file '%s': %s\n",
132 fname, strerror(errno));
Harald Welte81bfef92018-09-25 16:34:19 +0200133 return NULL;
Daniel Willmann83c71342018-09-27 17:16:00 +0200134 }
Harald Welte81bfef92018-09-25 16:34:19 +0200135
136 root = node_alloc(ctx);
137 last = root;
138 while (fgets(line, sizeof(line), infile)) {
139 line_num++;
140 chomp(line);
141 int indent = count_indent(line);
142 struct node *n;
143 if (indent > cur_indent) {
144 if (indent > cur_indent+1) {
145 fprintf(stderr, "File '%s' isn't well-formed in line %u, aborting!\n",
146 fname, line_num);
Harald Welte41b6b5e2018-10-21 10:57:36 +0200147 fclose(infile);
Daniel Willmann83c71342018-09-27 17:16:00 +0200148 return NULL;
Harald Welte81bfef92018-09-25 16:34:19 +0200149 }
150 /* new child to last node */
151 n = node_alloc_child(last);
152 } else if (indent < cur_indent) {
Harald Welteeda6fe42018-09-25 22:51:20 +0200153 int i;
154 for (i = 0; i < cur_indent - indent; i++) {
Harald Welte81bfef92018-09-25 16:34:19 +0200155 /* go to parent, add another sibling */
156 if (last->parent)
157 last = last->parent;
158 }
159 n = node_alloc_child(last->parent);
160 } else {
161 /* add a new sibling (child of parent) */
162 n = node_alloc_child(last->parent);
163 }
164 n->line = talloc_strdup(n, line);
165
166 last = n;
167 cur_indent = indent;
168 }
169
Harald Welte41b6b5e2018-10-21 10:57:36 +0200170 fclose(infile);
Harald Welte81bfef92018-09-25 16:34:19 +0200171 return root;
172}
173
174static void append_patch(struct node *cfg, struct node *patch)
175{
176 struct node *n;
177
178 llist_for_each_entry(n, &patch->children, list) {
179 if (llist_empty(&n->children)) {
180 struct node *t;
181 /* we are an end-node, i.e. something that needs to be
182 * patched into the original tree. We do this by simply
183 * appending it to the list of siblings */
184 t = node_alloc_child(cfg);
185 t->line = talloc_strdup(t, n->line);
186 } else {
187 struct node *c;
188 /* we need to iterate / recurse further */
189
190 /* try to find the matching original node */
191 c = node_find_child(cfg, n->line);
192 if (!c) {
193 /* create it, if it's missing */
194 c = node_alloc_child(cfg);
195 c->line = talloc_strdup(c, n->line);
196 }
197 append_patch(c, n);
198 }
199 }
200}
201
202
203static int level = -1;
204
205static void dump_node(struct node *root, FILE *out, bool print_node_depth)
206{
207 struct node *n;
208 level++;
209
210 if (root->line) {
211 if (print_node_depth) {
Harald Welteeda6fe42018-09-25 22:51:20 +0200212 int i;
213 for (i = 0; i < level; i++)
Harald Welte81bfef92018-09-25 16:34:19 +0200214 fputc('*', out);
215 }
216
217 fprintf(out, "%s\n", root->line);
218 }
219
220 llist_for_each_entry(n, &root->children, list) {
221 dump_node(n, out, print_node_depth);
222 }
223 level--;
224}
225
226static void exit_usage(int rc)
227{
228 fprintf(stderr, "Usage: osmo-config-merge <config-file> <config-patch> [--debug]\n");
229 exit(rc);
230}
231
232
233int main(int argc, char **argv)
234{
235 const char *base_fname, *patch_fname;
236 struct node *base_tree, *patch_tree;
237 bool debug_enabled = false;
Daniel Willmann83c71342018-09-27 17:16:00 +0200238 void *ctx;
Harald Welte81bfef92018-09-25 16:34:19 +0200239
240 if (argc < 3)
241 exit_usage(1);
242
243 base_fname = argv[1];
244 patch_fname = argv[2];
245
246 if (argc > 3) {
247 if (!strcmp(argv[3], "--debug"))
248 debug_enabled = true;
249 else
250 exit_usage(1);
251 }
252
Daniel Willmann83c71342018-09-27 17:16:00 +0200253 ctx = talloc_named_const(NULL, 0, "root");
254
Harald Welte81bfef92018-09-25 16:34:19 +0200255 base_tree = file_read(ctx, base_fname);
256 patch_tree = file_read(ctx, patch_fname);
257
Daniel Willmann83c71342018-09-27 17:16:00 +0200258 if (!base_tree || ! patch_tree) {
259 talloc_free(ctx);
260 return 2;
261 }
262
Harald Welte81bfef92018-09-25 16:34:19 +0200263 if (debug_enabled) {
264 fprintf(stderr, "====== dumping tree (base)\n");
265 dump_node(base_tree, stderr, true);
266 fprintf(stderr, "====== dumping tree (patch)\n");
267 dump_node(patch_tree, stderr, true);
268 }
269
270 append_patch(base_tree, patch_tree);
271
272 if (debug_enabled)
273 fprintf(stderr, "====== dumping tree (patched)\n");
274 dump_node(base_tree, stdout, false);
275 fflush(stdout);
276
277 /* make AddressSanitizer / LeakSanitizer happy by recursively freeing the trees */
278 talloc_free(patch_tree);
279 talloc_free(base_tree);
Daniel Willmann83c71342018-09-27 17:16:00 +0200280 talloc_free(ctx);
Vadim Yanitskiyf7aec792018-09-27 04:43:12 +0700281
282 return 0;
Harald Welte81bfef92018-09-25 16:34:19 +0200283}