7618761b9fe8f9af8feba3331067cfe3131907a5
[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         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 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         size_t len;
190 #else
191         char *tmp = ast_strdupa(id);
192 #endif
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(uri_scheme, ':'))) {
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         RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup);
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_shutdown_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         size_t len;
406 #else
407         char *tmp = ast_strdupa(uri);
408 #endif
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(uri_scheme, ':'))) {
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(const 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                         ao2_ref(uri, -1);
524                         break;
525                 }
526         }
527         ao2_iterator_destroy(&i);
528
529         if (res) {
530                 return NULL;
531         }
532
533         files = ast_json_array_create();
534         if (!files) {
535                 return NULL;
536         }
537
538         if (ast_json_object_set(json, "files", files)) {
539                 return NULL;
540         }
541
542         i = ao2_iterator_init(bucket->files, 0);
543         for (; (uri = ao2_iterator_next(&i)); ao2_ref(uri, -1)) {
544                 struct ast_json *file_uri = ast_json_string_create(uri);
545
546                 if (!file_uri || ast_json_array_append(files, file_uri)) {
547                         res = -1;
548                         ao2_ref(uri, -1);
549                         break;
550                 }
551         }
552         ao2_iterator_destroy(&i);
553
554         if (res) {
555                 return NULL;
556         }
557
558         ast_json_ref(json);
559         return json;
560 }
561
562 /*! \brief Hashing function for file metadata */
563 static int bucket_file_metadata_hash(const void *obj, const int flags)
564 {
565         const struct ast_bucket_metadata *object;
566         const char *key;
567
568         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
569         case OBJ_KEY:
570                 key = obj;
571                 return ast_str_hash(key);
572         case OBJ_POINTER:
573                 object = obj;
574                 return ast_str_hash(object->name);
575         default:
576                 /* Hash can only work on something with a full key */
577                 ast_assert(0);
578                 return 0;
579         }
580 }
581
582 /*! \brief Comparison function for file metadata */
583 static int bucket_file_metadata_cmp(void *obj, void *arg, int flags)
584 {
585         struct ast_bucket_metadata *metadata1 = obj, *metadata2 = arg;
586         const char *name = arg;
587
588         return !strcmp(metadata1->name, flags & OBJ_KEY ? name : metadata2->name) ? CMP_MATCH | CMP_STOP : 0;
589 }
590
591 /*! \brief Destructor for bucket files */
592 static void bucket_file_destroy(void *obj)
593 {
594         struct ast_bucket_file *file = obj;
595
596         if (file->scheme_impl->destroy) {
597                 file->scheme_impl->destroy(file);
598         }
599
600         ao2_cleanup(file->scheme_impl);
601         ao2_cleanup(file->metadata);
602 }
603
604 /*! \brief Allocator for bucket files */
605 static void *bucket_file_alloc(const char *name)
606 {
607         RAII_VAR(struct ast_bucket_file *, file, NULL, ao2_cleanup);
608
609         file = ast_sorcery_generic_alloc(sizeof(*file), bucket_file_destroy);
610         if (!file) {
611                 return NULL;
612         }
613
614         if (ast_string_field_init(file, 128)) {
615                 return NULL;
616         }
617
618         file->metadata = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, METADATA_BUCKETS,
619                 bucket_file_metadata_hash, bucket_file_metadata_cmp);
620         if (!file->metadata) {
621                 return NULL;
622         }
623
624         ao2_ref(file, +1);
625         return file;
626 }
627
628 struct ast_bucket_file *ast_bucket_file_alloc(const char *uri)
629 {
630 #ifdef HAVE_URIPARSER
631         UriParserStateA state;
632         UriUriA full_uri;
633         size_t len;
634 #else
635         char *tmp = ast_strdupa(uri);
636 #endif
637         char *uri_scheme;
638         RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup);
639         struct ast_bucket_file *file;
640
641         if (ast_strlen_zero(uri)) {
642                 return NULL;
643         }
644
645 #ifdef HAVE_URIPARSER
646         state.uri = &full_uri;
647         if (uriParseUriA(&state, uri) != URI_SUCCESS ||
648                 !full_uri.scheme.first || !full_uri.scheme.afterLast ||
649                 !full_uri.pathTail) {
650                 uriFreeUriMembersA(&full_uri);
651                 return NULL;
652         }
653
654         len = (full_uri.scheme.afterLast - full_uri.scheme.first) + 1;
655         uri_scheme = ast_alloca(len);
656         ast_copy_string(uri_scheme, full_uri.scheme.first, len);
657
658         uriFreeUriMembersA(&full_uri);
659 #else
660         uri_scheme = tmp;
661         if (!(tmp = strchr(uri_scheme, ':'))) {
662                 return NULL;
663         }
664         *tmp = '\0';
665 #endif
666
667         scheme = ao2_find(schemes, uri_scheme, OBJ_KEY);
668         if (!scheme) {
669                 return NULL;
670         }
671
672         file = ast_sorcery_alloc(bucket_sorcery, "file", uri);
673         if (!file) {
674                 return NULL;
675         }
676
677         ao2_ref(scheme, +1);
678         file->scheme_impl = scheme;
679
680         ast_string_field_set(file, scheme, uri_scheme);
681
682         if (scheme->create(file)) {
683                 ao2_ref(file, -1);
684                 return NULL;
685         }
686
687         return file;
688 }
689
690 int ast_bucket_file_create(struct ast_bucket_file *file)
691 {
692         return ast_sorcery_create(bucket_sorcery, file);
693 }
694
695 /*! \brief Copy a file, shamelessly taken from file.c */
696 static int bucket_copy(const char *infile, const char *outfile)
697 {
698         int ifd, ofd, len;
699         char buf[4096]; /* XXX make it lerger. */
700
701         if ((ifd = open(infile, O_RDONLY)) < 0) {
702                 ast_log(LOG_WARNING, "Unable to open %s in read-only mode, error: %s\n", infile, strerror(errno));
703                 return -1;
704         }
705         if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, AST_FILE_MODE)) < 0) {
706                 ast_log(LOG_WARNING, "Unable to open %s in write-only mode, error: %s\n", outfile, strerror(errno));
707                 close(ifd);
708                 return -1;
709         }
710         while ( (len = read(ifd, buf, sizeof(buf)) ) ) {
711                 int res;
712                 if (len < 0) {
713                         ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
714                         break;
715                 }
716                 /* XXX handle partial writes */
717                 res = write(ofd, buf, len);
718                 if (res != len) {
719                         ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
720                         len = -1; /* error marker */
721                         break;
722                 }
723         }
724         close(ifd);
725         close(ofd);
726         if (len < 0) {
727                 unlink(outfile);
728                 return -1; /* error */
729         }
730         return 0;       /* success */
731 }
732
733 struct ast_bucket_file *ast_bucket_file_copy(struct ast_bucket_file *file, const char *uri)
734 {
735         RAII_VAR(struct ast_bucket_file *, copy, ast_bucket_file_alloc(uri), ao2_cleanup);
736
737         if (!copy) {
738                 return NULL;
739         }
740
741         ao2_cleanup(copy->metadata);
742         copy->metadata = ao2_container_clone(file->metadata, 0);
743         if (!copy->metadata ||
744                 bucket_copy(file->path, copy->path)) {
745                 return NULL;
746         }
747
748         ao2_ref(copy, +1);
749         return copy;
750 }
751
752 struct ast_bucket_file *ast_bucket_file_retrieve(const char *uri)
753 {
754         if (ast_strlen_zero(uri)) {
755                 return NULL;
756         }
757
758         return ast_sorcery_retrieve_by_id(bucket_sorcery, "file", uri);
759 }
760
761 int ast_bucket_file_observer_add(const struct ast_sorcery_observer *callbacks)
762 {
763         return ast_sorcery_observer_add(bucket_sorcery, "file", callbacks);
764 }
765
766 void ast_bucket_file_observer_remove(const struct ast_sorcery_observer *callbacks)
767 {
768         ast_sorcery_observer_remove(bucket_sorcery, "file", callbacks);
769 }
770
771 int ast_bucket_file_update(struct ast_bucket_file *file)
772 {
773         return ast_sorcery_update(bucket_sorcery, file);
774 }
775
776 int ast_bucket_file_delete(struct ast_bucket_file *file)
777 {
778         return ast_sorcery_delete(bucket_sorcery, file);
779 }
780
781 struct ast_json *ast_bucket_file_json(const struct ast_bucket_file *file)
782 {
783         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
784         struct ast_json *id, *metadata;
785         struct ao2_iterator i;
786         struct ast_bucket_metadata *attribute;
787         int res = 0;
788
789         json = ast_sorcery_objectset_json_create(bucket_sorcery, file);
790         if (!json) {
791                 return NULL;
792         }
793
794         id = ast_json_string_create(ast_sorcery_object_get_id(file));
795         if (!id) {
796                 return NULL;
797         }
798
799         if (ast_json_object_set(json, "id", id)) {
800                 return NULL;
801         }
802
803         metadata = ast_json_object_create();
804         if (!metadata) {
805                 return NULL;
806         }
807
808         if (ast_json_object_set(json, "metadata", metadata)) {
809                 return NULL;
810         }
811
812         i = ao2_iterator_init(file->metadata, 0);
813         for (; (attribute = ao2_iterator_next(&i)); ao2_ref(attribute, -1)) {
814                 struct ast_json *value = ast_json_string_create(attribute->value);
815
816                 if (!value || ast_json_object_set(metadata, attribute->name, value)) {
817                         res = -1;
818                         break;
819                 }
820         }
821         ao2_iterator_destroy(&i);
822
823         if (res) {
824                 return NULL;
825         }
826
827         ast_json_ref(json);
828         return json;
829 }
830
831 int ast_bucket_file_temporary_create(struct ast_bucket_file *file)
832 {
833         int fd;
834
835         ast_copy_string(file->path, "/tmp/bucket-XXXXXX", sizeof(file->path));
836
837         fd = mkstemp(file->path);
838         if (fd < 0) {
839                 return -1;
840         }
841
842         close(fd);
843         return 0;
844 }
845
846 void ast_bucket_file_temporary_destroy(struct ast_bucket_file *file)
847 {
848         if (!ast_strlen_zero(file->path)) {
849                 unlink(file->path);
850         }
851 }
852
853 /*! \brief Hashing function for scheme container */
854 static int bucket_scheme_hash(const void *obj, const int flags)
855 {
856         const struct ast_bucket_scheme *object;
857         const char *key;
858
859         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
860         case OBJ_KEY:
861                 key = obj;
862                 return ast_str_hash(key);
863         case OBJ_POINTER:
864                 object = obj;
865                 return ast_str_hash(object->name);
866         default:
867                 /* Hash can only work on something with a full key */
868                 ast_assert(0);
869                 return 0;
870         }
871 }
872
873 /*! \brief Comparison function for scheme container */
874 static int bucket_scheme_cmp(void *obj, void *arg, int flags)
875 {
876         struct ast_bucket_scheme *scheme1 = obj, *scheme2 = arg;
877         const char *name = arg;
878
879         return !strcmp(scheme1->name, flags & OBJ_KEY ? name : scheme2->name) ? CMP_MATCH | CMP_STOP : 0;
880 }
881
882 /*! \brief Cleanup function for graceful shutdowns */
883 static void bucket_cleanup(void)
884 {
885         ast_sorcery_unref(bucket_sorcery);
886         bucket_sorcery = NULL;
887
888         ast_sorcery_wizard_unregister(&bucket_wizard);
889         ast_sorcery_wizard_unregister(&bucket_file_wizard);
890
891         ao2_cleanup(schemes);
892 }
893
894 /*! \brief Custom handler for translating from a string timeval to actual structure */
895 static int timeval_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
896 {
897         struct timeval *field = (struct timeval *)(obj + aco_option_get_argument(opt, 0));
898         return ast_get_timeval(var->value, field, ast_tv(0, 0), NULL);
899 }
900
901 /*! \brief Custom handler for translating from an actual structure timeval to string */
902 static int timeval_struct2str(const void *obj, const intptr_t *args, char **buf)
903 {
904         struct timeval *field = (struct timeval *)(obj + args[0]);
905         return (ast_asprintf(buf, "%lu.%06lu", (unsigned long)field->tv_sec, (unsigned long)field->tv_usec) < 0) ? -1 : 0;
906 }
907
908 /*! \brief Initialize bucket support */
909 int ast_bucket_init(void)
910 {
911         ast_register_cleanup(&bucket_cleanup);
912
913         schemes = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, SCHEME_BUCKETS, bucket_scheme_hash,
914                 bucket_scheme_cmp);
915         if (!schemes) {
916                 ast_log(LOG_ERROR, "Failed to create container for Bucket schemes\n");
917                 return -1;
918         }
919
920         if (__ast_sorcery_wizard_register(&bucket_wizard, NULL)) {
921                 ast_log(LOG_ERROR, "Failed to register sorcery wizard for 'bucket' intermediary\n");
922                 return -1;
923         }
924
925         if (__ast_sorcery_wizard_register(&bucket_file_wizard, NULL)) {
926                 ast_log(LOG_ERROR, "Failed to register sorcery wizard for 'file' intermediary\n");
927                 return -1;
928         }
929
930         if (!(bucket_sorcery = ast_sorcery_open())) {
931                 ast_log(LOG_ERROR, "Failed to create sorcery instance for Bucket support\n");
932                 return -1;
933         }
934
935         if (ast_sorcery_apply_default(bucket_sorcery, "bucket", "bucket", NULL) == AST_SORCERY_APPLY_FAIL) {
936                 ast_log(LOG_ERROR, "Failed to apply intermediary for 'bucket' object type in Bucket sorcery\n");
937                 return -1;
938         }
939
940         if (ast_sorcery_object_register(bucket_sorcery, "bucket", bucket_alloc, NULL, NULL)) {
941                 ast_log(LOG_ERROR, "Failed to register 'bucket' object type in Bucket sorcery\n");
942                 return -1;
943         }
944
945         ast_sorcery_object_field_register(bucket_sorcery, "bucket", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket, scheme));
946         ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, created));
947         ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, modified));
948
949         if (ast_sorcery_apply_default(bucket_sorcery, "file", "bucket_file", NULL) == AST_SORCERY_APPLY_FAIL) {
950                 ast_log(LOG_ERROR, "Failed to apply intermediary for 'file' object type in Bucket sorcery\n");
951                 return -1;
952         }
953
954         if (ast_sorcery_object_register(bucket_sorcery, "file", bucket_file_alloc, NULL, NULL)) {
955                 ast_log(LOG_ERROR, "Failed to register 'file' object type in Bucket sorcery\n");
956                 return -1;
957         }
958
959         ast_sorcery_object_field_register(bucket_sorcery, "file", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket_file, scheme));
960         ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, created));
961         ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, modified));
962
963         return 0;
964 }