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  * TODO: add unittests
27  */
28 
29 #include "dkinfo.h"
30 
31 /*
32  * libbac uses its own sscanf implementation which is not compatible with
33  * libc implementation, unfortunately.
34  * use bsscanf for Bacula sscanf flavor
35  */
36 #ifdef sscanf
37 #undef sscanf
38 #endif
39 
40 /*
41  * The base constructor
42  */
DKVOLS(DKINFO * dk)43 DKVOLS::DKVOLS(DKINFO *dk)
44 {
45    vol = dk;
46    destination = get_pool_memory(PM_FNAME);
47 };
48 
49 /*
50  * Default class destructor
51  */
~DKVOLS()52 DKVOLS::~DKVOLS()
53 {
54    // WARINIG: The this->destination is freed outside the class
55    // TODO: It should be verified why and where the destination variable is freed!
56    // free_and_null_pool_memory(destination);
57 };
58 
59 /*
60  * DKINFO class constructor, does initialization for unknown.
61  */
DKINFO(DKINFO_OBJ_t t)62 DKINFO::DKINFO(DKINFO_OBJ_t t)
63 {
64    init(t);
65 };
66 
67 /*
68  * DKINFO base copy constructor by simple copy variables by value
69  */
DKINFO(const DKINFO & dkinfo)70 DKINFO::DKINFO(const DKINFO& dkinfo)
71 {
72    init(dkinfo.Type);
73    switch (Type){
74       case DOCKER_CONTAINER:
75          set_container_id(*dkinfo.data.container.containerid);
76          set_container_names(dkinfo.data.container.names);
77          set_container_size(dkinfo.data.container.size);
78          set_container_mounts(dkinfo.data.container.mounts);
79          set_container_status(dkinfo.data.container.status);
80          set_container_imagesave(*dkinfo.data.container.imagesave);
81          set_container_imagesave_tag(dkinfo.data.container.imagesave_tag);
82          break;
83       case DOCKER_IMAGE:
84          set_image_id(*dkinfo.data.image.imageid);
85          set_image_repository(dkinfo.data.image.repository);
86          set_image_tag(dkinfo.data.image.tag);
87          set_image_size(dkinfo.data.image.size);
88          set_image_created(dkinfo.data.image.created);
89          break;
90       case DOCKER_VOLUME:
91          set_volume_name(dkinfo.data.volume.name);
92          set_volume_created(dkinfo.data.volume.created);
93          set_volume_size(dkinfo.data.volume.size);
94          set_volume_linknr(dkinfo.data.volume.linknr);
95          break;
96    }
97 };
98 
99 /*
100  * DKINFO destructor, releases all memory allocated.
101  */
~DKINFO()102 DKINFO::~DKINFO()
103 {
104    DKVOLS *v;
105 
106    switch(Type){
107       case DOCKER_CONTAINER:
108          if (data.container.containerid){
109             delete data.container.containerid;
110          }
111          if (data.container.imagesave){
112             delete data.container.imagesave;
113          }
114          if (data.container.vols){
115             foreach_alist(v, data.container.vols){
116                delete v;
117             }
118             delete data.container.vols;
119          }
120          free_and_null_pool_memory(data.container.names);
121          free_and_null_pool_memory(data.container.mounts);
122          free_and_null_pool_memory(data.container.imagesave_tag);
123          break;
124       case DOCKER_IMAGE:
125          if (data.image.imageid){
126             delete data.image.imageid;
127          }
128          free_and_null_pool_memory(data.image.repository);
129          free_and_null_pool_memory(data.image.tag);
130          free_and_null_pool_memory(data.image.repository_tag);
131          break;
132       case DOCKER_VOLUME:
133          free_and_null_pool_memory(data.volume.name);
134          break;
135       default:
136          break;
137    }
138 };
139 
140 /*
141  * initialization of the DKINFO class.
142  */
init(DKINFO_OBJ_t t)143 void DKINFO::init(DKINFO_OBJ_t t)
144 {
145    Type = t;
146    switch(Type){
147       case DOCKER_CONTAINER:
148          data.container.containerid = New(DKID);
149          data.container.names = get_pool_memory(PM_NAME);
150          data.container.size = 0;
151          data.container.mounts = get_pool_memory(PM_MESSAGE);
152          data.container.status = DKUNKNOWN;
153          data.container.imagesave = New(DKID);
154          data.container.imagesave_tag = get_pool_memory(PM_NAME);
155          data.container.vols = New(alist(10, not_owned_by_alist));
156          break;
157       case DOCKER_IMAGE:
158          data.image.imageid = New(DKID);
159          data.image.repository = get_pool_memory(PM_NAME);
160          data.image.size = 0;
161          data.image.tag = get_pool_memory(PM_NAME);
162          data.image.repository_tag = get_pool_memory(PM_NAME);
163          data.image.created = 0;
164          break;
165       case DOCKER_VOLUME:
166          data.volume.name = get_pool_memory(PM_NAME);
167          data.volume.created = 0;
168          data.volume.linknr = 1;
169          break;
170       default:
171          bmemzero(&data, sizeof(data));
172    }
173 };
174 
175 /*
176  * Sets the container size based on the docker size string:
177  *       "123B (virtual 319MB)"
178  *
179  * in:
180  *    s - the string which represents the Docker container size
181  * out:
182  *    none
183  */
scan_container_size(POOLMEM * str)184 void DKINFO::scan_container_size(POOLMEM* str)
185 {
186    int status;
187    float srw;
188    char suff[2];
189    float sv;
190    uint64_t srwsize, svsize;
191 
192    if (Type == DOCKER_CONTAINER && str){
193       status = sscanf(str, "%f%c%*c%*s%f%c", &srw, &suff[0], &sv, &suff[1]);
194       if (status == 4){
195          /* scan successful */
196          srwsize = pluglib_size_suffix(srw, suff[0]);
197          svsize = pluglib_size_suffix(sv, suff[1]);
198          data.container.size = srwsize + svsize;
199       }
200    }
201 };
202 
203 /*
204  * Sets the image size based on the docker size string:
205  *       "319MB"
206  *
207  * in:
208  *    s - the string which represents the Docker image size
209  * out:
210  *    none
211  */
scan_image_size(POOLMEM * str)212 void DKINFO::scan_image_size(POOLMEM* str)
213 {
214    int status;
215    float fsize;
216    char suff;
217 
218    if (Type == DOCKER_IMAGE && str){
219       status = sscanf(str, "%f%c", &fsize, &suff);
220       if (status == 2){
221          /* scan successful */
222          data.image.size = pluglib_size_suffix(fsize, suff);
223       }
224    }
225 };
226 
227 /*
228  * Sets the volume size based on the docker volume size string:
229  *       "319MB"
230  *
231  * in:
232  *    s - the string which represents the Docker volume size
233  * out:
234  *    none
235  */
scan_volume_size(POOLMEM * str)236 void DKINFO::scan_volume_size(POOLMEM* str)
237 {
238    int status;
239    float fsize;
240    char suff;
241 
242    if (Type == DOCKER_VOLUME && str){
243       if (bstrcmp(str, "N/A")){
244          data.volume.size = 0;
245       } else {
246          status = sscanf(str, "%f%c", &fsize, &suff);
247          if (status == 2){
248             /* scan successful */
249             data.volume.size = pluglib_size_suffix(fsize, suff);
250          }
251       }
252    }
253 };
254 
255 /*
256  * Setup an image repository/tag variables from a single image repository:tag string.
257  *    The class uses three variables to store repository:tag data.
258  *    - data.image.repository_tag is used for full info string
259  *    - data.image.repository is used to store repository part
260  *    - data.image.tag is used to store a tag part
261  * TODO: optimize repository_tag storage
262  *
263  * in:
264  *    rt - a repository:tag string
265  * out:
266  *    none
267  */
scan_image_repository_tag(POOL_MEM & rt)268 void DKINFO::scan_image_repository_tag(POOL_MEM& rt)
269 {
270    char *colon;
271 
272    if (Type == DOCKER_IMAGE){
273       pm_strcpy(data.image.repository_tag, rt);
274       colon = strchr(data.image.repository_tag, ':');
275       if (colon){
276          /* have a colon in string, so split it */
277          pm_strcpy(data.image.tag, colon);
278          *colon = 0;    // temporary usage
279          pm_strcpy(data.image.repository, data.image.repository_tag);
280          *colon = ':';  // restore
281       } else {
282          pm_strcpy(data.image.repository, rt);
283          pm_strcpy(data.image.tag, NULL);
284       }
285    }
286 };
287 
288 /*
289  * Sets the container status based on the status string.
290  *
291  * in:
292  *    s - the string which represents the status
293  * out:
294  *    none
295  */
set_container_status(POOL_MEM & s)296 void DKINFO::set_container_status(POOL_MEM &s)
297 {
298    if (Type == DOCKER_CONTAINER){
299       /* scan a container state and save it */
300       if (bstrcmp(s.c_str(), "exited")){
301          /* container exited */
302          data.container.status = DKEXITED;
303       } else
304       if (bstrcmp(s.c_str(), "running")){
305          /* vm is running */
306          data.container.status = DKRUNNING;
307       } else
308       if (bstrcmp(s.c_str(), "paused")){
309          /* container paused */
310          data.container.status = DKPAUSED;
311       } else {
312          data.container.status = DKUNKNOWN;
313       }
314    }
315 }
316 
317 /* fake dkid for volumes */
318 static DKID volfakeid;
319 
320 /*
321  * Return object ID based on object type.
322  */
id()323 DKID *DKINFO::id()
324 {
325    switch(Type){
326       case DOCKER_CONTAINER:
327          return data.container.containerid;
328       case DOCKER_IMAGE:
329          return data.image.imageid;
330       case DOCKER_VOLUME:
331          return &volfakeid;
332    }
333    return NULL;
334 };
335 
336 /*
337  * Return object name based on object type.
338  */
name()339 POOLMEM *DKINFO::name()
340 {
341    switch(Type){
342       case DOCKER_CONTAINER:
343          return data.container.names;
344       case DOCKER_IMAGE:
345          return data.image.repository_tag;
346       case DOCKER_VOLUME:
347          return data.volume.name;
348    }
349    return NULL;
350 };
351 
352 /*
353  * Return object type string constant.
354  */
type_str()355 const char *DKINFO::type_str()
356 {
357    switch(Type){
358       case DOCKER_CONTAINER:
359          return "Docker Container";
360       case DOCKER_IMAGE:
361          return "Docker Image";
362       case DOCKER_VOLUME:
363          return "Docker Volume";
364    }
365    return "Unknown";
366 };
367 
368 /*
369  * Return object size info.
370  */
size()371 uint64_t DKINFO::size()
372 {
373    switch(Type){
374       case DOCKER_CONTAINER:
375          return data.container.size;
376       case DOCKER_IMAGE:
377          return data.image.size;
378       default:
379          break;
380    }
381    return 0;
382 };
383