]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/dird_conf.c
a16f3aa3352e687c76a271d451ac59f2659e3d4f
[bacula/bacula] / bacula / src / dird / dird_conf.c
1 /*
2  *   Main configuration file parser for Bacula Directors,
3  *    some parts may be split into separate files such as
4  *    the schedule configuration (sch_config.c).
5  *
6  *   Note, the configuration file parser consists of three parts
7  *
8  *   1. The generic lexical scanner in lib/lex.c and lib/lex.h
9  *
10  *   2. The generic config  scanner in lib/parse_config.c and 
11  *      lib/parse_config.h.
12  *      These files contain the parser code, some utility
13  *      routines, and the common store routines (name, int,
14  *      string).
15  *
16  *   3. The daemon specific file, which contains the Resource
17  *      definitions as well as any specific store routines
18  *      for the resource records.
19  *
20  *     Kern Sibbald, January MM
21  *
22  *     Version $Id$
23  */
24 /*
25    Copyright (C) 2000, 2001, 2002 Kern Sibbald and John Walker
26
27    This program is free software; you can redistribute it and/or
28    modify it under the terms of the GNU General Public License as
29    published by the Free Software Foundation; either version 2 of
30    the License, or (at your option) any later version.
31
32    This program is distributed in the hope that it will be useful,
33    but WITHOUT ANY WARRANTY; without even the implied warranty of
34    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
35    General Public License for more details.
36
37    You should have received a copy of the GNU General Public
38    License along with this program; if not, write to the Free
39    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
40    MA 02111-1307, USA.
41
42  */
43
44 #include "bacula.h"
45 #include "dird.h"
46
47 /* Define the first and last resource ID record
48  * types. Note, these should be unique for each
49  * daemon though not a requirement.
50  */
51 int r_first = R_FIRST;
52 int r_last  = R_LAST;
53 pthread_mutex_t res_mutex =  PTHREAD_MUTEX_INITIALIZER;
54
55 /* Imported subroutines */
56 extern void store_run(LEX *lc, struct res_items *item, int index, int pass);
57
58
59 /* Forward referenced subroutines */
60
61 static void store_inc(LEX *lc, struct res_items *item, int index, int pass);
62 static void store_backup(LEX *lc, struct res_items *item, int index, int pass);
63 static void store_restore(LEX *lc, struct res_items *item, int index, int pass);
64 static void store_jobtype(LEX *lc, struct res_items *item, int index, int pass);
65 static void store_level(LEX *lc, struct res_items *item, int index, int pass);
66
67
68 /* We build the current resource here as we are
69  * scanning the resource configuration definition,
70  * then move it to allocated memory when the resource
71  * scan is complete.
72  */
73 URES res_all;
74 int  res_all_size = sizeof(res_all);
75
76
77 /* Definition of records permitted within each
78  * resource with the routine to process the record 
79  * information.  NOTE! quoted names must be in lower case.
80  */ 
81 /* 
82  *    Director Resource
83  *
84  *   name          handler     value                 code flags    default_value
85  */
86 static struct res_items dir_items[] = {
87    {"name",        store_name,     ITEM(res_dir.hdr.name), 0, ITEM_REQUIRED, 0},
88    {"description", store_str,      ITEM(res_dir.hdr.desc), 0, 0, 0},
89    {"messages",    store_res,      ITEM(res_dir.messages), R_MSGS, 0, 0},
90    {"dirport",     store_pint,     ITEM(res_dir.DIRport),  0, ITEM_REQUIRED, 0},
91    {"queryfile",   store_dir,      ITEM(res_dir.query_file), 0, 0, 0},
92    {"workingdirectory", store_dir, ITEM(res_dir.working_directory), 0, ITEM_REQUIRED, 0},
93    {"piddirectory", store_dir,     ITEM(res_dir.pid_directory), 0, ITEM_REQUIRED, 0},
94    {"subsysdirectory", store_dir,  ITEM(res_dir.subsys_directory), 0, ITEM_REQUIRED, 0},
95    {"maximumconcurrentjobs", store_pint, ITEM(res_dir.MaxConcurrentJobs), 0, ITEM_DEFAULT, 1},
96    {"password",    store_password, ITEM(res_dir.password), 0, ITEM_REQUIRED, 0},
97    {"fdconnecttimeout", store_time,ITEM(res_dir.FDConnectTimeout), 0, ITEM_DEFAULT, 60 * 30},
98    {"sdconnecttimeout", store_time,ITEM(res_dir.SDConnectTimeout), 0, ITEM_DEFAULT, 60 * 30},
99    {NULL, NULL, NULL, 0, 0, 0}
100 };
101
102 /* 
103  *    Client or File daemon resource
104  *
105  *   name          handler     value                 code flags    default_value
106  */
107
108 static struct res_items cli_items[] = {
109    {"name",     store_name,       ITEM(res_client.hdr.name), 0, ITEM_REQUIRED, 0},
110    {"description", store_str,     ITEM(res_client.hdr.desc), 0, 0, 0},
111    {"address",  store_str,        ITEM(res_client.address),  0, ITEM_REQUIRED, 0},
112    {"fdport",   store_pint,       ITEM(res_client.FDport),   0, ITEM_REQUIRED, 0},
113    {"password", store_password,   ITEM(res_client.password), 0, ITEM_REQUIRED, 0},
114    {"catalog",  store_res,        ITEM(res_client.catalog),  R_CATALOG, 0, 0},
115    {"fileretention", store_time,  ITEM(res_client.FileRetention), 0, ITEM_DEFAULT, 60*60*24*60},
116    {"jobretention",  store_time,  ITEM(res_client.JobRetention),  0, ITEM_DEFAULT, 60*60*24*180},
117    {"autoprune", store_yesno,     ITEM(res_client.AutoPrune), 1, ITEM_DEFAULT, 1},
118    {NULL, NULL, NULL, 0, 0, 0} 
119 };
120
121 /* Storage daemon resource
122  *
123  *   name          handler     value                 code flags    default_value
124  */
125 static struct res_items store_items[] = {
126    {"name",      store_name,     ITEM(res_store.hdr.name),   0, ITEM_REQUIRED, 0},
127    {"description", store_str,    ITEM(res_store.hdr.desc),   0, 0, 0},
128    {"sdport",    store_pint,     ITEM(res_store.SDport),     0, ITEM_REQUIRED, 0},
129    {"sddport",   store_pint,     ITEM(res_store.SDDport),    0, 0, 0}, /* deprecated */
130    {"address",   store_str,      ITEM(res_store.address),    0, ITEM_REQUIRED, 0},
131    {"password",  store_password, ITEM(res_store.password),   0, ITEM_REQUIRED, 0},
132    {"device",    store_strname,  ITEM(res_store.dev_name),   0, ITEM_REQUIRED, 0},
133    {"mediatype", store_strname,  ITEM(res_store.media_type), 0, ITEM_REQUIRED, 0},
134    {NULL, NULL, NULL, 0, 0, 0} 
135 };
136
137 /* 
138  *    Catalog Resource Directives
139  *
140  *   name          handler     value                 code flags    default_value
141  */
142 static struct res_items cat_items[] = {
143    {"name",     store_name,     ITEM(res_cat.hdr.name),    0, ITEM_REQUIRED, 0},
144    {"description", store_str,   ITEM(res_cat.hdr.desc),    0, 0, 0},
145    {"address",  store_str,      ITEM(res_cat.address),     0, 0, 0},
146    {"dbport",   store_pint,     ITEM(res_cat.DBport),      0, 0, 0},
147    /* keep this password as store_str for the moment */
148    {"password", store_str,      ITEM(res_cat.db_password), 0, 0, 0},
149    {"user",     store_str,      ITEM(res_cat.db_user),     0, 0, 0},
150    {"dbname",   store_str,      ITEM(res_cat.db_name),     0, ITEM_REQUIRED, 0},
151    {NULL, NULL, NULL, 0, 0, 0} 
152 };
153
154 /* 
155  *    Job Resource Directives
156  *
157  *   name          handler     value                 code flags    default_value
158  */
159 static struct res_items job_items[] = {
160    {"name",     store_name,    ITEM(res_job.hdr.name), 0, ITEM_REQUIRED, 0},
161    {"description", store_str,  ITEM(res_job.hdr.desc), 0, 0, 0},
162    {"backup",   store_backup,  ITEM(res_job),          JT_BACKUP, 0, 0},
163    {"verify",   store_backup,  ITEM(res_job),          JT_VERIFY, 0, 0},
164    {"restore",  store_restore, ITEM(res_job),          JT_RESTORE, 0, 0},
165    {"schedule", store_res,     ITEM(res_job.schedule), R_SCHEDULE, 0, 0},
166    {"type",     store_jobtype, ITEM(res_job),          0, 0, 0},
167    {"level",    store_level,   ITEM(res_job),          0, 0, 0},
168    {"messages", store_res,     ITEM(res_job.messages), R_MSGS, 0, 0},
169    {"storage",  store_res,     ITEM(res_job.storage),  R_STORAGE, 0, 0},
170    {"pool",     store_res,     ITEM(res_job.pool),     R_POOL, 0, 0},
171    {"client",   store_res,     ITEM(res_job.client),   R_CLIENT, 0, 0},
172    {"fileset",  store_res,     ITEM(res_job.fs),       R_FILESET, 0, 0},
173    {"where",    store_dir,     ITEM(res_job.RestoreWhere), 0, 0, 0},
174    {"bootstrap",store_dir,     ITEM(res_job.RestoreBootstrap), 0, 0, 0},
175    {"maxruntime", store_time,  ITEM(res_job.MaxRunTime), 0, 0, 0},
176    {"maxstartdelay", store_time,ITEM(res_job.MaxStartDelay), 0, 0, 0},
177    {"prunejobs",   store_yesno, ITEM(res_job.PruneJobs), 1, ITEM_DEFAULT, 0},
178    {"prunefiles",  store_yesno, ITEM(res_job.PruneFiles), 1, ITEM_DEFAULT, 0},
179    {"prunevolumes", store_yesno, ITEM(res_job.PruneVolumes), 1, ITEM_DEFAULT, 0},
180    {"runbeforejob", store_str,  ITEM(res_job.RunBeforeJob), 0, 0, 0},
181    {"runafterjob",  store_str,  ITEM(res_job.RunAfterJob),  0, 0, 0},
182    {NULL, NULL, NULL, 0, 0, 0} 
183 };
184
185 /* FileSet resource
186  *
187  *   name          handler     value                 code flags    default_value
188  */
189 static struct res_items fs_items[] = {
190    {"name",        store_name, ITEM(res_fs.hdr.name), 0, ITEM_REQUIRED, 0},
191    {"description", store_str,  ITEM(res_fs.hdr.desc), 0, 0, 0},
192    {"include",     store_inc,  NULL,                  0, 0, 0},
193    {"exclude",     store_inc,  NULL,                  1, 0, 0},
194    {NULL,          NULL,       NULL,                  0, 0, 0} 
195 };
196
197 /* Schedule -- see run_conf.c */
198 /* Schedule
199  *
200  *   name          handler     value                 code flags    default_value
201  */
202 static struct res_items sch_items[] = {
203    {"name",     store_name,  ITEM(res_sch.hdr.name), 0, ITEM_REQUIRED, 0},
204    {"description", store_str, ITEM(res_sch.hdr.desc), 0, 0, 0},
205    {"run",      store_run,   ITEM(res_sch.run),      0, 0, 0},
206    {NULL, NULL, NULL, 0, 0, 0} 
207 };
208
209 /* Group resource -- not implemented
210  *
211  *   name          handler     value                 code flags    default_value
212  */
213 static struct res_items group_items[] = {
214    {"name",        store_name, ITEM(res_group.hdr.name), 0, ITEM_REQUIRED, 0},
215    {"description", store_str,  ITEM(res_group.hdr.desc), 0, 0, 0},
216    {NULL, NULL, NULL, 0, 0, 0} 
217 };
218
219 /* Pool resource
220  *
221  *   name             handler     value                        code flags default_value
222  */
223 static struct res_items pool_items[] = {
224    {"name",            store_name,    ITEM(res_pool.hdr.name),        0, ITEM_REQUIRED, 0},
225    {"description",     store_str,     ITEM(res_pool.hdr.desc),        0, 0,     0},
226    {"pooltype",        store_strname, ITEM(res_pool.pool_type),       0, ITEM_REQUIRED, 0},
227    {"labelformat",     store_strname, ITEM(res_pool.label_format),    0, 0,     0},
228    {"usecatalog",      store_yesno, ITEM(res_pool.use_catalog),     1, ITEM_DEFAULT,  1},
229    {"usevolumeonce",   store_yesno, ITEM(res_pool.use_volume_once), 1, 0,       0},
230    {"maximumvolumes",  store_pint,  ITEM(res_pool.max_volumes),     0, 0,             0},
231    {"acceptanyvolume", store_yesno, ITEM(res_pool.accept_any_volume), 1, 0,     0},
232    {"catalogfiles",    store_yesno, ITEM(res_pool.catalog_files),   1, ITEM_DEFAULT,  1},
233    {"volumeretention", store_time,  ITEM(res_pool.VolRetention), 0, ITEM_DEFAULT, 60*60*24*365},
234    {"autoprune",       store_yesno, ITEM(res_pool.AutoPrune), 1, ITEM_DEFAULT, 1},
235    {"recycle",         store_yesno, ITEM(res_pool.Recycle),     1, ITEM_DEFAULT, 1},
236    {NULL, NULL, NULL, 0, 0, 0} 
237 };
238
239 /* 
240  * Counter Resource
241  *   name             handler     value                        code flags default_value
242  */
243 static struct res_items counter_items[] = {
244    {"name",            store_name,    ITEM(res_counter.hdr.name),        0, ITEM_REQUIRED, 0},
245    {"description",     store_str,     ITEM(res_counter.hdr.desc),        0, 0,     0},
246    {"minimum",         store_int,     ITEM(res_counter.MinValue),        0, ITEM_DEFAULT, 0},
247    {"maximum",         store_pint,    ITEM(res_counter.MaxValue),        0, ITEM_DEFAULT, INT32_MAX},
248    {"global",          store_yesno,   ITEM(res_counter.Global),          0, ITEM_DEFAULT, 0},
249    {"wrapcounter",     store_strname, ITEM(res_counter.WrapCounter),     0, 0, 0},
250    {NULL, NULL, NULL, 0, 0, 0} 
251 };
252
253
254 /* Message resource */
255 extern struct res_items msgs_items[];
256
257 /* 
258  * This is the master resource definition.  
259  * It must have one item for each of the resources.
260  *
261  *  name             items        rcode        res_head
262  */
263 struct s_res resources[] = {
264    {"director",      dir_items,   R_DIRECTOR,  NULL},
265    {"client",        cli_items,   R_CLIENT,    NULL},
266    {"job",           job_items,   R_JOB,       NULL},
267    {"storage",       store_items, R_STORAGE,   NULL},
268    {"catalog",       cat_items,   R_CATALOG,   NULL},
269    {"schedule",      sch_items,   R_SCHEDULE,  NULL},
270    {"fileset",       fs_items,    R_FILESET,   NULL},
271    {"group",         group_items, R_GROUP,     NULL},
272    {"pool",          pool_items,  R_POOL,      NULL},
273    {"messages",      msgs_items,  R_MSGS,      NULL},
274    {"counter",       counter_items, R_COUNTER, NULL},
275    {NULL,            NULL,        0,           NULL}
276 };
277
278
279 /* Keywords (RHS) permitted in Job Level records   
280  *
281  *   level_name      level              job_type
282  */
283 struct s_jl joblevels[] = {
284    {"Full",          L_FULL,            JT_BACKUP},
285    {"Incremental",   L_INCREMENTAL,     JT_BACKUP},
286    {"Differential",  L_DIFFERENTIAL,    JT_BACKUP},
287    {"Level",         L_LEVEL,           JT_BACKUP},
288    {"Since",         L_SINCE,           JT_BACKUP},
289    {"Catalog",       L_VERIFY_CATALOG,  JT_VERIFY},
290    {"Initcatalog",   L_VERIFY_INIT,     JT_VERIFY},
291    {"VolumeToCatalog", L_VERIFY_VOLUME_TO_CATALOG,   JT_VERIFY},
292    {"Data",          L_VERIFY_DATA,     JT_VERIFY},
293    {NULL,            0}
294 };
295
296 /* Keywords (RHS) permitted in Job type records   
297  *
298  *   type_name       job_type
299  */
300 struct s_jt jobtypes[] = {
301    {"backup",        JT_BACKUP},
302    {"admin",         JT_ADMIN},
303    {"verify",        JT_VERIFY},
304    {"restore",       JT_RESTORE},
305    {NULL,            0}
306 };
307
308
309 /* Keywords (RHS) permitted in Backup and Verify records */
310 static struct s_kw BakVerFields[] = {
311    {"client",        'C'},
312    {"fileset",       'F'},
313    {"level",         'L'}, 
314    {NULL,            0}
315 };
316
317 /* Keywords (RHS) permitted in Restore records */
318 static struct s_kw RestoreFields[] = {
319    {"client",        'C'},
320    {"fileset",       'F'},
321    {"jobid",         'J'},            /* JobId to restore */
322    {"where",         'W'},            /* root of restore */
323    {"replace",       'R'},            /* replacement options */
324    {"bootstrap",     'B'},            /* bootstrap file */
325    {NULL,              0}
326 };
327
328 /* Options permitted in Restore replace= */
329 static struct s_kw ReplaceOptions[] = {
330    {"always",         'A'},           /* always */
331    {"ifnewer",        'W'},
332    {"never",          'N'},
333    {NULL,               0}
334 };
335
336
337
338 /* Define FileSet KeyWord values */
339
340 #define FS_KW_NONE         0
341 #define FS_KW_COMPRESSION  1
342 #define FS_KW_SIGNATURE    2
343 #define FS_KW_ENCRYPTION   3
344 #define FS_KW_VERIFY       4
345 #define FS_KW_ONEFS        5
346 #define FS_KW_RECURSE      6
347
348 /* FileSet keywords */
349 static struct s_kw FS_option_kw[] = {
350    {"compression", FS_KW_COMPRESSION},
351    {"signature",   FS_KW_SIGNATURE},
352    {"encryption",  FS_KW_ENCRYPTION},
353    {"verify",      FS_KW_VERIFY},
354    {"onefs",       FS_KW_ONEFS},
355    {"recurse",     FS_KW_RECURSE},
356    {NULL,          0}
357 };
358
359 /* Options for FileSet keywords */
360
361 struct s_fs_opt {
362    char *name;
363    int keyword;
364    char *option;
365 };
366
367 /* Options permitted for each keyword and resulting value */
368 static struct s_fs_opt FS_options[] = {
369    {"md5",      FS_KW_SIGNATURE,    "M"},
370    {"gzip",     FS_KW_COMPRESSION,  "Z6"},
371    {"gzip1",    FS_KW_COMPRESSION,  "Z1"},
372    {"gzip2",    FS_KW_COMPRESSION,  "Z2"},
373    {"gzip3",    FS_KW_COMPRESSION,  "Z3"},
374    {"gzip4",    FS_KW_COMPRESSION,  "Z4"},
375    {"gzip5",    FS_KW_COMPRESSION,  "Z5"},
376    {"gzip6",    FS_KW_COMPRESSION,  "Z6"},
377    {"gzip7",    FS_KW_COMPRESSION,  "Z7"},
378    {"gzip8",    FS_KW_COMPRESSION,  "Z8"},
379    {"gzip9",    FS_KW_COMPRESSION,  "Z9"},
380    {"blowfish", FS_KW_ENCRYPTION,    "B"},   /* ***FIXME*** not implemented */
381    {"3des",     FS_KW_ENCRYPTION,    "3"},   /* ***FIXME*** not implemented */
382    {"yes",      FS_KW_ONEFS,         "0"},
383    {"no",       FS_KW_ONEFS,         "f"},
384    {"yes",      FS_KW_RECURSE,       "0"},
385    {"no",       FS_KW_RECURSE,       "h"},
386    {NULL,       0,                   0}
387 };
388
389 char *level_to_str(int level)
390 {
391    int i;
392    static char level_no[30];
393    char *str = level_no;
394
395    sprintf(level_no, "%d", level);    /* default if not found */
396    for (i=0; joblevels[i].level_name; i++) {
397       if (level == joblevels[i].level) {
398          str = joblevels[i].level_name;
399          break;
400       }
401    }
402    return str;
403 }
404
405
406
407 /* Dump contents of resource */
408 void dump_resource(int type, RES *reshdr, void sendit(void *sock, char *fmt, ...), void *sock)
409 {
410    int i;
411    URES *res = (URES *)reshdr;
412    int recurse = 1;
413
414    if (res == NULL) {
415       sendit(sock, "No %s resource defined\n", res_to_str(type));
416       return;
417    }
418    if (type < 0) {                    /* no recursion */
419       type = - type;
420       recurse = 0;
421    }
422    switch (type) {
423       case R_DIRECTOR:
424          char ed1[30], ed2[30];
425          sendit(sock, "Director: name=%s maxjobs=%d FDtimeout=%s SDtimeout=%s\n", 
426             reshdr->name, res->res_dir.MaxConcurrentJobs, 
427             edit_uint64(res->res_dir.FDConnectTimeout, ed1),
428             edit_uint64(res->res_dir.SDConnectTimeout, ed2));
429          if (res->res_dir.query_file) {
430             sendit(sock, "   query_file=%s\n", res->res_dir.query_file);
431          }
432          if (res->res_dir.messages) {
433             sendit(sock, "  --> ");
434             dump_resource(-R_MSGS, (RES *)res->res_dir.messages, sendit, sock);
435          }
436          break;
437       case R_CLIENT:
438          sendit(sock, "Client: name=%s address=%s FDport=%d\n",
439             res->res_client.hdr.name, res->res_client.address, res->res_client.FDport);
440          sendit(sock, "JobRetention=%" lld " FileRetention=%" lld " AutoPrune=%d\n",
441             res->res_client.JobRetention, res->res_client.FileRetention,
442             res->res_client.AutoPrune);
443          if (res->res_client.catalog) {
444             sendit(sock, "  --> ");
445             dump_resource(-R_CATALOG, (RES *)res->res_client.catalog, sendit, sock);
446          }
447          break;
448       case R_STORAGE:
449          sendit(sock, "Storage: name=%s address=%s SDport=%d\n\
450          DeviceName=%s MediaType=%s\n",
451             res->res_store.hdr.name, res->res_store.address, res->res_store.SDport,
452             res->res_store.dev_name, res->res_store.media_type);
453          break;
454       case R_CATALOG:
455          sendit(sock, "Catalog: name=%s address=%s DBport=%d db_name=%s\n\
456          db_user=%s\n",
457             res->res_cat.hdr.name, NPRT(res->res_cat.address),
458             res->res_cat.DBport, res->res_cat.db_name, NPRT(res->res_cat.db_user));
459          break;
460       case R_JOB:
461          sendit(sock, "Job: name=%s JobType=%d level=%s\n", res->res_job.hdr.name, 
462             res->res_job.JobType, level_to_str(res->res_job.level));
463          if (res->res_job.client) {
464             sendit(sock, "  --> ");
465             dump_resource(-R_CLIENT, (RES *)res->res_job.client, sendit, sock);
466          }
467          if (res->res_job.fs) {
468             sendit(sock, "  --> ");
469             dump_resource(-R_FILESET, (RES *)res->res_job.fs, sendit, sock);
470          }
471          if (res->res_job.schedule) {
472             sendit(sock, "  --> ");
473             dump_resource(-R_SCHEDULE, (RES *)res->res_job.schedule, sendit, sock);
474          }
475          if (res->res_job.RestoreWhere) {
476             sendit(sock, "  --> Where=%s\n", NPRT(res->res_job.RestoreWhere));
477          }
478          if (res->res_job.RestoreBootstrap) {
479             sendit(sock, "  --> Bootstrap=%s\n", NPRT(res->res_job.RestoreBootstrap));
480          }
481          if (res->res_job.RunBeforeJob) {
482             sendit(sock, "  --> RunBefore=%s\n", NPRT(res->res_job.RunBeforeJob));
483          }
484          if (res->res_job.RunAfterJob) {
485             sendit(sock, "  --> RunAfter=%s\n", NPRT(res->res_job.RunAfterJob));
486          }
487          if (res->res_job.storage) {
488             sendit(sock, "  --> ");
489             dump_resource(-R_STORAGE, (RES *)res->res_job.storage, sendit, sock);
490          }
491          if (res->res_job.pool) {
492             sendit(sock, "  --> ");
493             dump_resource(-R_POOL, (RES *)res->res_job.pool, sendit, sock);
494          } else {
495             sendit(sock, "!!! No Pool resource\n");
496          }
497          if (res->res_job.messages) {
498             sendit(sock, "  --> ");
499             dump_resource(-R_MSGS, (RES *)res->res_job.messages, sendit, sock);
500          }
501          break;
502       case R_FILESET:
503          sendit(sock, "FileSet: name=%s\n", res->res_fs.hdr.name);
504          for (i=0; i<res->res_fs.num_includes; i++)
505             sendit(sock, "      Inc: %s\n", res->res_fs.include_array[i]);
506          for (i=0; i<res->res_fs.num_excludes; i++)
507             sendit(sock, "      Exc: %s\n", res->res_fs.exclude_array[i]);
508          break;
509       case R_SCHEDULE:
510          if (res->res_sch.run) {
511             int i;
512             RUN *run = res->res_sch.run;
513             char buf[1000], num[10];
514             sendit(sock, "Schedule: name=%s\n", res->res_sch.hdr.name);
515             if (!run) {
516                break;
517             }
518 next_run:
519             sendit(sock, "  --> Run Level=%s\n", level_to_str(run->level));
520             strcpy(buf, "      hour=");
521             for (i=0; i<24; i++) {
522                if (bit_is_set(i, run->hour)) {
523                   sprintf(num, "%d ", i+1);
524                   strcat(buf, num);
525                }
526             }
527             strcat(buf, "\n");
528             sendit(sock, buf);
529             strcpy(buf, "      mday=");
530             for (i=0; i<31; i++) {
531                if (bit_is_set(i, run->mday)) {
532                   sprintf(num, "%d ", i+1);
533                   strcat(buf, num);
534                }
535             }
536             strcat(buf, "\n");
537             sendit(sock, buf);
538             strcpy(buf, "      month=");
539             for (i=0; i<12; i++) {
540                if (bit_is_set(i, run->month)) {
541                   sprintf(num, "%d ", i+1);
542                   strcat(buf, num);
543                }
544             }
545             strcat(buf, "\n");
546             sendit(sock, buf);
547             strcpy(buf, "      wday=");
548             for (i=0; i<7; i++) {
549                if (bit_is_set(i, run->wday)) {
550                   sprintf(num, "%d ", i+1);
551                   strcat(buf, num);
552                }
553             }
554             strcat(buf, "\n");
555             sendit(sock, buf);
556             sendit(sock, "      mins=%d\n", run->minute);
557             if (run->pool) {
558                sendit(sock, "     --> ");
559                dump_resource(-R_POOL, (RES *)run->pool, sendit, sock);
560             }
561             if (run->storage) {
562                sendit(sock, "     --> ");
563                dump_resource(-R_STORAGE, (RES *)run->storage, sendit, sock);
564             }
565             if (run->msgs) {
566                sendit(sock, "     --> ");
567                dump_resource(-R_MSGS, (RES *)run->msgs, sendit, sock);
568             }
569             /* If another Run record is chained in, go print it */
570             if (run->next) {
571                run = run->next;
572                goto next_run;
573             }
574          } else {
575             sendit(sock, "Schedule: name=%s\n", res->res_sch.hdr.name);
576          }
577          break;
578       case R_GROUP:
579          sendit(sock, "Group: name=%s\n", res->res_group.hdr.name);
580          break;
581       case R_POOL:
582          sendit(sock, "Pool: name=%s PoolType=%s\n", res->res_pool.hdr.name,
583                  res->res_pool.pool_type);
584          sendit(sock, "      use_cat=%d use_once=%d acpt_any=%d cat_files=%d\n",
585                  res->res_pool.use_catalog, res->res_pool.use_volume_once,
586                  res->res_pool.accept_any_volume, res->res_pool.catalog_files);
587          sendit(sock, "      max_vols=%d auto_prune=%d VolRetention=%" lld "\n",
588                  res->res_pool.max_volumes, res->res_pool.AutoPrune,
589                  res->res_pool.VolRetention);
590          sendit(sock, "      recycle=%d LabelFormat=%s\n", res->res_pool.Recycle,
591                  NPRT(res->res_pool.label_format));
592          break;
593       case R_MSGS:
594          sendit(sock, "Messages: name=%s\n", res->res_msgs.hdr.name);
595          if (res->res_msgs.mail_cmd) 
596             sendit(sock, "      mailcmd=%s\n", res->res_msgs.mail_cmd);
597          if (res->res_msgs.operator_cmd) 
598             sendit(sock, "      opcmd=%s\n", res->res_msgs.operator_cmd);
599          break;
600       default:
601          sendit(sock, "Unknown resource type %d\n", type);
602          break;
603    }
604    if (recurse && res->res_dir.hdr.next) {
605       dump_resource(type, res->res_dir.hdr.next, sendit, sock);
606    }
607 }
608
609 /* 
610  * Free memory of resource.  
611  * NB, we don't need to worry about freeing any references
612  * to other resources as they will be freed when that 
613  * resource chain is traversed.  Mainly we worry about freeing
614  * allocated strings (names).
615  */
616 void free_resource(int type)
617 {
618    int num;
619    URES *res;
620    RES *nres;
621    int rindex = type - r_first;
622
623    res = (URES *)resources[rindex].res_head;
624
625    if (res == NULL)
626       return;
627
628    /* common stuff -- free the resource name and description */
629    nres = (RES *)res->res_dir.hdr.next;
630    if (res->res_dir.hdr.name) {
631       free(res->res_dir.hdr.name);
632    }
633    if (res->res_dir.hdr.desc) {
634       free(res->res_dir.hdr.desc);
635    }
636
637    switch (type) {
638       case R_DIRECTOR:
639          if (res->res_dir.working_directory)
640             free(res->res_dir.working_directory);
641          if (res->res_dir.pid_directory)
642             free(res->res_dir.pid_directory);
643          if (res->res_dir.subsys_directory)
644             free(res->res_dir.subsys_directory);
645          if (res->res_dir.password)
646             free(res->res_dir.password);
647          if (res->res_dir.query_file)
648             free(res->res_dir.query_file);
649          break;
650       case R_CLIENT:
651          if (res->res_client.address)
652             free(res->res_client.address);
653          if (res->res_client.password)
654             free(res->res_client.password);
655          break;
656       case R_STORAGE:
657          if (res->res_store.address)
658             free(res->res_store.address);
659          if (res->res_store.password)
660             free(res->res_store.password);
661          if (res->res_store.media_type)
662             free(res->res_store.media_type);
663          if (res->res_store.dev_name)
664             free(res->res_store.dev_name);
665          break;
666       case R_CATALOG:
667          if (res->res_cat.address)
668             free(res->res_cat.address);
669          if (res->res_cat.db_user)
670             free(res->res_cat.db_user);
671          if (res->res_cat.db_name)
672             free(res->res_cat.db_name);
673          if (res->res_cat.db_password)
674             free(res->res_cat.db_password);
675          break;
676       case R_FILESET:
677          if ((num=res->res_fs.num_includes)) {
678             while (--num >= 0)    
679                free(res->res_fs.include_array[num]);
680             free(res->res_fs.include_array);
681          }
682          if ((num=res->res_fs.num_excludes)) {
683             while (--num >= 0)    
684                free(res->res_fs.exclude_array[num]);
685             free(res->res_fs.exclude_array);
686          }
687          break;
688       case R_POOL:
689          if (res->res_pool.pool_type) {
690             free(res->res_pool.pool_type);
691          }
692          if (res->res_pool.label_format) {
693             free(res->res_pool.label_format);
694          }
695          break;
696       case R_SCHEDULE:
697          if (res->res_sch.run) {
698             RUN *nrun, *next;
699             nrun = res->res_sch.run;
700             while (nrun) {
701                next = nrun->next;
702                free(nrun);
703                nrun = next;
704             }
705          }
706          break;
707       case R_JOB:
708          if (res->res_job.RestoreWhere) {
709             free(res->res_job.RestoreWhere);
710          }
711          if (res->res_job.RestoreBootstrap) {
712             free(res->res_job.RestoreBootstrap);
713          }
714          if (res->res_job.RunBeforeJob) {
715             free(res->res_job.RunBeforeJob);
716          }
717          if (res->res_job.RunAfterJob) {
718             free(res->res_job.RunAfterJob);
719          }
720          break;
721       case R_MSGS:
722          if (res->res_msgs.mail_cmd)
723             free(res->res_msgs.mail_cmd);
724          if (res->res_msgs.operator_cmd)
725             free(res->res_msgs.operator_cmd);
726          free_msgs_res((MSGS *)res);  /* free message resource */
727          res = NULL;
728          break;
729       case R_GROUP:
730          break;
731       default:
732          printf("Unknown resource type %d\n", type);
733    }
734    /* Common stuff again -- free the resource, recurse to next one */
735    if (res) {
736       free(res);
737    }
738    resources[rindex].res_head = nres;
739    if (nres) {
740       free_resource(type);
741    }
742 }
743
744 /*
745  * Save the new resource by chaining it into the head list for
746  * the resource. If this is pass 2, we update any resource
747  * pointers because they may not have been defined until 
748  * later in pass 1.
749  */
750 void save_resource(int type, struct res_items *items, int pass)
751 {
752    URES *res;
753    int rindex = type - r_first;
754    int i, size;
755    int error = 0;
756    
757    /* 
758     * Ensure that all required items are present
759     */
760    for (i=0; items[i].name; i++) {
761       if (items[i].flags & ITEM_REQUIRED) {
762             if (!bit_is_set(i, res_all.res_dir.hdr.item_present)) {  
763                Emsg2(M_ABORT, 0, "%s item is required in %s resource, but not found.\n",
764                  items[i].name, resources[rindex]);
765              }
766       }
767       /* If this triggers, take a look at lib/parse_conf.h */
768       if (i >= MAX_RES_ITEMS) {
769          Emsg1(M_ABORT, 0, "Too many items in %s resource\n", resources[rindex]);
770       }
771    }
772
773    /* During pass 2 in each "store" routine, we looked up pointers 
774     * to all the resources referrenced in the current resource, now we
775     * must copy their addresses from the static record to the allocated
776     * record.
777     */
778    if (pass == 2) {
779       switch (type) {
780          /* Resources not containing a resource */
781          case R_CATALOG:
782          case R_STORAGE:
783          case R_FILESET:
784          case R_GROUP:
785          case R_POOL:
786          case R_MSGS:
787             break;
788
789          /* Resources containing another resource */
790          case R_DIRECTOR:
791             if ((res = (URES *)GetResWithName(R_DIRECTOR, res_all.res_dir.hdr.name)) == NULL) {
792                Emsg1(M_ABORT, 0, "Cannot find Director resource %s\n", res_all.res_dir.hdr.name);
793             }
794             res->res_dir.messages = res_all.res_dir.messages;
795             break;
796          case R_JOB:
797             if ((res = (URES *)GetResWithName(R_JOB, res_all.res_dir.hdr.name)) == NULL) {
798                Emsg1(M_ABORT, 0, "Cannot find Job resource %s\n", res_all.res_dir.hdr.name);
799             }
800             res->res_job.messages = res_all.res_job.messages;
801             res->res_job.schedule = res_all.res_job.schedule;
802             res->res_job.client   = res_all.res_job.client;
803             res->res_job.fs       = res_all.res_job.fs;
804             res->res_job.storage  = res_all.res_job.storage;
805             res->res_job.pool     = res_all.res_job.pool;
806             if (res->res_job.JobType == 0) {
807                Emsg1(M_ABORT, 0, "Job Type not defined for Job resource %s\n", res_all.res_dir.hdr.name);
808             }
809             if (res->res_job.level != 0) {
810                int i;
811                for (i=0; joblevels[i].level_name; i++) {
812                   if (joblevels[i].level == res->res_job.level &&
813                       joblevels[i].job_type == res->res_job.JobType) {
814                      i = 0;
815                      break;
816                   }
817                }
818                if (i != 0) {
819                   Emsg1(M_ABORT, 0, "Inappropriate level specified in Job resource %s\n", 
820                      res_all.res_dir.hdr.name);
821                }
822             }
823             break;
824          case R_CLIENT:
825             if ((res = (URES *)GetResWithName(R_CLIENT, res_all.res_client.hdr.name)) == NULL) {
826                Emsg1(M_ABORT, 0, "Cannot find Client resource %s\n", res_all.res_client.hdr.name);
827             }
828             res->res_client.catalog = res_all.res_client.catalog;
829             break;
830          case R_SCHEDULE:
831             /* Schedule is a bit different in that it contains a RUN record
832              * chain which isn't a "named" resource. This chain was linked
833              * in by run_conf.c during pass 2, so here we jam the pointer 
834              * into the Schedule resource.                         
835              */
836             if ((res = (URES *)GetResWithName(R_SCHEDULE, res_all.res_client.hdr.name)) == NULL) {
837                Emsg1(M_ABORT, 0, "Cannot find Schedule resource %s\n", res_all.res_client.hdr.name);
838             }
839             res->res_sch.run = res_all.res_sch.run;
840             break;
841          default:
842             Emsg1(M_ERROR, 0, "Unknown resource type %d\n", type);
843             error = 1;
844             break;
845       }
846       /* Note, the resource name was already saved during pass 1,
847        * so here, we can just release it.
848        */
849       if (res_all.res_dir.hdr.name) {
850          free(res_all.res_dir.hdr.name);
851          res_all.res_dir.hdr.name = NULL;
852       }
853       if (res_all.res_dir.hdr.desc) {
854          free(res_all.res_dir.hdr.desc);
855          res_all.res_dir.hdr.desc = NULL;
856       }
857       return;
858    }
859
860    /* The following code is only executed for pass 1 */
861    switch (type) {
862       case R_DIRECTOR:
863          size = sizeof(DIRRES);
864          break;
865       case R_CLIENT:
866          size =sizeof(CLIENT);
867          break;
868       case R_STORAGE:
869          size = sizeof(STORE); 
870          break;
871       case R_CATALOG:
872          size = sizeof(CAT);
873          break;
874       case R_JOB:
875          size = sizeof(JOB);
876          break;
877       case R_FILESET:
878          size = sizeof(FILESET);
879          break;
880       case R_SCHEDULE:
881          size = sizeof(SCHED);
882          break;
883       case R_GROUP:
884          size = sizeof(GROUP);
885          break;
886       case R_POOL:
887          size = sizeof(POOL);
888          break;
889       case R_MSGS:
890          size = sizeof(MSGS);
891          break;
892       default:
893          printf("Unknown resource type %d\n", type);
894          error = 1;
895          break;
896    }
897    /* Common */
898    if (!error) {
899       res = (URES *)malloc(size);
900       memcpy(res, &res_all, size);
901       if (!resources[rindex].res_head) {
902          resources[rindex].res_head = (RES *)res; /* store first entry */
903       } else {
904          RES *next;
905          /* Add new res to end of chain */
906          for (next=resources[rindex].res_head; next->next; next=next->next)
907             { }
908          next->next = (RES *)res;
909          Dmsg2(90, "Inserting %s res: %s\n", res_to_str(type),
910                res->res_dir.hdr.name);
911       }
912    }
913 }
914
915 /* 
916  * Store JobType (backup, verify, restore)
917  *
918  */
919 static void store_jobtype(LEX *lc, struct res_items *item, int index, int pass)
920 {
921    int token, i;   
922
923    token = lex_get_token(lc, T_NAME);
924    /* Store the type both pass 1 and pass 2 */
925    for (i=0; jobtypes[i].type_name; i++) {
926       if (strcasecmp(lc->str, jobtypes[i].type_name) == 0) {
927          ((JOB *)(item->value))->JobType = jobtypes[i].job_type;
928          i = 0;
929          break;
930       }
931    }
932    if (i != 0) {
933       scan_err1(lc, "Expected a Job Type keyword, got: %s", lc->str);
934    }
935    scan_to_eol(lc);
936    set_bit(index, res_all.hdr.item_present);
937 }
938
939 /* 
940  * Store Job Level (Full, Incremental, ...)
941  *
942  */
943 static void store_level(LEX *lc, struct res_items *item, int index, int pass)
944 {
945    int token, i;
946
947    token = lex_get_token(lc, T_NAME);
948    /* Store the level pass 2 so that type is defined */
949    for (i=0; joblevels[i].level_name; i++) {
950       if (strcasecmp(lc->str, joblevels[i].level_name) == 0) {
951          ((JOB *)(item->value))->level = joblevels[i].level;
952          i = 0;
953          break;
954       }
955    }
956    if (i != 0) {
957       scan_err1(lc, "Expected a Job Level keyword, got: %s", lc->str);
958    }
959    scan_to_eol(lc);
960    set_bit(index, res_all.hdr.item_present);
961 }
962
963
964
965 /* 
966  * Store backup/verify info for Job record 
967  *
968  * Note, this code is used for both BACKUP and VERIFY jobs
969  *
970  *    Backup = Client=<client-name> FileSet=<FileSet-name> Level=<level>
971  */
972 static void store_backup(LEX *lc, struct res_items *item, int index, int pass)
973 {
974    int token, i;
975    RES *res;
976    int options = lc->options;
977
978    lc->options |= LOPT_NO_IDENT;      /* make spaces significant */
979
980    
981    ((JOB *)(item->value))->JobType = item->code;
982    while ((token = lex_get_token(lc, T_ALL)) != T_EOL) {
983       int found;
984
985       Dmsg1(150, "store_backup got token=%s\n", lex_tok_to_str(token));
986       
987       if (token != T_IDENTIFIER && token != T_UNQUOTED_STRING && token != T_QUOTED_STRING) {
988          scan_err1(lc, "Expected a backup/verify keyword, got: %s", lc->str);
989       }
990       Dmsg1(190, "Got keyword: %s\n", lc->str);
991       found = FALSE;
992       for (i=0; BakVerFields[i].name; i++) {
993          if (strcasecmp(lc->str, BakVerFields[i].name) == 0) {
994             found = TRUE;
995             if (lex_get_token(lc, T_ALL) != T_EQUALS) {
996                scan_err1(lc, "Expected an equals, got: %s", lc->str);
997             }
998             token = lex_get_token(lc, T_NAME);
999             Dmsg1(190, "Got value: %s\n", lc->str);
1000             switch (BakVerFields[i].token) {
1001                case 'C':
1002                   /* Find Client Resource */
1003                   if (pass == 2) {
1004                      res = GetResWithName(R_CLIENT, lc->str);
1005                      if (res == NULL) {
1006                         scan_err1(lc, "Could not find specified Client Resource: %s",
1007                                    lc->str);
1008                      }
1009                      res_all.res_job.client = (CLIENT *)res;
1010                   }
1011                   break;
1012                case 'F':
1013                   /* Find FileSet Resource */
1014                   if (pass == 2) {
1015                      res = GetResWithName(R_FILESET, lc->str);
1016                      if (res == NULL) {
1017                         scan_err1(lc, "Could not find specified FileSet Resource: %s\n",
1018                                     lc->str);
1019                      }
1020                      res_all.res_job.fs = (FILESET *)res;
1021                   }
1022                   break;
1023                case 'L':
1024                   /* Get level */
1025                   for (i=0; joblevels[i].level_name; i++) {
1026                      if (joblevels[i].job_type == item->code && 
1027                           strcasecmp(lc->str, joblevels[i].level_name) == 0) {
1028                         ((JOB *)(item->value))->level = joblevels[i].level;
1029                         i = 0;
1030                         break;
1031                      }
1032                   }
1033                   if (i != 0) {
1034                      scan_err1(lc, "Expected a Job Level keyword, got: %s", lc->str);
1035                   }
1036                   break;
1037             } /* end switch */
1038             break;
1039          } /* end if strcmp() */
1040       } /* end for */
1041       if (!found) {
1042          scan_err1(lc, "%s not a valid Backup/verify keyword", lc->str);
1043       }
1044    } /* end while */
1045    lc->options = options;             /* reset original options */
1046    set_bit(index, res_all.hdr.item_present);
1047 }
1048
1049 /* 
1050  * Store restore info for Job record 
1051  *
1052  *    Restore = JobId=<job-id> Where=<root-directory> Replace=<options> Bootstrap=<file>
1053  *
1054  */
1055 static void store_restore(LEX *lc, struct res_items *item, int index, int pass)
1056 {
1057    int token, i;
1058    RES *res;
1059    int options = lc->options;
1060
1061    lc->options |= LOPT_NO_IDENT;      /* make spaces significant */
1062
1063    Dmsg0(190, "Enter store_restore()\n");
1064    
1065    ((JOB *)(item->value))->JobType = item->code;
1066    while ((token = lex_get_token(lc, T_ALL)) != T_EOL) {
1067       int found; 
1068
1069       if (token != T_IDENTIFIER && token != T_UNQUOTED_STRING && token != T_QUOTED_STRING) {
1070          scan_err1(lc, "expected a name, got: %s", lc->str);
1071       }
1072       found = FALSE;
1073       for (i=0; RestoreFields[i].name; i++) {
1074          Dmsg1(190, "Restore kw=%s\n", lc->str);
1075          if (strcasecmp(lc->str, RestoreFields[i].name) == 0) {
1076             found = TRUE;
1077             if (lex_get_token(lc, T_ALL) != T_EQUALS) {
1078                scan_err1(lc, "Expected an equals, got: %s", lc->str);
1079             }
1080             token = lex_get_token(lc, T_ALL);
1081             Dmsg1(190, "Restore value=%s\n", lc->str);
1082             switch (RestoreFields[i].token) {
1083                case 'B':
1084                   /* Bootstrap */
1085                   if (token != T_IDENTIFIER && token != T_UNQUOTED_STRING && token != T_QUOTED_STRING) {
1086                      scan_err1(lc, "Expected a Restore bootstrap file, got: %s", lc->str);
1087                   }
1088                   if (pass == 1) {
1089                      res_all.res_job.RestoreBootstrap = bstrdup(lc->str);
1090                   }
1091                   break;
1092                case 'C':
1093                   /* Find Client Resource */
1094                   if (pass == 2) {
1095                      res = GetResWithName(R_CLIENT, lc->str);
1096                      if (res == NULL) {
1097                         scan_err1(lc, "Could not find specified Client Resource: %s",
1098                                    lc->str);
1099                      }
1100                      res_all.res_job.client = (CLIENT *)res;
1101                   }
1102                   break;
1103                case 'F':
1104                   /* Find FileSet Resource */
1105                   if (pass == 2) {
1106                      res = GetResWithName(R_FILESET, lc->str);
1107                      if (res == NULL) {
1108                         scan_err1(lc, "Could not find specified FileSet Resource: %s\n",
1109                                     lc->str);
1110                      }
1111                      res_all.res_job.fs = (FILESET *)res;
1112                   }
1113                   break;
1114                case 'J':
1115                   /* JobId */
1116                   if (token != T_NUMBER) {
1117                      scan_err1(lc, "expected an integer number, got: %s", lc->str);
1118                   }
1119                   errno = 0;
1120                   res_all.res_job.RestoreJobId = strtol(lc->str, NULL, 0);
1121                   Dmsg1(190, "RestorJobId=%d\n", res_all.res_job.RestoreJobId);
1122                   if (errno != 0) {
1123                      scan_err1(lc, "expected an integer number, got: %s", lc->str);
1124                   }
1125                   break;
1126                case 'W':
1127                   /* Where */
1128                   if (token != T_IDENTIFIER && token != T_UNQUOTED_STRING && token != T_QUOTED_STRING) {
1129                      scan_err1(lc, "Expected a Restore root directory, got: %s", lc->str);
1130                   }
1131                   if (pass == 1) {
1132                      res_all.res_job.RestoreWhere = bstrdup(lc->str);
1133                   }
1134                   break;
1135                case 'R':
1136                   /* Replacement options */
1137                   if (token != T_IDENTIFIER && token != T_UNQUOTED_STRING && token != T_QUOTED_STRING) {
1138                      scan_err1(lc, "Expected a keyword name, got: %s", lc->str);
1139                   }
1140                   /* Fix to scan Replacement options */
1141                   for (i=0; ReplaceOptions[i].name; i++) {
1142                      if (strcasecmp(lc->str, ReplaceOptions[i].name) == 0) {
1143                          ((JOB *)(item->value))->RestoreOptions = ReplaceOptions[i].token;
1144                         i = 0;
1145                         break;
1146                      }
1147                   }
1148                   if (i != 0) {
1149                      scan_err1(lc, "Expected a Restore replacement option, got: %s", lc->str);
1150                   }
1151                   break;
1152             } /* end switch */
1153             break;
1154          } /* end if strcmp() */
1155       } /* end for */
1156       if (!found) {
1157          scan_err1(lc, "%s not a valid Restore keyword", lc->str);
1158       }
1159    } /* end while */
1160    lc->options = options;             /* reset original options */
1161    set_bit(index, res_all.hdr.item_present);
1162 }
1163
1164
1165
1166 /* 
1167  * Scan for FileSet options (keyword=option) is converted into one or
1168  *  two characters. Verifyopts=xxxx is Vxxxx:
1169  */
1170 static char *scan_fs_options(LEX *lc, int keyword)
1171 {
1172    int token, i;
1173    static char opts[100];
1174    char option[3];
1175
1176    option[0] = 0;                     /* default option = none */
1177    opts[0] = option[2] = 0;           /* terminate options */
1178    for (;;) {
1179       token = lex_get_token(lc, T_NAME);             /* expect at least one option */       
1180       if (keyword == FS_KW_VERIFY) { /* special case */
1181          /* ***FIXME**** ensure these are in permitted set */
1182          strcpy(option, "V");         /* indicate Verify */
1183          strcat(option, lc->str);
1184          strcat(option, ":");         /* terminate it */
1185       } else {
1186          for (i=0; FS_options[i].name; i++) {
1187             if (strcasecmp(lc->str, FS_options[i].name) == 0 && FS_options[i].keyword == keyword) {
1188                option[0] = FS_options[i].option[0];
1189                option[1] = FS_options[i].option[1];
1190                i = 0;
1191                break;
1192             }
1193          }
1194          if (i != 0) {
1195             scan_err1(lc, "Expected a FileSet option keyword, got: %s", lc->str);
1196          }
1197       }
1198       strcat(opts, option);
1199
1200       /* check if more options are specified */
1201       if (lc->ch != ',') {
1202          break;                       /* no, get out */
1203       }
1204       token = lex_get_token(lc, T_ALL);      /* yes, eat comma */
1205    }
1206
1207    return opts;
1208 }
1209
1210
1211 /* Store FileSet Include/Exclude info */
1212 static void store_inc(LEX *lc, struct res_items *item, int index, int pass)
1213 {
1214    int token, i;
1215    int options = lc->options;
1216    int keyword;
1217    char *fname;
1218    char inc_opts[100];
1219    int inc_opts_len;
1220
1221    lc->options |= LOPT_NO_IDENT;      /* make spaces significant */
1222
1223    /* Get include options */
1224    strcpy(inc_opts, "0");             /* set no options */
1225    while ((token=lex_get_token(lc, T_ALL)) != T_BOB) {
1226       keyword = FS_KW_NONE;
1227       for (i=0; FS_option_kw[i].name; i++) {
1228          if (strcasecmp(lc->str, FS_option_kw[i].name) == 0) {
1229             keyword = FS_option_kw[i].token;
1230             break;
1231          }
1232       }
1233       if (keyword == FS_KW_NONE) {
1234          scan_err1(lc, "Expected a FileSet keyword, got: %s", lc->str);
1235       }
1236       /* Option keyword should be following by = <option> */
1237       if ((token=lex_get_token(lc, T_ALL)) != T_EQUALS) {
1238          scan_err1(lc, "expected an = following keyword, got: %s", lc->str);
1239       }
1240       strcat(inc_opts, scan_fs_options(lc, keyword));
1241       if (token == T_BOB) {
1242          break;
1243       }
1244    }
1245    strcat(inc_opts, " ");             /* add field separator */
1246    inc_opts_len = strlen(inc_opts);
1247
1248
1249    if (pass == 1) {
1250       if (!res_all.res_fs.have_MD5) {
1251          MD5Init(&res_all.res_fs.md5c);
1252          res_all.res_fs.have_MD5 = TRUE;
1253       }
1254       /* Pickup include/exclude names. Note, they are stored as
1255        * XYZ fname
1256        * where XYZ are the include/exclude options for the FileSet
1257        *     a "0 " (zero) indicates no options,
1258        * and fname is the file/directory name given
1259        */
1260       while ((token = lex_get_token(lc, T_ALL)) != T_EOB) {
1261          switch (token) {
1262             case T_COMMA:
1263             case T_EOL:
1264                continue;
1265
1266             case T_IDENTIFIER:
1267             case T_UNQUOTED_STRING:
1268             case T_QUOTED_STRING:
1269                fname = (char *) malloc(lc->str_len + inc_opts_len + 1);
1270                strcpy(fname, inc_opts);
1271                strcat(fname, lc->str);
1272                if (res_all.res_fs.have_MD5) {
1273                   MD5Update(&res_all.res_fs.md5c, (unsigned char *) fname, inc_opts_len + lc->str_len);
1274                }
1275                if (item->code == 0) { /* include */
1276                   if (res_all.res_fs.num_includes == res_all.res_fs.include_size) {
1277                      res_all.res_fs.include_size += 10;
1278                      if (res_all.res_fs.include_array == NULL) {
1279                         res_all.res_fs.include_array = (char **) malloc(sizeof(char *) * res_all.res_fs.include_size);
1280                      } else {
1281                         res_all.res_fs.include_array = (char **) realloc(res_all.res_fs.include_array,
1282                            sizeof(char *) * res_all.res_fs.include_size);
1283                      }
1284                   }
1285                   res_all.res_fs.include_array[res_all.res_fs.num_includes++] =    
1286                      fname;
1287                } else {                /* exclude */
1288                   if (res_all.res_fs.num_excludes == res_all.res_fs.exclude_size) {
1289                      res_all.res_fs.exclude_size += 10;
1290                      if (res_all.res_fs.exclude_array == NULL) {
1291                         res_all.res_fs.exclude_array = (char **) malloc(sizeof(char *) * res_all.res_fs.exclude_size);
1292                      } else {
1293                         res_all.res_fs.exclude_array = (char **) realloc(res_all.res_fs.exclude_array,
1294                            sizeof(char *) * res_all.res_fs.exclude_size);
1295                      }
1296                   }
1297                   res_all.res_fs.exclude_array[res_all.res_fs.num_excludes++] =    
1298                      fname;
1299                }
1300                break;
1301             default:
1302                scan_err1(lc, "Expected a filename, got: %s", lc->str);
1303          }                                 
1304       }
1305    } else { /* pass 2 */
1306       while (lex_get_token(lc, T_ALL) != T_EOB) 
1307          {}
1308    }
1309    scan_to_eol(lc);
1310    lc->options = options;
1311    set_bit(index, res_all.hdr.item_present);
1312 }