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 */