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