1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2012 Planets Communications B.V.
6 Copyright (C) 2013-2021 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
11 in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23 /*
24 * Kern Sibbald, MM
25 */
26 /**
27 * @file
28 * Second generation Storage daemon.
29 *
30 * It accepts a number of simple commands from the File daemon
31 * and acts on them. When a request to append data is made,
32 * it opens a data channel and accepts data from the
33 * File daemon.
34 */
35
36 #include "include/bareos.h"
37 #include "stored/stored.h"
38 #include "lib/crypto_cache.h"
39 #include "stored/acquire.h"
40 #include "stored/autochanger.h"
41 #include "stored/bsr.h"
42 #include "stored/device.h"
43 #include "stored/jcr_private.h"
44 #include "stored/job.h"
45 #include "stored/label.h"
46 #include "stored/ndmp_tape.h"
47 #include "stored/sd_backends.h"
48 #include "stored/sd_device_control_record.h"
49 #include "stored/sd_stats.h"
50 #include "stored/socket_server.h"
51 #include "stored/stored_globals.h"
52 #include "stored/wait.h"
53 #include "lib/berrno.h"
54 #include "lib/bsock.h"
55 #include "lib/bnet_network_dump.h"
56 #include "lib/daemon.h"
57 #include "lib/bsignal.h"
58 #include "lib/parse_conf.h"
59 #include "lib/thread_specific_data.h"
60 #include "lib/util.h"
61 #include "lib/watchdog.h"
62 #include "include/jcr.h"
63
64
65 namespace storagedaemon {
66 extern bool ParseSdConfig(const char* configfile, int exit_code);
67 }
68
69 using namespace storagedaemon;
70
71 /* Imported functions */
72 extern bool PrintMessage(void* sock, const char* fmt, ...);
73
74 /* Forward referenced functions */
75 namespace storagedaemon {
76 #if !defined(HAVE_WIN32)
77 static
78 #endif
79 void
80 TerminateStored(int sig);
81 } // namespace storagedaemon
82 static int CheckResources();
83 static void CleanUpOldFiles();
84
85 extern "C" void* device_initialization(void* arg);
86
87 /* Global static variables */
88 static bool foreground = 0;
89
usage()90 static void usage()
91 {
92 kBareosVersionStrings.PrintCopyrightWithFsfAndPlanets(stderr, 2000);
93 fprintf(
94 stderr,
95 _("Usage: bareos-sd [options]\n"
96 " -c <path> use <path> as configuration file or directory\n"
97 " -d <nn> set debug level to <nn>\n"
98 " -dt print timestamp in debug output\n"
99 " -f run in foreground (for debugging)\n"
100 " -g <group> run as group <group>\n"
101 " -m print kaboom output (for debugging)\n"
102 " -p proceed despite I/O errors\n"
103 " -s no signals (for debugging)\n"
104 " -t test - read configuration and exit\n"
105 " -u <user> run as user <user>\n"
106 " -v verbose user messages\n"
107 " -xc print configuration and exit\n"
108 " -xs print configuration file schema in JSON format "
109 "and exit\n"
110 " -? print this message.\n"
111 "\n"));
112 exit(1);
113 }
114
115 /*********************************************************************
116 *
117 * Main Bareos Storage Daemon
118 *
119 */
120 #if defined(HAVE_WIN32)
121 # define main BareosMain
122 #endif
123
main(int argc,char * argv[])124 int main(int argc, char* argv[])
125 {
126 int ch;
127 bool no_signals = false;
128 bool test_config = false;
129 bool export_config = false;
130 bool export_config_schema = false;
131 pthread_t thid;
132 char* uid = NULL;
133 char* gid = NULL;
134
135 setlocale(LC_ALL, "");
136 tzset();
137 bindtextdomain("bareos", LOCALEDIR);
138 textdomain("bareos");
139
140 InitStackDump();
141 MyNameIs(argc, argv, "bareos-sd");
142 InitMsg(NULL, NULL);
143 daemon_start_time = time(NULL);
144
145 /*
146 * Sanity checks
147 */
148 if (TAPE_BSIZE % B_DEV_BSIZE != 0 || TAPE_BSIZE / B_DEV_BSIZE == 0) {
149 Emsg2(M_ABORT, 0,
150 _("Tape block size (%d) not multiple of system size (%d)\n"),
151 TAPE_BSIZE, B_DEV_BSIZE);
152 }
153 if (TAPE_BSIZE != (1 << (ffs(TAPE_BSIZE) - 1))) {
154 Emsg1(M_ABORT, 0, _("Tape block size (%d) is not a power of 2\n"),
155 TAPE_BSIZE);
156 }
157
158 while ((ch = getopt(argc, argv, "c:d:fg:mpstu:vx:z:?")) != -1) {
159 switch (ch) {
160 case 'c': /* configuration file */
161 if (configfile != NULL) { free(configfile); }
162 configfile = strdup(optarg);
163 break;
164
165 case 'd': /* debug level */
166 if (*optarg == 't') {
167 dbg_timestamp = true;
168 } else {
169 debug_level = atoi(optarg);
170 if (debug_level <= 0) { debug_level = 1; }
171 }
172 break;
173
174 case 'f': /* run in foreground */
175 foreground = true;
176 break;
177
178 case 'g': /* set group id */
179 gid = optarg;
180 break;
181
182 case 'm': /* print kaboom output */
183 prt_kaboom = true;
184 break;
185
186 case 'p': /* proceed in spite of I/O errors */
187 forge_on = true;
188 break;
189
190 case 's': /* no signals */
191 no_signals = true;
192 break;
193
194 case 't':
195 test_config = true;
196 break;
197
198 case 'u': /* set uid */
199 uid = optarg;
200 break;
201
202 case 'v': /* verbose */
203 verbose++;
204 break;
205
206 case 'x': /* export configuration/schema and exit */
207 if (*optarg == 's') {
208 export_config_schema = true;
209 } else if (*optarg == 'c') {
210 export_config = true;
211 } else {
212 usage();
213 }
214 break;
215
216 case 'z': /* switch network debugging on */
217 if (!BnetDump::EvaluateCommandLineArgs(optarg)) { exit(1); }
218 break;
219
220 case '?':
221 default:
222 usage();
223 break;
224 }
225 }
226 argc -= optind;
227 argv += optind;
228
229 if (!no_signals) { InitSignals(TerminateStored); }
230
231 if (argc) {
232 if (configfile != NULL) { free(configfile); }
233 configfile = strdup(*argv);
234 argc--;
235 argv++;
236 }
237 if (argc) { usage(); }
238
239 /*
240 * See if we want to drop privs.
241 */
242 if (geteuid() == 0) { drop(uid, gid, false); }
243
244 if (export_config_schema) {
245 PoolMem buffer;
246
247 my_config = InitSdConfig(configfile, M_ERROR_TERM);
248 PrintConfigSchemaJson(buffer);
249 printf("%s\n", buffer.c_str());
250 goto bail_out;
251 }
252
253 my_config = InitSdConfig(configfile, M_ERROR_TERM);
254 ParseSdConfig(configfile, M_ERROR_TERM);
255
256 if (forge_on) {
257 my_config->AddWarning(
258 "Running with '-p' is for testing and emergency recovery purposes "
259 "only");
260 }
261
262 if (export_config) {
263 my_config->DumpResources(PrintMessage, NULL);
264 goto bail_out;
265 }
266
267 if (!foreground && !test_config) {
268 daemon_start(); /* become daemon */
269 InitStackDump(); /* pick up new pid */
270 }
271
272 if (InitCrypto() != 0) {
273 Jmsg((JobControlRecord*)NULL, M_ERROR_TERM, 0,
274 _("Cryptography library initialization failed.\n"));
275 }
276
277 if (!CheckResources()) {
278 Jmsg((JobControlRecord*)NULL, M_ERROR_TERM, 0,
279 _("Please correct the configuration in %s\n"),
280 my_config->get_base_config_path().c_str());
281 }
282
283 InitReservationsLock();
284
285 if (test_config) {
286 if (my_config->HasWarnings()) {
287 /* messaging not initialized, so Jmsg with M_WARNING doesn't work */
288 fprintf(stderr, _("There are configuration warnings:\n"));
289 for (auto& warning : my_config->GetWarnings()) {
290 fprintf(stderr, " * %s\n", warning.c_str());
291 }
292 }
293 TerminateStored(0);
294 }
295
296 MyNameIs(0, (char**)NULL, me->resource_name_); /* Set our real name */
297
298 CreatePidFile(me->pid_directory, "bareos-sd",
299 GetFirstPortHostOrder(me->SDaddrs));
300 ReadStateFile(me->working_directory, "bareos-sd",
301 GetFirstPortHostOrder(me->SDaddrs));
302 ReadCryptoCache(me->working_directory, "bareos-sd",
303 GetFirstPortHostOrder(me->SDaddrs));
304
305 SetJcrInThreadSpecificData(nullptr);
306
307 LoadSdPlugins(me->plugin_directory, me->plugin_names);
308
309 CleanUpOldFiles();
310
311 /* Ensure that Volume Session Time and Id are both
312 * set and are both non-zero.
313 */
314 vol_session_time = (uint32_t)daemon_start_time;
315 if (vol_session_time == 0) { /* paranoid */
316 Jmsg0(NULL, M_ABORT, 0, _("Volume Session Time is ZERO!\n"));
317 }
318
319 /*
320 * Start the device allocation thread
321 */
322 CreateVolumeLists(); /* do before device_init */
323 if (pthread_create(&thid, NULL, device_initialization, NULL) != 0) {
324 BErrNo be;
325 Emsg1(M_ABORT, 0, _("Unable to create thread. ERR=%s\n"), be.bstrerror());
326 }
327
328 InitJcrChain();
329 StartWatchdog(); /* start watchdog thread */
330 if (me->jcr_watchdog_time) {
331 InitJcrSubsystem(
332 me->jcr_watchdog_time); /* start JobControlRecord watchdogs etc. */
333 }
334
335 StartStatisticsThread();
336
337 #if HAVE_NDMP
338 /*
339 * Separate thread that handles NDMP connections
340 */
341 if (me->ndmp_enable) {
342 StartNdmpThreadServer(me->NDMPaddrs, me->MaxConnections);
343 }
344 #endif
345
346 /*
347 * Single server used for Director/Storage and File daemon
348 */
349 StartSocketServer(me->SDaddrs);
350
351 /* to keep compiler quiet */
352 TerminateStored(0);
353
354 bail_out:
355 return 0;
356 }
357
358 /* Check Configuration file for necessary info */
CheckResources()359 static int CheckResources()
360 {
361 bool OK = true;
362 const std::string& configfile = my_config->get_base_config_path();
363
364 if (my_config->GetNextRes(R_STORAGE, (BareosResource*)me) != NULL) {
365 Jmsg1(NULL, M_ERROR, 0, _("Only one Storage resource permitted in %s\n"),
366 configfile.c_str());
367 OK = false;
368 }
369
370 if (my_config->GetNextRes(R_DIRECTOR, NULL) == NULL) {
371 Jmsg1(NULL, M_ERROR, 0,
372 _("No Director resource defined in %s. Cannot continue.\n"),
373 configfile.c_str());
374 OK = false;
375 }
376
377 if (my_config->GetNextRes(R_DEVICE, NULL) == NULL) {
378 Jmsg1(NULL, M_ERROR, 0,
379 _("No Device resource defined in %s. Cannot continue.\n"),
380 configfile.c_str());
381 OK = false;
382 }
383
384 /*
385 * Sanity check.
386 */
387 if (me->MaxConnections < ((2 * me->MaxConcurrentJobs) + 2)) {
388 me->MaxConnections = (2 * me->MaxConcurrentJobs) + 2;
389 }
390
391 if (!me->messages) {
392 me->messages = (MessagesResource*)my_config->GetNextRes(R_MSGS, NULL);
393 if (!me->messages) {
394 Jmsg1(NULL, M_ERROR, 0,
395 _("No Messages resource defined in %s. Cannot continue.\n"),
396 configfile.c_str());
397 OK = false;
398 }
399 }
400
401 if (!me->working_directory) {
402 Jmsg1(NULL, M_ERROR, 0,
403 _("No Working Directory defined in %s. Cannot continue.\n"),
404 configfile.c_str());
405 OK = false;
406 }
407
408 StorageResource* store = me;
409 if (store->IsTlsConfigured()) {
410 if (!have_tls) {
411 Jmsg(NULL, M_FATAL, 0, _("TLS required but not compiled into Bareos.\n"));
412 OK = false;
413 }
414 }
415
416 DeviceResource* device_resource = nullptr;
417 foreach_res (device_resource, R_DEVICE) {
418 if (device_resource->drive_crypto_enabled
419 && BitIsSet(CAP_LABEL, device_resource->cap_bits)) {
420 Jmsg(NULL, M_FATAL, 0,
421 _("LabelMedia enabled is incompatible with tape crypto on Device "
422 "\"%s\" in %s.\n"),
423 device_resource->resource_name_, configfile.c_str());
424 OK = false;
425 }
426 }
427
428 if (OK) { OK = InitAutochangers(); }
429
430 if (OK) {
431 CloseMsg(NULL); /* close temp message handler */
432 InitMsg(NULL, me->messages); /* open daemon message handler */
433 SetWorkingDirectory(me->working_directory);
434 if (me->secure_erase_cmdline) {
435 SetSecureEraseCmdline(me->secure_erase_cmdline);
436 }
437 if (me->log_timestamp_format) {
438 SetLogTimestampFormat(me->log_timestamp_format);
439 }
440 }
441
442 return OK;
443 }
444
445 /**
446 * Remove old .spool files written by me from the working directory.
447 */
CleanUpOldFiles()448 static void CleanUpOldFiles()
449 {
450 DIR* dp;
451 struct dirent* result;
452 #ifdef USE_READDIR_R
453 struct dirent* entry;
454 #endif
455 int rc, name_max;
456 int my_name_len = strlen(my_name);
457 int len = strlen(me->working_directory);
458 POOLMEM* cleanup = GetPoolMemory(PM_MESSAGE);
459 POOLMEM* basename = GetPoolMemory(PM_MESSAGE);
460 regex_t preg1{};
461 char prbuf[500];
462 BErrNo be;
463
464 /* Look for .spool files but don't allow spaces */
465 const char* pat1 = "^[^ ]+\\.spool$";
466
467 /* Setup working directory prefix */
468 PmStrcpy(basename, me->working_directory);
469 if (len > 0 && !IsPathSeparator(me->working_directory[len - 1])) {
470 PmStrcat(basename, "/");
471 }
472
473 /* Compile regex expressions */
474 rc = regcomp(&preg1, pat1, REG_EXTENDED);
475 if (rc != 0) {
476 regerror(rc, &preg1, prbuf, sizeof(prbuf));
477 Pmsg2(000, _("Could not compile regex pattern \"%s\" ERR=%s\n"), pat1,
478 prbuf);
479 goto get_out2;
480 }
481
482 name_max = pathconf(".", _PC_NAME_MAX);
483 if (name_max < 1024) { name_max = 1024; }
484
485 if (!(dp = opendir(me->working_directory))) {
486 BErrNo be;
487 Pmsg2(000, "Failed to open working dir %s for cleanup: ERR=%s\n",
488 me->working_directory, be.bstrerror());
489 goto get_out1;
490 }
491
492 #ifdef USE_READDIR_R
493 entry = (struct dirent*)malloc(sizeof(struct dirent) + name_max + 1000);
494 while (1) {
495 if ((Readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
496 #else
497 while (1) {
498 result = readdir(dp);
499 if (result == NULL) {
500 #endif
501 break;
502 }
503
504 /* Exclude any name with ., .., not my_name or containing a space */
505 if (strcmp(result->d_name, ".") == 0 || strcmp(result->d_name, "..") == 0
506 || strncmp(result->d_name, my_name, my_name_len) != 0) {
507 Dmsg1(500, "Skipped: %s\n", result->d_name);
508 continue;
509 }
510
511 /* Unlink files that match regex */
512 if (regexec(&preg1, result->d_name, 0, NULL, 0) == 0) {
513 PmStrcpy(cleanup, basename);
514 PmStrcat(cleanup, result->d_name);
515 Dmsg1(500, "Unlink: %s\n", cleanup);
516 SecureErase(NULL, cleanup);
517 }
518 }
519 #ifdef USE_READDIR_R
520 free(entry);
521 #endif
522 closedir(dp);
523
524 get_out1:
525 regfree(&preg1);
526 get_out2:
527 FreePoolMemory(cleanup);
528 FreePoolMemory(basename);
529 }
530
531
532 /**
533 * Here we attempt to init and open each device. This is done once at startup in
534 * a separate thread.
535 */
536 extern "C" void* device_initialization(void* arg)
537 {
538 DeviceResource* device_resource = nullptr;
539 DeviceControlRecord* dcr;
540 JobControlRecord* jcr;
541 Device* dev;
542 int errstat;
543
544 LockRes(my_config);
545
546 pthread_detach(pthread_self());
547 jcr = NewStoredJcr();
548 NewPlugins(jcr); /* instantiate plugins */
549 jcr->setJobType(JT_SYSTEM);
550
551 /*
552 * Initialize job start condition variable
553 */
554 errstat = pthread_cond_init(&jcr->impl->job_start_wait, NULL);
555 if (errstat != 0) {
556 BErrNo be;
557 Jmsg1(jcr, M_ABORT, 0,
558 _("Unable to init job start cond variable: ERR=%s\n"),
559 be.bstrerror(errstat));
560 }
561
562 /*
563 * Initialize job end condition variable
564 */
565 errstat = pthread_cond_init(&jcr->impl->job_end_wait, NULL);
566 if (errstat != 0) {
567 BErrNo be;
568 Jmsg1(jcr, M_ABORT, 0,
569 _("Unable to init job endstart cond variable: ERR=%s\n"),
570 be.bstrerror(errstat));
571 }
572
573 foreach_res (device_resource, R_DEVICE) {
574 Dmsg1(90, "calling FactoryCreateDevice %s\n",
575 device_resource->archive_device_string);
576 dev = FactoryCreateDevice(NULL, device_resource);
577 Dmsg1(10, "SD init done %s\n", device_resource->archive_device_string);
578 if (!dev) {
579 Jmsg1(NULL, M_ERROR, 0, _("Could not initialize %s\n"),
580 device_resource->archive_device_string);
581 continue;
582 }
583
584 dcr = new StorageDaemonDeviceControlRecord;
585 jcr->impl->dcr = dcr;
586 SetupNewDcrDevice(jcr, dcr, dev, NULL);
587 jcr->impl->dcr->SetWillWrite();
588 GeneratePluginEvent(jcr, bSdEventDeviceInit, dcr);
589 if (dev->AttachedToAutochanger()) {
590 /*
591 * If autochanger set slot in dev structure
592 */
593 GetAutochangerLoadedSlot(dcr);
594 }
595
596 if (BitIsSet(CAP_ALWAYSOPEN, device_resource->cap_bits)) {
597 Dmsg1(20, "calling FirstOpenDevice %s\n", dev->print_name());
598 if (!FirstOpenDevice(dcr)) {
599 Jmsg1(NULL, M_ERROR, 0, _("Could not open device %s\n"),
600 dev->print_name());
601 Dmsg1(20, "Could not open device %s\n", dev->print_name());
602 FreeDeviceControlRecord(dcr);
603 jcr->impl->dcr = NULL;
604 continue;
605 }
606 }
607
608 if (BitIsSet(CAP_AUTOMOUNT, device_resource->cap_bits) && dev->IsOpen()) {
609 switch (ReadDevVolumeLabel(dcr)) {
610 case VOL_OK:
611 memcpy(&dev->VolCatInfo, &dcr->VolCatInfo, sizeof(dev->VolCatInfo));
612 VolumeUnused(dcr); /* mark volume "released" */
613 break;
614 default:
615 Jmsg1(NULL, M_WARNING, 0, _("Could not mount device %s\n"),
616 dev->print_name());
617 break;
618 }
619 }
620 FreeDeviceControlRecord(dcr);
621 jcr->impl->dcr = NULL;
622 }
623 FreeJcr(jcr);
624 init_done = true;
625 UnlockRes(my_config);
626 return NULL;
627 }
628
629 /**
630 * Clean up and then exit
631 */
632 namespace storagedaemon {
633
634 #if !defined(HAVE_WIN32)
635 static
636 #endif
637 void
638 TerminateStored(int sig)
639 {
640 static bool in_here = false;
641 DeviceResource* device_resource = nullptr;
642 JobControlRecord* jcr;
643
644 if (in_here) { /* prevent loops */
645 Bmicrosleep(2, 0); /* yield */
646 exit(1);
647 }
648 in_here = true;
649 debug_level = 0; /* turn off any debug */
650 StopStatisticsThread();
651 #if HAVE_NDMP
652 if (me->ndmp_enable) { StopNdmpThreadServer(); }
653 #endif
654 StopSocketServer();
655
656 StopWatchdog();
657
658 if (sig == SIGTERM) { /* normal shutdown request? */
659 /*
660 * This is a normal shutdown request. We wiffle through
661 * all open jobs canceling them and trying to wake
662 * them up so that they will report back the correct
663 * volume status.
664 */
665 foreach_jcr (jcr) {
666 BareosSocket* fd;
667 if (jcr->JobId == 0) { continue; /* ignore console */ }
668 jcr->setJobStatus(JS_Canceled);
669 fd = jcr->file_bsock;
670 if (fd) {
671 fd->SetTimedOut();
672 jcr->MyThreadSendSignal(TIMEOUT_SIGNAL);
673 Dmsg1(100, "term_stored killing JobId=%d\n", jcr->JobId);
674 /* ***FIXME*** wiffle through all dcrs */
675 if (jcr->impl->dcr && jcr->impl->dcr->dev
676 && jcr->impl->dcr->dev->blocked()) {
677 pthread_cond_broadcast(&jcr->impl->dcr->dev->wait_next_vol);
678 Dmsg1(100, "JobId=%u broadcast wait_device_release\n",
679 (uint32_t)jcr->JobId);
680 ReleaseDeviceCond();
681 }
682 if (jcr->impl->read_dcr && jcr->impl->read_dcr->dev
683 && jcr->impl->read_dcr->dev->blocked()) {
684 pthread_cond_broadcast(&jcr->impl->read_dcr->dev->wait_next_vol);
685 Dmsg1(100, "JobId=%u broadcast wait_device_release\n",
686 (uint32_t)jcr->JobId);
687 ReleaseDeviceCond();
688 }
689 Bmicrosleep(0, 50000);
690 }
691 FreeJcr(jcr);
692 }
693 Bmicrosleep(0, 500000); /* give them 1/2 sec to clean up */
694 }
695
696 WriteStateFile(me->working_directory, "bareos-sd",
697 GetFirstPortHostOrder(me->SDaddrs));
698 DeletePidFile(me->pid_directory, "bareos-sd",
699 GetFirstPortHostOrder(me->SDaddrs));
700
701 Dmsg1(200, "In TerminateStored() sig=%d\n", sig);
702
703 UnloadSdPlugins();
704 FlushCryptoCache();
705 FreeVolumeLists();
706
707 foreach_res (device_resource, R_DEVICE) {
708 Dmsg1(10, "Term device %s\n", device_resource->archive_device_string);
709 if (device_resource->dev) {
710 device_resource->dev->ClearVolhdr();
711 delete device_resource->dev;
712 device_resource->dev = nullptr;
713 } else {
714 Dmsg1(10, "No dev structure %s\n",
715 device_resource->archive_device_string);
716 }
717 }
718
719 #if defined(HAVE_DYNAMIC_SD_BACKENDS)
720 FlushAndCloseBackendDevices();
721 #endif
722
723 if (configfile) {
724 free(configfile);
725 configfile = NULL;
726 }
727 if (my_config) {
728 delete my_config;
729 my_config = NULL;
730 }
731
732 if (debug_level > 10) { PrintMemoryPoolStats(); }
733 TermMsg();
734 CleanupCrypto();
735 TermReservationsLock();
736 CloseMemoryPool();
737
738 exit(sig);
739 }
740
741 } /* namespace storagedaemon */
742