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