CI: Various updates to buildAsterisk.sh
[asterisk/asterisk.git] / main / bucket.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Bucket File API
22  *
23  * \author Joshua Colp <jcolp@digium.com>
24  */
25
26 /*** MODULEINFO
27         <use type="external">uriparser</use>
28         <support_level>core</support_level>
29  ***/
30
31 /*** DOCUMENTATION
32         <configInfo name="core" language="en_US">
33                 <synopsis>Bucket file API</synopsis>
34                 <configFile name="bucket">
35                         <configObject name="bucket">
36                                 <configOption name="scheme">
37                                         <synopsis>Scheme in use for bucket</synopsis>
38                                 </configOption>
39                                 <configOption name="created">
40                                         <synopsis>Time at which the bucket was created</synopsis>
41                                 </configOption>
42                                 <configOption name="modified">
43                                         <synopsis>Time at which the bucket was last modified</synopsis>
44                                 </configOption>
45                         </configObject>
46                         <configObject name="file">
47                                 <configOption name="scheme">
48                                         <synopsis>Scheme in use for file</synopsis>
49                                 </configOption>
50                                 <configOption name="created">
51                                         <synopsis>Time at which the file was created</synopsis>
52                                 </configOption>
53                                 <configOption name="modified">
54                                         <synopsis>Time at which the file was last modified</synopsis>
55                                 </configOption>
56                         </configObject>
57                 </configFile>
58         </configInfo>
59 ***/
60
61 #include "asterisk.h"
62
63 #ifdef HAVE_URIPARSER
64 #include <uriparser/Uri.h>
65 #endif
66
67 #include "asterisk/logger.h"
68 #include "asterisk/sorcery.h"
69 #include "asterisk/bucket.h"
70 #include "asterisk/config_options.h"
71 #include "asterisk/astobj2.h"
72 #include "asterisk/strings.h"
73 #include "asterisk/json.h"
74 #include "asterisk/file.h"
75 #include "asterisk/module.h"
76
77 /*! \brief Number of buckets for the container of schemes */
78 #define SCHEME_BUCKETS 53
79
80 /*! \brief Number of buckets for the container of metadata in a file */
81 #define METADATA_BUCKETS 53
82
83 /*! \brief Sorcery instance for all bucket operations */
84 static struct ast_sorcery *bucket_sorcery;
85
86 /*! \brief Container of registered schemes */
87 static struct ao2_container *schemes;
88
89 /*! \brief Structure for available schemes */
90 struct ast_bucket_scheme {
91         /*! \brief Wizard for buckets */
92         struct ast_sorcery_wizard *bucket;
93         /*! \brief Wizard for files */
94         struct ast_sorcery_wizard *file;
95         /*! \brief Pointer to the file snapshot creation callback */
96         bucket_file_create_cb create;
97         /*! \brief Pointer to the file snapshot destruction callback */
98         bucket_file_destroy_cb destroy;
99         /*! \brief Name of the scheme */
100         char name[0];
101 };
102
103 /*! \brief Callback function for creating a bucket */
104 static int bucket_wizard_create(const struct ast_sorcery *sorcery, void *data, void *object)
105 {
106         struct ast_bucket *bucket = object;
107
108         return bucket->scheme_impl->bucket->create(sorcery, data, object);
109 }
110
111 /*! \brief Callback function for retrieving a bucket */
112 static void *bucket_wizard_retrieve(const struct ast_sorcery *sorcery, void *data, const char *type,
113         const char *id)
114 {
115 #ifdef HAVE_URIPARSER
116         UriParserStateA state;
117         UriUriA uri;
118         size_t len;
119 #else
120         char *tmp = ast_strdupa(id);
121 #endif
122         SCOPED_AO2RDLOCK(lock, schemes);
123         char *uri_scheme;
124         RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup);
125
126 #ifdef HAVE_URIPARSER
127         state.uri = &uri;
128         if (uriParseUriA(&state, id) != URI_SUCCESS ||
129                 !uri.scheme.first || !uri.scheme.afterLast) {
130                 uriFreeUriMembersA(&uri);
131                 return NULL;
132         }
133
134         len = (uri.scheme.afterLast - uri.scheme.first) + 1;
135         uri_scheme = ast_alloca(len);
136         ast_copy_string(uri_scheme, uri.scheme.first, len);
137
138         uriFreeUriMembersA(&uri);
139 #else
140         uri_scheme = tmp;
141         if (!(tmp = strchr(uri_scheme, ':'))) {
142                 return NULL;
143         }
144         *tmp = '\0';
145 #endif
146
147         scheme = ao2_find(schemes, uri_scheme, OBJ_KEY | OBJ_NOLOCK);
148
149         if (!scheme) {
150                 return NULL;
151         }
152
153         return scheme->bucket->retrieve_id(sorcery, data, type, id);
154 }
155
156 /*! \brief Callback function for deleting a bucket */
157 static int bucket_wizard_delete(const struct ast_sorcery *sorcery, void *data, void *object)
158 {
159         struct ast_bucket *bucket = object;
160
161         return bucket->scheme_impl->bucket->delete(sorcery, data, object);
162 }
163
164 /*! \brief Callback function for determining if a bucket is stale */
165 static int bucket_wizard_is_stale(const struct ast_sorcery *sorcery, void *data, void *object)
166 {
167         struct ast_bucket *bucket = object;
168
169         if (!bucket->scheme_impl->bucket->is_stale) {
170                 return 0;
171         }
172
173         return bucket->scheme_impl->bucket->is_stale(sorcery, data, object);
174 }
175
176 /*! \brief Intermediary bucket wizard */
177 static struct ast_sorcery_wizard bucket_wizard = {
178         .name = "bucket",
179         .create = bucket_wizard_create,
180         .retrieve_id = bucket_wizard_retrieve,
181         .delete = bucket_wizard_delete,
182         .is_stale = bucket_wizard_is_stale,
183 };
184
185 /*! \brief Callback function for creating a bucket file */
186 static int bucket_file_wizard_create(const struct ast_sorcery *sorcery, void *data, void *object)
187 {
188         struct ast_bucket_file *file = object;
189
190         return file->scheme_impl->file->create(sorcery, data, object);
191 }
192
193 /*! \brief Callback function for retrieving a bucket file */
194 static void *bucket_file_wizard_retrieve(const struct ast_sorcery *sorcery, void *data, const char *type,
195         const char *id)
196 {
197 #ifdef HAVE_URIPARSER
198         UriParserStateA state;
199         UriUriA uri;
200         size_t len;
201 #else
202         char *tmp = ast_strdupa(id);
203 #endif
204         char *uri_scheme;
205         SCOPED_AO2RDLOCK(lock, schemes);
206         RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup);
207
208 #ifdef HAVE_URIPARSER
209         state.uri = &uri;
210         if (uriParseUriA(&state, id) != URI_SUCCESS ||
211                 !uri.scheme.first || !uri.scheme.afterLast) {
212                 uriFreeUriMembersA(&uri);
213                 return NULL;
214         }
215
216         len = (uri.scheme.afterLast - uri.scheme.first) + 1;
217         uri_scheme = ast_alloca(len);
218         ast_copy_string(uri_scheme, uri.scheme.first, len);
219
220         uriFreeUriMembersA(&uri);
221 #else
222         uri_scheme = tmp;
223         if (!(tmp = strchr(uri_scheme, ':'))) {
224                 return NULL;
225         }
226         *tmp = '\0';
227 #endif
228
229         scheme = ao2_find(schemes, uri_scheme, OBJ_KEY | OBJ_NOLOCK);
230
231         if (!scheme) {
232                 return NULL;
233         }
234
235         return scheme->file->retrieve_id(sorcery, data, type, id);
236 }
237
238 /*! \brief Callback function for updating a bucket file */
239 static int bucket_file_wizard_update(const struct ast_sorcery *sorcery, void *data, void *object)
240 {
241         struct ast_bucket_file *file = object;
242
243         return file->scheme_impl->file->update(sorcery, data, object);
244 }
245
246 /*! \brief Callback function for deleting a bucket file */
247 static int bucket_file_wizard_delete(const struct ast_sorcery *sorcery, void *data, void *object)
248 {
249         struct ast_bucket_file *file = object;
250
251         return file->scheme_impl->file->delete(sorcery, data, object);
252 }
253
254 /*! \brief Callback function for determining if a bucket is stale */
255 static int bucket_file_wizard_is_stale(const struct ast_sorcery *sorcery, void *data, void *object)
256 {
257         struct ast_bucket_file *file = object;
258
259         if (!file->scheme_impl->file->is_stale) {
260                 return 0;
261         }
262
263         return file->scheme_impl->file->is_stale(sorcery, data, object);
264 }
265
266 /*! \brief Intermediary file wizard */
267 static struct ast_sorcery_wizard bucket_file_wizard = {
268         .name = "bucket_file",
269         .create = bucket_file_wizard_create,
270         .retrieve_id = bucket_file_wizard_retrieve,
271         .update = bucket_file_wizard_update,
272         .delete = bucket_file_wizard_delete,
273         .is_stale = bucket_file_wizard_is_stale,
274 };
275
276 int __ast_bucket_scheme_register(const char *name, struct ast_sorcery_wizard *bucket,
277         struct ast_sorcery_wizard *file, bucket_file_create_cb create_cb,
278         bucket_file_destroy_cb destroy_cb, struct ast_module *module)
279 {
280         SCOPED_AO2WRLOCK(lock, schemes);
281         RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup);
282
283         if (ast_strlen_zero(name) || !bucket || !file ||
284             !bucket->create || !bucket->delete || !bucket->retrieve_id ||
285             (!bucket->create && !create_cb)) {
286                 return -1;
287         }
288
289         scheme = ao2_find(schemes, name, OBJ_KEY | OBJ_NOLOCK);
290         if (scheme) {
291                 return -1;
292         }
293
294         scheme = ao2_alloc(sizeof(*scheme) + strlen(name) + 1, NULL);
295         if (!scheme) {
296                 return -1;
297         }
298
299         strcpy(scheme->name, name);
300         scheme->bucket = bucket;
301         scheme->file = file;
302         scheme->create = create_cb;
303         scheme->destroy = destroy_cb;
304
305         ao2_link_flags(schemes, scheme, OBJ_NOLOCK);
306
307         ast_verb(2, "Registered bucket scheme '%s'\n", name);
308
309         ast_module_shutdown_ref(module);
310
311         return 0;
312 }
313
314 /*! \brief Allocator for metadata attributes */
315 static struct ast_bucket_metadata *bucket_metadata_alloc(const char *name, const char *value)
316 {
317         int name_len = strlen(name) + 1, value_len = strlen(value) + 1;
318         struct ast_bucket_metadata *metadata = ao2_alloc(sizeof(*metadata) + name_len + value_len, NULL);
319         char *dst;
320
321         if (!metadata) {
322                 return NULL;
323         }
324
325         dst = metadata->data;
326         metadata->name = strcpy(dst, name);
327         dst += name_len;
328         metadata->value = strcpy(dst, value);
329
330         return metadata;
331 }
332
333 int ast_bucket_file_metadata_set(struct ast_bucket_file *file, const char *name, const char *value)
334 {
335         RAII_VAR(struct ast_bucket_metadata *, metadata, bucket_metadata_alloc(name, value), ao2_cleanup);
336
337         if (!metadata) {
338                 return -1;
339         }
340
341         ao2_find(file->metadata, name, OBJ_NODATA | OBJ_UNLINK | OBJ_KEY);
342         ao2_link(file->metadata, metadata);
343
344         return 0;
345 }
346
347 int ast_bucket_file_metadata_unset(struct ast_bucket_file *file, const char *name)
348 {
349         RAII_VAR(struct ast_bucket_metadata *, metadata, ao2_find(file->metadata, name, OBJ_UNLINK | OBJ_KEY), ao2_cleanup);
350
351         if (!metadata) {
352                 return -1;
353         }
354
355         return 0;
356 }
357
358 struct ast_bucket_metadata *ast_bucket_file_metadata_get(struct ast_bucket_file *file, const char *name)
359 {
360         return ao2_find(file->metadata, name, OBJ_KEY);
361 }
362
363 void ast_bucket_file_metadata_callback(struct ast_bucket_file *file, ao2_callback_fn cb, void *arg)
364 {
365         ao2_callback(file->metadata, 0, cb, arg);
366 }
367
368
369 /*! \brief Destructor for buckets */
370 static void bucket_destroy(void *obj)
371 {
372         struct ast_bucket *bucket = obj;
373
374         ao2_cleanup(bucket->scheme_impl);
375         ast_string_field_free_memory(bucket);
376         ao2_cleanup(bucket->buckets);
377         ao2_cleanup(bucket->files);
378 }
379
380 /*! \brief Sorting function for red black tree string container */
381 static int bucket_rbtree_str_sort_cmp(const void *obj_left, const void *obj_right, int flags)
382 {
383         const char *str_left = obj_left;
384         const char *str_right = obj_right;
385         int cmp = 0;
386
387         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
388         default:
389         case OBJ_POINTER:
390         case OBJ_KEY:
391                 cmp = strcmp(str_left, str_right);
392                 break;
393         case OBJ_PARTIAL_KEY:
394                 cmp = strncmp(str_left, str_right, strlen(str_right));
395                 break;
396         }
397         return cmp;
398 }
399
400 /*! \brief Allocator for buckets */
401 static void *bucket_alloc(const char *name)
402 {
403         RAII_VAR(struct ast_bucket *, bucket, NULL, ao2_cleanup);
404
405         bucket = ast_sorcery_generic_alloc(sizeof(*bucket), bucket_destroy);
406         if (!bucket) {
407                 return NULL;
408         }
409
410         if (ast_string_field_init(bucket, 128)) {
411                 return NULL;
412         }
413
414         bucket->buckets = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK,
415                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, bucket_rbtree_str_sort_cmp, NULL);
416         if (!bucket->buckets) {
417                 return NULL;
418         }
419
420         bucket->files = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK,
421                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, bucket_rbtree_str_sort_cmp, NULL);
422         if (!bucket->files) {
423                 return NULL;
424         }
425
426         ao2_ref(bucket, +1);
427         return bucket;
428 }
429
430 struct ast_bucket *ast_bucket_alloc(const char *uri)
431 {
432 #ifdef HAVE_URIPARSER
433         UriParserStateA state;
434         UriUriA full_uri;
435         size_t len;
436 #else
437         char *tmp = ast_strdupa(uri);
438 #endif
439         char *uri_scheme;
440         RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup);
441         struct ast_bucket *bucket;
442
443         if (ast_strlen_zero(uri)) {
444                 return NULL;
445         }
446
447 #ifdef HAVE_URIPARSER
448         state.uri = &full_uri;
449         if (uriParseUriA(&state, uri) != URI_SUCCESS ||
450                 !full_uri.scheme.first || !full_uri.scheme.afterLast ||
451                 !full_uri.pathTail) {
452                 uriFreeUriMembersA(&full_uri);
453                 return NULL;
454         }
455
456         len = (full_uri.scheme.afterLast - full_uri.scheme.first) + 1;
457         uri_scheme = ast_alloca(len);
458         ast_copy_string(uri_scheme, full_uri.scheme.first, len);
459
460         uriFreeUriMembersA(&full_uri);
461 #else
462         uri_scheme = tmp;
463         if (!(tmp = strchr(uri_scheme, ':'))) {
464                 return NULL;
465         }
466         *tmp = '\0';
467 #endif
468
469         scheme = ao2_find(schemes, uri_scheme, OBJ_KEY);
470         if (!scheme) {
471                 return NULL;
472         }
473
474         bucket = ast_sorcery_alloc(bucket_sorcery, "bucket", uri);
475         if (!bucket) {
476                 return NULL;
477         }
478
479         ao2_ref(scheme, +1);
480         bucket->scheme_impl = scheme;
481
482         ast_string_field_set(bucket, scheme, uri_scheme);
483
484         return bucket;
485 }
486
487 int ast_bucket_create(struct ast_bucket *bucket)
488 {
489         return ast_sorcery_create(bucket_sorcery, bucket);
490 }
491
492 /*!
493  * \internal
494  * \brief Sorcery object type copy handler for \c ast_bucket
495  */
496 static int bucket_copy_handler(const void *src, void *dst)
497 {
498         const struct ast_bucket *src_bucket = src;
499         struct ast_bucket *dst_bucket = dst;
500
501         dst_bucket->scheme_impl = ao2_bump(src_bucket->scheme_impl);
502         ast_string_field_set(dst_bucket, scheme, src_bucket->scheme);
503         dst_bucket->created = src_bucket->created;
504         dst_bucket->modified = src_bucket->modified;
505
506         return 0;
507 }
508
509 struct ast_bucket *ast_bucket_clone(struct ast_bucket *bucket)
510 {
511         return ast_sorcery_copy(bucket_sorcery, bucket);
512 }
513
514 struct ast_bucket *ast_bucket_retrieve(const char *uri)
515 {
516         if (ast_strlen_zero(uri)) {
517                 return NULL;
518         }
519
520         return ast_sorcery_retrieve_by_id(bucket_sorcery, "bucket", uri);
521 }
522
523 int ast_bucket_is_stale(struct ast_bucket *bucket)
524 {
525         return ast_sorcery_is_stale(bucket_sorcery, bucket);
526 }
527
528 int ast_bucket_observer_add(const struct ast_sorcery_observer *callbacks)
529 {
530         return ast_sorcery_observer_add(bucket_sorcery, "bucket", callbacks);
531 }
532
533 void ast_bucket_observer_remove(const struct ast_sorcery_observer *callbacks)
534 {
535         ast_sorcery_observer_remove(bucket_sorcery, "bucket", callbacks);
536 }
537
538 int ast_bucket_delete(struct ast_bucket *bucket)
539 {
540         return ast_sorcery_delete(bucket_sorcery, bucket);
541 }
542
543 struct ast_json *ast_bucket_json(const struct ast_bucket *bucket)
544 {
545         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
546         struct ast_json *id, *files, *buckets;
547         struct ao2_iterator i;
548         char *uri;
549         int res = 0;
550
551         json = ast_sorcery_objectset_json_create(bucket_sorcery, bucket);
552         if (!json) {
553                 return NULL;
554         }
555
556         id = ast_json_string_create(ast_sorcery_object_get_id(bucket));
557         if (!id) {
558                 return NULL;
559         }
560
561         if (ast_json_object_set(json, "id", id)) {
562                 return NULL;
563         }
564
565         buckets = ast_json_array_create();
566         if (!buckets) {
567                 return NULL;
568         }
569
570         if (ast_json_object_set(json, "buckets", buckets)) {
571                 return NULL;
572         }
573
574         i = ao2_iterator_init(bucket->buckets, 0);
575         for (; (uri = ao2_iterator_next(&i)); ao2_ref(uri, -1)) {
576                 struct ast_json *bucket_uri = ast_json_string_create(uri);
577
578                 if (!bucket_uri || ast_json_array_append(buckets, bucket_uri)) {
579                         res = -1;
580                         ao2_ref(uri, -1);
581                         break;
582                 }
583         }
584         ao2_iterator_destroy(&i);
585
586         if (res) {
587                 return NULL;
588         }
589
590         files = ast_json_array_create();
591         if (!files) {
592                 return NULL;
593         }
594
595         if (ast_json_object_set(json, "files", files)) {
596                 return NULL;
597         }
598
599         i = ao2_iterator_init(bucket->files, 0);
600         for (; (uri = ao2_iterator_next(&i)); ao2_ref(uri, -1)) {
601                 struct ast_json *file_uri = ast_json_string_create(uri);
602
603                 if (!file_uri || ast_json_array_append(files, file_uri)) {
604                         res = -1;
605                         ao2_ref(uri, -1);
606                         break;
607                 }
608         }
609         ao2_iterator_destroy(&i);
610
611         if (res) {
612                 return NULL;
613         }
614
615         ast_json_ref(json);
616         return json;
617 }
618
619 /*! \brief Hashing function for file metadata */
620 AO2_STRING_FIELD_HASH_FN(ast_bucket_metadata, name)
621
622 /*! \brief Comparison function for file metadata */
623 AO2_STRING_FIELD_CMP_FN(ast_bucket_metadata, name)
624
625 /*! \brief Destructor for bucket files */
626 static void bucket_file_destroy(void *obj)
627 {
628         struct ast_bucket_file *file = obj;
629
630         if (file->scheme_impl->destroy) {
631                 file->scheme_impl->destroy(file);
632         }
633
634         ao2_cleanup(file->scheme_impl);
635         ao2_cleanup(file->metadata);
636 }
637
638 /*! \brief Allocator for bucket files */
639 static void *bucket_file_alloc(const char *name)
640 {
641         RAII_VAR(struct ast_bucket_file *, file, NULL, ao2_cleanup);
642
643         file = ast_sorcery_generic_alloc(sizeof(*file), bucket_file_destroy);
644         if (!file) {
645                 return NULL;
646         }
647
648         if (ast_string_field_init(file, 128)) {
649                 return NULL;
650         }
651
652         file->metadata = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, METADATA_BUCKETS,
653                 ast_bucket_metadata_hash_fn, NULL, ast_bucket_metadata_cmp_fn);
654         if (!file->metadata) {
655                 return NULL;
656         }
657
658         ao2_ref(file, +1);
659         return file;
660 }
661
662 struct ast_bucket_file *ast_bucket_file_alloc(const char *uri)
663 {
664 #ifdef HAVE_URIPARSER
665         UriParserStateA state;
666         UriUriA full_uri;
667         size_t len;
668 #else
669         char *tmp = ast_strdupa(uri);
670 #endif
671         char *uri_scheme;
672         RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup);
673         struct ast_bucket_file *file;
674
675         if (ast_strlen_zero(uri)) {
676                 return NULL;
677         }
678
679 #ifdef HAVE_URIPARSER
680         state.uri = &full_uri;
681         if (uriParseUriA(&state, uri) != URI_SUCCESS ||
682                 !full_uri.scheme.first || !full_uri.scheme.afterLast ||
683                 !full_uri.pathTail) {
684                 uriFreeUriMembersA(&full_uri);
685                 return NULL;
686         }
687
688         len = (full_uri.scheme.afterLast - full_uri.scheme.first) + 1;
689         uri_scheme = ast_alloca(len);
690         ast_copy_string(uri_scheme, full_uri.scheme.first, len);
691
692         uriFreeUriMembersA(&full_uri);
693 #else
694         uri_scheme = tmp;
695         if (!(tmp = strchr(uri_scheme, ':'))) {
696                 return NULL;
697         }
698         *tmp = '\0';
699 #endif
700
701         scheme = ao2_find(schemes, uri_scheme, OBJ_KEY);
702         if (!scheme) {
703                 return NULL;
704         }
705
706         file = ast_sorcery_alloc(bucket_sorcery, "file", uri);
707         if (!file) {
708                 return NULL;
709         }
710
711         ao2_ref(scheme, +1);
712         file->scheme_impl = scheme;
713
714         ast_string_field_set(file, scheme, uri_scheme);
715
716         if (scheme->create && scheme->create(file)) {
717                 ao2_ref(file, -1);
718                 return NULL;
719         }
720
721         return file;
722 }
723
724 int ast_bucket_file_create(struct ast_bucket_file *file)
725 {
726         return ast_sorcery_create(bucket_sorcery, file);
727 }
728
729 /*! \brief Copy a file, shamelessly taken from file.c */
730 static int bucket_copy(const char *infile, const char *outfile)
731 {
732         int ifd, ofd, len;
733         char buf[4096]; /* XXX make it lerger. */
734
735         if ((ifd = open(infile, O_RDONLY)) < 0) {
736                 ast_log(LOG_WARNING, "Unable to open %s in read-only mode, error: %s\n", infile, strerror(errno));
737                 return -1;
738         }
739         if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, AST_FILE_MODE)) < 0) {
740                 ast_log(LOG_WARNING, "Unable to open %s in write-only mode, error: %s\n", outfile, strerror(errno));
741                 close(ifd);
742                 return -1;
743         }
744         while ( (len = read(ifd, buf, sizeof(buf)) ) ) {
745                 int res;
746                 if (len < 0) {
747                         ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
748                         break;
749                 }
750                 /* XXX handle partial writes */
751                 res = write(ofd, buf, len);
752                 if (res != len) {
753                         ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
754                         len = -1; /* error marker */
755                         break;
756                 }
757         }
758         close(ifd);
759         close(ofd);
760         if (len < 0) {
761                 unlink(outfile);
762                 return -1; /* error */
763         }
764         return 0;       /* success */
765 }
766
767 /*!
768  * \internal
769  * \brief Sorcery object type copy handler for \c ast_bucket_file
770  */
771 static int bucket_file_copy_handler(const void *src, void *dst)
772 {
773         const struct ast_bucket_file *src_file = src;
774         struct ast_bucket_file *dst_file = dst;
775
776         dst_file->scheme_impl = ao2_bump(src_file->scheme_impl);
777         ast_string_field_set(dst_file, scheme, src_file->scheme);
778         dst_file->created = src_file->created;
779         dst_file->modified = src_file->modified;
780         strcpy(dst_file->path, src_file->path); /* safe */
781
782         dst_file->metadata = ao2_container_clone(src_file->metadata, 0);
783         if (!dst_file->metadata) {
784                 return -1;
785         }
786
787         return 0;
788 }
789
790 struct ast_bucket_file *ast_bucket_file_copy(struct ast_bucket_file *file, const char *uri)
791 {
792         RAII_VAR(struct ast_bucket_file *, copy, ast_bucket_file_alloc(uri), ao2_cleanup);
793
794         if (!copy) {
795                 return NULL;
796         }
797
798         ao2_cleanup(copy->metadata);
799         copy->metadata = ao2_container_clone(file->metadata, 0);
800         if (!copy->metadata ||
801                 bucket_copy(file->path, copy->path)) {
802                 return NULL;
803         }
804
805         ao2_ref(copy, +1);
806         return copy;
807 }
808
809 struct ast_bucket_file *ast_bucket_file_clone(struct ast_bucket_file *file)
810 {
811         return ast_sorcery_copy(bucket_sorcery, file);
812 }
813
814 struct ast_bucket_file *ast_bucket_file_retrieve(const char *uri)
815 {
816         if (ast_strlen_zero(uri)) {
817                 return NULL;
818         }
819
820         return ast_sorcery_retrieve_by_id(bucket_sorcery, "file", uri);
821 }
822
823 int ast_bucket_file_is_stale(struct ast_bucket_file *file)
824 {
825         return ast_sorcery_is_stale(bucket_sorcery, file);
826 }
827
828 int ast_bucket_file_observer_add(const struct ast_sorcery_observer *callbacks)
829 {
830         return ast_sorcery_observer_add(bucket_sorcery, "file", callbacks);
831 }
832
833 void ast_bucket_file_observer_remove(const struct ast_sorcery_observer *callbacks)
834 {
835         ast_sorcery_observer_remove(bucket_sorcery, "file", callbacks);
836 }
837
838 int ast_bucket_file_update(struct ast_bucket_file *file)
839 {
840         return ast_sorcery_update(bucket_sorcery, file);
841 }
842
843 int ast_bucket_file_delete(struct ast_bucket_file *file)
844 {
845         return ast_sorcery_delete(bucket_sorcery, file);
846 }
847
848 struct ast_json *ast_bucket_file_json(const struct ast_bucket_file *file)
849 {
850         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
851         struct ast_json *id, *metadata;
852         struct ao2_iterator i;
853         struct ast_bucket_metadata *attribute;
854         int res = 0;
855
856         json = ast_sorcery_objectset_json_create(bucket_sorcery, file);
857         if (!json) {
858                 return NULL;
859         }
860
861         id = ast_json_string_create(ast_sorcery_object_get_id(file));
862         if (!id) {
863                 return NULL;
864         }
865
866         if (ast_json_object_set(json, "id", id)) {
867                 return NULL;
868         }
869
870         metadata = ast_json_object_create();
871         if (!metadata) {
872                 return NULL;
873         }
874
875         if (ast_json_object_set(json, "metadata", metadata)) {
876                 return NULL;
877         }
878
879         i = ao2_iterator_init(file->metadata, 0);
880         for (; (attribute = ao2_iterator_next(&i)); ao2_ref(attribute, -1)) {
881                 struct ast_json *value = ast_json_string_create(attribute->value);
882
883                 if (!value || ast_json_object_set(metadata, attribute->name, value)) {
884                         res = -1;
885                         break;
886                 }
887         }
888         ao2_iterator_destroy(&i);
889
890         if (res) {
891                 return NULL;
892         }
893
894         ast_json_ref(json);
895         return json;
896 }
897
898 int ast_bucket_file_temporary_create(struct ast_bucket_file *file)
899 {
900         int fd;
901
902         ast_copy_string(file->path, "/tmp/bucket-XXXXXX", sizeof(file->path));
903
904         fd = mkstemp(file->path);
905         if (fd < 0) {
906                 return -1;
907         }
908
909         close(fd);
910         return 0;
911 }
912
913 void ast_bucket_file_temporary_destroy(struct ast_bucket_file *file)
914 {
915         if (!ast_strlen_zero(file->path)) {
916                 unlink(file->path);
917         }
918 }
919
920 /*! \brief Hashing function for scheme container */
921 AO2_STRING_FIELD_HASH_FN(ast_bucket_scheme, name)
922
923 /*! \brief Comparison function for scheme container */
924 AO2_STRING_FIELD_CMP_FN(ast_bucket_scheme, name)
925
926 /*! \brief Cleanup function for graceful shutdowns */
927 static void bucket_cleanup(void)
928 {
929         ast_sorcery_unref(bucket_sorcery);
930         bucket_sorcery = NULL;
931
932         ast_sorcery_wizard_unregister(&bucket_wizard);
933         ast_sorcery_wizard_unregister(&bucket_file_wizard);
934
935         ao2_cleanup(schemes);
936 }
937
938 /*! \brief Custom handler for translating from a string timeval to actual structure */
939 static int timeval_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
940 {
941         struct timeval *field = (struct timeval *)(obj + aco_option_get_argument(opt, 0));
942         return ast_get_timeval(var->value, field, ast_tv(0, 0), NULL);
943 }
944
945 /*! \brief Custom handler for translating from an actual structure timeval to string */
946 static int timeval_struct2str(const void *obj, const intptr_t *args, char **buf)
947 {
948         struct timeval *field = (struct timeval *)(obj + args[0]);
949         return (ast_asprintf(buf, "%lu.%06lu", (unsigned long)field->tv_sec, (unsigned long)field->tv_usec) < 0) ? -1 : 0;
950 }
951
952 /*! \brief Initialize bucket support */
953 int ast_bucket_init(void)
954 {
955         ast_register_cleanup(&bucket_cleanup);
956
957         schemes = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0, SCHEME_BUCKETS,
958                 ast_bucket_scheme_hash_fn, NULL, ast_bucket_scheme_cmp_fn);
959         if (!schemes) {
960                 ast_log(LOG_ERROR, "Failed to create container for Bucket schemes\n");
961                 return -1;
962         }
963
964         if (__ast_sorcery_wizard_register(&bucket_wizard, NULL)) {
965                 ast_log(LOG_ERROR, "Failed to register sorcery wizard for 'bucket' intermediary\n");
966                 return -1;
967         }
968
969         if (__ast_sorcery_wizard_register(&bucket_file_wizard, NULL)) {
970                 ast_log(LOG_ERROR, "Failed to register sorcery wizard for 'file' intermediary\n");
971                 return -1;
972         }
973
974         if (!(bucket_sorcery = ast_sorcery_open())) {
975                 ast_log(LOG_ERROR, "Failed to create sorcery instance for Bucket support\n");
976                 return -1;
977         }
978
979         if (ast_sorcery_apply_default(bucket_sorcery, "bucket", "bucket", NULL) == AST_SORCERY_APPLY_FAIL) {
980                 ast_log(LOG_ERROR, "Failed to apply intermediary for 'bucket' object type in Bucket sorcery\n");
981                 return -1;
982         }
983
984         if (ast_sorcery_object_register(bucket_sorcery, "bucket", bucket_alloc, NULL, NULL)) {
985                 ast_log(LOG_ERROR, "Failed to register 'bucket' object type in Bucket sorcery\n");
986                 return -1;
987         }
988
989         ast_sorcery_object_field_register(bucket_sorcery, "bucket", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket, scheme));
990         ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, created));
991         ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, modified));
992         ast_sorcery_object_set_copy_handler(bucket_sorcery, "bucket", bucket_copy_handler);
993
994         if (ast_sorcery_apply_default(bucket_sorcery, "file", "bucket_file", NULL) == AST_SORCERY_APPLY_FAIL) {
995                 ast_log(LOG_ERROR, "Failed to apply intermediary for 'file' object type in Bucket sorcery\n");
996                 return -1;
997         }
998
999         if (ast_sorcery_object_register(bucket_sorcery, "file", bucket_file_alloc, NULL, NULL)) {
1000                 ast_log(LOG_ERROR, "Failed to register 'file' object type in Bucket sorcery\n");
1001                 return -1;
1002         }
1003
1004         ast_sorcery_object_field_register(bucket_sorcery, "file", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket_file, scheme));
1005         ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, created));
1006         ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, modified));
1007         ast_sorcery_object_set_copy_handler(bucket_sorcery, "file", bucket_file_copy_handler);
1008
1009         return 0;
1010 }