2 * Master Configuration routines.
4 * This file contains the common parts of the Bacula
5 * configuration routines.
7 * Note, the configuration file parser consists of three parts
9 * 1. The generic lexical scanner in lib/lex.c and lib/lex.h
11 * 2. The generic config scanner in lib/parse_conf.c and
13 * These files contain the parser code, some utility
14 * routines, and the common store routines (name, int,
15 * string, time, int64, size, ...).
17 * 3. The daemon specific file, which contains the Resource
18 * definitions as well as any specific store routines
19 * for the resource records.
21 * N.B. This is a two pass parser, so if you malloc() a string
22 * in a "store" routine, you must ensure to do it during
23 * only one of the two passes, or to free it between.
24 * Also, note that the resource record is malloced and
25 * saved in save_resource() during pass 1. Anything that
26 * you want saved after pass two (e.g. resource pointers)
27 * must explicitly be done in save_resource. Take a look
28 * at the Job resource in src/dird/dird_conf.c to see how
31 * Kern Sibbald, January MM
36 Copyright (C) 2000-2006 Kern Sibbald
38 This program is free software; you can redistribute it and/or
39 modify it under the terms of the GNU General Public License
40 version 2 as amended with additional clauses defined in the
41 file LICENSE in the main source directory.
43 This program is distributed in the hope that it will be useful,
44 but WITHOUT ANY WARRANTY; without even the implied warranty of
45 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46 the file LICENSE for additional details.
53 /* Each daemon has a slightly different set of
54 * resources, so it will define the following
59 extern RES_TABLE resources[];
60 extern RES **res_head;
63 // work around visual studio name manling preventing external linkage since res_all
64 // is declared as a different type when instantiated.
65 extern "C" CURES res_all;
69 extern int res_all_size;
71 extern brwlock_t res_lock; /* resource lock */
74 /* Forward referenced subroutines */
75 static void scan_types(LEX *lc, MSGS *msg, int dest, char *where, char *cmd);
78 /* Common Resource definitions */
80 /* Message resource directives
81 * name handler value code flags default_value
83 RES_ITEM msgs_items[] = {
84 {"name", store_name, ITEM(res_msgs.hdr.name), 0, 0, 0},
85 {"description", store_str, ITEM(res_msgs.hdr.desc), 0, 0, 0},
86 {"mailcommand", store_str, ITEM(res_msgs.mail_cmd), 0, 0, 0},
87 {"operatorcommand", store_str, ITEM(res_msgs.operator_cmd), 0, 0, 0},
88 {"syslog", store_msgs, ITEM(res_msgs), MD_SYSLOG, 0, 0},
89 {"mail", store_msgs, ITEM(res_msgs), MD_MAIL, 0, 0},
90 {"mailonerror", store_msgs, ITEM(res_msgs), MD_MAIL_ON_ERROR, 0, 0},
91 {"file", store_msgs, ITEM(res_msgs), MD_FILE, 0, 0},
92 {"append", store_msgs, ITEM(res_msgs), MD_APPEND, 0, 0},
93 {"stdout", store_msgs, ITEM(res_msgs), MD_STDOUT, 0, 0},
94 {"stderr", store_msgs, ITEM(res_msgs), MD_STDERR, 0, 0},
95 {"director", store_msgs, ITEM(res_msgs), MD_DIRECTOR, 0, 0},
96 {"console", store_msgs, ITEM(res_msgs), MD_CONSOLE, 0, 0},
97 {"operator", store_msgs, ITEM(res_msgs), MD_OPERATOR, 0, 0},
98 {"catalog", store_msgs, ITEM(res_msgs), MD_CATALOG, 0, 0},
99 {NULL, NULL, {0}, 0, 0, 0}
106 /* Various message types */
107 static struct s_mtypes msg_types[] = {
112 {"warning", M_WARNING},
115 {"notsaved", M_NOTSAVED},
116 {"skipped", M_SKIPPED},
118 {"terminate", M_TERM},
119 {"restored", M_RESTORED},
120 {"security", M_SECURITY},
122 {"volmgmt", M_VOLMGMT},
127 /* Used for certain KeyWord tables */
134 * Tape Label types permitted in Pool records
136 * tape label label code = token
138 static s_kw tapelabels[] = {
139 {"bacula", B_BACULA_LABEL},
140 {"ansi", B_ANSI_LABEL},
141 {"ibm", B_IBM_LABEL},
146 /* Simply print a message */
147 static void prtmsg(void *sock, const char *fmt, ...)
151 va_start(arg_ptr, fmt);
152 vfprintf(stdout, fmt, arg_ptr);
156 const char *res_to_str(int rcode)
158 if (rcode < r_first || rcode > r_last) {
159 return _("***UNKNOWN***");
161 return resources[rcode-r_first].name;
167 * Initialize the static structure to zeros, then
168 * apply all the default values.
170 void init_resource(int type, RES_ITEM *items, int pass)
173 int rindex = type - r_first;
174 static bool first = true;
177 if (first && (errstat=rwl_init(&res_lock)) != 0) {
178 Emsg1(M_ABORT, 0, _("Unable to initialize resource lock. ERR=%s\n"),
183 memset(&res_all, 0, res_all_size);
184 res_all.hdr.rcode = type;
185 res_all.hdr.refcnt = 1;
187 /* Set defaults in each item */
188 for (i=0; items[i].name; i++) {
189 Dmsg3(900, "Item=%s def=%s defval=%d\n", items[i].name,
190 (items[i].flags & ITEM_DEFAULT) ? "yes" : "no",
191 items[i].default_value);
192 if (items[i].flags & ITEM_DEFAULT && items[i].default_value != 0) {
193 if (items[i].handler == store_bit) {
194 *(int *)(items[i].value) |= items[i].code;
195 } else if (items[i].handler == store_bool) {
196 *(bool *)(items[i].value) = items[i].default_value;
197 } else if (items[i].handler == store_pint ||
198 items[i].handler == store_int) {
199 *(int *)(items[i].value) = items[i].default_value;
200 } else if (items[i].handler == store_int64) {
201 *(int64_t *)(items[i].value) = items[i].default_value;
202 } else if (items[i].handler == store_size) {
203 *(uint64_t *)(items[i].value) = (uint64_t)items[i].default_value;
204 } else if (items[i].handler == store_time) {
205 *(utime_t *)(items[i].value) = (utime_t)items[i].default_value;
206 } else if (pass == 1 && items[i].handler == store_addresses) {
207 init_default_addresses((dlist**)items[i].value, items[i].default_value);
210 /* If this triggers, take a look at lib/parse_conf.h */
211 if (i >= MAX_RES_ITEMS) {
212 Emsg1(M_ERROR_TERM, 0, _("Too many items in %s resource\n"), resources[rindex]);
218 /* Store Messages Destination information */
219 void store_msgs(LEX *lc, RES_ITEM *item, int index, int pass)
226 Dmsg2(900, "store_msgs pass=%d code=%d\n", pass, item->code);
228 switch (item->code) {
231 case MD_SYSLOG: /* syslog */
234 scan_types(lc, (MSGS *)(item->value), item->code, NULL, NULL);
236 case MD_OPERATOR: /* send to operator */
237 case MD_DIRECTOR: /* send to Director */
238 case MD_MAIL: /* mail */
239 case MD_MAIL_ON_ERROR: /* mail if Job errors */
240 if (item->code == MD_OPERATOR) {
241 cmd = res_all.res_msgs.operator_cmd;
243 cmd = res_all.res_msgs.mail_cmd;
245 dest = get_pool_memory(PM_MESSAGE);
248 /* Pick up comma separated list of destinations */
250 token = lex_get_token(lc, T_NAME); /* scan destination */
251 dest = check_pool_memory_size(dest, dest_len + lc->str_len + 2);
253 pm_strcat(dest, " "); /* separate multiple destinations with space */
256 pm_strcat(dest, lc->str);
257 dest_len += lc->str_len;
258 Dmsg2(900, "store_msgs newdest=%s: dest=%s:\n", lc->str, NPRT(dest));
259 token = lex_get_token(lc, T_SKIP_EOL);
260 if (token == T_COMMA) {
261 continue; /* get another destination */
263 if (token != T_EQUALS) {
264 scan_err1(lc, _("expected an =, got: %s"), lc->str);
268 Dmsg1(900, "mail_cmd=%s\n", NPRT(cmd));
269 scan_types(lc, (MSGS *)(item->value), item->code, dest, cmd);
270 free_pool_memory(dest);
271 Dmsg0(900, "done with dest codes\n");
273 case MD_FILE: /* file */
274 case MD_APPEND: /* append */
275 dest = get_pool_memory(PM_MESSAGE);
276 /* Pick up a single destination */
277 token = lex_get_token(lc, T_NAME); /* scan destination */
278 pm_strcpy(dest, lc->str);
279 dest_len = lc->str_len;
280 token = lex_get_token(lc, T_SKIP_EOL);
281 Dmsg1(900, "store_msgs dest=%s:\n", NPRT(dest));
282 if (token != T_EQUALS) {
283 scan_err1(lc, _("expected an =, got: %s"), lc->str);
285 scan_types(lc, (MSGS *)(item->value), item->code, dest, NULL);
286 free_pool_memory(dest);
287 Dmsg0(900, "done with dest codes\n");
291 scan_err1(lc, _("Unknown item code: %d\n"), item->code);
296 set_bit(index, res_all.hdr.item_present);
297 Dmsg0(900, "Done store_msgs\n");
301 * Scan for message types and add them to the message
302 * destination. The basic job here is to connect message types
303 * (WARNING, ERROR, FATAL, INFO, ...) with an appropriate
304 * destination (MAIL, FILE, OPERATOR, ...)
306 static void scan_types(LEX *lc, MSGS *msg, int dest_code, char *where, char *cmd)
314 lex_get_token(lc, T_NAME); /* expect at least one type */
316 if (lc->str[0] == '!') {
323 for (i=0; msg_types[i].name; i++) {
324 if (strcasecmp(str, msg_types[i].name) == 0) {
325 msg_type = msg_types[i].token;
331 scan_err1(lc, _("message type: %s not found"), str);
335 if (msg_type == M_MAX+1) { /* all? */
336 for (i=1; i<=M_MAX; i++) { /* yes set all types */
337 add_msg_dest(msg, dest_code, i, where, cmd);
340 rem_msg_dest(msg, dest_code, msg_type, where);
342 add_msg_dest(msg, dest_code, msg_type, where, cmd);
347 Dmsg0(900, "call lex_get_token() to eat comma\n");
348 lex_get_token(lc, T_ALL); /* eat comma */
350 Dmsg0(900, "Done scan_types()\n");
355 * This routine is ONLY for resource names
356 * Store a name at specified address.
358 void store_name(LEX *lc, RES_ITEM *item, int index, int pass)
360 POOLMEM *msg = get_pool_memory(PM_EMSG);
361 lex_get_token(lc, T_NAME);
362 if (!is_name_valid(lc->str, &msg)) {
363 scan_err1(lc, "%s\n", msg);
365 free_pool_memory(msg);
366 /* Store the name both pass 1 and pass 2 */
367 if (*(item->value)) {
368 scan_err2(lc, _("Attempt to redefine name \"%s\" to \"%s\"."),
369 *(item->value), lc->str);
371 *(item->value) = bstrdup(lc->str);
373 set_bit(index, res_all.hdr.item_present);
378 * Store a name string at specified address
379 * A name string is limited to MAX_RES_NAME_LENGTH
381 void store_strname(LEX *lc, RES_ITEM *item, int index, int pass)
383 lex_get_token(lc, T_NAME);
386 *(item->value) = bstrdup(lc->str);
389 set_bit(index, res_all.hdr.item_present);
392 /* Store a string at specified address */
393 void store_str(LEX *lc, RES_ITEM *item, int index, int pass)
395 lex_get_token(lc, T_STRING);
397 *(item->value) = bstrdup(lc->str);
400 set_bit(index, res_all.hdr.item_present);
404 * Store a directory name at specified address. Note, we do
405 * shell expansion except if the string begins with a vertical
406 * bar (i.e. it will likely be passed to the shell later).
408 void store_dir(LEX *lc, RES_ITEM *item, int index, int pass)
410 lex_get_token(lc, T_STRING);
412 if (lc->str[0] != '|') {
413 do_shell_expansion(lc->str, sizeof(lc->str));
415 *(item->value) = bstrdup(lc->str);
418 set_bit(index, res_all.hdr.item_present);
422 /* Store a password specified address in MD5 coding */
423 void store_password(LEX *lc, RES_ITEM *item, int index, int pass)
426 struct MD5Context md5c;
427 unsigned char digest[CRYPTO_DIGEST_MD5_SIZE];
431 lex_get_token(lc, T_STRING);
434 MD5Update(&md5c, (unsigned char *) (lc->str), lc->str_len);
435 MD5Final(digest, &md5c);
436 for (i = j = 0; i < sizeof(digest); i++) {
437 sprintf(&sig[j], "%02x", digest[i]);
440 *(item->value) = bstrdup(sig);
443 set_bit(index, res_all.hdr.item_present);
447 /* Store a resource at specified address.
448 * If we are in pass 2, do a lookup of the
451 void store_res(LEX *lc, RES_ITEM *item, int index, int pass)
455 lex_get_token(lc, T_NAME);
457 res = GetResWithName(item->code, lc->str);
459 scan_err3(lc, _("Could not find config Resource %s referenced on line %d : %s\n"),
460 lc->str, lc->line_no, lc->line);
462 if (*(item->value)) {
463 scan_err3(lc, _("Attempt to redefine resource \"%s\" referenced on line %d : %s\n"),
464 item->name, lc->line_no, lc->line);
466 *(item->value) = (char *)res;
469 set_bit(index, res_all.hdr.item_present);
473 * Store a resource pointer in an alist. default_value indicates how many
474 * times this routine can be called -- i.e. how many alists
476 * If we are in pass 2, do a lookup of the
479 void store_alist_res(LEX *lc, RES_ITEM *item, int index, int pass)
482 int count = item->default_value;
487 if (count == 0) { /* always store in item->value */
489 if ((item->value)[i] == NULL) {
490 list = New(alist(10, not_owned_by_alist));
492 list = (alist *)(item->value)[i];
495 /* Find empty place to store this directive */
496 while ((item->value)[i] != NULL && i++ < count) { }
498 scan_err4(lc, _("Too many %s directives. Max. is %d. line %d: %s\n"),
499 lc->str, count, lc->line_no, lc->line);
501 list = New(alist(10, not_owned_by_alist));
505 lex_get_token(lc, T_NAME); /* scan next item */
506 res = GetResWithName(item->code, lc->str);
508 scan_err3(lc, _("Could not find config Resource \"%s\" referenced on line %d : %s\n"),
509 item->name, lc->line_no, lc->line);
511 Dmsg5(900, "Append %p to alist %p size=%d i=%d %s\n",
512 res, list, list->size(), i, item->name);
514 (item->value)[i] = (char *)list;
515 if (lc->ch != ',') { /* if no other item follows */
518 lex_get_token(lc, T_ALL); /* eat comma */
522 set_bit(index, res_all.hdr.item_present);
527 * Store a string in an alist.
529 void store_alist_str(LEX *lc, RES_ITEM *item, int index, int pass)
534 if (*(item->value) == NULL) {
535 list = New(alist(10, owned_by_alist));
537 list = (alist *)(*(item->value));
540 lex_get_token(lc, T_STRING); /* scan next item */
541 Dmsg4(900, "Append %s to alist %p size=%d %s\n",
542 lc->str, list, list->size(), item->name);
543 list->append(bstrdup(lc->str));
544 *(item->value) = (char *)list;
547 set_bit(index, res_all.hdr.item_present);
553 * Store default values for Resource from xxxDefs
554 * If we are in pass 2, do a lookup of the
555 * resource and store everything not explicitly set
558 * Note, here item points to the main resource (e.g. Job, not
559 * the jobdefs, which we look up).
561 void store_defs(LEX *lc, RES_ITEM *item, int index, int pass)
565 lex_get_token(lc, T_NAME);
567 Dmsg2(900, "Code=%d name=%s\n", item->code, lc->str);
568 res = GetResWithName(item->code, lc->str);
570 scan_err3(lc, _("Missing config Resource \"%s\" referenced on line %d : %s\n"),
571 lc->str, lc->line_no, lc->line);
579 /* Store an integer at specified address */
580 void store_int(LEX *lc, RES_ITEM *item, int index, int pass)
582 lex_get_token(lc, T_INT32);
583 *(int *)(item->value) = lc->int32_val;
585 set_bit(index, res_all.hdr.item_present);
588 /* Store a positive integer at specified address */
589 void store_pint(LEX *lc, RES_ITEM *item, int index, int pass)
591 lex_get_token(lc, T_PINT32);
592 *(int *)(item->value) = lc->pint32_val;
594 set_bit(index, res_all.hdr.item_present);
598 /* Store an 64 bit integer at specified address */
599 void store_int64(LEX *lc, RES_ITEM *item, int index, int pass)
601 lex_get_token(lc, T_INT64);
602 *(int64_t *)(item->value) = lc->int64_val;
604 set_bit(index, res_all.hdr.item_present);
607 /* Store a size in bytes */
608 void store_size(LEX *lc, RES_ITEM *item, int index, int pass)
614 Dmsg0(900, "Enter store_size\n");
615 token = lex_get_token(lc, T_SKIP_EOL);
620 case T_UNQUOTED_STRING:
621 bstrncpy(bsize, lc->str, sizeof(bsize)); /* save first part */
622 /* if terminated by space, scan and get modifier */
623 while (lc->ch == ' ') {
624 token = lex_get_token(lc, T_ALL);
628 case T_UNQUOTED_STRING:
629 bstrncat(bsize, lc->str, sizeof(bsize));
633 if (!size_to_uint64(bsize, strlen(bsize), &uvalue)) {
634 scan_err1(lc, _("expected a size number, got: %s"), lc->str);
636 *(uint64_t *)(item->value) = uvalue;
639 scan_err1(lc, _("expected a size, got: %s"), lc->str);
642 if (token != T_EOL) {
645 set_bit(index, res_all.hdr.item_present);
646 Dmsg0(900, "Leave store_size\n");
650 /* Store a time period in seconds */
651 void store_time(LEX *lc, RES_ITEM *item, int index, int pass)
657 token = lex_get_token(lc, T_SKIP_EOL);
662 case T_UNQUOTED_STRING:
663 bstrncpy(period, lc->str, sizeof(period)); /* get first part */
664 /* if terminated by space, scan and get modifier */
665 while (lc->ch == ' ') {
666 token = lex_get_token(lc, T_ALL);
670 case T_UNQUOTED_STRING:
671 bstrncat(period, lc->str, sizeof(period));
675 if (!duration_to_utime(period, &utime)) {
676 scan_err1(lc, _("expected a time period, got: %s"), period);
678 *(utime_t *)(item->value) = utime;
681 scan_err1(lc, _("expected a time period, got: %s"), lc->str);
684 if (token != T_EOL) {
687 set_bit(index, res_all.hdr.item_present);
691 /* Store a yes/no in a bit field */
692 void store_bit(LEX *lc, RES_ITEM *item, int index, int pass)
694 lex_get_token(lc, T_NAME);
695 if (strcasecmp(lc->str, "yes") == 0 || strcasecmp(lc->str, "true") == 0) {
696 *(int *)(item->value) |= item->code;
697 } else if (strcasecmp(lc->str, "no") == 0 || strcasecmp(lc->str, "false") == 0) {
698 *(int *)(item->value) &= ~(item->code);
700 scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, TRUE, or FALSE", lc->str); /* YES and NO must not be translated */
703 set_bit(index, res_all.hdr.item_present);
706 /* Store a bool in a bit field */
707 void store_bool(LEX *lc, RES_ITEM *item, int index, int pass)
709 lex_get_token(lc, T_NAME);
710 if (strcasecmp(lc->str, "yes") == 0 || strcasecmp(lc->str, "true") == 0) {
711 *(bool *)(item->value) = true;
712 } else if (strcasecmp(lc->str, "no") == 0 || strcasecmp(lc->str, "false") == 0) {
713 *(bool *)(item->value) = false;
715 scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, TRUE, or FALSE", lc->str); /* YES and NO must not be translated */
718 set_bit(index, res_all.hdr.item_present);
723 * Store Tape Label Type (Bacula, ANSI, IBM)
726 void store_label(LEX *lc, RES_ITEM *item, int index, int pass)
730 token = lex_get_token(lc, T_NAME);
731 /* Store the label pass 2 so that type is defined */
732 for (i=0; tapelabels[i].name; i++) {
733 if (strcasecmp(lc->str, tapelabels[i].name) == 0) {
734 *(int *)(item->value) = tapelabels[i].token;
740 scan_err1(lc, _("Expected a Tape Label keyword, got: %s"), lc->str);
743 set_bit(index, res_all.hdr.item_present);
753 /*********************************************************************
755 * Parse configuration file
757 * Return 0 if reading failed, 1 otherwise
758 * Note, the default behavior unless you have set an alternate
759 * scan_error handler is to die on an error.
762 parse_config(const char *cf, LEX_ERROR_HANDLER *scan_error, int err_type)
767 enum parse_state state = p_none;
768 RES_ITEM *items = NULL;
771 /* Make two passes. The first builds the name symbol table,
772 * and the second picks up the items.
774 Dmsg0(900, "Enter parse_config()\n");
775 for (pass=1; pass <= 2; pass++) {
776 Dmsg1(900, "parse_config pass %d\n", pass);
777 if ((lc = lex_open_file(lc, cf, scan_error)) == NULL) {
779 /* We must create a lex packet to print the error */
780 lc = (LEX *)malloc(sizeof(LEX));
781 memset(lc, 0, sizeof(LEX));
783 lc->scan_error = scan_error;
785 lex_set_default_error_handler(lc);
787 lex_set_error_handler_error_type(lc, err_type) ;
788 bstrncpy(lc->str, cf, sizeof(lc->str));
790 scan_err2(lc, _("Cannot open config file \"%s\": %s\n"),
791 lc->str, be.strerror());
795 lex_set_error_handler_error_type(lc, err_type) ;
796 while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
797 Dmsg1(900, "parse got token=%s\n", lex_tok_to_str(token));
800 if (token == T_EOL) {
803 if (token != T_IDENTIFIER) {
804 scan_err1(lc, _("Expected a Resource name identifier, got: %s"), lc->str);
807 for (i=0; resources[i].name; i++)
808 if (strcasecmp(resources[i].name, lc->str) == 0) {
810 items = resources[i].items;
811 res_type = resources[i].rcode;
812 init_resource(res_type, items, pass);
815 if (state == p_none) {
816 scan_err1(lc, _("expected resource name, got: %s"), lc->str);
827 scan_err1(lc, _("not in resource definition: %s"), lc->str);
830 for (i=0; items[i].name; i++) {
831 if (strcasecmp(items[i].name, lc->str) == 0) {
832 /* If the ITEM_NO_EQUALS flag is set we do NOT
833 * scan for = after the keyword */
834 if (!(items[i].flags & ITEM_NO_EQUALS)) {
835 token = lex_get_token(lc, T_SKIP_EOL);
836 Dmsg1 (900, "in T_IDENT got token=%s\n", lex_tok_to_str(token));
837 if (token != T_EQUALS) {
838 scan_err1(lc, _("expected an equals, got: %s"), lc->str);
842 Dmsg1(800, "calling handler for %s\n", items[i].name);
843 /* Call item handler */
844 items[i].handler(lc, &items[i], i, pass);
850 Dmsg2(900, "level=%d id=%s\n", level, lc->str);
851 Dmsg1(900, "Keyword = %s\n", lc->str);
852 scan_err1(lc, _("Keyword \"%s\" not permitted in this resource.\n"
853 "Perhaps you left the trailing brace off of the previous resource."), lc->str);
861 Dmsg0(900, "T_EOB => define new resource\n");
862 if (res_all.hdr.name == NULL) {
863 scan_err0(lc, _("Name not specified for resource"));
865 save_resource(res_type, items, pass); /* save resource */
872 scan_err2(lc, _("unexpected token %d %s in resource definition"),
873 token, lex_tok_to_str(token));
878 scan_err1(lc, _("Unknown parser state %d\n"), state);
882 if (state != p_none) {
883 scan_err0(lc, _("End of conf file reached with unclosed resource."));
886 if (debug_level >= 900 && pass == 2) {
888 for (i=r_first; i<=r_last; i++) {
889 dump_resource(i, res_head[i-r_first], prtmsg, NULL);
892 lc = lex_close_file(lc);
894 Dmsg0(900, "Leave parse_config()\n");
898 /*********************************************************************
900 * Free configuration resources
903 void free_config_resources()
905 for (int i=r_first; i<=r_last; i++) {
906 free_resource(res_head[i-r_first], i);
907 res_head[i-r_first] = NULL;
911 RES **save_config_resources()
913 int num = r_last - r_first + 1;
914 RES **res = (RES **)malloc(num*sizeof(RES *));
915 for (int i=0; i<num; i++) {
916 res[i] = res_head[i];
924 int size = (r_last - r_first + 1) * sizeof(RES *);
925 RES **res = (RES **)malloc(size);
926 memset(res, 0, size);