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(&param_include_container);
91    release_all_pm_list(&param_exclude_container);
92    release_all_pm_list(&param_include_image);
93    release_all_pm_list(&param_exclude_image);
94    release_all_pm_list(&param_container);
95    release_all_pm_list(&param_image);
96    release_all_pm_list(&param_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, &param_container_create, "container_create", item.name, item.val.boolval)){
891       return;
892    }
893    /* container_create variable */
894    if (render_param(ctx, &param_container_run, "container_run", item.name, item.val.boolval)){
895       return;
896    }
897    /* container_create variable */
898    if (render_param(ctx, &param_container_imageid, "container_imageid", item.name, item.val.boolval)){
899       return;
900    }
901    /* container_create variable */
902    if (render_param(ctx, &param_container_defaultnames, "container_defaultnames", item.name, item.val.boolval)){
903       return;
904    }
905    /* docker_host variable */
906    if (render_param(ctx, &param_docker_host, "docker_host", item.name, item.val.strval)){
907       return;
908    }
909    /* timeout variable */
910    if (render_param(ctx, &param_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, &param_container, "container", argk, argv)){
941       return bRC_OK;
942    }
943    /* check and handle include_container list */
944    if (add_param_str(ctx, &param_include_container, "include_container", argk, argv)){
945       return bRC_OK;
946    }
947    /* check and handle exclude_container list */
948    if (add_param_str(ctx, &param_exclude_container, "exclude_container", argk, argv)){
949       return bRC_OK;
950    }
951    /* check and handle image list */
952    if (add_param_str(ctx, &param_image, "image", argk, argv)){
953       return bRC_OK;
954    }
955    /* check and handle include_image list */
956    if (add_param_str(ctx, &param_include_image, "include_image", argk, argv)){
957       return bRC_OK;
958    }
959    /* check and handle exclude_image list */
960    if (add_param_str(ctx, &param_exclude_image, "exclude_image", argk, argv)){
961       return bRC_OK;
962    }
963    /* check and handle volume list */
964    if (add_param_str(ctx, &param_volume, "volume", argk, argv)){
965       return bRC_OK;
966    }
967    /* check and handle timeout parameter */
968    if (parse_param(ctx, &param_timeout, "timeout", argk, argv)){
969       return bRC_OK;
970    }
971    /* check mode parameter */
972    if (parse_param(ctx, &param_mode, "mode", argk, argv)){
973       return bRC_OK;
974    }
975    /* check docker_host parameter */
976    if (parse_param(ctx, &param_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