]>
Commit | Line | Data |
---|---|---|
31b3e1bb MM |
1 | /* |
2 | * BIRD Internet Routing Daemon -- Configuration File Handling | |
3 | * | |
50fe90ed | 4 | * (c) 1998--2000 Martin Mares <mj@ucw.cz> |
31b3e1bb MM |
5 | * |
6 | * Can be freely distributed and used under the terms of the GNU GPL. | |
7 | */ | |
8 | ||
06607335 MM |
9 | /** |
10 | * DOC: Configuration manager | |
11 | * | |
725270cb | 12 | * Configuration of BIRD is complex, yet straightforward. There are three |
06607335 | 13 | * modules taking care of the configuration: config manager (which takes care |
58f7d004 | 14 | * of storage of the config information and controls switching between configs), |
2e9b2421 | 15 | * lexical analyzer and parser. |
06607335 MM |
16 | * |
17 | * The configuration manager stores each config as a &config structure | |
18 | * accompanied by a linear pool from which all information associated | |
19 | * with the config and pointed to by the &config structure is allocated. | |
20 | * | |
725270cb | 21 | * There can exist up to four different configurations at one time: an active |
06607335 | 22 | * one (pointed to by @config), configuration we are just switching from |
9b9a7143 OZ |
23 | * (@old_config), one queued for the next reconfiguration (@future_config; if |
24 | * there is one and the user wants to reconfigure once again, we just free the | |
25 | * previous queued config and replace it with the new one) and finally a config | |
26 | * being parsed (@new_config). The stored @old_config is also used for undo | |
27 | * reconfiguration, which works in a similar way. Reconfiguration could also | |
28 | * have timeout (using @config_timer) and undo is automatically called if the | |
29 | * new configuration is not confirmed later. The new config (@new_config) and | |
30 | * associated linear pool (@cfg_mem) is non-NULL only during parsing. | |
06607335 | 31 | * |
9b9a7143 OZ |
32 | * Loading of new configuration is very simple: just call config_alloc() to get |
33 | * a new &config structure, then use config_parse() to parse a configuration | |
34 | * file and fill all fields of the structure and finally ask the config manager | |
35 | * to switch to the new config by calling config_commit(). | |
06607335 MM |
36 | * |
37 | * CLI commands are parsed in a very similar way -- there is also a stripped-down | |
2e9b2421 | 38 | * &config structure associated with them and they are lex-ed and parsed by the |
06607335 MM |
39 | * same functions, only a special fake token is prepended before the command |
40 | * text to make the parser recognize only the rules corresponding to CLI commands. | |
41 | */ | |
42 | ||
31b3e1bb MM |
43 | #include <setjmp.h> |
44 | #include <stdarg.h> | |
45 | ||
6b9fa320 | 46 | #undef LOCAL_DEBUG |
50fe90ed | 47 | |
31b3e1bb | 48 | #include "nest/bird.h" |
0e02abfd | 49 | #include "nest/route.h" |
31b3e1bb MM |
50 | #include "nest/protocol.h" |
51 | #include "nest/iface.h" | |
1b7ddb0e PT |
52 | #include "lib/resource.h" |
53 | #include "lib/string.h" | |
50fe90ed | 54 | #include "lib/event.h" |
7152e5ef | 55 | #include "sysdep/unix/timer.h" |
31b3e1bb | 56 | #include "conf/conf.h" |
1b7ddb0e | 57 | #include "filter/filter.h" |
9b0a0ba9 | 58 | |
31b3e1bb MM |
59 | |
60 | static jmp_buf conf_jmpbuf; | |
61 | ||
a92cf57d OZ |
62 | struct config *config, *new_config; |
63 | ||
64 | static struct config *old_config; /* Old configuration */ | |
65 | static struct config *future_config; /* New config held here if recon requested during recon */ | |
66 | static int old_cftype; /* Type of transition old_config -> config (RECONFIG_SOFT/HARD) */ | |
67 | static int future_cftype; /* Type of scheduled transition, may also be RECONFIG_UNDO */ | |
68 | /* Note that when future_cftype is RECONFIG_UNDO, then future_config is NULL, | |
69 | therefore proper check for future scheduled config checks future_cftype */ | |
70 | ||
71 | static event *config_event; /* Event for finalizing reconfiguration */ | |
72 | static timer *config_timer; /* Timer for scheduled configuration rollback */ | |
73 | ||
74 | /* These are public just for cmd_show_status(), should not be accessed elsewhere */ | |
75 | int shutting_down; /* Shutdown requested, do not accept new config changes */ | |
76 | int configuring; /* Reconfiguration is running */ | |
77 | int undo_available; /* Undo was not requested from last reconfiguration */ | |
78 | /* Note that both shutting_down and undo_available are related to requests, not processing */ | |
31b3e1bb | 79 | |
06607335 MM |
80 | /** |
81 | * config_alloc - allocate a new configuration | |
82 | * @name: name of the config | |
83 | * | |
84 | * This function creates new &config structure, attaches a resource | |
85 | * pool and a linear memory pool to it and makes it available for | |
86 | * further use. Returns a pointer to the structure. | |
87 | */ | |
31b3e1bb | 88 | struct config * |
9b0a0ba9 | 89 | config_alloc(const char *name) |
31b3e1bb MM |
90 | { |
91 | pool *p = rp_new(&root_pool, "Config"); | |
d4ff7482 | 92 | linpool *l = lp_new(p, 4080); |
31b3e1bb MM |
93 | struct config *c = lp_allocz(l, sizeof(struct config)); |
94 | ||
9b9a7143 OZ |
95 | /* Duplication of name string in local linear pool */ |
96 | uint nlen = strlen(name) + 1; | |
97 | char *ndup = lp_allocu(l, nlen); | |
98 | memcpy(ndup, name, nlen); | |
99 | ||
9b0a0ba9 | 100 | init_list(&c->tests); |
cf31112f | 101 | c->mrtdump_file = -1; /* Hack, this should be sysdep-specific */ |
31b3e1bb | 102 | c->pool = p; |
9b9a7143 OZ |
103 | c->mem = l; |
104 | c->file_name = ndup; | |
43270902 | 105 | c->load_time = now; |
90eb5e7a OZ |
106 | c->tf_route = c->tf_proto = (struct timeformat){"%T", "%F", 20*3600}; |
107 | c->tf_base = c->tf_log = (struct timeformat){"%F %T", NULL, 0}; | |
0c791f87 | 108 | c->gr_wait = DEFAULT_GR_WAIT; |
c37e7851 | 109 | |
31b3e1bb MM |
110 | return c; |
111 | } | |
112 | ||
06607335 MM |
113 | /** |
114 | * config_parse - parse a configuration | |
115 | * @c: configuration | |
116 | * | |
117 | * config_parse() reads input by calling a hook function pointed to | |
118 | * by @cf_read_hook and parses it according to the configuration | |
119 | * grammar. It also calls all the preconfig and postconfig hooks | |
2e9b2421 | 120 | * before, resp. after parsing. |
06607335 MM |
121 | * |
122 | * Result: 1 if the config has been parsed successfully, 0 if any | |
2e9b2421 | 123 | * error has occurred (such as anybody calling cf_error()) and |
06607335 MM |
124 | * the @err_msg field has been set to the error message. |
125 | */ | |
31b3e1bb MM |
126 | int |
127 | config_parse(struct config *c) | |
128 | { | |
9b9a7143 | 129 | int done = 0; |
f30b86f9 | 130 | DBG("Parsing configuration file `%s'\n", c->file_name); |
31b3e1bb | 131 | new_config = c; |
31b3e1bb MM |
132 | cfg_mem = c->mem; |
133 | if (setjmp(conf_jmpbuf)) | |
9b9a7143 OZ |
134 | goto cleanup; |
135 | ||
48ec367a | 136 | cf_lex_init(0, c); |
7c0cc76e | 137 | sysdep_preconfig(c); |
31b3e1bb | 138 | protos_preconfig(c); |
0e02abfd | 139 | rt_preconfig(c); |
31b3e1bb | 140 | cf_parse(); |
f4a60a9b | 141 | |
97e46d28 OZ |
142 | if (EMPTY_LIST(c->protos)) |
143 | cf_error("No protocol is specified in the config file"); | |
f4a60a9b OZ |
144 | |
145 | /* | |
dce26783 | 146 | if (!c->router_id) |
fe9f1a6d | 147 | cf_error("Router ID must be configured manually"); |
f4a60a9b | 148 | */ |
fe9f1a6d | 149 | |
9b9a7143 OZ |
150 | done = 1; |
151 | ||
152 | cleanup: | |
153 | new_config = NULL; | |
154 | cfg_mem = NULL; | |
155 | return done; | |
31b3e1bb MM |
156 | } |
157 | ||
06607335 MM |
158 | /** |
159 | * cli_parse - parse a CLI command | |
160 | * @c: temporary config structure | |
161 | * | |
162 | * cli_parse() is similar to config_parse(), but instead of a configuration, | |
163 | * it parses a CLI command. See the CLI module for more information. | |
164 | */ | |
bc2fb680 MM |
165 | int |
166 | cli_parse(struct config *c) | |
167 | { | |
9b9a7143 | 168 | int done = 0; |
c9aae7f4 | 169 | c->sym_fallback = config->sym_hash; |
9b9a7143 | 170 | new_config = c; |
bc2fb680 MM |
171 | cfg_mem = c->mem; |
172 | if (setjmp(conf_jmpbuf)) | |
9b9a7143 OZ |
173 | goto cleanup; |
174 | ||
48ec367a | 175 | cf_lex_init(1, c); |
bc2fb680 | 176 | cf_parse(); |
9b9a7143 OZ |
177 | done = 1; |
178 | ||
179 | cleanup: | |
180 | c->sym_fallback = NULL; | |
181 | new_config = NULL; | |
182 | cfg_mem = NULL; | |
183 | return done; | |
bc2fb680 MM |
184 | } |
185 | ||
06607335 MM |
186 | /** |
187 | * config_free - free a configuration | |
188 | * @c: configuration to be freed | |
189 | * | |
190 | * This function takes a &config structure and frees all resources | |
191 | * associated with it. | |
192 | */ | |
31b3e1bb MM |
193 | void |
194 | config_free(struct config *c) | |
195 | { | |
a92cf57d OZ |
196 | if (c) |
197 | rfree(c->pool); | |
31b3e1bb MM |
198 | } |
199 | ||
200 | void | |
50fe90ed MM |
201 | config_add_obstacle(struct config *c) |
202 | { | |
203 | DBG("+++ adding obstacle %d\n", c->obstacle_count); | |
204 | c->obstacle_count++; | |
205 | } | |
206 | ||
207 | void | |
208 | config_del_obstacle(struct config *c) | |
31b3e1bb | 209 | { |
50fe90ed MM |
210 | DBG("+++ deleting obstacle %d\n", c->obstacle_count); |
211 | c->obstacle_count--; | |
212 | if (!c->obstacle_count) | |
a92cf57d | 213 | ev_schedule(config_event); |
50fe90ed MM |
214 | } |
215 | ||
216 | static int | |
bf8558bc | 217 | global_commit(struct config *new, struct config *old) |
50fe90ed MM |
218 | { |
219 | if (!old) | |
220 | return 0; | |
789772ed | 221 | |
d72cdff4 OZ |
222 | if (!ipa_equal(old->listen_bgp_addr, new->listen_bgp_addr) || |
223 | (old->listen_bgp_port != new->listen_bgp_port) || | |
224 | (old->listen_bgp_flags != new->listen_bgp_flags)) | |
789772ed OZ |
225 | log(L_WARN "Reconfiguration of BGP listening socket not implemented, please restart BIRD."); |
226 | ||
bf8558bc | 227 | if (!new->router_id) |
79b4e12e OZ |
228 | { |
229 | new->router_id = old->router_id; | |
230 | ||
231 | if (new->router_id_from) | |
232 | { | |
233 | u32 id = if_choose_router_id(new->router_id_from, old->router_id); | |
234 | if (!id) | |
235 | log(L_WARN "Cannot determine router ID, using old one"); | |
236 | else | |
237 | new->router_id = id; | |
238 | } | |
239 | } | |
240 | ||
50fe90ed MM |
241 | return 0; |
242 | } | |
243 | ||
244 | static int | |
bf1aec97 | 245 | config_do_commit(struct config *c, int type) |
50fe90ed | 246 | { |
a92cf57d OZ |
247 | if (type == RECONFIG_UNDO) |
248 | { | |
249 | c = old_config; | |
250 | type = old_cftype; | |
251 | } | |
252 | else | |
253 | config_free(old_config); | |
50fe90ed | 254 | |
50fe90ed | 255 | old_config = config; |
a92cf57d OZ |
256 | old_cftype = type; |
257 | config = c; | |
258 | ||
259 | configuring = 1; | |
260 | if (old_config && !config->shutdown) | |
261 | log(L_INFO "Reconfiguring"); | |
262 | ||
50fe90ed MM |
263 | if (old_config) |
264 | old_config->obstacle_count++; | |
76b53a4e | 265 | |
50fe90ed | 266 | DBG("sysdep_commit\n"); |
a92cf57d | 267 | int force_restart = sysdep_commit(c, old_config); |
50fe90ed MM |
268 | DBG("global_commit\n"); |
269 | force_restart |= global_commit(c, old_config); | |
270 | DBG("rt_commit\n"); | |
271 | rt_commit(c, old_config); | |
272 | DBG("protos_commit\n"); | |
bf1aec97 | 273 | protos_commit(c, old_config, force_restart, type); |
a92cf57d | 274 | |
a92cf57d | 275 | int obs = 0; |
50fe90ed | 276 | if (old_config) |
a92cf57d OZ |
277 | obs = --old_config->obstacle_count; |
278 | ||
279 | DBG("do_commit finished with %d obstacles remaining\n", obs); | |
280 | return !obs; | |
50fe90ed MM |
281 | } |
282 | ||
8f6accb5 | 283 | static void |
7c103b1e | 284 | config_done(void *unused UNUSED) |
50fe90ed | 285 | { |
a92cf57d OZ |
286 | if (config->shutdown) |
287 | sysdep_shutdown_done(); | |
288 | ||
289 | configuring = 0; | |
290 | if (old_config) | |
291 | log(L_INFO "Reconfigured"); | |
50fe90ed | 292 | |
a92cf57d | 293 | if (future_cftype) |
50fe90ed | 294 | { |
a92cf57d OZ |
295 | int type = future_cftype; |
296 | struct config *conf = future_config; | |
297 | future_cftype = RECONFIG_NONE; | |
50fe90ed | 298 | future_config = NULL; |
a92cf57d | 299 | |
76b53a4e | 300 | log(L_INFO "Reconfiguring to queued configuration"); |
a92cf57d OZ |
301 | if (config_do_commit(conf, type)) |
302 | config_done(NULL); | |
50fe90ed | 303 | } |
50fe90ed MM |
304 | } |
305 | ||
06607335 MM |
306 | /** |
307 | * config_commit - commit a configuration | |
308 | * @c: new configuration | |
bf1aec97 | 309 | * @type: type of reconfiguration (RECONFIG_SOFT or RECONFIG_HARD) |
a92cf57d | 310 | * @timeout: timeout for undo (or 0 for no timeout) |
06607335 MM |
311 | * |
312 | * When a configuration is parsed and prepared for use, the | |
313 | * config_commit() function starts the process of reconfiguration. | |
314 | * It checks whether there is already a reconfiguration in progress | |
315 | * in which case it just queues the new config for later processing. | |
316 | * Else it notifies all modules about the new configuration by calling | |
317 | * their commit() functions which can either accept it immediately | |
318 | * or call config_add_obstacle() to report that they need some time | |
319 | * to complete the reconfiguration. After all such obstacles are removed | |
320 | * using config_del_obstacle(), the old configuration is freed and | |
321 | * everything runs according to the new one. | |
322 | * | |
a92cf57d OZ |
323 | * When @timeout is nonzero, the undo timer is activated with given |
324 | * timeout. The timer is deactivated when config_commit(), | |
325 | * config_confirm() or config_undo() is called. | |
326 | * | |
06607335 MM |
327 | * Result: %CONF_DONE if the configuration has been accepted immediately, |
328 | * %CONF_PROGRESS if it will take some time to switch to it, %CONF_QUEUED | |
329 | * if it's been queued due to another reconfiguration being in progress now | |
330 | * or %CONF_SHUTDOWN if BIRD is in shutdown mode and no new configurations | |
331 | * are accepted. | |
332 | */ | |
50fe90ed | 333 | int |
a92cf57d | 334 | config_commit(struct config *c, int type, int timeout) |
50fe90ed | 335 | { |
a92cf57d | 336 | if (shutting_down) |
50fe90ed | 337 | { |
a92cf57d OZ |
338 | config_free(c); |
339 | return CONF_SHUTDOWN; | |
50fe90ed | 340 | } |
a92cf57d OZ |
341 | |
342 | undo_available = 1; | |
343 | if (timeout > 0) | |
344 | tm_start(config_timer, timeout); | |
345 | else | |
346 | tm_stop(config_timer); | |
347 | ||
348 | if (configuring) | |
50fe90ed | 349 | { |
a92cf57d | 350 | if (future_cftype) |
50fe90ed MM |
351 | { |
352 | log(L_INFO "Queueing new configuration, ignoring the one already queued"); | |
353 | config_free(future_config); | |
354 | } | |
355 | else | |
a92cf57d OZ |
356 | log(L_INFO "Queueing new configuration"); |
357 | ||
358 | future_cftype = type; | |
50fe90ed MM |
359 | future_config = c; |
360 | return CONF_QUEUED; | |
361 | } | |
76b53a4e | 362 | |
bf1aec97 | 363 | if (config_do_commit(c, type)) |
50fe90ed MM |
364 | { |
365 | config_done(NULL); | |
366 | return CONF_DONE; | |
367 | } | |
a92cf57d OZ |
368 | return CONF_PROGRESS; |
369 | } | |
370 | ||
371 | /** | |
372 | * config_confirm - confirm a commited configuration | |
373 | * | |
374 | * When the undo timer is activated by config_commit() with nonzero timeout, | |
375 | * this function can be used to deactivate it and therefore confirm | |
376 | * the current configuration. | |
377 | * | |
378 | * Result: %CONF_CONFIRM when the current configuration is confirmed, | |
379 | * %CONF_NONE when there is nothing to confirm (i.e. undo timer is not active). | |
380 | */ | |
381 | int | |
382 | config_confirm(void) | |
383 | { | |
384 | if (config_timer->expires == 0) | |
385 | return CONF_NOTHING; | |
386 | ||
387 | tm_stop(config_timer); | |
388 | ||
389 | return CONF_CONFIRM; | |
390 | } | |
391 | ||
392 | /** | |
393 | * config_undo - undo a configuration | |
394 | * | |
395 | * Function config_undo() can be used to change the current | |
396 | * configuration back to stored %old_config. If no reconfiguration is | |
397 | * running, this stored configuration is commited in the same way as a | |
398 | * new configuration in config_commit(). If there is already a | |
399 | * reconfiguration in progress and no next reconfiguration is | |
400 | * scheduled, then the undo is scheduled for later processing as | |
401 | * usual, but if another reconfiguration is already scheduled, then | |
402 | * such reconfiguration is removed instead (i.e. undo is applied on | |
403 | * the last commit that scheduled it). | |
404 | * | |
405 | * Result: %CONF_DONE if the configuration has been accepted immediately, | |
406 | * %CONF_PROGRESS if it will take some time to switch to it, %CONF_QUEUED | |
407 | * if it's been queued due to another reconfiguration being in progress now, | |
408 | * %CONF_UNQUEUED if a scheduled reconfiguration is removed, %CONF_NOTHING | |
409 | * if there is no relevant configuration to undo (the previous config request | |
c8cafc8e | 410 | * was config_undo() too) or %CONF_SHUTDOWN if BIRD is in shutdown mode and |
a92cf57d OZ |
411 | * no new configuration changes are accepted. |
412 | */ | |
413 | int | |
414 | config_undo(void) | |
415 | { | |
416 | if (shutting_down) | |
417 | return CONF_SHUTDOWN; | |
418 | ||
419 | if (!undo_available || !old_config) | |
420 | return CONF_NOTHING; | |
421 | ||
422 | undo_available = 0; | |
423 | tm_stop(config_timer); | |
424 | ||
425 | if (configuring) | |
50fe90ed | 426 | { |
a92cf57d OZ |
427 | if (future_cftype) |
428 | { | |
429 | config_free(future_config); | |
430 | future_config = NULL; | |
431 | ||
432 | log(L_INFO "Removing queued configuration"); | |
433 | future_cftype = RECONFIG_NONE; | |
434 | return CONF_UNQUEUED; | |
435 | } | |
436 | else | |
437 | { | |
438 | log(L_INFO "Queueing undo configuration"); | |
439 | future_cftype = RECONFIG_UNDO; | |
440 | return CONF_QUEUED; | |
441 | } | |
442 | } | |
443 | ||
444 | if (config_do_commit(NULL, RECONFIG_UNDO)) | |
445 | { | |
446 | config_done(NULL); | |
447 | return CONF_DONE; | |
50fe90ed MM |
448 | } |
449 | return CONF_PROGRESS; | |
31b3e1bb MM |
450 | } |
451 | ||
a92cf57d OZ |
452 | extern void cmd_reconfig_undo_notify(void); |
453 | ||
454 | static void | |
3e236955 | 455 | config_timeout(struct timer *t UNUSED) |
a92cf57d OZ |
456 | { |
457 | log(L_INFO "Config timeout expired, starting undo"); | |
458 | cmd_reconfig_undo_notify(); | |
459 | ||
460 | int r = config_undo(); | |
461 | if (r < 0) | |
462 | log(L_ERR "Undo request failed"); | |
463 | } | |
464 | ||
465 | void | |
466 | config_init(void) | |
467 | { | |
468 | config_event = ev_new(&root_pool); | |
469 | config_event->hook = config_done; | |
470 | ||
471 | config_timer = tm_new(&root_pool); | |
472 | config_timer->hook = config_timeout; | |
473 | } | |
474 | ||
06607335 MM |
475 | /** |
476 | * order_shutdown - order BIRD shutdown | |
477 | * | |
478 | * This function initiates shutdown of BIRD. It's accomplished by asking | |
479 | * for switching to an empty configuration. | |
480 | */ | |
bf8558bc MM |
481 | void |
482 | order_shutdown(void) | |
483 | { | |
484 | struct config *c; | |
485 | ||
486 | if (shutting_down) | |
487 | return; | |
a92cf57d | 488 | |
bf8558bc MM |
489 | log(L_INFO "Shutting down"); |
490 | c = lp_alloc(config->mem, sizeof(struct config)); | |
491 | memcpy(c, config, sizeof(struct config)); | |
492 | init_list(&c->protos); | |
493 | init_list(&c->tables); | |
494 | c->shutdown = 1; | |
a92cf57d OZ |
495 | |
496 | config_commit(c, RECONFIG_HARD, 0); | |
bf8558bc MM |
497 | shutting_down = 1; |
498 | } | |
499 | ||
06607335 MM |
500 | /** |
501 | * cf_error - report a configuration error | |
502 | * @msg: printf-like format string | |
503 | * | |
504 | * cf_error() can be called during execution of config_parse(), that is | |
505 | * from the parser, a preconfig hook or a postconfig hook, to report an | |
506 | * error in the configuration. | |
507 | */ | |
31b3e1bb | 508 | void |
1a7daab1 | 509 | cf_error(const char *msg, ...) |
31b3e1bb MM |
510 | { |
511 | char buf[1024]; | |
512 | va_list args; | |
513 | ||
514 | va_start(args, msg); | |
515 | if (bvsnprintf(buf, sizeof(buf), msg, args) < 0) | |
516 | strcpy(buf, "<bug: error message too long>"); | |
8f01879c | 517 | va_end(args); |
31b3e1bb | 518 | new_config->err_msg = cfg_strdup(buf); |
4be266a9 OZ |
519 | new_config->err_lino = ifs->lino; |
520 | new_config->err_file_name = ifs->file_name; | |
0c3d9dac | 521 | cf_lex_unwind(); |
31b3e1bb MM |
522 | longjmp(conf_jmpbuf, 1); |
523 | } | |
524 | ||
06607335 MM |
525 | /** |
526 | * cfg_strdup - copy a string to config memory | |
527 | * @c: string to copy | |
528 | * | |
529 | * cfg_strdup() creates a new copy of the string in the memory | |
530 | * pool associated with the configuration being currently parsed. | |
531 | * It's often used when a string literal occurs in the configuration | |
532 | * and we want to preserve it for further use. | |
533 | */ | |
31b3e1bb | 534 | char * |
c8cafc8e | 535 | cfg_strdup(const char *c) |
31b3e1bb MM |
536 | { |
537 | int l = strlen(c) + 1; | |
538 | char *z = cfg_allocu(l); | |
539 | memcpy(z, c, l); | |
540 | return z; | |
541 | } | |
a7f23f58 OZ |
542 | |
543 | ||
544 | void | |
545 | cfg_copy_list(list *dest, list *src, unsigned node_size) | |
546 | { | |
547 | node *dn, *sn; | |
548 | ||
549 | init_list(dest); | |
550 | WALK_LIST(sn, *src) | |
551 | { | |
552 | dn = cfg_alloc(node_size); | |
553 | memcpy(dn, sn, node_size); | |
554 | add_tail(dest, dn); | |
555 | } | |
556 | } |