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