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