[asterisk-commits] file: trunk r397600 - in /trunk: ./ build_tools/ include/asterisk/ main/ tests/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri Aug 23 16:49:52 CDT 2013


Author: file
Date: Fri Aug 23 16:49:47 2013
New Revision: 397600

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=397600
Log:
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/

Added:
    trunk/include/asterisk/bucket.h   (with props)
    trunk/main/bucket.c   (with props)
    trunk/tests/test_bucket.c   (with props)
Modified:
    trunk/build_tools/menuselect-deps.in
    trunk/configure
    trunk/configure.ac
    trunk/include/asterisk/autoconfig.h.in
    trunk/include/asterisk/config_options.h
    trunk/main/Makefile
    trunk/main/asterisk.c
    trunk/main/config_options.c
    trunk/main/sorcery.c
    trunk/makeopts.in

Modified: trunk/build_tools/menuselect-deps.in
URL: http://svnview.digium.com/svn/asterisk/trunk/build_tools/menuselect-deps.in?view=diff&rev=397600&r1=397599&r2=397600
==============================================================================
--- trunk/build_tools/menuselect-deps.in (original)
+++ trunk/build_tools/menuselect-deps.in Fri Aug 23 16:49:47 2013
@@ -26,6 +26,7 @@
 IXJUSER=@PBX_IXJUSER@
 JACK=@PBX_JACK@
 JANSSON=@PBX_JANSSON@
+URIPARSER=@PBX_URIPARSER@
 KQUEUE=@PBX_KQUEUE@
 LDAP=@PBX_LDAP@
 LIBEDIT=@PBX_LIBEDIT@

Modified: trunk/configure.ac
URL: http://svnview.digium.com/svn/asterisk/trunk/configure.ac?view=diff&rev=397600&r1=397599&r2=397600
==============================================================================
--- trunk/configure.ac (original)
+++ trunk/configure.ac Fri Aug 23 16:49:47 2013
@@ -408,6 +408,7 @@
 AST_EXT_LIB_SETUP([ISDNNET], [ISDN4Linux], [isdnnet])
 AST_EXT_LIB_SETUP([JACK], [Jack Audio Connection Kit], [jack])
 AST_EXT_LIB_SETUP([JANSSON], [Jansson JSON library], [jansson])
+AST_EXT_LIB_SETUP([URIPARSER], [uriparser library], [uriparser])
 AST_EXT_LIB_SETUP([KQUEUE], [kqueue support], [kqueue])
 AST_EXT_LIB_SETUP([LDAP], [OpenLDAP], [ldap])
 AST_LIBCURL_CHECK_CONFIG([], [7.10.1])
@@ -543,6 +544,8 @@
 if test "x$JANSSON_LIB" == "x"; then
   AC_MSG_ERROR([*** JSON support not found (this typically means the libjansson development package is missing)])
 fi
+
+AST_EXT_LIB_CHECK([URIPARSER], [uriparser], [uriParseUriA], [uriparser/Uri.h])
 
 # Another mandatory item (unless it's explicitly disabled)
 AC_ARG_ENABLE([xmldoc],

Modified: trunk/include/asterisk/autoconfig.h.in
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/autoconfig.h.in?view=diff&rev=397600&r1=397599&r2=397600
==============================================================================
--- trunk/include/asterisk/autoconfig.h.in (original)
+++ trunk/include/asterisk/autoconfig.h.in Fri Aug 23 16:49:47 2013
@@ -1021,6 +1021,9 @@
 
 /* Define to 1 if you have the `unsetenv' function. */
 #undef HAVE_UNSETENV
+
+/* Define to 1 if you have the uriparser library library. */
+#undef HAVE_URIPARSER
 
 /* Define to 1 if you have the `utime' function. */
 #undef HAVE_UTIME

Added: trunk/include/asterisk/bucket.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/bucket.h?view=auto&rev=397600
==============================================================================
--- trunk/include/asterisk/bucket.h (added)
+++ trunk/include/asterisk/bucket.h Fri Aug 23 16:49:47 2013
@@ -1,0 +1,397 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp at 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 at digium.com>
+ * \ref AstBucket
+ */
+
+/*!
+ * \page AstBucket Bucket File API
+ *
+ * Bucket is an API which provides directory and file access in a generic fashion. It is
+ * implemented as a thin wrapper over the sorcery data access layer API and is written in
+ * a pluggable fashion to allow different backend storage mechanisms.
+ *
+ */
+
+#ifndef _ASTERISK_BUCKET_H
+#define _ASTERISK_BUCKET_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+#include "asterisk/sorcery.h"
+
+/*! \brief Opaque structure for internal details about a scheme */
+struct ast_bucket_scheme;
+
+/*! \brief Bucket metadata structure, AO2 key value pair */
+struct ast_bucket_metadata {
+	/*! \brief Name of the attribute */
+	const char *name;
+	/*! \brief Value of the attribute */
+	const char *value;
+	/*! \brief Storage for the above name and value */
+	char data[0];
+};
+
+/*! \brief Bucket structure, contains other buckets and files */
+struct ast_bucket {
+	/*! \brief Sorcery object information */
+	SORCERY_OBJECT(details);
+	/*! \brief Scheme implementation in use */
+	struct ast_bucket_scheme *scheme_impl;
+	/*! \brief Stringfields */
+	AST_DECLARE_STRING_FIELDS(
+		/*! \brief Name of scheme in use */
+		AST_STRING_FIELD(scheme);
+	);
+	/*! \brief When this bucket was created */
+	struct timeval created;
+	/*! \brief When this bucket was last modified */
+	struct timeval modified;
+	/*! \brief Container of string URIs of buckets within this bucket */
+	struct ao2_container *buckets;
+	/*! \brief Container of string URIs of files within this bucket */
+	struct ao2_container *files;
+};
+
+/*! \brief Bucket file structure, contains reference to file and information about it */
+struct ast_bucket_file {
+	/*! \brief Sorcery object information */
+	SORCERY_OBJECT(details);
+	/*! \brief Scheme implementation in use */
+	struct ast_bucket_scheme *scheme_impl;
+	/*! \brief Stringfields */
+	AST_DECLARE_STRING_FIELDS(
+		/*! \brief Name of scheme in use */
+		AST_STRING_FIELD(scheme);
+	);
+	/*! \brief When this file was created */
+	struct timeval created;
+	/*! \brief When this file was last modified */
+	struct timeval modified;
+	/*! \brief Container of metadata attributes about file */
+	struct ao2_container *metadata;
+	/*! \brief Local path to this file */
+	char path[PATH_MAX];
+};
+
+/*!
+ * \brief A callback function invoked when creating a file snapshot
+ *
+ * \param file Pointer to the file snapshot
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+typedef int (*bucket_file_create_cb)(struct ast_bucket_file *file);
+
+/*!
+ * \brief A callback function invoked when destroying a file snapshot
+ *
+ * \param file Pointer to the file snapshot
+ */
+typedef void (*bucket_file_destroy_cb)(struct ast_bucket_file *file);
+
+/*!
+ * \brief Initialize bucket support
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_bucket_init(void);
+
+/*!
+ * \brief Register support for a specific scheme
+ *
+ * \param name Name of the scheme, used to find based on scheme in URIs
+ * \param bucket Sorcery wizard used for buckets
+ * \param file Sorcery wizard used for files
+ * \param create_cb Required file snapshot creation callback
+ * \param destroy_cb Optional file snapshot destruction callback
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note Once a scheme has been registered it can not be unregistered
+ */
+#define ast_bucket_scheme_register(name, bucket, file, create_cb, destroy_cb) __ast_bucket_scheme_register(name, bucket, file, create_cb, destroy_cb, ast_module_info ? ast_module_info->self : NULL)
+
+/*!
+ * \brief Register support for a specific scheme
+ *
+ * \param name Name of the scheme, used to find based on scheme in URIs
+ * \param bucket Sorcery wizard used for buckets
+ * \param file Sorcery wizard used for files
+ * \param create_cb Required file snapshot creation callback
+ * \param destroy_cb Optional file snapshot destruction callback
+ * \param module The module which implements this scheme
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note Once a scheme has been registered it can not be unregistered
+ */
+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);
+
+/*!
+ * \brief Set a metadata attribute on a file to a specific value
+ *
+ * \param file The bucket file
+ * \param name Name of the attribute
+ * \param value Value of the attribute
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note This function will overwrite an existing attribute of the same name, unless an error
+ * occurs. If an error occurs the existing attribute is left alone.
+ */
+int ast_bucket_file_metadata_set(struct ast_bucket_file *file, const char *name, const char *value);
+
+/*!
+ * \brief Unset a specific metadata attribute on a file
+ *
+ * \param file The bucket file
+ * \param name Name of the attribute
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_bucket_file_metadata_unset(struct ast_bucket_file *file, const char *name);
+
+/*!
+ * \brief Retrieve a metadata attribute from a file
+ *
+ * \param file The bucket file
+ * \param name Name of the attribute
+ *
+ * \retval non-NULL if found
+ * \retval NULL if not found
+ *
+ * \note The object is returned with reference count increased
+ */
+struct ast_bucket_metadata *ast_bucket_file_metadata_get(struct ast_bucket_file *file, const char *name);
+
+/*!
+ * \brief Allocate a new bucket
+ *
+ * \param uri Complete URI for the bucket
+ *
+ * \param non-NULL success
+ * \param NULL failure
+ *
+ * \note This only creates a local bucket object, to persist in backend storage you must call
+ * ast_bucket_create
+ */
+struct ast_bucket *ast_bucket_alloc(const char *uri);
+
+/*!
+ * \brief Create a new bucket in backend storage
+ *
+ * \param bucket The bucket
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_bucket_create(struct ast_bucket *bucket);
+
+/*!
+ * \brief Delete a bucket from backend storage
+ *
+ * \param bucket The bucket
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_bucket_delete(struct ast_bucket *bucket);
+
+/*!
+ * \brief Retrieve information about a bucket
+ *
+ * \param uri Complete URI of the bucket
+ *
+ * \retval non-NULL if found
+ * \retval NULL if not found
+ *
+ * \note The object is returned with reference count increased
+ */
+struct ast_bucket *ast_bucket_retrieve(const char *uri);
+
+/*!
+ * \brief Add an observer for bucket creation and deletion operations
+ *
+ * \param callbacks Implementation of the sorcery observer interface
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note You must be ready to accept observer invocations before this function is called
+ */
+int ast_bucket_observer_add(const struct ast_sorcery_observer *callbacks);
+
+/*!
+ * \brief Remove an observer from bucket creation and deletion
+ *
+ * \param callbacks Implementation of the sorcery observer interface
+ */
+void ast_bucket_observer_remove(struct ast_sorcery_observer *callbacks);
+
+/*!
+ * \brief Get a JSON representation of a bucket
+ *
+ * \param bucket The specific bucket
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \note The returned ast_json object must be unreferenced using ast_json_unref
+ */
+struct ast_json *ast_bucket_json(const struct ast_bucket *bucket);
+
+/*!
+ * \brief Allocate a new bucket file
+ *
+ * \param uri Complete URI for the bucket file
+ *
+ * \param non-NULL success
+ * \param NULL failure
+ *
+ * \note This only creates a local bucket file object, to persist in backend storage you must call
+ * ast_bucket_file_create
+ */
+struct ast_bucket_file *ast_bucket_file_alloc(const char *uri);
+
+/*!
+ * \brief Create a new bucket file in backend storage
+ *
+ * \param file The bucket file
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_bucket_file_create(struct ast_bucket_file *file);
+
+/*!
+ * \brief Copy a bucket file to a new URI
+ *
+ * \param file The source bucket file
+ * \param uri The new URI
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \note This operation stages things locally, you must call ast_bucket_file_create on the file
+ * that is returned to commit the copy to backend storage
+ *
+ */
+struct ast_bucket_file *ast_bucket_file_copy(struct ast_bucket_file *file, const char *uri);
+
+/*!
+ * \brief Update an existing bucket file in backend storage
+ *
+ * \param file The bucket file
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note This operation will update both the actual content of the file and the metadata associated with it
+ */
+int ast_bucket_file_update(struct ast_bucket_file *file);
+
+/*!
+ * \brief Delete a bucket file from backend storage
+ *
+ * \param file The bucket file
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_bucket_file_delete(struct ast_bucket_file *file);
+
+/*!
+ * \brief Retrieve a bucket file
+ *
+ * \param uri Complete URI of the bucket file
+ *
+ * \retval non-NULL if found
+ * \retval NULL if not found
+ *
+ * \note The object is returned with reference count increased
+ */
+struct ast_bucket_file *ast_bucket_file_retrieve(const char *uri);
+
+/*!
+ * \brief Add an observer for bucket file creation and deletion operations
+ *
+ * \param callbacks Implementation of the sorcery observer interface
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note You must be ready to accept observer invocations before this function is called
+ */
+int ast_bucket_file_observer_add(const struct ast_sorcery_observer *callbacks);
+
+/*!
+ * \brief Remove an observer from bucket file creation and deletion
+ *
+ * \param callbacks Implementation of the sorcery observer interface
+ */
+void ast_bucket_file_observer_remove(struct ast_sorcery_observer *callbacks);
+
+/*!
+ * \brief Get a JSON representation of a bucket file
+ *
+ * \param file The specific bucket file
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \note The returned ast_json object must be unreferenced using ast_json_unref
+ */
+struct ast_json *ast_bucket_file_json(const struct ast_bucket_file *file);
+
+/*!
+ * \brief Common file snapshot creation callback for creating a temporary file
+ *
+ * \param file Pointer to the file snapshot
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_bucket_file_temporary_create(struct ast_bucket_file *file);
+
+/*!
+ * \brief Common file snapshot destruction callback for deleting a temporary file
+ *
+ * \param file Pointer to the file snapshot
+ */
+void ast_bucket_file_temporary_destroy(struct ast_bucket_file *file);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_BUCKET_H */

Propchange: trunk/include/asterisk/bucket.h
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: trunk/include/asterisk/bucket.h
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: trunk/include/asterisk/bucket.h
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: trunk/include/asterisk/config_options.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/config_options.h?view=diff&rev=397600&r1=397599&r2=397600
==============================================================================
--- trunk/include/asterisk/config_options.h (original)
+++ trunk/include/asterisk/config_options.h Fri Aug 23 16:49:47 2013
@@ -608,6 +608,16 @@
  * \retval value of the flags on the config option
  */
 unsigned int aco_option_get_flags(const struct aco_option *option);
+
+/*!
+ * \brief Get the offset position for an argument within a config option
+ *
+ * \param option Pointer to the aco_option struct
+ * \param arg Argument number
+ *
+ * \retval position of the argument
+ */
+intptr_t aco_option_get_argument(const struct aco_option *option, unsigned int position);
 
 /*! \note  Everything below this point is to handle converting varargs
  * containing field names, to varargs containing a count of args, followed

Modified: trunk/main/Makefile
URL: http://svnview.digium.com/svn/asterisk/trunk/main/Makefile?view=diff&rev=397600&r1=397599&r2=397600
==============================================================================
--- trunk/main/Makefile (original)
+++ trunk/main/Makefile Fri Aug 23 16:49:47 2013
@@ -37,6 +37,7 @@
 AST_LIBS+=$(SQLITE3_LIB)
 AST_LIBS+=$(ASTSSL_LIBS)
 AST_LIBS+=$(JANSSON_LIB)
+AST_LIBS+=$(URIPARSER_LIB)
 AST_LIBS+=$(UUID_LIB)
 AST_LIBS+=$(CRYPT_LIB)
 
@@ -155,6 +156,7 @@
 asterisk.o: _ASTCFLAGS+=$(LIBEDIT_INCLUDE)
 cli.o: _ASTCFLAGS+=$(LIBEDIT_INCLUDE)
 json.o: _ASTCFLAGS+=$(JANSSON_INCLUDE)
+bucket.o: _ASTCFLAGS+=$(URIPARSER_INCLUDE)
 crypt.o: _ASTCFLAGS+=$(CRYPT_INCLUDE)
 uuid.o: _ASTCFLAGS+=$(UUID_INCLUDE)
 

Modified: trunk/main/asterisk.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/asterisk.c?view=diff&rev=397600&r1=397599&r2=397600
==============================================================================
--- trunk/main/asterisk.c (original)
+++ trunk/main/asterisk.c Fri Aug 23 16:49:47 2013
@@ -241,6 +241,7 @@
 #include "asterisk/aoc.h"
 #include "asterisk/uuid.h"
 #include "asterisk/sorcery.h"
+#include "asterisk/bucket.h"
 #include "asterisk/stasis.h"
 #include "asterisk/json.h"
 #include "asterisk/stasis_endpoints.h"
@@ -4187,6 +4188,11 @@
 
 	aco_init();
 
+	if (ast_bucket_init()) {
+		printf("%s", term_quit());
+		exit(1);
+	}
+
 	if (stasis_init()) {
 		printf("Stasis initialization failed.\n%s", term_quit());
 		exit(1);

Added: trunk/main/bucket.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/bucket.c?view=auto&rev=397600
==============================================================================
--- trunk/main/bucket.c (added)
+++ trunk/main/bucket.c Fri Aug 23 16:49:47 2013
@@ -1,0 +1,963 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp at 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 at 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)
+{

[... 1097 lines stripped ...]



More information about the asterisk-commits mailing list