1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 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 * Routines for writing Cloud drivers
21 *
22 * Written by Kern Sibbald, May MMXVI
23 *
24 */
25
26 #include "cloud_parts.h"
27
28 bool operator==(const cloud_part& lhs, const cloud_part& rhs)
29 {
30 return (lhs.index == rhs.index &&
31 lhs.mtime == rhs.mtime &&
32 lhs.size == rhs.size);
33 }
34
35 bool operator!=(const cloud_part& lhs, const cloud_part& rhs)
36 {
37 return !operator==(lhs, rhs);
38 }
39
40 bool operator==(const cloud_part& lhs, const uint32_t& rhs)
41 {
42 return (lhs.index == rhs);
43 }
44
45 bool operator!=(const cloud_part& lhs, const uint32_t& rhs)
46 {
47 return !operator==(lhs, rhs);
48 }
49
50 /* compares the all cloud_part, according to the operator==() above*/
list_contains_part(ilist * parts,cloud_part * p)51 bool list_contains_part(ilist *parts, cloud_part *p)
52 {
53 if (parts && p) {
54 cloud_part *ap = (cloud_part *)parts->get(p->index);
55 if (ap && *ap == *p) {
56 return true;
57 }
58 }
59 return false;
60 }
61
62 /* only checks if the part_idx part exists in the parts lst*/
list_contains_part(ilist * parts,uint32_t part_idx)63 bool list_contains_part(ilist *parts, uint32_t part_idx)
64 {
65 if (parts && part_idx > 0) {
66 return parts->get(part_idx) != NULL;
67 }
68 return false;
69 }
70
identical_lists(ilist * parts1,ilist * parts2)71 bool identical_lists(ilist *parts1, ilist *parts2)
72 {
73 if (parts1 && parts2) {
74 /* Using indexed ilist forces us to treat it differently (foreach not working b.e.) */
75 int max_size = parts1->last_index();
76 if (parts2->last_index() > parts1->last_index()) {
77 max_size = parts2->last_index();
78 }
79 for(int index=0; index<=max_size; index++ ) {
80 cloud_part *p1 = (cloud_part *)parts1->get(index);
81 cloud_part *p2 = (cloud_part *)parts2->get(index);
82 if (!p1) {
83 if (p2) return false;
84 } else if (!p2) {
85 if (p1) return false;
86 } else if (*p1 != *p2) {
87 return false;
88 }
89 }
90 return true;
91 }
92 return false;
93 }
94
95 /* cloud_parts present in source but not in dest are appended to diff.
96 * there's no cloud_part copy made.
97 * Diff only holds references and shoudn't own them */
diff_lists(ilist * source,ilist * dest,ilist * diff)98 bool diff_lists(ilist *source, ilist *dest, ilist *diff)
99 {
100 if (source && dest && diff) {
101 /* Using indexed list forces us to treat it differently (foreach not working b.e.) */
102 int max_size = source->last_index();
103 if (dest->last_index() > source->last_index()) {
104 max_size = dest->last_index();
105 }
106 for(int index=0; index<=max_size; index++ ) {
107 cloud_part *p1 = (cloud_part *)source->get(index);
108 cloud_part *p2 = (cloud_part *)dest->get(index);
109 if (!p1) {
110 if (p2) diff->put(index, p1);
111 } else if (!p2) {
112 if (p1) diff->put(index, p1);
113 } else if (*p1 != *p2) {
114 diff->put(index, p1);
115 }
116 }
117 return true;
118 }
119 return false;
120 }
121
122 /*=================================================
123 * cloud_proxy definitions
124 ================================================= */
125
126 cloud_proxy * cloud_proxy::m_pinstance=NULL;
127 uint64_t cloud_proxy::m_count=0;
128
129 /* hash table node structure */
130 typedef struct {
131 hlink hlnk;
132 ilist *parts_lst;
133 char *key_name;
134 } VolHashItem;
135
136 /* constructor
137 * size: the default hash size
138 * owns: determines if the ilists own the cloud_parts or not */
cloud_proxy(uint32_t size,bool owns)139 cloud_proxy::cloud_proxy(uint32_t size, bool owns)
140 {
141 pthread_mutex_init(&m_mutex, 0);
142 VolHashItem *hitem=NULL;
143 m_hash = New(htable(hitem, &hitem->hlnk, size));
144 m_owns = owns;
145 }
146
147 /* destructor
148 * we need to go thru each htable node and manually delete
149 * the associated alist before deleting the htable itself */
~cloud_proxy()150 cloud_proxy::~cloud_proxy()
151 {
152 VolHashItem *hitem;
153 foreach_htable(hitem, m_hash) {
154 delete hitem->parts_lst;
155 free (hitem->key_name);
156 }
157 delete m_hash;
158 pthread_mutex_destroy(&m_mutex);
159 }
160
161 /* insert the cloud_part into the proxy.
162 * create the volume ilist if necessary */
set(const char * volume,cloud_part * part)163 bool cloud_proxy::set(const char *volume, cloud_part *part)
164 {
165 if (part) {
166 return set(volume, part->index, part->mtime, part->size);
167 }
168 return false;
169 }
170
set(const char * volume,uint32_t index,utime_t mtime,uint64_t size)171 bool cloud_proxy::set(const char *volume, uint32_t index, utime_t mtime, uint64_t size)
172 {
173 if (!volume || index < 1) {
174 return false;
175 }
176 lock_guard lg(m_mutex);
177 /* allocate new part */
178 cloud_part *part = (cloud_part*) malloc(sizeof(cloud_part));
179 /* fill it with result info from the transfer */
180 part->index = index;
181 part->mtime = mtime;
182 part->size = size;
183
184 VolHashItem *hitem = (VolHashItem*)m_hash->lookup(const_cast<char*>(volume));
185 if (hitem) { /* when the node already exist, put the cloud_part into the vol list */
186 /* free the existing part */
187 if (hitem->parts_lst->get(index)) {
188 free(hitem->parts_lst->get(index));
189 }
190 hitem->parts_lst->put(index, part);
191 return true;
192 } else { /* if the node doesnt exist for this key, create it */
193 ilist *new_lst = New(ilist(100,m_owns));
194 new_lst->put(part->index, part);
195 /* use hashtable helper malloc */
196 VolHashItem *new_hitem = (VolHashItem *) m_hash->hash_malloc(sizeof(VolHashItem));
197 new_hitem->parts_lst = new_lst;
198 new_hitem->key_name = bstrdup(volume);
199 return m_hash->insert(new_hitem->key_name, new_hitem);
200 }
201 return false;
202 }
203
204 /* retrieve the cloud_part for the volume name at index part idx
205 * can return NULL */
get(const char * volume,uint32_t index)206 cloud_part *cloud_proxy::get(const char *volume, uint32_t index)
207 {
208 lock_guard lg(m_mutex);
209 if (volume) {
210 VolHashItem *hitem = (VolHashItem *)m_hash->lookup(const_cast<char*>(volume));
211 if (hitem) {
212 ilist * ilst = hitem->parts_lst;
213 if (ilst) {
214 return (cloud_part*)ilst->get(index);
215 }
216 }
217 }
218 return NULL;
219 }
220
get_size(const char * volume,uint32_t part_idx)221 uint64_t cloud_proxy::get_size(const char *volume, uint32_t part_idx)
222 {
223 cloud_part *cld_part = get(volume, part_idx);
224 return cld_part ? cld_part->size:0;
225 }
226
227 /* Check if the volume entry exists and return true if it's the case */
volume_lookup(const char * volume)228 bool cloud_proxy::volume_lookup(const char *volume)
229 {
230 lock_guard lg(m_mutex);
231 return ( (volume) && m_hash->lookup(const_cast<char*>(volume)) );
232 }
233
234 /* reset the volume list content with the content of part_list */
reset(const char * volume,ilist * part_list)235 bool cloud_proxy::reset(const char *volume, ilist *part_list)
236 {
237 lock_guard lg(m_mutex);
238 if (volume && part_list) {
239 VolHashItem *hitem = (VolHashItem*)m_hash->lookup(const_cast<char*>(volume));
240 if (hitem) { /* when the node already exist, recycle it */
241 delete hitem->parts_lst;
242 } else { /* create the node */
243 hitem = (VolHashItem *) m_hash->hash_malloc(sizeof(VolHashItem));
244 hitem->key_name = bstrdup(volume);
245 if (!m_hash->insert(hitem->key_name, hitem)) {
246 return false;
247 }
248 }
249 /* re-create the volume list */
250 hitem->parts_lst = New(ilist(100, m_owns));
251 /* feed it with cloud_part elements */
252 for(int index=1; index<=part_list->last_index(); index++ ) {
253 cloud_part *part = (cloud_part *)part_list->get(index);
254 if (part) {
255 hitem->parts_lst->put(index, part);
256 }
257 }
258 return true;
259 }
260 return false;
261 }
262
last_index(const char * volume)263 uint32_t cloud_proxy::last_index(const char *volume)
264 {
265 lock_guard lg(m_mutex);
266 if (volume) {
267 VolHashItem *hitem = (VolHashItem*)m_hash->lookup(const_cast<char*>(volume));
268 if (hitem && hitem->parts_lst) {
269 return hitem->parts_lst->last_index();
270 }
271 }
272 return 0;
273 }
274
exclude(const char * volume,ilist * exclusion_lst)275 ilist *cloud_proxy::exclude(const char *volume, ilist *exclusion_lst)
276 {
277 if (volume && exclusion_lst) {
278 VolHashItem *hitem = (VolHashItem*)m_hash->lookup(const_cast<char*>(volume));
279 if (hitem) {
280 ilist *res_lst = New(ilist(100, false));
281 if (diff_lists(hitem->parts_lst, exclusion_lst, res_lst)) {
282 return res_lst;
283 }
284 }
285 }
286 return NULL;
287 }
get_instance()288 cloud_proxy *cloud_proxy::get_instance()
289 {
290 if (!m_pinstance) {
291 m_pinstance = New(cloud_proxy());
292 }
293 ++m_count;
294 return m_pinstance;
295 }
296
release()297 void cloud_proxy::release()
298 {
299 if (--m_count == 0) {
300 delete m_pinstance;
301 m_pinstance = NULL;
302 }
303 }
304
dump()305 void cloud_proxy::dump()
306 {
307 VolHashItem *hitem;
308 foreach_htable(hitem, m_hash) {
309 Dmsg2(0, "proxy (%d) Volume:%s\n", m_hash->size(), hitem->hlnk.key.key);
310 for(int index=0; index<=hitem->parts_lst->last_index(); index++ ) {
311 cloud_part *p = (cloud_part *)hitem->parts_lst->get(index);
312 if (p) {
313 Dmsg1(0, "part.%d\n", p->index);
314 }
315 }
316 }
317 }
318
319 //=================================================
320 #ifdef TEST_PROGRAM
main(int argc,char * argv[])321 int main (int argc, char *argv[])
322 {
323 pthread_attr_t attr;
324
325 void * start_heap = sbrk(0);
326 (void)start_heap;
327
328 setlocale(LC_ALL, "");
329 bindtextdomain("bacula", LOCALEDIR);
330 textdomain("bacula");
331 init_stack_dump();
332 my_name_is(argc, argv, "cloud_parts_test");
333 init_msg(NULL, NULL);
334 daemon_start_time = time(NULL);
335 set_thread_concurrency(150);
336 lmgr_init_thread(); /* initialize the lockmanager stack */
337 pthread_attr_init(&attr);
338 berrno be;
339
340 printf("Test0\n");
341 {
342 cloud_part p1, p2, p3, p4;
343
344 p1.index = 1;
345 p1.mtime = 1000;
346 p1.size = 1000;
347
348 p2.index = 2;
349 p2.mtime = 2000;
350 p2.size = 2020;
351
352 p3.index = 3;
353 p3.mtime = 3000;
354 p3.size = 3030;
355
356 p4.index = 4;
357 p4.mtime = 4000;
358 p4.size = 4040;
359
360 ilist l(10,false);
361 l.put(p1.index,&p1);
362 l.put(p2.index,&p2);
363 l.put(p3.index,&p3);
364
365 ASSERT(list_contains_part(&l, &p1));
366 ASSERT(list_contains_part(&l, &p2));
367 ASSERT(list_contains_part(&l, &p3));
368 ASSERT(!list_contains_part(&l, &p4));
369
370 ASSERT(list_contains_part(&l, 3));
371 ASSERT(list_contains_part(&l, 1));
372 ASSERT(list_contains_part(&l, 2));
373 ASSERT(!list_contains_part(&l, 4));
374 }
375
376 printf("Test1\n");
377 {
378 cloud_part p1, p2, p3;
379
380 p1.index = 1;
381 p1.mtime = 1000;
382 p1.size = 1000;
383
384 p2.index = 2;
385 p2.mtime = 2000;
386 p2.size = 2020;
387
388 p3.index = 3;
389 p3.mtime = 3000;
390 p3.size = 3030;
391
392 ilist cloud(10,false);
393 cloud.put(p1.index, &p1);
394 cloud.put(p2.index, &p2);
395
396 ilist cache(10,false);
397 cache.put(p3.index, &p3);
398
399 ASSERT(!identical_lists(&cloud, &cache));
400
401 cache.put(p1.index, &p1);
402 ASSERT(!identical_lists(&cloud, &cache));
403 }
404
405 printf("Test2\n");
406 {
407 cloud_part p1, p2, p3, p4;
408
409 p1.index = 1;
410 p1.mtime = 1000;
411 p1.size = 1000;
412
413 p2.index = 2;
414 p2.mtime = 2000;
415 p2.size = 2020;
416
417 p3.index = 3;
418 p3.mtime = 3000;
419 p3.size = 3030;
420
421 p4.index = 4;
422 p4.mtime = 4000;
423 p4.size = 4040;
424
425 ilist cloud(10,false);
426 cloud.put(p1.index, &p1);
427 cloud.put(p2.index, &p2);
428
429 ilist cache(10,false);
430 cloud.put(p3.index, &p3);
431 cloud.put(p4.index, &p4);
432
433 ASSERT(!identical_lists(&cloud, &cache));
434
435 cache.put(p1.index, &p1);
436 ASSERT(!identical_lists(&cloud, &cache));
437 }
438
439 printf("Test3\n");
440 {
441 cloud_part p1, p2, p3;
442
443 p1.index = 1;
444 p1.mtime = 1000;
445 p1.size = 1000;
446
447 p2.index = 2;
448 p2.mtime = 2000;
449 p2.size = 2020;
450
451 p3.index = 3;
452 p3.mtime = 3000;
453 p3.size = 3030;
454
455 ilist cloud(10,false);
456 cloud.put(p1.index, &p1);
457 cloud.put(p2.index, &p2);
458 cloud.put(p3.index, &p3);
459
460 ilist cache(10,false);
461 cache.put(p3.index, &p3);
462 cache.put(p1.index, &p1);
463 cache.put(p2.index, &p2);
464
465 ASSERT(identical_lists(&cloud, &cache));
466 }
467
468 printf("Test4\n");
469 {
470 cloud_part p1, p2, p3;
471
472 p1.index = 1;
473 p1.mtime = 1000;
474 p1.size = 1000;
475
476 p2.index = 2;
477 p2.mtime = 2000;
478 p2.size = 2020;
479
480 p3.index = 3;
481 p3.mtime = 3000;
482 p3.size = 3030;
483
484 ilist cloud(10,false);
485 cloud.put(p1.index, &p1);
486 cloud.put(p2.index, &p2);
487 cloud.put(p3.index, &p3);
488
489 ilist cache(10,false);
490 cache.put(p2.index, &p2);
491 cache.put(p1.index, &p1);
492
493 ASSERT(!identical_lists(&cloud, &cache));
494 ilist diff(10,false);
495 ASSERT(diff_lists(&cloud, &cache, &diff));
496 ASSERT(diff.size() == 1);
497 cloud_part *dp = (cloud_part *)diff.get(3);
498 ASSERT(*dp == p3);
499 }
500
501 printf("Test proxy set\\get\n");
502 {
503 cloud_part p1, p2, p3;
504
505 p1.index = 1;
506 p1.mtime = 1000;
507 p1.size = 1000;
508
509 p2.index = 2;
510 p2.mtime = 2000;
511 p2.size = 2020;
512
513 p3.index = 3;
514 p3.mtime = 3000;
515 p3.size = 3030;
516
517 cloud_proxy *prox = cloud_proxy::get_instance();
518
519 /* add to the cloud proxy with no error */
520 /* in volume1 */
521 ASSERT(prox->set("volume1", &p1));
522 ASSERT(prox->set("volume1", &p2));
523 /* in volume2 */
524 ASSERT(prox->set("volume2", &p3));
525
526 /* retrieve the correct elements */
527 ASSERT(prox->get("volume1", 1) != NULL);
528 ASSERT(prox->get("volume1", 1)->mtime == 1000);
529 ASSERT(prox->get("volume1", 1)->size == 1000);
530 ASSERT(prox->get("volume1", 2) != NULL);
531 ASSERT(prox->get("volume1", 2)->mtime == 2000);
532 ASSERT(prox->get("volume1", 2)->size == 2020);
533 /* part3 is in volume2, not in volume1 */
534 ASSERT(prox->get("volume1", 3) == NULL);
535 ASSERT(prox->get("volume2", 3) != NULL);
536 ASSERT(prox->get("volume2", 3)->mtime == 3000);
537 ASSERT(prox->get("volume2", 3)->size == 3030);
538 /* there's no volume3 */
539 ASSERT(prox->get("volume3", 1) == NULL);
540 /* there's no volume3 nor part4 */
541 ASSERT(prox->get("volume3", 4) == NULL);
542 }
543 printf("Test proxy reset\n");
544 {
545 cloud_part p1, p2, p3, p4, p5;
546
547 p1.index = 1;
548 p1.mtime = 1000;
549 p1.size = 1000;
550
551 p2.index = 2;
552 p2.mtime = 2000;
553 p2.size = 2020;
554
555 p3.index = 3;
556 p3.mtime = 3000;
557 p3.size = 3030;
558
559 cloud_proxy *prox = cloud_proxy::get_instance();
560
561 /* add to the cloud proxy with no error */
562 /* in volume1 */
563 ASSERT(prox->set("volume1", &p1));
564 ASSERT(prox->set("volume1", &p2));
565 /* in volume2 */
566 ASSERT(prox->set("volume2", &p3));
567
568 p4.index = 3;
569 p4.mtime = 4000;
570 p4.size = 4040;
571
572 p5.index = 50;
573 p5.mtime = 5000;
574 p5.size = 5050;
575
576 ilist part_list(10,false);
577 part_list.put(p4.index, &p4);
578 part_list.put(p5.index, &p5);
579
580 /* reset volume 1 */
581 prox->reset("volume1", &part_list);
582 /* old elements are gone */
583 ASSERT(prox->get("volume1", 1) == NULL);
584 ASSERT(prox->get("volume1", 2) == NULL);
585 /* new elements are at the correct index */
586 ASSERT(prox->get("volume1", 3) != NULL);
587 ASSERT(prox->get("volume1", 3)->mtime == 4000);
588 ASSERT(prox->get("volume1", 3)->size == 4040);
589 ASSERT(prox->get("volume1", 50) != NULL);
590 ASSERT(prox->get("volume1", 50)->mtime == 5000);
591 ASSERT(prox->get("volume1", 50)->size == 5050);
592 /* part3 is still in volume2 */
593 ASSERT(prox->get("volume2", 3) != NULL);
594 ASSERT(prox->get("volume2", 3)->mtime == 3000);
595 ASSERT(prox->get("volume2", 3)->size == 3030);
596 /* there's no volume3 */
597 ASSERT(prox->get("volume3", 1) == NULL);
598 /* there's no volume3 nor part.index 4 */
599 ASSERT(prox->get("volume3", 4) == NULL);
600 prox->dump();
601 }
602
603
604 return 0;
605
606 }
607
608 #endif /* TEST_PROGRAM */
609