1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8 
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  * Bacula conf to json
21  *
22  *  Kern Sibbald, MMXII
23  *
24  */
25 
26 #include "bacula.h"
27 #include "stored.h"
28 
29 /* Imported functions */
30 extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
31 
32 /* Imported variables */
33 #if defined(_MSC_VER)
34 extern "C" { // work around visual compiler mangling variables
35    extern URES res_all;
36 }
37 #else
38 extern URES res_all;
39 #endif
40 extern s_kw msg_types[];
41 extern s_kw dev_types[];
42 extern s_kw tapelabels[];
43 extern s_kw cloud_drivers[];
44 extern s_kw dedup_drivers[];
45 extern s_kw trunc_opts[];
46 extern s_kw upload_opts[];
47 extern s_kw proto_opts[];
48 extern s_kw uri_opts[];
49 extern s_kw restore_prio_opts[];
50 
51 extern RES_TABLE resources[];
52 
53 typedef struct
54 {
55    bool do_list;
56    bool do_one;
57    bool do_only_data;
58    char *resource_type;
59    char *resource_name;
60    regex_t directive_reg;
61 } display_filter;
62 
63 /* Forward referenced functions */
64 void terminate_stored(int sig);
65 static int check_resources();
66 static void dump_json(display_filter *filter);
67 
68 #define CONFIG_FILE "bacula-sd.conf"  /* Default config file */
69 
70 /* Global variables exported */
71 STORES *me = NULL;                    /* our Global resource */
72 
73 char *configfile = NULL;
74 
75 /* Global static variables */
76 static CONFIG *config;
77 static void sendit(void *sock, const char *fmt, ...);
78 
usage()79 static void usage()
80 {
81    fprintf(stderr, _(
82 PROG_COPYRIGHT
83 "\n%sVersion: %s (%s)\n\n"
84 "Usage: bsdjson [options] [config_file]\n"
85 "        -r <res>    get resource type <res>\n"
86 "        -n <name>   get resource <name>\n"
87 "        -l <dirs>   get only directives matching dirs (use with -r)\n"
88 "        -D          get only data\n"
89 "        -c <file>   use <file> as configuration file\n"
90 "        -d <nn>     set debug level to <nn>\n"
91 "        -dt         print timestamp in debug output\n"
92 "        -t          test - read config and exit\n"
93 "        -v          verbose user messages\n"
94 "        -?          print this message.\n"
95 "\n"), 2012, BDEMO, VERSION, BDATE);
96 
97    exit(1);
98 }
99 
100 /*********************************************************************
101  *
102  *  Main Bacula Unix Storage Daemon
103  *
104  */
105 #if defined(HAVE_WIN32)
106 #define main BaculaMain
107 #endif
108 
main(int argc,char * argv[])109 int main (int argc, char *argv[])
110 {
111    int ch;
112    bool test_config = false;
113    display_filter filter;
114    memset(&filter, 0, sizeof(filter));
115 
116    setlocale(LC_ALL, "");
117    bindtextdomain("bacula", LOCALEDIR);
118    textdomain("bacula");
119 
120    my_name_is(argc, argv, "bacula-sd");
121    init_msg(NULL, NULL);
122 
123    while ((ch = getopt(argc, argv, "Dc:d:tv?r:n:l:")) != -1) {
124       switch (ch) {
125       case 'D':
126          filter.do_only_data = true;
127          break;
128 
129       case 'l':
130          filter.do_list = true;
131          /* Might use something like -l '^(Name|Description)$' */
132          filter.do_list = true;
133          if (regcomp(&filter.directive_reg, optarg, REG_EXTENDED) != 0) {
134             Jmsg((JCR *)NULL, M_ERROR_TERM, 0,
135                  _("Please use valid -l argument: %s\n"), optarg);
136          }
137          break;
138 
139       case 'r':
140          filter.resource_type = optarg;
141          break;
142 
143       case 'n':
144          filter.resource_name = optarg;
145          break;
146 
147       case 'c':                    /* configuration file */
148          if (configfile != NULL) {
149             free(configfile);
150          }
151          configfile = bstrdup(optarg);
152          break;
153 
154       case 'd':                    /* debug level */
155          if (*optarg == 't') {
156             dbg_timestamp = true;
157          } else {
158             debug_level = atoi(optarg);
159             if (debug_level <= 0) {
160                debug_level = 1;
161             }
162          }
163          break;
164 
165       case 't':
166          test_config = true;
167          break;
168 
169       case 'v':                    /* verbose */
170          verbose++;
171          break;
172 
173       case '?':
174       default:
175          usage();
176          break;
177       }
178    }
179    argc -= optind;
180    argv += optind;
181 
182    if (argc) {
183       if (configfile != NULL) {
184          free(configfile);
185       }
186       configfile = bstrdup(*argv);
187       argc--;
188       argv++;
189    }
190 
191    if (argc) {
192       usage();
193    }
194 
195    if (filter.do_list && !filter.resource_type) {
196       usage();
197    }
198 
199    if (filter.resource_type && filter.resource_name) {
200       filter.do_one = true;
201    }
202 
203    if (configfile == NULL || configfile[0] == 0) {
204       configfile = bstrdup(CONFIG_FILE);
205    }
206 
207    if (test_config && verbose > 0) {
208       char buf[1024];
209       find_config_file(configfile, buf, sizeof(buf));
210       sendit(NULL, "config_file=%s\n", buf);
211    }
212 
213    config = New(CONFIG());
214    config->encode_password(false);
215    parse_sd_config(config, configfile, M_ERROR_TERM);
216 
217    if (!check_resources()) {
218       Jmsg((JCR *)NULL, M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
219    }
220 
221    if (test_config) {
222       terminate_stored(0);
223    }
224 
225    my_name_is(0, (char **)NULL, me->hdr.name);     /* Set our real name */
226 
227    dump_json(&filter);
228 
229    if (filter.do_list) {
230       regfree(&filter.directive_reg);
231    }
232 
233    terminate_stored(0);
234 }
235 
display_devtype(HPKT & hpkt)236 static void display_devtype(HPKT &hpkt)
237 {
238    int i;
239    for (i=0; dev_types[i].name; i++) {
240       if (*(int32_t *)(hpkt.ritem->value) == dev_types[i].token) {
241          hpkt.sendit(hpkt, "\n    \"%s\": \"%s\"", hpkt.ritem->name,
242                 dev_types[i].name);
243          return;
244       }
245    }
246 }
247 
display_label(HPKT & hpkt)248 static void display_label(HPKT &hpkt)
249 {
250    int i;
251    for (i=0; tapelabels[i].name; i++) {
252       if (*(int32_t *)(hpkt.ritem->value) == tapelabels[i].token) {
253          hpkt.sendit(hpkt, "\n      \"%s\": \"%s\"", hpkt.ritem->name,
254                 tapelabels[i].name);
255          return;
256       }
257    }
258 }
259 
display_cloud_driver(HPKT & hpkt)260 static void display_cloud_driver(HPKT &hpkt)
261 {
262    int i;
263    for (i=0; cloud_drivers[i].name; i++) {
264       if (*(int32_t *)(hpkt.ritem->value) == cloud_drivers[i].token) {
265          hpkt.sendit(hpkt, "\n    \"%s\": \"%s\"", hpkt.ritem->name,
266                 cloud_drivers[i].name);
267          return;
268       }
269    }
270 }
271 
272 #ifdef SD_DEDUP_SUPPORT
display_dedup_driver(HPKT & hpkt)273 static void display_dedup_driver(HPKT &hpkt)
274 {
275    int i;
276    for (i=0; dedup_drivers[i].name; i++) {
277       if (*(int32_t *)(hpkt.ritem->value) == dedup_drivers[i].token) {
278          hpkt.sendit(hpkt, "\n      \"%s\": \"%s\"", hpkt.ritem->name,
279                 dedup_drivers[i].name);
280          return;
281       }
282    }
283 }
284 #endif
285 
display_protocol(HPKT & hpkt)286 static void display_protocol(HPKT &hpkt)
287 {
288    int i;
289    for (i=0; proto_opts[i].name; i++) {
290       if (*(int32_t *)(hpkt.ritem->value) == proto_opts[i].token) {
291          hpkt.sendit(hpkt, "\n    \"%s\": \"%s\"", hpkt.ritem->name,
292                 proto_opts[i].name);
293          return;
294       }
295    }
296 }
297 
display_truncate_cache(HPKT & hpkt)298 static void display_truncate_cache(HPKT &hpkt)
299 {
300    int i;
301    for (i=0; trunc_opts[i].name; i++) {
302       if (*(int32_t *)(hpkt.ritem->value) == trunc_opts[i].token) {
303          hpkt.sendit(hpkt, "\n    \"%s\": \"%s\"", hpkt.ritem->name,
304                 trunc_opts[i].name);
305          return;
306       }
307    }
308 }
309 
display_uri_style(HPKT & hpkt)310 static void display_uri_style(HPKT &hpkt)
311 {
312    int i;
313    for (i=0; uri_opts[i].name; i++) {
314       if (*(int32_t *)(hpkt.ritem->value) == uri_opts[i].token) {
315          hpkt.sendit(hpkt, "\n    \"%s\": \"%s\"", hpkt.ritem->name,
316                 uri_opts[i].name);
317          return;
318       }
319    }
320 }
321 
display_upload(HPKT & hpkt)322 static void display_upload(HPKT &hpkt)
323 {
324    int i;
325    for (i=0; upload_opts[i].name; i++) {
326       if (*(int32_t *)(hpkt.ritem->value) == upload_opts[i].token) {
327          hpkt.sendit(hpkt, "\n    \"%s\": \"%s\"", hpkt.ritem->name,
328                 upload_opts[i].name);
329          return;
330       }
331    }
332 }
333 
334 
display_transfer_priority(HPKT & hpkt)335 static void display_transfer_priority(HPKT &hpkt)
336 {
337    int i;
338    for (i=0; restore_prio_opts[i].name; i++) {
339       if (*(int32_t *)(hpkt.ritem->value) == restore_prio_opts[i].token) {
340          hpkt.sendit(hpkt, "\n    \"%s\": \"%s\"", hpkt.ritem->name,
341                 restore_prio_opts[i].name);
342          return;
343       }
344    }
345 }
346 /*
347  * Dump out all resources in json format.
348  * Note!!!! This routine must be in this file rather
349  *  than in src/lib/parser_conf.c otherwise the pointers
350  *  will be all messed up.
351  */
dump_json(display_filter * filter)352 static void dump_json(display_filter *filter)
353 {
354    int resinx, item, directives, first_directive;
355    bool first_res;
356    RES_ITEM *items;
357    RES *res;
358    HPKT hpkt;
359    regmatch_t pmatch[32];
360    STORES *me = (STORES *)GetNextRes(R_STORAGE, NULL);
361 
362    if (init_crypto() != 0) {
363       Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
364    }
365 
366    init_hpkt(hpkt);
367 
368    if (filter->do_only_data) {
369       hpkt.sendit(hpkt, "[");
370 
371    /* List resources and directives */
372    /* { "aa": { "Name": "aa",.. }, "bb": { "Name": "bb", ... }
373     * or print a single item
374     */
375    } else if (filter->do_one || filter->do_list) {
376       hpkt.sendit(hpkt, "{");
377 
378    } else {
379    /* [ { "Device": { "Name": "aa",.. } }, { "Director": { "Name": "bb", ... } } ]*/
380       hpkt.sendit(hpkt, "[");
381    }
382 
383    first_res = true;
384    /* Loop over all resource types */
385    for (resinx=0; resources[resinx].name; resinx++) {
386       if (!resources[resinx].items) {
387           continue;           /* skip dummy entries */
388       }
389 
390       /* Skip this resource type */
391       if (filter->resource_type &&
392           strcasecmp(filter->resource_type, resources[resinx].name) != 0) {
393          continue;
394       }
395 
396       directives = 0;
397       /* Loop over all resources of this type */
398       foreach_rblist(res, res_head[resinx]->res_list) {
399          hpkt.res = res;
400          items = resources[resinx].items;
401          if (!items) {
402             continue;
403          }
404 
405          /* Copy the resource into res_all */
406          memcpy(&res_all, res, sizeof(res_all));
407 
408          if (filter->resource_name) {
409             bool skip=true;
410             /* The Name should be at the first place, so this is not a real loop */
411             for (item=0; items[item].name; item++) {
412                if (strcasecmp(items[item].name, "Name") == 0) {
413                   if (strcasecmp(*(items[item].value), filter->resource_name) == 0) {
414                      skip = false;
415                   }
416                   break;
417                }
418             }
419             if (skip) {         /* The name doesn't match, so skip it */
420                continue;
421             }
422          }
423 
424          if (first_res) {
425             hpkt.sendit(hpkt, "\n");
426          } else {
427             hpkt.sendit(hpkt, ",\n");
428          }
429 
430          if (filter->do_only_data) {
431             hpkt.sendit(hpkt, " {");
432 
433          } else if (filter->do_one) {
434             /* Nothing to print */
435 
436          /* When sending the list, the form is:
437           *  { aa: { Name: aa, Description: aadesc...}, bb: { Name: bb
438           */
439          } else if (filter->do_list) {
440             /* Search and display Name, should be the first item */
441             for (item=0; items[item].name; item++) {
442                if (strcmp(items[item].name, "Name") == 0) {
443                   hpkt.sendit(hpkt, "%s: {\n", quote_string(hpkt.edbuf2, *items[item].value));
444                   break;
445                }
446             }
447          } else {
448             /* Begin new resource */
449             hpkt.sendit(hpkt, "{\n  \"%s\": {", resources[resinx].name);
450          }
451 
452          first_res = false;
453          first_directive = 0;
454          directives = 0;
455          for (item=0; items[item].name; item++) {
456             /* Check user argument -l */
457             if (filter->do_list &&
458                 regexec(&filter->directive_reg,
459                         items[item].name, 32, pmatch, 0) != 0)
460             {
461                continue;
462             }
463 
464             hpkt.ritem = &items[item];
465             if (bit_is_set(item, res_all.hdr.item_present)) {
466                if (first_directive++ > 0) printf(",");
467 
468                /* 1: found, 0: not found, -1 found but empty */
469                int ret = display_global_item(hpkt);
470                if (ret == -1) {
471                   /* Do not print a comma after this empty directive */
472                   first_directive = 0;
473                } else if (ret == 1) {
474                   /* Fall-through wanted */
475 
476                } else if (items[item].handler == store_maxblocksize) {
477                   display_int32_pair(hpkt);
478                } else if (items[item].handler == store_devtype) {
479                   display_devtype(hpkt);
480                } else if (items[item].handler == store_label) {
481                   display_label(hpkt);
482                } else if (items[item].handler == store_cloud_driver) {
483                   display_cloud_driver(hpkt);
484 #ifdef SD_DEDUP_SUPPORT
485                } else if (items[item].handler == store_dedup_driver) {
486                   display_dedup_driver(hpkt);
487 #endif
488                } else if (items[item].handler == store_protocol) {
489                   display_protocol(hpkt);
490                } else if (items[item].handler == store_uri_style) {
491                   display_uri_style(hpkt);
492                } else if (items[item].handler == store_truncate) {
493                   display_truncate_cache(hpkt);
494                } else if (items[item].handler == store_upload) {
495                   display_upload(hpkt);
496                } else if (items[item].handler == store_coll_type) {
497                   display_collector_types(hpkt);
498                } else if (items[item].handler == store_transfer_priority) {
499                   display_transfer_priority(hpkt);
500                } else {
501                   printf("\n      \"%s\": \"null\"", items[item].name);
502                }
503                directives++;
504             } else { /* end if is present */
505                /* For some directive, the bitmap is not set (like addresses) */
506                if (me && strcmp(resources[resinx].name, "Storage") == 0) {
507                   if (strcmp(items[item].name, "SdPort") == 0) {
508                      if (get_first_port_host_order(me->sdaddrs) != items[item].default_value) {
509                         if (first_directive++ > 0) hpkt.sendit(hpkt, ",");
510                         hpkt.sendit(hpkt, "\n    \"SdPort\": %d",
511                            get_first_port_host_order(me->sdaddrs));
512                      }
513                   } else if (me && strcmp(items[item].name, "SdAddress") == 0) {
514                      char buf[500];
515                      get_first_address(me->sdaddrs, buf, sizeof(buf));
516                      if (strcmp(buf, "0.0.0.0") != 0) {
517                         if (first_directive++ > 0) hpkt.sendit(hpkt, ",");
518                         hpkt.sendit(hpkt, "\n    \"SdAddress\": \"%s\"", buf);
519                      }
520                   }
521                }
522             }
523             if (items[item].flags & ITEM_LAST) {
524                display_last(hpkt);    /* If last bit set always call to cleanup */
525             }
526          }
527 
528          /* { "aa": { "Name": "aa",.. }, "bb": { "Name": "bb", ... } */
529          if (filter->do_only_data || filter->do_list) {
530             hpkt.sendit(hpkt, "\n }"); /* Finish the Resource with a single } */
531 
532          } else {
533             if (filter->do_one) {
534                /* don't print anything */
535 
536             } else if (first_directive > 0) {
537                hpkt.sendit(hpkt, "\n  }\n}");  /* end of resource */
538 
539             } else {
540                hpkt.sendit(hpkt, "}\n }");
541             }
542          }
543 
544       } /* End loop over all resources of this type */
545    } /* End loop all resource types */
546 
547    if (filter->do_only_data) {
548       hpkt.sendit(hpkt, "\n]\n");
549 
550    /* In list context, we are dealing with a hash */
551    } else if (filter->do_one || filter->do_list) {
552       hpkt.sendit(hpkt, "\n}\n");
553 
554    } else {
555       hpkt.sendit(hpkt, "\n]\n");
556    }
557    term_hpkt(hpkt);
558 }
559 
560 
561 /* Check Configuration file for necessary info */
check_resources()562 static int check_resources()
563 {
564    bool OK = true;
565    bool tls_needed;
566    AUTOCHANGER *changer;
567    DEVRES *device;
568 
569    me = (STORES *)GetNextRes(R_STORAGE, NULL);
570    if (!me) {
571       Jmsg1(NULL, M_ERROR, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
572          configfile);
573       OK = false;
574    }
575 
576    if (GetNextRes(R_STORAGE, (RES *)me) != NULL) {
577       Jmsg1(NULL, M_ERROR, 0, _("Only one Storage resource permitted in %s\n"),
578          configfile);
579       OK = false;
580    }
581    if (GetNextRes(R_DIRECTOR, NULL) == NULL) {
582       Jmsg1(NULL, M_ERROR, 0, _("No Director resource defined in %s. Cannot continue.\n"),
583          configfile);
584       OK = false;
585    }
586    if (GetNextRes(R_DEVICE, NULL) == NULL){
587       Jmsg1(NULL, M_ERROR, 0, _("No Device resource defined in %s. Cannot continue.\n"),
588            configfile);
589       OK = false;
590    }
591 
592    if (!me->messages) {
593       me->messages = (MSGS *)GetNextRes(R_MSGS, NULL);
594       if (!me->messages) {
595          Jmsg1(NULL, M_ERROR, 0, _("No Messages resource defined in %s. Cannot continue.\n"),
596             configfile);
597          OK = false;
598       }
599    }
600 
601    if (!me->working_directory) {
602       Jmsg1(NULL, M_ERROR, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
603          configfile);
604       OK = false;
605    }
606 
607    DIRRES *director;
608    STORES *store;
609    foreach_res(store, R_STORAGE) {
610       /* tls_require implies tls_enable */
611       if (store->tls_require) {
612          if (have_tls) {
613             if (store->tls_certfile || store->tls_keyfile) {
614                store->tls_enable = true;
615             }
616          } else {
617             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
618             OK = false;
619             continue;
620          }
621       }
622 
623       tls_needed = store->tls_enable || store->tls_authenticate;
624 
625       if (!store->tls_certfile && tls_needed) {
626          Jmsg(NULL, M_FATAL, 0, _("\"TLS Certificate\" file not defined for Storage \"%s\" in %s.\n"),
627               store->hdr.name, configfile);
628          OK = false;
629       }
630 
631       if (!store->tls_keyfile && tls_needed) {
632          Jmsg(NULL, M_FATAL, 0, _("\"TLS Key\" file not defined for Storage \"%s\" in %s.\n"),
633               store->hdr.name, configfile);
634          OK = false;
635       }
636 
637       if ((!store->tls_ca_certfile && !store->tls_ca_certdir) && tls_needed && store->tls_verify_peer) {
638          Jmsg(NULL, M_FATAL, 0, _("Neither \"TLS CA Certificate\""
639               " or \"TLS CA Certificate Dir\" are defined for Storage \"%s\" in %s."
640               " At least one CA certificate store is required"
641               " when using \"TLS Verify Peer\".\n"),
642               store->hdr.name, configfile);
643          OK = false;
644       }
645    }
646 
647    foreach_res(director, R_DIRECTOR) {
648       /* tls_require implies tls_enable */
649       if (director->tls_require) {
650          if (director->tls_certfile || director->tls_keyfile) {
651             director->tls_enable = true;
652          }
653       }
654 
655       tls_needed = director->tls_enable || director->tls_authenticate;
656 
657       if (!director->tls_certfile && tls_needed) {
658          Jmsg(NULL, M_FATAL, 0, _("\"TLS Certificate\" file not defined for Director \"%s\" in %s.\n"),
659               director->hdr.name, configfile);
660          OK = false;
661       }
662 
663       if (!director->tls_keyfile && tls_needed) {
664          Jmsg(NULL, M_FATAL, 0, _("\"TLS Key\" file not defined for Director \"%s\" in %s.\n"),
665               director->hdr.name, configfile);
666          OK = false;
667       }
668 
669       if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && tls_needed && director->tls_verify_peer) {
670          Jmsg(NULL, M_FATAL, 0, _("Neither \"TLS CA Certificate\""
671               " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
672               " At least one CA certificate store is required"
673               " when using \"TLS Verify Peer\".\n"),
674               director->hdr.name, configfile);
675          OK = false;
676       }
677    }
678 
679    CLOUD *cloud;
680    /* TODO: Can use a table */
681    foreach_res(cloud, R_CLOUD) {
682       if (cloud->driver_type == C_S3_DRIVER  ||
683           cloud->driver_type == C_FILE_DRIVER)
684       {
685          if (cloud->host_name == NULL) {
686             Jmsg(NULL, M_FATAL, 0,
687                  _("Failed to initialize Cloud. Hostname not defined for Cloud \"%s\"\n"),
688                  cloud->hdr.name);
689             OK = false;
690          }
691       }
692       if (cloud->driver_type == C_WAS_DRIVER ||
693           cloud->driver_type == C_S3_DRIVER)
694       {
695          if (cloud->access_key == NULL) {
696             Jmsg(NULL, M_FATAL, 0,
697                  _("Failed to initialize Cloud. AccessKey not set for Cloud \"%s\"\n"),
698                  cloud->hdr.name);
699             OK = false;
700          }
701          if (cloud->secret_key == NULL) {
702             Jmsg(NULL, M_FATAL, 0,
703                  _("Failed to initialize Cloud. SecretKey not set for Cloud \"%s\"\n"),
704                  cloud->hdr.name);
705             OK = false;
706          }
707       }
708    }
709 #ifdef SD_DEDUP_SUPPORT
710    DEDUPRES *dedup;
711    foreach_res(dedup, R_DEDUP) {
712       if (dedup->driver_type == D_LEGACY_DRIVER)
713       {
714          if (dedup->dedup_dir == NULL) {
715             Jmsg(NULL, M_FATAL, 0,
716                  _("Failed to initialize Dedup. DedupDirectory not defined for Dedup \"%s\"\n"),
717                  dedup->hdr.name);
718             OK = false;
719          }
720       }
721    }
722 #endif
723    foreach_res(changer, R_AUTOCHANGER) {
724       foreach_alist(device, changer->device) {
725          device->cap_bits |= CAP_AUTOCHANGER;
726       }
727    }
728 
729    return OK;
730 }
731 
732 /* Clean up and then exit */
terminate_stored(int sig)733 void terminate_stored(int sig)
734 {
735    static bool in_here = false;
736 
737    if (in_here) {                     /* prevent loops */
738       bmicrosleep(2, 0);              /* yield */
739       exit(1);
740    }
741    in_here = true;
742    debug_level = 0;                   /* turn off any debug */
743 
744    if (configfile) {
745       free(configfile);
746       configfile = NULL;
747    }
748    if (config) {
749       delete config;
750       config = NULL;
751    }
752 
753    if (debug_level > 10) {
754       print_memory_pool_stats();
755    }
756    term_msg();
757    free(res_head);
758    res_head = NULL;
759    close_memory_pool();
760 
761    //sm_dump(false);                    /* dump orphaned buffers */
762    exit(sig);
763 }
764 
sendit(void * sock,const char * fmt,...)765 static void sendit(void *sock, const char *fmt, ...)
766 {
767    char buf[3000];
768    va_list arg_ptr;
769 
770    va_start(arg_ptr, fmt);
771    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
772    va_end(arg_ptr);
773    fputs(buf, stdout);
774    fflush(stdout);
775 }
776