]>
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" |
a6f79ca5 | 55 | #include "lib/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"); | |
05d47bd5 | 92 | linpool *l = lp_new_default(p); |
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; | |
f047271c OZ |
105 | c->load_time = current_time(); |
106 | c->tf_route = c->tf_proto = TM_ISO_SHORT_MS; | |
107 | c->tf_base = c->tf_log = TM_ISO_LONG_MS; | |
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; |
b7761af3 | 169 | c->fallback = config; |
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: | |
b7761af3 | 180 | c->fallback = NULL; |
9b9a7143 OZ |
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--; | |
863ecfc7 | 212 | if (!c->obstacle_count && (c != config)) |
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 | |
bf8558bc | 222 | if (!new->router_id) |
79b4e12e OZ |
223 | { |
224 | new->router_id = old->router_id; | |
225 | ||
226 | if (new->router_id_from) | |
227 | { | |
228 | u32 id = if_choose_router_id(new->router_id_from, old->router_id); | |
229 | if (!id) | |
230 | log(L_WARN "Cannot determine router ID, using old one"); | |
231 | else | |
232 | new->router_id = id; | |
233 | } | |
234 | } | |
235 | ||
50fe90ed MM |
236 | return 0; |
237 | } | |
238 | ||
239 | static int | |
bf1aec97 | 240 | config_do_commit(struct config *c, int type) |
50fe90ed | 241 | { |
a92cf57d OZ |
242 | if (type == RECONFIG_UNDO) |
243 | { | |
244 | c = old_config; | |
245 | type = old_cftype; | |
246 | } | |
247 | else | |
248 | config_free(old_config); | |
50fe90ed | 249 | |
50fe90ed | 250 | old_config = config; |
a92cf57d OZ |
251 | old_cftype = type; |
252 | config = c; | |
253 | ||
254 | configuring = 1; | |
255 | if (old_config && !config->shutdown) | |
256 | log(L_INFO "Reconfiguring"); | |
257 | ||
50fe90ed MM |
258 | if (old_config) |
259 | old_config->obstacle_count++; | |
76b53a4e | 260 | |
50fe90ed | 261 | DBG("sysdep_commit\n"); |
a92cf57d | 262 | int force_restart = sysdep_commit(c, old_config); |
50fe90ed MM |
263 | DBG("global_commit\n"); |
264 | force_restart |= global_commit(c, old_config); | |
265 | DBG("rt_commit\n"); | |
266 | rt_commit(c, old_config); | |
267 | DBG("protos_commit\n"); | |
bf1aec97 | 268 | protos_commit(c, old_config, force_restart, type); |
a92cf57d | 269 | |
a92cf57d | 270 | int obs = 0; |
50fe90ed | 271 | if (old_config) |
a92cf57d OZ |
272 | obs = --old_config->obstacle_count; |
273 | ||
274 | DBG("do_commit finished with %d obstacles remaining\n", obs); | |
275 | return !obs; | |
50fe90ed MM |
276 | } |
277 | ||
8f6accb5 | 278 | static void |
7c103b1e | 279 | config_done(void *unused UNUSED) |
50fe90ed | 280 | { |
a92cf57d OZ |
281 | if (config->shutdown) |
282 | sysdep_shutdown_done(); | |
283 | ||
284 | configuring = 0; | |
285 | if (old_config) | |
286 | log(L_INFO "Reconfigured"); | |
50fe90ed | 287 | |
a92cf57d | 288 | if (future_cftype) |
50fe90ed | 289 | { |
a92cf57d OZ |
290 | int type = future_cftype; |
291 | struct config *conf = future_config; | |
292 | future_cftype = RECONFIG_NONE; | |
50fe90ed | 293 | future_config = NULL; |
a92cf57d | 294 | |
76b53a4e | 295 | log(L_INFO "Reconfiguring to queued configuration"); |
a92cf57d OZ |
296 | if (config_do_commit(conf, type)) |
297 | config_done(NULL); | |
50fe90ed | 298 | } |
50fe90ed MM |
299 | } |
300 | ||
06607335 MM |
301 | /** |
302 | * config_commit - commit a configuration | |
303 | * @c: new configuration | |
bf1aec97 | 304 | * @type: type of reconfiguration (RECONFIG_SOFT or RECONFIG_HARD) |
3e405fb1 | 305 | * @timeout: timeout for undo (in seconds; or 0 for no timeout) |
06607335 MM |
306 | * |
307 | * When a configuration is parsed and prepared for use, the | |
308 | * config_commit() function starts the process of reconfiguration. | |
309 | * It checks whether there is already a reconfiguration in progress | |
310 | * in which case it just queues the new config for later processing. | |
311 | * Else it notifies all modules about the new configuration by calling | |
312 | * their commit() functions which can either accept it immediately | |
313 | * or call config_add_obstacle() to report that they need some time | |
314 | * to complete the reconfiguration. After all such obstacles are removed | |
315 | * using config_del_obstacle(), the old configuration is freed and | |
316 | * everything runs according to the new one. | |
317 | * | |
a92cf57d OZ |
318 | * When @timeout is nonzero, the undo timer is activated with given |
319 | * timeout. The timer is deactivated when config_commit(), | |
320 | * config_confirm() or config_undo() is called. | |
321 | * | |
06607335 MM |
322 | * Result: %CONF_DONE if the configuration has been accepted immediately, |
323 | * %CONF_PROGRESS if it will take some time to switch to it, %CONF_QUEUED | |
324 | * if it's been queued due to another reconfiguration being in progress now | |
325 | * or %CONF_SHUTDOWN if BIRD is in shutdown mode and no new configurations | |
326 | * are accepted. | |
327 | */ | |
50fe90ed | 328 | int |
3e405fb1 | 329 | config_commit(struct config *c, int type, uint timeout) |
50fe90ed | 330 | { |
a92cf57d | 331 | if (shutting_down) |
50fe90ed | 332 | { |
a92cf57d OZ |
333 | config_free(c); |
334 | return CONF_SHUTDOWN; | |
50fe90ed | 335 | } |
a92cf57d OZ |
336 | |
337 | undo_available = 1; | |
3e405fb1 | 338 | if (timeout) |
a6f79ca5 | 339 | tm_start(config_timer, timeout S); |
a92cf57d | 340 | else |
a6f79ca5 | 341 | tm_stop(config_timer); |
a92cf57d OZ |
342 | |
343 | if (configuring) | |
50fe90ed | 344 | { |
a92cf57d | 345 | if (future_cftype) |
50fe90ed MM |
346 | { |
347 | log(L_INFO "Queueing new configuration, ignoring the one already queued"); | |
348 | config_free(future_config); | |
349 | } | |
350 | else | |
a92cf57d OZ |
351 | log(L_INFO "Queueing new configuration"); |
352 | ||
353 | future_cftype = type; | |
50fe90ed MM |
354 | future_config = c; |
355 | return CONF_QUEUED; | |
356 | } | |
76b53a4e | 357 | |
bf1aec97 | 358 | if (config_do_commit(c, type)) |
50fe90ed MM |
359 | { |
360 | config_done(NULL); | |
361 | return CONF_DONE; | |
362 | } | |
a92cf57d OZ |
363 | return CONF_PROGRESS; |
364 | } | |
365 | ||
366 | /** | |
367 | * config_confirm - confirm a commited configuration | |
368 | * | |
369 | * When the undo timer is activated by config_commit() with nonzero timeout, | |
370 | * this function can be used to deactivate it and therefore confirm | |
371 | * the current configuration. | |
372 | * | |
373 | * Result: %CONF_CONFIRM when the current configuration is confirmed, | |
374 | * %CONF_NONE when there is nothing to confirm (i.e. undo timer is not active). | |
375 | */ | |
376 | int | |
377 | config_confirm(void) | |
378 | { | |
379 | if (config_timer->expires == 0) | |
380 | return CONF_NOTHING; | |
381 | ||
a6f79ca5 | 382 | tm_stop(config_timer); |
a92cf57d OZ |
383 | |
384 | return CONF_CONFIRM; | |
385 | } | |
386 | ||
387 | /** | |
388 | * config_undo - undo a configuration | |
389 | * | |
390 | * Function config_undo() can be used to change the current | |
391 | * configuration back to stored %old_config. If no reconfiguration is | |
392 | * running, this stored configuration is commited in the same way as a | |
393 | * new configuration in config_commit(). If there is already a | |
394 | * reconfiguration in progress and no next reconfiguration is | |
395 | * scheduled, then the undo is scheduled for later processing as | |
396 | * usual, but if another reconfiguration is already scheduled, then | |
397 | * such reconfiguration is removed instead (i.e. undo is applied on | |
398 | * the last commit that scheduled it). | |
399 | * | |
400 | * Result: %CONF_DONE if the configuration has been accepted immediately, | |
401 | * %CONF_PROGRESS if it will take some time to switch to it, %CONF_QUEUED | |
402 | * if it's been queued due to another reconfiguration being in progress now, | |
403 | * %CONF_UNQUEUED if a scheduled reconfiguration is removed, %CONF_NOTHING | |
404 | * if there is no relevant configuration to undo (the previous config request | |
c8cafc8e | 405 | * was config_undo() too) or %CONF_SHUTDOWN if BIRD is in shutdown mode and |
a92cf57d OZ |
406 | * no new configuration changes are accepted. |
407 | */ | |
408 | int | |
409 | config_undo(void) | |
410 | { | |
411 | if (shutting_down) | |
412 | return CONF_SHUTDOWN; | |
413 | ||
414 | if (!undo_available || !old_config) | |
415 | return CONF_NOTHING; | |
416 | ||
417 | undo_available = 0; | |
a6f79ca5 | 418 | tm_stop(config_timer); |
a92cf57d OZ |
419 | |
420 | if (configuring) | |
50fe90ed | 421 | { |
a92cf57d OZ |
422 | if (future_cftype) |
423 | { | |
424 | config_free(future_config); | |
425 | future_config = NULL; | |
426 | ||
427 | log(L_INFO "Removing queued configuration"); | |
428 | future_cftype = RECONFIG_NONE; | |
429 | return CONF_UNQUEUED; | |
430 | } | |
431 | else | |
432 | { | |
433 | log(L_INFO "Queueing undo configuration"); | |
434 | future_cftype = RECONFIG_UNDO; | |
435 | return CONF_QUEUED; | |
436 | } | |
437 | } | |
438 | ||
439 | if (config_do_commit(NULL, RECONFIG_UNDO)) | |
440 | { | |
441 | config_done(NULL); | |
442 | return CONF_DONE; | |
50fe90ed MM |
443 | } |
444 | return CONF_PROGRESS; | |
31b3e1bb MM |
445 | } |
446 | ||
a92cf57d OZ |
447 | extern void cmd_reconfig_undo_notify(void); |
448 | ||
449 | static void | |
02552526 | 450 | config_timeout(timer *t UNUSED) |
a92cf57d OZ |
451 | { |
452 | log(L_INFO "Config timeout expired, starting undo"); | |
453 | cmd_reconfig_undo_notify(); | |
454 | ||
455 | int r = config_undo(); | |
456 | if (r < 0) | |
457 | log(L_ERR "Undo request failed"); | |
458 | } | |
459 | ||
460 | void | |
461 | config_init(void) | |
462 | { | |
463 | config_event = ev_new(&root_pool); | |
464 | config_event->hook = config_done; | |
465 | ||
a6f79ca5 | 466 | config_timer = tm_new(&root_pool); |
a92cf57d OZ |
467 | config_timer->hook = config_timeout; |
468 | } | |
469 | ||
06607335 MM |
470 | /** |
471 | * order_shutdown - order BIRD shutdown | |
472 | * | |
473 | * This function initiates shutdown of BIRD. It's accomplished by asking | |
474 | * for switching to an empty configuration. | |
475 | */ | |
bf8558bc MM |
476 | void |
477 | order_shutdown(void) | |
478 | { | |
479 | struct config *c; | |
480 | ||
481 | if (shutting_down) | |
482 | return; | |
a92cf57d | 483 | |
bf8558bc MM |
484 | log(L_INFO "Shutting down"); |
485 | c = lp_alloc(config->mem, sizeof(struct config)); | |
486 | memcpy(c, config, sizeof(struct config)); | |
487 | init_list(&c->protos); | |
488 | init_list(&c->tables); | |
489 | c->shutdown = 1; | |
a92cf57d OZ |
490 | |
491 | config_commit(c, RECONFIG_HARD, 0); | |
bf8558bc MM |
492 | shutting_down = 1; |
493 | } | |
494 | ||
06607335 MM |
495 | /** |
496 | * cf_error - report a configuration error | |
497 | * @msg: printf-like format string | |
498 | * | |
499 | * cf_error() can be called during execution of config_parse(), that is | |
500 | * from the parser, a preconfig hook or a postconfig hook, to report an | |
501 | * error in the configuration. | |
502 | */ | |
31b3e1bb | 503 | void |
1a7daab1 | 504 | cf_error(const char *msg, ...) |
31b3e1bb MM |
505 | { |
506 | char buf[1024]; | |
507 | va_list args; | |
508 | ||
509 | va_start(args, msg); | |
510 | if (bvsnprintf(buf, sizeof(buf), msg, args) < 0) | |
511 | strcpy(buf, "<bug: error message too long>"); | |
8f01879c | 512 | va_end(args); |
31b3e1bb | 513 | new_config->err_msg = cfg_strdup(buf); |
4be266a9 | 514 | new_config->err_lino = ifs->lino; |
d50b0bc4 | 515 | new_config->err_chno = ifs->chno - ifs->toklen + 1; |
4be266a9 | 516 | new_config->err_file_name = ifs->file_name; |
0c3d9dac | 517 | cf_lex_unwind(); |
31b3e1bb MM |
518 | longjmp(conf_jmpbuf, 1); |
519 | } | |
520 | ||
06607335 MM |
521 | /** |
522 | * cfg_strdup - copy a string to config memory | |
523 | * @c: string to copy | |
524 | * | |
525 | * cfg_strdup() creates a new copy of the string in the memory | |
526 | * pool associated with the configuration being currently parsed. | |
527 | * It's often used when a string literal occurs in the configuration | |
528 | * and we want to preserve it for further use. | |
529 | */ | |
31b3e1bb | 530 | char * |
c8cafc8e | 531 | cfg_strdup(const char *c) |
31b3e1bb MM |
532 | { |
533 | int l = strlen(c) + 1; | |
534 | char *z = cfg_allocu(l); | |
535 | memcpy(z, c, l); | |
536 | return z; | |
537 | } | |
a7f23f58 OZ |
538 | |
539 | ||
540 | void | |
541 | cfg_copy_list(list *dest, list *src, unsigned node_size) | |
542 | { | |
543 | node *dn, *sn; | |
544 | ||
545 | init_list(dest); | |
546 | WALK_LIST(sn, *src) | |
547 | { | |
548 | dn = cfg_alloc(node_size); | |
549 | memcpy(dn, sn, node_size); | |
550 | add_tail(dest, dn); | |
551 | } | |
552 | } |