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