diff options
author | Joshua Colp <jcolp@digium.com> | 2013-08-23 21:49:47 +0000 |
---|---|---|
committer | Joshua Colp <jcolp@digium.com> | 2013-08-23 21:49:47 +0000 |
commit | dd33217762c1c4e5a3289213068bac1fa2efb83b (patch) | |
tree | 13648ad693d1558e14783a7d4254735b669b09ff /main/bucket.c | |
parent | cd7436a6c02aaccc0909def8eb834d879050738f (diff) |
Add the bucket API.
Bucket is a URI based API for the creation, retrieval, updating, and deletion
of "buckets" and files contained within them.
Review: https://reviewboard.asterisk.org/r/2715/
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@397600 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main/bucket.c')
-rw-r--r-- | main/bucket.c | 963 |
1 files changed, 963 insertions, 0 deletions
diff --git a/main/bucket.c b/main/bucket.c new file mode 100644 index 000000000..0efd4871c --- /dev/null +++ b/main/bucket.c @@ -0,0 +1,963 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Bucket File API + * + * \author Joshua Colp <jcolp@digium.com> + */ + +/*** MODULEINFO + <use type="external">uriparser</use> + <support_level>core</support_level> + ***/ + +/*** DOCUMENTATION + <configInfo name="core" language="en_US"> + <synopsis>Bucket file API</synopsis> + <configFile name="bucket"> + <configObject name="bucket"> + <configOption name="scheme"> + <synopsis>Scheme in use for bucket</synopsis> + </configOption> + <configOption name="created"> + <synopsis>Time at which the bucket was created</synopsis> + </configOption> + <configOption name="modified"> + <synopsis>Time at which the bucket was last modified</synopsis> + </configOption> + </configObject> + <configObject name="file"> + <configOption name="scheme"> + <synopsis>Scheme in use for file</synopsis> + </configOption> + <configOption name="created"> + <synopsis>Time at which the file was created</synopsis> + </configOption> + <configOption name="modified"> + <synopsis>Time at which the file was last modified</synopsis> + </configOption> + </configObject> + </configFile> + </configInfo> +***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#ifdef HAVE_URIPARSER +#include <uriparser/Uri.h> +#endif + +#include "asterisk/logger.h" +#include "asterisk/sorcery.h" +#include "asterisk/bucket.h" +#include "asterisk/config_options.h" +#include "asterisk/astobj2.h" +#include "asterisk/strings.h" +#include "asterisk/json.h" +#include "asterisk/file.h" +#include "asterisk/module.h" + +/*! \brief Number of buckets for the container of schemes */ +#define SCHEME_BUCKETS 53 + +/*! \brief Number of buckets for the container of metadata in a file */ +#define METADATA_BUCKETS 53 + +/*! \brief Sorcery instance for all bucket operations */ +static struct ast_sorcery *bucket_sorcery; + +/*! \brief Container of registered schemes */ +static struct ao2_container *schemes; + +/*! \brief Structure for available schemes */ +struct ast_bucket_scheme { + /*! \brief Wizard for buckets */ + struct ast_sorcery_wizard *bucket; + /*! \brief Wizard for files */ + struct ast_sorcery_wizard *file; + /*! \brief Pointer to the file snapshot creation callback */ + bucket_file_create_cb create; + /*! \brief Pointer to the file snapshot destruction callback */ + bucket_file_destroy_cb destroy; + /*! \brief Name of the scheme */ + char name[0]; +}; + +/*! \brief Callback function for creating a bucket */ +static int bucket_wizard_create(const struct ast_sorcery *sorcery, void *data, void *object) +{ + struct ast_bucket *bucket = object; + + return bucket->scheme_impl->bucket->create(sorcery, data, object); +} + +/*! \brief Callback function for retrieving a bucket */ +static void *bucket_wizard_retrieve(const struct ast_sorcery *sorcery, void *data, const char *type, + const char *id) +{ +#ifdef HAVE_URIPARSER + UriParserStateA state; + UriUriA uri; +#else + char *tmp = ast_strdupa(id); +#endif + SCOPED_AO2RDLOCK(lock, schemes); + size_t len; + char *uri_scheme; + RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup); + +#ifdef HAVE_URIPARSER + state.uri = &uri; + if (uriParseUriA(&state, id) != URI_SUCCESS || + !uri.scheme.first || !uri.scheme.afterLast) { + uriFreeUriMembersA(&uri); + return NULL; + } + + len = (uri.scheme.afterLast - uri.scheme.first) + 1; + uri_scheme = ast_alloca(len); + ast_copy_string(uri_scheme, uri.scheme.first, len); + + uriFreeUriMembersA(&uri); +#else + uri_scheme = tmp; + if (!(tmp = strchr(':'))) { + return NULL; + } + *tmp = '\0'; +#endif + + scheme = ao2_find(schemes, uri_scheme, OBJ_KEY | OBJ_NOLOCK); + + if (!scheme) { + return NULL; + } + + return scheme->bucket->retrieve_id(sorcery, data, type, id); +} + +/*! \brief Callback function for deleting a bucket */ +static int bucket_wizard_delete(const struct ast_sorcery *sorcery, void *data, void *object) +{ + struct ast_bucket *bucket = object; + + return bucket->scheme_impl->bucket->delete(sorcery, data, object); +} + +/*! \brief Intermediary bucket wizard */ +static struct ast_sorcery_wizard bucket_wizard = { + .name = "bucket", + .create = bucket_wizard_create, + .retrieve_id = bucket_wizard_retrieve, + .delete = bucket_wizard_delete, +}; + +/*! \brief Callback function for creating a bucket file */ +static int bucket_file_wizard_create(const struct ast_sorcery *sorcery, void *data, void *object) +{ + struct ast_bucket_file *file = object; + + return file->scheme_impl->file->create(sorcery, data, object); +} + +/*! \brief Callback function for retrieving a bucket file */ +static void *bucket_file_wizard_retrieve(const struct ast_sorcery *sorcery, void *data, const char *type, + const char *id) +{ +#ifdef HAVE_URIPARSER + UriParserStateA state; + UriUriA uri; +#else + char *tmp = ast_strdupa(id); +#endif + size_t len; + char *uri_scheme; + SCOPED_AO2RDLOCK(lock, schemes); + RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup); + +#ifdef HAVE_URIPARSER + state.uri = &uri; + if (uriParseUriA(&state, id) != URI_SUCCESS || + !uri.scheme.first || !uri.scheme.afterLast) { + uriFreeUriMembersA(&uri); + return NULL; + } + + len = (uri.scheme.afterLast - uri.scheme.first) + 1; + uri_scheme = ast_alloca(len); + ast_copy_string(uri_scheme, uri.scheme.first, len); + + uriFreeUriMembersA(&uri); +#else + uri_scheme = tmp; + if (!(tmp = strchr(':'))) { + return NULL; + } + *tmp = '\0'; +#endif + + scheme = ao2_find(schemes, uri_scheme, OBJ_KEY | OBJ_NOLOCK); + + if (!scheme) { + return NULL; + } + + return scheme->file->retrieve_id(sorcery, data, type, id); +} + +/*! \brief Callback function for updating a bucket file */ +static int bucket_file_wizard_update(const struct ast_sorcery *sorcery, void *data, void *object) +{ + struct ast_bucket_file *file = object; + + return file->scheme_impl->file->update(sorcery, data, object); +} + +/*! \brief Callback function for deleting a bucket file */ +static int bucket_file_wizard_delete(const struct ast_sorcery *sorcery, void *data, void *object) +{ + struct ast_bucket_file *file = object; + + return file->scheme_impl->file->delete(sorcery, data, object); +} + +/*! \brief Intermediary file wizard */ +static struct ast_sorcery_wizard bucket_file_wizard = { + .name = "bucket_file", + .create = bucket_file_wizard_create, + .retrieve_id = bucket_file_wizard_retrieve, + .update = bucket_file_wizard_update, + .delete = bucket_file_wizard_delete, +}; + +int __ast_bucket_scheme_register(const char *name, struct ast_sorcery_wizard *bucket, + struct ast_sorcery_wizard *file, bucket_file_create_cb create_cb, + bucket_file_destroy_cb destroy_cb, struct ast_module *module) +{ + SCOPED_AO2WRLOCK(lock, schemes); + struct ast_bucket_scheme *scheme; + + if (ast_strlen_zero(name) || !bucket || !file || + !bucket->create || !bucket->delete || !bucket->retrieve_id || + !create_cb) { + return -1; + } + + scheme = ao2_find(schemes, name, OBJ_KEY | OBJ_NOLOCK); + if (scheme) { + return -1; + } + + scheme = ao2_alloc(sizeof(*scheme) + strlen(name) + 1, NULL); + if (!scheme) { + return -1; + } + + strcpy(scheme->name, name); + scheme->bucket = bucket; + scheme->file = file; + scheme->create = create_cb; + scheme->destroy = destroy_cb; + + ao2_link_flags(schemes, scheme, OBJ_NOLOCK); + + ast_verb(2, "Registered bucket scheme '%s'\n", name); + + ast_module_ref(module); + + return 0; +} + +/*! \brief Allocator for metadata attributes */ +static struct ast_bucket_metadata *bucket_metadata_alloc(const char *name, const char *value) +{ + int name_len = strlen(name) + 1, value_len = strlen(value) + 1; + struct ast_bucket_metadata *metadata = ao2_alloc(sizeof(*metadata) + name_len + value_len, NULL); + char *dst; + + if (!metadata) { + return NULL; + } + + dst = metadata->data; + metadata->name = strcpy(dst, name); + dst += name_len; + metadata->value = strcpy(dst, value); + + return metadata; +} + +int ast_bucket_file_metadata_set(struct ast_bucket_file *file, const char *name, const char *value) +{ + RAII_VAR(struct ast_bucket_metadata *, metadata, bucket_metadata_alloc(name, value), ao2_cleanup); + + if (!metadata) { + return -1; + } + + ao2_find(file->metadata, name, OBJ_NODATA | OBJ_UNLINK | OBJ_KEY); + ao2_link(file->metadata, metadata); + + return 0; +} + +int ast_bucket_file_metadata_unset(struct ast_bucket_file *file, const char *name) +{ + RAII_VAR(struct ast_bucket_metadata *, metadata, ao2_find(file->metadata, name, OBJ_UNLINK | OBJ_KEY), ao2_cleanup); + + if (!metadata) { + return -1; + } + + return 0; +} + +struct ast_bucket_metadata *ast_bucket_file_metadata_get(struct ast_bucket_file *file, const char *name) +{ + return ao2_find(file->metadata, name, OBJ_KEY); +} + +/*! \brief Destructor for buckets */ +static void bucket_destroy(void *obj) +{ + struct ast_bucket *bucket = obj; + + ao2_cleanup(bucket->scheme_impl); + ast_string_field_free_memory(bucket); + ao2_cleanup(bucket->buckets); + ao2_cleanup(bucket->files); +} + +/*! \brief Sorting function for red black tree string container */ +static int bucket_rbtree_str_sort_cmp(const void *obj_left, const void *obj_right, int flags) +{ + const char *str_left = obj_left; + const char *str_right = obj_right; + int cmp = 0; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + default: + case OBJ_POINTER: + case OBJ_KEY: + cmp = strcmp(str_left, str_right); + break; + case OBJ_PARTIAL_KEY: + cmp = strncmp(str_left, str_right, strlen(str_right)); + break; + } + return cmp; +} + +/*! \brief Allocator for buckets */ +static void *bucket_alloc(const char *name) +{ + RAII_VAR(struct ast_bucket *, bucket, NULL, ao2_cleanup); + + bucket = ast_sorcery_generic_alloc(sizeof(*bucket), bucket_destroy); + if (!bucket) { + return NULL; + } + + if (ast_string_field_init(bucket, 128)) { + return NULL; + } + + bucket->buckets = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, + AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, bucket_rbtree_str_sort_cmp, NULL); + if (!bucket->buckets) { + return NULL; + } + + bucket->files = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, + AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, bucket_rbtree_str_sort_cmp, NULL); + if (!bucket->files) { + return NULL; + } + + ao2_ref(bucket, +1); + return bucket; +} + +struct ast_bucket *ast_bucket_alloc(const char *uri) +{ +#ifdef HAVE_URIPARSER + UriParserStateA state; + UriUriA full_uri; +#else + char *tmp = ast_strdupa(uri); +#endif + size_t len; + char *uri_scheme; + RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup); + struct ast_bucket *bucket; + + if (ast_strlen_zero(uri)) { + return NULL; + } + +#ifdef HAVE_URIPARSER + state.uri = &full_uri; + if (uriParseUriA(&state, uri) != URI_SUCCESS || + !full_uri.scheme.first || !full_uri.scheme.afterLast || + !full_uri.pathTail) { + uriFreeUriMembersA(&full_uri); + return NULL; + } + + len = (full_uri.scheme.afterLast - full_uri.scheme.first) + 1; + uri_scheme = ast_alloca(len); + ast_copy_string(uri_scheme, full_uri.scheme.first, len); + + uriFreeUriMembersA(&full_uri); +#else + uri_scheme = tmp; + if (!(tmp = strchr(':'))) { + return NULL; + } + *tmp = '\0'; +#endif + + scheme = ao2_find(schemes, uri_scheme, OBJ_KEY); + if (!scheme) { + return NULL; + } + + bucket = ast_sorcery_alloc(bucket_sorcery, "bucket", uri); + if (!bucket) { + return NULL; + } + + ao2_ref(scheme, +1); + bucket->scheme_impl = scheme; + + ast_string_field_set(bucket, scheme, uri_scheme); + + return bucket; +} + +int ast_bucket_create(struct ast_bucket *bucket) +{ + return ast_sorcery_create(bucket_sorcery, bucket); +} + +struct ast_bucket *ast_bucket_retrieve(const char *uri) +{ + if (ast_strlen_zero(uri)) { + return NULL; + } + + return ast_sorcery_retrieve_by_id(bucket_sorcery, "bucket", uri); +} + +int ast_bucket_observer_add(const struct ast_sorcery_observer *callbacks) +{ + return ast_sorcery_observer_add(bucket_sorcery, "bucket", callbacks); +} + +void ast_bucket_observer_remove(struct ast_sorcery_observer *callbacks) +{ + ast_sorcery_observer_remove(bucket_sorcery, "bucket", callbacks); +} + +int ast_bucket_delete(struct ast_bucket *bucket) +{ + return ast_sorcery_delete(bucket_sorcery, bucket); +} + +struct ast_json *ast_bucket_json(const struct ast_bucket *bucket) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + struct ast_json *id, *files, *buckets; + struct ao2_iterator i; + char *uri; + int res = 0; + + json = ast_sorcery_objectset_json_create(bucket_sorcery, bucket); + if (!json) { + return NULL; + } + + id = ast_json_string_create(ast_sorcery_object_get_id(bucket)); + if (!id) { + return NULL; + } + + if (ast_json_object_set(json, "id", id)) { + return NULL; + } + + buckets = ast_json_array_create(); + if (!buckets) { + return NULL; + } + + if (ast_json_object_set(json, "buckets", buckets)) { + return NULL; + } + + i = ao2_iterator_init(bucket->buckets, 0); + for (; (uri = ao2_iterator_next(&i)); ao2_ref(uri, -1)) { + struct ast_json *bucket_uri = ast_json_string_create(uri); + + if (!bucket_uri || ast_json_array_append(buckets, bucket_uri)) { + res = -1; + break; + } + } + ao2_iterator_destroy(&i); + + if (res) { + return NULL; + } + + files = ast_json_array_create(); + if (!files) { + return NULL; + } + + if (ast_json_object_set(json, "files", files)) { + return NULL; + } + + i = ao2_iterator_init(bucket->files, 0); + for (; (uri = ao2_iterator_next(&i)); ao2_ref(uri, -1)) { + struct ast_json *file_uri = ast_json_string_create(uri); + + if (!file_uri || ast_json_array_append(files, file_uri)) { + res = -1; + break; + } + } + ao2_iterator_destroy(&i); + + if (res) { + return NULL; + } + + ast_json_ref(json); + return json; +} + +/*! \brief Hashing function for file metadata */ +static int bucket_file_metadata_hash(const void *obj, const int flags) +{ + const struct ast_bucket_metadata *object; + const char *key; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key = obj; + return ast_str_hash(key); + case OBJ_POINTER: + object = obj; + return ast_str_hash(object->name); + default: + /* Hash can only work on something with a full key */ + ast_assert(0); + return 0; + } +} + +/*! \brief Comparison function for file metadata */ +static int bucket_file_metadata_cmp(void *obj, void *arg, int flags) +{ + struct ast_bucket_metadata *metadata1 = obj, *metadata2 = arg; + const char *name = arg; + + return !strcmp(metadata1->name, flags & OBJ_KEY ? name : metadata2->name) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Destructor for bucket files */ +static void bucket_file_destroy(void *obj) +{ + struct ast_bucket_file *file = obj; + + if (file->scheme_impl->destroy) { + file->scheme_impl->destroy(file); + } + + ao2_cleanup(file->scheme_impl); + ao2_cleanup(file->metadata); +} + +/*! \brief Allocator for bucket files */ +static void *bucket_file_alloc(const char *name) +{ + RAII_VAR(struct ast_bucket_file *, file, NULL, ao2_cleanup); + + file = ast_sorcery_generic_alloc(sizeof(*file), bucket_file_destroy); + if (!file) { + return NULL; + } + + if (ast_string_field_init(file, 128)) { + return NULL; + } + + file->metadata = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, METADATA_BUCKETS, + bucket_file_metadata_hash, bucket_file_metadata_cmp); + if (!file->metadata) { + return NULL; + } + + ao2_ref(file, +1); + return file; +} + +struct ast_bucket_file *ast_bucket_file_alloc(const char *uri) +{ +#ifdef HAVE_URIPARSER + UriParserStateA state; + UriUriA full_uri; +#else + char *tmp = ast_strdupa(uri); +#endif + size_t len; + char *uri_scheme; + RAII_VAR(struct ast_bucket_scheme *, scheme, NULL, ao2_cleanup); + struct ast_bucket_file *file; + + if (ast_strlen_zero(uri)) { + return NULL; + } + +#ifdef HAVE_URIPARSER + state.uri = &full_uri; + if (uriParseUriA(&state, uri) != URI_SUCCESS || + !full_uri.scheme.first || !full_uri.scheme.afterLast || + !full_uri.pathTail) { + uriFreeUriMembersA(&full_uri); + return NULL; + } + + len = (full_uri.scheme.afterLast - full_uri.scheme.first) + 1; + uri_scheme = ast_alloca(len); + ast_copy_string(uri_scheme, full_uri.scheme.first, len); + + uriFreeUriMembersA(&full_uri); +#else + uri_scheme = tmp; + if (!(tmp = strchr(':'))) { + return NULL; + } + *tmp = '\0'; +#endif + + scheme = ao2_find(schemes, uri_scheme, OBJ_KEY); + if (!scheme) { + return NULL; + } + + file = ast_sorcery_alloc(bucket_sorcery, "file", uri); + if (!file) { + return NULL; + } + + ao2_ref(scheme, +1); + file->scheme_impl = scheme; + + ast_string_field_set(file, scheme, uri_scheme); + + if (scheme->create(file)) { + ao2_ref(file, -1); + return NULL; + } + + return file; +} + +int ast_bucket_file_create(struct ast_bucket_file *file) +{ + return ast_sorcery_create(bucket_sorcery, file); +} + +/*! \brief Copy a file, shamelessly taken from file.c */ +static int bucket_copy(const char *infile, const char *outfile) +{ + int ifd, ofd, len; + char buf[4096]; /* XXX make it lerger. */ + + if ((ifd = open(infile, O_RDONLY)) < 0) { + ast_log(LOG_WARNING, "Unable to open %s in read-only mode, error: %s\n", infile, strerror(errno)); + return -1; + } + if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, AST_FILE_MODE)) < 0) { + ast_log(LOG_WARNING, "Unable to open %s in write-only mode, error: %s\n", outfile, strerror(errno)); + close(ifd); + return -1; + } + while ( (len = read(ifd, buf, sizeof(buf)) ) ) { + int res; + if (len < 0) { + ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno)); + break; + } + /* XXX handle partial writes */ + res = write(ofd, buf, len); + if (res != len) { + ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno)); + len = -1; /* error marker */ + break; + } + } + close(ifd); + close(ofd); + if (len < 0) { + unlink(outfile); + return -1; /* error */ + } + return 0; /* success */ +} + +struct ast_bucket_file *ast_bucket_file_copy(struct ast_bucket_file *file, const char *uri) +{ + RAII_VAR(struct ast_bucket_file *, copy, ast_bucket_file_alloc(uri), ao2_cleanup); + + if (!copy) { + return NULL; + } + + ao2_cleanup(copy->metadata); + copy->metadata = ao2_container_clone(file->metadata, 0); + if (!copy->metadata || + bucket_copy(file->path, copy->path)) { + return NULL; + } + + ao2_ref(copy, +1); + return copy; +} + +struct ast_bucket_file *ast_bucket_file_retrieve(const char *uri) +{ + if (ast_strlen_zero(uri)) { + return NULL; + } + + return ast_sorcery_retrieve_by_id(bucket_sorcery, "file", uri); +} + +int ast_bucket_file_observer_add(const struct ast_sorcery_observer *callbacks) +{ + return ast_sorcery_observer_add(bucket_sorcery, "file", callbacks); +} + +void ast_bucket_file_observer_remove(struct ast_sorcery_observer *callbacks) +{ + ast_sorcery_observer_remove(bucket_sorcery, "file", callbacks); +} + +int ast_bucket_file_update(struct ast_bucket_file *file) +{ + return ast_sorcery_update(bucket_sorcery, file); +} + +int ast_bucket_file_delete(struct ast_bucket_file *file) +{ + return ast_sorcery_delete(bucket_sorcery, file); +} + +struct ast_json *ast_bucket_file_json(const struct ast_bucket_file *file) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + struct ast_json *id, *metadata; + struct ao2_iterator i; + struct ast_bucket_metadata *attribute; + int res = 0; + + json = ast_sorcery_objectset_json_create(bucket_sorcery, file); + if (!json) { + return NULL; + } + + id = ast_json_string_create(ast_sorcery_object_get_id(file)); + if (!id) { + return NULL; + } + + if (ast_json_object_set(json, "id", id)) { + return NULL; + } + + metadata = ast_json_object_create(); + if (!metadata) { + return NULL; + } + + if (ast_json_object_set(json, "metadata", metadata)) { + return NULL; + } + + i = ao2_iterator_init(file->metadata, 0); + for (; (attribute = ao2_iterator_next(&i)); ao2_ref(attribute, -1)) { + struct ast_json *value = ast_json_string_create(attribute->value); + + if (!value || ast_json_object_set(metadata, attribute->name, value)) { + res = -1; + break; + } + } + ao2_iterator_destroy(&i); + + if (res) { + return NULL; + } + + ast_json_ref(json); + return json; +} + +int ast_bucket_file_temporary_create(struct ast_bucket_file *file) +{ + int fd; + + ast_copy_string(file->path, "/tmp/bucket-XXXXXX", sizeof(file->path)); + + fd = mkstemp(file->path); + if (fd < 0) { + return -1; + } + + close(fd); + return 0; +} + +void ast_bucket_file_temporary_destroy(struct ast_bucket_file *file) +{ + if (!ast_strlen_zero(file->path)) { + unlink(file->path); + } +} + +/*! \brief Hashing function for scheme container */ +static int bucket_scheme_hash(const void *obj, const int flags) +{ + const struct ast_bucket_scheme *object; + const char *key; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key = obj; + return ast_str_hash(key); + case OBJ_POINTER: + object = obj; + return ast_str_hash(object->name); + default: + /* Hash can only work on something with a full key */ + ast_assert(0); + return 0; + } +} + +/*! \brief Comparison function for scheme container */ +static int bucket_scheme_cmp(void *obj, void *arg, int flags) +{ + struct ast_bucket_scheme *scheme1 = obj, *scheme2 = arg; + const char *name = arg; + + return !strcmp(scheme1->name, flags & OBJ_KEY ? name : scheme2->name) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Cleanup function for graceful shutdowns */ +static void bucket_cleanup(void) +{ + if (bucket_sorcery) { + ast_sorcery_unref(bucket_sorcery); + } + + ast_sorcery_wizard_unregister(&bucket_wizard); + ast_sorcery_wizard_unregister(&bucket_file_wizard); + + ao2_cleanup(schemes); +} + +/*! \brief Custom handler for translating from a string timeval to actual structure */ +static int timeval_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct timeval *field = (struct timeval *)(obj + aco_option_get_argument(opt, 0)); + return ast_get_timeval(var->value, field, ast_tv(0, 0), NULL); +} + +/*! \brief Custom handler for translating from an actual structure timeval to string */ +static int timeval_struct2str(const void *obj, const intptr_t *args, char **buf) +{ + struct timeval *field = (struct timeval *)(obj + args[0]); + return (ast_asprintf(buf, "%lu.%06lu", field->tv_sec, field->tv_usec) < 0) ? -1 : 0; +} + +/*! \brief Initialize bucket support */ +int ast_bucket_init(void) +{ + ast_register_cleanup(&bucket_cleanup); + + schemes = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, SCHEME_BUCKETS, bucket_scheme_hash, + bucket_scheme_cmp); + if (!schemes) { + ast_log(LOG_ERROR, "Failed to create container for Bucket schemes\n"); + return -1; + } + + if (__ast_sorcery_wizard_register(&bucket_wizard, NULL)) { + ast_log(LOG_ERROR, "Failed to register sorcery wizard for 'bucket' intermediary\n"); + return -1; + } + + if (__ast_sorcery_wizard_register(&bucket_file_wizard, NULL)) { + ast_log(LOG_ERROR, "Failed to register sorcery wizard for 'file' intermediary\n"); + return -1; + } + + if (!(bucket_sorcery = ast_sorcery_open())) { + ast_log(LOG_ERROR, "Failed to create sorcery instance for Bucket support\n"); + return -1; + } + + if (ast_sorcery_apply_default(bucket_sorcery, "bucket", "bucket", NULL)) { + ast_log(LOG_ERROR, "Failed to apply intermediary for 'bucket' object type in Bucket sorcery\n"); + return -1; + } + + if (ast_sorcery_object_register(bucket_sorcery, "bucket", bucket_alloc, NULL, NULL)) { + ast_log(LOG_ERROR, "Failed to register 'bucket' object type in Bucket sorcery\n"); + return -1; + } + + ast_sorcery_object_field_register(bucket_sorcery, "bucket", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket, scheme)); + ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "created", "", timeval_str2struct, timeval_struct2str, 0, FLDSET(struct ast_bucket, created)); + ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "modified", "", timeval_str2struct, timeval_struct2str, 0, FLDSET(struct ast_bucket, modified)); + + if (ast_sorcery_apply_default(bucket_sorcery, "file", "bucket_file", NULL)) { + ast_log(LOG_ERROR, "Failed to apply intermediary for 'file' object type in Bucket sorcery\n"); + return -1; + } + + if (ast_sorcery_object_register(bucket_sorcery, "file", bucket_file_alloc, NULL, NULL)) { + ast_log(LOG_ERROR, "Failed to register 'file' object type in Bucket sorcery\n"); + return -1; + } + + ast_sorcery_object_field_register(bucket_sorcery, "file", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket_file, scheme)); + ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "created", "", timeval_str2struct, timeval_struct2str, 0, FLDSET(struct ast_bucket_file, created)); + ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "modified", "", timeval_str2struct, timeval_struct2str, 0, FLDSET(struct ast_bucket_file, modified)); + + return 0; +} |