Merge "translate: Skip matrix_rebuild during shutdown."
[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 static int bucket_file_metadata_hash(const void *obj, const int flags)
621 {
622         const struct ast_bucket_metadata *object;
623         const char *key;
624
625         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
626         case OBJ_KEY:
627                 key = obj;
628                 return ast_str_hash(key);
629         case OBJ_POINTER:
630                 object = obj;
631                 return ast_str_hash(object->name);
632         default:
633                 /* Hash can only work on something with a full key */
634                 ast_assert(0);
635                 return 0;
636         }
637 }
638
639 /*! \brief Comparison function for file metadata */
640 static int bucket_file_metadata_cmp(void *obj, void *arg, int flags)
641 {
642         struct ast_bucket_metadata *metadata1 = obj, *metadata2 = arg;
643         const char *name = arg;
644
645         return !strcmp(metadata1->name, flags & OBJ_KEY ? name : metadata2->name) ? CMP_MATCH | CMP_STOP : 0;
646 }
647
648 /*! \brief Destructor for bucket files */
649 static void bucket_file_destroy(void *obj)
650 {
651         struct ast_bucket_file *file = obj;
652
653         if (file->scheme_impl->destroy) {
654                 file->scheme_impl->destroy(file);
655         }
656
657         ao2_cleanup(file->scheme_impl);
658         ao2_cleanup(file->metadata);
659 }
660
661 /*! \brief Allocator for bucket files */
662 static void *bucket_file_alloc(const char *name)
663 {
664         RAII_VAR(struct ast_bucket_file *, file, NULL, ao2_cleanup);
665
666         file = ast_sorcery_generic_alloc(sizeof(*file), bucket_file_destroy);
667         if (!file) {
668                 return NULL;
669         }
670
671         if (ast_string_field_init(file, 128)) {
672                 return NULL;
673         }
674
675         file->metadata = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, METADATA_BUCKETS,
676                 bucket_file_metadata_hash, bucket_file_metadata_cmp);
677         if (!file->metadata) {
678                 return NULL;
679         }
680
681         ao2_ref(file, +1);
682         return file;
683 }
684
685 struct ast_bucket_file *ast_bucket_file_alloc(const char *uri)
686 {
687 #ifdef HAVE_URIPARSER
688         UriParserStateA state;
689         UriUriA full_uri;
690         size_t len;
691 #else
692         char *tmp = ast_strdupa(uri);
693 #endif
694         char *uri_scheme;
695         RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup);
696         struct ast_bucket_file *file;
697
698         if (ast_strlen_zero(uri)) {
699                 return NULL;
700         }
701
702 #ifdef HAVE_URIPARSER
703         state.uri = &full_uri;
704         if (uriParseUriA(&state, uri) != URI_SUCCESS ||
705                 !full_uri.scheme.first || !full_uri.scheme.afterLast ||
706                 !full_uri.pathTail) {
707                 uriFreeUriMembersA(&full_uri);
708                 return NULL;
709         }
710
711         len = (full_uri.scheme.afterLast - full_uri.scheme.first) + 1;
712         uri_scheme = ast_alloca(len);
713         ast_copy_string(uri_scheme, full_uri.scheme.first, len);
714
715         uriFreeUriMembersA(&full_uri);
716 #else
717         uri_scheme = tmp;
718         if (!(tmp = strchr(uri_scheme, ':'))) {
719                 return NULL;
720         }
721         *tmp = '\0';
722 #endif
723
724         scheme = ao2_find(schemes, uri_scheme, OBJ_KEY);
725         if (!scheme) {
726                 return NULL;
727         }
728
729         file = ast_sorcery_alloc(bucket_sorcery, "file", uri);
730         if (!file) {
731                 return NULL;
732         }
733
734         ao2_ref(scheme, +1);
735         file->scheme_impl = scheme;
736
737         ast_string_field_set(file, scheme, uri_scheme);
738
739         if (scheme->create && scheme->create(file)) {
740                 ao2_ref(file, -1);
741                 return NULL;
742         }
743
744         return file;
745 }
746
747 int ast_bucket_file_create(struct ast_bucket_file *file)
748 {
749         return ast_sorcery_create(bucket_sorcery, file);
750 }
751
752 /*! \brief Copy a file, shamelessly taken from file.c */
753 static int bucket_copy(const char *infile, const char *outfile)
754 {
755         int ifd, ofd, len;
756         char buf[4096]; /* XXX make it lerger. */
757
758         if ((ifd = open(infile, O_RDONLY)) < 0) {
759                 ast_log(LOG_WARNING, "Unable to open %s in read-only mode, error: %s\n", infile, strerror(errno));
760                 return -1;
761         }
762         if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, AST_FILE_MODE)) < 0) {
763                 ast_log(LOG_WARNING, "Unable to open %s in write-only mode, error: %s\n", outfile, strerror(errno));
764                 close(ifd);
765                 return -1;
766         }
767         while ( (len = read(ifd, buf, sizeof(buf)) ) ) {
768                 int res;
769                 if (len < 0) {
770                         ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
771                         break;
772                 }
773                 /* XXX handle partial writes */
774                 res = write(ofd, buf, len);
775                 if (res != len) {
776                         ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
777                         len = -1; /* error marker */
778                         break;
779                 }
780         }
781         close(ifd);
782         close(ofd);
783         if (len < 0) {
784                 unlink(outfile);
785                 return -1; /* error */
786         }
787         return 0;       /* success */
788 }
789
790 /*!
791  * \internal
792  * \brief Sorcery object type copy handler for \c ast_bucket_file
793  */
794 static int bucket_file_copy_handler(const void *src, void *dst)
795 {
796         const struct ast_bucket_file *src_file = src;
797         struct ast_bucket_file *dst_file = dst;
798
799         dst_file->scheme_impl = ao2_bump(src_file->scheme_impl);
800         ast_string_field_set(dst_file, scheme, src_file->scheme);
801         dst_file->created = src_file->created;
802         dst_file->modified = src_file->modified;
803         strcpy(dst_file->path, src_file->path); /* safe */
804
805         dst_file->metadata = ao2_container_clone(src_file->metadata, 0);
806         if (!dst_file->metadata) {
807                 return -1;
808         }
809
810         return 0;
811 }
812
813 struct ast_bucket_file *ast_bucket_file_copy(struct ast_bucket_file *file, const char *uri)
814 {
815         RAII_VAR(struct ast_bucket_file *, copy, ast_bucket_file_alloc(uri), ao2_cleanup);
816
817         if (!copy) {
818                 return NULL;
819         }
820
821         ao2_cleanup(copy->metadata);
822         copy->metadata = ao2_container_clone(file->metadata, 0);
823         if (!copy->metadata ||
824                 bucket_copy(file->path, copy->path)) {
825                 return NULL;
826         }
827
828         ao2_ref(copy, +1);
829         return copy;
830 }
831
832 struct ast_bucket_file *ast_bucket_file_clone(struct ast_bucket_file *file)
833 {
834         return ast_sorcery_copy(bucket_sorcery, file);
835 }
836
837 struct ast_bucket_file *ast_bucket_file_retrieve(const char *uri)
838 {
839         if (ast_strlen_zero(uri)) {
840                 return NULL;
841         }
842
843         return ast_sorcery_retrieve_by_id(bucket_sorcery, "file", uri);
844 }
845
846 int ast_bucket_file_is_stale(struct ast_bucket_file *file)
847 {
848         return ast_sorcery_is_stale(bucket_sorcery, file);
849 }
850
851 int ast_bucket_file_observer_add(const struct ast_sorcery_observer *callbacks)
852 {
853         return ast_sorcery_observer_add(bucket_sorcery, "file", callbacks);
854 }
855
856 void ast_bucket_file_observer_remove(const struct ast_sorcery_observer *callbacks)
857 {
858         ast_sorcery_observer_remove(bucket_sorcery, "file", callbacks);
859 }
860
861 int ast_bucket_file_update(struct ast_bucket_file *file)
862 {
863         return ast_sorcery_update(bucket_sorcery, file);
864 }
865
866 int ast_bucket_file_delete(struct ast_bucket_file *file)
867 {
868         return ast_sorcery_delete(bucket_sorcery, file);
869 }
870
871 struct ast_json *ast_bucket_file_json(const struct ast_bucket_file *file)
872 {
873         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
874         struct ast_json *id, *metadata;
875         struct ao2_iterator i;
876         struct ast_bucket_metadata *attribute;
877         int res = 0;
878
879         json = ast_sorcery_objectset_json_create(bucket_sorcery, file);
880         if (!json) {
881                 return NULL;
882         }
883
884         id = ast_json_string_create(ast_sorcery_object_get_id(file));
885         if (!id) {
886                 return NULL;
887         }
888
889         if (ast_json_object_set(json, "id", id)) {
890                 return NULL;
891         }
892
893         metadata = ast_json_object_create();
894         if (!metadata) {
895                 return NULL;
896         }
897
898         if (ast_json_object_set(json, "metadata", metadata)) {
899                 return NULL;
900         }
901
902         i = ao2_iterator_init(file->metadata, 0);
903         for (; (attribute = ao2_iterator_next(&i)); ao2_ref(attribute, -1)) {
904                 struct ast_json *value = ast_json_string_create(attribute->value);
905
906                 if (!value || ast_json_object_set(metadata, attribute->name, value)) {
907                         res = -1;
908                         break;
909                 }
910         }
911         ao2_iterator_destroy(&i);
912
913         if (res) {
914                 return NULL;
915         }
916
917         ast_json_ref(json);
918         return json;
919 }
920
921 int ast_bucket_file_temporary_create(struct ast_bucket_file *file)
922 {
923         int fd;
924
925         ast_copy_string(file->path, "/tmp/bucket-XXXXXX", sizeof(file->path));
926
927         fd = mkstemp(file->path);
928         if (fd < 0) {
929                 return -1;
930         }
931
932         close(fd);
933         return 0;
934 }
935
936 void ast_bucket_file_temporary_destroy(struct ast_bucket_file *file)
937 {
938         if (!ast_strlen_zero(file->path)) {
939                 unlink(file->path);
940         }
941 }
942
943 /*! \brief Hashing function for scheme container */
944 static int bucket_scheme_hash(const void *obj, const int flags)
945 {
946         const struct ast_bucket_scheme *object;
947         const char *key;
948
949         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
950         case OBJ_KEY:
951                 key = obj;
952                 return ast_str_hash(key);
953         case OBJ_POINTER:
954                 object = obj;
955                 return ast_str_hash(object->name);
956         default:
957                 /* Hash can only work on something with a full key */
958                 ast_assert(0);
959                 return 0;
960         }
961 }
962
963 /*! \brief Comparison function for scheme container */
964 static int bucket_scheme_cmp(void *obj, void *arg, int flags)
965 {
966         struct ast_bucket_scheme *scheme1 = obj, *scheme2 = arg;
967         const char *name = arg;
968
969         return !strcmp(scheme1->name, flags & OBJ_KEY ? name : scheme2->name) ? CMP_MATCH | CMP_STOP : 0;
970 }
971
972 /*! \brief Cleanup function for graceful shutdowns */
973 static void bucket_cleanup(void)
974 {
975         ast_sorcery_unref(bucket_sorcery);
976         bucket_sorcery = NULL;
977
978         ast_sorcery_wizard_unregister(&bucket_wizard);
979         ast_sorcery_wizard_unregister(&bucket_file_wizard);
980
981         ao2_cleanup(schemes);
982 }
983
984 /*! \brief Custom handler for translating from a string timeval to actual structure */
985 static int timeval_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
986 {
987         struct timeval *field = (struct timeval *)(obj + aco_option_get_argument(opt, 0));
988         return ast_get_timeval(var->value, field, ast_tv(0, 0), NULL);
989 }
990
991 /*! \brief Custom handler for translating from an actual structure timeval to string */
992 static int timeval_struct2str(const void *obj, const intptr_t *args, char **buf)
993 {
994         struct timeval *field = (struct timeval *)(obj + args[0]);
995         return (ast_asprintf(buf, "%lu.%06lu", (unsigned long)field->tv_sec, (unsigned long)field->tv_usec) < 0) ? -1 : 0;
996 }
997
998 /*! \brief Initialize bucket support */
999 int ast_bucket_init(void)
1000 {
1001         ast_register_cleanup(&bucket_cleanup);
1002
1003         schemes = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, SCHEME_BUCKETS, bucket_scheme_hash,
1004                 bucket_scheme_cmp);
1005         if (!schemes) {
1006                 ast_log(LOG_ERROR, "Failed to create container for Bucket schemes\n");
1007                 return -1;
1008         }
1009
1010         if (__ast_sorcery_wizard_register(&bucket_wizard, NULL)) {
1011                 ast_log(LOG_ERROR, "Failed to register sorcery wizard for 'bucket' intermediary\n");
1012                 return -1;
1013         }
1014
1015         if (__ast_sorcery_wizard_register(&bucket_file_wizard, NULL)) {
1016                 ast_log(LOG_ERROR, "Failed to register sorcery wizard for 'file' intermediary\n");
1017                 return -1;
1018         }
1019
1020         if (!(bucket_sorcery = ast_sorcery_open())) {
1021                 ast_log(LOG_ERROR, "Failed to create sorcery instance for Bucket support\n");
1022                 return -1;
1023         }
1024
1025         if (ast_sorcery_apply_default(bucket_sorcery, "bucket", "bucket", NULL) == AST_SORCERY_APPLY_FAIL) {
1026                 ast_log(LOG_ERROR, "Failed to apply intermediary for 'bucket' object type in Bucket sorcery\n");
1027                 return -1;
1028         }
1029
1030         if (ast_sorcery_object_register(bucket_sorcery, "bucket", bucket_alloc, NULL, NULL)) {
1031                 ast_log(LOG_ERROR, "Failed to register 'bucket' object type in Bucket sorcery\n");
1032                 return -1;
1033         }
1034
1035         ast_sorcery_object_field_register(bucket_sorcery, "bucket", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket, scheme));
1036         ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, created));
1037         ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, modified));
1038         ast_sorcery_object_set_copy_handler(bucket_sorcery, "bucket", bucket_copy_handler);
1039
1040         if (ast_sorcery_apply_default(bucket_sorcery, "file", "bucket_file", NULL) == AST_SORCERY_APPLY_FAIL) {
1041                 ast_log(LOG_ERROR, "Failed to apply intermediary for 'file' object type in Bucket sorcery\n");
1042                 return -1;
1043         }
1044
1045         if (ast_sorcery_object_register(bucket_sorcery, "file", bucket_file_alloc, NULL, NULL)) {
1046                 ast_log(LOG_ERROR, "Failed to register 'file' object type in Bucket sorcery\n");
1047                 return -1;
1048         }
1049
1050         ast_sorcery_object_field_register(bucket_sorcery, "file", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket_file, scheme));
1051         ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, created));
1052         ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, modified));
1053         ast_sorcery_object_set_copy_handler(bucket_sorcery, "file", bucket_file_copy_handler);
1054
1055         return 0;
1056 }