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