1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2019 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 * This is a Bacula plugin for backup/restore Docker using native tools.
21 *
22 * Author: Radosław Korzeniewski, MMXIX
23 * radoslaw@korzeniewski.net, radekk@inteos.pl
24 * Inteos Sp. z o.o. http://www.inteos.pl/
25 */
26
27 #include "dkcommctx.h"
28
29 #define PLUGINPREFIX "dkcommctx:"
30
31 /*
32 * Constructor, does simple initialization.
33 */
DKCOMMCTX(const char * cmd)34 DKCOMMCTX::DKCOMMCTX(const char *cmd) :
35 bpipe(NULL),
36 param_include_container(NULL),
37 param_include_image(NULL),
38 param_exclude_container(NULL),
39 param_exclude_image(NULL),
40 param_container(NULL),
41 param_image(NULL),
42 param_volume(NULL),
43 param_mode(DKPAUSE), /* default backup mode */
44 param_container_create(true),
45 param_container_run(false),
46 param_container_imageid(false),
47 param_container_defaultnames(false),
48 param_docker_host(NULL),
49 param_timeout(0),
50
51 abort_on_error(false),
52 all_containers(NULL),
53 all_images(NULL),
54 all_volumes(NULL),
55 objs_to_backup(NULL),
56 all_to_backup(false),
57 all_vols_to_backup(false),
58 f_eod(false),
59 f_error(false),
60 f_fatal(false),
61 ini(NULL),
62 workingvolume(NULL),
63 workingdir(NULL)
64 {
65 /* setup initial plugin command here */
66 command = bstrdup(cmd);
67 /* prepare backup list */
68 objs_to_backup = New(alist(32, not_owned_by_alist));
69 param_timeout = 30; // this is a default you can overwrite
70 };
71
72 /*
73 * Destructor, releases all memory allocated.
74 */
~DKCOMMCTX()75 DKCOMMCTX::~DKCOMMCTX()
76 {
77 if (command){
78 free(command);
79 }
80 if (ini){
81 delete ini;
82 }
83
84 release_all_dkinfo_list(&all_containers);
85 release_all_dkinfo_list(&all_images);
86 release_all_dkinfo_list(&all_volumes);
87 if (objs_to_backup){
88 delete objs_to_backup;
89 }
90 release_all_pm_list(¶m_include_container);
91 release_all_pm_list(¶m_exclude_container);
92 release_all_pm_list(¶m_include_image);
93 release_all_pm_list(¶m_exclude_image);
94 release_all_pm_list(¶m_container);
95 release_all_pm_list(¶m_image);
96 release_all_pm_list(¶m_volume);
97 free_and_null_pool_memory(param_docker_host);
98 free_and_null_pool_memory(workingvolume);
99 free_and_null_pool_memory(workingdir);
100 };
101
102 /*
103 * sets runtime workingdir variable used in working volume creation.
104 *
105 * in:
106 * workdir - the file daemon working directory parameter
107 * out:
108 * none
109 */
setworkingdir(char * workdir)110 void DKCOMMCTX::setworkingdir(char* workdir)
111 {
112 if (workingdir == NULL){
113 /* not allocated yet */
114 workingdir = get_pool_memory(PM_FNAME);
115 }
116 pm_strcpy(&workingdir, workdir);
117 DMSG1(NULL, DVDEBUG, "workingdir: %s\n", workingdir);
118 };
119
120 /*
121 * Releases the memory allocated by all_* list.
122 *
123 * in:
124 * alist - a list to release
125 * out:
126 * none
127 */
release_all_dkinfo_list(alist ** list)128 void DKCOMMCTX::release_all_dkinfo_list(alist **list)
129 {
130 DKINFO *dkinfo;
131
132 if (*list){
133 foreach_alist(dkinfo, *list){
134 if (dkinfo){
135 delete dkinfo;
136 }
137 }
138 delete *list;
139 }
140 *list = NULL;
141 }
142
143 /*
144 * Releases the memory allocated by param_* list.
145 *
146 * in:
147 * alist - a list to release
148 * out:
149 * none
150 */
release_all_pm_list(alist ** list)151 void DKCOMMCTX::release_all_pm_list(alist **list)
152 {
153 POOLMEM *pm;
154
155 if (*list){
156 foreach_alist(pm, *list){
157 free_and_null_pool_memory(pm);
158 }
159 delete *list;
160 }
161 *list = NULL;
162 }
163
164 /*
165 * Prepare a docker volume directory for container execution.
166 *
167 * in:
168 * bpContext - required for debug/job messages
169 * jobid - for volume directory distinguish and jobid tracking
170 * out:
171 * bRC_OK - on success
172 * bRC_Error - on any error
173 */
prepare_working_volume(bpContext * ctx,int jobid)174 bRC DKCOMMCTX::prepare_working_volume(bpContext *ctx, int jobid)
175 {
176 char *dir;
177 struct stat statp;
178 pid_t pid = getpid();
179
180 DMSG0(ctx, DINFO, "prepare_working_volume called\n");
181 if (workingvolume == NULL){
182 workingvolume = get_pool_memory(PM_FNAME);
183 /* create dir template for mkdtemp function */
184 Mmsg(workingvolume, "%s/docker-%d-%d-XXXXXX",
185 workingdir != NULL ? workingdir : WORKDIR, jobid, pid);
186 dir = mkdtemp(workingvolume);
187 if (dir == NULL){
188 /* failback to standard method */
189 Mmsg(workingvolume, "%s/docker-%d-%d",
190 workingdir != NULL ? workingdir : WORKDIR, jobid, pid);
191 if (stat(workingvolume, &statp) != 0){
192 berrno be;
193 /* if the path does not exist then create one */
194 if (be.code() != ENOENT || mkdir(workingvolume, 0700) != 0){
195 /* creation or other error, set new errno and proceed to inform user */
196 be.set_errno(errno);
197 DMSG2(ctx, DERROR, "working volume path (%s) creation Err=%s\n", workingvolume, be.bstrerror());
198 JMSG2(ctx, abort_on_error ? M_FATAL : M_ERROR,
199 "Working volume path (%s) creation Err=%s!\n", workingvolume, be.bstrerror());
200 return bRC_Error;
201 }
202 } else
203 if (!S_ISDIR(statp.st_mode)){
204 /* the expected working dir/volume is already available and it is not a directory, strange */
205 DMSG2(ctx, DERROR, "working volume path (%s) is not directory Mode=%o\n", workingvolume, statp.st_mode);
206 JMSG2(ctx, abort_on_error ? M_FATAL : M_ERROR,
207 "Working volume path (%s) is not directory Mode=%o\n", workingvolume, statp.st_mode);
208 return bRC_Error;
209 }
210 }
211 }
212 DMSG1(ctx, DINFO, "prepare_working_volume finish: %s\n", workingvolume);
213 return bRC_OK;
214 };
215
216 /*
217 * It removes files and a volume directory after a successful backup/restore
218 *
219 * in:
220 * bpContext - required for debug/job messages
221 * out
222 * none
223 */
clean_working_volume(bpContext * ctx)224 void DKCOMMCTX::clean_working_volume(bpContext* ctx)
225 {
226 POOL_MEM fname(PM_FNAME);
227 int status;
228 int a;
229 bool ferr = false;
230 const char *ftab[] = {
231 BACULACONTAINERERRLOG,
232 BACULACONTAINERARCHLOG,
233 BACULACONTAINERFIN,
234 BACULACONTAINERFOUT,
235 NULL,
236 };
237
238 DMSG0(ctx, DDEBUG, "clean_working_volume called\n");
239 for (a = 0; ftab[a] != NULL; a++) {
240 render_working_volume_filename(fname, ftab[a]);
241 status = unlink(fname.c_str());
242 if (status < 0){
243 /* unlink error - report to user */
244 berrno be;
245 if (be.code() == ENOENT){
246 continue;
247 }
248 ferr = true;
249 DMSG2(ctx, DERROR, "unlink error: %s Err=%s\n", fname.c_str(), be.bstrerror());
250 JMSG2(ctx, M_ERROR, "Cannot unlink a file: %s Err=%s\n", fname.c_str(), be.bstrerror());
251 }
252 DMSG1(ctx, DDEBUG, "removing: %s\n", fname.c_str())
253 }
254 if (!ferr){
255 status = rmdir(workingvolume);
256 if (status < 0){
257 /* unlink error - report to user */
258 berrno be;
259 DMSG2(ctx, DERROR, "rmdir error: %s Err=%s\n", workingvolume, be.bstrerror());
260 JMSG2(ctx, M_ERROR, "Cannot remove directory: %s Err=%s\n", workingvolume, be.bstrerror());
261 }
262 }
263 free_and_null_pool_memory(workingvolume);
264 DMSG0(ctx, DDEBUG, "clean_working_volume finish.\n");
265 };
266
267 /*
268 * Terminate the connection represented by BPIPE object.
269 * it shows a debug and job messages when connection close
270 * is unsuccessful and when ctx is available only.
271 *
272 * in:
273 * bpContext - Bacula Plugin context required for debug/job
274 * messages to show, it could be NULL in this
275 * case no messages will be shown.
276 * out:
277 * none
278 */
terminate(bpContext * ctx)279 void DKCOMMCTX::terminate(bpContext *ctx)
280 {
281 int status;
282
283 if (is_closed()){
284 return;
285 }
286
287 DMSG(ctx, DDEBUG, "Terminating PID=%d\n", bpipe->worker_pid);
288 status = close_bpipe(bpipe);
289 if (status){
290 /* error during close */
291 berrno be;
292 f_error = true;
293 DMSG(ctx, DERROR, "Error closing backend. Err=%s\n", be.bstrerror(status));
294 JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "Error closing backend. Err=%s\n", be.bstrerror(status));
295 }
296 if (bpipe->worker_pid){
297 /* terminate the backend */
298 kill(bpipe->worker_pid, SIGTERM);
299 }
300 bpipe = NULL;
301 };
302
303 /*
304 * Run the command using *_CMD compile variable and prepared
305 * parameters.
306 *
307 * in:
308 * bpContext - for Bacula debug and jobinfo messages
309 * cmd - the command type to execute
310 * args - the command arguments
311 * out:
312 * True - when command execute successfully
313 * False - when execution return error
314 */
execute_command(bpContext * ctx,POOL_MEM & args)315 bool DKCOMMCTX::execute_command(bpContext *ctx, POOL_MEM &args)
316 {
317 return execute_command(ctx, args.c_str());
318 }
319
320 /*
321 * Run the command using *_CMD compile variable and prepared
322 * parameters.
323 *
324 * in:
325 * bpContext - for Bacula debug and jobinfo messages
326 * cmd - the command type to execute
327 * args - the command arguments
328 * out:
329 * True - when command execute successfully
330 * False - when execution return error
331 */
execute_command(bpContext * ctx,const char * args)332 bool DKCOMMCTX::execute_command(bpContext *ctx, const char *args)
333 {
334 return execute_command(ctx, (char*)args);
335 }
336
337 /*
338 * Run the command using *_CMD compile variable and prepared
339 * parameters.
340 *
341 * in:
342 * bpContext - for Bacula debug and jobinfo messages
343 * cmd - the command type to execute
344 * args - the command arguments
345 * out:
346 * True - when command execute successfully
347 * False - when execution return error
348 */
execute_command(bpContext * ctx,POOLMEM * args)349 bool DKCOMMCTX::execute_command(bpContext *ctx, POOLMEM *args)
350 {
351 POOL_MEM exe_cmd(PM_FNAME);
352 POOL_MEM DH(PM_NAME);
353 const char *command = DOCKER_CMD;
354 char *envp[3];
355 int a = 0;
356
357 if (args == NULL){
358 /* cannot execute command with args NULL */
359 DMSG0(ctx, DERROR, "Logic error: Cannot execute empty command tool!\n");
360 JMSG0(ctx, M_FATAL, "Logic error: Cannot execute empty command tool!\n");
361 return false;
362 }
363 /* check if command is still available to Bacula */
364 if (access(command, X_OK) < 0){
365 berrno be;
366 DMSG2(ctx, DERROR, "Unable to access %s command. Err=%s\n", command, be.bstrerror());
367 JMSG2(ctx, M_FATAL, "Unable to access %s command. Err=%s\n", command, be.bstrerror());
368 return false;
369 }
370 /* yes, we still have access to it.
371 * the format of a command line to execute is: <cmd> <params> */
372 Mmsg(exe_cmd, "%s %s", command, args);
373 DMSG(ctx, DINFO, "Executing: %s\n", exe_cmd.c_str());
374 /* preparing envinroment variables */
375 envp[a++] = bstrdup("LANG=C");
376 if (param_docker_host != NULL){
377 Mmsg(DH, "DOCKER_HOST=%s", param_docker_host);
378 envp[a++] = bstrdup(DH.c_str());
379 }
380 envp[a] = NULL;
381 bpipe = open_bpipe(exe_cmd.c_str(), 0, "rw", envp);
382 a = 0;
383 while (envp[a] != NULL){
384 free(envp[a++]);
385 }
386 if (bpipe == NULL){
387 berrno be;
388 DMSG(ctx, DERROR, "Unable to execute command. Err=%s\n", be.bstrerror());
389 JMSG(ctx, M_FATAL, "Unable to execute command. Err=%s\n", be.bstrerror());
390 return false;
391 }
392 DMSG(ctx, DINFO, "Command executed at PID=%d\n", get_backend_pid());
393 return true;
394 }
395
396 /*
397 * Read all output from command tool - until eod and save it
398 * in the out buffer.
399 *
400 * in:
401 * bpContext - for Bacula debug jobinfo messages
402 * out - the POOL_MEM buffer we will read data
403 * out:
404 * -1 - when we've got any error; the function will report
405 * it to Bacula when ctx is not NULL
406 * 0 - when no more data to read - EOD
407 * <n> - the size of received message
408 */
read_output(bpContext * ctx,POOL_MEM & out)409 int32_t DKCOMMCTX::read_output(bpContext *ctx, POOL_MEM &out)
410 {
411 int status;
412 int rbytes;
413 bool ndone;
414
415 if (is_closed()){
416 f_error = true;
417 DMSG0(ctx, DERROR, "BPIPE to command tool is closed, cannot get data.\n");
418 JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE to command tool is closed, cannot get data.\n");
419 return -1;
420 }
421
422 /* set variables */
423 rbytes = 0;
424 ndone = true;
425 /* wait a bit for a command to execute */
426 bmicrosleep(0, 1000); // sleep 1mS
427 /* read all output data */
428 while (ndone){
429 status = read_data(ctx, out.c_str() + rbytes, out.size() - rbytes);
430 if (status < 0){
431 /* error */
432 return -1;
433 }
434 rbytes += status;
435 if (is_eod()){
436 /* we read all data available */
437 ndone = false;
438 continue;
439 }
440 /* it seems out buffer is too small for all data */
441 out.check_size(rbytes + 1024);
442 }
443 return rbytes;
444 }
445
446 /*
447 * Reads a single data block from command tool.
448 * It reads as more data as is available on the other size and will fit into
449 * a memory buffer - buf. When EOD encountered during reading it will set
450 * f_eod flag, so checking this flag is mandatory!
451 *
452 * in:
453 * bpContext - for Bacula debug jobinfo messages
454 * buf - a memory buffer for data
455 * len - the length of the memory buffer - buf
456 * out:
457 * -1 - when we've got any error; the function reports it to Bacula when
458 * ctx is not NULL
459 * when no more data to read - EOD
460 * <n> - the size of received data
461 */
read_data(bpContext * ctx,POOLMEM * buf,int32_t len)462 int32_t DKCOMMCTX::read_data(bpContext *ctx, POOLMEM *buf, int32_t len)
463 {
464 int status;
465 int nbytes;
466 int rbytes;
467 int timeout;
468
469 if (buf == NULL || len < 1){
470 /* we have no space to read data */
471 f_error = true;
472 DMSG0(ctx, DERROR, "No space to read data from command tool.\n");
473 JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "No space to read data from command tool.\n");
474 return -1;
475 }
476
477 if (is_closed()){
478 f_error = true;
479 DMSG0(ctx, DERROR, "BPIPE to command tool is closed, cannot get data.\n");
480 JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE to command tool is closed, cannot get data.\n");
481 return -1;
482 }
483
484 /* we will read no more then len bytes available in the buf */
485 nbytes = len;
486 rbytes = 0;
487 /* clear flags */
488 f_eod = f_error = f_fatal = false;
489 timeout = 200; // timeout of 200ms
490 while (nbytes){
491 status = fread(buf + rbytes, 1, nbytes, bpipe->rfd);
492 if (status == 0){
493 berrno be;
494 if (ferror(bpipe->rfd) != 0){
495 f_error = true;
496 DMSG(ctx, DERROR, "BPIPE read error: ERR=%s\n", be.bstrerror());
497 JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE read error: ERR=%s\n", be.bstrerror());
498 return -1;
499 }
500 if (feof(bpipe->rfd) != 0){
501 f_eod = true;
502 return rbytes;
503 }
504 bmicrosleep(0, 1000); // sleep 1mS
505 if (!timeout--){
506 /* reach timeout*/
507 f_error = true;
508 DMSG0(ctx, DERROR, "BPIPE read timeout.\n");
509 JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE read timeout.\n");
510 return -1;
511 }
512 } else {
513 timeout = 200; // reset timeout
514 }
515 nbytes -= status;
516 rbytes += status;
517 }
518 return rbytes;
519 }
520
521 /*
522 * Sends a raw data block to command tool.
523 *
524 * in:
525 * bpContext - for Bacula debug and jobinfo messages
526 * buf - a message buffer contains data to send
527 * len - the length of the data to send
528 * out:
529 * -1 - when encountered any error
530 * <n> - the number of bytes sent, success
531 */
write_data(bpContext * ctx,POOLMEM * buf,int32_t len)532 int32_t DKCOMMCTX::write_data(bpContext *ctx, POOLMEM *buf, int32_t len)
533 {
534 int status;
535 int nbytes;
536 int wbytes;
537 int timeout;
538
539 if (buf == NULL){
540 /* we have no data to write */
541 f_error = true;
542 DMSG0(ctx, DERROR, "No data to send to command tool.\n");
543 JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "No data to send to command tool.\n");
544 return -1;
545 }
546
547 if (is_closed()){
548 f_error = true;
549 DMSG0(ctx, DERROR, "BPIPE to command tool is closed, cannot send data.\n");
550 JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE to command tool is closed, cannot send data.\n");
551 return -1;
552 }
553
554 /* we will write len bytes available in the buf */
555 nbytes = len;
556 wbytes = 0;
557 /* clear flags */
558 f_eod = f_error = f_fatal = false;
559 timeout = 200; // timeout of 200ms
560 while (nbytes){
561 status = fwrite(buf + wbytes, 1, nbytes, bpipe->wfd);
562 if (status == 0){
563 berrno be;
564 if (ferror(bpipe->wfd) != 0){
565 f_error = true;
566 DMSG(ctx, DERROR, "BPIPE write error: ERR=%s\n", be.bstrerror());
567 JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE write error: ERR=%s\n", be.bstrerror());
568 return -1;
569 }
570 bmicrosleep(0, 1000); // sleep 1mS
571 if (!timeout--){
572 /* reached timeout*/
573 f_error = true;
574 DMSG0(ctx, DERROR, "BPIPE write timeout.\n");
575 JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE write timeout.\n");
576 return -1;
577 }
578 } else {
579 timeout = 200; // reset timeout
580 }
581 nbytes -= status;
582 wbytes += status;
583 }
584 return wbytes;
585 }
586
587 /*
588 * Render a command tool parameter for string value.
589 *
590 * in:
591 * bpContext - for Bacula debug and jobinfo messages
592 * param - a pointer to the param variable where we will render a parameter
593 * pname - a name of the parameter to compare
594 * fmt - a low-level parameter name
595 * name - a name of the parameter from parameter list
596 * value - a value to render
597 * out:
598 * True if parameter was rendered
599 * False if it was not the parameter required
600 */
render_param(bpContext * ctx,POOLMEM ** param,const char * pname,const char * fmt,const char * name,char * value)601 bool DKCOMMCTX::render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *fmt, const char *name, char *value)
602 {
603 if (bstrcasecmp(name, pname)){
604 if (!*param){
605 *param = get_pool_memory(PM_NAME);
606 Mmsg(*param, " -%s '%s' ", fmt, value);
607 DMSG(ctx, DDEBUG, "render param:%s\n", *param);
608 }
609 return true;
610 }
611 return false;
612 }
613
614 /*
615 * Render a command tool parameter for integer value.
616 *
617 * in:
618 * bpContext - for Bacula debug and jobinfo messages
619 * param - a pointer to the param variable where we will render a parameter
620 * pname - a name of the parameter to compare
621 * fmt - a low-level parameter name
622 * name - a name of the parameter from parameter list
623 * value - a value to render
624 * out:
625 * True if parameter was rendered
626 * False if it was not the parameter required
627 */
render_param(bpContext * ctx,POOLMEM ** param,const char * pname,const char * fmt,const char * name,int value)628 bool DKCOMMCTX::render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *fmt, const char *name, int value)
629 {
630 if (bstrcasecmp(name, pname)){
631 if (!*param){
632 *param = get_pool_memory(PM_NAME);
633 Mmsg(*param, " -%s %d ", value);
634 DMSG(ctx, DDEBUG, "render param:%s\n", *param);
635 }
636 return true;
637 }
638 return false;
639 }
640
641 /*
642 * Render a command tool parameter for string value.
643 *
644 * in:
645 * bpContext - for Bacula debug and jobinfo messages
646 * param - a pointer to the param variable where we will render a parameter
647 * pname - a name of the parameter to compare
648 * name - a name of the parameter from parameter list
649 * value - a value to render
650 * out:
651 * True if parameter was rendered
652 * False if it was not the parameter required
653 */
render_param(bpContext * ctx,POOLMEM ** param,const char * pname,const char * name,char * value)654 bool DKCOMMCTX::render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *name, char *value)
655 {
656 if (bstrcasecmp(name, pname)){
657 if (!*param){
658 *param = get_pool_memory(PM_NAME);
659 Mmsg(*param, "%s", value);
660 DMSG(ctx, DDEBUG, "render param:%s\n", *param);
661 }
662 return true;
663 }
664 return false;
665 }
666
667 /*
668 * Setup DKCOMMCTX parameter for boolean value.
669 *
670 * in:
671 * bpContext - for Bacula debug and jobinfo messages
672 * param - a pointer to the param variable where we will render a parameter
673 * pname - a name of the parameter to compare
674 * name - a name of the parameter from parameter list
675 * value - a value to render
676 * out:
677 * True if parameter was rendered
678 * False if it was not the parameter required
679 */
render_param(bpContext * ctx,bool * param,const char * pname,const char * name,bool value)680 bool DKCOMMCTX::render_param(bpContext *ctx, bool *param, const char *pname, const char *name, bool value)
681 {
682 if (bstrcasecmp(name, pname)){
683 if (param){
684 *param = value;
685 DMSG2(ctx, DDEBUG, "render param: %s=%s\n", pname, *param ? "True" : "False");
686 }
687 return true;
688 }
689 return false;
690 }
691
692 /*
693 * Setup DKCOMMCTX parameter for int32_t value.
694 *
695 * in:
696 * bpContext - for Bacula debug and jobinfo messages
697 * param - a pointer to the param variable where we will render a parameter
698 * pname - a name of the parameter to compare
699 * name - a name of the parameter from parameter list
700 * value - a value to render
701 * out:
702 * True if parameter was rendered
703 * False if it was not the parameter required
704 */
render_param(bpContext * ctx,int32_t * param,const char * pname,const char * name,int32_t value)705 bool DKCOMMCTX::render_param(bpContext *ctx, int32_t *param, const char *pname, const char *name, int32_t value)
706 {
707 if (bstrcasecmp(name, pname)){
708 if (param){
709 *param = value;
710 DMSG2(ctx, DDEBUG, "render param: %s=%d\n", pname, *param);
711 }
712 return true;
713 }
714 return false;
715 }
716
717 /*
718 * Setup DKCOMMCTX parameter for boolean from string value.
719 * The parameter value will be false if value start with '0' character and
720 * will be true in any other case. So, when a plugin will have a following:
721 * param
722 * param=...
723 * param=1
724 * then a param will be set to true.
725 *
726 * in:
727 * bpContext - for Bacula debug and jobinfo messages
728 * param - a pointer to the param variable where we will render a parameter
729 * pname - a name of the parameter to compare
730 * name - a name of the parameter from parameter list
731 * value - a value to render
732 * out:
733 * True if parameter was rendered
734 * False if it was not the parameter required
735 */
parse_param(bpContext * ctx,bool * param,const char * pname,const char * name,char * value)736 bool DKCOMMCTX::parse_param(bpContext *ctx, bool *param, const char *pname, const char *name, char *value)
737 {
738 if (bstrcasecmp(name, pname)){
739 if (value && *value == '0'){
740 *param = false;
741 } else {
742 *param = true;
743 }
744 DMSG2(ctx, DINFO, "%s parameter: %s\n", name, *param ? "True" : "False");
745 return true;
746 }
747 return false;
748 }
749
750 /*
751 * Setup DKCOMMCTX parameter for integer from string value.
752 *
753 * in:
754 * bpContext - for Bacula debug and jobinfo messages
755 * param - a pointer to the param variable where we will render a parameter
756 * pname - a name of the parameter to compare
757 * name - a name of the parameter from parameter list
758 * value - a value to render
759 * out:
760 * True if parameter was rendered
761 * False if it was not the parameter required
762 */
parse_param(bpContext * ctx,int32_t * param,const char * pname,const char * name,char * value)763 bool DKCOMMCTX::parse_param(bpContext *ctx, int32_t *param, const char *pname, const char *name, char *value)
764 {
765 if (value && bstrcasecmp(name, pname)){
766 /* convert str to integer */
767 *param = atoi(value);
768 if (*param == 0){
769 /* error in conversion */
770 f_error = true;
771 DMSG2(ctx, DERROR, "Invalid %s parameter: %s\n", name, value);
772 JMSG2(ctx, M_ERROR, "Invalid %s parameter: %s\n", name, value);
773 return false;
774 }
775 DMSG2(ctx, DINFO, "%s parameter: %d\n", name, *param);
776 return true;
777 }
778 return false;
779 }
780
781 /*
782 * Setup DKCOMMCTX parameter for DOCKER_BACKUP_MODE_T from string value.
783 * supported values are: pause, nopause
784 * any other will be ignored.
785 *
786 * in:
787 * bpContext - for Bacula debug and jobinfo messages
788 * param - a pointer to the param variable where we will render a parameter
789 * pname - a name of the parameter to compare
790 * name - a name of the parameter from parameter list
791 * value - a value to render
792 * out:
793 * True if parameter was rendered
794 * False if it was not the parameter required
795 */
parse_param(bpContext * ctx,DOCKER_BACKUP_MODE_T * param,const char * pname,const char * name,char * value)796 bool DKCOMMCTX::parse_param(bpContext *ctx, DOCKER_BACKUP_MODE_T *param, const char *pname, const char *name, char *value)
797 {
798 if (bstrcasecmp(name, pname)){
799 if (value){
800 if (strcasecmp(value, "pause") == 0){
801 *param = DKPAUSE;
802 } else
803 if (strcasecmp(value, "nopause") == 0){
804 *param = DKNOPAUSE;
805 }
806 }
807 switch (*param){
808 case DKPAUSE:
809 DMSG(ctx, DINFO, "%s parameter: DKPAUSE\n", name);
810 break;
811 case DKNOPAUSE:
812 DMSG(ctx, DINFO, "%s parameter: DKNOPAUSE\n", name);
813 break;
814 }
815 return true;
816 }
817 return false;
818 }
819
820 /*
821 * Render and add a parameter for string value to alist.
822 * When alist is NULL (uninitialized) then it creates a new list to use.
823 *
824 * in:
825 * bpContext - for Bacula debug and jobinfo messages
826 * list - pointer to alist class to use
827 * pname - a name of the parameter to compare
828 * name - a name of the parameter from parameter list
829 * value - a value to render
830 * out:
831 * True if parameter was rendered
832 * False if it was not the parameter required
833 */
add_param_str(bpContext * ctx,alist ** list,const char * pname,const char * name,char * value)834 bool DKCOMMCTX::add_param_str(bpContext *ctx, alist **list, const char *pname, const char *name, char *value)
835 {
836 POOLMEM *param;
837
838 if (bstrcasecmp(name, pname)){
839 if (!*list){
840 *list = New(alist(8, not_owned_by_alist));
841 }
842 param = get_pool_memory(PM_NAME);
843 Mmsg(param, "%s", value);
844 (*list)->append(param);
845 DMSG2(ctx, DDEBUG, "add param: %s=%s\n", name, value);
846 return true;
847 }
848 return false;
849 }
850
851 /*
852 * Render and set a parameter for string value.
853 * When param is NULL (uninitialized) then it allocates a new string.
854 *
855 * in:
856 * bpContext - for Bacula debug and jobinfo messages
857 * list - pointer to alist class to use
858 * pname - a name of the parameter to compare
859 * name - a name of the parameter from parameter list
860 * value - a value to render
861 * out:
862 * True if parameter was rendered
863 * False if it was not the parameter required
864 */
parse_param(bpContext * ctx,POOLMEM ** param,const char * pname,const char * name,char * value)865 bool DKCOMMCTX::parse_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *name, char *value)
866 {
867 if (bstrcasecmp(name, pname)){
868 if (!*param){
869 *param = get_pool_memory(PM_NAME);
870 pm_strcpy(param, value);
871 DMSG2(ctx, DDEBUG, "add param: %s=%s\n", name, value);
872 }
873 return true;
874 }
875 return false;
876 }
877
878 /*
879 * Parse a restore plugin parameters for DKCOMMCTX class (single at a time).
880 *
881 * in:
882 * bpContext - for Bacula debug and jobinfo messages
883 * item - restore plugin parameter to parse
884 * out:
885 * if parameter found it will be rendered in class variable
886 */
parse_parameters(bpContext * ctx,ini_items & item)887 void DKCOMMCTX::parse_parameters(bpContext *ctx, ini_items &item)
888 {
889 /* container_create variable */
890 if (render_param(ctx, ¶m_container_create, "container_create", item.name, item.val.boolval)){
891 return;
892 }
893 /* container_create variable */
894 if (render_param(ctx, ¶m_container_run, "container_run", item.name, item.val.boolval)){
895 return;
896 }
897 /* container_create variable */
898 if (render_param(ctx, ¶m_container_imageid, "container_imageid", item.name, item.val.boolval)){
899 return;
900 }
901 /* container_create variable */
902 if (render_param(ctx, ¶m_container_defaultnames, "container_defaultnames", item.name, item.val.boolval)){
903 return;
904 }
905 /* docker_host variable */
906 if (render_param(ctx, ¶m_docker_host, "docker_host", item.name, item.val.strval)){
907 return;
908 }
909 /* timeout variable */
910 if (render_param(ctx, ¶m_timeout, "timeout", item.name, item.val.int32val)){
911 return;
912 }
913 f_error = true;
914 DMSG(ctx, DERROR, "INI: Unknown parameter: %s\n", item.name);
915 JMSG(ctx, M_ERROR, "INI: Unknown parameter: %s\n", item.name);
916 }
917 /*
918 * Check and render DKCOMMCTX required parameters (single at a time).
919 *
920 * in:
921 * bpContext - for Bacula debug and jobinfo messages
922 * argk - the parameter name
923 * argv - the parameter value (it could be null)
924 * out:
925 * bRC_OK - when parameter found and rendered successfully
926 * bRC_Error - on any error
927 * bRC_Max - when parameter not found and should be checked elsewhere
928 */
parse_parameters(bpContext * ctx,char * argk,char * argv)929 bRC DKCOMMCTX::parse_parameters(bpContext *ctx, char *argk, char *argv)
930 {
931 /* check abort_on_error parameter */
932 if (parse_param(ctx, &abort_on_error, "abort_on_error", argk, argv)){
933 return bRC_OK;
934 }
935 /* check allvolumes parameter */
936 if (parse_param(ctx, &all_vols_to_backup, "allvolumes", argk, argv)){
937 return bRC_OK;
938 }
939 /* check and handle container list */
940 if (add_param_str(ctx, ¶m_container, "container", argk, argv)){
941 return bRC_OK;
942 }
943 /* check and handle include_container list */
944 if (add_param_str(ctx, ¶m_include_container, "include_container", argk, argv)){
945 return bRC_OK;
946 }
947 /* check and handle exclude_container list */
948 if (add_param_str(ctx, ¶m_exclude_container, "exclude_container", argk, argv)){
949 return bRC_OK;
950 }
951 /* check and handle image list */
952 if (add_param_str(ctx, ¶m_image, "image", argk, argv)){
953 return bRC_OK;
954 }
955 /* check and handle include_image list */
956 if (add_param_str(ctx, ¶m_include_image, "include_image", argk, argv)){
957 return bRC_OK;
958 }
959 /* check and handle exclude_image list */
960 if (add_param_str(ctx, ¶m_exclude_image, "exclude_image", argk, argv)){
961 return bRC_OK;
962 }
963 /* check and handle volume list */
964 if (add_param_str(ctx, ¶m_volume, "volume", argk, argv)){
965 return bRC_OK;
966 }
967 /* check and handle timeout parameter */
968 if (parse_param(ctx, ¶m_timeout, "timeout", argk, argv)){
969 return bRC_OK;
970 }
971 /* check mode parameter */
972 if (parse_param(ctx, ¶m_mode, "mode", argk, argv)){
973 return bRC_OK;
974 }
975 /* check docker_host parameter */
976 if (parse_param(ctx, ¶m_docker_host, "docker_host", argk, argv)){
977 return bRC_OK;
978 }
979
980 /* parameter unknown */
981 return bRC_Max;
982 }
983
984 /*
985 * Used for dumping current restore object contents for debugging.
986 */
dump_robjdebug(bpContext * ctx,restore_object_pkt * rop)987 void DKCOMMCTX::dump_robjdebug(bpContext* ctx, restore_object_pkt* rop)
988 {
989 POOL_MEM out(PM_FNAME);
990
991 if (rop){
992 out.check_size(rop->object_len + 1);
993 pm_memcpy(out, rop->object, rop->object_len);
994 DMSG1(ctx, DERROR, "failed restore object:\n%s\n", out.c_str());
995 }
996 }
997
998 /*
999 * Parse a Restore Object saved during backup and modified by user during restore.
1000 * Every RO received will allocate a dedicated command context which is used
1001 * by bEventRestoreCommand to handle default parameters for restore.
1002 *
1003 * in:
1004 * bpContext - for Bacula debug and jobinfo messages
1005 * rop - a restore object structure to parse
1006 * out:
1007 * bRC_OK - on success
1008 * bRC_Error - on error
1009 */
parse_restoreobj(bpContext * ctx,restore_object_pkt * rop)1010 bRC DKCOMMCTX::parse_restoreobj(bpContext *ctx, restore_object_pkt *rop)
1011 {
1012 int i;
1013
1014 DMSG(ctx, DINFO, "INIcmd: %s\n", command);
1015 if (!ini){
1016 ini = new ConfigFile();
1017 }
1018 if (!ini->dump_string(rop->object, rop->object_len)){
1019 DMSG0(ctx, DERROR, "ini->dump_string failed.\n");
1020 dump_robjdebug(ctx, rop);
1021 return bRC_OK;
1022 }
1023 ini->register_items(plugin_items_dump, sizeof(struct ini_items));
1024 if (!ini->parse(ini->out_fname)){
1025 DMSG0(ctx, DERROR, "ini->parse failed.\n");
1026 dump_robjdebug(ctx, rop);
1027 return bRC_OK;
1028 }
1029 for (i=0; ini->items[i].name; i++){
1030 if (ini->items[i].found){
1031 parse_parameters(ctx, ini->items[i]);
1032 }
1033 }
1034 return bRC_OK;
1035 }
1036
1037 /*
1038 * Sets all to backup variables.
1039 *
1040 * in:
1041 * bpContext - for Bacula debug and jobinfo messages
1042 * out:
1043 * none, internal variables set
1044 */
set_all_to_backup(bpContext * ctx)1045 void DKCOMMCTX::set_all_to_backup(bpContext* ctx)
1046 {
1047 set_all_containers_to_backup(ctx);
1048 set_all_images_to_backup(ctx);
1049 set_all_volumes_to_backup(ctx);
1050 all_to_backup = true;
1051 }
1052
1053 /*
1054 * Sets objs_to_backup list for all containers backup.
1055 */
set_all_containers_to_backup(bpContext * ctx)1056 void DKCOMMCTX::set_all_containers_to_backup(bpContext *ctx)
1057 {
1058 DKINFO *container;
1059
1060 if (all_containers){
1061 foreach_alist(container, all_containers){
1062 objs_to_backup->append(container);
1063 };
1064 }
1065 all_to_backup = true;
1066 };
1067
1068 /*
1069 * Sets objs_to_backup list for all images backup.
1070 */
set_all_images_to_backup(bpContext * ctx)1071 void DKCOMMCTX::set_all_images_to_backup(bpContext *ctx)
1072 {
1073 DKINFO *image;
1074
1075 if (all_images){
1076 foreach_alist(image, all_images){
1077 objs_to_backup->append(image);
1078 };
1079 }
1080 all_to_backup = true;
1081 };
1082
1083 /*
1084 * Sets objs_to_backup list for all volumes backup.
1085 */
set_all_volumes_to_backup(bpContext * ctx)1086 void DKCOMMCTX::set_all_volumes_to_backup(bpContext *ctx)
1087 {
1088 DKINFO *volume;
1089
1090 if (all_volumes){
1091 foreach_alist(volume, all_volumes){
1092 objs_to_backup->append(volume);
1093 };
1094 }
1095 all_to_backup = true;
1096 };
1097
1098 /*
1099 * Sets objs_to_backup list for all containers or images which match the
1100 * container/image id or container/image names parameters from plugin
1101 * command parameters.
1102 *
1103 * in:
1104 * bpContext - for Bacula debug and jobinfo messages
1105 * params - a list of parameters to compare
1106 * dklist - a list of containers/images available
1107 * estimate - set to true when doing estimate
1108 * out:
1109 * this->objs_to_backup updated if required
1110 */
filter_param_to_backup(bpContext * ctx,alist * params,alist * dklist,bool estimate)1111 void DKCOMMCTX::filter_param_to_backup(bpContext *ctx, alist *params, alist *dklist, bool estimate)
1112 {
1113 DKID dkid;
1114 DKINFO *dkinfo;
1115 POOLMEM *pobj;
1116 bool found;
1117
1118 if (params){
1119 /* container parameter configured */
1120 foreach_alist(pobj, params){
1121 found = false;
1122 foreach_alist(dkinfo, dklist){
1123 DMSG3(ctx, DDEBUG, "compare: %s/%s vs %s\n",
1124 (char*)dkinfo->id(), dkinfo->name(), pobj);
1125 /* we have to check container id or container names */
1126 dkid = pobj;
1127 if (bstrcmp(pobj, dkinfo->name()) || dkid == *(dkinfo->id())
1128 || bstrcmp(pobj, dkinfo->get_image_repository())){
1129 /* container or image to backup found */
1130 objs_to_backup->append(dkinfo);
1131 found = true;
1132 DMSG3(ctx, DINFO, "adding %s to backup (1): %s (%s)\n",
1133 dkinfo->type_str(),
1134 dkinfo->name(), (char*)dkinfo->id());
1135 break;
1136 };
1137 };
1138 if (!found){
1139 /* docker object not found */
1140 f_error = true;
1141 if (!estimate){
1142 DMSG1(ctx, DERROR, "Not found to backup: %s!\n", pobj);
1143 JMSG1(ctx, is_fatal() ? M_FATAL : M_ERROR, "Not found to backup: %s!\n", pobj);
1144 } else {
1145 DMSG1(ctx, DERROR, "Not found to estimate: %s!\n", pobj);
1146 JMSG1(ctx, is_fatal() ? M_FATAL : M_ERROR, "Not found to estimate: %s!\n", pobj);
1147 };
1148 };
1149 };
1150 };
1151 };
1152
1153 /*
1154 * It is called when 'allvolumes' parameter is set for backup and add
1155 * a volumes mounted in containers selected to backup by reading
1156 * a Mounts parameter list from docker container.
1157 *
1158 * in:
1159 * bpContext - required for debug/job messages
1160 * out:
1161 * none
1162 */
add_container_volumes_to_backup(bpContext * ctx)1163 void DKCOMMCTX::add_container_volumes_to_backup(bpContext* ctx)
1164 {
1165 DKINFO *container;
1166 DKINFO *volume;
1167 DKINFO *obj;
1168 char *p, *q;
1169 POOL_MEM buf(PM_MESSAGE);
1170 int len;
1171 bool found;
1172 alist containerlist(16, not_owned_by_alist);
1173
1174 DMSG0(ctx, DDEBUG, "add_container_volumes_to_backup called\n");
1175 /* prepare containers to backup list */
1176 foreach_alist(container, objs_to_backup){
1177 if (container->type() == DOCKER_CONTAINER){
1178 containerlist.append(container);
1179 }
1180 }
1181 /* proceed if any container to backup */
1182 if (!containerlist.empty()){
1183 foreach_alist(container, &containerlist){
1184 DMSG1(ctx, DDEBUG, "processing container: %s\n", container->get_container_id());
1185 p = container->get_container_mounts();
1186 if (p != NULL && *p != 0){
1187 /* the container has mounts, so iterate on them and check volumes to backup */
1188 len = strlen(p);
1189 pm_strcpy(buf, p);
1190 p = buf.c_str();
1191 while (*p != 0){
1192 if ((q = strchr(p, ',')) != NULL){
1193 *q = 0; // terminate comma as a string separator
1194 } else {
1195 q = buf.c_str() + len - 1;
1196 }
1197 DMSG1(ctx, DDEBUG, "volmount: %s\n", p);
1198 /* at 'p' we have mounted docker volume name as string
1199 * check if volume already selected for backup */
1200 found = false;
1201 foreach_alist(obj, objs_to_backup){
1202 if (obj->type() == DOCKER_VOLUME && bstrcmp(obj->get_volume_name(), p)){
1203 found = true;
1204 DMSG0(ctx, DDEBUG, "volume found in objs_to_backup, good!\n");
1205 break;
1206 }
1207 };
1208 /* not? simple check in volume list and add it to backup */
1209 if (!found){
1210 foreach_alist(volume, all_volumes){
1211 if (bstrcmp(volume->get_volume_name(), p)){
1212 /* this volume we should add for backup */
1213 objs_to_backup->append(volume);
1214 DMSG0(ctx, DDEBUG, "adding volume to backup!\n");
1215 break;
1216 }
1217 };
1218 }
1219 /* next in list */
1220 p = q + 1;
1221 }
1222 }
1223 }
1224 }
1225
1226 DMSG0(ctx, DDEBUG, "add_container_volumes_to_backup finish.\n");
1227 };
1228
1229 /*
1230 * It creates a list of volumes to backup for a particular container
1231 * which are selected manually to backup and should be reflected in
1232 * catalog database as a volumes links. It is called after 'allvolumes'
1233 * parameter verification.
1234 *
1235 * in:
1236 * bpContext - required for debug/job messages
1237 * out:
1238 * none
1239 */
select_container_vols(bpContext * ctx)1240 void DKCOMMCTX::select_container_vols(bpContext* ctx)
1241 {
1242 DKINFO *container;
1243 DKINFO *volume;
1244 DKVOLS *vols;
1245 char *p, *q;
1246 alist vollist(16, not_owned_by_alist);
1247 int len;
1248 POOL_MEM buf(PM_MESSAGE);
1249
1250 DMSG0(ctx, DDEBUG, "select_container_vols called\n");
1251 /* prepare volume to backup list */
1252 foreach_alist(volume, objs_to_backup){
1253 if (volume->type() == DOCKER_VOLUME){
1254 vollist.append(volume);
1255 }
1256 }
1257 /* proceed if any volume to backup */
1258 if (!vollist.empty()){
1259 foreach_alist(container, objs_to_backup){
1260 if (container->type() == DOCKER_CONTAINER){
1261 DMSG1(ctx, DDEBUG, "processing container: %s\n", container->get_container_id());
1262 p = container->get_container_mounts();
1263 if (p != NULL && *p != 0){
1264 /* the container has mounts, so iterate on them and check volumes to backup */
1265 len = strlen(p);
1266 pm_strcpy(buf, p);
1267 p = buf.c_str();
1268 while (*p != 0){
1269 if ((q = strchr(p, ',')) != NULL){
1270 *q = 0; // terminate comma as a string separator
1271 } else {
1272 q = buf.c_str() + len - 1;
1273 }
1274 DMSG1(ctx, DDEBUG, "volmount: %s\n", p);
1275 if (*p != '/'){
1276 foreach_alist(volume, &vollist){
1277 if (bstrcmp(volume->get_volume_name(), p)){
1278 volume->inc_volume_linknr();
1279 vols = New(DKVOLS(volume));
1280 update_vols_mounts(ctx, container, vols);
1281 container->container_append_vols(vols);
1282 DMSG0(ctx, DDEBUG, "adding to vols\n");
1283 break;
1284 }
1285 };
1286 }
1287 /* next param */
1288 p = q + 1;
1289 }
1290 }
1291 }
1292 };
1293 }
1294 DMSG0(ctx, DDEBUG, "select_container_vols finish.\n");
1295 };
1296
1297 /*
1298 * Checks for common Docker error strings.
1299 *
1300 * in:
1301 * bpContext - required for debug/job messages
1302 * buf - the string to scan
1303 * out:
1304 * true - when a common error found
1305 * false - no common errors found
1306 */
check_for_docker_errors(bpContext * ctx,char * buf)1307 bool DKCOMMCTX::check_for_docker_errors(bpContext* ctx, char* buf)
1308 {
1309 const char *err1 = "Cannot connect to the Docker daemon";
1310 const char *err2 = "Unable to find image '" BACULATARIMAGE "' locally";
1311 int len;
1312
1313 /* no docker running error */
1314 len = strlen(err1);
1315 if (strncmp(buf, err1, len) == 0){
1316 DMSG1(ctx, DERROR, "no docker running error! Err=%s\n", buf);
1317 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "No Docker is running. Cannot continue!\n");
1318 return true;
1319 }
1320
1321 /* cannot find baculatar image */
1322 len = strlen(err2);
1323 if (strncmp(buf, err2, len) == 0){
1324 DMSG1(ctx, DERROR, "cannot find baculatar image! Err=%s\n", buf);
1325 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR,
1326 "Docker is unable to find required Bacula container backup image. Cannot continue!\n");
1327 return true;
1328 }
1329
1330 return false;
1331 };
1332
1333 /*
1334 * Sets objs_to_backup list for all containers or images which match the
1335 * container/image names based on include_* / exclude_* regex parameters from plugin
1336 * command parameters.
1337 *
1338 * in:
1339 * bpContext - for Bacula debug and jobinfo messages
1340 * params_include - a list of include regex parameters to match
1341 * params_exclude - a list of exclude regex parameters to not match
1342 * dklist - a list of containers/images available
1343 * out:
1344 * this->objs_to_backup updated if required
1345 */
filter_incex_to_backup(bpContext * ctx,alist * params_include,alist * params_exclude,alist * dklist)1346 void DKCOMMCTX::filter_incex_to_backup(bpContext* ctx, alist* params_include, alist *params_exclude, alist* dklist)
1347 {
1348 alist inex_list(16, not_owned_by_alist);
1349 POOLMEM *expr;
1350 bool found;
1351 int options = REG_EXTENDED | REG_ICASE;
1352 int rc, indx;
1353 char prbuf[500];
1354 DKINFO *dkinfo;
1355
1356 /* prepare a list of objects from include regex */
1357 if (params_include){
1358 foreach_alist(expr, params_include){
1359 DMSG(ctx, DDEBUG, "processing include: %s\n", expr);
1360 rc = regcomp(&preg, expr, options);
1361 if (rc != 0) {
1362 f_error = true;
1363 regerror(rc, &preg, prbuf, sizeof(prbuf));
1364 DMSG(ctx, DERROR, "include regex compilation error: %s\n", prbuf);
1365 JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "include_container regex compilation error: %s\n", prbuf);
1366 continue;
1367 }
1368 /* include regex compiled, so iterate through all_containers */
1369 foreach_alist(dkinfo, dklist){
1370 rc = regexec(&preg, dkinfo->name(), 0, NULL, 0);
1371 if (rc == 0){
1372 /* found */
1373 inex_list.append(dkinfo);
1374 DMSG2(ctx, DDEBUG, "include %s found: %s\n", dkinfo->type_str(), dkinfo->name());
1375 }
1376 };
1377 regfree(&preg);
1378 };
1379 }
1380
1381 /* exclude objects from include list using exclude regex */
1382 if (params_exclude){
1383 foreach_alist(expr, params_exclude){
1384 DMSG(ctx, DDEBUG, "processing exclude: %s\n", expr);
1385 rc = regcomp(&preg, expr, options);
1386 if (rc != 0) {
1387 f_error = true;
1388 regerror(rc, &preg, prbuf, sizeof(prbuf));
1389 DMSG(ctx, DERROR, "exclude regex compilation error: %s\n", prbuf);
1390 JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "exclude regex compilation error: %s\n", prbuf);
1391 continue;
1392 }
1393 /* iterate through objects list found used params_include */
1394 found = true;
1395 while (found){
1396 foreach_alist(dkinfo, &inex_list){
1397 DMSG2(ctx, DDEBUG, "exclude processing %s: %s\n", dkinfo->type_str(), dkinfo->name());
1398 rc = regexec(&preg, dkinfo->name(), 0, NULL, 0);
1399 if (rc == 0){
1400 /* found */
1401 indx = inex_list.current() - 1;
1402 DMSG(ctx, DVDEBUG, "inex_list_indx: %d\n", indx);
1403 inex_list.remove(indx);
1404 /* we have to start again as inex_list->cur_item points to the wrong position */
1405 DMSG2(ctx, DDEBUG, "exclude %s found: %s\n", dkinfo->type_str(), dkinfo->name());
1406 break;
1407 }
1408 };
1409 if (!dkinfo){
1410 DMSG0(ctx, DDEBUG, "exclude no more objects to check\n");
1411 found = false;
1412 }
1413 }
1414 regfree(&preg);
1415 };
1416 }
1417 if (inex_list.size()){
1418 /* move dkinfos to objs_to_backup list */
1419 foreach_alist(dkinfo, &inex_list){
1420 objs_to_backup->append(dkinfo);
1421 DMSG3(ctx, DINFO, "adding %s to backup (2): %s (%s)\n",
1422 dkinfo->type_str(),
1423 dkinfo->name(), (char*)dkinfo->id());
1424 };
1425 }
1426 };
1427
1428 /*
1429 * Prepares a DKCOMMCTX class for a single Plugin parameters for backup and estimate jobs.
1430 * The main purpose is to set a objs_to_backup list for a list of vms to backup.
1431 *
1432 * in:
1433 * bpContext - for Bacula debug and jobinfo messages
1434 * estimate - if the preparation for estimate (true) or backup (false) job
1435 * out:
1436 * bRC_OK - when preparation was successful
1437 * bRC_Error - on any error
1438 */
prepare_bejob(bpContext * ctx,bool estimate)1439 bRC DKCOMMCTX::prepare_bejob(bpContext *ctx, bool estimate)
1440 {
1441 /* get list of all objects */
1442 if (!get_all_containers(ctx) || !get_all_images(ctx)){
1443 return bRC_Error;
1444 }
1445 /* when docker_host defined then skip all volumes */
1446 if (param_docker_host == NULL && !get_all_volumes(ctx)){
1447 return bRC_Error;
1448 }
1449
1450 /* when no volume/container/image/include/exclude parameters found that all objects should be saved */
1451 if (!param_container && !param_image && !param_include_container && !param_exclude_container
1452 && !param_include_image && !param_exclude_image && !param_volume){
1453 set_all_to_backup(ctx);
1454 } else {
1455 all_to_backup = false;
1456 /* find all objects on param_* lists */
1457 filter_param_to_backup(ctx, param_container, all_containers, estimate);
1458 filter_param_to_backup(ctx, param_image, all_images, estimate);
1459 if (param_volume && param_docker_host == NULL){
1460 filter_param_to_backup(ctx, param_volume, all_volumes, estimate);
1461 }
1462
1463 /* handle include/exclude regex for containers and images only */
1464 filter_incex_to_backup(ctx, param_include_container, param_exclude_container, all_containers);
1465 filter_incex_to_backup(ctx, param_include_image, param_exclude_image, all_images);
1466
1467 /* handle allvolumes for containers backup */
1468 if (all_vols_to_backup && param_docker_host == NULL){
1469 add_container_volumes_to_backup(ctx);
1470 }
1471
1472 /* generate a warning message if required */
1473 if ((param_volume || all_vols_to_backup) && param_docker_host){
1474 DMSG0(ctx, DINFO, "Docker Volume backup with docker_host is unsupported!\n");
1475 JMSG0(ctx, M_WARNING, "Docker Volume backup with docker_host is unsupported!\n");
1476 }
1477 }
1478
1479 select_container_vols(ctx);
1480
1481 return bRC_OK;
1482 }
1483
1484 /*
1485 * Prepares a DKCOMMCTX class for a single Plugin parameters for restore job.
1486 * The main purpose is to handle storage_res restore parameter.
1487 *
1488 * in:
1489 * bpContext - for Bacula debug and jobinfo messages
1490 * out:
1491 * bRC_OK - when preparation was successful
1492 * bRC_Error - on any error (unimplemented)
1493 */
prepare_restore(bpContext * ctx)1494 bRC DKCOMMCTX::prepare_restore(bpContext* ctx)
1495 {
1496 DMSG0(ctx, DDEBUG, "prepare_restore called\n");
1497 return bRC_OK;
1498 }
1499
1500 /*
1501 * Setup DKINFO class values based on object type and string parameters from
1502 * docker command output.
1503 *
1504 * in:
1505 * bpContext - for Bacula debug and jobinfo messages
1506 * type - a docker object type
1507 * paramtab - a table of docker command output values
1508 * dkinfo - a class to setup
1509 * out:
1510 * dkinfo updated
1511 */
setup_dkinfo(bpContext * ctx,DKINFO_OBJ_t type,char * paramtab[],DKINFO * dkinfo)1512 void DKCOMMCTX::setup_dkinfo(bpContext* ctx, DKINFO_OBJ_t type, char *paramtab[], DKINFO *dkinfo)
1513 {
1514 switch (type){
1515 case DOCKER_CONTAINER:
1516 setup_container_dkinfo(ctx, paramtab, dkinfo);
1517 break;
1518 case DOCKER_IMAGE:
1519 setup_image_dkinfo(ctx, paramtab, dkinfo);
1520 break;
1521 case DOCKER_VOLUME:
1522 setup_volume_dkinfo(ctx, paramtab, dkinfo);
1523 break;
1524 }
1525 };
1526
1527 /*
1528 * Setup DKINFO container class values based on string parameters from docker command output.
1529 * It is required to setup a following parameters in paramtab array
1530 * [0] - container id
1531 * [1] - container name
1532 * [2] - container size
1533 * other parameters will be ignored.
1534 *
1535 * in:
1536 * bpContext - for Bacula debug and jobinfo messages
1537 * paramtab - a table of docker command output values
1538 * dkinfo - a class to setup
1539 * out:
1540 * dkinfo updated
1541 */
setup_container_dkinfo(bpContext * ctx,char * paramtab[],DKINFO * dkinfo)1542 void DKCOMMCTX::setup_container_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo)
1543 {
1544 dkinfo->set_container_id(paramtab[0]);
1545 dkinfo->set_container_names(paramtab[1]);
1546 dkinfo->scan_container_size(paramtab[2]);
1547 dkinfo->set_container_mounts(paramtab[3]);
1548 DMSG3(ctx, DINFO, "setup_container_dkinfo: %s %s %d\n",
1549 (char*)dkinfo->get_container_id(), dkinfo->get_container_names(), dkinfo->get_container_size());
1550 DMSG1(ctx, DINFO, "setup_container_dkinfo: %s\n", dkinfo->get_container_mounts());
1551 };
1552
1553 /*
1554 * Setup DKINFO image class values based on string parameters from docker command output.
1555 * It is required to setup a following parameters in paramtab array
1556 * [0] - image id
1557 * [1] - image repository
1558 * [2] - image tag
1559 * [3] - image size
1560 * [4] - image creation date
1561 * other parameters will be ignored.
1562 *
1563 * in:
1564 * bpContext - for Bacula debug and jobinfo messages
1565 * paramtab - a table of docker command output values
1566 * dkinfo - a class to setup
1567 * out:
1568 * dkinfo updated
1569 */
setup_image_dkinfo(bpContext * ctx,char * paramtab[],DKINFO * dkinfo)1570 void DKCOMMCTX::setup_image_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo)
1571 {
1572 dkinfo->set_image_id(paramtab[0]);
1573 dkinfo->set_image_repository(paramtab[1]);
1574 dkinfo->set_image_tag(paramtab[2]);
1575 dkinfo->scan_image_size(paramtab[3]);
1576 dkinfo->set_image_created(str_to_utime(paramtab[4]));
1577 DMSG3(ctx, DINFO, "setup_image_dkinfo: %s %s : %s\n",
1578 (char*)dkinfo->get_image_id(), dkinfo->get_image_repository(), dkinfo->get_image_tag());
1579 DMSG2(ctx, DINFO, "setup_image_dkinfo: %d %ld\n", dkinfo->get_image_size(), dkinfo->get_image_created());
1580 };
1581
1582 /*
1583 * Setup DKINFO volume class values based on string parameters from docker command output.
1584 * It is required to setup a following parameters in paramtab array
1585 * [0] - volume name
1586 * [1] - volume size
1587 * other parameters will be ignored.
1588 *
1589 * To get a volume created time you have to inspect details of the particular volume:
1590 * $ docker volume inspect --format "{{.CreatedAt}}" 0a5f9bf16602f2de6c9e701e6fb6d4ce1292ee336348c7c2624f4a08aaacebc4
1591 * 2019-06-21T17:11:33+02:00
1592 *
1593 * in:
1594 * bpContext - for Bacula debug and jobinfo messages
1595 * paramtab - a table of docker command output values
1596 * dkinfo - a class to setup
1597 * out:
1598 * dkinfo updated
1599 */
setup_volume_dkinfo(bpContext * ctx,char * paramtab[],DKINFO * dkinfo)1600 void DKCOMMCTX::setup_volume_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo)
1601 {
1602 dkinfo->set_volume_name(paramtab[0]);
1603 dkinfo->scan_volume_size(paramtab[1]);
1604 DMSG2(ctx, DINFO, "setup_volume_dkinfo: %s %ld\n",
1605 dkinfo->get_volume_name(), dkinfo->get_volume_size());
1606 };
1607
1608 /*
1609 * Setup a list of objects based on docker command execution.
1610 * The docker command should format its output into a number of columns separated by a tab character - '/t'
1611 * The function support no more then 10 columns to scan. In current implementation we are using no more then 6.
1612 * It will setup a list of all docker objects at 'dklist' list based on 'type' of docker objects.
1613 *
1614 * in:
1615 * bpContext - for Bacula debug and jobinfo messages
1616 * cmd - a docker command to execute
1617 * cols - a number of columns to scan
1618 * dklist - a pointer to dklist variable to update
1619 * type - a docker object type to setup
1620 * out:
1621 * dklist - a new list allocated and populated
1622 */
get_all_list_from_docker(bpContext * ctx,const char * cmd,int cols,alist ** dklist,DKINFO_OBJ_t type)1623 alist *DKCOMMCTX::get_all_list_from_docker(bpContext* ctx, const char *cmd, int cols, alist **dklist, DKINFO_OBJ_t type)
1624 {
1625 POOL_MEM out(PM_MESSAGE);
1626 int a;
1627 char *paramtab[10];
1628 int status;
1629 DKINFO *dkinfo;
1630 char *p, *q, *t;
1631
1632 if (cols > 10){
1633 DMSG1(ctx, DERROR, "BUG! unsupported number of parameter columns: %d\n", cols);
1634 JMSG1(ctx, M_FATAL, "Unsupported number of parameter columns: %d You should call a support!\n", cols);
1635 return NULL;
1636 }
1637 /* invalid pointer to list*/
1638 if (!dklist){
1639 DMSG0(ctx, DERROR, "BUG! invalid pointer to dklist\n");
1640 return NULL;
1641 }
1642 if (!*dklist){
1643 DMSG0(ctx, DINFO, "get_all_list_from_docker called\n");
1644 /* first get all containers list */
1645 if (!execute_command(ctx, cmd)){
1646 /* some error executing command */
1647 DMSG0(ctx, DERROR, "get_all_list_from_docker execution error\n");
1648 return NULL;
1649 }
1650 /* allocate a new list */
1651 *dklist = New(alist(32, not_owned_by_alist));
1652 memset(out.c_str(), 0, out.size());
1653 if ((status = read_output(ctx, out)) > 0){
1654 /* we read a string, so terminate it with nul char */
1655 p = out.c_str();
1656 p[status] = 0;
1657 while (*p != 0 && (q = strchr(p, '\n')) != NULL){
1658 /* p is the start of the string and q is the end of line */
1659 *q = 0; // q+1 will be the next line
1660 DMSG(ctx, DVDEBUG, "get_all_list_from_docker scanning: %s\n", p);
1661 if (check_for_docker_errors(ctx, p)){
1662 goto bailout; // TODO: remove this goto
1663 }
1664 t = p;
1665 /* expect 5 tabs-separators for 6 parameters handled in-place */
1666 for (a = 0; a < cols; a++){
1667 paramtab[a] = t;
1668 t = strchr(t, '\t');
1669 if (t != NULL){
1670 *t = 0;
1671 t++; // next param
1672 } else {
1673 break; // finish scanning
1674 }
1675 }
1676 for (a = 0; a < cols; a++){
1677 DMSG2(ctx, DDEBUG, "get_all_list_from_docker paramtab[%d]: %s\n", a, paramtab[a]);
1678 }
1679 /* so, the single line is between ( p ; q ) and consist of 6 nul terminated string parameters */
1680 dkinfo = New(DKINFO(type));
1681 setup_dkinfo(ctx, type, paramtab, dkinfo);
1682 (*dklist)->append(dkinfo);
1683 if (dkinfo->type() != DOCKER_VOLUME){
1684 DMSG3(ctx, DDEBUG, "found %s: %s -> %s\n", dkinfo->type_str(), (char*)dkinfo->id(), dkinfo->name());
1685 } else {
1686 DMSG2(ctx, DDEBUG, "found %s: %s\n", dkinfo->type_str(), dkinfo->name());
1687 }
1688 /* next line */
1689 DMSG0(ctx, DVDEBUG, "get_all_list_from_docker next line\n");
1690 p = q + 1;
1691 }
1692 } else {
1693 DMSG0(ctx, DINFO, "get_all_list_from_docker no container found.\n");
1694 }
1695 terminate(ctx);
1696 } else {
1697 DMSG1(ctx, DINFO, "get_all_list_from_docker used cached data: %p\n", *dklist);
1698 }
1699
1700 bailout:
1701 DMSG0(ctx, DINFO, "get_all_list_from_docker finish.\n");
1702 return *dklist;
1703 }
1704
1705 /*
1706 * Updates a docker volume mount point in docker list of mounted vols
1707 * for proper volume support file (link) name rendering as: <volname> -> </mount/dir>
1708 *
1709 * in:
1710 * bpContext - for Bacula debug and jobinfo messages
1711 * dkinfo - the container to scan
1712 * dkvols - the volume mount information to update
1713 * out:
1714 * none
1715 */
update_vols_mounts(bpContext * ctx,DKINFO * container,DKVOLS * volume)1716 void DKCOMMCTX::update_vols_mounts(bpContext* ctx, DKINFO *container, DKVOLS *volume)
1717 {
1718 POOL_MEM out(PM_MESSAGE);
1719 POOL_MEM cmd(PM_MESSAGE);
1720 int status;
1721 char *p, *q, *t;
1722
1723 DMSG0(ctx, DINFO, "update_volume_mounts called\n");
1724 if (container && volume){
1725 /* get details about container mounts */
1726 Mmsg(cmd, "container inspect --format '{{range .Mounts}}{{.Name}}{{print \"\\t\"}}{{println .Destination}}{{end}}' %s", container->get_container_id());
1727 if (!execute_command(ctx, cmd)){
1728 /* some error executing command */
1729 DMSG0(ctx, DERROR, "update_volume_mounts execution error\n");
1730 return;
1731 }
1732 /* process data:
1733 * aa9d3074f8c65a5afafddc6eaaf9827e99bb51f676aafaacc05cfca0188e65bf /var/log\n
1734 * 9194f415cafcf8d234673478f3358728d43e0203e58d0338b4ee18a4dca6646b /etc/logrotate.d\n
1735 * (...)
1736 */
1737 if ((status = read_output(ctx, out)) > 0){
1738 /* we read a string, so terminate it with nul char */
1739 p = out.c_str();
1740 p[status] = 0;
1741 while (*p != 0 && (q = strchr(p, '\n')) != NULL){
1742 /* p is the start of the string and q is the end of line */
1743 *q = 0; // q+1 will be the next line
1744 DMSG(ctx, DVDEBUG, "update_volume_mounts scanning: %s\n", p);
1745 if (check_for_docker_errors(ctx, p)){
1746 return;
1747 }
1748 /* expect 1 tab-separator for 2 parameters handled in-place */
1749 t = strchr(p, '\t');
1750 if (t != NULL){
1751 *t++ = 0;
1752 } else {
1753 /* scan error */
1754 return;
1755 }
1756 DMSG2(ctx, DDEBUG, "update_volume_mounts volname: %s dest: %s\n", p, t);
1757 if (bstrcmp(volume->vol->get_volume_name(), p)){
1758 /* this is the volume we are looking for */
1759 pm_strcpy(volume->destination, t);
1760 return;
1761 }
1762 /* next line */
1763 DMSG0(ctx, DVDEBUG, "get_all_list_from_docker next line\n");
1764 p = q + 1;
1765 }
1766 } else {
1767 DMSG0(ctx, DINFO, "get_all_list_from_docker no container found.\n");
1768 }
1769 terminate(ctx);
1770 } else {
1771 DMSG2(ctx, DERROR, "invalid parameters: c:%p v:%p\n", container, volume);
1772 return;
1773 }
1774 DMSG0(ctx, DINFO, "update_volume_mounts finish.\n");
1775 }
1776
1777 /*
1778 * Return a list of all containers available on Docker.
1779 *
1780 * the container list is described as the following text block:
1781 # docker ps -a --no-trunc=true --format "{{.ID}}\t{{.Names}}\t{{.Size}}\t{{.Mounts}}\t{{.Labels}}\t{{.Image}}"
1782 66f45d8601bae26a6b2ffeb46922318534d3b3905377b3a224693bd78601cb3b brave_edison 0B (virtual 228MB) c0a478d317195ba27dda1370b73e5cb94a7773f2a611142d7dff690abdcfdcbf postgres\n
1783
1784 *
1785 * in:
1786 * bpContext - for Bacula debug jobinfo messages
1787 * out:
1788 * NULL - on any error
1789 * empty alist - when no docker comntainers found
1790 * alist - a list of DKINFO class which describe a single docker container
1791 */
get_all_containers(bpContext * ctx)1792 alist *DKCOMMCTX::get_all_containers(bpContext* ctx)
1793 {
1794 return get_all_list_from_docker(ctx,
1795 "ps -a --no-trunc=true --format \"{{.ID}}\\t{{.Names}}\\t{{.Size}}\\t{{.Mounts}}\\t{{.Labels}}\\t{{.Image}}\"",
1796 6, &all_containers, DOCKER_CONTAINER);
1797 };
1798
1799 /*
1800 * Return a list of all images available on Docker.
1801 *
1802 * the images list is described as the following text block:
1803 # # docker image ls --no-trunc=true --format "{{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}"
1804 restore/a6ba1cb597d5 latest sha256:44c34a8a510dc08b7b0a8f6961257e89120152739e61611f564353e8feb95e68 319MB\n
1805
1806 *
1807 * in:
1808 * bpContext - for Bacula debug jobinfo messages
1809 * out:
1810 * NULL - on any error
1811 * empty alist - when no vms found
1812 * alist - a list of DKINFO class which describe a single VM
1813 */
get_all_images(bpContext * ctx)1814 alist *DKCOMMCTX::get_all_images(bpContext* ctx)
1815 {
1816 return get_all_list_from_docker(ctx,
1817 "image ls --no-trunc=true --format \"{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.Size}}\\t{{.CreatedAt}}\"",
1818 5, &all_images, DOCKER_IMAGE);
1819 }
1820
1821 /*
1822 * Return a list of all volumes available on Docker.
1823 *
1824 * the volumes list is described as the following text block:
1825 # # docker volume ls --format "{{.Name}}\t{{.Size}}"
1826 0a5f9bf16602f2de6c9e701e6fb6d4ce1292ee336348c7c2624f4a08aaacebc4 N/A\n
1827
1828 *
1829 * in:
1830 * bpContext - for Bacula debug jobinfo messages
1831 * out:
1832 * NULL - on any error
1833 * empty alist - when no vms found
1834 * alist - a list of DKINFO class which describe a single VM
1835 */
get_all_volumes(bpContext * ctx)1836 alist *DKCOMMCTX::get_all_volumes(bpContext* ctx)
1837 {
1838 return get_all_list_from_docker(ctx,
1839 "volume ls --format \"{{.Name}}\\t{{.Size}}\"", 2, &all_volumes, DOCKER_VOLUME);
1840 }
1841
1842 /*
1843 * Rename/set the docker image tag on selected image.
1844 */
docker_tag(bpContext * ctx,DKID & dkid,POOLMEM * tag)1845 bRC DKCOMMCTX::docker_tag(bpContext* ctx, DKID &dkid, POOLMEM *tag)
1846 {
1847 bRC rc = bRC_OK;
1848 POOL_MEM cmd(PM_FNAME);
1849 POOL_MEM out(PM_BSOCK);
1850 int status;
1851
1852 DMSG0(ctx, DINFO, "docker_tag called.\n");
1853 if (!tag){
1854 DMSG0(ctx, DERROR, "docker_tag tag is NULL!\n");
1855 return bRC_Error;
1856 }
1857 Mmsg(cmd, "image tag %s %s", (char*)dkid, tag);
1858 DMSG1(ctx, DDEBUG, "%s\n", cmd.c_str());
1859 if (!execute_command(ctx, cmd)){
1860 /* some error executing command */
1861 DMSG0(ctx, DERROR, "docker_tag execution error\n");
1862 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "docker_tag execution error\n");
1863 return bRC_Error;
1864 }
1865
1866 memset(out.c_str(), 0, out.size());
1867 status = read_output(ctx, out);
1868 if (status < 0){
1869 /* error reading data from docker command */
1870 DMSG0(ctx, DERROR, "docker_tag error reading data from docker command\n");
1871 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "docker_tag error reading data from docker command\n");
1872 rc = bRC_Error;
1873 } else
1874 if (status > 0 && check_for_docker_errors(ctx, out.c_str())){
1875 rc = bRC_Error;
1876 }
1877
1878 /* we do not expect any output here */
1879 terminate(ctx);
1880 DMSG0(ctx, DINFO, "docker_tag finish.\n");
1881 return rc;
1882 };
1883
1884 /*
1885 * Waits for restore commands to finish.
1886 * Closes a BPIPE write descriptor which means EOF to command tool. Then try
1887 * to read from tools output and checks if restore was successful or not.
1888 *
1889 * in:
1890 * bpContext - for Bacula debug jobinfo messages
1891 * out:
1892 * bRC_OK - restore was successful and vmuuid is filled with VM UUID restored.
1893 * bRC_Error - on any error
1894 */
wait_for_restore(bpContext * ctx,DKID & dkid)1895 bRC DKCOMMCTX::wait_for_restore(bpContext *ctx, DKID &dkid)
1896 {
1897 POOL_MEM out(PM_BSOCK);
1898 POOL_MEM buf(PM_BSOCK);
1899 int status;
1900 char *p;
1901 bRC rc = bRC_OK;
1902
1903 DMSG0(ctx, DINFO, "wait_for_restore called.\n");
1904 /* first flush any outstanding restore data and close write descriptor */
1905 close_wpipe(bpipe);
1906 /* now read the status from command */
1907 while ((status = read_output(ctx, out)) != 0){
1908 if (status < 0){
1909 /* error reading data from command tool */
1910 DMSG0(ctx, DERROR, "error reading data from command tool\n");
1911 rc = bRC_Error;
1912 goto bailout;
1913 }
1914 pm_strcat(buf, out);
1915 p = buf.c_str();
1916 p[status] = 0;
1917 }
1918
1919 /* check for errors */
1920 DMSG1(ctx, DVDEBUG, "bufout: %s\n", buf.c_str());
1921 p = buf.c_str();
1922 if (strstr(p, "Loaded image ID: ") == NULL){
1923 /* error, missing confirmation*/
1924 DMSG0(ctx, DERROR, "wait_for_restore confirmation error!\n");
1925 JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR, "Image restore commit error: %s\n", p);
1926 rc = bRC_Error;
1927 } else {
1928 dkid = (char*)(p+17);
1929 DMSG1(ctx, DDEBUG, "scanned dkid: %s\n", (char*)dkid);
1930 }
1931
1932 bailout:
1933 terminate(ctx);
1934 DMSG0(ctx, DINFO, "wait_for_restore finish.\n");
1935 return rc;
1936 }
1937
1938 /*
1939 * Executes docker command tool to make a container image commit.
1940 * Setup dkinfo->data.container.imagesave with image id of newly created image
1941 * which should be used at image save method.
1942 *
1943 * in:
1944 * bpContext - for Bacula debug jobinfo messages
1945 * dkinfo - container for commit
1946 * out:
1947 * bRC_OK - when command execution was successful
1948 * bRC_Error - on any error
1949 */
container_commit(bpContext * ctx,DKINFO * dkinfo,int jobid)1950 bRC DKCOMMCTX::container_commit(bpContext* ctx, DKINFO *dkinfo, int jobid)
1951 {
1952 POOL_MEM cmd(PM_FNAME);
1953 POOL_MEM imagename(PM_FNAME);
1954 POOL_MEM out(PM_MESSAGE);
1955 const char *mode = "";
1956 const char *PAUSE = "-p";
1957 DKID imagesave;
1958 bRC rc = bRC_OK;
1959 int status;
1960 char *p;
1961
1962 DMSG0(ctx, DINFO, "container_commit called.\n");
1963 if (dkinfo->type() != DOCKER_CONTAINER){
1964 /* commit works on containers only */
1965 return bRC_Error;
1966 }
1967 if (param_mode == DKPAUSE){
1968 mode = PAUSE;
1969 }
1970 // commit -p 66f45d8601bae26a6b2ffeb46922318534d3b3905377b3a224693bd78601cb3b mcache1/66f45d8601ba:backup
1971 render_imagesave_name(imagename, dkinfo, jobid);
1972 Mmsg(cmd, "commit %s %s %s", mode, (char*)dkinfo->get_container_id(), imagename.c_str());
1973 if (!execute_command(ctx, cmd)){
1974 /* some error executing command */
1975 DMSG0(ctx, DERROR, "container_commit execution error\n");
1976 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "container_commit execution error\n");
1977 return bRC_Error;
1978 }
1979
1980 memset(out.c_str(), 0, out.size());
1981 status = read_output(ctx, out);
1982 if (status < 0){
1983 /* error reading data from docker command */
1984 DMSG0(ctx, DERROR, "container_commit error reading data from docker command\n");
1985 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "container_commit error reading data from docker command\n");
1986 rc = bRC_Error;
1987 } else {
1988 /* terminate committed image id string */
1989 p = out.c_str();
1990 p[status] = 0;
1991 strip_trailing_junk(out.c_str());
1992
1993 /* check a known output error */
1994 if (status > 0 && check_for_docker_errors(ctx, out.c_str())){
1995 rc = bRC_Error;
1996 } else {
1997 // should return a string: sha256:290835d692069c376072061362cb11b1e99efd555f6fb83b7be3e524ba6067fc
1998 imagesave = p;
1999 if (imagesave.id() < 0){
2000 /* error scanning image id */
2001 DMSG1(ctx, DERROR, "container_commit cannot scan commit image id. Err=%s\n", p);
2002 JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR, "container_commit cannot scan commit image id. Err=%s\n", p);
2003 rc = bRC_Error;
2004 } else {
2005 dkinfo->set_container_imagesave(imagesave);
2006 dkinfo->set_container_imagesave_tag(imagename);
2007 DMSG(ctx, DINFO, "Commit created: %s\n", dkinfo->get_container_imagesave_tag());
2008 JMSG(ctx, M_INFO, "Commit created: %s\n", dkinfo->get_container_imagesave_tag());
2009 }
2010 }
2011 }
2012
2013 terminate(ctx);
2014 DMSG0(ctx, DINFO, "container_commit finish.\n");
2015 return rc;
2016 }
2017
2018 /*
2019 * Executes docker command tool to make a container image commit.
2020 * Setup dkinfo->data.container.imagesave with image id of newly created image
2021 * which should be used at image save method.
2022 *
2023 * in:
2024 * bpContext - for Bacula debug jobinfo messages
2025 * dkinfo - container for commit
2026 * out:
2027 * bRC_OK - when command execution was successful
2028 * bRC_Error - on any error
2029 */
delete_container_commit(bpContext * ctx,DKINFO * dkinfo,int jobid)2030 bRC DKCOMMCTX::delete_container_commit(bpContext* ctx, DKINFO *dkinfo, int jobid)
2031 {
2032 POOL_MEM cmd(PM_FNAME);
2033 POOL_MEM imagename(PM_FNAME);
2034 POOL_MEM out(PM_MESSAGE);
2035 DKID imagesave;
2036 bRC rc = bRC_OK;
2037 int status;
2038 char *p, *q;
2039 int noerror = 0;
2040
2041 DMSG0(ctx, DINFO, "delete_container_commit called.\n");
2042 if (dkinfo->type() != DOCKER_CONTAINER){
2043 /* commit works on containers only, silently ignore images */
2044 return bRC_OK;
2045 }
2046
2047 if (dkinfo->get_container_imagesave()->id() > 0){
2048 /* container has commit image */
2049 /*
2050 # docker rmi e7cd2a7f1c52a1fa8d88ab812abdcd814064e4884a12bd1f9acde16133023a69
2051 Untagged: mcache1/66f45d8601ba/123:backup
2052 Deleted: sha256:e7cd2a7f1c52a1fa8d88ab812abdcd814064e4884a12bd1f9acde16133023a69
2053 */
2054
2055 Mmsg(cmd, "rmi %s", (char*)dkinfo->get_container_imagesave());
2056 if (!execute_command(ctx, cmd)){
2057 /* some error executing command */
2058 DMSG0(ctx, DERROR, "delete_container_commit execution error\n");
2059 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "delete_container_commit execution error\n");
2060 return bRC_Error;
2061 }
2062
2063 memset(out.c_str(), 0, out.size());
2064 status = read_output(ctx, out);
2065 if (status < 0){
2066 /* error reading data from docker command */
2067 DMSG0(ctx, DERROR, "delete_container_commit error reading data from docker command\n");
2068 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR,
2069 "delete_container_commit error reading data from docker command\n");
2070 rc = bRC_Error;
2071 goto bailout;
2072 }
2073
2074 /* terminate output string */
2075 p = out.c_str();
2076 p[status] = 0;
2077
2078 /* check a known output error */
2079 if (status > 0 && (strncmp(out.c_str(), "Cannot connect to the Docker daemon", 35) == 0)){
2080 DMSG1(ctx, DERROR, "No Docker is running. Cannot continue! Err=%s\n", out.c_str());
2081 JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR, "No Docker is running. Err=%s\n", out.c_str());
2082 rc = bRC_Error;
2083 goto bailout;
2084 }
2085
2086 render_imagesave_name(imagename, dkinfo, jobid);
2087
2088 /* it should return the following string:
2089 Untagged: mcache1/66f45d8601ba:backup\n
2090 Deleted: sha256:e7cd2a7f1c52a1fa8d88ab812abdcd814064e4884a12bd1f9acde16133023a69\n
2091 */
2092 while (*p != 0 && (q = strchr(p, '\n')) != NULL){
2093 /* p is the start of the string and q is the end of line */
2094 *q = 0;
2095 DMSG(ctx, DVDEBUG, "delete_container_commit scanning: %s\n", p);
2096 if (strstr(p, "Untagged: ") == p && strstr(p, imagename.c_str()) != NULL){
2097 /* above message means 1/3 of success */
2098 noerror++;
2099 }
2100 if (strstr(p, "Deleted: ") == p){
2101 /* check if it deleted what we requested for */
2102 noerror++;
2103 imagesave = (char*)(p + 9);
2104 if (imagesave == *dkinfo->get_container_imagesave()){
2105 /* yes it deleted exactly what we are requesting for */
2106 noerror++;
2107 }
2108 }
2109 /* next line */
2110 DMSG0(ctx, DVDEBUG, "delete_snapshot next line\n");
2111 p = q + 1;
2112 }
2113
2114 if (noerror < 3){
2115 /* error deleting snapshot */
2116 strip_trailing_junk(out.c_str());
2117 DMSG(ctx, DERROR, "Error deleting commit image. Err=%s\n", out.c_str());
2118 JMSG(ctx, abort_on_error ? M_FATAL : M_ERROR, "Error deleting commit image. Err=%s\n", out.c_str());
2119 rc = bRC_Error;
2120 goto bailout;
2121 }
2122
2123 DMSG(ctx, DINFO, "Commit removed: %s\n", dkinfo->get_container_imagesave_tag());
2124 JMSG(ctx, M_INFO, "Commit removed: %s\n", dkinfo->get_container_imagesave_tag());
2125
2126 bailout:
2127 terminate(ctx);
2128 } else {
2129 DMSG0(ctx, DINFO, "container_commit no imagesave available.\n");
2130 }
2131
2132 DMSG0(ctx, DINFO, "container_commit finish.\n");
2133 return rc;
2134 }
2135
2136 /*
2137 * Executes docker command tool to save docker image.
2138 * The data to backup is generated on docker stdout channel and will be saved
2139 * on pluginIO calls.
2140 *
2141 * in:
2142 * bpContext - for Bacula debug jobinfo messages
2143 * dkid - docker image to save information
2144 * out:
2145 * bRC_OK - when command execution was successful
2146 * bRC_Error - on any error
2147 */
image_save(bpContext * ctx,DKID * dkid)2148 bRC DKCOMMCTX::image_save(bpContext* ctx, DKID *dkid)
2149 {
2150 POOL_MEM cmd(PM_FNAME);
2151
2152 DMSG0(ctx, DINFO, "image_save called.\n");
2153 Mmsg(cmd, "save %s", (char*)dkid);
2154 if (!execute_command(ctx, cmd)){
2155 /* some error executing command */
2156 DMSG0(ctx, DERROR, "image_save execution error\n");
2157 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "image_save execution error\n");
2158 return bRC_Error;
2159 }
2160 DMSG0(ctx, DINFO, "image_save finish, now we can read all the data.\n");
2161
2162 return bRC_OK;
2163 }
2164
2165 /*
2166 * It runs a Bacula Archive container for Docker Volume files data backup.
2167 *
2168 * in:
2169 * bpContext - for Bacula debug jobinfo messages
2170 * cmd - the command for Bacula Archive container execution ('backup' and 'restore' are supported)
2171 * volname - a volume name to backup from or restore to
2172 * jobid - required for proper support volume creation
2173 * out:
2174 * bRC_OK - when command execution was successful
2175 * bRC_Error - on any error
2176 */
run_container_volume_cmd(bpContext * ctx,const char * cmd,POOLMEM * volname,int jobid)2177 bRC DKCOMMCTX::run_container_volume_cmd(bpContext* ctx, const char *cmd, POOLMEM *volname, int jobid)
2178 {
2179 POOL_MEM bactarcmd(PM_FNAME);
2180 POOL_MEM out(PM_MESSAGE);
2181 int status;
2182 char *p;
2183
2184 DMSG1(ctx, DINFO, "run_container_volume_cmd called: %s.\n", cmd);
2185 if (workingvolume == NULL && prepare_working_volume(ctx, jobid) != bRC_OK){
2186 return bRC_Error;
2187 }
2188 /* Here we will run archive container for volume backup */
2189 Mmsg(bactarcmd, "run -d --rm -v %s:/%s -v %s:/logs %s %s", volname, cmd, workingvolume, BACULATARIMAGE, cmd);
2190 if (!execute_command(ctx, bactarcmd)){
2191 /* some error executing command */
2192 DMSG0(ctx, DERROR, "run_container_volume_cmd execution error\n");
2193 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "run_container_volume_cmd execution error\n");
2194 return bRC_Error;
2195 }
2196
2197 /* setup output buffer */
2198 memset(out.c_str(), 0, out.size());
2199 status = read_output(ctx, out);
2200 if (status < 0){
2201 /* error reading data from docker command */
2202 DMSG0(ctx, DERROR, "run_container_volume_cmd error reading data from docker command\n");
2203 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "run_container_volume_cmd error reading data from docker command\n");
2204 return bRC_Error;
2205 }
2206
2207 /* terminate container id string */
2208 p = out.c_str();
2209 p[status] = 0;
2210 strip_trailing_junk(out.c_str());
2211
2212 /* check a known output error */
2213 if (status > 0 && check_for_docker_errors(ctx, out.c_str())){
2214 return bRC_Error;
2215 }
2216
2217 DMSG2(ctx, DINFO, "run_container_volume_cmd finish - acc: %s, now we can %s all the data.\n", out.c_str(), cmd);
2218
2219 return bRC_OK;
2220 }
2221
2222 /*
2223 * Execute a Bacula Archive Container for volume backup.
2224 *
2225 * in:
2226 * bpContext - required for debug/job messages
2227 * volname - a volume name to backup from
2228 * jobid - required for proper support volume creation
2229 * out:
2230 * bRC_OK - when command execution was successful
2231 * bRC_Error - on any error
2232 */
run_container_volume_save(bpContext * ctx,POOLMEM * volname,int jobid)2233 bRC DKCOMMCTX::run_container_volume_save(bpContext* ctx, POOLMEM* volname, int jobid)
2234 {
2235 return run_container_volume_cmd(ctx, "backup", volname, jobid);
2236 };
2237
2238 /*
2239 * Execute a Bacula Archive Container for volume restore.
2240 *
2241 * in:
2242 * bpContext - required for debug/job messages
2243 * volname - a volume name to restore to
2244 * jobid - required for proper support volume creation
2245 * out:
2246 * bRC_OK - when command execution was successful
2247 * bRC_Error - on any error
2248 */
run_container_volume_load(bpContext * ctx,POOLMEM * volname,int jobid)2249 bRC DKCOMMCTX::run_container_volume_load(bpContext* ctx, POOLMEM* volname, int jobid)
2250 {
2251 return run_container_volume_cmd(ctx, "restore", volname, jobid);
2252 };
2253
2254 /*
2255 * Execute Docker commands to perform backup procedure.
2256 * Commit container then save committed image for container backup
2257 * or simply save docker image for image backup.
2258 *
2259 * in:
2260 * bpContext - for Bacula debug jobinfo messages
2261 * dkinfo - the docker object to backup
2262 * jobid - bacula jobid number
2263 * out:
2264 * bRC_OK - when command execution was successful
2265 * bRC_Error - on any error
2266 */
backup_docker(bpContext * ctx,DKINFO * dkinfo,int jobid)2267 bRC DKCOMMCTX::backup_docker(bpContext *ctx, DKINFO *dkinfo, int jobid)
2268 {
2269 DMSG0(ctx, DINFO, "backup_docker called.\n");
2270 switch (dkinfo->type()){
2271 case DOCKER_CONTAINER:
2272 /* create container commit */
2273 if (container_commit(ctx, dkinfo, jobid) == bRC_OK){
2274 if (dkinfo->get_container_imagesave()->id() > 0){
2275 return image_save(ctx, dkinfo->get_container_imagesave());
2276 }
2277 }
2278 break;
2279 case DOCKER_IMAGE:
2280 return image_save(ctx, dkinfo->get_image_id());
2281 case DOCKER_VOLUME:
2282 return run_container_volume_save(ctx, dkinfo->get_volume_name(), jobid);
2283 default:
2284 break;
2285 }
2286 DMSG0(ctx, DINFO, "backup_docker finish with error.\n");
2287 return bRC_Error;
2288 };
2289
2290 /*
2291 * Executes Docker commands to perform restore.
2292 * The data to restore is gathered on command stdin channel and will be sent
2293 * on pluginIO calls.
2294 *
2295 * in:
2296 * bpContext - for Bacula debug jobinfo messages
2297 * out:
2298 * bRC_OK - when command execution was successful
2299 * bRC_Error - on any error
2300 */
restore_docker(bpContext * ctx,DKINFO * dkinfo,int jobid)2301 bRC DKCOMMCTX::restore_docker(bpContext *ctx, DKINFO *dkinfo, int jobid)
2302 {
2303 DMSG0(ctx, DINFO, "restore_docker called.\n");
2304 if (dkinfo != NULL && dkinfo->type() == DOCKER_VOLUME){
2305 return run_container_volume_load(ctx, dkinfo->get_volume_name(), jobid);
2306 } else {
2307 if (!execute_command(ctx, "load")){
2308 /* some error executing command */
2309 DMSG0(ctx, DERROR, "restore_docker execution error\n");
2310 return bRC_Error;
2311 }
2312 }
2313 DMSG0(ctx, DINFO, "restore_docker finish, now we can write the data.\n");
2314 return bRC_OK;
2315 };
2316
2317 /*
2318 * Create or run docker container based on restored image.
2319 *
2320 * # docker container create --name mcache1_59 mcache1/b97d4dd88063/59:restore
2321 *
2322 * in:
2323 * bpContext - for Bacula debug jobinfo messages
2324 * dkinfo - restored image for container creation or run
2325 * out:
2326 * bRC_OK - when creation/run was successful
2327 * bRC_Error - on any error
2328 */
docker_create_run_container(bpContext * ctx,DKINFO * dkinfo)2329 bRC DKCOMMCTX::docker_create_run_container(bpContext* ctx, DKINFO *dkinfo)
2330 {
2331 POOL_MEM cmd(PM_FNAME);
2332 POOL_MEM out(PM_BSOCK);
2333 bRC rc = bRC_OK;
2334 int status;
2335 char *p;
2336 char *imagelabel;
2337 const char *namepar;
2338 const char *nameval;
2339 DKID containerid;
2340
2341 if (!param_container_create && !param_container_run){
2342 DMSG0(ctx, DINFO, "docker_create_container skipped on request.\n");
2343 return bRC_OK;
2344 }
2345 DMSG0(ctx, DINFO, "docker_create_container called.\n");
2346 if (dkinfo){
2347 imagelabel = param_container_imageid ? (char*)dkinfo->get_container_imagesave() : dkinfo->get_container_imagesave_tag();
2348 namepar = param_container_defaultnames ? "" : "--name ";
2349 nameval = param_container_defaultnames ? "" : dkinfo->get_container_names();
2350 if (param_container_run){
2351 // create and run the container
2352 Mmsg(cmd, "run -d %s%s %s", namepar, nameval, imagelabel);
2353 } else {
2354 // create only
2355 Mmsg(cmd, "container create %s%s %s", namepar, nameval, imagelabel);
2356 }
2357 if (!execute_command(ctx, cmd.c_str())){
2358 /* some error executing command */
2359 DMSG0(ctx, DERROR, "docker_create_container execution error\n");
2360 return bRC_Error;
2361 }
2362
2363 memset(out.c_str(), 0, out.size());
2364 status = read_output(ctx, out);
2365 if (status < 0){
2366 /* error reading data from docker command */
2367 DMSG0(ctx, DERROR, "docker_create_container error reading data from docker command\n");
2368 JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR,
2369 "docker_create_container error reading data from docker command\n");
2370 rc = bRC_Error;
2371 goto bailout;
2372 }
2373
2374 /* terminate committed image id string */
2375 p = out.c_str();
2376 p[status] = 0;
2377 strip_trailing_junk(out.c_str());
2378
2379 /* check a known output error */
2380 if (status > 0 && (strncmp(out.c_str(), "Cannot connect to the Docker daemon", 35) == 0)){
2381 DMSG1(ctx, DERROR, "No Docker is running. Cannot continue! Err=%s\n", out.c_str());
2382 JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR, "No Docker is running. Err=%s\n", out.c_str());
2383 rc = bRC_Error;
2384 goto bailout;
2385 }
2386
2387 // should return a string like: 5dd2e72fd9981184ddb8b04aaea06003617fd3f09ad0764921694e20be680c54
2388 containerid = p;
2389 if (containerid.id() < 0){
2390 /* error scanning container id */
2391 DMSG1(ctx, DERROR, "docker_create_container cannot scan commit image id. Err=%s\n", p);
2392 JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR,
2393 "docker_create_container cannot scan commit image id. Err=%s\n", p);
2394 rc = bRC_Error;
2395 goto bailout;
2396 } else {
2397 dkinfo->set_container_id(containerid);
2398 if (param_container_run){
2399 DMSG1(ctx, DINFO, "docker_create_container successfully run container as: %s\n", (char*)containerid);
2400 JMSG1(ctx, M_INFO, "Successfully run container as: (%s)\n", containerid.digest_short());
2401 }
2402 }
2403 }
2404
2405 bailout:
2406 terminate(ctx);
2407 DMSG0(ctx, DINFO, "docker_create_container finish.\n");
2408 return rc;
2409 };
2410